Receitas para ELFs

imagem


Em russo, não há informações suficientes sobre como trabalhar com arquivos ELF (formato executável e vinculável - o formato principal para arquivos executáveis ​​do Linux e de muitos sistemas Unix). Não pretendemos cobrir totalmente todos os cenários possíveis de trabalho com elfos, mas esperamos que as informações sejam úteis na forma de um livro de referência e uma coleção de receitas para programadores e engenheiros reversos.


Entende-se que o leitor em um nível básico está familiarizado com o formato ELF (caso contrário, recomendamos a série de artigos Executable and Linkable Format 101 ).


Abaixo do corte, serão listadas as ferramentas para o trabalho, técnicas descritas para leitura de meta-informações, modificação, verificação e criação criando elfos, bem como links para materiais úteis.


"Eu também sou um elfo ... Azul em vermelho ... Elfos são muito pacientes ... Azul em vermelho ... E nós somos elfos! .. Azul em vermelho ... Existem apenas problemas de mágica ...
(c) O pequeno reino de Ben e Holly

As ferramentas


Na maioria dos casos, os exemplos podem ser executados no Linux e no Windows.


Nas receitas, usaremos as seguintes ferramentas:


  • utilitários do conjunto binutils (objcopy, objdump, readelf, strip);
  • quadro radare2 ;
  • editor hexadecimal com suporte para modelos de arquivo (os exemplos mostram 010Editor , mas você pode usar, por exemplo, Veles grátis);
  • Python e a biblioteca LIEF ;
  • outros utilitários (os links estão na receita).

Elfos de teste


Como um "experimental", usaremos o arquivo ELF simples da tarefa PieIsMyFav da nutcake no crackmes.one, mas qualquer representante da família "elven" o fará. Se o arquivo finalizado com as características necessárias não for encontrado no domínio público, será fornecido um método para criar esse elfo.


Elfos Livres também podem ser encontrados nos links:



Lendo, obtendo informações


Tipo de arquivo, título, seção


Dependendo da tarefa, pode ser interessante o seguinte:


  • tipo de arquivo (biblioteca DYN, EXEC - executável, RELOC - vinculável);
  • arquitetura de destino (E_MACHINE - x86_64, x86, ARM, etc.);
  • ponto de entrada do aplicativo (ponto de entrada);
  • informações da seção.

010Editor


O HEX Editor 010Editor fornece um sistema de modelos. Para arquivos ELF, o modelo é chamado, por incrível que pareça, ELF.bt e está localizado na categoria Executável (menu Modelos - Executável).
De interesse pode ser, por exemplo, o ponto de entrada para o arquivo executável (ponto de entrada) (registrado no cabeçalho do arquivo).


imagem


readelf


O utilitário readelf pode ser considerado o padrão de fato para obter informações sobre um arquivo ELF.


  • Leia o cabeçalho do arquivo:
    $ readelf -h simple 

Resultado da equipe
 ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x1070 Start of program headers: 64 (bytes into file) Start of section headers: 14800 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 11 Size of section headers: 64 (bytes) Number of section headers: 30 Section header string table index: 29 

  • Leia informações sobre segmentos e seções:
     $ readelf -l -W simple 

Resultado da equipe

Para facilitar a leitura, os endereços são convertidos para o formato de 32 bits:


 Elf file type is DYN (Shared object file) Entry point 0x1070 There are 11 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x00000040 0x00000040 0x000268 0x000268 R 0x8 INTERP 0x0002a8 0x000002a8 0x000002a8 0x00001c 0x00001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x000000 0x00000000 0x00000000 0x0005f8 0x0005f8 R 0x1000 LOAD 0x001000 0x00001000 0x00001000 0x00026d 0x00026d RE 0x1000 LOAD 0x002000 0x00002000 0x00002000 0x0001b8 0x0001b8 R 0x1000 LOAD 0x002de8 0x00003de8 0x00003de8 0x000258 0x000260 RW 0x1000 DYNAMIC 0x002df8 0x00003df8 0x00003df8 0x0001e0 0x0001e0 RW 0x8 NOTE 0x0002c4 0x000002c4 0x000002c4 0x000044 0x000044 R 0x4 GNU_EH_FRAME 0x002070 0x00002070 0x00002070 0x00003c 0x00003c R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x000000 0x000000 RW 0x10 GNU_RELRO 0x002de8 0x00003de8 0x00003de8 0x000218 0x000218 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 03 .init .plt .plt.got .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .dynamic .got .got.plt .data .bss 06 .dynamic 07 .note.ABI-tag .note.gnu.build-id 08 .eh_frame_hdr 09 10 .init_array .fini_array .dynamic .got 

  • Leia as informações da seção:
     $ readelf -S -W simple 

