最近打算把某鹅安全竞赛做一遍,来练练手。做到20年的时候发现20年的题解不是很多,仅有的几个写的也不是很详细,于是就打算来写一个详细点(新手向)的,供和我一样刚入门的朋友们参考。其实对于vmp壳我还有许多的知识待学习,中间写的有些错误与问题,还请大家多多赐教。
题目在这里可以下到,本文为初赛ring0题的题解,因为ring3那道题实在是没啥难度。题目如下:
驱动文件拿过来先丢到IDA里看看,很明显是一个vmp的壳,令人十分头大。说实话到这一步我就已经去搜其他人的题解了,因为我之前也没怎么涉猎过有关vmp的知识,只知道这是个强壳。但是大概瞟了一眼前面的方法似乎也没有很复杂,直接开冲。
网络上有关驱动vmp加壳分析的博客不是很多,博客<某驱动脱壳 vmp>对于分析加了vmp壳的驱动程序所用的思路与脱一些常规壳差不多,还是去手工跳过前面的解密阶段,去找程序的找OEP,然后就可以去分析没被vmp保护的代码。我去试了试,并没成功(我还是太菜了)。
这是DriverDemo.sys
的导入表,很明显MmGetSystemRoutineAddress
这个函数是多出来的,似乎有些函数调用vmp壳并不会将其隐藏起来,那在这个函数下断岂不是就可以断到解密完的地方,然后把内存dump下来再继续分析。
上双机调试,加载驱动,断到MmGetSystemRoutineAddress
函数处,跳出,来到一段看起来像是正常代码的地方。
MmGetSystemRoutineAddress
是内核里根据函数名获取函数地址的一个常用函数,可以看到获取到的函数地址保存在fffff8045ca94010
处,然后后面紧接着调用了这个地址。
跟进去发现fffff8045ca94010
处存的是KdDisableDebugger
的地址,这个函数是一个禁止内核调试的一个函数,常用于内核反调试,到这里差不多可以确信现在执行的代码差不多是解密后的关键代码。用Windbg把这个驱动现在的内存dump下来,拖到IDA里面看看。
博客<利用IDA的F5来反VMP2.x的Mutation保护>建议,分析混淆后的程序最好把IDA的这个选项勾掉,以免乱创建函数,影响graph和F5,去变异代码那手动创建函数会好很多,我们这里照做。
进去后查看字符串窗口,可以看到一些解密后的字符串信息。
随便点一个跟过去,可以发现更多的有用的字符串。
比如这个应该是一个设备的名字,一路查看引用向上回溯。
最终类似于博客<某驱动脱壳 vmp>,来到了自动识别出来的DriverEntry
处。(后期补充:用hzqst大表哥的这个工具也可以达到类似的效果)
在此新建个函数,F5,简直完美,似乎我们的分析到这里感觉就差不多了。
但是当我们随便在反编译的窗口随便点进一个函数,然后再出来,就发现DriverEntry
变成了这个样子,这是发生甚么事了。
反汇编窗口来到被截断的地方,发现这里莫名其妙多了一条分割线,把DriverEntry
给切断了。
研究了一会,发现是在sub_FFFFF8045CD26B60()
的最后,出现了一个JUMPOUT,应该是这个东西把自身以及外面的DriverEntry
给都截断了。
IDA反汇编加上动态调试一下,就可以知道这个sub_FFFFF8045CD2E587
实际上是一个系统调用,里面左跳右跳最终会去执行RtlInitUnicodeString
函数,有关vmp里面的系统调用可以参照博文<手动分析VMP加密的x64驱动导入表>。返回地址是int 3
下面的那条指令,而正是这个int 3
造成了截断反编译结果的JUMPOUT。所以这里我们把这个int 3
改成nop
,然后把所有的函数删掉再重新分析。
可以看到反编译的结果就比较正常了,在后面还会有几次这样类似的截断,同样的方式处理就可以,剩下的工作就是把所有的系统函数名字、对应的变量类型都给标上。
标完以后,其实后面就没什么难度了。回到题目,首先是让驱动正常加载,这里重点来看sub_FFFFF8045CD26A00()
和sub_FFFFF8045CD26B60()
这两个函数。
sub_FFFFF8045CD26A00()
主要是反双机调试,分别设置kdDebuggerEnabled
和KdDebuggerNotPresent
,并调用KdDisableDebugger
来实现反调试。效果就是当我们调试这个驱动时,F5后想再断下就断不下了。这里我们只需要重新将kdDebuggerEnabled
置为1(执行ed kdDebuggerEnabled 1
),然后跳过运行KdDisableDebugger
即可。
这里有一个问题,在我的虚拟机里运行时,不知道为什么设置KdDebuggerNotPresent
为1没有效果,就算我手动修改也不行。
来到sub_FFFFF8045CD26B60()
函数,在DriverEntry
里,如果这个函数返回0,则驱动就会退出,所以让驱动正常加载的关键部分就是这个函数。这个函数主要逻辑就是判断某一个注册表项是否为1,如果不为1就返回0,让驱动退出。这里对于注册表操作的代码可以参照《Windows内核安全》的4.2小节,几乎是一样的。所以只需要在对应的注册表处添加一个键值为1的注册表项就可以让驱动正常运行。
这里还有一个小问题值得说一下,当把API标完以后,会发现这几个函数的参数十分奇怪,怎么也对不上。
研究了一下,是这个压栈的操作影响了IDA的参数识别,因为64位是通过寄存器和堆栈一块传参的。而这个push rdx
应该不是传参,大概是和vmp的系统函数调用有关,所以删掉了不影响分析。
这样就正常了。
如何使其输出hello world也就十分清晰了,就是设置一下同步事件就可以了。这部分的代码与《Windows内核安全》的4.4小节也十分类似,大家可以对照学习。具体的代码如下。
回过头来看其实难度也不是很大,只要相关知识都掌握了,得出题解也是一眼的事。像我一样刚入门或还未入门的新手朋友们可以来试一试,锻炼一下。文中的一些附件会上传至文末。还有驱动加vmp壳感觉强度也不是很高?为什么dump下内存来字符串都是明文的,我又去自己给驱动加了vmp壳试了试,似乎dump下来字符串信息都是明文的。所以vmp加在驱动上和加载exe文件上有啥区别?感觉vmp强度应该是蛮高的才对。这方面的资料也不是很多,还请各位看官多多指教。
初赛ring0题目:
DriverDemo.sys是一个驱动程序,它内置了一些限制。
1
, 不能篡改该文件,尝试使驱动成功加载。
2
, 该驱动程序成功加载后,突破它的限制,但不允许patch文件或内存,使它成功打印出(用dbgview可接受)调试信息
"hello world!"
.
初赛ring0题目:
DriverDemo.sys是一个驱动程序,它内置了一些限制。
1
, 不能篡改该文件,尝试使驱动成功加载。
2
, 该驱动程序成功加载后,突破它的限制,但不允许patch文件或内存,使它成功打印出(用dbgview可接受)调试信息
"hello world!"
.
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-8-4 16:22
被危楼高百尺编辑
,原因: 添加引用