| ESTUDIO COLECTIVO DE DESPROTECCIONES Última Actualizacion: 25/10/2001 |
Programa | AWave 5.4, Notepad | W95 / W98 / NT
Descripción |
Tipo | Trial de 30 dias
Tipo de Tutorial | [X]Original, []Adaptación, []Aplicación, []Traducción
Url | http://www.fjmsoft.com
Protección | Trial
Dificultad | 1) Principiante, 2) Amateur, 3) Aficionado, 4) Profesional, 5) Especialista
Herramientas | Code SnippetCreator, HexWorkShop v2.5x, SICE 3.XX o superior, MASM 6.X o TASM 5.0, SNIP.ZIP
Objetivo | Romper protecciones de empaquetados.
Cracker | nuMIT_or
Grupo | KuT
Fecha | 20 de Noviembre de 1999
CONTENIDO
· INTRO · PRESENTACIÓN GENERAL DE Snippet Creator · EL PODER: Insertar un objeto nuevo en un archivo PE · Tomando el control desde el principio: pasos para insertar el recorte · ALGUNOS TIPS · Importar una función nueva · El recorte es grande y no quiero aumentar el programa · Perfeccionando programas : Eliminar una limitación de notepad · Observaciones · MALDITAS PROTECCIONES: quebrando a AWAVE v4.5 · Parchando archivos empaquetados con SnippetCreator · Todo el poder: intercepción de llamadas a APIs · El verdadero crack · ALGUNAS PREGUNTAS - ALGUNAS RESPUESTAS · ¿Existen otros programas, además de Snippet Creator? · ¿Por qué mantener el tamaño del archivo parchado es importante? · ¿Las jmp de las apis están siempre en secciones .text? · ¿Qué significan las características de las secciones de un PE? · ¿Qué es una DLL y para qué sirve? ¿cómo se crea?
AL ATAQUE
INTRO
Particularmente veo el mayor potencial de conocer los encabezados del PE en la personalización de programas. Sin un editor de recursos se puede cambiar el comportamiento de los menúes, de los diálogos y mucho más. También podríamos ampliar la funcionalidad de las aplicaciones y mejorarlas. Es un trabajo que podemos hacer "manualmente". Pero antes de hacerlo, creo conveniente aprovechar la oportunidad que nos brinda una herramienta como Snippet Creator de Iczelion. Es una herramienta que requiere cierto conocimiento de programación y, especialmente, conocer sobre los encabezados PE. Ahora que sabemos algo de esto último podemos emplearla. De hecho, el uso de esta herramienta nos permitirá aprender más sobre la estructura y funcionamiento de los archivos con formato PE. Si no tienes conocimientos de programación para W32 en lenguaje ensamblador, este es el momento de empezar: hay un buen tutor en español de Mr.Crimson: http://members.xoom.com/crk10/archivos/MrCrimson.zip. Sólo algunas indicaciones preliminares. Para facilitar la creación de programas es recomendable que configures correctamente el entorno de tu sistema. Para esto, si haz instalado en tu disco duro el MASM distribuido por Hutch (lo puedes bajar de http://members.xoom.com/crk10/archivos), debes agregar las siguientes en tu archivo autoexec.bat: SET BIN = C:\MASM32\BIN SET INCLUDE = C:\MASM32\INCLUDE SET LIB = C:\MASM32\LIB SET PATH = %BIN%;%INCLUDE%;%LIB%;%PATH% En el directorio C:\MASM32\BIN estarán los BINarios ejecutables del programa; en C:\MASM32\INCLUDE, los archivos .INC; y en el directorio C:\MASM32\LIB las LIBrerías. Colocando estas líneas en autoexec.bat, puedes llamar a cualquier archivo en uno de estos directorios desde cualquier directorio del sistema.
PRESENTACIÓN GENERAL DE Snippet Creator
Snippet Creator permite insertar código nuevo en archivos con formato PE y, por lo tanto, es una herramienta útil para personalizar la conducta de las aplicaciones. Funciona como una especie de IDE (Integrated Development Environment: Ambiente de Desarrollo Integrado), brindándonos un entorno propicio para el desarrollo de snippets (recortes) para injertar a nuestro gusto en archivos con formato PE: IMAGEN 1 Primero tenemos que crear un proyecto con el comando "File\New Proyect". En la ventana "New Project Options" colocamos un nombre en el campo Project. NEWPAD, por ejemplo. Hacemos una copia de notepad.exe y la renombramos como NEWPAD.EXE (yo he empleado la versión antigua de Win 95 en el archivo SNIP.ZIP). Pulsamos BROWSE buscamos NEWPAD.EXE. Pulsamos Create. IMAGEN 2 Ahora Snippet Creator ha creado una carpeta en su directorio con el nombre NEWPAD. En ella se crearán: · Un archivo .INI que guarda la información y configuración del proyecto · El archivo fuente .ASM del recorte que escribamos. · Los archivos .OBJ, .EXE y .BIN que se compilarán. Una vez creado el proyecto, puede ser cerrado y abierto de nuevo: ir a File/Open Project. Esto te permitirá abrir un archivo .INF que guarda la información que hemos salvado sobre el proyecto previamente creado. En nuestro caso abre el directorio NEWPAD y abre el archivo NEWPAD.INF. Ahora vayamos a PE Info\View PE header. Se desplegará la caja de diálogo "PE Header Information" correspondiente a NEWPAD.EXE. IMAGEN 3 Encontraremos varios campos que ya el uso de PROCDUMP, si lo hemos utilizado, nos ha hecho familiares. Esta ventana nos ha de servir para editar algunos valores del encabezado. Si no entiendes ni papa, revísate el tutorial Descabezando Archivos Ejecutables Portables, el primero de esta serie. Ahora vayamos a "CView Section Info". Se despliega la caja de diálogo "Section Information". Es idéntica a la ventana "Sectons Editor" de PROCDUMP. IMAGEN 4 Esta ventana permite editar de varias maneras las secciones del encabezado. Pulsemos con el botón derecho del ratón sobre uno de los nombres y se desplegará el siguiente menú: IMAGEN 5 Creo que los nombres son bastante ilustrativos de lo que se puede hacer aquí. Si escogemos"Edit Sections" aparecerá el siguiente diálogo: IMAGEN 6 Es semejante a la ventana "Modify Secton Value" de ProcDump, pero permite elegir características por el nombre de constantes. Basta pisar el botoncito '...' del lado derecho a "Chracteristics". El comando "PEInfo\View Import Fuctions" despliega una ventana con información sobre los módulos y funciones importadas. IMAGEN 7 El comando "PEInfo\View Data Directories" despliega la siguiente caja de diálogo. IMAGEN 8 Permite editar los valores del Directorio de datos.
EL PODER: Insertar un objeto nuevo en un archivo PE
Este programa realmente hace sentir el poder del conocimiento de la estructura de los archivos PE. Insertemos un objeto nuevo en NEWPAD.EXE. Con Snippet Creator podemos colocar el recorte en cualquier parte del PE, sea formando una sección nueva o como código nuevo dentro de una sección ya existente. Esta última opción, que no requiere la creación de una sección nueva, es la mejor porque no incrementa el tamaño del archivo, y es posible siempre y cuando encontremos en alguna de las secciones originales espacio para ello. Veremos que casi siempre hay. Debemos decidir donde colocar el injerto, pero antes determinar cuando ha de ejecutarse esta rutina nueva. La manera más sencilla es hacer que tome el control al inicio del programa. Si elegimos esta opción, tenemos que especificar si la nueva rutina devuelve el control al programa o no. Otra posibilidad es colocar el injerto para que se inicie con otro evento o al iniciar alguna rutina específica. Veamos estas posibilidades.
Tomando el control desde el principio: pasos para insertar el recorte
Como dije, lo más sencillo es hacer que el injerto tome el control al principio y después termine el proceso o retorne el control al programa. Siempre lo más sencillo es injertar un mensaje. De hecho, SC tiene un tutorial (en inglés) que explica esto. Para los que les cuesta el inglés, resumiré lo que explica Icz: 1. Debemos primero hacer unos ajustes: vamos a Action/Options. Verás la siguiente ventana: FIGURA 9 No sé si sea necesario explicarlo, pero tienes que: 1.1. Indicar si usarás MASM o TASM. Son los ensambladores comerciales más populares. 1.2. Para cada uno de ellos, si los tienes, eliges los comandos con sus respectivos comutadores. 1.3. Debes indicar también el directorio donde será creado el proyecto es decir, la rutina que injertarás en el PE. 1.4. OK. 1.5. Si prefieres y no eres tan quisquilloso, simplemente pulsa DEFAULT y se establecerán los valores por defecto. No es una mala opción. 2. Ahora debes crear un proyecto (File/New Project) u opcionalmente determinar el target (Acton/New Target). 3. Luego viene lo interesante: crear el recorte (snippet). Ahora tienes que escribir el código del recorte en la ventana de edición del programa. Los siguientes son más o menos los ejemplos de Icz: Para MASM: include windows.inc include user32.inc includelib user32.lib jmp init Message db "Probando, un, dos, tres. probando",0 init: invoke MessageBox,NULL,addr Message,NULL,MB_OK Para TASM: UNICODE=0 include w32.inc jmp ComienzoReal Message db "Probando, un, dos, tres. probandot",0 ComienzoReal: call MessageBox,NULL,offset Message, NULL,MB_O K Ahora guarda el código escrito ("File/Save Project"). Snippet Creator creará en la carpeta del proyecto un archivo .INF que salvará la información sobre los avances realizados en el proyecto. Es necesario observar que el código anotado arriba sólo funciona si, en este caso, MessageBox es una de las funciones importadas por el archivo target. Para asegurarse de esto, simplemente vamos a la ventana "Import Information" a través de "PEInfo\View Import Fuctions". Hacemos click con el botón derecho del ratón sobre USER32.DLL y revisamos. Ya, aparece la función MessageBox. Newpad la tiene. No hay problemas. ¿Pero si no la tuviera? Pronto veremos este caso. Por ahora estamos tanteando el programilla. 4. El siguiente paso es ensamblar el recorte: pulsa el botón "Assemble" debajo de la ventana de edición o ejecuta el comando "Action/Assemble". Si todo va bien, vamos al siguiente punto. 5. Exportar el recorte. Al ejecutar el comando "File/Export", se abre la caja de diálogo común "SAVE AS". Busca la carpeta del proyecto y salva el archivo .BIN: NEWPAD.BIN, en nuestro caso. Se trata del recorte propiamente dicho. Si lo abres con HIEW.EXE (Hacker View) podrás ver un desensamblado de lo que escribiste. Esto ya es bastante. Puedes tomar el contenido de este archivo y pegarlo manualmente en el sitio indicado del target y ya. Pero SC te puede ahorrar este trabajo. 6. Ahora el momento crucial: injertar el recorte en el target. Para hacer primero debes ejecutar el comando "Acton/Project Options". Esto desplegará la siguiente caja de diálogo: FIGURA 10 6.1. Debes llenar la caja VA con la dirección virtual donde deseas insertar el recorte (snippet). Para esto, hay que encontrar un punto disponible dónde hacerlo. Los sitios posibles son: a. Al final del archivo, como una nueva sección. Esto incrementa el tamaño del archivo. b. Entre las tablas de las secciones y la primera sección. También como una nueva sección. c. En algún espacio no utilizado de una sección. Pienso que esta es la mejor opción ya que no tiene que crearse una nueva sección ni aumenta el tamaño del archivo. Tomaremos la opción c. Abrimos el target con un editor hexadecimal y buscamos un área vacía, llena de ceros. Una vez encontrada, anotamos el desplazamiento donde empieza y el desplazamiento donde termina. En NEWPAD.EXE hayamos un gran espacio en el desplazamiento 4020h. Luego, empleando el comando "PE Info/View Section Info", abrimos el diálogo "Section Information" y en la columna "RAW offset" revisamos en cual sección se halla el espacio que he elegido. Es la sección .data. Luego calculamos la dirección virtual equivalente al desplazamiento dentro del archivo: VA del recorte = (Desplazamiento del recorte - RawOffset de la Sección) + (ImageBase + VirtualOffset de la sección) (00004020h - 00003E00h) + (00400000h + 00006000h) = 00406220h Colocamos este valor en el campo "Snippet VA" del diálogo "Project Options". 6.2. Según sea la opción que hayamos elegido, hay que indicarlo en el área "Patch Options" de la caja de diálogo "Project Options". Elijamos "Patch into Existing Section". 6.3. Después de escoger dónde insertar el recorte, debemos decidir como el recorte ha de tomar el control. Si queremos que el código se ejecute antes del código del archivo objeto o víctima, debemos redirigir el control desde el punto de entrada RVA (opción "Redirect Entry Point RVA"). Con esta opción, Window$ dará el control a tu recorte cuando el archivo esté listo para ser ejecutado. Si queremos que el recorte reciba el control después de que algunas rutinas del archivo objeto se haya ejecutado, debemos elegir entre dos opciones: "redirect-from-code-section" (redirigir-desde-la-sección-de-código) o "redirect-from-call/jmp" (redirigir-desde-call/jmp). La opción "redirect-from-code-section" insertará las instrucciones en la dirección virtual que pasará el control al recorte. Sin embargo, debes tener cuidado al elegir la dirección desde donde redirigirás el control: debe estar en el límite de una instrucción. Por ejemplo, para una sección como esta: :00401007 6819304000 push 00403019 :0040100C 6A00 push 00000000 * Reference To: USER32.MessageBoxA, Ord:0195h | :0040100E E807000000 Call 0040101A :00401013 6A00 push 00000000 puedes escoger 40100C como la dirección de redirección, pero no 40100D. Snippet Creator no será capaz de capturar este tipo de error, así que debes ser cuidadoso en esto. Para pasar el control al recorte, Snippet Creator reemplazará la(s) instrucción(es) en esa dirección virtual con "push [VA del recorte]" y luego"ret". Redirection from call/jmp es otro método. Esta opción requiere que se diga a Snippet Creator donde está la instrucción call/jmp y automáticamente reemplazará la instrucción original call/jmp con la dirección de tu recorte. En este ejemplo, si empleamos esta opción, debemos pasar 40100E como la dirección virtual que redirigirá el control. Snippet Creator almacena esta dirección, la de rutina original, en una variable llamada OriginalLocation. Luego, si se desea entregar de nuevo el control a la rutina original, en el recorte se puede hacer algo como "jmp dword ptr [OriginalLocation]" o "call dword ptr [OriginalLocation]", dependiendo de si originalmente era una llamada (call) o un salto (jump). Si prefieres redirigir el control desde algún sitio en el código existente, puedes almacenar las instrucciones sobre-escritas después de que tu recorte obtenga el control. Lo puedes hacer estableciendo la caja de chequeo (checkbox) restore-overwritten-instruction en la parate inferior del cuadro de diálogo "Project Option". Snippet Creator colocará el código necesario para restaurar las instrucciones originales dentro del recorte. Luego, hay que considerar qué hacer después de que la última instrucción del recorte haya sido ejecutada. Se puede elegir devolver el control al archivo objeto (target) en una dirección virtual específica. Si no, se debería poner alguna instrucción que sería la última del recorte, tal como ExitProcess. En nuestro caso particular, ya hemos calculamos el nuevo punto de entrada: 00406220h. Como queremos que nuestro recorte tome el control al iniciar el programa, elegimos la opción "Redirect Entry Point RVA". Esto dará el control al recorte desde el comienzo. Además queremos que el programa siga su curso después de la ejecución del recorte, entonces elegimos la opción "Return To Program" y en la casilla "Virtual Address" colocamos la dirección del punto de entrada original: 00401000. 6.4. Una vez establecidas las opciones del proyecto, se inserta el recorte en el archivo objeto usando "Action/Patch Target File". Snippet Creator no preservará registros/banderas (flags), es tu responsabilidad. Si algo no funciona como se esperaba, se puede examinar el código fuente del recorte en el directorio del proyecto y ver cómo va la cosa. Esto es todo. Ahora activemos el target (NEWPAD). ¿Qué te parece? Compara el tamaño del NEWPAD.EXE con el de NOTEPAD.EXE. ¡No hay cambios! Icz termina su tutorial recordando que SC puede ser empleado también como un simple editor de archivos PE, lo mismo que ProcDump.
ALGUNOS TIPS
Ya jugamos un poco. Ahora hagamos cosas interesantes. Mi objetivo no es enseñar realmente a manejar este programa, sino aprender de él. De todos modos daré antes unos tips que pueden ser útiles, si se te ocurren más truquitos te agradecería muchísimo que me informes.
Importar una función nueva
En el ejemplo del tutorial de Icz, nos encontramos con que las funciones de la API de W32 que llamamos deben estar incluidas en la tabla de nombres importados del archivo objeto. ¿Por qué? Recordemos la razón por la cual se ha incluido una tabla de nombres importados entre los múltiples encabezados de los archivos con formato PE: Cuando se llama a una función en otro módulo (por ejemplo, GetMessage en USER32.DLL), la instrucción CALL emitida por el compilador no transfiere el control directamente a la función en la DLL. En vez de eso, la instrucción call transfiere control a una instrucción JMP DWORD PTR [XXXXXXXX] que está en la sección .text. La instrucción JMP conduce indirectamente a través de una variable DWORD en la sección .idata. Esta variable DWORD de la sección .idata contiene la dirección real del punto de entrada de la función del sistema operativo. De esta manera, al hacer todas las llamadas a una función dada de una DLL a través de una localización, el cargador no necesita parchar cada instrucción que llama (calls) a una DLL. Todo lo que tiene que hacer el cargador del PE es poner la dirección correcta de la función objeto (target) dentro de la variable DWORD en la sección .idata. Ninguna de las instrucciones call necesitan ser parchadas. Otro es el caso de los archivo NE, donde cada segmento contiene una lista de "fixups" que necesitan ser aplicados al segmento. Si el segmento llama a una función de una DLL dada 20 veces, el cargador debe escribir la dirección de esa función 20 veces en el segmento. Esta es la razón por la cual se utilizan los archivos .LIB al enlazar programas con MASM o TASM. Los archivos .LIB contienen las direcciones de las funciones a ser importadas desde el archivo a crear. Esto viene a colación porque es algo que debemos tomar en cuenta al enlazar los archivos .OBJ y cuando insertamos el recorte en el archivo objeto (target) si trabajamos con Snippet Creator. Aunque ensamblemos con éxito un recorte con Snippet Creator, podemos toparnos con que el archivo objeto (target) no incluye alguna de las funciones importadas por el recorte entre las funciones importadas, cuya lista se encuentra en la sección .idata. Esto puede ser solucionado rápidamente con Snippet Creator. Si es el caso que el archivo objeto no importa alguna función llamada por el nuevo recorte, entonces activamos el comando "Options/Add New Imports" y se abrirá el cuadro de diálogo "Add new import functions": Examinamos (pulsamos el botón "Browse") y buscamos la DLL donde se encuentra la función que deseamos importar. Si no sabes en cuál DLL se encuentra la función que deseas importar, busca en tu "Win32 Programmer's Reference" la información de la función que te interesa y en la parte superior haz click con el botón izquierdo del ratón sobre "QuickInfo". Aquí encontrarás en cual .LIB del sistema se encuentra la función API que llamas. Esta .LIB tiene el mismo nombre de su .DLL correspondiente. Una vez determinado el módulo donde está la función a importar, marcas con el ratón la función que te interesa, pulsas "Add" y ya está importada esta función dentro del archivo objeto (el target). Un ejemplo práctico de esto lo encontraremos un poco más adelante.
El recorte es grande y no quiero aumentar el programa
En estos caso lo mejor, en mi opinión, es implementar una DLL. Así sólo tengo que insertar la llamada a la DLL en el archivo objeto y listo. Si no sabes qué es una DLL, como se programa, y para qué sirve, revisa el final de este documento. Ejemplo: Este tutorial viene acompañado con el archivo NAG.DLL (está en SNIP.ZIP) ¿Qué hace? Ya veremos. Mientras tanto, coloca NAG.DLL en el mismo directorio de NEWPAD.EXE (Siempre que uses este archivo haz un respaldo). Lo que vamos a hacer es que NEWPAD llame a esta DLL y active el programa que está en ella. Esta vez vamos a emplear TASM 5.0. Así que vamos a "Action/Options" y en el diálogo "General Options" elegimos TASM. Abrimos el proyecto NEWPAD y escribimos en la ventana de edición de SC las siguientes líneas de código: ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - extrn GetModuleHandleA:Proc extrn MessageBoxA:Proc extrn LoadLibraryA:Proc extrn FreeLibrary:Proc jmp init hInst dd 0 DLLname db "NAG.DLL",0 init: ; Obtener el manejador del modulo que se está cargando en memoria push 0 call GetModuleHandleA mov [hInst], eax ; Ejecutar el programa en la DLL push offset DLLname ; Cargar en RAM NAG.DLL call LoadLibraryA push eax call FreeLibrary ; Liberar NAG.DLL de la memoria ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Lo que hace este código es obtener el manejador del módulo NEWPAD y ejecutar el código en la DLL. Ejecutado este código se libera la DLL. Ahora ensamblamos el código escrito. Saldrá el mensaje "You use a function that is not present in the target file" (Usas una función no presente en el archivo objeto). Es decir, las funciones que llamamos (LoadLibrary y FreeLibrary) no aparecen en la lista de funciones importadas por NEWPAD. Esto, como ya lo hemos mencionado, es sencillo de solucionar: 1. Vamos a "Action/Add New" Imports y abriremos el cuadro de diálogo "Add new import functions". 2. Examinamos (pulsamos el botón "Browse") y buscamos la DLL donde se encuentra la función que deseamos importar. Buscamos KERNEL32.DLL elegimos LoadLibrary y FreeLibrary. Pulsamos "Add". Agregadas las nuevas funciones importadas, buscamos un espacio en NEWPAD para ubicar el recorte. Ya hemos encontrado que hay espacio en el desplazamiento 4020h del archivo. Ya hemos calculado la dirección virtual correspondiente a este desplazamiento con una fórmula como esta: [RVA de la sección + Image Base] + [offset del recorte - RAW data de la sección] [006000h + 00400000h] + [4020h - 3E00h] = 00406220h Anotamos este valor en el campo "Snippet VA" del cuadro de diálogo "Project Options". Aprovechamos también para elegir "Patch into Existing Section". Como vamos a ejecutar el recorte al inicio del programa, elegimos la opción "Redirect EntryPoint RVA" del diálogo "Project Options" y colocamos en el campo "Virtual Address" la dirección virtual del punto de entrada original: 00401000. Todo listo. Sólo activamos el comando Action/Patch Target, y ya. Ahora activemos NEWPAD: ¡sorpresa! ¿Te gusta?
Perfeccionando programas : Eliminar una limitación de notepad
Ahora vamos a intentar solucionar un aspecto desagradable de NOTEPAD.EXE: el límite que impone al tamaño de los archivos que puede leer. Todos sabemos que si tenemos un archivo ASCII mayor de 65 KB y lo intentamos abrir con NOTEPAD, saldrá un mensaje que dice: "Este archivo es demasiado grande para abrirlo con el block de notas...". Si decidimos abrirlo, el archivo es abierto por WORDPAD.EXE. El mismo límite se presenta cuando estamos escribiendo en archivo y superamos el límite de los 65 KB; sale un mensaje que dice: "No hay suficiente memoria para realizar esta operación...". La razón del límite impuesto al tamaño de los archivos que podemos abrir con NOTEPAD es que el contenido de los archivos que abrimos con este programa es desplegado en una clase de ventana llamada control "Edit". Expliquemos esto. Cuando se hacen programas para Windows con interface gráfica se tienen que crear ventanas.Para crear las ventanas, los programas primero definen una plantilla para la ventana llamada "Clase de Ventana". Luego a partir de esta clase, el programa crea la ventana propiamente dicha. Es decir, las ventanas se crean a partir de plantillas llamadas clases de ventanas. Un programa Windows standard con interface gráfica tiene por lo menos dos partes: un procedimiento principal llamado WinMain y un procedimiento de ventana que podríamos llamar WndProc. La definición de la clase de ventana y la creación de la ventana misma se realiza en WinMain. La manera como se comporta el programa para cada una de las acciones que realicemos sobre él (abrir un archivo, copiar un fragmento, etc.) está codificada en WndProc. En pseudo código: WinMain: ClaseDeVentana = Registrar_ClaseDe(Ventana) Ventana1 = CrearVentanaCon(ClaseDeVentana) Dispensador de Mensajes(Ventana1) WndProc: Para acción1 en (Ventana1), hacer: reacción1 Para acción2 en (Ventana1), hacer: reacción2 etc... Lo cierto es que un programa Windows tiene que registrar una clase de ventana que sirva como interface gráfica. Generalmente, los programas implementan más de una ventana, creándose así una jerarquía de ventanas. Tenemos entonces una ventana principal llamada ventana padre y una o más ventanas llamadas ventanas hijas. Notepad, por ejemplo, posee una ventana principal, sobre la que están los menúes, y por lo menos una ventana hija, la ventana de edición donde leemos y escribimos archivos. Windows suministra varias clases de ventanas predeterminadas, que el programador no necesita registrar. Estas ventanas predeterminadas son los llamados controles. Los controles incluyen botones, casillas de chequeo, algunos diálogos y ventanas de edición. Nos interesa este último tipo de control: el control "Edit". Se trata de una ventana que nos permite editar texto, la ventana de edición empleada por NOTEPAD. Este control presenta un inconveniente: no puede editar archivos mayos de 65 KB. Afortunadamente, Windows suministra otro tipo de ventana de edición que salva este límite: el control "RichEdit". Entonces, para superar el problema de limitación de NOTEPAD sólo tenemos que reemplazar un control, el control Edit, por otro, el control RichEdit. ¿Cómo lo hacemos? Veamos como un programa implementa un control de ventana hija Edit. 1. Registrar la clase de la ventana principal. Se emplea la función RegisterClassExA. Esto se realiza en el procedimiento WinMain. 2. Crear la ventana principal a partir de la clase registrada. Se emplea la función CreateWindows, la cual regresa en el registro EAX un valor, llamado el manejador de ventana (hWnd), que permite una manipulación ágil de la ventana creada. La llamada a esta función (generalmente realizada inmediatamente después de registrar la clase de ventana en WinMain) hará que el sistema envíe una orden a la ventana que se está creando. Esta orden se llama WM_CREATE y es procesada por WndProc. 3. Al procesar el mensaje WM_CREATE, WndProc crea la ventana hija a partir de la clase predeterminada control de ventana hija Edit. Emplea una vez más la función CreateWindows. Esta función tiene entre sus parámetros uno donde debe incluirse el manejador de la ventana padre (hWnd, el 9no. parámetro) y otro que debe incluir el nombre de la ventana (por ejemplo, Edit; es el 2do. parámetro). ¿Qué encontramos aquí? Bueno, que para reemplazar un control de edición por otro sólo tenemos que reemplazar el nombre pasado como parámetro en la llamada a la función CreateWindow que crea la ventana de edición. Así de sencillo. Manos a la obra. Abramos NOTEPAD con un heditor hexadecimal. Busquemos la cadena "Edit". Encontraremos dos. Una en el desplazamiento 3F90h y otra en 3FD4h. Cualquiera sea la cadena empleada por el programa para crear la ventana de edición se hará a través de un llamado a CreateWindows que debe incluir una instrucción con la siguiente forma: push offset dir_virtual_del_nombre_del_control ------ call CreateWindow En optal, la instrucción que nos interesa tiene la forma: 68 XX XX XX XX, donde 68 es la instrucción PUSH y XX XX XX XX es la dirección virtual donde se encuentra la cadena con el nombre del control de edición. Calculemos la dirección virtual para las cadenas con el nombre Edit. Las cadenas se encuentran en la sección .data para la cual tenemos los siguientes valores: RVA Offset = 6000h RAW Data = 3E00h La base de la imagen es 00400000h. Ahora procedamos: Dir Virtual = (Desplazamiento del dato - RawData) + (ImageBase + RVA Offset) (3F90h - 3E00h) + (00406000h) = 00406190h (3FD4h - 3E00h) + (00406000h) = 004061D4h Ahora veamos si encontramos la instrucción que buscamos: 1. 68 90 61 00 40 -> push 00406190 2. 68 D4 61 00 40 -> push 004061D4 En el desplazamiento 1BA5h encontraremos 68 90 61 00 40 68 ... Esta debe ser la instrucción. Calculemos la dirección virtual correspondiente a esta instrucción. Suponemos que está en la sección .text, que es la sección de código, con: RVA Offset = 1000h RAW Data = 0400h Aplicando la fórmula tenemos: Dir Virtual = (Desplazamiento del dato - RawData) + (ImageBase + RVA Offset) (1BA5h - 0400h) + (00401000h) = 004027A5h Podemos verificar con SICE. Cargamos en el loader NEWPAD.EXE. Cuando se inicia el programa hacemos BPX 4027A5 y F5. La ejecución se detendrá en: 004027A5 6890614000 push 00406190h ; Dir de la cadena "Edit" 004027AA 6800020000 push 00002000h 004027AF FFD7 call EDI ; Llamar a CreateWindowExA Si subimos en la ventana de código de SICE encontraremos: 00402719 8B3DC4734000 mov edi,[USER32!CreateWindowExA] Si hacemos D 00406190, veremos en la ventana de datos de SICE: Edit. Hemos encontrado donde es creada la ventana de edición en NEWPAD. Lo que tenemos que hacer es colocar en alguna parte de NEWPAD la cadena "RichEdit", calcular su dirección virtual y reemplazar en la instrucción 4027A5h la dirección virtual empujada a la pila por la nueva. Abramos NOTEPAD con el editor hexadecimal. Hayamos un espacio en 41E0h, dentro de la sección de datos. Escribimos ahí "RichEdit". La dirección virtual equivalente de este desplazamiento es: (41E0h - 3E00) + 00406000 = 004063E0h Ahora reemplazamos en el desplazamiento 1BA5h la cadena 68 90 61 40 00 -> push 00406190 por la cadena: 68 E0 63 40 00 -> push 040063E0 Sin embargo, con esto no es suficiente. Los controles comunes se encuentran en el archivo COMCTL32.dll. Pero el control RichEdit se encuentra en RICHED32.DLL. Así que tendremos que cargar en el espacio de direcciones del proceso de NEWPAD a RICHED32.DLL. Aquí entra en juego Snippet Creator. Después de las configuraciones de rutina escribimos la siguiente rutina en la ventana de edición de Snippet Creator: extrn GetModuleHandleA:Proc extrn LoadLibraryA:Proc extrn FreeLibrary:Proc jmp init hInst dd 0 DLLname db "NAG.DLL",0 RichEditName db "RichEd32.dll",0 init: push 0 call GetModuleHandleA mov [hInst], eax ; Cargar la librería para el control RichEdit push offset RichEditName call LoadLibraryA Ensamblamos este código (con TASM) y parchamos NEWPAD. Ahora podremos abrir archivos sin limitación de tamaño. Lo único es que cuando sobrepasemos ese tamaño, no podremos escribir sobre NEWPAD. Podríamos cambiar nuestro código de esta manera, y también funcionaría: extrn GetModuleHandleA:Proc extrn LoadLibraryA:Proc jmp init hInst dd 0 RichEditName db "RichEd32.dll",0 RichEditStr db "RichEdit",0 init: push 0 call GetModuleHandleA mov [hInst], eax ; Cargar la librería para el control RichEdit push offset RichEditName call LoadLibraryA ; Reemplazar las instrucciones lea esi,RichEditStr ; Dir de la cadena mov edi,004027A5h ; Dir de la instrucción a cambiar mov dword ptr [edi+1],esi ; Nueva dir De esta manera nos liberamos de buscar donde escribir la cadena "RichEdit" y del cálculo de su dirección virtual. Pero para que este parche funcione necesitamos cambiar los atributos de la sección de código .text, si no al ejecutar este código se producirá un error de protección. Abrimos en Snippet Creator el cuadro de diálogo "Section Information" con el comando "PE Info/View Section Info". Sobre la sección .text hacemos click con el botón derecho del ratón y elegimos de ese menú "Edit Section". Vemos que el campo "Characteristics" = 60000020. Si pulsamos el botón "..." se desplegarán los atributos de esta sección. Veremos que no se incluye el atributo de escritura, así que hacemos click en IMAGE_SCN_ MEM_WRITE. Cerramos esta ventana y veremos "Charactheristics" = E0000020. Ahora sí se puede sobreescribir esta sección cuando está en memoria. Todavía nos quedan un par de mensajes fastidiosos. Los que hemos comentado al comienzo. Librémonos de ellos. NAG 1: Nos libramos de la primera ventana si reemplazamos la instrucción 00402E8B 81FEFFFF0000 cmp esi,0000FFFFh ; El archivo a abrir es mayor a 65 KB? 00402E91 0F8FEC010000 jg 004083 ; Si es mayor, no cargarlo y mostrar mensaje Por: 00402E8B EB 0A jmp 402E97h ; Saltarse la prueba y abrir archivo NAG2: Nos libramos de la segunda ventana si reemplazamos la instrucción: 00401E1A 7415 jz00401E31h por: 00401E1A EB10 jmp 401E2Ch En vez de parchar con el editor hexadecimal, agreguemos las siguientes instrucciones a nuestro recorte: ;Eliminar NAG1: ¿abrir con WORDPAD? mov edi,402E8Bh mov word ptr [edi],00AEBh ;Eliminar NAG2: No hay memoria para escribir mov edi,401E1Ah mov word ptr [edi],010EBh Ahora sí. Hemos roto los límites de lectura de NEWPAD. Nos quedaría todavía romper los límites de escritura... Todo el recorte para romper los límites de tamaño de archivo que puede abrir notepad sería: extrn GetModuleHandleA:Proc extrn LoadLibraryA:Proc jmp init hInst dd 0 RichEditName db "RichEd32.dll",0 RichEditStr db "RichEdit",0 init: push 0 call GetModuleHandleA mov [hInst], eax ; Cargar la librería para el control RichEdit push offset RichEditName call LoadLibraryA ; Reemplazar las instrucciones lea esi,RichEditStr ; Dir de la cadena mov edi,004027A5h ; Dir de la instrucción a cambiar mov dword ptr [edi+1],esi ; Nueva dir ; Eliminar NAG1: ¿abrir con WORDPAD? mov edi,402E8Bh mov word ptr [edi],00AEBh ; Eliminar NAG2: No hay memoria para escribir mov edi,401E1Ah mov word ptr [edi],010EBh
Observaciones:
Bueno, quisiera decir que ahora sí tenemos un block de notas más potente. Pero para que esta afirmación sea una realidad tenemos que eliminar todavía algunos bugs remanentes: · El límite de los 65 KB ha sido sólo superado para lectura, no para escritura. · Cuando pegamos un fragmento de texto desde el ClipBoard, se duplica el pegado. Son unos bugs importantes. Con un poco de trabajo podemos corregirlo. Pero dado que nuestro interés es aprender a inyectar código nuevo en archivo PE usando Snippet Creator, el trabajo que invertiríamos corrigiendo estos bugs preferiríamos usarlo haciendo nuestro propio editor, por supuesto, en lenguaje ensamblador, para que sea más rápido, pequeño y potente que NOTEPAD. Realmente este camino sería el más fácil. Esta vez la inserción del recorte ha aumentado un poco el tamaño del archivo. ¿Por qué? porque hemos insertado una nueva función a importar: LoadLibraryA. Snippet Creator ha creado una nueva sección al final del archivo. Por este motivo, sería mejor encontrar otro método que nos permita evitar esto. Posiblemente, si nos tomamos un tiempo podremos resolver este problema manualmente. Mantener el tamaño del archivo parchado es importante y en todo caso debe evitarse. Esto permite el uso de motores creadores de rompedores (CRACKZ). Además, hay situaciones donde, por más cuidadosos que seamos al crear una nueva sección, el cambio de tamaño deteriorará el archivo.
MALDITAS PROTECCIONES: quebrando AWAVE v4.5 Parchando archivos empaquetados con SnippetCreator
No soy ya muy asiduo a la eliminación de protecciones. Mi actividad se ha dirigido hacia otro oficio: mejorar los programas y poner al acceso de quien lo quiera de la posibilidad de hacerlo. Peor que desproteger un programa es hacerlo mejor y regalarlo. ¿Se puede hacer? Ya hemos visto que es posible al parchar NOTEPAD. Sin embargo haré un excepción. Voy a intentar desproteger un programita llamado AWAVE v4.5. Es un programa ejemplar para mostrar como romper protecciones de archivos ejecutables empaquetados a través de la inserción de código nuevo en ellos empleando Snippet Creator. Quiero subrayar que no tengo nada contra el programador que creó este buen programa. Por el trabajo que me dio, tengo la impresión de que sabía lo que hacía. Yo aconsejaría que quienes deseen usar el programa, lo registren. Si yo pudiera lo haría. Pero mi objetivo no es aprovechar el programa sino enseñar el estilo de romper protecciones que estoy implementando. Para romper las protecciones de archivos empaquetados, la táctica empleada generalmente es esperar que el archivo se despliegue en memoria y sobreescribir la rutina de protección en tiempo de ejecución. Esto comúnmente se ha hecho con un cargador (loader) que atrapa una llamada a alguna API de W32 que realice el programa, y entrega el control a la rutina que desprotegerá el programa: el mismo cargador tomará el control y cambiará en memoria el esquema de protección. El uso de loaders me parece que presenta una dificultad: en este método entran en juego dos procesos distintos, el del loader y el del programa mismo. Esto dificulta las cosas y nos obliga a escribir un número significativo de líneas de código. Las funciones de W32 para escribir procesos remotos, son complejas. El loader puede tener fácilmente un tamaño de 100 KB, especialmente si no lo escribimos en lenguaje ensamblador. Si hacemos que la rutina que rompe las protecciones del programa se ejecuten en su propio proceso, me liberaré de la carga que implica enganchar instrucciones en procesos remotos, sobrescribir y leer procesos remotos, etc. Esto lo podemos lograr simplemente insertando la rutina desprotectora en el mismo ejecutable del programa. Veamos el caso particular de AWAVE v4.5. El programa presenta dos limitaciones engorrosas. La primera es un NAG que aparece al comienzo y que recuerda que se trata de un programa no registrado y retarda el inicio del programa. La otra protección importante es que el programa no permite guardar más de un archivo por sección. Hay un par de cuestiones secundarias: son dos cadenas, una dice "Unregistered copy" y aparece en la parte izquierda del área cliente; la otra dice "(Unreg)" y aparece en la "Caption" de la ventana. Aunque no es tarea fácil, eliminaremos estas molestias. Si intentamos cargar el programa con SICE, no se disparará la ventana del depurador porque el programa está empaquetado y se han cambiado los atributos de las secciones. Entonces creamos en SnippetCreator un nuevo proyecto: awave, cuyo target sería awave.exe. Revisamos los atributos de las secciones y cambiamos los de las secciones .text y .adata, que son, respectivamente las secciones de código ejecutable y la rutina de descompresión. El punto de entrada está en esta última sección. Los atributos deben ser E0000020. Cambiados los atributos, encontramos que ahora si se dispara la ventana de SICE cuando cargamos awave.exe con SICE. Tenemos que buscar varias direcciones: 1. La dirección donde finaliza la rutina de descompresión. Cuando el programa llega a este punto, ya el archivo está totalmente descomprimido en la memoria. 2. Las direcciones de los puntos que deseamos cambiar o parchar. 3. El desplazamiento en el archivo donde escribiremos el recorte nuevo. Esto no se hace con SICE sino con un editor hexadecimal. Como el archivo está empaquetado, existen diferencias sustanciales entre la lista muerta que es el archivo mismo y el código vivo ya desempacado que vemos con SICE. Por este motivo no sirve establecer el desplazamiento en el archivo correspondiente a las direcciones de memoria donde deben hacerse cambios. Así que la estrategia es agregar una rutina al programa que sobreescriba en tiempo de ejecución las rutinas que queremos cambiar. Otro cambio que debemos introducir al archivo es el concerniente a la instrucción que entregará el control a nuestro nuevo recorte. Determinemos el desplazamiento del recorte. Con el editor hexadecimal podemos ubicar un espacio vacío en el desplazamiento 0004 3140h. A partir de él y los calculamos la dirección virtual de su ubicación utilizando la fórmula: Dir Virtual = (Desplazamiento del dato - RawData) + (ImageBase + RVA Offset) Los datos que necesitamos para este cálculo los hallamos con el mismo Snippet Creator. En Snippet Creator activamos el comando "Action\New Target". Pulsamos BROWSE y ubicamos AWAVE.EXE y pulsamos SAVE. Ahora activamos "PE Info/View PE header". Encontramos que: ImageBase = 0040000h Luego activamos "PE Info/View Section Info". Para la sección .text encontramos: RawData = 1000h RVA Offset = 1000h RawSize = 042200h Comprobamos que es en esta sección donde se encuentra el desplazamiento para nuestro injerto. Ahora calculemos la dirección virtual: (43140h - 1000h) + (400000h + 1000h) = 00443140h Debemos entregar el control a esta rutina cuando el archivo ya esté desempacado. Con SICE hemos ubicado esta rutina: 0050B54B 0385AC504400 add eax,[ebp+004450AC] 0050B551 5B pop eax 0050B552 ..... 0050B558 61 popad 0050B559 7508 jnz 0050B55B B801000000 mov eax,1 0050B560 02C000 ret 0Ch 0050B563 50 push eax 0050B564 C3 ret Debemos insertar en alguna parte de este código la siguiente instrucción, que entregará el control al recorte: 68 00 44 31 40 push 00443140h C3 ret Como vemos, la instrucción consta de seis bytes. Analizando el código de más arriba, vemos que la instrucción en 50B54B tiene ese tamaño, así que tomamos esa. Calculamos el desplazamiento en el archivo correspondiente a esta dir. virtual y es 05454Bh. Si revisamos con el editor hexadecimal este desplazamiento veremos que no se corresponden los valores con los que vemos en SICE. Lo que ocurre, me imagino, es que se trata de un desempacador compuesto de dos partes. El desempacador mismo está empacado. Hay pues dos desempacadores, uno desempaca el desempacador del programa. Así que necesitamos una dirección más: la dirección final del primer desempacador o ubicar una punto en el primer desempacador donde la instrucción que nos toca ya esté desempacada. Ubicada esta dirección, pasamos desde aquí el control al recorte; hacemos que el recorte escriba la instrucción 0050B54B para que vuelva a pasar el control al recorte, que romperá ahora las protecciones sobre el archivo desplegado. Para hallarla, cargamos el ejecutable en SICE y hacemos: D 50B54B Esto desplegará en la ventana de datos de SICE el contenido en hexadecimal que hay en 0050B54B. Luego ejecutamos F10 hasta ver en la ventana de datos de SICE algo como esto: 0385AC504400. Yo he determinado esta dirección: 0050B0B7 8B85B5504400 mov eax, [ebp + 004450B5] Como ven, la instrucción tiene seis bytes. El desplazamiento de esta dirección en el archivo es 0540B7h. Con el heditor hexadecimal escribimos en esta instrucción: 68 40 31 44 00 C3 que es el equivalente octal de las instrucciones "push 443140h" - "ret". Cuando se ejecute el programa y llegue a este punto, pasará el control a las rutinas escritas en el desplazamiento 43140h del archivo, correspondiente a la dirección virtual 00443140h del programa. La rutina del recorte deberá incluir una rutina que restaure la instrucción que hemos modificado (en 0050B0B7) y que modifique el final del segundo desempacador, cuando el programa ya está totalmente desempacado, para que se entregue el control de nuevo a una rutina que introducirá por fin los cambios definitivos en el programa. Tenemos ya las siguientes direcciones: 1. Dirección de la instrucción que entregará el control al recorte. Final del primer desempacador: 50B0B7h. La llamamos VA1. 2. Dirección del recorte en el archivo: 00443140h. VA2. 3. Dirección de la instrucción que devolverá de nuevo el control al recorte 50B54Bh. La llamamos VA3. Ahora, queda romper las protecciones para obtener las últimas direcciones: las que hay que cambiar para desproteger el programa. Necesito estas direcciones para escribir el código del recorte. Por razones de tiempo no entraré en detalles y las daré de inmediato: 1. Eliminar el NAG del comienzo: Reemplazar: 0045041E 8B1584F84A00 mov edx,[004AF884] por: 0045041E EB19 jmp 00450435 Con esto saltamos por encima de la llamada a DialogBoxParam que despliega el NAG del comienzo. 2. Permitir que el programa guarde más de un archivo en cada sección: Reemplazar: 0045F0FD 8A08 mov CL,[eax] por: 0045F0FD EB08 jmp 455F0B Así saltamos la prueba que revisa si ya se ha guardado un archivo antes de la llamada a GetSaveFileName. 3. Eliminar cadena "Unregistered copy": Colocar en 004A2100 un 00h. Esto coloca cero al comienzo de una cadena ASCIIZ. 4: Eliminar cadena "(Unreg)": Colocar en 0049FCD3 un 00h. El código de parche rompedor sería entonces algo como esto: ; -------------------------------------------------------------------------------------------------- .386 locals jumps .model flat,STDCALL jmp init: OldInstruction1 db 08Bh,085h,0B5h,050h,044h,000h OldInstruction2 db 003h,085h,0ACh,050h,044h,000h NewInstruction2 db 068h,000h,004h,040h,000h,0C3h NewInstruction3 db 0EBh,019h PatchVA1 dd 0050B0B7h PatchVA2 dd 0050B54Bh init: ; Restaurar en 50B0B7h "add eax [ebp+004450AC]" pushad lea esi,OldInstruction1 mov edi,PatchVA1 mov ecx,6 rep movsb ; Escribir en 50B54Bh "push 00400400h - ret" lea esi,NewInstruction2 mov edi,PatchVA2 mov ecx,6 rep movsb popad push 0050B0B7h ret write_code: ; Resaturar código en 50B0B7h pushad lea esi,OldInstruction2 mov edi,PatchVA2 mov ecx,6 rep movsb ; Eliminar NAG: Saltarse la llamada a DialogBoxParamA mov edi,0045041Ah lea esi,NewInstruction3 mov ecx,2 rep movsb ; Permitir guardar más de un archivo mov edi,455EFDh mov byte ptr [edi],0EBh ; regresar popad push 0050B0B7h ret ; ------------------------------------------------------------------------------------------------- Hemos agregado aquí: 1. El código que restaura la instrucción que cambiamos en el primer desempacador. 2. La rutina que sobrescribe la instrucción al final del segundo desempacador. 3. La rutina que restaura la instrucción anterior. Antes de escribir este código en la ventana de edición de SC y ensamblarlo, necesitamos configurar el programa. En las opciones generales ("Action/Options), he empleado TASM. En las opciones del proyecto ("Action/Project Options")debes colocar la dirección del recorte: "Snippet VA" = 00443140. Después de gran cantidad de pruebas, noté que no se pueden escoger ciertas opciones ya que por algún motivo dañan el ejecutable. Así que parte del trabajo tendremos que hacerlo manualmente. Debemos escoger las siguientes opciones, para evitar que Snipet Creator escriba sobre el ejecutable: · Patch Into Existing Section · No Redirection · Don't Return Control to Program Otro cosa importante que debemos hacer es cambiar los atributos de las secciones que serán sobre-escritas durante la ejecución del proceso. De lo contrario, se cometerá error de protección. Estas secciones son, para awave, .adata, .text y .data. Esto lo hacemos fácilmente con SC. Abrimos "Section Information" ("PE Info/View Section Info"). Con el botón derecho del ratón pulsamos sobre cada una de estas secciones. Elegimos "Edit Section" en el menú. Saldrá la ventana "Modify Section Values". Pulsamos el botón "..." al lado de "Caractheristics" y en la ventana "Section Characteristics" establecemos "IMAGE_SCN_MEM_WRITE", lo cual permitirá que se pueda sobrescribir la sección cuando está en memoria.. SAVE. Ahora escribimos el código de arriba, lo ensamblamos y lo exportamos. Abrimos el archivo .BIN exportado con el editor hexadecimal lo copiamos y lo pegamos cuidadosamente en el archivo ejecutable en la dirección que hemos elegido para el recorte. No debemos copiar de más ni de menos; tampoco debemos comernos algún byte cuando peguemos el nuevo recorte, cualquier mínimo error deteriora el ejecutable. Antes de hacer cualquier otro cambio ejecuta el programa para constatar que no lo haz deteriorado. El copiado del recorte he tenido que hacerlo manualmente porque en varias ocasiones se me deterioró cuando lo hice con SC. Ahora escribimos el siguiente código en el desplazamiento correspondiente al final del primer desempacador: en la dirección 00500BB7h cuyo desplazamiento en el archivo es 0540B7h: 68 00 40 31 44 00 = push 00443140h C3 = ret No ejecutes el programa todavía. Esto debería marchar bien, pero no hemos contado con que el espacio que hemos elegido para colocar el recorte será sobrescrito cuando el programa quede desempaquetado (grrr...). Entonces, cuando por segunda vez el programa original devuelva el control al recorte ya en la dirección del recorte no estará el código que escribimos sino el código del programa original. Podríamos hacer muchas pruebas y cálculos para determinar si podemos colocar el recorte en alguna sección donde no vaya a ser sobrescrito por el archivo original al desempacarse. Si creamos una sección nueva tenemos grandes posibilidades de deteriorar el archivo además de que aumentamos su tamaño. Así que lo que hice fue implementar una DLL. Esto facilita las cosas enormemente. Ahora la estrategia es re-escribir el recorte para que sólo haga los siguiente: 1. restaurar la dirección original 2. cargar nuestra DLL en el espacio de direcciones del proceso. 3. cambiar la dirección del final del segundo desempacador para que pase el control a la DLL: ; ---------------------------------- Código del recorte --------------------------------- extrn LoadLibraryA: Proc extrn GetProcAddress: Proc jmp init DllName db "aw_patch.dll",0 FunctionName db "patch",0 OldInstruction db 08Bh,085h,0B5h,050h,044h,000h NewInstruction db 068h,000h,000h,000h,000h,0C3h NewInstruction3 db 0EBh,019h FunctionName db 'patch',0 init: ; Restaurar antigua instrucción pushad mov edi,0050B0B7h lea esi,OldInstruction mov ecx,6 rep movsb ; Cargar librería y obtener su dirección en la memoria lea esi,DllName call LoadLibraryA,esi call GetProcAddress,eax,offset FunctionName ; Escribir nueva instrucción: entregará el control a la DLL mov edi,0050B54Bh lea esi,NewInstruction mov dword ptr [esi+1],eax ; Dir de DLL en la memoria mov ecx,6 rep movsb ; Regresar popad push 0050B0B7h ret ; ------------------------------------------------------------------------------------------------ Esto nos abre enormes posibilidades ya que nos permite disfrutar de todas las ventajas de usar DLLs. Ahora sólo será necesario colocar el recorte que cargue la DLL en el espacio de direcciones del proceso y luego el código de la DLL hará lo que, por razones de espacio, no puede hacer el recorte. Esto nos permitirá hacer cambios posteriores sin tener que tocar para nada el archivo original. Para introducir un cambio sólo necesitamos cambiar el código de la DLL. ¡Y todo esto sólo con un pequeño parchesito que carga la DLL en la memoria del proceso original!. La DLL debe hacer lo siguiente: 1. restaurar la dirección original. 2. realizar todos los parches. ; ---------------------------------- Código de la DLL ---------------------------------------- .386 locals jumps .model flat,STDCALL extrn GetModuleHandleA:Proc extrn GetProcAddress:Proc IDOK equ 1 IDM_ABOUT equ 0105 public patch public RestaureDialog .data OldInstruction db 003h,085h,0ACh,050h,044h,000h NewInstruction db 0EBh,019h ModuleName db 'aw_patch.dll',0 FunctionName db 'RestaureDialog',0 .code Start: dll proc instance:DWORD, reason:DWORD, reserved:DWORD mov eax,1 ret endp dll ; Rutina que parcha la víctima patch: ; Resaturar código en 50B54Bh pushad lea esi,OldInstruction mov edi,0050B54Bh mov ecx,6 rep movsb ; Eliminar NAG: saltarse la llamada a DialogBoxParamA mov edi,0045041Ah lea esi,NewInstruction mov ecx,2 rep movsb ; Permitir guardar más de un archivo mov edi,455EFDh mov byte ptr [edi],0EBh ; regresar popad push 0050B0B7h ret ; ----------------------------------------------------------------------------------------------- Con esta estrategia también se podrían ampliar enormemente las funcionalidades del programa. Ahora escribimos el nuevo recorte, lo ensamblamos y los exportamos. Abrimos awave.bin con el editor hexadecimal y lo pegamos en la dirección correspondiente. Ensamblamos y enlazamos aparte nuestra DLL. Para ello pueden emplearse los siguientes archivos: ; -----------------------------------aw_patch.def----------------------------------------------- LIBRARY aw_patch EXPORTS MyAboutDialog EXPORTS patch DESCRIPTION 'ASM program' EXETYPE WINDOWS CODE PRELOAD MOVEABLE DATA PRELOAD MOVEABLE MULTIPLE ; -------------------------------------------------------------------------------------------------- ; ---------------------------------- makefile (TASM)-------------------------------------------- NAME = aw_patch OBJS = $(NAME).obj DEF = $(NAME).def RCS = $(NAME).rc RES = $(NAME).res IMPORT=C:\TA\lib\import32 TASMDEBUG=/zi LINKDEBUG=/v $(NAME).DLL: $(OBJS) $(RES) $(DEF) tlink32 /Tpd /aa /c $(OBJS),$(NAME),, $(IMPORT), $(DEF), $(NAME) .asm.obj: tasm32 /ml /m2 $&.asm .rc.res : brc32 -r $(NAME).rc ; ------------------------------------------------------------------------------------------------- Con esto sólo tenemos que ejecutar desde la línea de comando, mientras apunta al directorio donde está los archivos fuentes ( C:\temp\aw_parch, por ejemplo), el comando MAKE, suponiendo que tenemos en entorno el patch hacia el directorio donde tenemos los binarios del ensamblador. Una vez creada nuestra DLL, la colocamos en el mismo directorio del recorte. Ensamblado el recorte y pegado en la dirección indicada de la víctima, ejecutamos finalmente awave.exe: ¡Ja, ja!
Todo el poder: intercepción de llamadas a APIs
El plan que he trazado ha tenido el objeto de ir preparando el terreno de un próximo proyecto de indagación: intersección de procesos. Ya he señalado un poco las dificultades de esta técnica, que como la inserción de recortes, es buena para personalizar programas. Sin embargo, las dificultades que he mencionado no disminuyen las grandes posibilidades del parche de procesos, por ejemplo el enganche de llamados a funciones API de W32. Así que imaginen qué sería combinar ambas técnicas: injertos en los PE que intercepten los procesos y los hilos de la aplicación. Expliquemos esto un poco. Para intervenir sobre un programa en ejecución (a esto es lo que llamamos proceso) debemos interceptarlo. La intercepción de procesos necesita primero obtener el manejador del proceso. Debido a que el espacio de memoria asignado a cada proceso es algo a lo que teóricamente no debería poder accederse desde otro proceso, es una tarea un poco complicada obtener el manejador de un proceso remoto e interceptarlo ese proceso desde otro proceso. Si lo hacemos desde un recorte dentro del mismo archivo PE, se facilita la tarea, ya que la intercepción se hace desde el mismo proceso. Sólo tenemos que interceptar las llamadas a las funciones API que deseamos redirigir a alguna otra rutina que también insertemos dentro del archivo objeto. Empleando este método no necesitaríamos un cargador (LOADER), ya que la rutina que intercepta y la que toma el control en la intersección se encuentran en el mismo PE y es arrancada junto al programa cuando es cargado. Una de las maneras de hacer la intercepción es colocar en la variable DWORD que emplea la instrucción de tipo JMP DWORD PTR [XXXXXXXX] para llamar a las API en DLLs externas, la dirección de la rutina nueva. Cada vez que se llame en el programa a la API elegida, muestro recorte tomará el control. Esto no es una ficción y voy a comprobarlo. Algo que no hemos corregido en el rompedor para awave 5.4 es el diálogo ABOUT. Se trata de la misma ventana que aparece al iniciar el programa. Ahora no aparece. Escribí un código para la DLL que, en vez de saltarse la llamada al NAG del inicio, pasaba de nuevo el control a la DLL para que ésta restaurase de nuevo la llamada al diálogo ABOUT. El problema es que esta ventana delata todavía que el programa es Shareware y no está registrado. Una estrategia para mejorarlo, en cuanto a tamaño y contenido, podría ser meternos en la sección de recursos del archivo (.rsrc) y analizar las posibles modificaciones. Luego agregaríamos código a nuestra DLL para que parche la sección de recursos en memoria.Es un acercamiento importante al problema, pero para hacer esto necesitamos conocimiento de la estructura de la sección de recursos de un PE, cuestión que he dejado para la tercera parte de nuestro estudio. Implementaré otra estrategia. Para mostrar la ventana ABOUT, awave llama a la API DialogBoxParamA. Lo que haré es algo que comúnmente hacen los loaders: interceptar las llamadas a esta función y desplegar otro diálogo creado por mí. Para interceptar la llamada, debemos recordar que al cargar el programa W32 coloca en alguna parte de la sección .idata la dirección en RAM donde se encuentran cargadas las funciones que el programa importa. Luego llama a estas funciones con una instrucción del tipo "JMP DWORD PTR [XXXXXXXX]". El valor de XXXXXXXXXX es la dirección virtual donde el cargador ha puesto la dirección en memoria donde inicia la función importada. Una llamada a esa función puede ser Call yyyyyyyy, donde yyyyyyyy es la dirección donde está la instrucción del tipo "JMP DWORD PTR [XXXXXXXX]" que pasa el control a la API. Para encontrar donde el cargador guarda la dirección de DialogBoxParamA, hacemos en SICE: BPX DialogBoxParamA. Abrimos awave, ya crackeado inclusive. Llamamos a la ventana ABOUT. F11 y F12 hasta llegar al punto donde se llama a DialogBoxParamA: 00450425 6A00 push 0 00450426 6840044500 push 00450440 0045042B 51 push ecx 0045042C 6A22 push 22h 0045042E 52 push edx 0045042F FF151CB24800 call [User32!DialogBoxParamA] Vemos que la llamada a DialogBoxParamaA equivale a: call [004824CB] Hacemos ahora D 4824CB y vemos en la ventana de datos una serie de valores DWORD: BFF6DC9 BFF64B27 BFF61A62 BFF6456D Estos valores son punteros a las entradas de varias funciones API importadas por AWAVE. La primera de ellas, la que se encuentra en la localidad 4824CBh y corresponde a la dirección donde inicia la función DialogBoxParamA: BFF6 0000 = Base del módulo con las funciones a importar: USER32.DLL 0000 6DC9 = RVA del punto de entrada de una función particular: DialogBoxParamA. Encontramos que el programa llama directamente a las funciones que importa, sin utilizar de manera indirecta la instrucción de tipo JMP DWORD PTR [004824CB], que sería el caso al llamar a DialogBoxParamA. El programador evitó esto y llamó directamente a esta función con call [004824CB]. Bueno, ya tenemos la dirección donde está guardada la dirección del punto de entrada de DialogBoxParamA: 004824CBh. Para interceptar esta función simplemente hacemos que nuestra DLL ponga en esta localidad un valor DWORD con la dirección del punto de entrada de la nueva función que haremos a tomar el control con cada llamada a DialogBoxParamA: ; -------------------------------------------------------------------------------------------------- .data WinDialogBoxParamA dd 0 function2 db 'MyAboutDialog',0 .code ; Salvar la dirección original de DialogBoxParamA mov esi,dword ptr [004824CBh] ; Dir. donde se ubica punto de entrada de DialogBoxParamA mov WinDialogBoxParamA,esi ; Variable que guardará esta dir ; Hacer que la función MyAboutDialog tome el control a cada llamada a DialogBoxParamA mov edi,offset MyAboutDialog mov eax,hInstModuleName call GetProcAddress,eax,edi ; Obtener dir de MyAboutDialog mov dword ptr esi,eax ; y reenplazarla por la dir de DialogBoxParam ; --------------------------------------------------------------------------------------------------- Hay varias cosas que debemos tomar en cuenta para la rutina que reemplazará las llamadas a DialogBoxParam: 1. Debe tener los mismos parámetros. 2. Debe diferenciar entre las llamadas a la ventana ABOUT y las llamadas a otras ventanas. Esta es la rutina que he creado para esto MASM 6.14: ; -------------------------------------------------------------------------------------------------- TITLE AW_PATCH.DLL: Elimina NAG de awave v5.4 y reemplaza su diálogo ABOUT .386 .model flat,STDCALL includelib kernel32.lib includelib user32.lib GetModuleHandleA proto :dword GetProcAddress proto :dword,:dword EndDialog proto :dword,:dword IDOK equ 1 IDM_ABOUT equ 0105 public patch public MyAboutDialog public Start .data OldInstruction1 db 003h,085h,0ACh,050h,044h,000h OldInstruction2 db 08Bh,015h,084h,0F8h,04Ah,000h NewInstruction1 db 068h,000h,000h,000h,000h,0C3h NewInstruction2 db 0B8h,001h,000h,000h,000h,0EBh,010h ModuleName db 'aw_patch.dll',0 WinDialogBoxParamA dd 0 FunctionName db 'MyAboutDialog',0 DlgName db "AboutDlg",0 hInstance dd 0 hWndIntercepted dd 0 original_esp dd 0 original_edx dd 0 counter dd 0 .code Start: dll proc instance:DWORD, reason:DWORD, reserved:DWORD mov eax,1 ret dll endp patch: ; Eliminar NAG ; Salvar la dirección original de DialogBoxParamA pushad mov eax,0048B24Ch mov esi,dword ptr [eax] ; Dir. del puntero a DialogBoxParamA mov WinDialogBoxParamA,esi ; Variable que guardará esta dir ; Hacer que MyAboutDialog tome el control a cada llamada a DialogBoxParamA mov edi,offset FunctionName push offset ModuleName call GetModuleHandleA mov hInstance,eax push edi push eax call GetProcAddress ; Obtener dir de MyAboutDialog mov edi,0048B24Ch mov dword ptr [edi],eax ; y reemplazarla por la dir de DialogBoxParam ; Permitir guardar más de un archivo mov edi,00455EFDh mov byte ptr [edi],0EBh ; Borrar cadena "Unregistered Copy" mov edi,004A2100h mov byte ptr [edi],0 ; Borrar cadena "(Unreg)" mov edi,0049FCD3h mov byte ptr [edi],0 ; Restaurar código en 0050B54Bh mov edi,0050B54Bh lea esi,OldInstruction1 mov ecx,6 rep movsb ; regresar popad push 0050B54Bh ret MyAboutDialog proc hInst:dword, DlgN:dword, hWp:dword, DlgP:dword, i:dword ; Guardar la dirección de retorno ; Verificar si es la primera llamada a DialogBoxParamA inc counter cmp counter,1 je pop_parameters ; Sacar los parámetros de la pila y no mostrar diálogo ; ; Revisar si es el diálogo "ABOUT" mov eax,hWp cmp hWndIntercepted,eax jne call_WinDialogBoxParamA ; Si no es, continuar normal ; Si es el Dlg, desplegar MyDialogBox push 0 push offset MyDlgProc push hWp push offset DlgName push hInstance call_WinDialogBoxParamA: call WinDialogBoxParamA ret ; Si es la primera llamada, pasar por alto pop_parameters: mov eax,hWp ; Obtiene el hWp mov hWndIntercepted,eax ret MyAboutDialog endp MyDlgProc: pop ebx mov original_esp,ebx mov eax,dword ptr [esp+4] ; uMsg: mensaje cmp eax,110h je dlg_initdialog cmp eax,111h je dlg_command cmp eax,0010h je dlg_close return_false: xor eax,eax jmp pop_it2 dlg_initdialog: mov eax,1 jmp pop_it2 dlg_command: mov eax,dword ptr [esp+8] cmp eax,1 je dlg_close jmp return_false dlg_close: push 0 push dword ptr [esp+4] call EndDialog mov eax,1 pop_it2: push original_esp ret End Start ; ---------------------------------------------------------------------------------------------- Esta es la versión para TASM 5.0. ; ---------------------------------------------------------------------------------------------- TITLE AW_PATCH.DLL: Elimina NAG de awave v5.4 y reemplaza su diálogo ABOUT .386 locals jumps .model flat,STDCALL extrn GetModuleHandleA:Proc extrn GetProcAddress:Proc extrn EndDialog:Proc IDOK equ 1 IDM_ABOUT equ 0105 public patch public MyAboutDialog public Start .data OldInstruction1 db 003h,085h,0ACh,050h,044h,000h OldInstruction2 db 08Bh,015h,084h,0F8h,04Ah,000h NewInstruction1 db 068h,000h,000h,000h,000h,0C3h NewInstruction2 db 0B8h,001h,000h,000h,000h,0EBh,010h ModuleName db 'aw_patch.dll',0 WinDialogBoxParamA dd 0 FunctionName db 'MyAboutDialog',0 DlgName db "AboutDlg",0 hInstance dd 0 hWndIntercepted dd 0 original_esp dd 0 original_edx dd 0 counter dd 0 .code Start: dll proc instance:DWORD, reason:DWORD, reserved:DWORD mov eax,1 ret dll endp patch: pushad ; Eliminar NAG ; Salvar la dirección original de DialogBoxParamA mov eax,0048B24Ch mov esi,dword ptr [eax] ; Dir. del puntero a DialogBoxParamA mov WinDialogBoxParamA,esi ; Variable que guardará esta dir ; Hacer que MyAboutDialog tome el control a cada llamada a DialogBoxParamA mov edi,offset FunctionName push offset ModuleName call GetModuleHandleA mov hInstance,eax push edi push eax call GetProcAddress ; Obtener dir de MyAboutDialog mov edi,0048B24Ch mov dword ptr [edi],eax ; y reemplazarla por la dir de DialogBoxParam ; Permitir guardar más de un archivo mov edi,00455EFDh mov byte ptr [edi],0EBh ; Borrar cadena "Unregistered Copy" mov edi,004A2100h mov byte ptr [edi],0 ; Borrar cadena "(Unreg)" mov edi,0049FCD3h mov byte ptr [edi],0 ; Restaurar código en 0050B54Bh mov edi,0050B54Bh lea esi,OldInstruction1 mov ecx,6 rep movsb popad ; regresar push 0050B54Bh ret MyAboutDialog: ; Guardar la dirección de retorno push ebp mov ebp,esp pushad ; Verificar si es la primera llamada a DialogBoxParamA inc counter cmp counter,1 je pop_parameters ; Sacar los parámetros de la pila y no mostrar diálogo ; Revisar si es el diálogo "ABOUT" mov eax,dword ptr [ebp+12] ; hDlg cmp hWndIntercepted,eax jne call_WinDialogBoxParamA ; Si no es, continuar normal ; Si es el Dlg, desplegar MyDialogBox push 0 push offset MyDlgProc push dword ptr [ebp+16] push offset DlgName push hInstance call_WinDialogBoxParamA: call WinDialogBoxParamA ; jmp ready_MyDialog ; Si es la primera llamada, pasar por alto pop_parameters: mov eax,dword ptr [ebp+12] ; Obtiene el hWnd original mov hWndIntercepted,eax ready_MyDialog: popad pop ebp ret 20 MyDlgProc: pop ebx mov original_esp,ebx mov eax,dword ptr [esp+4] ; uMsg: mensaje cmp eax,110h je dlg_initdialog cmp eax,111h je dlg_command cmp eax,0010h je dlg_close return_false: xor eax,eax jmp pop_it2 dlg_initdialog: mov eax,1 jmp pop_it2 dlg_command: mov eax,dword ptr [esp+8] cmp eax,1 je dlg_close jmp return_false dlg_close: push 0 push dword ptr [esp+4] call EndDialog jmp dlg_initdialog pop_it2: pop ebx loop pop_it2 push original_esp ret 16 End Start ; ---------------------------------------------------------------------------------------------- Este es el código para nuestro diálogo en el guión de recursos. Sirve tanto para MASM como para TASM. ; ---------------------------------------------------------------------------------------------- #define WS_POPUP 0x80000000L #define WS_DLGFRAME 0x00400000L #define WS_GROUP 0x00020000L #define IDOK 1 #define IDCANCEL 2 #define IDM_ABOUT 0105 AW_PATCH ICON aw_patch.ico AboutDlg DIALOG 10, 10, 140, 92 STYLE WS_POPUP | WS_DLGFRAME FONT 8, "Arial" { CTEXT "Awave v5.4", -1, 0,12,140,16 ICON "AW_PATCH", -1, 16,8,0,0 CTEXT "Audio and Wavetable Instrument Coverter, Editor and Player", -1, 0,36,140, 16 CTEXT "(c) Markus Jönsson, 1993, 1999", -1, 0,60,140,16 DEFPUSHBUTTON "Ok", IDOK, 54, 72, 32, 14, WS_GROUP } ; ----------------------------------------------------------------------------------------------- El código de la última parte de este tutorial está en SNIP.ZIP, junto a la AW_PATCH.DLL. En él se podrá detallar con mayor precisión el proceso seguido. También hay un parcheador para awave.exe v5.4: aw45_crk.exe. Sólo hay ejecutar aw45_crk.exe en el mismo directorio de awave.exe y automáticamente escribirá el recorte en awave.exe. Después de ensamblar y enlazar la nueva DLL, sólo hay que colocarla en el mismo directorio que awave.exe y listo: AWAVE v5.4 simula ser freeware. Bueno, todavía no. Quedan cuestiones importantes. El programa tiene una protección de tiempo. Después de 30 días el programa no trabajará. Hay también varias funciones desactivadas. Lo mejor es registrarlo...
El verdadero crack
Para registrar el programa debes enviar un email a su autor y cancelarle no sé cuántos dólares. Luego recibirás a través de cualquier mecanismo un archivo .TXT o .KEY que debes abrir con el programa: "Options\Program Setup\Register". Este proceso es engorroso, así que puedes hacer lo siguiente para disfrutar el programa en toda su plenitud: 1. En el registro de Win, en la clave HKCU\Software\FMJ-Software\Awave: Crear nuevo valor de cadena: Username "mi nombre" 2. Aplicar los parches En 0047EFCC 7540 jnz 0047F00E Poner 0047EFCC EB0D jmp 0047EFDB En 0047EFEC 7405 JZ 0047E7F3 Poner 0047EFEC EB09 0047EFF3 Los parches no se pueden hacer directamente porque el archivo está empaquetado. Entonces, aprovechando que ya hemos parchado el archivo para que pase el control a aw_patch.dll, simplemente escribimos la dll para que haga los parches: ; ------------------------------------------------------------------------------------------------- TITLE AW_PATCH.DLL: Registra el programa awave v5.4 .386 .model flat,STDCALL public patch public Start .data OldInstruction db 003h,085h,0ACh,050h,044h,000h .code Start: dll proc instance:DWORD, reason:DWORD, reserved:DWORD mov eax,1 ret dll endp patch: ; Parchar el target para simular registro pushad mov edi,0047EFCCh mov word ptr [edi],00DEBh mov edi,0047EFECh mov word ptr [edi],009EBh ; Restaurar código en 0050B54Bh mov edi,0050B54Bh lea esi,OldInstruction mov ecx,6 rep movsb ; regresar popad push 0050B54Bh ret End Start ; ------------------------------------------------------------------------------------------------- Sólo que hacernos un parchador. En este caso habrá que adicionar código para que escriba en el registro de Window$ el nombre del usuario. Para revisar o escribir claves en el registro Window$ suministra varias funciones en la librería ADVAPI32.DLL: RegCreateKeyEx: crea una clave en el registro; si la clave ya existe, la abre. RegQueryValueEx: retorna el tipo y los datos del nombre de un valor específico asociado con una clave de registro abierta. Para introducir el nombre de usuario, hay también que implementar un cuadro de diálogo que sirva como interface entre el programa y el usuario que introducirá el nombre. Nuestro parchador deberá: · abrir el archivo objeto · revisar si tiene el tamaño correcto · ubicar el punto donde se insertarán los parches. · revisar si no ha sido parchado ya. · escribir el parche. · desplegar un cuadro de diálogo para que el usuario introduzca su nombre de registro. A continuación el código: ; ------------------------- Parchador para AWAVE 5.4 ---------------------------------------
ALGUNAS PREGUNTAS - ALGUNAS RESPUESTAS ¿Existen otros programas, además de Snippet Creator, que ayuden a insertar código nuevo en archivos ejecutables con formato PE?
Por supuesto que sí. Otras herramientas útiles para hacer esto son: TOPO v1.1, de Mr. Crimson: herramienta excelente para añadir líneas de código a un ejecutable. Sobre esta herramienta, hay un tutorial disponible en WKT. Página web de Mr. Crimson: http://i.am/MrCrimson SADD, de Neural Noise: herramienta que agrega secciones a cualquier archivo PE cuidando el tamaño de la imagen, alienaciones, etc. No tan bueno como TOPO, pero también sirve. OPGEN_10 de Neural Noise: herramienta que genera código octal para llamadas y saltos lejanos en w32 bit; evita la engorrosa tarea de copiar las instrucciones en ensamblador desde SoftIce y evita el cálculo de los desplazamientos del archivo correspondientes a las direcciones virtuales de la aplicación. Lee la tabla de importaciones y dice el desplazamiento exacto de los diversos puntos idóneos para llamar dentro del vector de llamadas a APIs (no más dolores de cabeza para encontrar qué llamar cuando se invierte o se busca manualmente el punto para entregar el control al código nuevo cuando se quiere llamar una función API dentro del código de un archivo ejecutable objeto). Página WEB de Neural Noise: http://members.xoom.it/neural/ Estos programas están disponibles en http://members.xoom.com/crk10/archivos
¿Por qué mantener el tamaño del archivo parchado es importante y en todo caso debe evitarse?
Parte de la justificación del uso de ASM es mantener los programas en un tamaño lo más pequeño posible. Claro que ahora no existen los límites en cuanto a disco duro y esas cosas. Pero mientras más pequeño, más rápidos, menos memoria consumida, etc. Pero además de eso hay otras cuestiones. Si mantenemos el tamaño del archivo, sin crear una nueva sección, el recorte es prácticamente indetectable. Prefiero crear las nuevas secciones, si se necesitan, en una nueva DLL. El recorte sólo ahorra el trabajo de insertar una DLL en un proceso remoto, que es lo que hacen muchos LOADERS. Se trata de combinar el método de las LOADERS con el de los recortes. Además, cuando nos metemos con archivos empaquetados, encontramos que el programa revisa el tamaño del archivo para comprobar si está descomprimido: un byte de más o de menos y deterioras el ejecutable. Pregúntenle a quienes se han dado de cabeza con AWAVE.
¿Las jmp de las apis están siempre en secciones .text?
Creo que no necesariamente. Por el trabajo con SC y por un post en el foro Spanish Reverse Engineering me he enterado que los nombres importados no tienen que estar en la sección .idata. Los programas W32 trabajan con un modelo de memoria FLAT; es como un archivo DOS .COM, pero sin el límite estrecho de 65 KB: el límite son 4 GB. Todo está un gran segmento de 4 GB. Datos y Código puede ir en cualquier parte. El único límite, hasta dónde he visto, son precisamente los atributos de las secciones donde se ubican los datos brutos. Por eso, yo creo, aunque no lo he probado, que uno puede poner en una sección de datos inicializados lo siguiente: THUNK1: jmp [api_address] Y llamar a la API desde una instrucción así: call THUNK1 En teoría debería trabajar. No lo he probado todavía.
¿Cuál es el significado de las características de las secciones? Todavía no entiendo porque modificándolas, también se modifica la carga del ejecutable (y se despliega el Sice en la primera instrucción).
He adelantado alguito antes. Las PCs están basadas en una arquitectura llamada von Newmann. Si quieres saber quien es este tipo te lo envío. Se le considera erróneamente el padre de la computadora moderna. La verdad es que fue un matemático bestial, muy activo durante la 2da. Guerra. Fue uno de los impulsores del proyecto ENIAC, si no me equivoco. Necesitaban computadoras para cálculos avanzados y, especialmente para crackear claves y mensajes. De ahí salió también la cibernética de Norbert Wiener. Más importante en esta arquitectura, para mí, es A. Turing, el de la máquina universal. Lo cierto es que el descubrimiento de estos tipos fue el tratamiento del código de un programa como si fueran sus datos. Gracias a esto, se pueden almacenar las secuencias de instrucciones como si fueran datos. W32 le saca la punta a esto y aprovecha el modelo de memoria FLAT de los procesadores INTEL x86. Ya te he explicado qué es este modelo. Hay más info en las referencias de los procesadores de este tipo. Ahora bien, como cualquier cosa puede ser dato, incluso el código, a cada sección del PE se le dan algunos atributos durante el enlazado para discriminar un poco entre lo que es dato y lo que es código, donde puede escribirse y donde no, etc. Cuando un archivo es comprimido, a las secciones se le dan atributos de datos, por lo que el depurador, que sólo lee código, no las leerá. Es más, si le das a las secciones de datos atributos de código, te las desplegará hasta el W32DASM. La siguiente tabla ilustra el significado de las características: · 000000020h __Código. · 000000040h __Datos inicializados. · 000000080h __Datos no inicializados. · 040000000h __Sección cacheable. · 080000000h __Sección paginable. · 100000000h __Sección compartida. · 200000000h __Ejecutable. · 400000000h __Se puede leer. · 800000000h __Se puede escribir en la sección. Por ejemplo, si las características es E0000020H, entonces se trata de una sección en la que 1. se pueden escribir datos 80000000h 2. se pueden leer datos 40000000h 3. se pueden compartir datos 10000000h 4. hay código 00000020h --------------- E0000020h
¿Qué es una DLL y para qué sirve? ¿cómo se crea?
DLL es la extensión de archivos empleados por Window$ para enlace dinámico. DLL es la abreviatura de Dinamic Link Library, que significa librería de enlace dinámico. Un archivo DLL está compuesto por un conjunto de funciones y rutinas que pueden ser empleadas y llamadas desde un archivo ejecutable .EXE o desde cualquier otra .DLL. El uso de estas funciones se llama enlace dinámico porque se trata de un "enlace" semejante al realizado cuando enlazamos archivos .OBJ y .RES con el enlazador (linker) para crear ejecutables .EXE, pero que ocurre en tiempo de ejecución y sin el uso de enlazador. Esto fue implementado por Window$ considerando que muchas rutinas empleadas por varios programas eran las mismas. Para evitar perdida de espacio de memoria, Window$ reúne esas rutinas comunes en archivos DLL que pueden ser accedidas, una vez cargadas en el área de memoria compartida de un proceso, por más de un programa o proceso. Pero las DLL no sólo ahorran espacio de memoria útil. También ofrecen otras ventajas. Cómo se encuentran en un área de memoria compartida del proceso, son una vía de acceso a proyectos remotos. Es algo complejo, pero se puede hacer. Además de esto, las DLL permiten agregar código a un programa sin tener que cambiar el ejecutable para nada: se podría hacer actualizaciones importantes de programas sólo cambiando una DLL. ¿Cómo escribir una DLL? Toda DLL tiene un punto de entrada el cual será llamado por Win cada vez que - la DLL sea cargada - la DLL sea descargada - un hilo sea creado en el mismo proceso Este es un ejemplo del código del punto de entrada de una DLL: DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD mov eax,TRUE ret DllEntry Endp El punto de entrada puede tener cualquier nombre y apunta a una función con tres parámetros: · hInstDll = manejador de instancia del módulo DLL. · reason = bandera con uno los siguientes valores: DLL_PROCESS_ATTACH: La DLL recibe este valor cuando injertado por vez primera en el espacio de direcciones del proceso. Se puede usar esta posibilidad para hacer inicialización. DLL_PROCESS_DETACH: La DLL recibe este valor cuando está siendo descargada del espacio de direcciones del proceso. Se puede usar esta posibilidad para hacer una limpieza como deslocalización de memoria. DLL_THREAD_ATTACH: La DLL recibe este valor cuando el proceso crea un nuevo hilo. DLL_THREAD_DETACH : La DLL recibe este valor cuando un hilo del proceso es destruido Para que la DLL sea cargada dentro de un proceso, debe retornar TRUE en eax. En caso contrario, no será cargada. Las funciones de la DLL pueden ser colocadas antes o después de la entrada. Pero para que puedan ser llamadas desde otros programas, deben colocarse las siguientes líneas en el archivo DEF de definición: LIBRARY DLL_Name EXPORTS Function_Name La directiva LIBRARY define el nombre del módulo DLL. Debe señalarse con el nombre de archivo de la DLL. La directiva EXPORTS dice al enlazador (linker) cuales funciones de la DLL son exportadas, es decir, pueden ser llamadas desde otros programas. ¿Cómo compilar y enlazar un archivo DLL? También hay que indicar en los conmutadores del enlazador la opción /DLL y /DEF:DEF_name: link /DLL /SUBSYSTEM:WINDOWS /DEF:DEF_name /LIBPATH:c:\masm32\lib OBJ_name.obj Los conmutadores del ensamblador son los mismos: /c /coff /Cp En el caso de Borland Turbo Assembler, en vez de /DLL, se coloca /Tpd: tlink32 /Tpd /aa /c /v $(OBJS),$(NAME),, $(IMPORT), $(DEF), $(NAME) Después de enlazar el archivo objeto, se obtendrá la DLL y un archivo .lib. Este archivo .lib es la librería de importación que puede ser usada para enlazar programas que usan las funciones que está en la DLL. Para que un ejecutable EXE u otra DLL llame a funciones dentro de una DLL, debe proyectar primero la DLL en el espacio de direcciones del proceso que llama. ¿Como cargar la DLL en el espacio de nombres del proceso? Hay dos maneras: 1. Enlazado implícito: es el más común. Como hemos visto al crear aplicaciones W32, al enlazar un ejecutable debemos indicar un conjunto de archivos LIB al enlazador. Estos archivos LIB contienen una lista de las funciones de la DLL que pueden ser importadas desde otros archivos EXE o DLL. Cuando hacemos el enlace, el enlazador toma información de los archivos LIB correspondientes y la incrusta en el archivo EXE creado. Luego, cuando el sistema cargue el EXE, el cargador examinará el encabezado de este archivo y establecerá las DLLs que deberán ser cargadas en el espacio de direcciones del proceso para que se ejecute la aplicación. El sistema buscará las DLLs requeridas en los directorios de sistema e intentará cargarlas. 2. Enlazado explícito: llamando a LoadLibrary con el nombre de la DLL deseada. Si la función tiene éxito, devuelve un manejador a la librería (DLL). Si no, retornará NULL: invoke LoadLibrary,addr LibName El manejador devuelto se puede pasar a GetProcAddress o a cualquier otra librería que requiera este manejador como parámetro: mov hLib,eax invoke GetProcAddress,hLib,addr FunctionName Esta llamada devuelve la dirección de la función cuyo nombre ha sido pasado como segundo parámetro. De otra manera, retorna NULL. El valor devuelto ahora puede ser usado para llamar a la función deseada: mov TestHelloAddr,eax call [TestHelloAddr] Usada la librería DLL, se descarga con FreeLibrary: invoke FreeLibrary,hLib 2. El trabajo con snippet Creator tenía como finalidad fundamental consolidar nuestros conocimientos sobre el encabezado de los archivos PE. Pero todavía nos quedan varios aspectos que todavía no hemos explotado como lo que llamo suma de chequeo (CRC) y la sección de recursos. Me he enterado que ya otros trabajan sobre esto, así que trataremos de adentrarnos en la sección de recursos .rsrc. Y tú, cabeza de recursos (en preparación) AGRADECIMIENTOS Quiero agradecer a: GERZA, KARPOFF, Kr@cKerZ United Team (KuT), TNT.ORG y WKT por su gran contribución en la distribución de estos trabajos. Especial agradecimiento a: GERZA (una vez más), ManiacPC y KuT. Comentarios y observaciones: nuMIT_or@iname.com
[ Entrada | Documentos Genéricos | WkT! Web Site ]
[ Todo el ECD | x Tipo de Protección | x Fecha de Publicación | x Orden Alfabético ]
(c) Whiskey Kon Tekila [WkT!] - The Original Spanish Reversers. Si necesitas contactar con nosotros , lee esto antes e infórmate de cómo puedes ayudarnos