Windows Ring3层注入——“QueueUserAPC” APC注入(四)
APC概念
正常情况下,线程自己不主动调用ExitThread函数,或者一个进程内的其他线程不调用TerminateThread函数来终止线程的话,线程本身的行为是不会改变的。
那如果想改变一个线程的行为可以给他提供一个函数,让它自己去调用,这个函数就是APC(Asyncroneus Procedure Call),即异步过程调用。
APC(Asynchronous Procedure Call 异步过程调用)是一种与常用的和简单的同步对象不同的一种同步机制。它是一种软中断机制。
- APC有两种形式:
- 用户模式APC:由应用程序产生,APC函数地址位于用户空间,在用户空间执行。
- 内核模式APC:由系统产生,APC函数地址位于内核空间,在内核空间执行.
举一个例子理解一下APC的实际作用:假设一个线程在执行过程中,发出一个I/O请求,然后设备驱动执行线程传过来的I/O请求,但发出I/O请求的线程会继续执行下去,但是当线程走不下去的时候(必须得到I/O请求的的返回结果),线程需要获得返回结果才能继续进行,然后设备驱动执行完的I/O请求结果是通过插入APC队列来告诉线程的。但是设备驱动如何知道线程索要的结果怎么返还给他?因为在线程刚开始发送I/O请求时,线程就已经告诉设备驱动执行后的结果放到APC队列中就可以。
更多详细的APC解释可以去:https://blog.csdn.net/youyou519/article/details/82355009
APC重点解释
- 每个线程都会有一个APC队列
- 当一个线程从等待状态中苏醒(线程调用SlleepEx、SignalObjectAndWait、MsgWaitForMultiple、ObjectsEx、WaitForMultipleObjectsEx、WaitForSingleObjectEx函数时会进入可唤醒状态),进入"Alterable WaitStatus" 状态的时候,系统遍历该线程的APC队列,然后按照FIFO的顺序来执行APC。
- 在用户模式下,我们可以像创建远程线程一样,使用QueueUserAPC把APC过程添加到目标线程的APC队列中,等这个线程恢复执行时,就会执行我们插入的APC过程了。
APC调用回调函数的条件
- 1、首先必须是多线程的条件。
- 2、多线程的条件下执行了上述的需要调用的函数(SlleepEx、SignalObjectAndWait、MsgWaitForMultiple、ObjectsEx、WaitForMultipleObjectsEx、WaitForSingleObjectEx等)进入**“Alterable Wait Status”**状态。
APC注入函数原型及原理
QueueUserAPC函数原型:
DWORD WINAPI QueueUserAPC(
PAPCFUNC pfnAPC, //APC的函数地址
HANDLE hThread, //线程的句柄
ULONG_PTR dwData //APC函数的参数
);
因为QueueUserAPC是Windows提供的允许我们自己把回调函数插入APC队列,并且函数的hThread参数可以跨进程,也就是说可以是电脑的任何一个线程,所以这样可以使我们轻而易举在其他的进程的线程中插入一段我们的函数。
APC函数原型如下:
VOID CALLBACK APCProc(
ULONG_PTR dwParam
);//可以对比一下LoadLibrary函数原型
APC注入步骤
- 1、多线程程序执行上面所提到的等待函数之后,产生一个中断。
- 2、通过函数QueueUserAPC在队列中加入一个回调函数。
- 3、插入回调函数时,把回调函数地址改为LoadLibrary,我们使用VirtualAllocEx申请内存插入函数,并且写入内存。
- 4、当所被中断的程序被唤醒的时候,线程会优先执行APC队列中的回调函数。
注:为了增加调用机会,应向所有线程插入APC
APC注入代码示例
///APCInject.h
//以Suspend打开目标进程 与上一章函数相同
BOOL OpenTargetProcess(LPCWSTR ProcName , STARTUPINFO &st , PROCESS_INFORMATION &pi );
//在目标进程地址空间中分配待加载的DLL路径空间 与上一章函数相同
BOOL TagetAlloc(HANDLE hTargetProcess, LPVOID &lpAddr);
//把待加载的DLL路径写入目标进程空间 与上一章函数相同
BOOL WriteDLLToTarget(HANDLE hTargetProcess , LPVOID lpAddr , LPCWSTR lpBuffer);
///main函数
void main()
{
PROCESS_INFORMATION stProcessInfo; // 存储进程信息的PROCESS_INFORMATION 结构体
::memset(&stProcessInfo, 0 ,sizeof(stProcessInfo)); //分配结构体内存
STARTUPINFO stStartUpInfo; //进程的主窗体显示信息的STARTUPINFO结构体
::memset(&stStartUpInfo, 0 ,sizeof(stStartUpInfo)); //分配结构体内存
stStartUpInfo.cb = sizeof(stStartUpInfo);
LPCWSTR ProcName; //目标进程地址名
LPVOID lpAddr; //目标进程申请内存空间的指针
LPCWSTR lpBuffer; //要注入的DLL在磁盘中的地址
LPVOID pnfStartAddr ; //LoadLibrary地址
ProcName = L"D:\\Program Files (x86)\\Notepad++\\notepad++.exe";
if( OpenTargetProcess(ProcName , stStartUpInfo , stProcessInfo ) == FALSE )
{
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
HANDLE hTargetProcess= stProcessInfo.hProcess; //目标进程的句柄
if( TagetAlloc(hTargetProcess , lpAddr) == FALSE )
{
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
lpBuffer= TEXT("C:\\Users\\10178\\Desktop\\InjectDllFile.dll");
if(WriteDLLToTarget(hTargetProcess, lpAddr , lpBuffer)== FALSE )
{
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
HANDLE hTargetThread = stProcessInfo.hThread; //目标线程的句柄
//获取LoadLibrary地址
pnfStartAddr = (LPVOID)GetProcAddress(GetModuleHandle (TEXT ("Kernel32")) , "LoadLibraryW");
if(!QueueUserAPC((PAPCFUNC)pnfStartAddr , hTargetThread , (ULONG_PTR)lpAddr))
{
MessageBox(L"注入失败",L"提示",MB_OK);
return;
}
//恢复注入的目标线程挂起状态
ResumeThread(hTargetThread);
}
APC注入优缺点
优点:
比较隐蔽,代码简单,理解过程简单。如果能够加载驱动,就可以在驱动中向目标进程中的线程插入APC,并直接修改线程对象的某些域,使得该线程满足调用APC的条件了(这样做的成功率几乎可以达到100%)。
缺点:
实现的条件苛刻,能利用的机会并不多。