DLL注入技术之APC注入

DLL注入技术之APC注入

APC注入的原理是利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的,其具体流程如下:
1)当EXE里某个线程执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断。
2)当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。
3)利用QueueUserAPC()这个API可以在软中断时向线程的APC队列插入一个函数指针,如果我们插入的是Loadlibrary()执行函数的话,就能达到注入DLL的目的。

1.编写测试文件
新建MFC工程,添加按钮控件,双击写代码如下所示:

  1. void CMfcTextApcInjectDlg::OnBnClickedSleepex()
  2. {
  3. // TODO: 在此添加控件通知处理程序代码
  4. SleepEx(5000,TRUE);
  5. }

这里我们需要注意一下SleepEx中第二个参数为TRUE,查下msdn,上面写到:

bAlertable [in]
If this parameter is FALSE, the function does not return until the time-out period has elapsed. If an I/O completion callback occurs, the function does not return and the I/O completion function is not executed. If an APC is queued tothe thread, the function does not return and the APC function is not executed.

大概意思是说当第二个参数为FALSE,APC是不被执行的,从此可以认为APC注入的使用条件还是有很大约束的。

2.编写APC注入程序
由于我们需要时使用LoadLibrary()函数完成注入,因此需要为其先准备好必要的参数,需要我们可以通过在远程进程中申请空间的方式写入LoadLibrary()函数所需要的参数(也就是DLL的路径)。关键代码如下所示:

  1. //打开远程进程
  2. handle = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcessId);
  3. if (handle)
  4. {
  5. //在远程进程申请空间
  6. lpData = VirtualAllocEx(handle,
  7. NULL,
  8. 1024,
  9. MEM_COMMIT,
  10. PAGE_EXECUTE_READWRITE);
  11.  
  12. if (lpData)
  13. {
  14. //在远程进程申请空间中写入待注入DLL的路径
  15. bRet = WriteProcessMemory(handle,
  16. lpData,
  17. (LPVOID)sDllName,
  18. 1024,&dwRet);
  19. }
  20. //关闭句柄
  21. CloseHandle(handle);
  22. }

当我们准备好用于注入DLL的LoadLibrary()函数后,接下来需要使用QueueUserAPC()函数将此函数插入到软中断线程的APC队列中。但是由于QueueUserAPC()函数的第三个参数是线程ID,因此我们需要根据现有进程ID,并通过遍历对比得到线程ID,具体API如下表所示:

CreateToolhelp32Snapshot 创建线程快照
Thread32First 得到第一个线程快照
Thread32Next 循环下一个线程快照

关键代码如下所示:

  1. THREADENTRY32 te = {0};
  2. te.dwSize = sizeof(THREADENTRY32);
  3. //得到线程快照
  4. HANDLE handleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0);
  5. if (INVALID_HANDLE_VALUE == handleSnap)
  6. {
  7. return FALSE;
  8. }
  9.  
  10. BOOL bStat = FALSE;
  11. //得到第一个线程
  12. if (Thread32First(handleSnap,&te))
  13. {
  14. do
  15. {
  16. //进行进程ID对比
  17. if (te.th32OwnerProcessID == dwProcessId)
  18. {
  19. //得到线程句柄
  20. HANDLE handleThread = OpenThread(
  21. THREAD_ALL_ACCESS,
  22. FALSE,
  23. te.th32ThreadID);
  24.  
  25. if (handleThread)
  26. {
  27. //向线程插入APC
  28. dwRet = QueueUserAPC(
  29. (PAPCFUNC)LoadLibrary,
  30. handleThread,
  31. (ULONG_PTR)lpData);
  32. if (dwRet > 0)
  33. {
  34. bStat = TRUE;
  35. }
  36. //关闭句柄
  37. CloseHandle(handleThread);
  38. }
  39. }
  40. //循环下一个线程
  41. } while (Thread32Next(handleSnap,&te));
  42. }
  43. CloseHandle(handleSnap);

3.MFC工程设置和提升权限
经过以上两步的操作,我们已经准备好APC注入的关键代码,现在我们需要将自己的程序提升权限以方便注入操作(另,动态MFC库编译有可能造成注入失败)。主要代码如下:

  1. int CApcInjectDll::EnablePrivilege(bool isStart)
  2. {
  3. //1. 得到令牌句柄
  4. HANDLE hToken = NULL; //令牌句柄
  5. if (!::OpenProcessToken( GetCurrentProcess(),
  6. TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_READ,
  7. &hToken))
  8. {
  9. return FALSE;
  10. }
  11.  
  12. //2. 得到特权值
  13. LUID luid = {0}; //特权值
  14. if (!::LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
  15. {
  16. return FALSE;
  17. }
  18.  
  19. //3. 提升令牌句柄权限
  20. TOKEN_PRIVILEGES tp = {0}; //令牌新权限
  21. tp.PrivilegeCount = 1;
  22. tp.Privileges[0].Luid = luid;
  23. tp.Privileges[0].Attributes = isStart ? SE_PRIVILEGE_ENABLED : 0;
  24.  
  25. if (!::AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL))
  26. {
  27. return FALSE;
  28. }
  29.  
  30. //4. 关闭令牌句柄
  31. ::CloseHandle(hToken);
  32. return 0;
  33. }

4.测试注入效果
点击待注入的EXE进行SleepEx,这时EXE的窗口是不可以移动的,因为只有一个线程,处于SleepEx的挂起状态,然后进行注入,我们此时会发现处于挂起状态的进程窗口突然可以移动了,这是因为进程在挂起状态等待时,如果有APC队列就会退出等待并执行APC队列中的函数,然后程序继续运行。
APC注入因为受目标进程使用API的条件而受限,并且处于等待的线程被注入后会立即返回,也有可能造成线程的运行错误,所以应用起来不是很通用。
作者: xusir98
来源: 黑客反病毒 (http://bbs.hackav.com)

还没有评论,快来抢沙发!

发表评论

  • 😉
  • 😐
  • 😡
  • 😈
  • 😯
  • 😛
  • 😳
  • 😮
  • 😆
  • 💡
  • 😀
  • 👿
  • 😥
  • 😎
  • 😕