Resultado da equipe

Para facilitar a leitura, os endereços são convertidos para o formato de 32 bits:


 There are 30 section headers, starting at offset 0x39d0: Section Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 000002a8 0002a8 00001c 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 000002c4 0002c4 000020 00 A 0 0 4 [ 3] .note.gnu.build-id NOTE 000002e4 0002e4 000024 00 A 0 0 4 [ 4] .gnu.hash GNU_HASH 00000308 000308 000024 00 A 5 0 8 [ 5] .dynsym DYNSYM 00000330 000330 0000d8 18 A 6 1 8 [ 6] .dynstr STRTAB 00000408 000408 0000a2 00 A 0 0 1 [ 7] .gnu.version VERSYM 000004aa 0004aa 000012 02 A 5 0 2 [ 8] .gnu.version_r VERNEED 000004c0 0004c0 000030 00 A 6 1 8 [ 9] .rela.dyn RELA 000004f0 0004f0 0000c0 18 A 5 0 8 [10] .rela.plt RELA 000005b0 0005b0 000048 18 AI 5 23 8 [11] .init PROGBITS 00001000 001000 000017 00 AX 0 0 4 [12] .plt PROGBITS 00001020 001020 000040 10 AX 0 0 16 [13] .plt.got PROGBITS 00001060 001060 000008 08 AX 0 0 8 [14] .text PROGBITS 00001070 001070 0001f2 00 AX 0 0 16 [15] .fini PROGBITS 00001264 001264 000009 00 AX 0 0 4 [16] .rodata PROGBITS 00002000 002000 000070 00 A 0 0 8 [17] .eh_frame_hdr PROGBITS 00002070 002070 00003c 00 A 0 0 4 [18] .eh_frame PROGBITS 000020b0 0020b0 000108 00 A 0 0 8 [19] .init_array INIT_ARRAY 00003de8 002de8 000008 08 WA 0 0 8 [20] .fini_array FINI_ARRAY 00003df0 002df0 000008 08 WA 0 0 8 [21] .dynamic DYNAMIC 00003df8 002df8 0001e0 10 WA 6 0 8 [22] .got PROGBITS 00003fd8 002fd8 000028 08 WA 0 0 8 [23] .got.plt PROGBITS 00004000 003000 000030 08 WA 0 0 8 [24] .data PROGBITS 00004030 003030 000010 00 WA 0 0 8 [25] .bss NOBITS 00004040 003040 000008 00 WA 0 0 1 [26] .comment PROGBITS 00000000 003040 00001c 01 MS 0 0 1 [27] .symtab SYMTAB 00000000 003060 000630 18 28 44 8 [28] .strtab STRTAB 00000000 003690 000232 00 0 0 1 [29] .shstrtab STRTAB 00000000 0038c2 000107 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) 

  • Leia as informações do símbolo:
     $ readelf -s -W simple 

Resultado da equipe

A saída é reduzida para facilitar a leitura:


 Symbol table '.dynsym' contains 9 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable 2: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2) 3: 00000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2) 4: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 5: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 6: 00000000 0 FUNC GLOBAL DEFAULT UND __isoc99_scanf@GLIBC_2.7 (3) 7: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 8: 00000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) Symbol table '.symtab' contains 66 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 000002a8 0 SECTION LOCAL DEFAULT 1 2: 000002c4 0 SECTION LOCAL DEFAULT 2 3: 000002e4 0 SECTION LOCAL DEFAULT 3 4: 00000308 0 SECTION LOCAL DEFAULT 4 5: 00000330 0 SECTION LOCAL DEFAULT 5 6: 00000408 0 SECTION LOCAL DEFAULT 6 7: 000004aa 0 SECTION LOCAL DEFAULT 7 .... 26: 00000000 0 SECTION LOCAL DEFAULT 26 27: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 28: 000010a0 0 FUNC LOCAL DEFAULT 14 deregister_tm_clones 29: 000010d0 0 FUNC LOCAL DEFAULT 14 register_tm_clones 30: 00001110 0 FUNC LOCAL DEFAULT 14 __do_global_dtors_aux 31: 00004040 1 OBJECT LOCAL DEFAULT 25 completed.7389 .... 

