Öncelikle şunu belirteyim, tam olarak hazır bir program vermeyeceğim, bu konular genelde iyi amaçlar için kullanılmadığından, hazır fonksiyonu kodunda kullanamayacak, veya bu örnekte gereken dll injection kodunu yazamayacak kişilerin eline hazır program vermek istemiyorum.
Listelenen dosyaları elde etmenin yolları şunlardır (farklı yollar da vardır tabi ama api hook dediğin için)
1) FindFirstFile ve FindNextFile ı hook edersin, bu fonksiyonu hook fonksiyonundan çağırdıktan sonra dosya adını alırsın.
2) Bu fonksiyonların kendi içinde çağırdığı native api olan NtQueryDirectoryFile apisini hook edersin, ntdll.dll içindeki native apileri hook etmenin yolu tamamen farklıdır ve sakıncaları olabilir bazı durumlarda ama kernele çıkan tek yol olduğu için avantajı da var.
Bu iki yol user mode idi, bunları yapmak için her process'in kendi bellek alanına hook fonksiyonunu yazman lazım ve orda çalıştırman lazım (bkz korumalı mod) ayrıca her process' in kernel32 si normalde bellekte tek kopyadır ama sen editlersen editlediğin yerin sana özel bir kopyası çıkarılır, diğer processler yine editlenmemiş olanı kullanır (ntdll.dll içinde geçerli) (bkz copy on write) doğal olarak her process de hook u ayrıca yapman lazım. bunun da en basit yolu = dll injection. (senin durumunda tek hook edeceğin explorer.exe olacağı için sıkıntı yok)
Gelelim kernel mode da ne yapabiliriz.
3) Aygıt sürücü yazarsın, kernel modda NtQueryDirectoryFile ın kernel kodunu hook edersin, (bkz ssdt hook) kendi fonksiyonunu yazarsın oraya, tüm processleri ayrı ayrı hook etmekle uğraşmazsın, kernelde herşeyin tek kopyası vardır ve tüm kernel mod programlar ve kernel aynı bellek alanını kullanır. (bkz kernel space vs user space)
4) FileSystem Filter Driver yazarsın, her dizin listeleme isteği geldiğinde önce listelenmiş dosya isimlerini sen ele geçirirsin ve user mod programına yollarsın. Aslında kernel mode da gördüğün gibi herşey daha kolaydır ancak kernelde kod yazmak da ayrıca deneyim isteyen birşey.
Şimdi yukardaki adımları açıklıyorum.
1) Bir dll hazırlayacaksın, yüklenir yüklenmez FindFirstFile ve FindNextFile apilerini kernel32 den bulup oraya kendi fonksiyonunun adresini yazacaksın, kendi fonksiyonunda da yine bu apileri çağırıp sonuçlarını ele geçireceksin. Bunu yapan kendi fonksiyonum (j4x ile birlikte yazdık ancak şuan kullanmıyoruz)
BOOL SetApiHook (LPCSTR szDllName,
LPCSTR szApiName,
PHOOK PhkFunc,
LPVOID lpHookFunc
)
{
HMODULE hHookDll = NULL;
DWORD dwOldFlags = 0;
LPVOID lpFunc = NULL;
*((int*)(JmpCode + 1)) = (int)lpHookFunc;
CopyMemory(PhkFunc->JmpCode, JmpCode, TRAMPOLINE_SIZE);
hHookDll = GetModuleHandle(szDllName);
if (hHookDll == NULL)
{
return FALSE;
}
lpFunc = GetProcAddress(hHookDll, szApiName);
if (lpFunc == NULL)
{
return FALSE;
}
PhkFunc->FuncPtr = lpFunc;
if (!VirtualProtect (lpFunc,
TRAMPOLINE_SIZE,
PAGE_EXECUTE_READWRITE,
&dwOldFlags)) {
return FALSE;
}
CopyMemory(PhkFunc->OrgBytes, lpFunc, TRAMPOLINE_SIZE);
CopyMemory(lpFunc, JmpCode, TRAMPOLINE_SIZE);
return TRUE;
}
Fonksiyonu kullanman için gereken bilgiler şunlar;
jmpcode bir global değişken, bunun içinde
push senin_hook_fonksiyonun
ret
komutlarının hex kodları var, senin_hook_fonksiyonun yerine kendi adresini yazıyosun kodda. bu değer ney dersen
char JmpCode[TRAMPOLINE_SIZE] = {0x68, 0x00, 0x00, 0x00, 0x00 ,0xC3};
İkincisi HOOK yapısı, ziraa parametrelerden birisi PHOOK, o da şöyle
typedef struct _HOOK{
LPVOID FuncPtr;
char OrgBytes[TRAMPOLINE_SIZE];
char JmpCode[TRAMPOLINE_SIZE];
} HOOK, *PHOOK;
#define TRAMPOLINE_SIZE 6
Bu bilgilerle bu fonksiyonu kendi kodunda çağırabilmen lazım.
2) Native Api hook biraz daha farklı, ntdll.dll içinde 15 baytlık kodlar vardır her fonksiyon için, bu kodlar sadece eax e uygun fonksiyon no koyar ve ordan sysenter ile kernel mode geçer. İşte biz bu 15 baytı çalıyoruz yerine kendi kodumuzu koyuyoruz, o kod ile hook fonksiyonuna atlıyor, iş bittikten sonra bu 15 baytı yine çalıştırıyoruz. bunu yapan fonksiyon;
BOOL SetNativeApiHook (LPCSTR szApiName, LPVOID lpCallStub, LPVOID lpHookFunc)
{
HMODULE hNtdll = NULL;
DWORD dwOldFlags = 0;
LPVOID lpFunc = NULL;
*((int*)(JmpCode + 1)) = (int)lpHookFunc;
hNtdll = GetModuleHandle("Ntdll.dll");
if (hNtdll == NULL)
{
return FALSE;
}
lpFunc = GetProcAddress(hNtdll, szApiName);
if (lpFunc == NULL)
{
return FALSE;
}
if (!VirtualProtect (lpCallStub,
NATIVE_CALL_STUB_SIZE,
PAGE_EXECUTE_READWRITE,
&dwOldFlags)) {
return FALSE;
}
if (!VirtualProtect (lpFunc,
NATIVE_CALL_STUB_SIZE,
PAGE_EXECUTE_READWRITE,
&dwOldFlags)) {
return FALSE;
}
CopyMemory(lpCallStub, lpFunc, NATIVE_CALL_STUB_SIZE);
CopyMemory(lpFunc, JmpCode, TRAMPOLINE_SIZE);
return TRUE;
}
Bu fonksiyonu çağırman için gereken yukardakiler dışında şu;
#define NATIVE_CALL_STUB_SIZE 16
char CallStubName[NATIVE_CALL_STUB_SIZE];
Artık bunu da kullanabilmen gerek. CallStubName her fonksiyon için ayrı olmalı, çünkü ntdll.dll den çaldığın baytlar burda, fonksiyon ismiyle ilişkili birşey yazman daha iyi.
3) SSDT Hook için yıllar önce Mark Russinovich'in filemon için yazdığı bir dizi hook makrosu vardır, ben dahil herkes bunları kullanır ancak mantığını anladıktan sonra kendin de yazarsın, bu konuyu uzun uzadıya açıklasam da büyük ihtimal kimse birşey anlamayacak, makroları veriyorum.
#define HOOK_SYSCALL(_Function, _Hook, _Orig ) \
_Orig = (PVOID) InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
#define UNHOOK_SYSCALL(_Function, _Hook, _Orig ) \
InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)]
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
HOOK_SYSCALL( ZwQueryDirectoryFile, NewZwQueryDirectoryFile, OldZwQueryDirectoryFile ); şeklinde çağıracaksın
4) Filesystem filter driver ın aslında api hook ile pek ilgisi yok ama güzel bir yöntem olduğu için kısaca değineyim. önce bir filter driver yazıp ntfs.sys nin üstüne yerleştireceksin, böylece bu driver a giden tüm IRP paketleri önce sana uğrayacak. Gelen IRP nin major fonksiyonu IRP_MJ_DIRECTORY_CONTROL ise, minor fonksiyonu da IRP_MN_QUERY_DIRECTORY ise, demekki bu driver a gelen IRP paketinin amacı belli bir dizindeki dosyaları listelemek.
Biz hemen bir callback fonksiyon set ediyoruz IoSetCompletionRoutine fonksiyonuyla, listeleme işlemi bitince kernel tarafından bizim fonksiyonumuz çağırılıyor. bu fonksiyonda IRP->UserBuffer da dosya ve dizin isimleri var , alıp user mode programımıza yollayabiliriz.
Kernel mod olanlar büyük ihtimal işine yaramaz ancak user mode olan 2 hook yöntemi çok sağlamdır. Ayrıca inline hook dışında bir de import table hook var, ben pek sevmiyorum ama aynı işlemleri onunla da yaparsın user mode da.