En ambos ejemplos asumía que el S.O. cargaría las APIs de Kernell32.dll en direcciones equivalentes cosa que viene siendo habitual en windows. En Windows todos los procesos cargan una copia de Kernel32.dll y no es obligatoria la carga en direcciones equivalentes.
La diferencia fundamental de las dos técnicas de inyección expuestas es que, mientras que CreateRemoteThread crea un hilo paralelo al principal con un comienzo y un fin claro, la técnica de suspensión de hilo requiere un "cambio de vias del procesador" para un mismo hilo y por tanto tenemos que tener prevista la vuelta del código inyectado o la app victima morirá. Esto lo conseguimos modificando el código del Shellcode antes de la inyección, para colocar el camino de vuelta. Recordar que en asm, un call provoca un push del punto de retorno y luego el salto a la subrutina. La instrucción ret realiza un pop recuperando la dirección de vuelta, para luego saltar a ésta. Conociendo esto, es el truco que usaremos para engañar al código y obligarlo a devolver el control al hilo interrumpido.
Ambas técnicas son un ejercicio interesante, pero, en principio los shellcodes usados deben tener un diseño específico..., o no.
Tras la magnífica publicación de seoane Crear Shellcode en delphi me volvió a picar el gusanillo de este tema y quise resolver dos cosas que no lo estaban en mis dos anteriores tutoriales:
1- Que un mismo shellcode pueda ser inyectado con las dos técnicas indistintamente, y por otro lado.
2- Que el shellcode encuentre por si mismo la dirección base de Kernell32.dll (hModule) y la fundamental API GetProcAddress.
El primer punto lo resuelvo asumiendo una dirección de retorno imposible, 0h que si es cambiada, corresponderá al deseo de inyección por suspensión de thread, apilándola en este caso. Si su valor es cero asumo que la inyección es por CreateRemoteThread o una ejecución directa, con lo que no es apilada.
Este segundo punto lo resolvió brillantemente seoane escaneando a lo bruto el espacio de direcciones del proceso anfitrión, yo voy a usar otra técnica más directa. Conociendo que la estructura PEB se encuentra en fs:0x30 podemos acceder al elemento PPEB_LDR_DATA apuntado por un offset 0Ch (ver estructura) y en este encontrar InLoadOrderModuleList también apuntado en un offset 0Ch, que apunta a una lista doblemente enlazada donde encontraremos en su primera entrada a ntdll y segunda entrada a Kernell32. Así conoceremos el BaseAddress de kernel32.dll. Si Microsoft no varía esto en un futuro seguirá siendo perfectamente válido. Encontrareis aquí una explicación mas detallada de esto.
DWORD GetKernel32Base() { asm{ mov eax, fs:[0x30] // Puntero al PEB mov eax, [eax + 0x0C] // Puntero a PPEB_LDR_DATA mov eax, [eax + 0x0C] // Puntero a InLoadOrderModuleList mov eax, [eax] // Entrada de ntdll.dll mov eax, [eax] // Entrada de kernel32.dll mov eax, [eax + 0x18] // BaseAddress de kernel32.dll } }
Si recordáis, en este enlace, "Encontrar los datos exportados por un módulo" trataba precisamente de encontrar todas las funciones y variables que exporta una dll o ejecutable. En dicha publicación exponía este código:
PIMAGE_EXPORT_DIRECTORY ImageExportDirectory(HMODULE hModule) { DWORD VirtualAddress_edata = ((DWORD)hModule + *(DWORD*)((DWORD)hModule + 0x3C) + 0x78); return PIMAGE_EXPORT_DIRECTORY((DWORD)hModule + *(DWORD*)VirtualAddress_edata); } //----------------------------------------------------------------------------- void __fastcall GetExportData(char* ModuleName, TMemo* Memo1) { Memo1->Clear(); HMODULE hModule = LoadLibrary(ModuleName); if(!hModule) hModule = GetModuleHandle(ModuleName); if(!hModule){ Memo1->Text = "Can't open module"; return; } PIMAGE_EXPORT_DIRECTORY IED = ImageExportDirectory(hModule); char* ModuleName = (char*)(IED->Name + (DWORD)hModule); char** Names = (char**)(IED->AddressOfNames + (DWORD)hModule); DWORD* EntryPoints = (DWORD*)(IED->AddressOfFunctions + (DWORD)hModule); WORD* Index = (WORD*)(IED->AddressOfNameOrdinals + (DWORD)hModule); // Listar las funciones exportadas: for(int n=0; n<IED->NumberOfNames; n++){ if(Index[n]>=IED->NumberOfFunctions) continue; char* Name = Names[n] + (DWORD)hModule; Memo1->Lines->Add("0x" + IntToHex((int)(EntryPoints[Index[n]] + (DWORD)hModule), 8) + ": " + String(Name)); Application->ProcessMessages(); } FreeLibrary(hModule); }
El código expuesto servirá de base para encontrar la API GetProcAddress una vez que ya conocemos el hModule de Kernel32.dll:
mov eax, fs:[0x30] // Puntero al PEB mov eax, [eax + 0x0C] // Puntero a PPEB_LDR_DATA mov eax, [eax + 0x0C] // Puntero a InLoadOrderModuleList mov eax, [eax] // InLoadOrderModuleList de ntdll.dll mov eax, [eax] // InLoadOrderModuleList de kernel32.dll mov eax, [eax + 0x18] // BaseAddress de kernel32.dll mov KB, eax // Encuentro PIMAGE_EXPORT_DIRECTORY mov eax, [eax + 0x3C] add eax, 0x78 add eax, KB mov eax, [eax] add eax, KB mov IED, eax
Por no alargar mucho el mensaje termino aquí esta parte.
Saludos.