A opção -W é necessária para aumentar a largura da saída do console (padrão, 80 caracteres).


Lief


Você pode ler as informações de cabeçalho e seção usando o código Python e a biblioteca LIEF (fornece uma API não apenas para Python):


 import lief binary = lief.parse("simple.elf") header = binary.header print("Entry point: %08x" % header.entrypoint) print("Architecture: ", header.machine_type) for section in binary.sections: print("Section %s - size: %s bytes" % (section.name, section.size) 

Informações do compilador


Para obter informações sobre o compilador e a compilação, consulte as .note e .note .


objdump


 $ objdump -s --section .comment simple 

Resultado da equipe
 simple: file format elf64-x86-64 Contents of section .comment: 0000 4743433a 20284465 6269616e 20382e32 GCC: (Debian 8.2 0010 2e302d39 2920382e 322e3000 .0-9) 8.2.0. 

readelf


 $ readelf -p .comment simple 

Resultado da equipe
 String dump of section '.comment': [ 0] GCC: (Debian 8.2.0-9) 8.2.0 

 $ readelf -n simple 

Resultado da equipe
 Displaying notes found at file offset 0x000002c4 with length 0x00000020: Owner Data size Description GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag) OS: Linux, ABI: 3.2.0 Displaying notes found at file offset 0x000002e4 with length 0x00000024: Owner Data size Description GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) Build ID: dae0509e4edb79719a65af37962b74e4cf2a8c2e 

Lief


 import lief binary = lief.parse("simple") comment = binary.get_section(".comment") print("Comment: ", bytes(comment.content)) 

Vou calcular você por ... RPATH


Elfos podem salvar caminhos para encontrar bibliotecas conectadas dinamicamente. Para não definir a variável do sistema LD_LIBRARY_PATH antes de iniciar o aplicativo, você pode simplesmente "incorporar" esse caminho ao arquivo ELF.


Para fazer isso, use a entrada na seção .dynamic com o tipo DT_RPATH ou DT_RUNPATH (consulte o capítulo Diretórios Pesquisados ​​pelo Runtime Linker na documentação).


E tenha cuidado, jovem desenvolvedor, não "durma" o diretório do seu projeto!


Como o RPATH aparece?


O principal motivo para a aparência de um registro RPATH em um elfo é a opção linker -rpath para procurar uma biblioteca dinâmica. Algo assim:


 $ gcc -L./lib -Wall -Wl,-rpath=/run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/ -o test_rpath.elf bubble_main.c -lbubble 

Esse comando criará um registro RPATH na seção .dynamic com o valor /run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/ .


readelf


Você pode visualizar os elementos da seção .dynamic (entre os quais há RPATH) da seguinte maneira:


 $ readelf -d test_rpath.elf 

Resultado da equipe

Para facilitar a leitura, o resultado do comando é reduzido:


 Dynamic section at offset 0x2dd8 contains 28 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libbubble.so] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000f (RPATH) Library rpath: [/run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/] 0x000000000000000c (INIT) 0x1000 0x000000000000000d (FINI) 0x11c8 .... 

Lief


Usando a biblioteca LIEF, você também pode ler o registro RPATH no elfo:


 import lief from lief.ELF import DYNAMIC_TAGS elf = lief.parse("test_rpath.elf") if elf.has(DYNAMIC_TAGS.RPATH): rpath = next(filter(lambda x: x.tag == DYNAMIC_TAGS.RPATH, elf.dynamic_entries)) for path in rpath.paths: print(path) else: print("No RPATH in ELF") 

Leia sobre a seção .dynamic


Verificando Elf para Segurança


O script de verificação de segurança checksec.sh do pesquisador Tobias Klein (autor do Diário de um caçador de insetos ) não é atualizado desde 2011. Este script para arquivos ELF verifica a disponibilidade das opções RelRO (realocações somente leitura), NX (pilha não executável), canários de pilha, PIE (executáveis ​​independentes de posição) e usa o utilitário readelf para seu trabalho.


Lief


