首页
社区
课程
招聘
[原创] 用户APC远程CALL 解决不执行,无法插入32位进程,无法释放内存,无法获取返回值问题
发表于: 2024-6-12 15:08 3362

[原创] 用户APC远程CALL 解决不执行,无法插入32位进程,无法释放内存,无法获取返回值问题

2024-6-12 15:08
3362

问题1.插入用户APC成功后无法得到执行

问题2.如何在64位系统中向32位程序插入APC

问题3.如何获取远程CALL的返回值

问题4.如何释放给对方进程申请的内存


1.接下来我们来看看插入用户APC成功后无法得到执行的问题

原因是在执行用户APC的时候有判断UserApcPending是否为1,但是在插入的时候因条件未满足而没有设置UserApcPending=1

下面的截图来自于KiDeliverApc函数

设置UserApcPending=1的条件是KernelRoutine == PsExitSpecialApc (很明显我们是不能满足这个条件的)

下面的代码截图来自于KiInsertQueueApc函数

设置UserApcPending=1的第二个条件是Thread->State==5,Thread->WaitMode==1,MiscFlags==Alertable

这里就是在判断线程为等待状态并且可唤醒的时候把UserApcPending=1 (很明显我们是不能满足这个条件的)

下面的代码截图来自于KiInsertQueueApc函数

解决方法在APC插入完毕之后 自己设置 UserApcPending = 1

2.如何在64位系统中向32位程序插入APC

当插入到32位进程的时候会崩溃,原因是在Wow64.dll中的whNtQueueApcThread函数里面对ApcRoutine做了转换

解决方法,进行挂靠调用PsWrapApcWow64Thread函数即可

问题3.如何获取远程CALL的返回值

问题4.如何释放给对方进程申请的内存

我的解决方法就是让Shellcode自己写入到自己的末尾8个字节中,其中4个字节是状态,4个字节是返回值

通过创建一个线程一直读取状态来判断shellcode是否执行完毕,如果执行完毕就证明返回值也写入成功了

这个时候就可以调用VirtualFree进行释放内存 (如果没有看懂Shellcode的写法可以去看看动态重定位Call Next)

下面是微软的官方文档对QueueUserAPC函数进行解释 

微软告诉我们,他不建议把APC插入到其他进程当中,在64位进程插入到32位进程的时候会崩溃

插入用户APC之后并不会立刻执行,如果要立刻执行,必须是可警告状态,线程处于挂起状态,线程处于等待状态

调用SleepEx,WaitForSingleObjectEx这种函数的时候

微软还告诉我们在ReadFileEx这种支持异步操作的函数内部使用了APC

下面是用户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的地址需要自己更改














[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2024-6-12 15:57 被旺仔_小可爱编辑 ,原因:
上传的附件:
收藏
免费 1
支持
分享
最新回复 (3)
雪    币: 105
活跃值: (635)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
如果频繁调用call,会导致目标进程未响应,这个怎么解决?
2024-6-14 14:41
0
雪    币: 1819
活跃值: (2733)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
小豆丁CE 如果频繁调用call,会导致目标进程未响应,这个怎么解决?
非必要,不要插在UI线程
2024-6-15 09:57
0
雪    币: 729
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
win10好像不能这么干
    //让用户APC执行的快一些
    *(PUCHAR)((PUCHAR)pThread + UserApcPending) = 1;
3天前
0
游客
登录 | 注册 方可回帖
返回
//