|
|
 |
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_OK
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 |
|
|
|
|
|