Você pode fazer seu próprio analógico em joelho Python e LIEF (um pouco mais curto que o progenitor e com verificação adicional da opção de código separado ):


 import lief from lief.ELF import DYNAMIC_TAGS, SEGMENT_TYPES def filecheck(filename): binary = lief.parse(filename) # check RELRO if binary.has(SEGMENT_TYPES.GNU_RELRO): print("+ Full RELRO") if binary.has(DYNAMIC_TAGS.BIND_NOW) else print("~ Partial RELRO") else: print("- No RELRO") # check for stack canary support print("+ Canary found") if binary.has_symbol("__stack_chk_fail") else print("- No canary found") # check for NX support (check X-flag for GNU_STACK-segment) print("+ NX enabled") if binary.has_nx else print("- NX disabled") # check for PIE support print("+ PIE enabled") if binary.is_pie else print("- No PIE") # check for rpath / run path print("+ RPATH") if binary.has(DYNAMIC_TAGS.RPATH) else print("- No RPATH") print("+ RUNPATH")if binary.has(DYNAMIC_TAGS.RUNPATH) else print("- No RUNPATH") # check separate-code option if set(binary.get_section('.text').segments) == set(binary.get_section('.rodata').segments): print("- Not Separated Code Sections") else: print("+ Separated Code Sections") filecheck('test_rpath.elf') 

Radare2


Agradecemos a dukebarman pela adição do uso do Radare2 para exibir informações semelhantes ao checksec :


 > r2 -ci~pic,canary,nx,crypto,stripped,static,relocs test_stack_proteck 

Código bruto da Elf (binário da ELF)


Há situações em que “roupas élficas” na forma de uma estrutura ELF não são necessárias, mas apenas o código do aplicativo executável “vazio” é necessário.


objcopy


O uso de objcopy provavelmente é familiar para quem escreve firmware:


 $ objcopy -O binary -S -g simple.elf simple.bin 

  • -S - para excluir as informações dos personagens;
  • -g - para remover informações de depuração.

Lief


Nenhuma mágica. Basta pegar o conteúdo das seções carregadas e criar um binário a partir delas:


 import lief from lief.ELF import SECTION_FLAGS, SECTION_TYPES binary = lief.parse("test") end_addr = 0 data = [] for section in filter(lambda x: x.has(SECTION_FLAGS.ALLOC) and x.type != SECTION_TYPES.NOBITS, binary.sections): if 0 < end_addr < section.virtual_address: align_bytes = b'\x00' * (section.virtual_address - end_addr) data.append(align_bytes) data.append(bytes(section.content)) end_addr = section.virtual_address + section.size with open('test.lief.bin', 'wb') as f: for d_bytes in data: f.write(d_bytes) 

Mangled - nomes de funções desmangled


Nos ELFs criados a partir do código C ++, os nomes das funções são decorados (desconfigurados) para simplificar a pesquisa da função de classe correspondente. No entanto, a leitura desses nomes na análise não é muito conveniente.


Elfo de teste


nm


Para representar nomes em formato legível por humanos, você pode usar o utilitário nm do conjunto binutils:


 #        $ nm -D demangle-test-cpp ... U _Unwind_Resume U _ZdlPv U _Znwm U _ZSt17__throw_bad_allocv U _ZSt20__throw_length_errorPKc #        $ nm -D --demangle demangle-test-cpp ... U _Unwind_Resume U operator delete(void*) U operator new(unsigned long) U std::__throw_bad_alloc() U std::__throw_length_error(char const*) 

Lief


Exibindo nomes de símbolos em formato desmangulado usando a biblioteca LIEF:


 import lief binary = lief.parse("demangle-test-cpp") for symb in binary.symbols: print(symb.name, symb.demangled_name) 

Montagem, gravação, modificação do elfo


Elfo sem meta-informação


Depois que o aplicativo é depurado e lançado no mundo selvagem, faz sentido remover as meta-informações:


  • seções de depuração - inúteis na maioria dos casos;
  • nomes de variáveis ​​e funções - absolutamente não afetam nada para o usuário final (complica ligeiramente o inverso);
  • tabela de seção - absolutamente não é necessário para executar o aplicativo (sua falta complicará levemente o inverso).

Excluir informações do personagem


Informações sobre caracteres são os nomes de objetos e funções. Sem ele, o inverso do aplicativo é um pouco mais complicado.


tira


No caso mais simples, você pode usar o utilitário strip do conjunto binutils. Para excluir todas as informações de caracteres, basta executar o comando:


  • para arquivo executável:
     $ strip -s simple 
  • para biblioteca dinâmica:
     $ strip --strip-unneeded libsimple.so 

