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函数,不过我们依然有办法解决这种小问题。
有时间开下一帖讲一下。
- End -
原文作者:万剑归宗
原文链接:https://bbs.pediy.com/thread-247671.htm
转载请注明:转自看雪学院
更多阅读:
1、[原创]Flare-on5 12题 suspicious_floppy writeup
2、[原创] ratel,让xposed模块在免root的环境下跑起来
3、[原创]反编译原理(2)-中间表示
4、[原创]代码混淆之我见(一)