oj ludzie, ludzie... kombinujecie jak konie pod górę:
Kopiuj
#include <windows.h>
#include <stdio.h>
void MemWrite (void* dest, void* src, int size=1)
{
DWORD written;
WriteProcessMemory (GetCurrentProcess(), dest, src, size, &written);
};
LONG WINAPI UnhandledExceptionFilter( EXCEPTION_POINTERS* ExceptionInfo);
void* pWriteFile;
static unsigned char int3 = 0xCC;
static unsigned char org_byte;
static unsigned long count;
static char info1 [] = "[PROGRAM] wywolanie WriteFile powodujace breakpointa\n";
static char info2 [] = "[PROGRAM] ...i znow WriteFile...\n";
static char info3 [] = "[PROGRAM] ...i jeszcze raz WriteFile...\n";
static char info4 [] = "[PROGRAM] cos komus to moze przypomina? :>\n";
int main ()
{
DWORD written;
pWriteFile = (void*) GetProcAddress (GetModuleHandle("kernel32.dll"), "WriteFile");
org_byte = *(char*)pWriteFile;
SetUnhandledExceptionFilter(&UnhandledExceptionFilter);
printf ("[BPX] Ustawianie breakpointa na WriteFile - adres: %lx\n", pWriteFile);
MemWrite (pWriteFile, &int3);
HANDLE StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
WriteFile (StdOut, &info1, sizeof(info1)-1, &written, 0);
WriteFile (StdOut, &info2, sizeof(info2)-1, &written, 0);
WriteFile (StdOut, &info3, sizeof(info3)-1, &written, 0);
WriteFile (StdOut, &info4, sizeof(info4)-1, &written, 0);
MemWrite (pWriteFile, &org_byte);
printf ("[BPX] Usunieto breakpointa na WriteFile - uaktywniony %lu razy\n", count);
return 0;
};
LONG WINAPI UnhandledExceptionFilter (EXCEPTION_POINTERS* ExceptionInfo)
{
switch(ExceptionInfo->ExceptionRecord->ExceptionCode)
{
case EXCEPTION_BREAKPOINT:
if (ExceptionInfo->ExceptionRecord->ExceptionAddress != pWriteFile)
return 1;
ExceptionInfo->ContextRecord->EFlags |= 0x100;
MemWrite (pWriteFile, &org_byte);
printf ("[UEF] Wylapano wywolanie WriteFile z adresem powrotu %lx. Ustawianie Trap Flag\n",
*(DWORD*)ExceptionInfo->ContextRecord->Esp);
return EXCEPTION_CONTINUE_EXECUTION;
case EXCEPTION_SINGLE_STEP:
DWORD Eip(ExceptionInfo->ContextRecord->Eip), ddWriteFile ((DWORD)pWriteFile);
if ((Eip < ddWriteFile) || (Eip > ddWriteFile+15))
return 1;
printf ("[UEF] Pierwsza instrukcja WriteFile zostala wykonana. Ponowne ustawianie breakpointa.\n");
MemWrite (pWriteFile, &int3);
count++;
return EXCEPTION_CONTINUE_EXECUTION;
default:
return 1;
};
};
Wynik z konsoli:
Kopiuj
[BPX] Ustawianie breakpointa na WriteFile - adres: 7c82f42e
[UEF] Wylapano wywolanie WriteFile z adresem powrotu 40140a. Ustawianie Trap Flag
[UEF] Pierwsza instrukcja WriteFile zostala wykonana. Ponowne ustawianie breakpointa.
[PROGRAM] wywolanie WriteFile powodujace breakpointa
[UEF] Wylapano wywolanie WriteFile z adresem powrotu 401431. Ustawianie Trap Flag
[UEF] Pierwsza instrukcja WriteFile zostala wykonana. Ponowne ustawianie breakpointa.
[PROGRAM] ...i znow WriteFile...
[UEF] Wylapano wywolanie WriteFile z adresem powrotu 401458. Ustawianie Trap Flag
[UEF] Pierwsza instrukcja WriteFile zostala wykonana. Ponowne ustawianie breakpointa.
[PROGRAM] ...i jeszcze raz WriteFile...
[UEF] Wylapano wywolanie WriteFile z adresem powrotu 401482. Ustawianie Trap Flag
[UEF] Pierwsza instrukcja WriteFile zostala wykonana. Ponowne ustawianie breakpointa.
[PROGRAM] cos komus to moze przypomina? :>
[BPX] Usunieto breakpointa na WriteFile - uaktywniony 4 razy
Kompilowane g++, pod Windows Server 2003 sp1 chodzi. Stosowanie SEHa do hooków to moim zdaniem głupota - biblioteki /np. VCL/ i sam system tworzą niejawnie wątki mogące wywoływać daną funkcję. Oczywiście pamiętamy, że SEH jest lokalny, działa w obrębie jednego wątku /i co wtedy z BP?/. Można też użyć rejestrów DR ale też będzie ograniczenie do jednego wątku /ale inne się nie wysypią/. UEF to niezła alternatywa - hook globalny, ale jest coś jeszcze lepszego /niestety tylko na NT/ - VEH. Coś pięknego - taki globalny SEH /łańcuch handlerów/ tylko wygodniejszy :>. Coś o tym pisałem przy okazji ostatniej prowokacji barta.
Małego wyjaśnienia może wymagać:
if ((Eip < ddWriteFile) || (Eip > ddWriteFile+15))
Maksymalny rozmiar instrukcji akceptowany przed dekodery x86 to 15 bajtów - sprawdzamy czy wyjątek wystąpił w oczekiwanym miejscu - przejście do następnej instrukcji /tak mniej więcej ;P - bez dekodera nie można określić dokładnej pozycji 2 instrukcji wcześniej/ . I jedna uwaga do TF - flaga jest resetowana przez procesor po wykonaniu pojedynczej instrukcji. Może po południu jeszcze coś stuknę...
//edit: dodałem brakującego printfa - zamiast 2dw zrobiłem 2dd [rotfl]
ciekawy pomysł: można w 'sprytny' sposób ustalić adres 2 instrukcji danej funkcji poprzez breakpointa sprzętowego obsługiwanego SEH'em - wyłapywany jest wyjątek, ustawiany jest TF i znów SEH :>. bum - mamy adres ;P biorę się za implementację... chyba ;P
/edit2: pomysł może i ciekawy ale... Wygląda na to, że Windows nie pozwala wątkowi na ustawienie rejestru DR7 /w którymkolwiek handlerze - SEH, VEH, UEF ani poprzez SetThreadContext.../, co ciekawe DR0 można ustawić :/ W każdym razie 2k3 nie pozwala na włączenie breakpointów sprzętowych... w sumie to się nie dziwię - można sobie podmapować ntoskrnl.exe, przelecieć exporty i ustalić adres interesującego nas kodu /byłoby przenośne - na każdego NT/, przy pomocy API ustalić adres tego modułu /niby jest stały... ale nie wiem czy we wszystkich NT jest ten sam/ i ustawić HBP. Co by to dało gdyby się dało? Program mógłby zmusić kod ring0 do w sumie wszystkiego ;P Oczywiście to nie jest jedyne ograniczenie :>. Tzn. DR7 można ustawić ale wątek sam sobie nie może - coś musi robić za debugger /przynajmniej tak to na moim 2k3 wygląda/. Ale adres takiej drugiej instrukcji w funkcji takiej jak WriteFile można ustalić na setki sposobów, np. śledzenie poprzez TF. Tu się SEH przyda - mamy wyłączność, a co za tym idzie pewność, że żaden wyjątek nie pojawi się nieprzewidziany.
Zrezygnowałem ze standardowej konwencji struktury ERR wrzucanej po kawałku na stos, na rzecz czego bardziej a'la SetUnhandledExceptionFilter. Powyższy kod rozbudowałem o śledzenie do drugiej instrukcji WriteFile. Wstawki asm dla SEHa w at&t /gcc się kłania/, tak się całość prezentuje:
Kopiuj
#include <windows.h>
#include <excpt.h>
#include <stdio.h>
#define ASMV asm volatile
typedef struct ERR;
typedef int (__cdecl *EXCEPTION_HANDLER) (EXCEPTION_RECORD*, ERR*, CONTEXT*);
typedef struct ERR
{
ERR* Next;
EXCEPTION_HANDLER Handler;
} *PERR;
inline PERR SEH_Get ()
{ PERR ptr; ASMV ("movl %%fs:0, %%eax":"=a"(ptr):); return ptr; };
inline PERR SEH_Set (PERR err)
{ PERR ptr; ASMV ("xchgl %%fs:0, %%eax":"=a"(ptr): "a" (err)); return ptr; };
int __cdecl SEH_ExceptionHandler (EXCEPTION_RECORD*, PERR , CONTEXT*);
LONG WINAPI UnhandledExceptionFilter( EXCEPTION_POINTERS* ExceptionInfo);
void MemWrite (void* dest, void* src, int size=1)
{
DWORD written;
WriteProcessMemory (GetCurrentProcess(), dest, src, size, &written);
};
void* pWriteFile;
DWORD SecondInst;
int trigger;
static unsigned char int3 = 0xCC;
static unsigned char org_byte;
static unsigned long count;
static char info1 [] = "[PROGRAM] wywolanie WriteFile powodujace breakpointa\n";
static char info2 [] = "[PROGRAM] ...i znow WriteFile...\n";
static char info3 [] = "[PROGRAM] ...i jeszcze raz WriteFile...\n";
static char info4 [] = "[PROGRAM] cos komus to moze przypomina? :>\n";
int main ()
{
ERR Error;
DWORD written;
pWriteFile = (void*) GetProcAddress (GetModuleHandle("kernel32.dll"), "WriteFile");
org_byte = *(char*) pWriteFile;
Error.Next = SEH_Get();
Error.Handler = &SEH_ExceptionHandler;
printf ("[BPX] Ustawianie handlera SEH, poprzednia struktura ERR: %lx\n", Error.Next);
SEH_Set (&Error);
printf ("[SEH] Rozpoczecie sledzenia do drugiej instrukcji WriteFile\n");
RaiseException (EXCEPTION_SINGLE_STEP, 0, 0, 0);
WriteFile (0, 0, 0, 0, 0);
printf ("[SEH] Druga instrukcja WriteFile znaleziona - adres: %lx\n", SecondInst);
SEH_Set (Error.Next);
printf ("[BPX] Przywrocono poprzedni handler SEH\n");
SetUnhandledExceptionFilter (&UnhandledExceptionFilter);
printf ("[BPX] Ustawianie breakpointa na WriteFile - adres: %lx\n", pWriteFile);
MemWrite (pWriteFile, &int3);
HANDLE StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
WriteFile (StdOut, &info1, sizeof(info1)-1, &written, 0);
WriteFile (StdOut, &info2, sizeof(info2)-1, &written, 0);
WriteFile (StdOut, &info3, sizeof(info3)-1, &written, 0);
WriteFile (StdOut, &info4, sizeof(info4)-1, &written, 0);
MemWrite (pWriteFile, &org_byte);
printf ("[BPX] Usunieto breakpointa na WriteFile - uaktywniony %lu razy\n", count);
return 0;
};
LONG WINAPI UnhandledExceptionFilter (EXCEPTION_POINTERS* ExceptionInfo)
{
switch(ExceptionInfo->ExceptionRecord->ExceptionCode)
{
case EXCEPTION_BREAKPOINT:
if (ExceptionInfo->ContextRecord->Eip != (DWORD)pWriteFile)
return 1;
ExceptionInfo->ContextRecord->EFlags |= 0x100;
MemWrite (pWriteFile, &org_byte);
printf ("[UEF] Wylapano wywolanie WriteFile, adres powrotu: %lx. Ustawianie Trap Flag\n",
*(DWORD*)ExceptionInfo->ContextRecord->Esp);
return EXCEPTION_CONTINUE_EXECUTION;
case EXCEPTION_SINGLE_STEP:
DWORD Eip(ExceptionInfo->ContextRecord->Eip), ddWriteFile ((DWORD)pWriteFile);
if (ExceptionInfo->ContextRecord->Eip != SecondInst)
return 1;
printf ("[UEF] Pierwsza instrukcja WriteFile wykonana. Ponowne ustawianie breakpointa.\n");
MemWrite (pWriteFile, &int3);
count++;
return EXCEPTION_CONTINUE_EXECUTION;
default:
return 1;
};
};
int __cdecl SEH_ExceptionHandler (EXCEPTION_RECORD *ExceptionRecord, PERR ErrorRecord, CONTEXT *ContextRecord)
{
if (ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP) return 1;
if(!trigger)
{
trigger = ContextRecord->Eip == (DWORD) pWriteFile;
ContextRecord->EFlags |= 0x100;
}
else
SecondInst = ContextRecord->Eip;
return 0;
};
Zaraz ktoś mnie pewenie zamorduje za objętość... hm, debugger to nie jest, ale sprawdza się całkiem nieźle. Jedyny minus to konieczność wywołania monitorowanej funkcji. Trochę tych printfów jest, ale dzieki temu mniej\więcej widać co i kiedy się dzieje /większa szansa, że ktoś więcej to zrozumie/. Do tego wszystkiego można się też dobrać poprzez tablicę importów - monitorowane będą wywołania z konkretnego modułu /jeżeli funkcja nie jest pobierana dynamicznie/. Zmodyfikowanie exportów też się przydać może - ale zadziała tylko dla modułów załadowanych po modyfikacji. Tylko kto nam broni przelecieć IAT wszystkich załadowanych modułów i powstawiać hooka gdzie trzeba? Wcześniej by wypadało exporty 'poprawić'. Problemem będą jednak dynamicznie pobierane adresy, które są zapisywane na potrzeby dalszego działania programu /program się odpala, pobiera adres za pomocą GetProcAddress i zapisuje go w sekcji danych na użytek późniejszych wywołań/. Chociaż... w sumie 2^32 kombinacji ta na tyle dużo, aby szansa na pomyłkę przy przeszukiwaniu całej pamięci była znikoma. W ostateczności też można nadpisać początek funkcji, ale tu bez chociaż podstaw disasemblera się nie obejdzie. Później może napiszę modyfikowanie importów i exportów /dla mnie to banał... już tele razy to 'ręcznie' robiłem, że napisanie automatu będzie miłą odmianą/. W sumie... jak sądzicie, czy aby upewnić się, że dany dword to adres funkcji jest podmalować moduł, w którym się ów dword znajduje i porównać?
p.s. sorry, za taki czas reakcji, ale miałem masę spraw na głowie... do tego padł mi HDD - w sumie podstawową sprawność jeżeli chodzi o programowanie to system odzyskał dopiero wczoraj :/ Cóz, wracam do akcji :>