tira


Para remover cuidadosamente as informações dos caracteres (incluindo zero bytes desnecessários no final do arquivo), você pode usar o utilitário sstrip do conjunto ELFkickers . Para excluir todas as informações de caracteres, basta executar o comando:


 $ sstrip -z simple 

Lief


Usando a biblioteca LIEF, você também pode fazer uma faixa rápida (a tabela de símbolos é excluída - seção .symtab ):


 import lief binary = lief.parse("simple") binary.strip() binary.write("simple.stripped") 

Excluindo uma tabela de partição


Como mencionado acima, a presença / ausência de uma tabela de seção não afeta a operação do aplicativo. Mas, ao mesmo tempo, sem uma tabela de seção, o inverso do aplicativo se torna um pouco mais complicado.
Usaremos a biblioteca LIEF em Python e o exemplo de exclusão da tabela de seção :


 import lief binary = lief.parse("simple") binary.header.numberof_sections = 0 binary.header.section_header_offset = 0 binary.write("simple.modified") 

Modifique e exclua o RPATH


chrpath, PatchELF


Para alterar o RPATH no Linux, você pode usar os utilitários chrpath (disponíveis na maioria das distribuições) ou o PatchELF .


  • Alterar RPATH:


     $ chrpath -r /opt/my-libs/lib:/foo/lib test_rpath.elf 

    ou


     $ patchelf --set-rpath /opt/my-libs/lib:/foo/lib test_rpath.elf 

  • Remova o RPATH:


     $ chrpath -d test_rpath.elf 

    ou


     $ patchelf --shrink-rpath test_rpath.elf 


Lief


A biblioteca LIEF também permite modificar e excluir um registro RPATH.


  • Alterar RPATH:


     import lief binary = lief.parse("test_rpath.elf") rpath = next(filter(lambda x: x.tag == lief.ELF.DYNAMIC_TAGS.RPATH, binary.dynamic_entries)) rpath.paths = ["/opt/my-lib/here"] binary.write("test_rpath.patched") 

  • Remova o RPATH:


     import lief binary = lief.parse("test_rpath.elf") binary.remove(lief.ELF.DYNAMIC_TAGS.RPATH) binary.write("test_rpath.patched") 


Ofuscação da informação do personagem


Para complicar o reverso do aplicativo, você pode salvar informações simbólicas, mas confundir os nomes dos objetos. Usamos o elfo crackme01_32bit de crackme01 da seveb como objeto de teste.


Uma versão simplificada de um exemplo da biblioteca LIEF pode ter a seguinte aparência:


 import lief binary = lief.parse("crackme01_32bit") for i, symb in enumerate(binary.static_symbols): symb.name = "zzz_%d" % i binary.write("crackme01_32bit.obfuscated") 

Como resultado, obtemos:


 $ readelf -s crackme01_32bit.obfuscated ... Symbol table '.symtab' contains 78 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND zzz_0 1: 08048154 0 SECTION LOCAL DEFAULT 1 zzz_1 2: 08048168 0 SECTION LOCAL DEFAULT 2 zzz_2 3: 08048188 0 SECTION LOCAL DEFAULT 3 zzz_3 4: 080481ac 0 SECTION LOCAL DEFAULT 4 zzz_4 5: 080481d0 0 SECTION LOCAL DEFAULT 5 zzz_5 6: 080482b0 0 SECTION LOCAL DEFAULT 6 zzz_6 7: 0804835a 0 SECTION LOCAL DEFAULT 7 zzz_7 8: 08048378 0 SECTION LOCAL DEFAULT 8 zzz_8 9: 080483b8 0 SECTION LOCAL DEFAULT 9 zzz_9 10: 080483c8 0 SECTION LOCAL DEFAULT 10 zzz_10 ... 

Substituição de função via PLT / GOT


Também conhecida como ELF PLT INFECTION .


Para não copiar e colar, basta deixar os links no tópico:



Alterar ponto de entrada


Pode ser útil ao criar patches, instalar ganchos e outra instrumentação dinâmica ou para invocar funções ocultas. Como experimental, usamos o elfo crackme01_32bit de crackme01 por seveb


radare2


