1.0
本贴只针对win7 x64系统下的 32位D3D9程序,局限性太大,仅作为抛砖引玉 。
下面进入正题。
基本流程如下:
1:在内核中 hook相关shadowSSDT或SSDT函数
2:在目标进程申请内存,写入用于D3D9绘制的shellcode
3:利用APC的力量回到 用户模式,执行用于D3D9绘制的
shellcode。
1.1
首先,如果我们要做一个hook D3D9的绘制。
自然是要编写dll,
注入到目标进程,
hook D3D9虚函数,
获取到D3D9设备,
执行绘制代码。
时至今日,这种技术已经是基本操作了。
经历各大游戏厂商(代理商)以及外挂作者们之间的斗争,
这种注入以及hook的方式已然不可取。
那么是否有一种相对隐蔽的方式来完成这项工作呢?
答案是有的。(废话,没有你写这贴干嘛【滑稽】)
从我们能想到的根本问题入手的话......
在应用层
hook D3D9虚函数以及直接注入dll会拉闸,
既然如此,我们在内核层做hook,代码全都以shellcode的形式
在目标进程里跑起来。
1.2
在win7 x64下,
通过调试一个网上下载的D3D9示例Demo
仔细跟踪其 D3D9的Present函数,我们发现
Present
最终会调用
USER32.HungWindowFromGhostWindow+20处的代码
反汇编如下:
call fs:[000000C0]其实是call Wow64子系统的调用
经过Wow64子系统的包装,最终进入内核。
mov eax, 000012CF这句汇编代码
其中 000012CF代表的是 shadowSSDT 的索引值
了解过 SSDT&shadowsSSDT的朋友都知道
第一个4k页面指向的是SSDT函数
第二个4k页面指向的是shadowSSDT函数
另外两个页面未使用(这是题外话。)
00000012CF在第二个页面上,所以这个索引指向shadowSSDT函数。
我们减去 0x1000 就是它的真实索引值 0x02CF,十进制就是 719。
我们打开PChunter这款非常有用的软件, 我们可以看到索引719指向的
shadowSSDT函数名 NtUserHwndQueryRedirectionInfo
接下来我们就hook这个函数并写好过滤
一般来说,这种shadowSSDT函数,只传进来一个参数(rcx), 保险起见我们写四个。
示例过滤函数如下:(自备shadowSSDT hook引擎,本贴不会放完整代码,防止伸手)
But, 光是这样是不够的, 我们还要在这里获取到D3D9的设备。
这就给我们出了一个难题,一般在应用层都是hook取设备的。
经过查资料,我们想到了一个好办法,利用栈回溯,找到最初调用
Present函数的地方, 并把第一个参数(也就是设备)取出来。
好在windows x86下的栈回溯是基于EBP来回溯的,原理相对简单,
我们可以手动编写一下。
1.3
应用层在通过syscall进入内核之前,会把当前的TrapFrame保存
起来。在内核中当前_KTHREAD成员
TrapFrame 就是应用层保存的
TrapFrame 指针。
在win7 x64下,其结构偏移为 0x1D8。
至此,我们可以顺利的获取应用层最后保存的RBP了。
接下来写栈回溯,具体原理我就不多做描述,大家可以移步这两个帖子看一下
https://blog.csdn.net/chenlycly/article/details/78144769
https://blog.csdn.net/wu330/article/details/29213201
示例代码如下:
笔者这里偷了个懒,直接判断call 地址是否小于 0x00500000
以此确定 当前帧就是call present的。
当然,获取到设备的同时,得以确认,当前线程就是从
Present函数一直call 进内核的。下面就可以放心的执行绘制操作了。
2.0
在内核中给当前进程申请内存,最方便的莫过于ZwAllocateVirtualMemory
申请内存成功之后,写入shell code。
比如直接call D3D9的 Clear函数,画个方块。
示例如下:
要注意的是,不要一直不停的申请内存,我想各位看官应该不会这么做[滑稽]。
3.0
做完如上工作,接下来就可以想办法回到用户模式,执行shellcode了。
回到用户模式的方法有好几种,比如KeUserModeCallBack, APC。
其中
KeUserModeCallBack 需要在应用层中写好代理分发,并且如果目标进程是
wow64进程,还需要多写一层代理函数,着实麻烦。
所以我们采用相对简单的 APC,利用APC机制回到用户模式。
代码如下:
注意这里的细节,如果目标进程是 wow64进程,起始地址需要 / -4。
然后就是常规的初始化APC,插入队列。
由于我们是回到当前进程的应用层,所以初始化APC时,
KeInitializeApc第二个参数填 PsGetCurrentThread()。
最后利用KeTestAlertThread直接触发队列里的APC Routine。
3.1
最后放上效果图如下:
结尾:
经过笔者测试,有些系统并不走 NtUserHwndQueryRedirectionInfo
而是另外的ShadowSSDT函数,不过我们依然有办法解决这种小问题。
有时间开下一帖讲一下
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。