Pregunta:
En un ejecutable Mach-O, ¿cómo puedo encontrar a qué función se dirige un stub?
zneak
2015-02-05 22:25:43 UTC
view on stackexchange narkive permalink

Antes de exponer mi problema, aquí está mi comprensión de todo el asunto, para que pueda corregirme si digo algo incorrecto.

En un archivo Mach-O (al menos en x86), la sección __TEXT .__ stubs normalmente tiene stubs que consisten en un solo salto indirecto, como este:

 ; __TEXT .__ stubs; símbolo stub para desvincular: 0x100000f46: jmpq * 0xc4 (% rip); símbolo stub para put: 0x100000f4c: jmpq * 0xc6 (% rip)  

Estos apuntan a una ubicación dentro de la sección __DATA .__ nl_symbol_ptr . El puntero va inicialmente a un auxiliar auxiliar en la sección __TEXT .__ stub_helper :

 ; __TEXT .__ stub_helper; auxiliar de código auxiliar para unlink0x100000f64: pushq $ 0x00x100000f69: jmp 0x100000f54; auxiliar de código auxiliar para put0x100000f6e: pushq $ 0xe0x100000f73: jmp 0x100000f54  

El auxiliar de código auxiliar llama a dyld_stub_binder , que usa el argumento insertado para averiguar qué código auxiliar es y qué función necesita buscar, luego reemplaza el valor en __DATA .__ nl_symbol_ptr con la dirección resuelta, y luego entregar el control a la función que se encontró (que luego regresa al código de llamada normalmente).

Para ayudar a la depuración, los depuradores encuentran códigos auxiliares y fingen tener símbolos para ellos. En este programa de ejemplo, siempre que lldb ve call 0x100000f58 , determina que el stub debe apuntar a unlink , y dice call 0x100000f58; símbolo stub para: unlink en el desmontaje.

Sin embargo, lldb no usa el valor insertado: parece depender de que el enlazador estático coloque símbolos y stubs indefinidos en el mismo orden, o alguna variante de eso. Simplemente así, parece más una heurística que una forma precisa de averiguar qué stub va y dónde, a menos que haya algo más que le impida manipular.

Entonces, ¿cómo puedo encontrar de manera confiable qué función es llamada por un código auxiliar? En los auxiliares de stub, ¿qué significan las constantes en pushq $ constant ?

Dos respuestas:
#1
+4
Igor Skochinsky
2015-02-06 21:27:49 UTC
view on stackexchange narkive permalink

el argumento numérico es un desplazamiento en el flujo de códigos de bytes de "información dyld comprimida". consulte https://stackoverflow.com/a/8836580 (iOS / arm pero aún se aplica)

