2020腾讯游戏安全竞赛初赛
0x00 题
初赛共分为ring3和ring0两道题
初赛ring3题目:
winmine.exe是一个扫雷游戏程序,winmine.dmp是该程序的一份进程dump, 在这份dump中,winmine.exe的内存映像有指令被篡改,篡改实现了外挂功能。
请找出dump中,winmine.exe的内存映像中2处被篡改实现外挂功能的指令(被篡改指令的偏移、篡改前后的指令分别是什么),并分析这些指令篡改所实现的外挂功能是什么。
初赛ring0题目:
DriverDemo.sys是一个驱动程序,它内置了一些限制。
1, 不能篡改该文件,尝试使驱动成功加载。
2, 该驱动程序成功加载后,突破它的限制,但不允许patch文件或内存,使它成功打印出(用dbgview可接受)调试信息”hello world!”。
驱动未签名,需要设置Windows 10高级启动选项,禁用驱动程序强制签名后方可答题,支持使用虚拟机。
0x01 解
ring3
给了一个dmp文件,可以考虑先上Windbg分析一下
题目中提到了内存映像中有指令被篡改,因此先把winmine.dmp的内容转写为文件
用windbg打开.dmp,然后再view中找到Modules窗口,然后可以看到这个(需要加载符号表)
知道了基址和大小,就可以将内存写出到文件了、
1 | .writemem winmine_dmp.exe 0x1000000 0x1020000 |
不能有中文路径(不是,哥们,都2024年了怎么还有中文路径问题啊)
由于这个写出的文件是从内存dump出来的,所以他的代码节位置和winmine.exe的代码节位置不同
我们并不需要这个dump出来的文件可运行,但是由于对比需要,我们还是需要找出它的代码节位置
这里可以从源文件中找几个有代表性的连续字节(特征码),然后去dump出来的文件那里找
使用CFF Explorer VIII查看winmine.exe的代码节位置,并挑选适合的特征码
然后在010editor中找到winmine_dmp.exe里对应的位置
再根据winmine.exe的PE结构知道代码节的大小
这里选用Raw Size即00003C00作为大小
用python整个diff脚本分析一下,注意两个文件的代码节起始位置
1 | import struct |
由于代码数据在导入内存后会发生地址的改变(对齐粒度),这种改变是极其正常的,所以前面字节的变化可以忽略
重点在地址比较大的地方发生的改变
1 | Memory Addr: 0x2ff5 ErrorDump:0x90,CorrectDump:0xff |
代码这里用的是装到内存中的基地址作为输出
从这里可以看出有两个地址范围发生了比较大的改变,0x2ff5-0x2ffa和0x3591-0x3592
丢x32dbg里面看看
先看看0x2ff5的
跳转到这个地址然后nop掉
对比发现计时器不会动了,所以可以判断这个地址篡改后的功能是暂停计时
然后再看0x3591
运行看看
懂了,点雷不炸,相当于锁血了
至此ring3结束,到ring0了
ring0
只给了一个驱动文件(DriverDemo.sys),试着加载一下
加载驱动:https://blog.csdn.net/weixin_41725706/article/details/127781355
入门了解一下驱动:https://zhuanlan.zhihu.com/p/690402217
运行驱动的代码参考:https://xia0ji233.pro/2024/03/30/tencent-race-2020-pre/
1 |
|
记得禁用驱动程序签名(要不然会报577错误):
- 首先按下Win键+I打开设置。
- 点击“更新与安全”,然后点击“恢复”。
- 在“高级启动”部分,点击“立即重启”。
- 计算机将进入高级启动选项。在这里,选择“疑难解答”。
- 接着选择“高级选项”。
- 在高级选项中,选择“启动设置”。
- 在启动设置中,点击“重新启动”。
- 计算机将重新启动并显示启动设置。在这里,找到“禁用驱动程序签名强制执行”的选项,按其对应的数字键(一般是7)。
- 计算机将继续启动,此时驱动程序强制签名已被禁用。
然后记得要管理员启动(要不然创建不了句柄)
加载之后的结果:
关于加载驱动之后的报错:https://www.cnblogs.com/qingtian224/p/5580393.html
〖31〗-连到系统上的设备没有发挥作用。
用IDA打开,可以看到VMP
好,如果是vmp的话,那么就不做了
这里有两个方法继续做:
1.用Unicorn_PE
2.动调然后把驱动dump下来
先看看1
1.Unicorn_PE
关于Unicorn_PE:https://github.com/hzqst/unicorn_pe
基于Unicorn+capstone+Blackbone用于对PE文件做模拟执行后分析的一个超级好用的项目。
从Git上拖下源码编译后直接对驱动模拟执行并Dump
1 | ./unicorn_pe.exe DriverDemo.sys -dump -k -packed |
这玩意坑挺多的,评价是dump的时候确实方便,等个十几分钟可以直接脱壳,但是配置环境的时候就不方便了(
还有参考的WP里面的是修过的,与实物不符(
dump出来的结果和2一样,后续分析后面了
来看看2
2.动调然后把驱动dump下来
这里需要用到双机调试
VS2019:https://blog.csdn.net/u011117126/article/details/124191548
Windbg:https://blog.csdn.net/qq_33504040/article/details/78349885
在进行调试之前先要知道断在哪个函数
本题加了VMP,但是导入表还是比较清楚的,可以找找相关函数
这里着重关注这个MmGetSystemRoutineAddress
参考:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/wdm/nf-wdm-mmgetsystemroutineaddress
这个函数可以通过函数名获得函数地址,也就是所谓的暗桩调用。
这个一般会拿来调用一些奇奇怪怪的函数,从而实现反调试(
先装载一下驱动
1 | sc create DriverDemo binpath="DriverDemo.sys" type=kernel |
然后在windbg里面下API断点(注意这个函数是在nt里面的调用的,所以不需要指定驱动)
1 | bu MmGetSystemRoutineAddress |
之后启动服务
1 | sc start DriverDemo |
然后就会断下来了
跳出去到调用这个函数的地方
发现这个函数去了一个地址存了起来,然后call了这个地址
跟进这个call看看
反调试
可以看看这个:https://blog.csdn.net/sanqiuai/article/details/120025003
处理这个反调试有两种方法:patch或者hook
这里参考:https://xia0ji233.pro/2024/03/30/tencent-race-2020-pre/
通过查阅 MSDN(https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/wdm/nf-wdm-kddisabledebugger)得知,该函数是用于反调试的。要把这个过掉,要么 PATCH 这个函数调用,要么hook这个函数,都可以,但是 PATCH 有一定的危险就是你不知道哪个时候的 RAX 的值是多少,因此选择 hook 掉这个函数然后直接返回STATUS_DEBUGGER_INACTIVE
用hook(此代码需要驱动开发相关知识)
1 |
|
先运行这个服务(和上面加载题目驱动一样),然后再运行题目,就可以调试了
不过似乎本题反调试的作用就是执行KdDisableDebugger后就无法手动break了,但是断点还是在的,一旦碰到就会断下来,可能没有进行什么更强的操作吧(
这题动调不太好看,还是直接dump驱动下来逆会比较好,断在MmGetSystemRoutineAddress的时候其实就相当于脱了VMP壳了,这个时候就可以得到DriverEntry了
本题有个问题就是DriverDemo.sys加载后不会出现在Windbg中,即无论是在Module窗口还是使用lm命令都无法查看到DriverDemo,从而也就找不到基址
但是有个邪门的招数,当加载这个驱动两次之后再加载,会发现出现了<Unloaded_DriverDemo.sys>+偏移:
的前缀,如下图
本来应该是用别的什么软件获取基址然后再dump的,但是这里我走了个邪道
不过要注意在执行邪道的时候要注意:
1.让windbg的pdb文件全部加载完成,模块窗口能显示模块且没有Unable to enumerate kernel-mode unloaded modules, Win32 error 0n30
报错
2.在两次之后要使用lm
查看一下模块,并且要不在驱动模块的地方进行查看,如在nt!MmGetSystemRoutineAddress
中进行查看,如下图:
总之这个时候就能dump了
1 | .writemem D:\DriverDemo_dump.sys 基址 L大小 |
这个大小可以根据lm
命令中的Unloaded Module中的DriverDemo大小计算出来
然后就和Unicorn_pe那边dump出来的东西一样了
接下来就开始分析吧
3.逆向工程
参考:https://bbs.kanxue.com/thread-268431.htm
这里采用unicorn_pe那边dump下来的进行分析,两个其实是一样的,就是地址不同罢了
重点是这个函数
需要去到反汇编窗口看
会发现这里有很多没有解析出来的数据,用c转为代码
然后去到函数开头设置一下函数尾
但是设置完之后会发现反编译还是不变,结果还是很奇怪
这个时候就要去找那两个sub了:sub_140001000和sub_140001160
应该是这两个函数里面识别出错导致IDA分析出问题了
以sub_140001160为例,可以在反汇编中看到这个:一个call后面接个int3
这个int3就是造成解析出错的罪魁祸首,它造成了函数的隔断,需要把它改为nop
其他地方也有很多这种情况,一一进行更改即可
然后是函数问题,IDA并没有想象中智能,经常会莫名其妙加个函数头,实际上是一个的函数会被他变成好几个,这个时候就需要去掉多出来的函数头,然后将真正的函数尾进行延长,从而让IDA能正确反编译
有些时候还会碰到这种中间有个0的,将0换成nop,其余变成代码
然后要把sub_140001160的返回地址位置改一改(最重要)
之后把1160给undefined,再重新识别,让IDA能识别出函数1160的return
最后就可以在sub_140143E84的位置看到完整的代码了
改一改函数名就可以去分析了
4.分析
改了一下函数名
第一问
让驱动能成功加载,那就看一下sub_140001160
v12被赋值,转为字符之后可以看到是这么一个东西
1 | \\REGISTRY\\MACHINE\\SOFTWARE\\AppDataLow\\Tencent\\{61B942F7-A946-4585-B624-B2C0228FFEBC} |
一眼注册表值,结合一下下面的key,不难判断这是对注册表某项的键值的验证
所以在注册表中指定位置加上上面那段即可
可以看到驱动加载了,而且输出了调试信息(这个调试信息要开Enable Verbose Kernel Capture才能看到)
第二问
搜字符串可以找到helloword的字样
跟过去找交叉引用
来到这个函数(这里也是参考进行修改)
往上跟会回到这里,是这个sub_140001D40里面的东西
看不太懂,参考一下大哥
驱动入口函数通过注册表check函数后创建了一个名为 “\BaseNamedObjects\tp2020” 的通知事件,并对这个事件调用了KeClearEvent函数对其清空,然后调用了 CreateThreadWaitEvent 函数,这个函数内创建了一个线程,跟进线程函数看一下
…
里面就是一个While循环并调用KeWaitForMutexObject等待前面创建的事件进入信号态,若事件进入信号态则会输出HelloWorld。并且这个驱动内再没有其他对于该事件对象的交叉引用了。 至此,题意非常明确了,要求我们写一个驱动将该事件设置为信号态,使得该驱动输出HelloWorld
这里本来应该有个while循环的,但我这边没有,不过不影响解题
因此就可以写驱动了
1 |
|
最后也是成功输出了
总结:不想再碰ring0的东西了(