O radare2 inicia no modo de gravação (opção -w ) - serão feitas alterações no arquivo original:


 $ ./crackme01_32bit Please enter the secret number: ^C $ r2 -w -nn crackme01_32bit [0x00000000]> .pf.elf_header.entry=0x0804860D [0x00000000]> q $ ./crackme01_32bit Nope. 

Lief


 import lief binary = lief.parse("crackme01_32bit") header = binary.header header.entrypoint = 0x0804860D binary.write("crackme01_32bit.patched") 

Patch de código


Como um teste simples, faça o crackmepal do novn91 com o crack . Quando iniciado sem parâmetros, o programa exibe:


 $ ./crackmeMario usage <password> 

Quando iniciado com um parâmetro de sequência arbitrária, ele exibe:


 ./crackmeMario qwerty try again pal. 

Faremos um patch para que o programa imediatamente na inicialização exiba a mensagem “bom trabalho! agora me keygen! ”


radare2


O radare2 pode corrigir qualquer formato que seja compatível. Nesse caso, é possível descrever os patches no formato de texto:


 # Rapatch for https://crackmes.one/crackme/5ccecc7e33c5d4419da559b3 !echo Patching crackme 0x115D : jmp 0x1226 

Você pode aplicar esse patch com o comando:


 $ r2 -P patch.txt crackmeMario 

Leia sobre o patch do código através do radare2:



Lief


O LIEF permite corrigir o elfo (sobrescrever bytes) no endereço virtual especificado. O patch pode estar na forma de uma matriz de bytes ou como um valor inteiro:


 import lief binary = lief.parse("crackmeMario") binary.patch_address(0x115D, bytearray(b"\xe9\xc4\x00\x00\x00")) binary.write("crackmeMario.patched") 

Depois de aplicar o patch, o programa exibirá:


 $ ./crackmeMario.patched good job! now keygen me! 

Adicionar seção ao ELF


objcopy


O objcopy permite adicionar uma seção, mas esta seção não pertencerá a nenhum segmento e não será carregada na RAM quando o aplicativo for iniciado:


 $ objcopy --add-section .testme=data.zip \ --set-section-flags .testme=alloc,contents,load,readonly \ --change-section-address .testme=0x08777777 \ simple simple.patched.elf 

Lief


A biblioteca LIEF permite adicionar uma nova seção e seu segmento correspondente (sinalizador loaded=True ) a um ELF existente:


 import lief binary = lief.parse("simple") data = bytearray(b"\xFF" * 16) section = lief.ELF.Section(".testme", lief.ELF.SECTION_TYPES.PROGBITS) section += lief.ELF.SECTION_FLAGS.EXECINSTR section += lief.ELF.SECTION_FLAGS.ALLOC section.content = data binary.add(section, loaded=True) binary.write("simple.testme.lief") 

Alterar Seção


objcopy


O objcopy permite substituir o conteúdo de uma seção pelos dados de um arquivo, além de alterar o endereço virtual de uma seção e sinalizadores:


 $ objcopy --update-section .testme=patch.bin \ --change-section-address .testme=0x08999999 simple simple.testme.elf 

Lief


 import lief binary = lief.parse("simple") data = bytearray(b"\xFF" * 17) section = binary.get_section(".text") section.content = data binary.write("simple.patched") 

Excluir seção


objcopy


O objcopy permite excluir uma seção específica por nome:


 $ objcopy --remove-section .testme simple.testme.elf simple.no_testme.elf 

Lief


A exclusão de uma seção usando a biblioteca LIEF fica assim:


 import lief binary = lief.parse("simple.testme.elf") binary.remove_section(".testme") binary.write("simple.no_testme") 

Container Elf


A receita é inspirada na magia de Gremlins e ELF: e se o arquivo ELF for um contêiner? . Também existem algumas informações sobre o utilitário elfwrap originalmente do Solaris, que permite criar um arquivo ELF a partir de dados arbitrários, e o formato ELF é usado simplesmente como um contêiner.


Vamos tentar fazer o mesmo em Python e LIEF.
Infelizmente, no momento, a biblioteca LIEF não pode criar um arquivo elfo do zero, então você precisa ajudá-lo - crie um modelo ELF vazio:


 $ echo "" | gcc -m32 -fpic -o empty.o -c -xc - $ gcc -m32 -shared -o libempty.so empty.o 

