0x01 前言
没有参赛 看了下题目 感觉不太难 附加题比较有意思 蹭个热度 发个WP
先说结果:通过置位*MmTrackPtes
标志位,让内核调用MiInsertPteTracker
,把Length存储到*MiDeadPteTrackerSListHead
链表,遍历检测这个链表中的Length是不是等于0x2023然后蓝屏。并且同时要置位*MiTrackPtesAborted
为1,不然在置位*MmTrackPtes
后会蓝屏。
备注:带*的(WRK里面叫这个但是pdb里面是去除了这个符号名称的)
0x02 过程
题目:

题目分析
1.ida打开ring3的程序先看看做了啥。

2.接着打开ring0的程序看看他做了啥。

非常好基本上全部都被tvm0保护了。
3.综上考虑下出题人思路并验证。
驱动被全v了,但是导入表并没有被保护。出题人大概是想考察黑盒分析。因为这么短的时间考察去虚拟化,显然是不太符合要求的。
先来验证下常规思路,既然在驱动加载后,ring3程序一打开就蓝屏。题目说检测读功能,考虑句柄表。
so,验证下...
a.在ReadProccesMemory
打断点,并不蓝屏,所以在CreateProcessA
以后拿到了句柄以后,依然不会蓝屏,排除句柄表。
b.根据a很明显问题可以定位在ReadProcessMemory
,观察ring3的程序,很明显的地方就是这个0x2023了罗。
c.让我们来验证下b,把这个0x2023改掉试试。
d.非常好,根本不蓝。
e.思路很清晰,大概率检测的Length。目标预期: 查找有无全局变量存储了Length,并且大概率这个全局变量是个数组、链表等。
f.打开Ntoskrnl.i64 来到NtReadVirtualMemory
观察Length的去向。

走进了MmCopyVirtualMemory
,在这个函数的内部发现Length被存到了一个临时的MDL结构里面。

所以我们继续跟踪MemoryDescriptorList
的去向看看是谁获取了结构体中的ByteCount
或者是Size
g.


只有这几个函数MmProbeAndLockPages
MmMapLockedPagesSpecifyCache
MmUnlockPages
。我们一个一个来看看有什么奥秘所在。
g.1.MmProbeAndLockPages
先来看看函数内部

非常好拿了我们的ByteCount
,接着我们来看看MiProbeAndLockPrepare
函数内部

总的来说这个函数里面并没有直接存储ByteCount
这个玩意,而是跟别的加加减减再搞一块。所以先放一边。
g.2.MmMapLockedPagesSpecifyCache
函数内部发现好几个函数引用了MemoryDescriptorList
思路大概跟g.1一样不细说了,没有直接存储ByteCout
或者Size
的函数直接先放一边,到时候回过头去看。
g.2.1
但是在MiInsertPteTracker
我们发现了奥秘所在。

接下来我们观察v8的在哪里来的就完事了。并且根据WRK我们可以知道v8是个什么玩意了。


很明显就是PTE_TRACKED这个结构
`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
typedef struct _PTE_TRACKER {
LIST_ENTRY ListEntry;
PMDL Mdl;
PFN_NUMBER Count;
PVOID SystemVa;
PVOID StartVa;
ULONG Offset;
ULONG Length;
PFN_NUMBER Page;
struct {
ULONG IoMapping:
1
;
ULONG Matched:
1
;
ULONG CacheAttribute :
2
;
ULONG Spare :
28
;
};
PVOID CallingAddress;
PVOID CallersCaller;
} PTE_TRACKER,
*
PPTE_TRACKER;
|
`
与预想中的非常符合。ByteCount
被直接存储到PTE_TRACKER::Length
里面了。
g2.2
而且非常明显是从全局变量MiDeadPteTrackerSListHead
里面拿到的v8。

g2.3
既然上述代码逻辑完全符合我们的目标预期,我们来看看在什么情况下,MmMapLockedPagesSpecifyCache
内部会调用MiInsertPteTracker

噔噔噔~,发现在dword_140CFB17C(MmTrackPtes)
为1的情况下会MiInsertPteTracker
g2.4
我们来双机看看这个标志位


很不幸,系统默认情况下,这个标志位为0。所以在默认情况下,压根不走这里。
那意思就是赛题的驱动把这个标志位改了?我们来验证一波。
在这里下个写入断点,然后加载赛题的驱动。
先ba w1 fffff8020617318c
(这是标志位地址,在上图)然后拉起驱动。

芜湖~,断在NotepadProtect.sys里面
至此分析完毕,其他的还分析个锤子。(其实还有)
0x03 完毕
1.综合0x02整个分析过程,绕过检测的思路已经非常简单了。给dword_140CFB17C(MmTrackPtes)
标志位置0,长度就不会再往全局数组里面插入了。
2.模拟他的检测思路就是,给dword_140CFB17C(MmTrackPtes)
标志位置1,然后遍历*MiDeadPteTrackerSListHead
中的长度是不是等于0x2023。
很不幸的是,在我们手动置位dword_140CFB17C(MmTrackPtes)
以后立马蓝屏了

我们来分析下为什么他不蓝屏,我们会蓝屏,先看看调用栈。

Ok,在函数里面主动蓝屏的,我们在WRK定位到相应位置看看

看来我们还需要给MiTrackPtesAborted
置1。
3.思路很清晰。
结论:通过置位*MmTrackPtes
标志位,让内核调用MiInsertPteTracker
,把Length存储到*MiDeadPteTrackerSListHead
链表,遍历检测这个链表中的Length是不是等于0x2023然后蓝屏。并且同时要置位*MiTrackPtesAborted
为1,不然在置位*MmTrackPtes
后会蓝屏。
0x04
就是大概看了一眼,代码没写。代码没什么技术含量,就是特征定位几个未导出的全局变量,然后遍历自己照着wrk写一遍就行。
0x05
总的来说,在加了tvm的情况下,只要思路清晰,就是钻一,还是能比较快速的解完这道题的。而且耗费的时间也并不长。
0x06
什么?你跟我说看完上面还是不会做?那把tvm去虚拟化了你总会做了吧?
附件是去虚拟化以后的NotepadProtect.sys
的ida分析文件。
(附件被吞了 看评论区~)
特别鸣谢
1.https://github1s.com/zhuhuibeishadiao/ntoskrnl (WRK)
2.不愿公开姓名的xxx提供的去虚拟化sys。