Este texto trata las tecnicas de cambiar las funciones de la API en
Windows. Todos los ejemplos aqui descritos funcionan perfectamente en sistemas basados en tecnologia NT version NT 4.0 y superior ( windows NT 4.0, Windows 2000 , Windows XP, Windows 2003). Probablemente tambien funcionara en futuros sistemas Windows.
Texto Completo:
===========================[ Hooking Windows API ]==============================
Tecnicas de Ganchos a Funciones de la API de Windows
----------------------------------------------------
Author: Holy_Father <
Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
>
Traduccion: Kintaro <
Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
>
Version: 1.0 Spanish
Fecha: 30.09.2004
=====[ 1. Contents ]============================================================
1. Contenidos
2. Introduccion
3. Metodos de enganche
3.1 Ganchos antes de la ejecucion
3.2 Enganchar durante la ejecucion
3.2.1 Enganchar nuestro propio proceso usando IAT
3.2.2 Enganchar nuestro propio proceso reescribiendo el entry point
3.2.3 Enganchar sin modificar las funciones originales
3.2.4 Enganchando otros procesos
3.2.4.1 Inyeccion DLL
3.2.4.2 Codigo independiente
3.2.4.3 Cambio Raw
4. Finalizando
=====[ 2. Introduccion ]========================================================
Este texto trata las tecnicas de enganchar las funciones de la API en
Windows. Todos los ejemplos aqui descritos funcionan perfectamente en sistemas
basados en tecnologia NT version NT 4.0 y superior ( windows NT 4.0,
Windows 2000 , Windows XP, Windows 2003). Probablemente tambien funcionara en
futuros sistemas Windows.
Deberias familiarzarte con procesos en windows, esamblador, estructura
de ficheros PE y algunas funciones de la API , para comprender enteramente el
texto.
Cuando usamos el termino "Enganchar la API" aqui, me refiero al cambio
completo de la API. Asi, cuando una API enganchada es llamada, nuestro codigo
es ejecutado inmediatamente. No trato solamente los casos de monitorizar la
API. Escribire todo sobre esta tecnica.
=====[ 3. Tecnicas de ganchos ]=================================================
Nuestra meta es normalmente reemplazar el codigo de alguna funcion con
nuestro codigo. Este problema puede ser solucionada a veces antes de ejecutar
el proceso. Esto puede hacerse la mayoria de las veces con codigo con
privilegios de usuario y la meta es, por ejemplo, cambiar el comportamiento
del programa. Un ejemplo de esto puede ser crackear una aplicacion: un programa
que pide el cd original al iniciarse (como ocurria en el juego Atlantis) y
nosotros queremos ejecutarlo sin CD. Si modificamos la funcion que compueba que
el tipo de disco sea cdrom, podemos ejecutarlo desde el disco duro.
Esto no puede hacerse o no queremos hacerlo cuando queremos enganchar
procesos de sistema (como servicios) o en el caso en que no sepamos el proceso
victima. Entonces tendremos que usar la tecnica de ganchos en ejecucion.
Ejemplos de esta tecnica son los rootkits o los virus con tecnicas
anti-antivirus.
=====[ 3.1 Ganchos antes de la ejecucion ]======================================
Se trata de cambios del modulo fisico (normalmente .exe o .dll) en
donde esta la funcion que nosotros queremos cambiar. Tenemos por lo menos tres
posibilidades de hacer esto.
La primera es encontrar el punto de entrada de esa funcion y
basicamente reescribir el codigo. Esto esta limitado por el tamaño de la
funcion pero siempre podemos cargar otro modulo dinamicamente (API LoadLibrary)
y asi tendriamos suficiente espacio.
Las funciones del kernel (kernel32.dll) pueden ser usadas en todos los
casos porque todos los procesos en windows tienen su propia copia de este
modulo. Otra ventaja es cuando sabemos en que SO sera modificado el modulo.
Podemos usar directamente un puntero a la funcion, en este caso seria la
direccion de LoadLibraryA. Esto es asi porque la direccion del modulo kernel en
memoria es estatica para una determinada version de Windows.
Tambien podemos hacer uso del comportamiento de los modulos cargados
dinamicamente, por el cual su parte de inicializacion es ejecutada
inmediatamente despues de cargar el modulo en memoria. En la parte de
inicializacion de un nuevo modulo no estamos limitados.
La segunda posibilidad de reemplazar funciones en un modulo reside en
la extension. Entonces tenemos que elegir entre reemplazar los 5 primeros bytes
con un salto relativo o reescribir la IAT. En el caso de poner un salto
relativo, redirigiria la ejecucion del modulo a nuestro codigo. En el otro
caso, cuando una es llamada una funcion cuyo registro de IAT ha sido
modificado, nuestro codigo seria ejecutado inmediatamente despues de esta
llamada. Pero cambiar la extension un modulo no es tarea facil porque hay que
controlar muy bien las cabeceras.
(Nota del traductor:
no se refiere a la extension de fichero .exe o .dll eh? se refiere al tamaño
que ocupa, que reside en la cabecera del modulo)
La tercera posibilidad es reemplazar el modulo entero. Es decir creamos
nuestra propia version del modulo qu puede cargar el modulo original y llamar
las funciones que no deseamos modificar. Pero reescribimos las funciones que si
nos interesan. Este metodo no es util para modulos muy grandes pues aveces
exportan cientos de funciones.
=====[ 3.2 Ganchos en ejecucion ]===============================================
Los ganchos antes de la ejecucion es mas bien para aplicaciones
concretas o un modulo concreto. Si reemplazamos un funcion de kernel32.dll o de
ntdll.dll obtendremos el comportamiento deseado en todos aquellos procesos que
sean ejecutados despues, aunque es dificil atinar con un codigo perfecto y
exacto para las funciones o modulos nuevos. Pero el principal problema es que
nuestros ganchos solo contemplaran los procesos ejecutados posteriormente (asi
pues para todos los procesos tendriamos que reiniciar el sistema). Otro
problema es el acceso a estos ficheros, que en sistemas NT estan protegidos.
Los ganchos en ejecucion solucionan esto. Si bien este metodo requiere muchos
mas conocimientos, el resultado es perfecto.Los ganchos en ejecucion pueden
aplicarse solo a procesos para los que tengamos acceso a su zona de memoria.
Para escribir en esta zona usaremos la funcion APi WriteProcessMemory.
Comencemos por enganchar nuestro propio proceso durante la ejecucion.
=====[ 3.2.1 Enganchar nuestro propio proceso usando IAT ]======================
Hay muchas posibilidades aqui. En prinicpio te mostrare como enganchar
funciones mediante la reescritura de la IAT. Este dibujo muestra la estructura
de un fichero PE:
+-------------------------------+ - offset 0
| Cabecera MS DOS ("MZ") |
+-------------------------------+
| firma de PE ("PE") |
+-------------------------------+
| .text | - codigo de modulo
| Codigo de programa |
| |
+-------------------------------+
| .data | - datos iniciados (global y static)
| Datos Inicializados |
| |
+-------------------------------+
| .idata | - info sobre funciones importadas
| Import Table | y datos
| |
+-------------------------------+
| .edata | - info sobre funciones exportadas
| Export Table | y datos
| |
+-------------------------------+
| Debug symbols |
+-------------------------------+
La parte importante para nosotros aqui es la Import Address Table(IAT)
en la zona .idata . Esta parte contiene una descripcion de las funciones
importadas y sus direcciones. Ahora es importante saber como se crean los
ficheros PE. Cuando se llama un funcion de la API indirectamente en un
lenguaje de programacion (es decir llamar usando el nombre , en vez de su
direccion especifica en el SO) el compilador no las convierte en llamadas
directas al modulo, sino que las convierte en saltos a direcciones, y estas
direcciones se encuentran almacenadas en la IAT. Cuando el proceso se carga el
cargador de proceso del Windows se encarga de rellear la IAT para cada
sistema. Esta es la razon por la que el mismo binario funciona en versiones
distintas del windows, a pesar de que tienen distintas direcciones para un
mismo modulo. Por tanto si fuesemos capaz de encontrar una funcion, que
queremos enganchar, en la IAT, podriamos cambiar facilmente la direccion y la
instruccion jmp redirigiria el flujo de ejecucion a nuestro codigo. Cualquier
llamada despues de hacer esto ejecutaria nuestro codigo.
(Nota del traductor:
He traducido el ultimo parrafo intentando que se comprenda lo mejor posible,
pero aun asi prefiero dar una explicacion propia sobre la IAT.
Si queremos realizar una llamada a una api de windows podemos escribir:
call direccion_de_la_api_en_memoria ( por ejemplo "call 0x07728188")
pero como en cada version de windows esa direccion cambia necesitamos de
alguna manera que escribiendo la misma instruccion se ejecute lo mismo
Lo que se hace es guardar estas direcciones en la IAT. La IAT es rellenada
por el So al crear el PE. La forma de acceder a las direcciones de la IAT es
la siguiente:
-------------------
call direccion api1
......
codigo
......
direccion:
jmp dword ptr [IAT] ;
--------------------
Asi un PE funciona en cualquier windows )
La ventaja de este metodo es que el resultado es perfecto. La desventaja es
que para cambiar el comportamiento de una funcion, deberiamos enganchar
varias funciones (me explico, si queremos cambiar el comportamiento de un
programa en la busqueda de un fichero tendremos que cambiar FindFirstFile y
FindNextFile, pero estas funciones tienen version ANSI y WIDE, asique
tendremos que cambiar FindFirstFileA, FindNextFileA, FindFirstFileW y
FindNextFileW en la IAT. Y aun quedan otras como FindFirstFileExA y su version
WIDE FindFirstFileExW que son llamadas por las funciones comentadas
previamente. Sabemos que FindFirstFileW llama a FindFirstFileExW pero esto se
hace directamente , sin usar la IAT. Y aun algunas otras. Hay por ejemplo
funciones ShellApi como SHGetDesktopFolder que tambien llaman directamente a
FindFirstFileW o FindFirstFileExW). Pero si lo conseguimos con todas ellas el
resultado sera perfecto. Podemos usar ImageDirectoryEntryToData de
imagehlp.dll para encontrar la IAT facilmente.
PVOID ImageDirectoryEntryToData(
IN LPVOID Base,
IN BOOLEAN MappedAsImage,
IN USHORT DirectoryEntry,
OUT PULONG Size
);
Usaremos Instance de nuestra aplicacion como Base
(Instance se obtiene con una llamada a GetModuleHandle:
hInstance = GetModuleHandleA(NULL);
), y como DirectoryEntry usaremos la constante IMAGE_DIRECTORY_ENTRY_IMPORT.
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1
El resultado de esta funcion es un puntero al primer registro de la IAT.
Los registros de la IAT son estructuras que estan definidas como
IMAGE_IMPORT_DESCRIPTOR.
Asi el resultado es un puntero a IMAGE_IMPORT_DESCRIPTOR.
typedef struct _IMAGE_THUNK_DATA {
union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;
} ;
} IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
PIMAGE_THUNK_DATA OriginalFirstThunk;
} ;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
PIMAGE_THUNK_DATA FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
El valor de Name en IMAGE_IMPORT_DESCRIPTOR es una referencia al
nombre del modulo. Si queremos enganchar una funcion, por ejemplo del
kernel32.dll, tenemos que encontrar aquella entrada de la tabla que
corresponde al descriptor de kernel32.dll.
(NOTA del traductor:
Aunque Holy_father lo da por supuesto, prefiero aclarar algo antes de
continuar, me refiero a la forma de recorrer la tabla. La IAT parece ser que
esta implementada como un vector estatico, donde cada casilla contiene la
direccion del descriptor. Por tanto si tenemos la direccion de la primera
entrada de la tabla, que nos la ha devuelto ImageDirectoryEntryToData, la
direccion de la siguiente entrada se incrementa en uno, y asi podemos
recorrer la tabla)
Llamaremos a ImageDirectoryEntryToData en un principio e intentaremos
encontrar aquel descriptor con Name "kernel32.dll" ( puede haber mas de un
descriptor con este nombre). Finalmente tendremos que encontrar nuestra
funcion en la lista de todas las funciones de ese descriptor (la direccion
de nuestra funcion la obtenemos con GetProcAddress). Si la encontramos
debemos usar VirtualProtect para cambiar la proteccion de pagina de memoria
y despues de esto podremos escribir en esta parte de memoria. Despues de
escribir la direccion debemos restaurar la proteccion original. Antes de
llamar a VirtualProtect tenemos que saber cierta informacion sobre esta
pagina de memoria. Esta info se obtiene con VirtualQuery. Podemos añadir
algunas pruebas en caso de algunas llamadas fallen ( por ejemplo no
continuar si la primera llamada a VirtualProtect falla, etc)
PCSTR pszHookModName = "kernel32.dll",pszSleepName = "Sleep";
HMODULE hKernel = GetModuleHandle(pszHookModName);
PROC pfnNew = (PROC)0x12345678, //la nueva direccion aqui
pfnHookAPIAddr = GetProcAddress(hKernel,pszSleepName);
ULONG ulSize;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc =
(PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(
hInstance,
TRUE,
IMAGE_DIRECTORY_ENTRY_IMPORT,
&ulSize
);
while (pImportDesc->Name)
{
PSTR pszModName = (PSTR)((PBYTE) hInstance + pImportDesc->Name);
if (stricmp(pszModName, pszHookModName) == 0)
break;
pImportDesc++;
}
PIMAGE_THUNK_DATA pThunk =
(PIMAGE_THUNK_DATA)((PBYTE) hInstance + pImportDesc->FirstThunk);
while (pThunk->u1.Function)
{
PROC* ppfn = (PROC*) &pThunk->u1.Function;
BOOL bFound = (*ppfn == pfnHookAPIAddr);
if (bFound)
{
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(
ppfn,
&mbi,
sizeof(MEMORY_BASIC_INFORMATION)
);
VirtualProtect(
mbi.BaseAddress,
mbi.RegionSize,
PAGE_READWRITE,
&mbi.Protect)
)
*ppfn = *pfnNew;
DWORD dwOldProtect;
VirtualProtect(
mbi.BaseAddress,
mbi.RegionSize,
mbi.Protect,
&dwOldProtect
);
break;
}
pThunk++;
}
El resultado de una llamada a Sleep(1000) podria ser:
00407BD8: 68E8030000 push 0000003E8h
00407BDD: E812FAFFFF call Sleep
........
codigo
........
Sleep: ;es es el salto a la IAT
004075F4: FF25BCA14000 jmp dword ptr [00040A1BCh]
tabla original:
0040A1BC: 79 67 E8 77 00 00 00 00
nueva tabla:
0040A1BC: 78 56 34 12 00 00 00 00
Asique al final el salto es a 0x12345678.
=====[ 3.2.2 Enganchar nuestro propio proceso reescribiendo el entry point ]===
El metodo de reescribir primero unas intrucciones en el punto de
entrada de la funcion es realmente simple. Como en el caso de reescribir la
IAT, tenemos que cambiar la proteccion de pagina antes de nada. Aqui seran los
5 primeros bytes de la funcion que queremos enganchar. Despues tendremos que
localizar memoria dinamica para la estructura MEMORY_BASIC_INFORMATION. El
comienzo de la funcion lo obtenemos con GetProcAddres. En esta direccion
insertaremos un salto relativo a nuestro codigo. El siguiente programa llama a
Sleep(5000) (espera 5 segundos), entonces la funcion Sleep es enganchada y
redirigida a new_sleep, y finalmente llama de nuevo a Sleep(5000). Como la
nueva funcion new_sleep no hace nada y termina inmediatamente el programa
entero tardara solo 5 segundos en lugar de 10.
(NOTA del traductor:
----------------------------
Holy_father ha puesto este codigo en win32asm. El codigo se
entiende bien pero lo voy a poner primero en C porque creo que se queda uno
con una vision mas general en la primera lectura. El codigo lo escribo sobre
la marcha mientras traduzco pero creo que esta bien. Luego puedes continuar
echarle un vistazo a como quedaria en win32asm:
.....
static void new_sleep()
{
......
}
.....
Sleep(5000);
HMODULE Hkernel=GetModuleHandleA("kernel32.dll")
FARPROC HSleep=GetProcAddress(Hkernel,"Sleep");
LPVOID pmbi=VirtualAlloc(0,sizeof(MEMORY_BASIC_INFORMATION)
,MEM_COMMIT,PAGE_READWRITE);
if (pmbi)
{
DWORD pinfo=VirtualQuery(HSleep,Hcode,
sizeof(MEMORY_BASIC_INFORMATION));
if (pinfo)
{
/*
como la funcion Sleep se acaba de ejecutar es probable que
aun este en cache asique vaciamos de la cache los 5 primeros
bytes de la funcion Sleep
*/
FlushInstructionCache(GetCurrentProcess(),HSleep,5);
//la estructura que hemos reservado es una mbi
//tenemos que poner mbi.Protect al valor devuelto por
//VirtualProtect en lpflOldProtect
//14h (= 20) es el desplazamiento de Protect dentro de la mbi
DWORD dwOldProtect=(DWORD)pmbi + 14h;
PDWORD pOldProtect=(PDWORD)dOldProtect;
DWORD dwsize=(DWORD)pmbi + 00ch;
PDWORD psize=(PDWORD)dwsize;
if (VirtualProtect( pmbi , *psize, PAGE_EXECUTE_READWRITE,
pOldProtect))
{
//el primer byte contieen el hex opcode de jmp
PWORD pSleep=(PWORD)HSleep;
*(pSleep[0])= (BYTE)0E9h; //jmp
DWORD offset_jmp=(DWORD)(PBYTE)new_sleep-(DWORD)(PBYTE)Sleep-5;
//los siguientes contienen el offset
memcpy(&Sleep[1],&offset_jmp,4);
DWORD tmp_oldprotect;
VirtualProtect( pmbi , *psize, pOldProtect, &tmp_oldprotect);
}
}
VirtualFree(pmbi,0,MEM_RELEASE);
}
Sleep(5000);
ExitProcess(0);
----------------------------
.386p
.model flat, stdcall
includelib lib\kernel32.lib
Sleep PROTO :DWORD
GetModuleHandleA PROTO :DWORD
GetProcAddress PROTO :DWORD,:DWORD
VirtualQuery PROTO :DWORD,:DWORD,:DWORD
VirtualProtect PROTO :DWORD,:DWORD,:DWORD,:DWORD
VirtualAlloc PROTO :DWORD,:DWORD,:DWORD,:DWORD
VirtualFree PROTO :DWORD,:DWORD,:DWORD
FlushInstructionCache PROTO :DWORD,:DWORD,:DWORD
GetCurrentProcess PROTO
ExitProcess PROTO :DWORD
.data
kernel_name db "kernel32.dll",0
sleep_name db "Sleep",0
old_protect dd ?
MEMORY_BASIC_INFORMATION_SIZE equ 28
PAGE_READWRITE dd 000000004h
PAGE_EXECUTE_READWRITE dd 000000040h
MEM_COMMIT dd 000001000h
MEM_RELEASE dd 000008000h
.code
start:
push 5000
call Sleep
do_hook:
push offset kernel_name
call GetModuleHandleA
push offset sleep_name
push eax
call GetProcAddress
mov edi,eax ;la direccion de Sleep
push PAGE_READWRITE
push MEM_COMMIT
push MEMORY_BASIC_INFORMATION_SIZE
push 0
call VirtualAlloc
test eax,eax
jz do_sleep
mov esi,eax ;lozalizacion de memoria para MBI
push MEMORY_BASIC_INFORMATION_SIZE
push esi
push edi
call VirtualQuery ;informacion de la pagina
test eax,eax
jz free_mem
call GetCurrentProcess
push 5
push edi
push eax
call FlushInstructionCache ;nos aseguramos por si acaso :)
lea eax,[esi+014h]
push eax
push PAGE_EXECUTE_READWRITE
lea eax,[esi+00Ch]
push [eax]
push [esi]
call VirtualProtect ;cambiaremos la proteccion un momento
;para que podamos escribir
test eax,eax
jz free_mem
mov byte ptr [edi],0E9h ;escribir el salto relativo
mov eax,offset new_sleep
sub eax,edi
sub eax,5
inc edi
stosd ;este es el salto relativo de jmp
push offset old_protect
lea eax,[esi+014h]
push [eax]
lea eax,[esi+00Ch]
push [eax]
push [esi]
call VirtualProtect ;restauramos la proteccion original
free_mem:
push MEM_RELEASE
push 0
push esi
call VirtualFree ;liberar memoria
do_sleep:
push 5000
call Sleep
push 0
call ExitProcess
new_sleep:
ret 004h
end start
Resultado de la segunda llamada a Sleep:
004010A4: 6888130000 push 000001388h
004010A9: E80A000000 call Sleep
Sleep: ;este es el salto en la direccion de la IAT
004010B8: FF2514204000 jmp dword ptr [000402014h]
tabulka:
00402014: 79 67 E8 77 6C 7D E8 77
Kernel32.Sleep:
77E86779: E937A95788 jmp 0004010B5h
new_sleep:
004010B5: C20400 ret 004h
=====[ 3.2.3 Enganchar sin modificar las funciones originales]==================
La mayoria de las veces solemos necesitar algo mas que un gancho. Por
ejemplo en el caso en que no queramos reemplazar una funcion, sino solo
comprobar su resultado, o en el caso de que queramos reemplazar el codigo de
la funcion solo algunas veces, por ejemplo cuando es llamada con unos
parametros determinados. Un buen ejemplo de esto es la ocultacion de ficheros,
mencionada antes, que se hace reemplazando funciones FindxxxFile. Asi si
queremos esconder unos ficheros especificos y no queremos ser descubiertos,
tenemos que dejar la funcion original para el resto de ficheros. Esto es facil
cuando usamos el metodo de reescribir la IAT. Para llamar a la funcion
original solo tendriamos que obtener la direccion con GetProcAddress y
llamarla directamente. Pero el problema viene cuando usamos la reescritura del
punto de entrada. Reescribiendo los 5 primeros bytes del punto de entrada de
las funciones perdemos una parte de la funcion, imposible de recuperar. Asique
lo que hacemos es guardar primero estas intrucciones. Podemos usar la
siguiente tecnica. Sabemos que solo reescribiremos los 5 primeros bytes, pero
no sabemos cuantas instrucciones hay ni la longitud de estas. Tenemos que
reservar suficiente memoria para las primeras instrucciones. 16 bytes podrian
ser suficientes porque no suele haber instrucciones largas al principio de una
funcion. Probablemente podriamos usar menos de 16. La memoria reservada la
llenaremos con operaciones nop (0x90) en el caso de que las instrucciones
ocupen menos de esos 16. Los 5 bytes siguiente a esos 16 seran llenados con un
salto relativo que luego rellenaremos su direccion.
old_hook: db 090h,090h,090h,090h,090h,090h,090h,090h
db 090h,090h,090h,090h,090h,090h,090h,090h
db 0E9h,000h,000h,000h,000h
Ahora ya podemos copiar las primeras instrucciones. Resulta algo largo obtener
la longitud de una instruccion, porque trabajaremos con el codigo completo de
un desensamblador. Este fue hecho por Z0MBiE. El argumento de entrada es la
direccion de la instruccion de la que queremos saber la longitud. La salida
se devuelve en eax.
; LDE32, Length-Disassembler Engine, 32-bit, (x) 1999-2000 Z0MBiE
; special edition for REVERT tool
; version 1.05
C_MEM1 equ 0001h ; |
C_MEM2 equ 0002h ; |puede usarse simultaneamente
C_MEM4 equ 0004h ; |
C_DATA1 equ 0100h ; |
C_DATA2 equ 0200h ; |puede usarse simultaneamente
C_DATA4 equ 0400h ; |
C_67 equ 0010h ; usado con C_PREFIX
C_MEM67 equ 0020h ; C_67 ? C_MEM2 : C_MEM4
C_66 equ 1000h ; usado con C_PREFIX
C_DATA66 equ 2000h ; C_66 ? C_DATA2 : C_DATA4
C_PREFIX equ 0008h ; prefijo. take opcode again
C_MODRM equ 4000h ; MODxxxR/M
C_DATAW0 equ 8000h ; opc&1 ? C_DATA66 : C_DATA1
p386
model flat
locals @@
.code
public disasm_main
public _disasm_main
public @disasm_main
public DISASM_MAIN
disasm_main:
_disasm_main:
@disasm_main:
DISASM_MAIN:
; devuelve la longitud de opcode en EAX o -1 si error
; entrada: puntero a opcode
; __fastcall EAX
; __cdecl [ESP+4]
;este es mi primer cambio aqui, es la etiqueta solo para llamar esta funcion
get_instr_len:
mov ecx, [esp+4] ; ECX = opcode ptr
xor edx, edx ; flags
xor eax, eax
@@prefix: and dl, not C_PREFIX
mov al, [ecx]
inc ecx
or edx, table_1[eax*4]
test dl, C_PREFIX
jnz @@prefix
cmp al, 0F6h
je @@test
cmp al, 0F7h
je @@test
cmp al, 0CDh
je @@int
cmp al, 0Fh
je @@0F
@@cont:
test dh, C_DATAW0 shr 8
jnz @@dataw0
@@dataw0done:
test dh, C_MODRM shr 8
jnz @@modrm
@@exitmodrm:
test dl, C_MEM67
jnz @@mem67
@@mem67done:
test dh, C_DATA66 shr 8
jnz @@data66
@@data66done:
mov eax, ecx
sub eax, [esp+4]
and edx,C_MEM1+C_MEM2+C_MEM4+C_DATA1+C_DATA2+C_DATA4
add al, dl
add al, dh
;mi segundo cambio aqui, habia retn solo en la version original
@@exit: ret 00004h
@@test: or dh, C_MODRM shr 8
test byte ptr [ecx], 00111000b ; F6/F7 -- test
jnz @@cont
or dh, C_DATAW0 shr 8
jmp @@cont
@@int: or dh, C_DATA1 shr 8
cmp byte ptr [ecx], 20h
jne @@cont
or dh, C_DATA4 shr 8
jmp @@cont
@@0F: mov al, [ecx]
inc ecx
or edx, table_0F[eax*4]
cmp edx, -1
jne @@cont
@@error: mov eax, edx
jmp @@exit
@@dataw0: xor dh, C_DATA66 shr 8
test al, 00000001b
jnz @@dataw0done
xor dh, (C_DATA66+C_DATA1) shr 8
jmp @@dataw0done
@@mem67: xor dl, C_MEM2
test dl, C_67
jnz @@mem67done
xor dl, C_MEM4+C_MEM2
jmp @@mem67done
@@data66: xor dh, C_DATA2 shr 8
test dh, C_66 shr 8
jnz @@data66done
xor dh, (C_DATA4+C_DATA2) shr 8
jmp @@data66done
@@modrm: mov al, [ecx]
inc ecx
mov ah, al ; ah=mod, al=rm
and ax, 0C007h
cmp ah, 0C0h
je @@exitmodrm
test dl, C_67
jnz @@modrm16
@@modrm32: cmp al, 04h
jne @@a
mov al, [ecx] ; sib
inc ecx
and al, 07h
@@a: cmp ah, 40h
je @@mem1
cmp ah, 80h
je @@mem4
cmp ax, 0005h
jne @@exitmodrm
@@mem4: or dl, C_MEM4
jmp @@exitmodrm
@@mem1: or dl, C_MEM1
jmp @@exitmodrm
@@modrm16: cmp ax, 0006h
je @@mem2
cmp ah, 40h
je @@mem1
cmp ah, 80h
jne @@exitmodrm
@@mem2: or dl, C_MEM2
jmp @@exitmodrm
endp
.data
;0F -- analizado en codigo, sin flags (i.e.flags must be 0)
;F6,F7 -- --//-- (ttt=000 -- 3 bytes, sino 2 bytes)
;CD -- --//-- (6 bytes si CD 20, sino 2 bytes)
table_1 label dword ; instrucciones normales
dd C_MODRM ; 00
dd C_MODRM ; 01
dd C_MODRM ; 02
dd C_MODRM ; 03
dd C_DATAW0 ; 04
dd C_DATAW0 ; 05
dd 0 ; 06
dd 0 ; 07
dd C_MODRM ; 08
dd C_MODRM ; 09
dd C_MODRM ; 0A
dd C_MODRM ; 0B
dd C_DATAW0 ; 0C
dd C_DATAW0 ; 0D
dd 0 ; 0E
dd 0 ; 0F
dd C_MODRM ; 10
dd C_MODRM ; 11
dd C_MODRM ; 12
dd C_MODRM ; 13
dd C_DATAW0 ; 14
dd C_DATAW0 ; 15
dd 0 ; 16
dd 0 ; 17
dd C_MODRM ; 18
dd C_MODRM ; 19
dd C_MODRM ; 1A
dd C_MODRM ; 1B
dd C_DATAW0 ; 1C
dd C_DATAW0 ; 1D
dd 0 ; 1E
dd 0 ; 1F
dd C_MODRM ; 20
dd C_MODRM ; 21
dd C_MODRM ; 22
dd C_MODRM ; 23
dd C_DATAW0 ; 24
dd C_DATAW0 ; 25
dd C_PREFIX ; 26
dd 0 ; 27
dd C_MODRM ; 28
dd C_MODRM ; 29
dd C_MODRM ; 2A
dd C_MODRM ; 2B
dd C_DATAW0 ; 2C
dd C_DATAW0 ; 2D
dd C_PREFIX ; 2E
dd 0 ; 2F
dd C_MODRM ; 30
dd C_MODRM ; 31
dd C_MODRM ; 32
dd C_MODRM ; 33
dd C_DATAW0 ; 34
dd C_DATAW0 ; 35
dd C_PREFIX ; 36
dd 0 ; 37
dd C_MODRM ; 38
dd C_MODRM ; 39
dd C_MODRM ; 3A
dd C_MODRM ; 3B
dd C_DATAW0 ; 3C
dd C_DATAW0 ; 3D
dd C_PREFIX ; 3E
dd 0 ; 3F
dd 0 ; 40
dd 0 ; 41
dd 0 ; 42
dd 0 ; 43
dd 0 ; 44
dd 0 ; 45
dd 0 ; 46
dd 0 ; 47
dd 0 ; 48
dd 0 ; 49
dd 0 ; 4A
dd 0 ; 4B
dd 0 ; 4C
dd 0 ; 4D
dd 0 ; 4E
dd 0 ; 4F
dd 0 ; 50
dd 0 ; 51
dd 0 ; 52
dd 0 ; 53
dd 0 ; 54
dd 0 ; 55
dd 0 ; 56
dd 0 ; 57
dd 0 ; 58
dd 0 ; 59
dd 0 ; 5A
dd 0 ; 5B
dd 0 ; 5C
dd 0 ; 5D
dd 0 ; 5E
dd 0 ; 5F
dd 0 ; 60
dd 0 ; 61
dd C_MODRM ; 62
dd C_MODRM ; 63
dd C_PREFIX ; 64
dd C_PREFIX ; 65
dd C_PREFIX+C_66 ; 66
dd C_PREFIX+C_67 ; 67
dd C_DATA66 ; 68
dd C_MODRM+C_DATA66 ; 69
dd C_DATA1 ; 6A
dd C_MODRM+C_DATA1 ; 6B
dd 0 ; 6C
dd 0 ; 6D
dd 0 ; 6E
dd 0 ; 6F
dd C_DATA1 ; 70
dd C_DATA1 ; 71
dd C_DATA1 ; 72
dd C_DATA1 ; 73
dd C_DATA1 ; 74
dd C_DATA1 ; 75
dd C_DATA1 ; 76
dd C_DATA1 ; 77
dd C_DATA1 ; 78
dd C_DATA1 ; 79
dd C_DATA1 ; 7A
dd C_DATA1 ; 7B
dd C_DATA1 ; 7C
dd C_DATA1 ; 7D
dd C_DATA1 ; 7E
dd C_DATA1 ; 7F
dd C_MODRM+C_DATA1 ; 80
dd C_MODRM+C_DATA66 ; 81
dd C_MODRM+C_DATA1 ; 82
dd C_MODRM+C_DATA1 ; 83
dd C_MODRM ; 84
dd C_MODRM ; 85
dd C_MODRM ; 86
dd C_MODRM ; 87
dd C_MODRM ; 88
dd C_MODRM ; 89
dd C_MODRM ; 8A
dd C_MODRM ; 8B
dd C_MODRM ; 8C
dd C_MODRM ; 8D
dd C_MODRM ; 8E
dd C_MODRM ; 8F
dd 0 ; 90
dd 0 ; 91
dd 0 ; 92
dd 0 ; 93
dd 0 ; 94
dd 0 ; 95
dd 0 ; 96
dd 0 ; 97
dd 0 ; 98
dd 0 ; 99
dd C_DATA66+C_MEM2 ; 9A
dd 0 ; 9B
dd 0 ; 9C
dd 0 ; 9D
dd 0 ; 9E
dd 0 ; 9F
dd C_MEM67 ; A0
dd C_MEM67 ; A1
dd C_MEM67 ; A2
dd C_MEM67 ; A3
dd 0 ; A4
dd 0 ; A5
dd 0 ; A6
dd 0 ; A7
dd C_DATA1 ; A8
dd C_DATA66 ; A9
dd 0 ; AA
dd 0 ; AB
dd 0 ; AC
dd 0 ; AD
dd 0 ; AE
dd 0 ; AF
dd C_DATA1 ; B0
dd C_DATA1 ; B1
dd C_DATA1 ; B2
dd C_DATA1 ; B3
dd C_DATA1 ; B4
dd C_DATA1 ; B5
dd C_DATA1 ; B6
dd C_DATA1 ; B7
dd C_DATA66 ; B8
dd C_DATA66 ; B9
dd C_DATA66 ; BA
dd C_DATA66 ; BB
dd C_DATA66 ; BC
dd C_DATA66 ; BD
dd C_DATA66 ; BE
dd C_DATA66 ; BF
dd C_MODRM+C_DATA1 ; C0
dd C_MODRM+C_DATA1 ; C1
dd C_DATA2 ; C2
dd 0 ; C3
dd C_MODRM ; C4
dd C_MODRM ; C5
dd C_MODRM+C_DATA1 ; C6
dd C_MODRM+C_DATA66 ; C7
dd C_DATA2+C_DATA1 ; C8
dd 0 ; C9
dd C_DATA2 ; CA
dd 0 ; CB
dd 0 ; CC
dd 0 ; CD
dd 0 ; CE
dd 0 ; CF
dd C_MODRM ; D0
dd C_MODRM ; D1
dd C_MODRM ; D2
dd C_MODRM ; D3
dd C_DATA1 ; D4
dd C_DATA1 ; D5
dd 0 ; D6
dd 0 ; D7
dd C_MODRM ; D8
dd C_MODRM ; D9
dd C_MODRM ; DA
dd C_MODRM ; DB
dd C_MODRM ; DC
dd C_MODRM ; DD
dd C_MODRM ; DE
dd C_MODRM ; DF
dd C_DATA1 ; E0
dd C_DATA1 ; E1
dd C_DATA1 ; E2
dd C_DATA1 ; E3
dd C_DATA1 ; E4
dd C_DATA1 ; E5
dd C_DATA1 ; E6
dd C_DATA1 ; E7
dd C_DATA66 ; E8
dd C_DATA66 ; E9
dd C_DATA66+C_MEM2 ; EA
dd C_DATA1 ; EB
dd 0 ; EC
dd 0 ; ED
dd 0 ; EE
dd 0 ; EF
dd C_PREFIX ; F0
dd 0 ; F1
dd C_PREFIX ; F2
dd C_PREFIX ; F3
dd 0 ; F4
dd 0 ; F5
dd 0 ; F6
dd 0 ; F7
dd 0 ; F8
dd 0 ; F9
dd 0 ; FA
dd 0 ; FB
dd 0 ; FC
dd 0 ; FD
dd C_MODRM ; FE
dd C_MODRM ; FF
table_0F label dword ; instrucciones con prefijo 0F
dd C_MODRM ; 00
dd C_MODRM ; 01
dd C_MODRM ; 02
dd C_MODRM ; 03
dd -1 ; 04
dd -1 ; 05
dd 0 ; 06
dd -1 ; 07
dd 0 ; 08
dd 0 ; 09
dd 0 ; 0A
dd 0 ; 0B
dd -1 ; 0C
dd -1 ; 0D
dd -1 ; 0E
dd -1 ; 0F
dd -1 ; 10
dd -1 ; 11
dd -1 ; 12
dd -1 ; 13
dd -1 ; 14
dd -1 ; 15
dd -1 ; 16
dd -1 ; 17
dd -1 ; 18
dd -1 ; 19
dd -1 ; 1A
dd -1 ; 1B
dd -1 ; 1C
dd -1 ; 1D
dd -1 ; 1E
dd -1 ; 1F
dd -1 ; 20
dd -1 ; 21
dd -1 ; 22
dd -1 ; 23
dd -1 ; 24
dd -1 ; 25
dd -1 ; 26
dd -1 ; 27
dd -1 ; 28
dd -1 ; 29
dd -1 ; 2A
dd -1 ; 2B
dd -1 ; 2C
dd -1 ; 2D
dd -1 ; 2E
dd -1 ; 2F
dd -1 ; 30
dd -1 ; 31
dd -1 ; 32
dd -1 ; 33
dd -1 ; 34
dd -1 ; 35
dd -1 ; 36
dd -1 ; 37
dd -1 ; 38
dd -1 ; 39
dd -1 ; 3A
dd -1 ; 3B
dd -1 ; 3C
dd -1 ; 3D
dd -1 ; 3E
dd -1 ; 3F
dd -1 ; 40
dd -1 ; 41
dd -1 ; 42
dd -1 ; 43
dd -1 ; 44
dd -1 ; 45
dd -1 ; 46
dd -1 ; 47
dd -1 ; 48
dd -1 ; 49
dd -1 ; 4A
dd -1 ; 4B
dd -1 ; 4C
dd -1 ; 4D
dd -1 ; 4E
dd -1 ; 4F
dd -1 ; 50
dd -1 ; 51
dd -1 ; 52
dd -1 ; 53
dd -1 ; 54
dd -1 ; 55
dd -1 ; 56
dd -1 ; 57
dd -1 ; 58
dd -1 ; 59
dd -1 ; 5A
dd -1 ; 5B
dd -1 ; 5C
dd -1 ; 5D
dd -1 ; 5E
dd -1 ; 5F
dd -1 ; 60
dd -1 ; 61
dd -1 ; 62
dd -1 ; 63
dd -1 ; 64
dd -1 ; 65
dd -1 ; 66
dd -1 ; 67
dd -1 ; 68
dd -1 ; 69
dd -1 ; 6A
dd -1 ; 6B
dd -1 ; 6C
dd -1 ; 6D
dd -1 ; 6E
dd -1 ; 6F
dd -1 ; 70
dd -1 ; 71
dd -1 ; 72
dd -1 ; 73
dd -1 ; 74
dd -1 ; 75
dd -1 ; 76
dd -1 ; 77
dd -1 ; 78
dd -1 ; 79
dd -1 ; 7A
dd -1 ; 7B
dd -1 ; 7C
dd -1 ; 7D
dd -1 ; 7E
dd -1 ; 7F
dd C_DATA66 ; 80
dd C_DATA66 ; 81
dd C_DATA66 ; 82
dd C_DATA66 ; 83
dd C_DATA66 ; 84
dd C_DATA66 ; 85
dd C_DATA66 ; 86
dd C_DATA66 ; 87
dd C_DATA66 ; 88
dd C_DATA66 ; 89
dd C_DATA66 ; 8A
dd C_DATA66 ; 8B
dd C_DATA66 ; 8C
dd C_DATA66 ; 8D
dd C_DATA66 ; 8E
dd C_DATA66 ; 8F
dd C_MODRM ; 90
dd C_MODRM ; 91
dd C_MODRM ; 92
dd C_MODRM ; 93
dd C_MODRM ; 94
dd C_MODRM ; 95
dd C_MODRM ; 96
dd C_MODRM ; 97
dd C_MODRM ; 98
dd C_MODRM ; 99
dd C_MODRM ; 9A
dd C_MODRM ; 9B
dd C_MODRM ; 9C
dd C_MODRM ; 9D
dd C_MODRM ; 9E
dd C_MODRM ; 9F
dd 0 ; A0
dd 0 ; A1
dd 0 ; A2
dd C_MODRM ; A3
dd C_MODRM+C_DATA1 ; A4
dd C_MODRM ; A5
dd -1 ; A6
dd -1 ; A7
dd 0 ; A8
dd 0 ; A9
dd 0 ; AA
dd C_MODRM ; AB
dd C_MODRM+C_DATA1 ; AC
dd C_MODRM ; AD
dd -1 ; AE
dd C_MODRM ; AF
dd C_MODRM ; B0
dd C_MODRM ; B1
dd C_MODRM ; B2
dd C_MODRM ; B3
dd C_MODRM ; B4
dd C_MODRM ; B5
dd C_MODRM ; B6
dd C_MODRM ; B7
dd -1 ; B8
dd -1 ; B9
dd C_MODRM+C_DATA1 ; BA
dd C_MODRM ; BB
dd C_MODRM ; BC
dd C_MODRM ; BD
dd C_MODRM ; BE
dd C_MODRM ; BF
dd C_MODRM ; C0
dd C_MODRM ; C1
dd -1 ; C2
dd -1 ; C3
dd -1 ; C4
dd -1 ; C5
dd -1 ; C6
dd -1 ; C7
dd 0 ; C8
dd 0 ; C9
dd 0 ; CA
dd 0 ; CB
dd 0 ; CC
dd 0 ; CD
dd 0 ; CE
dd 0 ; CF
dd -1 ; D0
dd -1 ; D1
dd -1 ; D2
dd -1 ; D3
dd -1 ; D4
dd -1 ; D5
dd -1 ; D6
dd -1 ; D7
dd -1 ; D8
dd -1 ; D9
dd -1 ; DA
dd -1 ; DB
dd -1 ; DC
dd -1 ; DD
dd -1 ; DE
dd -1 ; DF
dd -1 ; E0
dd -1 ; E1
dd -1 ; E2
dd -1 ; E3
dd -1 ; E4
dd -1 ; E5
dd -1 ; E6
dd -1 ; E7
dd -1 ; E8
dd -1 ; E9
dd -1 ; EA
dd -1 ; EB
dd -1 ; EC
dd -1 ; ED
dd -1 ; EE
dd -1 ; EF
dd -1 ; F0
dd -1 ; F1
dd -1 ; F2
dd -1 ; F3
dd -1 ; F4
dd -1 ; F5
dd -1 ; F6
dd -1 ; F7
dd -1 ; F8
dd -1 ; F9
dd -1 ; FA
dd -1 ; FB
dd -1 ; FC
dd -1 ; FD
dd -1 ; FE
dd -1 ; FF
end
Ahora podemos obtener la longitud de la instruccion en una direccion
arbitraria. Repetiremos esta llamada hasta que leamos los 5 bytes. Despues
esto copiara esos bytes a old_hook. Sabemos la longitud de las primeras
instrucciones, asique pordemos rellenar el salto relativo con la direccion
que va a continuacion de estas instrucciones en la funcion original.
.386p
.model flat, stdcall
...
.data
kernel_name db "kernel32.dll",0
sleep_name db "Sleep",0
...
MEM_RELEASE dd 000008000h
;16 nops + un salto relativo
old_sleep db 090h,090h,090h,090h,090h,090h,090h,090h,
090h,090h,090h,090h,090h,090h,090h,090h,
0E9h,000h,000h,000h,000h
.code
start:
push 5000
call Sleep
do_hook:
push offset kernel_name
call GetModuleHandleA
push offset sleep_name
push eax
call GetProcAddress
push eax
mov esi,eax
xor ecx,ecx
mov ebx,esi
get_five_bytes:
push ecx
push ebx
call get_instr_len ;llamamos al LDE32
pop ecx
add ecx,eax
add ebx,eax
cmp ecx,5
jb get_five_bytes
mov edi,offset old_sleep ;contando la direccion del salto
mov [edi+011h],ebx
sub [edi+011h],edi
sub dword ptr [edi+011h],015h
rep movsb
pop edi
;el siguiente codigo es lo mismo de antes
push PAGE_READWRITE
push MEM_COMMIT
push MEMORY_BASIC_INFORMATION_SIZE
push 0
call VirtualAlloc
test eax,eax
jz do_sleep
mov esi,eax
push MEMORY_BASIC_INFORMATION_SIZE
push esi
push edi
call VirtualQuery
test eax,eax
jz free_mem
call GetCurrentProcess
push 5
push edi
push eax
call FlushInstructionCache
lea eax,[esi+014h]
push eax
push PAGE_EXECUTE_READWRITE
lea eax,[esi+00Ch]
push [eax]
push [esi]
call VirtualProtect
test eax,eax
jz free_mem
mov byte ptr [edi],0E9h
mov eax,offset new_sleep
sub eax,edi
sub eax,5
inc edi
stosd
push offset old_protect
lea eax,[esi+014h]
push [eax]
lea eax,[esi+00Ch]
push [eax]
push [esi]
call VirtualProtect
free_mem:
push MEM_RELEASE
push 0
push esi
call VirtualFree
do_sleep:
push 5000
call Sleep
push 0
call ExitProcess
new_sleep:
mov eax,dword ptr [esp+004h]
add eax,eax ;doblando el tiempo de espera
push eax
mov eax,offset old_sleep ;llamada a la function antigua
call eax
ret 004h
despues del gancho tenemos lo siguiente:
004010CC: 6888130000 push 000001388h
004010D1: E818090000 call Sleep
Sleep: ;este es el salto en la direccion de la IAT
004019EE: FF2514204000 jmp dword ptr [000402014h]
tabulka:
00402014: 79 67 E8 77 6C 7D E8 77
Kernel32.Sleep:
77E86779: E95FA95788 jmp 0004010DDh
new_sleep:
004010DD: 8B442404 mov eax,dword ptr [esp+4]
004010E1: 03C0 add eax,eax
004010E3: 50 push eax
004010E4: B827304000 mov eax,000403027h
004010E9: FFD0 call eax
old_sleep:
00403027: 6A00 push 0
00403029: FF742408 push dword ptr [esp+8]
0040302D: 90 nop
0040302E: 90 nop
0040302F: 90 nop
00403030: 90 nop
00403031: 90 nop
00403032: 90 nop
00403033: 90 nop
00403034: 90 nop
00403035: 90 nop
00403036: 90 nop
00403037: E94337A877 jmp Kernel32.77E8677F
;esta instruccion es colocada un byte despues de la primera instruccion
;en Kernel32.Sleep
(77E86779)
Kernel32.77E8677F:
77E8677F: E803000000 call Kernel32.SleepEx
... ;lo restante no tiene importancia
Para poner esto mas claro, asi es como aparece la version original de
Kernel32.Sleep:
Kernel32.Sleep:
77E86779: 6A00 push 0
77E8677B: FF742408 push dword ptr [esp+8]
77E8677F: E803000000 call Kernel32.SleepEx
77E86784: C20400 ret 00004h
Como puedes ver hemos copiado la primera y segunda instruccion
(6 bytes) y el salto relativo apuntab a la siguiente instruccion y asi es como
debe ser. Hemos supuesto que no hay saltos relativos en los primeros bytes
de las funciones. Si los hubiera seria un problema. El siguiente problema es
con APIs como ntdll.DbgBreakPoint. Son demasiado cortas para este metodo de
gancheo.Y para tanto como es llamada por Kernel32.DebugBreak, no es gancheable
cambiando la IAT. ¿Pero quien quiere ganchear una funcion que solo consta de
la llamada a int 3 ?
Nada es imposible. Si piensas sobre esto podrias encontrar la forma.
Estuve pensando que se puede enganchar la funcion siguiente a una dada( seria
dañada al reescribir los 5 bytes primeros de la funcion previa). La funcion
DbdBreakPoint son 2 bytes, asique podemos poner algunos flags aqui e intentar
escribir un salto condicional en el comienzo de la segunda funcion...Pero este
no es nuestro asunto ahora.
Con el problema de salvaguardar la funcion original llegamos al desenganche.
El desenganche es restaurar los bytes cambiados con el estado original.
Cuando reescribimos la IAT tendremos que restaurar la direccion original a
la tabla. Cuando usemos el parche de los 5 bytes tendremos que volver a copiar
las instrucciones en sus direcciones originales. Ambas formas son muy
sencillas y no es necesario escribir mas sobre ello.
=====[ 3.2.4 Other process hooking ]============================================
Ahora haremos algo practico con los ganchos en ejecucion. ¿Quien
quiere enganchar su propio proceso? Esto servia para aprender la teoria
pero no es muy practico.
Te mostrare tres metodos de enganchar otros procesos. Dos de ellos
usan CreateRemoteThread que esta solo en Windows con tecnologia NT. El problema
de los ganchos para mi no es tan interesante en versiones antiguas de windows.
Despues de todo intentare explicar un metodo que no lo probe, asique podria no
funcionar.
Primero veamos un poco sobre CreateRemoteThread. Como la ayuda de esta
funcion dice, crea un hilo nuevo en cualquier proceso y ejecuta su codigo.
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
El manejador de hProcess lo conseguimos con OpenProcess. Aqui tenemos
que tener los privilegios necesarios. El puntero lpStartAddress apunta a un
lugar de memoria del procedo OBJETIVO donde esta la primera instruccion del
nuevo hilo. Como el nuevo hilo es creado en el proceso objetivo este estara
en la zona de memoria del proceso objetivo. El puntero lpParameter apunta a
un argumento que sera el del nuevo hilo.
=====[ 3.2.4.1 Inyeccion DLL]===================================================
Podemos ejecutar un nuevo hilo desde cualquier parte en la memoria de
un proceso objetivo. Esto es inutil amenos que tengamos codigo propio en el.
El primer metodo hace esto. Usa GetProcAddress para obtener la direcion actual
de LoadLibrary. Entonces pasa como lpStartAddress la direccion de LoadLibrary.
La funcion LoadLibrary tiene un solo parametro, igual que la funcion para nuevo
hilo en el proceso objetivo.
HINSTANCE LoadLibrary(
LPCTSTR lpLibFileName
);
Usaremos esta similidad de parametros y pasaremos el nombre la la DLL
como lpParameter. Despues de ejecutar el nuevo hilo, lpParameter contendra
lpLibFileName. Lo mas importante es el comportamiento descrito arriba.
Despues de cargar el nuevo modulo en la memoria del proceso victima, la parte
de inicializacion es ejecutada. Si colocamos funciones especificas que
enganchen lo que nosotros queramos en el modulo, ya esta todo hecho. Despues
de ejecutar la parte de inicializacion, el hilo no tendra nada que hacer y se
carrera. Pero nuestro modulo estara todavia en memoria. Ese metodo esta
curioso y es facil de implementar. Se le llama Inyeccion DLL. Pero si te pasa
como a mi, no te gusta tener que usar DLLs. Pero si no te importa tener
librerias esta es la forma mas facil y mas rapida (desde el punto de vista
del programador)
=====[ 3.2.4.2 Codigo Independiente ]===========================================
Este metodo es muy dificil pero es impresionante. Codigo independiente
es el codigo sin direcciones estaticas. Todo es relativo en el hacia cualquier
otra parte del codigo propio. Este codigo es hecho la mayoria de las veces si
no sabemos la direccion donde este codigo sera ejecutado. Seguramente seria
posible obtener esta direccion y reprogramar nuestro codigo para que funconara
en la nueva direccion sin errores, pero esto seria incluso mas dificil que
haciendo codigo independiente. Un ejemplo de este tipo de codigo puede ser el
codigo de un virus. El virus que infecta ejecutables se añade por si mismo al
ejecutable. En diferentes ejecutables el codigo del virus estara en sitios
diferente dependiendo de las estructuras, la longitud, etc
En principio tenemos que insertar nuestro codigo en un proceso
objetivo. Entonces la funcion CreateRemoteThread nos permitira ejecutar
nuestro codigo.Asi, al principio tenemos que obtener informacion sobre el
proceso victima y abrirlo con OpenProcess. La funcion VirtualAllocEx nos
reserva espacio en la zona de memoria del proceso victima. Finalmente usamos
WriteProcessMemory para escribir nuestro codigo en la memoria reservada y lo
ejecutamos. En CreateRemoteThread, lpStartAddress sera la direccion de la
memoria reservada y lpParameter puede ser lo que queramos. Me gusta este
metodo porque no necesitamos ficheros innecesarios.
=====[ 3.2.4.3 Cambio Raw ]====================================================
No hy una funcion CreateRemoteThread en versiones de windows
antiguas (sin NT). Asique no podemos usar esta funcion para los ganchos. Hay
probablmente mejores metodos para enganchar que el metodo que voy a contar. De
hecho nose si funcionara en la practica (uno nunca sabe cuando usa windows)
pero teoricamente esta todo correcto.
No necesitamos en absoluto tener nuestro codigo en el proceso victima
para enganchar sus funciones. Tenemos la funcion WriteProcessMemory (deberia
estar en todas las versiones de windows) y tenemos OpenProcess tambien. Lo
ultimo que necesitamos es VirtualProtectEx que permite cambiar el acceso a
paginas de memoria en otros procesos. No veo ninguna razon por la que no
podamos enganchar funciones directamente desde nuestro proceso.
=====[ 4. Finalizando ]=========================================================
Este pequeño manual termina aqui. Agradecere cualquier comentario que
describa metodos no mencionados de enganche, estoy seguro que hay un monton.
Tambien agradecere cualquier comentario en las partes que no estan descritas
tan detalladamente. Puedes enviarme codigo fuente si quieres para los
problemas de ganchos que por ejemplo estuve demasiado vago para escribir. La
meta de este docmento es mostrar detalles de cada tecnica de ganchos. Espero
haber hecho parte de esto.
Agradecimieno especial a Z0MBiE por su trabajo, que asi no tuve que
escribir yo mismo y dedicar años estudiando tablas para conseguir la longitud
de instruccion.
===================================[ Fin ]======================================
|