Phase 13: Tools & Protocols

Enlaces, cargas y binarios desnudos: PLT, GOT, importaciones, fijaciones e identificación de funciones

Un programa vinculado llama a funciones que no contiene. Esta lección lee las tablas que el editor de enlaces escribió para esas llamadas en tres formatos, sigue una llamada a la ranura que llena el cargador y encuentra las funciones de una construcción después de que se hayan eliminado sus nombres.

Type: Build

Languages: Python, C, x86-64 and arm64 assembly

Prerequisites: Phase 13 · 32 (ELF from scratch), 33 (disassembly and control flow), 38 (calling conventions)

Time: ~160 minutes

Objetivos de aprendizaje

  • Decida de un registro de reubicación y de las tablas de símbolos lo que el editor de enlaces haga de él: una distancia fija, una entrada PLT con un espacio GOT o una reubicación dinámica.
  • Lea la sección dinámica, el PLT y el GOT de un ejecutable ELF independiente de posición y siga una llamada a través de ellos con las fórmulas psABI.
  • Lea un objeto COFF (20 bytes de encabezado de archivo, entradas de sección de 40 bytes, símbolos de 18 bytes, relocalizaciones de 10 bytes) y los directorios de importación, exportación y relocalización de base de una imagen PE32+.
  • Descifrar una cadena de corrección en cadena Mach-O palabra por palabra y combinar cada palabra contradyld_info¿ Qué ?
  • ¿ Qué es lo que dice ?stripelimina y lo que sobrevive en cada formato, y ejecuta el programa despojado para demostrar que todavía funciona.
  • Recupera la función inicia de una construcción de arm64 despojada con LC_FUNCTION_STARTS, explora el prólogo y llama a objetivos, marca cada método en la tabla de símbolos del gemelo sin cortar, y nombra las funciones con una firma en forma de FLIRT.

El problema

