问题1.插入用户APC成功后无法得到执行
问题2.如何在64位系统中向32位程序插入APC
问题3.如何获取远程CALL的返回值
问题4.如何释放给对方进程申请的内存
1.接下来我们来看看插入用户APC成功后无法得到执行的问题
原因是在执行用户APC的时候有判断UserApcPending是否为1,但是在插入的时候因条件未满足而没有设置UserApcPending=1
下面的截图来自于KiDeliverApc函数
![](upload/attach/202406/891053_Q3FHD838FY6VAXQ.webp)
设置UserApcPending=1的条件是KernelRoutine == PsExitSpecialApc (很明显我们是不能满足这个条件的)
下面的代码截图来自于KiInsertQueueApc函数
![](upload/attach/202406/891053_D9W6SSP59UMGTYA.webp)
设置UserApcPending=1的第二个条件是Thread->State==5,Thread->WaitMode==1,MiscFlags==Alertable
这里就是在判断线程为等待状态并且可唤醒的时候把UserApcPending=1 (很明显我们是不能满足这个条件的)
下面的代码截图来自于KiInsertQueueApc函数
![](upload/attach/202406/891053_DH9WUNP58TPACVZ.webp)
解决方法在APC插入完毕之后 自己设置 UserApcPending = 1
![](upload/attach/202406/891053_27YRK2UDW3SQKUE.webp)
2.如何在64位系统中向32位程序插入APC
当插入到32位进程的时候会崩溃,原因是在Wow64.dll中的whNtQueueApcThread函数里面对ApcRoutine做了转换
![](upload/attach/202406/891053_AAGA2UGDPRXS5FS.webp)
解决方法,进行挂靠调用PsWrapApcWow64Thread函数即可
![](upload/attach/202406/891053_TJJJPHQ55N97QXK.webp)
问题3.如何获取远程CALL的返回值
问题4.如何释放给对方进程申请的内存
我的解决方法就是让Shellcode自己写入到自己的末尾8个字节中,其中4个字节是状态,4个字节是返回值
通过创建一个线程一直读取状态来判断shellcode是否执行完毕,如果执行完毕就证明返回值也写入成功了
这个时候就可以调用VirtualFree进行释放内存 (如果没有看懂Shellcode的写法可以去看看动态重定位Call Next)
![](upload/attach/202406/891053_QCQ4J5SDYFTUGSU.webp)
下面是微软的官方文档对QueueUserAPC函数进行解释
微软告诉我们,他不建议把APC插入到其他进程当中,在64位进程插入到32位进程的时候会崩溃
插入用户APC之后并不会立刻执行,如果要立刻执行,必须是可警告状态,线程处于挂起状态,线程处于等待状态
调用SleepEx,WaitForSingleObjectEx这种函数的时候
微软还告诉我们在ReadFileEx这种支持异步操作的函数内部使用了APC
![](upload/attach/202406/SQ39ZR4URWYVQF5.jpg)
下面是用户APC远程CALL的驱动部分关键代码
NTSTATUS UserAPCCallFun(PUSER_APC_DATA Info)
{
NTSTATUS ntStatus = STATUS_SUCCESS;
PETHREAD pThread = NULL;
PEPROCESS pProcess = NULL;
ntStatus = PsLookupProcessByProcessId((HANDLE)Info->inProcessID,&pProcess); //通过ID获取对象
if (!NT_SUCCESS(ntStatus)){
goto UserAPCCallFunEixt;
}
if (PsGetProcessExitStatus(pProcess) != STATUS_PENDING){ //判断进程是不是存在
ntStatus = STATUS_UNSUCCESSFUL;
goto UserAPCCallFunEixt;
}
ntStatus = PsLookupThreadByThreadId((HANDLE)Info->inThreadID,&pThread); //通过ID获取对象
if (!NT_SUCCESS(ntStatus)) {
goto UserAPCCallFunEixt;
}
PVOID pBaseAddress = NULL;
size_t inShellCodeSize = Info->inShellCodeSize;
char uShellCode1[256] = { 0 };
RtlCopyMemory(uShellCode1,Info->inShellCode,inShellCodeSize); //中转内存
//**************************************************************************************************************
KAPC_STATE kApcState = { 0 };
KeStackAttachProcess(pProcess,&kApcState); //挂靠
ULONG64 pBase = NULL;
size_t size1 = 0x1000;
ntStatus = ZwAllocateVirtualMemory(-1,&pBase,NULL,&size1,MEM_COMMIT,PAGE_EXECUTE_READWRITE); //申请内存
if (!NT_SUCCESS(ntStatus))
{
KeUnstackDetachProcess(&kApcState); //解除挂靠
goto UserAPCCallFunEixt;
}
RtlCopyMemory(pBase,uShellCode1,inShellCodeSize); //拷贝内存
pBaseAddress = pBase;
PsWrapApcWow64Thread(0,&pBase); //兼容32位程序
KeUnstackDetachProcess(&kApcState); //解除挂靠
//**************************************************************************************************************
PKAPC pApc = ExAllocatePool(NonPagedPool,sizeof(KAPC));
KeInitializeApc(pApc,pThread,OriginalApcEnvironment,KrlRoutine,NULL,pBase,UserMode,NULL); //初始化APC
KeInsertQueueApc(pApc,NULL,NULL,NULL); //插入APC
//让用户APC执行的快一些
*(PUCHAR)((PUCHAR)pThread + UserApcPending) = 1;
*(DWORD64*)&Info->retShellAddress = pBaseAddress; //返回申请的内存地址 让R3进行释放
UserAPCCallFunEixt:
if(pThread != NULL) ObDereferenceObject(pThread);
if(pProcess != NULL) ObDereferenceObject(pProcess);
return ntStatus;
}
代码的MFCApplication是MFC工程,MyDriver1是驱动工程,我的测试环境是在WIn7 SP1 64位,使用x64 debug模式进行编译,下面的驱动路径需要自己更改,MessAgeBox的地址需要自己更改
![](upload/attach/202406/891053_VQH2SVUET8WVE38.webp)
![](upload/attach/202406/891053_44S6VGS7JR3UZ2T.webp)
![](upload/attach/202406/891053_FRA29QBRR69C9ME.webp)
[课程]FART 脱壳王!加量不加价!FART作者讲授!
最后于 2024-6-12 15:57
被旺仔_小可爱编辑
,原因: