DLL注入技术
作者:互联网
文章目录
1.显式载入|卸载DLL模块
在任何时候,进程中的一个线程可以调用下面两个函数来将一个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
);
2.得到DLL符号地址
FARPROC GetProcAddress(
HMODULE hInstDll,
PCSTR pszSymbolName
);
3.Dll入口点函数
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;
}
4.DLL注入
4.1.使用注册表注入DLL
首先查找注册表的下面表项:
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的进程中。
4.2.使用windows挂钩来注入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注入到其他进程的目的。
4.3.使用远程线程注入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;
}
4.4.QueueUserApc/NtQueueAPCThread APC注入法
当一个线程从等待状态中苏醒时(线程调用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的条件了。
4.5.SetThreadContext法
在注入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;
}
4.6.输入表项DLL替换法(DLL劫持法)
注册表有一个叫KnownDLLs的设置项
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
这里面存在的库是不能替换的,因为加载的时候首先会查找该目录,如果该目录没有,可以放在exe根目录下面进行替换。
标签:FALSE,技术,DLL,线程,APC,return,NULL,注入 来源: https://blog.csdn.net/qq_43647628/article/details/117632402