Usted se conectómain.ccontralibledger.dylibY preguntó a un agente cuál es su función.mainllamadas en0x1000005a0El agente desmontó la instrucción y leyó .bl 0x100000694Esa dirección contiene doce bytes:adrp x16¿ Qué ?ldr x16, [x16, #0x38]¿ Qué ?br x16El agente siguió la carga hasta ...0x100004038, lee los ocho bytes 00 00 00 00 00 00 10 80, y reportó una llamada a través de un puntero corrupto en el espacio del núcleo.

Luego despojaste de la construcción estática del mismo programa y preguntaste cuántas funciones tiene.nm, vio un nombre,__mh_execute_header, y respondió uno.

Ambas respuestas leen el archivo como si fuera el proceso de ejecución.0x100004038Bit 63 dice "bind", 12 bits dicen dónde está la próxima fijación, y los bits bajos dicen qué importar para unir._putsantes demainEl archivo despojado perdió su tabla de símbolos y se mantuvoLC_FUNCTION_STARTS, que enumera las cinco funciones comienza en nueve bytes.

Esta lección construye cinco lectores en Python de biblioteca estándar. Un lector COFF para nuestros propios objetos y un lector PE32+ para los directorios de importación, exportación y reubicación de base. Un lector Mach-O para comandos de carga, estubes, la tabla de símbolos indirectos, la cadena de fijación en cadena y el trie de exportación, verificado palabra por palabra contra dyld_info. Un lector ELF para la sección dinámica, el PLT y el GOT, más un planificador que dice qué crearía un enlace PIE de nuestros objetos. Un buscador de funciones para una construcción despojada con cuatro métodos y una puntuación para cada uno. Un constructor de firma en forma de FLIRT, con bytes de reubicación con wildcard y un CRC16 de la cola.

Sólo Mach-O enlaces en esta máquina. los ejecutables arm64 y el dylib se conectaron, desnudos, y ejecutados. los objetos ELF y COFF compilado aquí conclang -target, y no se instala ningún editor de enlace ELF o PE, por lo que las dos imágenes vinculadas en esos formatos fueron escritas byte por byte a partir de las especificaciones por dos scripts Python en la carpeta de fijos. llvm-objdumpNo se han ejecutado, y el texto dice así donde importa.

Lo que hace el editor de enlaces con una reubicación

Lección 32 leer registros de reubicación y la lección 33 aplicada S + A - Pa una leaUn registro nombra un lugar .P, un tipo, un símbolo S, y un añadido A. La tabla 4.9 de la x86-64 psABI da una fórmula por tipo, con Lpara el lugar de la entrada PLT del símbolo, Gpor su compensación de GOT,GOTpara la dirección GOT, y BTres resultados cubren nuestros objetos.

El editor de enlace escribe el valor en el campo y el registro se ha ido. R_X86_64_PC32En el caso de las imágenes, la distancia entre dos bytes de una imagen nunca cambia, sea cual sea la dirección de carga.

El editor de enlaces hace una entrada PLT y una ranura GOT. R_X86_64_PLT32En este caso, la llamada se haceL + A - PLa entrada salta a través de la ranura.R_X86_64_JUMP_SLOTregistro en .rela.pltdice al enlace dinámico que llene el espacio.

El editor de enlaces mantiene una reubicación para el cargador. R_X86_64_64es una dirección absoluta de ocho bytes, y un ejecutable independiente de posición no sabe su dirección hasta que se carga.R_X86_64_RELATIVEcon fórmula B + AEn el.rela.dyn¿ Qué ?

main-x86_64-O1.otiene 18 registros: 14 en .text, 2 en ..data.rel.ro, y 2 en .rodata- ¿ Qué ?plan_dynamic_link()En elcode/main.pyaplica las tres reglas a ellos junto con ledger-x86_64-O1.o¿Qué es esto ?

RecordCountDecisionWhy
R_X86_64_PC32 to .rodata, .L.str, fns, the table in .rodata16in placethe symbol is defined in the inputs
R_X86_64_PLT32 to percent_change, grade, total, grade_label4direct, no PLTdefined in ledger-x86_64-O1.o, so the link editor reduces PLT32 to PC32
R_X86_64_PLT32 to puts twice and printf twice4PLT entry and GOT slotundefined, provided by a shared library
R_X86_64_64 to grade in fns[]2R_X86_64_RELATIVEan absolute field in a PIE

Dos importaciones dan un .pltde 48 bytes (PLT0 más dos entradas de 16), un .got.pltde 40 bytes (tres ranuras reservadas más dos), y un .rela.pltLos dos registros relativos dan un.rela.dynEstos números provienen de la especificación aplicada a nuestros registros.tiny-dyn-x86_64La Comisión ha adoptado el artículo 6 del Reglamento (CE) no 659/1999 de la Comisión, de conformidad con el artículo 6 del Reglamento (CE) no 659/1999.

El compilador escribe PLT32En cada llamada, definida o no definida, desde las binutils 2.31 (H.J. Lu, 2018-02-13) y LLVM 7.0.0 (D43383, 2018-02-20).PLT32En un objeto, por lo tanto, dice "una llamada", y no dice nada sobre si una entrada PLT existirá.R_X86_64_GOTPCRELXy R_X86_64_REX_GOTPCRELX(El artículoG + GOT + A - P) solicitar un espacio GOT sin PLT, que -fno-pltproducen y que el editor de enlaces puede relajar a una directleao callcuando el símbolo sea local (psABI apéndice B). R_X86_64_32y R_X86_64_32SNo puede contener una dirección en un PIE y el enlace falla.

La sección dinámica, el PLT y el GOT

El capítulo 5 de gABI dice que un ejecutable que participa en la vinculación dinámica tiene un PT_INTERPprograma, que debe preceder a cualquier segmento cargable, y una PT_DYNAMIC, que cubre el .dynamicLa sección es una matriz de Elf64_Dynpares, una etiqueta firmada y un valor, terminado por DT_NULL¿ Qué ?

tiny-dyn-x86_64es 5.184 bytes escritos por build_tiny_dyn_elf.pyLa sección dinámica de la misma contiene doce pares en la0x1000¿Qué es esto ?

TagNumberValueMeaning
DT_NEEDED1libc.so.6a string table offset, 0xb, the library to load
DT_HASH40x1a0the SysV hash table
DT_STRTAB50x188.dynstr
DT_SYMTAB60x140.dynsym, 24 bytes per entry
DT_STRSZ100x1521 bytes of strings
DT_SYMENT110x1824
DT_PLTGOT30x10c0.got.plt
DT_PLTRELSZ20x30two 24-byte records
DT_PLTREL207the records are DT_RELA
DT_JMPREL230x1b8.rela.plt
DT_FLAGS_10x6ffffffb0x08000000DF_1_PIE, a GNU tag
DT_NULL00end

El GABI dice por qué .DT_JMPRELSeparar estas entradas de reubicación permite que el enlace dinámico las ignore durante la inicialización del proceso, si se habilita la vinculación perezosa.DF_BIND_NOWEn elDT_FLAGS¿ Qué ?DF_1_NOWEn elDT_FLAGS_1, oLD_BIND_NOWen el entorno se apagan las fijaciones perezosas y el enlace dinámico las procesa en el inicio.

PLT0 y la entrada para los putos

psABI sección 5.2 da las dos formas en la figura 5.2. Aquí están los bytes de nuestro archivo:

text     1f0: ff 35 d2 0e 00 00    pushq  0xed2(%rip)      # 0x10c8 = GOT+8
     1f6: ff 25 d4 0e 00 00    jmpq   *0xed4(%rip)     # 0x10d0 = GOT+16
     1fc: 0f 1f 40 00          nopl   (%rax)

     200: ff 25 d2 0e 00 00    jmpq   *0xed2(%rip)     # 0x10d8 = GOT+24, the slot for puts
     206: 68 00 00 00 00       pushq  $0x0             # index 0 into DT_JMPREL
     20b: e9 e0 ff ff ff       jmp    0x1f0            # PLT0

Haga la aritmética una vez. El desplazamiento relativo al RIP es relativo a la siguiente instrucción. 0x1f6 + 0xed2 = 0x10c8, que es .got.plt + 8- ¿ Qué ?0x1fc + 0xed4 = 0x10d0, que es .got.plt + 16- ¿ Qué ?0x206 + 0xed2 = 0x10d8, la cuarta ranura.0x210 - 0x20 = 0x1f0Cada entrada es de 16 bytes, así que la entrada para exitcomienza en0x210, empuja$0x1, y lee0x10e0¿ Qué ?

El GOT reserva tres ranuras. psABI 5.2: la entrada cero tiene la dirección de _DYNAMIC, y "las entradas uno y dos en la tabla global de compensación también están reservadas"..got.pltLeía0x1000, 0, 0, 0x206, 0x216. glibc llena el espacio uno con su struct link_mapy la ranura dos con _dl_runtime_resolveCuando carga el archivo.0x206en el expediente, que es el pushqEn el interior .puts@plt. psABI: la ranura "initialemente ... contiene la dirección de la siguiente instrucción pushq".

Así que la primera llamada se ejecutacall 0x200¿ Qué ?jmpq *0x10d8las tierras enpushq $0¿ Qué ?jmp 0x1f0empuja GOT+8 y pasa a través de GOT+16 en el resolver, que lee el registro 0 de DT_JMPREL¿ Qué ?R_X86_64_JUMP_SLOT puts, pareceputsarriba enlibc.so.6, escribe su dirección en 0x10d8Y salta allí. La segunda llamada se ejecuta.call¿ Qué ?jmpq *0x10d8Drepper escribe que "el coste general para todos excepto la primera llamada es por lo tanto 'sólo' un salto indirecto".%r11explica una opción de registro en la lección 38: es un registro de rasguño por lo que "el código en el PLT no necesita verter ningún registro".

El sitio de llamadas en .textLeíae8 d4 ff ff ffEn el0x227, y 0x22c - 0x2c = 0x200- ¿ Qué ?llvm-objdumphuellascallq 0x200 <puts@plt>para el mismo y nombra la entrada de la .rela.pltregistro, que es cómo plt_entries()también lo nombrará.

Lo que el archivo no tiene

El archivo no tiene PT_GNU_RELRO. Jakub Jelinek propuso a RELRO en la lista de servicios binarios el 1 de enero de 2004: mover las secciones escritas sólo durante la reubicación al inicio del segmento de escritura, para que el enlace dinámico pueda mprotectEl script predeterminado de GNU ID pone.init_array¿ Qué ?.fini_array¿ Qué ?.data.rel.ro¿ Qué ?.dynamic, y .gotdentro, y sólo mantiene las tres ranuras reservadas de .got.pltdentro, 24 bytes, porque las otras ranuras están escritas con pereza.-z nowconjuntos DF_BIND_NOWy mueve todo..got.pltdentro, RELRO completo, porque nada se escribe después de la puesta en marcha._dl_protect_relrollamadasmprotect(start, end - start, PROT_READ)en el rango alineado con las páginas. -fno-pltva un paso más allá en el lado del compilador: las llamadas pasan jmpq *name@GOTPCREL(%rip), lazosos es imposible, y el manual del CCG dice que "todos los símbolos externos se resuelven en el momento de carga".

El archivo tampoco tiene secciones de versiones.libc.so.6añade .gnu.version, uno Elfxx_Halfpor .dynsymla entrada, y .gnu.version_r, una Verneedpara cada biblioteca con Vernauxregistros para cada nombre de versión. printf@GLIBC_2.2.5medios: la entrada de printfEn el.gnu.versiontiene un índice, el Vernauxcuyo vna_otheres igual a que el índice tiene vna_name"GLIBC_2.2.5", en el apartado Verneedcuyo vn_fileEl índice 0 es local, 1 es global y el bit 15 (VERSYM_HIDDEN¿ Qué ?0x8000) oculta una versión del editor de enlaces.

La tabla de hash es un SysV DT_HASHcon un cubo y una cadena de tres entradas, 24 bytes.DT_HASHo DT_GNU_HASHEste archivo no exporta nada, y no se ejecuta aquí, por lo que si Glibc acepta una tabla de un balde no se verifica.

.dynsym, .symtab y strip

El GABI hace.dynsymy .dynstrsecciones asignadas y .symtaby .strtabEl enlace dinámico lee el primer par y nunca el segundo. Drepper: "La tabla de símbolos normal no se utiliza en el tiempo de ejecución y, por lo tanto, no es necesario deshacerse de un binario. No tiene impacto en el rendimiento".

GNU strip --strip-allse elimina .symtab¿ Qué ?.strtab¿ Qué ?.debug_*, y .commentCada sección asignada se mantiene:.interp¿ Qué ?.dynsym¿ Qué ?.dynstr¿ Qué ?.gnu.hash¿ Qué ?.gnu.version*¿ Qué ?.rela.dyn¿ Qué ?.rela.plt¿ Qué ?.plt¿ Qué ?.text¿ Qué ?.rodata¿ Qué ?.eh_frame¿ Qué ?.init_array¿ Qué ?.dynamic¿ Qué ?.got¿ Qué ?.got.plt¿ Qué ?.data¿ Qué ?.bssLos cabezas de sección se quedan a menos que agreguen--strip-section-headers(artículos 2.41) o ejecutar llvm-strip --strip-sections- ¿ Qué ?--strip-debugconserva los símbolos.--only-keep-debugescribe el archivo de depuración que --add-gnu-debuglinknombres de la desnudada, con un CRC32 en .gnu_debuglink, y ld --build-idescribe .note.gnu.build-idAsí que un depurador puede coincidir con los dos.

El objeto de la COFF

clang -target x86_64-pc-windows-msvc -O1 -fno-asynchronous-unwind-tables -cproducido main-x86_64.objy ledger-x86_64.obj. La página de formato PE define el objeto: un encabezado de archivo de 20 bytes, sin encabezado opcional, una tabla de sección de entradas de 40 bytes, una tabla de símbolos de registros de 18 bytes y registros de reubicación de 10 bytes por sección.

CoffObjectEn elcode/main.pyEl encabezado del archivo dice máquina.0x8664, 10 secciones, 37 registros de símbolos,SizeOfOptionalHeader0. Los nombres de las tablas de las secciones .text¿ Qué ?.data¿ Qué ?.bss, seis secciones llamadas.rdata, y /16Un nombre que comienza con/es una desviación decimal en la tabla de cuerdas, que comienza con su propio tamaño de cuatro bytes; /16se resuelve a .llvm_addrsig, que es el nombre llvm-objdump -hLas huellas.

Los 37 registros incluyen registros auxiliares. Un símbolo de sección (clase de almacenamiento 3) contiene uno que contiene la longitud de la sección, el recuento de reubicación, una suma de comprobación y una selección COMDAT..rdatalas secciones tienen la selección 2, "cualquier", y contienen una cadena literal cada uno bajo un nombre MSVC-malgradado: ??_C@_04JDDJEAHN@even?$AA@El editor de enlaces guarda una copia de cada uno..filecon el registro auxiliar "main.c". llvm-objdump -tImprima esto como [ 0]¿ Qué ?AUX scnlen ...¿ Qué ?[ 2], y así sucesivamente, saltando los índices auxiliares, y la prueba compara nuestro índice y pares de nombres con ese texto.

Las reubicaciones: 14 IMAGE_REL_AMD64_REL32En el.texty 4 IMAGE_REL_AMD64_ADDR64En el.rdata- ¿ Qué ?REL32es "la dirección relativa de 32 bits del byte después de la reubicación".R_X86_64_PC32con el -4incorporado: el registro ELF contiene un complemento -4porque el registro tiene un campo de adjunto y el registro COFF no tiene ninguno. ledger-x86_64.objtiene tres REL32En el0x36¿ Qué ?0x84, y 0x93, y la lección 32's ledger-x86_64-O1.oTiene sus tres .R_X86_64_PC32El código es el mismo y los formatos difieren sólo en el lugar donde se escriben los cuatro. ADDR64es una dirección absoluta de ocho bytes, el labelstabla de los punteros de cuerdas, y se convierte en una reubicación de base en la imagen, como R_X86_64_64Se convierte enR_X86_64_RELATIVE¿ Qué ?

ELF Elf64_RelaCOFF relocationMach-O relocation_info
record size24 bytes10 bytes8 bytes
fieldsoffset, type and symbol in r_info, addendoffset, symbol index, typeaddress, then a 32-bit word with symbolnum:24, pcrel:1, length:2, extern:1, type:4
addendexplicitnone, lives in the fieldnone, or an ARM64_RELOC_ADDEND record before the real one
the call to putsR_X86_64_PLT32 puts-0x4IMAGE_REL_AMD64_REL32 putsARM64_RELOC_BRANCH26 _puts

La imagen del PE: importaciones, exportaciones y traslados de base

tiny-x86_64.exees 2,560 bytes escritos por build_tiny_pe.pyTiene el mismo trabajo que el pequeño ELF:putsy exitde lamsvcrt.dllTambién exporta un nombre y lleva una reubicación de base, por lo que los tres directorios que necesita esta lección están todos presentes. pefile2024.8.26 y llvm-objdump -pLos dos lo leen, y ambas capturas son accesorios.

El tema de MS-DOS comienza conMZy en compensación 0x3cTienee_lfanew¿ Qué ?0x40- En el0x40La firma PE\0\0Luego el encabezado del archivo COFF: máquina 0x8664, 4 secciones, SizeOfOptionalHeader240, características 0x22El encabezado opcional comienza con el Magiccampo, 0x20b, lo que significa PE32+. AddressOfEntryPoint¿ Es verdad ?0x1000¿ Qué ?ImageBase¿ Es verdad ?0x140000000¿ Qué ?SectionAlignment 0x1000¿ Qué ?FileAlignment 0x200¿ Qué ?SizeOfImage 0x5000¿ Qué ?Subsystem3 (consola). DllCharacteristics 0x8160decodificaciones de HIGH_ENTROPY_VA 0x0020¿ Qué ?DYNAMIC_BASE 0x0040¿ Qué ?NX_COMPAT 0x0100, y TERMINAL_SERVER_AWARE 0x8000. Luego 16 directorios de datos de RVA y tamaño.0x2080tamaño 0x4d, Importar en0x2000tamaño 0x28, Traslado de la Base en 0x4000tamaño 0xc, IAT en0x2040tamaño 0x18¿ Qué ?

Las direcciones en una imagen son RVAs, compensaciones desde la base de carga. La tabla de sección las mapea a los desplazamientos de archivo: .textRVA 0x1000en el archivo 0x200¿ Qué ?.rdataRVA 0x2000En el0x400¿ Qué ?.dataRVA 0x3000En el0x600¿ Qué ?.relocRVA 0x4000En el0x800- ¿ Qué ?PeImage.rva_to_offset()Es esa mesa.

El directorio de importaciones

En RVA 0x2000Se encuentra una matriz de descriptores de importación de 20 bytes terminados por un descriptor cero.0x2028, timestamp 0, cadena del remitente 0, nombre 0x2068, Tabla de direcciones de importación 0x2040. El nombre RVA apunta a "msvcrt.dll". La tabla de búsqueda contiene entradas de 64 bits terminadas con cero.0x2058(indicación 0, "puts") y 0x2060(indicación 0, "salida").

La especificación dice que el IAT es "identico al contenido de la tabla de búsqueda de importación hasta que la imagen está ligada".0x2040tiene las mismas dos RVAs, y la prueba verifica que los 24 bytes en 0x2028igual a los 24 bytes en 0x2040. "Durante la unión, las entradas en la tabla de direcciones de importación se sobrescriben con las direcciones de 32 bits (para PE32) o 64 bits (para PE32+) de los símbolos que se están importando". El cargador de Windows lo hace antes de que el punto de entrada se ejecute. No hay unión perezosa y no hay tritón. El código llega directamente a la ranura:

text    100b: ff 15 2f 10 00 00    call qword ptr [rip + 0x102f]   # 0x1011 + 0x102f = 0x2040, the IAT slot of puts
    1013: ff 15 2f 10 00 00    call qword ptr [rip + 0x102f]   # 0x1019 + 0x102f = 0x2048, the IAT slot of exit

FF /2Las dos instrucciones tienen los mismos bytes porque cada una se sienta ocho bytes después de la otra y lee una ranura ocho bytes después de la otra. iat_calls()escanear la sección de código para ff 15, añade seis y el desplazamiento, y nombra la ranura del directorio de importación.

El directorio de exportación

En RVA 0x2080la tabla de directorio de exportación de 40 bytes: banderas, timestamp, versión, un nombre RVA que apunta a "tiny-x86_64.exe", base ordinal 1, entrada de la tabla de direcciones 1, indicador de nombre.0x20a8tiene una RVA, 0x1000. La tabla de indicadores de nombres de exportación en 0x20acEl número de entradas de exportación ordinaria se encuentra en la tabla de la cadena "tiny_entry".0x20b0tiene un índice imparcial de 16 bits, 0. Nombre kmapas para la dirección de la entrada ordinals[k], y el ordenal exportado es ordinals[k] + ordinal base. La especificación añade una regla que no utilizamos: una entrada de dirección que apunta dentro de la sección de exportación es una cadena de remitente como MYDLL.expfunc, y la tabla de indicadores de nombre está "ordenada léxicamente para permitir búsquedas binarias".

Traslados de bases

.relocCada bloque comienza con una página RVA y un tamaño de bloque, luego entradas de 16 bits con el tipo en los cuatro bits superiores y una página compensada en la doce inferior. Nuestro bloque: página 0x3000, tamaño 12, entrada 0xa000, tipo 10, IMAGE_REL_BASED_DIR64, compensación 0, y una entrada de relleno de tipo 0, IMAGE_REL_BASED_ABSOLUTELos ocho bytes en RVA0x3000¿ Qué ?0x1400020d0, la base de imagen más el RVA de la cadena "enlazada a mano". Si el cargador coloca la imagen en otro lugar, añade la diferencia a ese campo. La especificación: "Los archivos de imagen no contienen relocaciones COFF ... Una imagen contiene información de reubicación en forma de relocaciones de base en la sección .reloc (a menos que la imagen tenga el atributo IMAGE_FILE_RELOCS_STRIPPED). " /DYNAMICBASEestá activado por defecto en el link.exe actual, por lo que las imágenes llevan .relocy DYNAMIC_BASE¿ Qué ?

Una imagen PE no tiene tabla de símbolos para desprender.PointerToSymbolTable"debería ser cero para una imagen porque la información de depuración de COFF está obsoleta", y los nombres viven en un PDB separado. Lo que sobrevive en cada imagen es el directorio de importación, el directorio de exportación y, para el código de 64 bits,.pdata, cuyo RUNTIME_FUNCTIONLas entradas nombran el comienzo y el final de cada función que asigna una pila o llama a otra función.

La imagen Mach-O: comandos de carga, estampas y la tabla de símbolos indirectos

ledger_maines el arm64 ejecutable vinculado. otool -hImprima 19 comandos de carga y banderas 0x00200085- ¿ Qué ?MH_NOUNDEFS 0x1¿ Qué ?MH_DYLDLINK 0x4¿ Qué ?MH_TWOLEVEL 0x80, y MH_PIE 0x200000Los 19 comandos son cuatro.LC_SEGMENT_64(El artículo__PAGEZERO¿ Qué ?__TEXT¿ Qué ?__DATA_CONST¿ Qué ?__LINKEDIT), dos LC_LOAD_DYLIB(El artículo@rpath/libledger.dylibEntonces .../usr/lib/libSystem.B.dylib), y uno de cada uno LC_DYLD_CHAINED_FIXUPS¿ Qué ?LC_DYLD_EXPORTS_TRIE¿ Qué ?LC_SYMTAB¿ Qué ?LC_DYSYMTAB¿ Qué ?LC_LOAD_DYLINKER¿ Qué ?LC_UUID¿ Qué ?LC_BUILD_VERSION¿ Qué ?LC_SOURCE_VERSION¿ Qué ?LC_MAIN¿ Qué ?LC_RPATH¿ Qué ?LC_FUNCTION_STARTS¿ Qué ?LC_DATA_IN_CODE, y LC_CODE_SIGNATURE- ¿ Qué ?MachoFile.command_names()devuelve la misma lista en el mismo orden que otool -l¿ Qué ?

__TEXTcomienza en0x100000000- ¿ Qué ?__textes 256 bytes en 0x100000558¿ Qué ?mainy nada más, porque las cuatro funciones del libro mayor viven en el Dylib. __stubses 84 bytes en 0x100000658, siete estubes de doce bytes.__DATA_CONSTcomienza en0x100004000con__got, 64 bytes, ocho ranuras de punteros de tipo sección S_NON_LAZY_SYMBOL_POINTERS, y __const, 32 bytes, el fns[]y names[]tablas de main.c¿ Qué ?

Un palo, a mano

El estubrio para _puts¿ Qué es ?0x100000694¿Qué es esto ?

text    30 00 00 90    adrp x16, 0x100004000
    10 1e 40 f9    ldr  x16, [x16, #0x38]
    00 02 1f d6    br   x16

0x90000030¿ Es verdad ?adrpEl registro es el bajo de cinco bits, 16.immloes bits 30 a 29, 0. immhies 23 bits a 5, 1. El valor es immhi << 2 | immlo = 4, en páginas de 4,096 bytes, por lo que la página de destino es la página de la instrucción, 0x100000000, más 0x4000- ¿ Qué ?0xf9401e10¿ Es verdad ?ldrcon un desafío sin firmar de 12 bits escalado por 8: bits 21 a 10 son 7, veces 8 es 0x38, así que la carga se lee0x100004038- ¿ Qué ?0xd61f0200¿ Es verdad ?br x16El arm64 en la fuente de ID64 es exactamente esta instrucción de tres.StubAtom¿ Qué ?

El nombre que pertenece a la ranura proviene de la tabla de símbolos indirectos. LC_DYSYMTABLos puntos de 15 índices en la tabla de símbolos.reserved1donde comienza su ciclo de índices y enreserved2¿Cuánto mide cada palo?reserved1y ocho bytes por ranura. __stubscomienza en el índice 0 y __goten el índice 7, por lo que rebote 5 mapas a la entrada de la tabla de símbolos 10, _puts, y la ranura 7 de __gotmapas de la misma entrada. otool -IvImprenta ambas corrientes, y indirect_table()reproducir cada fila.

Espacio de nombres de dos niveles

nm -mhuellas(undefined) external _puts (from libSystem)y (undefined) external _grade (from libledger)La biblioteca proviene del registro de símbolos.nlist.hdefine GET_LIBRARY_ORDINAL(n_desc)como bits de 15 a 8 de n_desc, 1 por la primera LC_LOAD_DYLIB, 2 por el segundo, 0xfepara la búsqueda dinámica, 0xffpara el ejecutable. ld(1): "Con -two_levelnamespace (el predeterminado), el enlace solo busca los dilibes en la línea de comandos para los símbolos y registros en los que se encontraron dilibes". nm_lines()Render los doce discos de n_type¿ Qué ?n_sect¿ Qué ?n_desc, y n_value, y el ensayo compara su línea de salida por línea con la capturada nm -m. Reconectarse con -Wl,-flat_namespacey leer .otool -hpara ver el MH_TWOLEVELUn poco desaparecer.

Correcciones en cadena, byte por byte

LC_DYLD_CHAINED_FIXUPSes un linkedit_data_command¿ Qué es esto ?dataoff 0x8000¿ Qué ?datasize 208Los 208 bytes siguen .fixup-chains.h¿ Qué ?

El encabezado es de siete campos de 32 bits: fixups_version 0¿ Qué ?starts_offset 32¿ Qué ?imports_offset 80¿ Qué ?symbols_offset 112¿ Qué ?imports_count 8¿ Qué ?imports_format 1(El artículoDYLD_CHAINED_IMPORT), symbols_format 0(no comprimido). llvm-objdump --macho --chained-fixupsImprime estos siete valores y la prueba los compara.

En el offset 32, dyld_chained_starts_in_image¿ Qué es esto ?seg_count 4y cuatro compensaciones, una por segmento, cero para segmentos sin correcciones. Sólo __DATA_CONSTTiene uno.dyld_chained_starts_in_segmentLeíasize 24¿ Qué ?page_size 0x4000¿ Qué ?pointer_format 6(El artículoDYLD_CHAINED_PTR_64_OFFSET, "el objetivo es vm compensado"), segment_offset 0x4000¿ Qué ?max_valid_pointer 0¿ Qué ?page_count 1, y uno page_startde 0. DYLD_CHAINED_PTR_START_NONE¿ Qué ?0xffff, significaría una página sin problemas.

La cadena comienza en el archivo de compensación 0x4000 + 0Cada palabra de 64 bits lleva dos cosas: qué escribir allí y dónde está la siguiente palabra.

text  dyld_chained_ptr_64_bind    ordinal:24  addend:8  reserved:19  next:12  bind:1 = 1
  dyld_chained_ptr_64_rebase  target:36   high8:8   reserved:7   next:12  bind:1 = 0

Lea la primera palabra,0x8010000000000000- Bit 63 está listo, un enlace.nextes bits 62 a 51, 0x002, en unidades de 4 bytes, así que la siguiente corrección es 8 bytes en.0x4058¿ Qué ?0x00000000000006db- 63 puntos libres, una rebasada.nextEs 0, el final de la cadena.0x6db, un vm de compensación, y la base de imagen 0x100000000¿ Qué hace ?0x1000006db, la cadena "ocio" en __cstringLa palabra antes de ella,0x00100000000006d6, rebajas names[0]a "incluso".

El paseo visita doce palabras: ocho se une en __gotcon ordenal 0 a 7, dos enlaces en __constcon ordenal 0 (ambas entradas de fns[]punto en _grade), y las dos rebases de names[]- ¿ Qué ?chained_fixups()los devuelve con sus ficheros de compensación y palabras crudas, y la prueba compara los doce (offset, raw)parejas con dyld_info -fixup_chain_details, que imprime raw: 0x8010000000000000 bind: (next: 002, bindOrdinal: 0x000000)Por primera vez.

La tabla de importaciones en el offset 80 tiene ocho entradas de 32 bits: lib_ordinal:8¿ Qué ?weak_import:1¿ Qué ?name_offset:23, con nombres en el descalzo 112._grade¿ Qué ?_grade_label¿ Qué ?_percent_change¿ Qué ?_totalde la biblioteca 1, y de 4 a 7 son ___stack_chk_fail¿ Qué ?___stack_chk_guard¿ Qué ?_printf¿ Qué ?_putsde la biblioteca 2. fixup_rows()se une las dos tablas en las líneas dyld_info -fixupshuellas, como __DATA_CONST __got 0x100004038 bind libSystem/_puts, y la prueba compara a los doce.

Así que el "puntero corrupto" en 0x100004038es importar 7. dyld camina esta cadena una vez, en carga, y escribe la dirección de _putsEn el ranura. La sesión WWDC22 de Apple lo expresó de esta manera: "en la ubicación del puntero de 64 bits en DATA, algunos de los bits contienen el desplazamiento a la próxima ubicación de fijación. También está un bit que dice si la fijación es un bind o una rebase". Xcode 13 hizo que el formato fuera el predeterminado para un objetivo de implementación de macOS 12 o iOS 15, y Xcode 13.3 lo extendió a macOS 11 e iOS 13.4. No hay un enlace perezoso en este formato. Cada ranura está ligada antes .main- ¿Qué?

El diseño que reemplazó

ledger_main_nochaines el mismo vínculo con -Wl,-no_fixup_chains- Sí , lo ha hecho .LC_DYLD_INFO_ONLYEn cambio, con cuatro tablas codificadas en byte: rebase 8 bytes, bind 72, lazy bind 120, export 64. También tiene tres secciones que faltan al archivo encadenado: __TEXT,__stub_helper(108 bytes), un __gotde sólo 16 bytes de capacidad ___stack_chk_guardy dyld_stub_binder, y __DATA,__la_symbol_ptr(56 bytes, siete ranuras). dyld_info -fixupslistas siete lazy-bindfilas y siete rebases que apuntan a cada ranura perezosa en su propio trampolín en__stub_helperEl trampolín empuja un índice y salta adyld_stub_binder, que resuelve el nombre y escribe la ranura. Es el PLT y GOT de la sección anterior en formas Mach-O: __la_symbol_ptr¿ Es verdad ?.got.plt¿ Qué ?__stub_helperes PLT0 y el pushq $index, y dyld_stub_binder¿ Es verdad ?_dl_runtime_resolve- ¿ Qué ?code/main.pyEl laboratorio de práctica solicita que se den las compensaciones y secciones de este archivo y no se decodifique el código de enlace.

El trie de las exportaciones

LC_DYLD_EXPORTS_TRIECada nodo es un tamaño terminal uleb128, entonces, si no es cero, uleb128 banderas y un uleb128 offset, entonces un byte de conteo de hijos y, para cada niño, un borde de cadena y un uleb128 nodo offset. exports_trie()- Caminando.libledger.dylibSe vuelve ._percent_change 0x410¿ Qué ?_grade 0x428¿ Qué ?_total 0x464¿ Qué ?_grade_label 0x498, los cuatro offsets dyld_info -exportsEn el ejecutable regresa.__mh_execute_header¿ Qué ?_fns, y _main¿ Qué ?

¿Qué tira de las hojas

La de Apple strip(1)dice que sin opciones en un ejecutable dinámicamente vinculado se comporta como -u -r: salvar símbolos indefinidos y símbolos referidos dinámicamente. -xelimina los símbolos locales. -Nelimina toda la tabla de símbolos y la tabla de cadenas. "Para las bibliotecas compartidas dinámicas, el nivel máximo de desprendimiento es generalmente -x", porque "Sólo se utilizan símbolos globales por el proceso de enlace dinámico".

Observado en nuestros accesorios.strip ledger_main¿ Qué ?LC_SYMTABde 12 a 10 registros y eliminados _main¿ Qué ?_fns, y _names. las ocho importaciones no definidas permanecen, porque se necesita que sus nombres se vinculen, y __mh_execute_headerse queda porque es REFERENCED_DYNAMICALLYEl décimo registro es un cuchillo de tipo .N_OPTnombreradr://5614542, que las herramientas de Apple escriben para que la mesa nunca esté vacía; nmEl programa desnudado imprime las mismas tres líneas que el original y sale de 0.LC_FUNCTION_STARTSAún tiene un comienzo,main¿ Qué ?

strip libledger.dylibSin opciones, fue más allá de lo habitual.-x¿ Qué es esto ?LC_SYMTABtiene 0 registros y nmEl trie de exportación sigue listando las cuatro funciones en los mismos desreglados, porque Dyld se une a través del trie y nunca a través.LC_SYMTAB- ¿ Qué ?LC_FUNCTION_STARTSEl laboratorio C vincula un nuevomain.ccontra el dillib despojado para demostrar que el editor de enlaces también es servido por el trie.

WhatELF after --strip-allPE imageMach-O after strip
static symbol tablegonenever present in an imagelocal and defined names gone, undefined imports stay
names the loader binds.dynsym and .dynstr stayimport directory staysimports stay in LC_SYMTAB and in the chained imports table
exported names.dynsym staysexport directory staysexports trie stays
function starts.eh_frame FDEs, unless built without unwind tables.pdata for non-leaf functionsLC_FUNCTION_STARTS stays
debug information.debug_* gonein the PDB, never in the imagein the dSYM, never in the image
section or load command headersstay unless --strip-section-headersstaystay

Encontrar funciones sin símbolos

static-arm64-O2enlacesmain-arm64-O2.oy ledger-arm64-O2.oen un ejecutable con cinco funciones en __text- ¿ Qué ?nm -nde la historia sin extraer es la verdad de fondo: _mainEn el0x100000500(244 bytes), _percent_changeEn el0x1000005f4(24), _gradeEn el0x10000060c60), _totalEn el0x100000648(248), _grade_labelEn el0x100000740(72) static-arm64-O2.strippedNo tieneN_SECTsímbolo dentro __text- ¿ Qué ?FunctionFinderejecuta cuatro métodos en ella y score()compara cada lista con la verdad.

LC_FUNCTION_STARTS

Los puntos de mando en nueve bytes en __LINKEDIT¿ Qué es esto ?80 0a f4 01 18 3c f8 01 00El formato es uleb128 deltas, el primero de la serie __TEXTdirección.80 0a¿ Es verdad ?0x500, así que el primer comienzo es0x100000500- ¿ Qué ?f4 01¿ Es verdad ?0xf4, así que0x1000005f4- ¿ Qué ?18da .0x10000060c¿ Qué ?3cda .0x100000648¿ Qué ?f8 01da .0x100000740, y 00Cinco de cinco, sin falsos positivos, en-O0y en-O2El editor de enlaces escribe esta tabla para el desmantelador y el reportero de choque, ystripGhidra lo lee en su analizador "Mach-O Function Starts",dyld_info -function_startsImprima, y LIEF lo expone como function_starts¿ Qué ?

Los patrones de inicio de Ghidra AARCH64 comienzan con fd 7b bf a9, que es stp x29, x30, [sp, #-0x10]!En nuestros archivos encuentra 0 de 5 en ambos niveles.sub sp, sp, #NPrimero y después .stp x29, x30, [sp, #M]con una compensación positiva, por lo que la forma preindexación nunca ocurre.sub sp, sp, #imm¿ Qué ?(word & 0xff0003ff) == 0xd10003ff, encuentra 5 de 5 en -O0, donde cada función tiene un marco, y 1 de 5 en -O2, donde percent_change¿ Qué ?grade¿ Qué ?total, y grade_labelson funciones de hoja sin marco en absoluto. percent_changecomienza con cbz w0, ...Un análisis de prólogo informa lo que el compilador eligió emitir, y la lección 38 mostró cuán a menudo eso no es nada.

Ninguno de los escaneadores produjo un falso positivo aquí, porque el programa es pequeño.55de push rbpEntra en otras instrucciones, por lo que los archivos de patrón x86-64 de Ghidra emparejan un patrón posterior como 55 48 89 e5con un modelo previo como reto nopque debe terminar con la función anterior.

Los objetivos de llamadas

Cada uno .blEn el__textse llama un inicio de función, porque un blen el centro de una función es algo que un compilador no emite para C. La codificación es 0x94000000con un contraste de palabras de 26 bits firmado.blEn el0x1000005a0Leía3d 00 00 94¿ Qué es esto ?0x3des 61, veces 4 es 0xf4, así que el objetivo es0x100000694En el .ledger_mainEse es el estómago para _putsEn la construcción estática,call_targets()Recolecta los objetivos dentro de la__text, deja caer a los que aterrizan en__stubs, y añade el LC_MAINEntradas, porque nada llamamainNucleus (Andriesse, Slowinska, Bos, 2017) construye su detección de funciones sobre esta idea de código generado por el compilador, y Andriesse y otros midieron en 2016 que los límites de funciones son la parte más difícil del desmonte, más difícil que los límites de instrucciones.

Una firma en forma de flauta

Hex-Rays describe FLIRT en su documentación: un patrón es "los primeros 32 bytes de una función donde todos los bytes variantes están marcados", los bytes variantes provienen de fijos y referencias externas, los patrones viven en un árbol, y más allá del patrón "calculamos el CRC16 de los bytes a partir de la posición 33 hasta el primer byte variante" y almacenamos esa longitud. Dos funciones con el mismo patrón y CRC se separan por un byte de cola en una posición donde difieren, luego por los nombres a los que se refieren. Una colisión que nada separa se "soluciona por inteligencia natural". Las funciones de menos de cuatro bytes sin referencias externas se excluyen, y "el archivo de firma no contiene ningún byte de las bibliotecas originales, excepto los nombres de las funciones".

signatures_from_object()construye eso desde ledger-arm64-O2.oy main-arm64-O2.o, la forma en que FLAIR lo construye a partir de los objetos de una biblioteca.relocation_infoEl registro cubre una palabra de cuatro bytes, y esos bytes se convierten en variantes..patel diseño de línea, documentado en el parser de rizin, es el patrón, la longitud CRC, la CRC16, la longitud de la función, :0000y el nombre, y ^offset namepara cada nombre de referencia:

textA00000342800004B890C8052087D091B000DC01AC0035FD6 00 0000 0018 :0000 _percent_change
E8CC8C52C8CCAC72087C289B09FD7FD308FD62930801090B081900511F110071 04 9AAF 003C :0000 _grade ^0000 l_switch.table.grade ^0001 l_switch.table.grade
E8CC8C52C8CCAC72087C289B09FD7FD308FD62930801090B081900511F110071 04 9AAF 0048 :0000 _grade_label ^0000 l_switch.table.grade_label ^0001 l_switch.table.grade_label ^0002 _labels ^0003 _labels

_percent_changees 24 bytes, así que su patrón es toda la función y la longitud CRC es 0. _gradees de 60 bytes con su primera reubicación en el byte 36, por lo que el CRC cubre cuatro bytes..patuso de parseres: polinomio 0x8408, valor inicial 0xffffSu valor para la cadena ASCII "123456789" es 0x6e90, y los pines de prueba que.

match_signatures()Prueba cada firma en cada inicio de función de la construcción despojada, patrón primero, luego CRC.-O0Las cinco funciones obtienen sus nombres y nada choca.-O2Las dos líneas de arriba chocan.grade_labelen líneagrade, por lo que las dos funciones comparten sus primeros 36 bytes y los cuatro bytes CRC. Ambas firmas coinciden en0x10000060cy en0x100000740La cola los divide: en 44 byte_grade¿ Qué ?20y _grade_label¿ Qué ?28, que es ldr w0, [x9, w8, uxtw #2]contraldr x8, [x9, w8, uxtw #3]El informe registra las colisiones y el byte que las decidió.

El examentest_object_bytes_equal_linked_bytes_outside_relocated_wordsverifica la premisa: cada byte no variante de cada función de objeto es igual al byte en el mismo desplazamiento en el ejecutable vinculado, por lo que el editor de enlace cambió sólo las palabras reubicadas. _totalNo tiene ninguna reubicación y ninguna variante de byte en absoluto.

Lo que las herramientas hacen con la misma idea

IDA Pro 9.4 envía firmas FLIRT por procesador y el sigmakeherramienta para construirlos; IDA Free cubre sólo x86 y x86-64 y es para uso no comercial. Lumina es un servidor que clave metadatos de función por md5 de la función y omite funciones menores a 32 bytes. Ghidra 12.1.4 (Apache-2.0) tiene ID de Función: un hash completo sobre mnemónicos y modos de dirección que es "robusto contra los cambios debido a la vinculación" y un hash específico que agrega operandos constantes, anotados por instrucción coincidente, con funciones de padre e hijo como corta-corta; sus bases de datos enviadas cubren solo las bibliotecas de Visual Studio, y usted llena la suya de sus propios binarios. Binary Ninja 6.0 Krypton (comercial, con una edición gratuita) utiliza WARP (Apache-2.0): la función GUID es una UUIDv5 sobre los GUID de bloque básicos, cada uno una UUIDv5 sobre los bytes de bloque después de ceroar "todas las instrucciones relocables" y dejar caer NOPs, con llamadas, llamador y funciones adyacentes como restricciones. BinDiff 8 (Apache-2.0) combina funciones entre dos binarios con un orden fijo de pasos, desde la coincidencia hash a través de índices de MD de gráfico de flujo hasta llamar coincidencia de secuencias, y pesa la similitud de bordes, bloques, instrucciones y el índice MD de gráfico de flujo. Diafora 3.4.3 (AGPL-3.0, IDA solamente) añade el producto de los primos pequeños, donde cada mapa mnemónico se asigna a un primo y una función es el producto, una huella digital multiset libre de orden. El radar2 6.2.4 (LGPL-3.0) tiene firmas (zg¿ Qué ?z/¿ Qué ?zb) y se lee FLIRT .sigarchivos con zfsLa rizina 0.9.1 (LGPL-3.0) crea .pato .sigde un binario abierto con Fcy aplica uno con Fs¿ Qué ?

IDA y Binary Ninja están cerrados y se describen aquí a partir de su documentación. radar2 y rizin no están instalados en esta máquina, y sus comandos se citan de sus libros y fuentes.clang¿ Qué ?ld¿ Qué ?llvm-objdump¿ Qué ?otool¿ Qué ?nm¿ Qué ?strip, y dyld_infoLIEF lee todos los formatos aquí, incluyendo los objetos COFF, que ha soportado desde 0.17.0, y informó diez ataduras y dos bases en ledger_main, las mismas doce correcciones . las pieles de trabajo sólo leen los archivos ELF .main-x86_64.objconPEFormatError: 'DOS Header magic not found.'y leer la pequeña imagen.

Construye el mismo

Ejecutar la demostración:

bashcd phases/13-tools-and-protocols/40-linking-loading-and-stripped-binaries
python3 code/main.py

La salida es un informe JSON con cinco partes. elftiene los recuentos de reubicación de los dos objetos, el plan de enlace y la sección dinámica, PLT y GOT de tiny-dyn-x86_64- ¿ Qué ?coffcontiene las secciones, las reubicaciones, las funciones y los nombres indefinidos de los dos objetos. peEn el caso de las empresas de transporte, el número de pasajeros en el mercado de transporte de mercancías es el número de pasajeros en el mercado de transporte.tiny-x86_64.exe- ¿ Qué ?machoTieneledger_maincon sus estumbres, arreglos encadenados, filas de arreglos y exportaciones, el diseño heredado de ledger_main_nochain, el Dyllib, y los dos informes de strip.identificationTiene la verdad, los cuatro métodos con puntajes, y las firmas con sus colisiones, en -O0y -O2¿ Qué ?

Forma esperada:

json{
  "elf": {"linkPlan": {"pltSymbols": ["puts", "printf"], "sizes": {".plt": 48, ".got.plt": 40, ".rela.plt": 48, ".rela.dyn": 48}, "...": "..."}, "tiny-dyn-x86_64": {"...": "..."}},
  "coff": {"main-x86_64.obj": {"...": "..."}},
  "pe": {"imports": [{"dll": "msvcrt.dll", "iat": "0x2040", "...": "..."}], "...": "..."},
  "macho": {"ledger_main": {"flags": "0x200085", "fixupRows": ["__DATA_CONST __got 0x100004000 bind libledger/_grade", "..."], "...": "..."}, "strip": {"...": "..."}},
  "identification": {"O2": {"methods": {"functionStarts": {"recall": "5/5"}, "prologueSub": {"recall": "1/5"}, "...": "..."}, "correctNames": 5}}
}

Leer .code/main.pyen este orden:

  1. ElfFilese leen los encabezados, las secciones, las tablas de símbolos y las reubicaciones, y dynamic_entries()¿ Qué ?jump_slots()¿ Qué ?plt_entries(), y plt0()Leer la imagen enlazada.
  2. plan_dynamic_link()se aplicará el cuadro 4.9 y la sección 5.2 a los registros de reubicación de los objetos reubicables.
  3. CoffObjectlee el encabezado del archivo, la tabla de secciones, los símbolos de 18 bytes con sus registros auxiliares y las reubicaciones de 10 bytes.
  4. PeImagese lee el documento de MS-DOS, la firma PE, el encabezado opcional, los directorios de datos, las secciones y los directorios de importación, exportación y reubicación de base, y iat_calls()decodificaciónff 15¿ Qué ?
  5. MachoFilelee comandos de carga, segmentos,nlistregistros, la tabla de símbolos indirectos, los estubes, las correcciones en cadena, el trie de exportación y las reubicaciones de objetos.
  6. FunctionFinderejecuta los cuatro buscadores de inicio y score()comparar cada uno con la verdad.
  7. signatures_from_object()y match_signatures()construir y aplicar las firmas en forma de FLIRT.
  8. El parse_*las funciones de lectura del texto de la herramienta capturada que las pruebas comparan con.

Más órdenes:python3 code/main.py elf FILE¿ Qué ?coff FILE¿ Qué ?pe FILE, y macho FILEimprimir un informe. python3 code/main.py plan OBJECT...Imprime el plan de enlace para cualquier conjunto de objetos ELF. python3 code/main.py functions STRIPPED UNSTRIPPED OBJECT...Pone los marcadores y las firmas en otro par de sus propias construcciones.

Usalo

Utilice los lectores en este orden en un programa que construyó.

  1. Leer los encabezados y nombrar el formato y el tipo: objeto trasladable, ejecutable o biblioteca compartida.
  2. Enumera las importaciones con sus proveedores: DT_NEEDEDy .dynsym, el directorio de importaciones o la tabla de importaciones en cadena y LC_LOAD_DYLIBCada llamada a un nombre en esa lista deja la imagen.
  3. Sigue una llamada a mano a su ranura. Escriba los bytes de llamada, los bytes de la barra o PLT, la dirección de la ranura, la palabra en el archivo y lo que escribe el cargador allí. Si la palabra en el archivo se parece a basura, decodifique como una corrección antes de llamarlo basura.
  4. Comparar la tabla de símbolos con las exportaciones y la función se inicia.stripNo se necesita un desembolso, pero se necesita un desembolso.
  5. En una construcción despojada, ejecuta los buscadores que puedes ejecutar y puntuarlos contra el gemelo sin despojar mientras aún tienes uno. Prefiere las tablas que el editor de enlaces escribió, LC_FUNCTION_STARTS¿ Qué ?.pdata, o.eh_frame, sobre patrones de byte, y utilizar objetivos de llamada para llenar las lagunas.
  6. Construye firmas de sus propios objetos y apliquelas. Graba cada colisión y el byte que la divide. Un nombre que dos firmas reclaman es un desconocido hasta que la cola o un nombre de referencia lo decida.

Mantenga el texto de la herramienta capturada con los archivos. Cuando la cadena de herramientas cambia, recapturar, porque una lista cambiada es un fijo cambiado.

Laboratorio interactivo

Laboratorio A: el PLT a mano

Desde elcodedirectorio, Python abierto:

bashcd phases/13-tools-and-protocols/40-linking-loading-and-stripped-binaries/code
python3 -q
  • ¿Qué quieres decir ?
pythonfrom main import *
elf = load_elf("tiny-dyn-x86_64")
[(e.tag_name, e.text) for e in elf.dynamic_entries()]
elf.plt0()
[(e.symbol, hex(e.address), hex(e.slot), hex(e.slot_initial)) for e in elf.plt_entries()]
[hex(w) for w in elf.got_plt_words()]

Antes de cada llamada, calcular la respuesta a partir de los bytes en el texto anterior.IMPORTSEn elbuild_tiny_dyn_elf.pya tres nombres, reconstruir y predecir los nuevos tamaños de .plt¿ Qué ?.got.plt, y .rela.pltantes de leerlas.

Laboratorio B: un arreglo encadenado

pythonexe = load_macho("ledger_main")
stub = next(s for s in exe.stubs() if s.symbol == "_puts")
stub.lines()
raw = int.from_bytes(exe.read(stub.slot, 8), "little")
hex(raw), raw >> 63, (raw >> 51) & 0xfff, raw & 0xffffff
[(i.index, i.name, i.library) for i in exe.chained_fixups()["imports"]]

Decodifica la palabra de enlace en papel primero: el bit de enlace, el nextEn el campo en unidades de cuatro bytes, la ordenal. Luego lee la palabra ocho bytes más adelante y confirmar que es la siguiente fijación.0x100004058y explicar por qué es nextes cero.

Laboratorio C: desprestigio y reencadenamiento

En el directorio de accesorios, enlazar un programa nuevo con el dilib completamente despojado:

bashcd phases/13-tools-and-protocols/40-linking-loading-and-stripped-binaries/code/fixtures
clang -target arm64-apple-macos -O1 main.c -L. -lledger.stripped -Wl,-rpath,@executable_path -o /tmp/relinked
nm /tmp/relinked | grep ' U '
dyld_info -exports libledger.stripped.dylib

El enlace tiene éxito con una biblioteca cuyo LC_SYMTABEs vacío, porque el editor de enlaces lee el trie de exportaciones.python3 ../main.py macho libledger.stripped.dyliby compararsymtab¿ Qué ?exports, y functionStartsLuego deshace la estructura estática con-NEn cambio , veamos qué .dyld_info -function_startstodavía huellas.

Laboratorio D: la colisión

pythonfrom main import *
stripped = load_macho("static-arm64-O2.stripped")
sigs = signatures_from_object(load_macho("ledger-arm64-O2.o"))
[s.pat_line() for s in sigs]
starts = FunctionFinder(stripped).function_starts()
match_signatures(sigs, stripped, starts)

Lea los dos registros de colisión y luego construye las firmas deledger-arm64-O0.o, emparejarlos contrastatic-arm64-O0.stripped, y explicar desde el .patlíneas por las que no se produce ninguna colisión en -O0Entonces acortad .PATTERN_LENGTHa 16 y contar cuántas funciones chocan.

Laboratorio de práctica

Descifrar las tablas de vinculación heredadas y agregar el buscador de PE.

Requisitos:

  • Descifrar el LC_DYLD_INFO_ONLYrebase, binda y perezoso binda flujos de código op de ledger_main_nochainen filas con segmento, sección, dirección, tipo y objetivo, y hacer que sean iguales a las 21 filas en la capturada dyld_info -fixups¿Qué es eso?
  • Añadir unPT_GNU_RELROprograma de título y un DT_FLAGSconDF_BIND_NOW¿ Qué ?build_tiny_dyn_elf.py, moverse.got.pltdentro del rango cubierto, y hacer llvm-objdump -pEn las fichas README, diga qué parte de RELRO completo reproduce y qué parte sólo puede realizar Glibc.
  • Escriba un.pdatalector para una imagen PE32+ y un buscador de funciones que utiliza RUNTIME_FUNCTIONComience con direcciones.clang -target x86_64-pc-windows-msvc -cSin-fno-asynchronous-unwind-tables, lee su .pdatalas transferencias y indicar cuáles de sus funciones son las funciones de hoja que la tabla omite.
  • Escriba una prueba para cada lector que comparar con el texto capturado de la herramienta.

El éxito significapython3 code/main.py macho ledger_main_nochainImprime las mismas 21 filas de fijación que dyld_infoLas huellas, el pequeño ELF lleva unPT_GNU_RELROEso es .llvm-objdumpLas listas y los.pdatael buscador nombra las funciones que puede y enumera las funciones de hoja que no puede.

Lección Artefacto

Esta lección incluye outputs/skill-linking-loading-and-stripped-binaries.md.Utilizarlo para convertir un programa que construye en una lista de sus importaciones con sus proveedores, una llamada rastreada a la ranura que llena el cargador, una declaración de lo que stripSe requiere el gemelo sin cortar como verdadera, una lista de herramientas capturadas para cada reclamación y un registro de cada colisión.

Verifique el hecho

Ejecutar la demostración y la suite determinista:

bashcd phases/13-tools-and-protocols/40-linking-loading-and-stripped-binaries
python3 code/main.py
python3 code/tests/test_main.py

La verificación deberá demostrar:

  • la sección dinámica de tiny-dyn-x86_64las partidasllvm-objdump -petiqueta por etiqueta, y DT_FLAGS_1que llevaDF_1_PIE
  • Los dos .R_X86_64_JUMP_SLOTlos registros coinciden .llvm-objdump -Ry nombreputsy exit
  • PLT0 empuja GOT+8 y salta a través de GOT+16, cada entrada lee su ranura y empuja su índice, y cada ranura tiene la dirección de su propia pushq
  • las secciones y los encabezados del programa del pequeño partido ELF llvm-objdump -h -p
  • Los registros de reubicación de los dos objetos de ELF coinciden llvm-objdump -r
  • El plan de enlace encuentra dos símbolos PLT, cuatro llamadas directas, dos reubicaciones relativas, y los tamaños de la pequeña imagen
  • la tabla de la sección COFF, 37 registros de símbolos con registros auxiliares y coincidencias de traslados llvm-objdump -h -t -r, y /16se resuelve a .llvm_addrsig
  • los tres COFF REL32registros de ledgerSiéntate en el ELF tresPC32compensaciones
  • los encabezados de la PE, DllCharacteristics, y los directorios de datos coinciden llvm-objdump -p
  • el directorio de importaciones, las entradas de indicios/nombres y la correspondencia de IAT llvm-objdumpy pefile, y el IAT es igual a la tabla de búsqueda antes de la unión
  • el directorio de exportaciones y el DIR64el código de reubicación de base, y el puntero reubicado nombra la cadena "enlazada a mano"
  • ff 15en RVA 0x100bse resuelve en la ranura de IAT de puts
  • Banderas Mach-O, los 19 nombres de comandos de carga, y las dos bibliotecas coinciden.otool
  • Los siete estubes se decodan a los ocho __gotranuras y coincidencias de la tabla de símbolos indirectos otool -Iv
  • Los encabezados en cadena coinciden .llvm-objdump --macho --chained-fixups
  • La cadena de paseo reproduce las doce palabras crudas de dyld_info -fixup_chain_details
  • las filas de fijación y la tabla de importaciones coinciden dyld_info -fixupsy -imports
  • las exportaciones se ajustan dyld_info -exportspara el dilib y para su copia despojada
  • nm_lines()igual nm -mpara el ejecutable y su copia despojada
  • stripSe retiró ._main¿ Qué ?_fns, y _names, mantuvo nueve nombres y uno radr://5614542puñala, y el programa despojado imprime la misma salida
  • el diseño heredado tiene LC_DYLD_INFO_ONLY, siete punteros perezosos, y rebases en __stub_helper
  • arm64 reubicaciones de objetos coinciden llvm-objdump -r
  • LC_FUNCTION_STARTSrecupera cinco de cinco en ambos niveles y es igual nmy dyld_info -function_starts
  • el sub spLa exploración encuentra cinco de cinco en -O0y uno de cinco en -O2, y el stpEl escaneo de marco no encuentra ninguna
  • blobjetivos más el LC_MAINentrada recuperada cinco de cinco
  • Las firmas nombran las cinco funciones en ambos niveles y registran las gradecolisión dividida por byte 44
  • La CRC16 de "123456789" es 0x6e90, y los ocho bytes bajo el par de carga GOT de mainson variantes
  • cada byte de objeto no variante es igual al byte vinculado
  • la demostración y los datos de las cifras informan las mismas direcciones, y el módulo de sitio incorpora los mismos datos en ES5

Modos de falla de producción

FailureWhat the weak tool reportsWhat the analysis must prove
A bl or call lands in a stub"calls an unnamed function at 0x694"the stub bytes, the slot it loads, and the import the slot binds
A GOT word read from the file"corrupt pointer 0x8010000000000007"bit 63 is bind, next is 2, ordinal 7 is _puts from library 2
PLT32 in an object"every call goes through the PLT"defined symbols are reduced to a direct distance, only undefined ones get an entry
Partial RELRO read as full"GOT is read-only".got.plt slots after the three reserved ones sit outside PT_GNU_RELRO without -z now
COFF REL32 compared with ELF PC32"the Windows build has different relocations"same offsets, the -4 lives in the ELF addend and in the COFF field
IAT read before load"imports point at strings"the IAT equals the lookup table until the loader binds, and the strings are hint/name entries
A stripped dylib"no symbols, nothing exported"the exports trie and LC_FUNCTION_STARTS list every function
nm on a stripped executable"one function"LC_FUNCTION_STARTS lists five starts, nine bytes long
A prologue scan at -O2"one function"four leaf functions have no frame, call targets and the starts table find them
Two functions share 36 bytes"grade_label is grade"the tail byte at 44 and the lengths 60 and 72 split the collision
A tool run on another machine's buildthe numbers in this textrecapture every listing from your own fixtures

Regla de funcionamiento

Siga cada llamada externa desde el sitio de llamada hasta la ranura que llena el cargador, y decodifique la palabra en esa ranura como la configuración la define antes de llamarla una dirección. Solo se desprenda después de haber grabado lo que el cargador todavía necesita. La función de recuperación comienza a partir de las tablas que el editor de enlaces escribió antes de escanear para bytes, marcar cada buscador contra un gemelo con símbolos, y grabar cada colisión de firma con el byte que lo divide.

Leer más

This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.

Browse the complete course catalog or open this lesson on GitHub.