Agora você pode usar este modelo para preencher dados:


 import lief binary = lief.parse("libempty.so") filename = "crackme.zip" data = open(filename, 'rb').read() # Add section with zip-archive as content section = lief.ELF.Section() section.content = data section.name = ".%s"%filename binary.add(section, loaded=True) # Add symbol as a reference to zip-archive symb = lief.ELF.Symbol() symb.type = lief.ELF.SYMBOL_TYPES.OBJECT symb.binding = lief.ELF.SYMBOL_BINDINGS.GLOBAL symb.size = len(data) symb.name = filename symb.value = section.virtual_address binary.add_static_symbol(symb) binary.write("libdata.crackme.container") 

Elfo "com um trailer"


O formato ELF não impõe restrições aos dados que estão no arquivo, mas não pertence a nenhum segmento. Assim, é possível criar um arquivo executável, que será armazenado após a estrutura ELF. Isso é algo que não será carregado na RAM no tempo de execução, mas será gravado no disco e a qualquer momento poderá ser lido no disco.


  • O IDA Pro não considerará esses dados ao analisar

Exemplo de estrutura de arquivo com um trailer
imagem


radare2


A presença de um "trailer" pode ser estabelecida comparando os tamanhos de arquivo reais e calculados:


 $ radare2 test.elf [0x00001040]> ?v $s 0x40c1 [0x00001040]> iZ 14699 

readelf


O readelf não mostra informações sobre a presença de um "trailer", mas pode ser calculado manualmente:


 $ ls -l test.elf #   16577  $ readelf -h test.elf Start of section headers e_shoff 14704 Size of section headers e_shentsize 64 Number of section headers e_shnum 29 #  ELF-: e_shoff + ( e_shentsize * e_shnum ) = 16560 

Lief


A biblioteca LIEF permite que você verifique a presença de um "trailer" e adicione-o. Usando o LIEF, tudo parece bem sucinto:


 import lief binary = lief.parse("test") # check if overlay exists print('ELF has overlay data') if binary.has_overlay else print("No overlay data") # add overlay data to ELF data = bytearray(b'\xFF'*17) binary.overlay = data binary.write('test.overlay') 

Elfo Vazio (ELF do zero)


Na Internet, você pode encontrar projetos para criar um arquivo ELF “manualmente” - sem usar um compilador e vinculador sob o nome geral “ELF from scratch”:



A familiaridade com esses projetos tem um efeito benéfico na absorção do formato ELF.


Elfo menor


Experimentos interessantes com a minimização do tamanho do elfo são descritos nos artigos:



Em resumo, o elf loader no sistema operacional não usa todos os campos de cabeçalho e tabelas de segmentos, e algum código executável mínimo pode ser colocado diretamente na estrutura do cabeçalho ELF (o código é retirado do primeiro artigo):


 ; tiny.asm BITS 32 org 0x00010000 db 0x7F, "ELF" ; e_ident dd 1 ; p_type dd 0 ; p_offset dd $$ ; p_vaddr dw 2 ; e_type ; p_paddr dw 3 ; e_machine dd _start ; e_version ; p_filesz dd _start ; e_entry ; p_memsz dd 4 ; e_phoff ; p_flags _start: mov bl, 42 ; e_shoff ; p_align xor eax, eax inc eax ; e_flags int 0x80 db 0 dw 0x34 ; e_ehsize dw 0x20 ; e_phentsize db 1 ; e_phnum ; e_shentsize ; e_shnum ; e_shstrndx filesize equ $ - $$ 

Monte e obtenha um ELF de tamanho ... 45 bytes :


  $ nasm -f bin -o a.out tiny.asm $ chmod +x a.out $ ./a.out ; echo $? 42 $ wc -c a.out 45 a.out 

Elfo padrão


Para criar um elfo usando a biblioteca LIEF, você pode seguir os seguintes passos (consulte a receita "Elf-container"):


  • pegue um arquivo ELF simples como modelo;
  • substitua o conteúdo das seções, adicione novas seções;
  • configure os parâmetros necessários (ponto de entrada, sinalizadores).

Em vez de uma conclusão


Adicionando ao artigo, descobrimos que era algo como uma ode à biblioteca LIEF. Mas isso não foi planejado - eu queria mostrar como trabalhar com arquivos ELF usando ferramentas diferentes.


Certamente existem ou precisam de scripts que não foram mencionados aqui - escreva sobre isso nos comentários.


Referências e literatura


Source: https://habr.com/ru/post/pt460247/


All Articles