在任何时候,进程中的一个线程可以调用下面两个函数来将一个DLL映射到进程的地址空间中:
HMODULE LoadLibrary(PCTSTR pszDLLPathName); HMODULE LoadLibraryEx( PCTSTR pszDLLPathName, HANDLE hFile, //默认NULL DWORD dwFlags //默认0 );
卸载函数
BOOL FreeLibrary(HMODULE hInstDll); VOID FreeLibraryAndExitThraed( HMODULE hInstDll, DWORD dwExitCode );
FARPROC GetProcAddress( HMODULE hInstDll, PCSTR pszSymbolName );
BOOL WINAPI DllMain(HINSTANCE hInstDll,DWORD fdwReason,PVOID fImpLoad){ switch(fdwReason){ //第一次将DLL映射到进程地址空间中调用 case DLL_PROCESS_ATTACH: break; //只有dll已经映射到进程地址空间后,有新线程才会调用这个 case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; //将DLL从进程地址空间释放时调用 case DLL_PROCESS_DETACH: break; } return TRUE; }
首先查找注册表的下面表项:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\
该表项中包含AppInit_DLLs和LoadAppInit_DLLs两个键
我们要做的就是将DLL路径写入AppInit_DLLs中并且将LoadAppInit_DLLs置为1
如果想注入多个DLL,注意将DLL放到系统路径,AppInit_DLLs中每一个DLL用空格或者逗号隔开,且第二个DLL开始就不能写路径了。
原理:User32.dll被映射到一个新进程时,会受到DLL_PROCESS_ATTACH通知,当User32.dll对其进行处理的时候,会取得上述注册表的值并调用LoadLibrary载入DLL。
优点:最方便
缺点:
(1)DLL只会被映射到那些使用了User32.dll的进程中
(2)DLL会被映射到所有使用了User32.dll的进程中。
首先了解一下挂钩函数
HHOOK hHook = SetwindowHookEx( int idHook, //安装的挂钩类型 HOOKPROC, //一个函数的地址 HINSTANCE hMod, //一个DLL,这个DLL包含了函数 DWORD dwThreadId //哪个线程安装hook,为0代表所有线程 );
原理:如果最后一个参数设置为0,那么安装的就是全局消息钩子,这时要求HOOKPROC必须在DLL中,并且指定第3个参数hMod。这样,系统在其他进程中调用HOOKPROC时,如果发现目标DLL尚未加载,就会使用KeUserModeCallback函数回调User32.dll的__ClientLoadLibrary()函数,由User32.dll把这个DLL加载到目标进程中,从而实现将DLL注入到其他进程的目的。
远程线程API
HANDLE CreateRemoteThread( HANDLE hProcess, //用来表示新创建的线程所属进程 LPSECURITY_ATTRIBUTES 1pThreadAttributes, //线程安全属性,NULL SIZE_T dwStacksize, //新线程的堆栈空间大小 LPTHREAD_START_ROUTINE lpStartAddress //新线程的函数地址 LPVOID lpParameter, //传递给新线程的数据 DWORD dwCreationFlags, //创建线程的方法 LPDWORD lpThreadId //用来接收新线程的ID,若为NULL,不返回线程ID );
两个问题:
(1)LoadLibrary不能直接放到CreateRemoteThread中去,因为对方进程不知道基址,应该先得到对方进程所在内存LoadLibrary的机制
(2)动态库字符串不能直接传递,因为对方内存地址没有该字符串,应该先把字符串传递到对方进程中。
BOOL WINAPI InjectDLLToProcess(DWORD dwTargetPid, LPCSTR DLLPath){ HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid); if (hProc == NULL) { printf("OpenProcess Faild\n"); return FALSE; } //使用VirtualAllocEx函数在远程进程内存地址分配DLL文件名缓冲区 LPTSTR psLibFileRemote = (LPTSTR)VirtualAllocEx(hProc, NULL, lstrlen((LPTSTR)DLLPath) + 1, MEM_COMMIT, PAGE_READWRITE); if (psLibFileRemote == NULL) { printf("VirtualAllocEx Faild\n"); return FALSE; } //使用WriteProcessMemory函数将DLL路径名复制到远程的内存空间中 if (WriteProcessMemory(hProc, psLibFileRemote, (LPCVOID)DLLPath, lstrlen((LPTSTR)DLLPath) + 1, NULL) == 0) { printf("WriteMemory Failed\n"); return FALSE; } //获得远程进程的LoadLibrary地址 PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA"); HANDLE hThread = CreateRemoteThread(hProc, NULL, 0, pfnThreadRtn,psLibFileRemote, 0, NULL); if (hThread == NULL) { printf("CreateRemoteThread Failed\n"); return FALSE; } printf("Inject Successfull\n"); return TRUE; }
当一个线程从等待状态中苏醒时(线程调用SleepEx,SingalObjectAndWait,MsgWaitForMultiple等等)他会检测有没有APC交付给自己。如果有,它就会执行这些APC过程。在用户层,我们可以像创建远程线程一样,使用QueueUserAPC把APC过程添加到目标线程的APC队列中,等这个线程恢复执行时,就会执行我们插入的APC过程了。
DWORD WINAPI QueueUserAPC( PAPCFUNC pfnAPC, //APC函数的地址 HANDLE hThread, ULONG_PTR dwData //APC函数的地址 );
在添加用户模式APC后线程不会直接调用APC函数,所以为了增加调用机会,应向所有线程插入APC。
BOOL WINAPI InjectDLLToProcessAPC(DWORD dwTargetPid, LPCSTR DLLPath) { HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid); if (hProc == NULL) { printf("OpenProcess Faild\n"); return FALSE; } //使用VirtualAllocEx函数在远程进程内存地址分配DLL文件名缓冲区 LPTSTR psLibFileRemote = (LPTSTR)VirtualAllocEx(hProc, NULL, lstrlen((LPTSTR)DLLPath) + 1, MEM_COMMIT, PAGE_READWRITE); if (psLibFileRemote == NULL) { printf("VirtualAllocEx Faild\n"); return FALSE; } //使用WriteProcessMemory函数将DLL路径名复制到远程的内存空间中 if (WriteProcessMemory(hProc, psLibFileRemote, (LPCVOID)DLLPath, lstrlen((LPTSTR)DLLPath) + 1, NULL) == 0) { printf("WriteMemory Failed\n"); return FALSE; } CloseHandle(hProc); BOOL status = FALSE; //定义线程信息结构 THREADENTRY32 te32 = { sizeof(te32) }; //创建系统当前线程快照 HANDLE hThreadShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hThreadShot == INVALID_HANDLE_VALUE) return FALSE; //循环枚举线程信息 if (Thread32First(hThreadShot, &te32)) { do { //判断是不是目标进程的子进程 if (te32.th32OwnerProcessID == dwTargetPid) { HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID); if (hThread){ //向指定线程添加APC DWORD dwRet = QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)psLibFileRemote); if (dwRet > 0) status = TRUE; CloseHandle(hThread); } } } while (Thread32Next(hThreadShot, &te32)); } CloseHandle(hThreadShot); return status; }
在实际使用中,由于条件苛刻,能成功利用的机会并不多。但是,如果能够加载驱动,就可以在驱动中向目标进程插入APC,并直接修改线程对象的某些域,使得该线程满足调用APC的条件了。
在注入DLL时,可以将目标进程中的线程暂停,然后向其写入ShellCode,把线程的context的eip设置为shellcode的地址,这样线程恢复执行时就会先执行我们的shellcode了。在ShellCode中加载目标DLL,然后调回原来的eip执行。
typedef struct INJECT_DATA { BYTE ShellCode[0x30]; //0x00 ULONG_PTR AddrofLoadLibraryA; //0x30 PBYTE lpDLLPath; //0x34 ULONG_PTR OriginalEIP; //0x38 char szDllPath[MAX_PATH]; //0x3C }; __declspec(naked) VOID ShellCodeFun(VOID) { __asm { push eax pushad popfd call L001 L001: pop ebx sub ebx,8 push dword ptr ds:[ebx+0x34] call dword ptr ds:[ebx+0x30] mov eax,dword ptr ds:[eax+0x38] xchg eax,[esp+0x24] popfd popad retn } } BOOL WINAPI InjectDLLToProcessSTC(DWORD dwTargetPid, LPCSTR DLLPath) { DWORD dwTidList[1024] = { 0 }; BOOL status = FALSE; int index = 0; //首先获取进程中的线程ID //定义线程信息结构 THREADENTRY32 te32 = { sizeof(te32) }; //创建系统当前线程快照 HANDLE hThreadShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hThreadShot == INVALID_HANDLE_VALUE) return FALSE; //循环枚举线程信息 if (Thread32First(hThreadShot, &te32)) { do { //判断是不是目标进程的子进程 if (te32.th32OwnerProcessID == dwTargetPid) { status = TRUE; dwTidList[index++] = te32.th32ThreadID; } } while (Thread32Next(hThreadShot, &te32)); } CloseHandle(hThreadShot); if (!status) { printf("该进程无线程"); return status; } //打开进程和线程,暂停线程 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid); HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwTidList[0]); if (hThread == NULL) { printf("打开线程失败"); return FALSE; } DWORD swSuppendCnt = SuspendThread(hThread); //获得线程的CONTEXT CONTEXT context; ULONG_PTR uEIP = 0; ZeroMemory(&context, sizeof(CONTEXT)); context.ContextFlags = CONTEXT_FULL; GetThreadContext(hThread, &context); uEIP = context.Eip; //申请内存准备写入ShellCode PBYTE lpData = (PBYTE)VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE); INJECT_DATA data; PBYTE pShellCode = (PBYTE)ShellCodeFun; if (pShellCode[0] == 0xE9) { pShellCode = pShellCode + *(ULONG*)(pShellCode + 1) + 5; } memcpy(data.ShellCode, pShellCode, 0x30); lstrcpyA(data.szDllPath,DLLPath); PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA"); data.AddrofLoadLibraryA = (ULONG_PTR)pfnThreadRtn; data.OriginalEIP = uEIP; data.lpDLLPath = lpData + FIELD_OFFSET(INJECT_DATA,szDllPath); printf("Shellcode填充完毕"); if (!WriteProcessMemory(hProcess, lpData, &data, sizeof(INJECT_DATA), NULL)) { printf("写入失败!"); return FALSE; } context.Eip = (ULONG)lpData; SetThreadContext(hThread, &context); ResumeThread(hThread); CloseHandle(hProcess); CloseHandle(hThread); printf("注入动态库成功!"); return TRUE; }
注册表有一个叫KnownDLLs的设置项
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
这里面存在的库是不能替换的,因为加载的时候首先会查找该目录,如果该目录没有,可以放在exe根目录下面进行替换。