Como esta pregunta se ha visto más de 500 veces, aquí hay una [implementación de un decodificador de bytecode] (https://github.com/zneak/fcd/blob/b29b4ac/scripts/macho.py) que escribí no hace mucho.
@zneak: agradable, ¿tal vez puedas escribir una explicación más detallada como respuesta para que otras personas conozcan los detalles? No me importa si no acepta mi respuesta.
#2
+4
zneak
2016-11-29 07:05:55 UTC
view on stackexchange narkive permalink

Yo escribí un script de Python que analiza los puntos de entrada e importa desde un ejecutable de Mach-O para uno de mis proyectos. El truco consiste en analizar los comandos del cargador LC_DYLD o LC_DYLD_ONLY . Estos dos comandos codifican tres tablas de importación: símbolos enlazados, símbolos débiles y símbolos perezosos.

  struct dyld_info_command {uint32_t cmd; uint32_t cmdsize; uint32_t rebase_off; uint32_t rebase_size; uint32_t bind_off; uint32_t bind_size; uint32_t debil_bind_off; uint32_t tamaño_de_vinculación débil; uint32_t lazy_bind_off; uint32_t lazy_bind_size; uint32_t export_off; uint32_t export_size;};  

Los campos interesantes son bind_off , bind_size , debil_bind_off , tamaño_de_vinculación débil , lazy_bind_off y tamaño_bind_lazy . Cada par codifica el desplazamiento y el tamaño de un bloque de datos, dentro del archivo ejecutable, que contiene los códigos de operación de la tabla de importación.

Se puede considerar que cada una de estas tablas tiene cuatro columnas (útiles): el segmento, desplazamiento del segmento, nombre de la biblioteca y nombre del símbolo. Juntos, el segmento y el desplazamiento del segmento indican la dirección donde se escribirá la dirección real del símbolo (por ejemplo, si tiene __TEXT y 0x40, esto significa conceptualmente que * (__ TEXT + 0x40 ) == resolveSymbolAddress ).

La tabla está codificada como un flujo de códigos de operación con fines de compresión. Los códigos de operación controlan una máquina de estado que contiene el estado de un posible símbolo, tiene operaciones para manipular ese estado y operaciones para "vincular" un símbolo (tomar todo ese estado y convertirlo en parte de la tabla de símbolos). Por ejemplo, puede ver:

  • establecer segmento en __TEXT
  • establecer desplazamiento en 0x40
  • establecer biblioteca en libSystem.dylib
  • establecer el nombre del símbolo en "printf"
  • enlazar símbolo
  • establecer el desplazamiento en 0x48
  • establecer el nombre del símbolo en "scanf"
  • símbolo de enlace

Al final de esta secuencia, obtiene dos símbolos: printf y scanf, cuyas direcciones están ubicadas en __TEXT + 0x40 y __TEXT + 0x48 respectivamente, de libSystem.dylib. Esto significa que si ve una instrucción como jmp [__TEXT + 0x48] (un salto indirecto a la dirección contenida en __TEXT + 0x48 ), sabrá que va a scanf . Así es como puede saber el destino de los stubs.

Cada código de operación tiene al menos 1 byte, separado como 0xCI (donde C es el nombre del comando e I es un valor inmediato, ambos de 4 bits). Cuando el comando necesita un operando más grande (o más operandos), se codifican en formato ULEB-128 (excepto para BIND_OPCODE_SET_ADDEND_SLEB , que usa LEB firmado, pero no realmente me importa con el propósito de encontrar importaciones).

  def readUleb (data, offset): byte = ord (data [offset]) offset + = 1 result = byte & 0x7f shift = 7 while byte & 0x80: byte = ord (data [offset]) result | = (byte & 0x7f) << shift shift + = 7 offset + = 1 return (result, offset)  

En realidad, las bibliotecas no se identifican por sus nombres en el flujo de comandos. Más bien, las bibliotecas se identifican por su "ordinal de biblioteca" basado en uno , que es solo el índice de la biblioteca dentro de todos los LC_LOAD_DYLIB , LC_LOAD_WEAK_DYLIB , comandos del cargador LC_REEXPORT_DYLIB y LC_LOAD_UPWARD_DYLIB . Por ejemplo, si un ejecutable tiene un comando LC_LOAD_DYLIB para libSystem y luego uno para libFoobar, libSystem tiene ordinal 1 y libFoobar tiene ordinal 2.

Hay tres valores especiales: ordinal -2 significa que el símbolo se busca en el espacio de nombres plano (gana la primera biblioteca con un símbolo con ese nombre); ordinal -1 busca un símbolo en el ejecutable principal, cualquiera que sea; y el ordinal 0 busca un símbolo dentro de este archivo. Como dijimos anteriormente, ordinal 1 y superior se refieren a bibliotecas.

Los nombres de los símbolos se codifican dentro del comando blob como cadenas terminadas en nulo.

Cada código de operación se describe fácilmente en el código , así que nos ahorraré la descripción de cada uno.

  BIND_OPCODE_DONE = 0BIND_OPCODE_SET_DYLIB_ORDINAL_IMM = 1BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB = 2BIND_OPCODE_SET_DYLIB_SPECIAL_IMM = 3BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM = 4BIND_OPCODE_SET_TYPE_IMM = 5BIND_OPCODE_SET_ADDEND_SLEB = 6BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 7BIND_OPCODE_ADD_ADDR_ULEB = 8BIND_OPCODE_DO_BIND = 9BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB = 10BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED = 11BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB = 12def parseImports (self, offset, tamaño): pointerWidth = Auto. bitness / 8 slice = self.data [offset: offset + size] índice = 0 nombre = "" segmento = 0 segmentoOffset = 0 libOrdinal = 0 stubs = [] def addStub (): stubs.append ((segmento, segmentoOffset, libOrdinal , nombre)) while index! = len (slice): byte = ord (slice [index]) opcode = byte >> 4 inmediato = byte & 0xf index + = 1 if opcode == BIND_OPCODE_DONE: pass el Si código de operación == BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: libOrdinal = código de operación elif inmediata == BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: libOrdinal, índice = self .__ readUleb (rebanada, índice) elif código de operación == BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: libOrdinal = -immediate elif código de operación == BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: nameEnd = slice.find (" \ 0 ", índice) nombre = segmento [índice: nombreEnd] índice = nombreEnd
elif opcode == BIND_OPCODE_SET_TYPE_IMM: pass elif opcode == BIND_OPCODE_SET_ADDEND_SLEB: _, index = self .__ readUleb (sector, índice) elif opcode == BIND_OPCODE_SET_SEGMENT_AND_OFFSETU_ULEB: segmento BIND_OPCODE_ADD_ADDR_ULEB: sumando, índice = self .__ readUleb (segmento, índice) segmentoOffset + = sumando elif código de operación == BIND_OPCODE_DO_BIND: addStub () elif código de operación == BIND_OPCODE_DO_BIND_ADD_de_DDRUleb (segmento) = sumar el código de operación elif == BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: addStub () segmentoOffset + = inmediato * pointerWidth elif código de operación == BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: tiempos, índice de rebanada__ lectura, índice de lectura .__ , índice) para i en el rango (tiempos): addStub () segmentoOffset + = pointerWidth + omitir else: sys.stderr.write ("advertencia: código de operación de enlace desconocido% u, inmediato% u \ n"% (código de operación, inmediato))  
Una adición: la lista de bibliotecas incluye no solo los comandos `LC_LOAD_DYLIB`, sino también` LC_LOAD_WEAK_DYLIB`, `LC_REEXPORT_DYLIB`,` LC_LAZY_LOAD_DYLIB` y `LC_LOAD_UPWARD_DYLIB` (ver fuentes dyld)
Además, acabo de encontrar esta página: http://newosxbook.com/articles/DYLD.html
Lo investigaré más tarde y actualizaré el script / respuesta. ¡Gracias!


Esta pregunta y respuesta fue traducida automáticamente del idioma inglés.El contenido original está disponible en stackexchange, a quien agradecemos la licencia cc by-sa 3.0 bajo la que se distribuye.
Loading...