一、前言
一直都对游戏保护感兴趣,最近想看一看游戏驱动是怎么写的。于是便尝试逆向分析一下。在这个过程中学到很多。
二、驱动调试环境的搭建
由于驱动运行在系统内核层,所以对驱动的调试一般采用双机调试。物理机对物理机,或者物理机对虚拟机。因为手上设备限制,就一个笔记本。所以首先想到的是开虚拟机(VMware Workstation Pro)进行双机调试。
2.1 虚拟机中游戏运行环境的准备
一般情况下,游戏都会检测虚拟机运行环境。网上收集一波资料,找到hzqst的一个项目。
hzqst/VmwareHardenedLoader: Vmware Hardened VM detection mitigation loader (anti anti-vm) (github.com)
按照使用说明,卸载vmtools,修改虚拟机配置文件,安装驱动,就能正常运行游戏。
但是卸载vmtools后,虚拟机很卡,而且屏幕显示也很别扭。所以还是希望能在安装了vmtools的情况下运行游戏。经过测试发现是可行的。以下是操作步骤:
1.修改虚拟机配置文件
1 2 3 4 5 6 7 | hypervisor.cpuid.v0 = "FALSE"
board - id .reflectHost = "TRUE"
hw.model.reflectHost = "TRUE"
serialNumber.reflectHost = "TRUE"
smbios.reflectHost = "TRUE"
SMBIOS.noOEMStrings = "TRUE"
ethernet0.address = "00:11:22:33:44:55" / / 这里随意
|
2.安装vmloader驱动
3.修改vmtools显卡驱动名字(可选)
安装驱动精灵或者驱动人生,备份显卡驱动。用7z或者winrar打开备份的压缩包(不解压),编辑inf文件。
1 2 3 4 | / / 将inf文件以下三行任意修改
DiskID = "VMware Tools"
CompanyName = "VMware, Inc."
SVGA = "VMware SVGA 3D"
|
保存文件后。用驱动精灵或者驱动人生还原显卡驱动即可。
完成修改后,成功运行游戏。
2.2 双机调试方式的选取
在看雪论坛看了一圈发现,游戏驱动基本都对Windbg双机调试进行了检测和处理。目前我并不清楚是怎么检测的,所以放弃VMWare+kdstub+windbg的双机调试方式,另寻他法。经过搜索,发现VMWare+gdbstub+IDA-gdb是个很好的方式。
2.2.1 IDA+VMWare调试配置
- 修改虚拟机配置文件
1 2 3 | debugStub.listen.guest64 = "TRUE"
debugStub.hideBreakpoints = "TRUE"
monitor.debugOnStartGuest64 = "TRUE"
|
IDA启动gdb调试器
虚拟机在本地运行,则hostname填localhost,目标系统是64位,则port填8864(32位是8832)在这个窗口等待备用。
去VMware启动虚拟机后,立刻回到IDA单击OK,那就成功附加上了。
2.2.2 ntoskrnl.exe调试符号加载
IDA-gdb成功附加之后,默认是没有符号。没有符号的情况下,调试内核并不方便。所以得想办法加载上微软的巨硬PDB。内核ntoskrnl.exe的pdb符号文件先下载好备用。IDA在调试时是可以加载pdb文件。
所以只需要得到内核模块ntoskrnl.exe的符号文件和内存加载地址就可以了。而IDA附加之后,断下的位置还在系统启动阶段,并没有加载ntoskrnl.exe。那么可以寻找到载入内核模块,并调用内核入口函数的时机。就能获取到KiSystemStartup函数地址减去函数偏移就能得到ntoskrnl的基址。简单逆向winload.exe(有符号)找到一个关键函数OslArchTransferToKernel。
那么只需要在这个位置下断点,即可跟踪到内核入口。IDA搜索一下OslArchTransferToKernel函数末尾指令对应的字节码:
49 8B CC 56 6A 10 41 55 48 CB
就能确定OslArchTransferToKernel函数位置。
在单步执行跳过去之前需要先映射一下内存区域。
然后单步执行跳到KiSystemStartup函数。
那么当前的KiSystemStartup函数地址为0xFFFFF80006F90010,KiSystemStartup的偏移是0x990010
1 | ntoskrnl.base = 0xFFFFF80006F90010 - 0x990010 = 0xFFFFF80006600000
|
现在终于获得了基址,配和之前下载备用的pdb,IDA再加载一下,就可以有符号了。
三、驱动分析过程
目前为止,我们已经有了可以运行游戏的虚拟机环境和能带微软符号调试内核的调试环境。那么就可以做一些感兴趣且有意思的事情了。前人栽树,后人乘凉。先找一下有没有分析过该游戏保护的,拿过来看一看,找一找思路。hzqst大佬!
[原创]某南极动物厂新版XX分析——系统线程部分(新瓶旧酒)-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com
看完他的分析,我也想定位一下游戏驱动创建的隐藏了入口的系统线程。那就在启动游戏之前,IDA里在PsCreateSystemThread函数处下断点,看第六个参数StartRoutine是不是SeSetAuditParameter函数内部jmp rcx所在地址。
一共确定了五个线程的地址,再减去游戏驱动的加载地址(IopLoadDriver下断获得)得到函数偏移,就能在IDA里面静态分析了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 1.FFFFF8001C0B5B60
2.FFFFF8001C0 * * * 70
3.FFFFF8001C0 * * * 80
4.FFFFF8001C0 * * * 80
5.FFFFF8001C0 * * * F0
FFFFE20CF9B6ABE0 0000000000000000 MEMORY:unk_0
FFFFE20CF9B6ABE8 FFFFE20CF9B6ACA9 MEMORY:FFFFE20CF9B6ACA9
FFFFE20CF9B6ABF0 FFFFF8001C1965A1 MEMORY:FFFFF8001C1965A1
FFFFE20CF9B6ABF8 FFFFE20C00000000 MEMORY:FFFFE20C00000000
FFFFE20CF9B6AC00 FFFFAD86F81E36F8 MEMORY:FFFFAD86F81E36F8
FFFFE20CF9B6AC08 FFFFF8000693CC31 SeSetAuditParameter + 41
FFFFE20CF9B6AC10 FFFFF8001C0B5B60 MEMORY:FFFFF8001C0B5B60
FFFFE20CF9B6AC90 0000000000000000 MEMORY:unk_0
FFFFE20CF9B6AC98 0000000000000002 MEMORY:word_2
FFFFE20CF9B6ACA0 FFFFF8001C14B419 MEMORY:FFFFF8001C14B419
FFFFE20CF9B6ACA8 FFFFF80000000000 MEMORY:FFFFF80000000000
FFFFE20CF9B6ACB0 FFFFAD86F81E3238 MEMORY:FFFFAD86F81E3238
FFFFE20CF9B6ACB8 FFFFF8000693CC31 SeSetAuditParameter + 41
FFFFE20CF9B6ACC0 FFFFF8001C0 * * * 70 MEMORY:FFFFF8001C0 * * * 70
FFFFE20CF9B6AC10 0000000000000000 MEMORY:unk_0
FFFFE20CF9B6AC18 0000000000000000 MEMORY:unk_0
FFFFE20CF9B6AC20 FFFFF8001C1962F0 MEMORY:FFFFF8001C1962F0
FFFFE20CF9B6AC28 0000000000000000 MEMORY:unk_0
FFFFE20CF9B6AC30 FFFFAD86F8168578 MEMORY:FFFFAD86F8168578
FFFFE20CF9B6AC38 FFFFF8000693CC31 SeSetAuditParameter + 41
FFFFE20CF9B6AC40 FFFFF8001C0 * * * 80 MEMORY:FFFFF8001C0 * * * 80
FFFFE20CF9B6AC50 0000000000000000 MEMORY:unk_0
FFFFE20CF9B6AC58 0000000000000002 MEMORY:word_2
FFFFE20CF9B6AC60 FFFFF8001C180C6D MEMORY:FFFFF8001C180C6D
FFFFE20CF9B6AC68 0000000000000000 MEMORY:unk_0
FFFFE20CF9B6AC70 FFFFAD86F8168338 MEMORY:FFFFAD86F8168338
FFFFE20CF9B6AC78 FFFFF8000693CC31 SeSetAuditParameter + 41
FFFFE20CF9B6AC80 FFFFF8001C0 * * * 80 MEMORY:FFFFF8001C0 * * * 80
FFFFE20CF9B6AC90 0000000000000000 MEMORY:unk_0
FFFFE20CF9B6AC98 0000000000000002 MEMORY:word_2
FFFFE20CF9B6ACA0 FFFFF8001C1835B0 MEMORY:FFFFF8001C1835B0
FFFFE20CF9B6ACA8 0000000000000000 MEMORY:unk_0
FFFFE20CF9B6ACB0 FFFFAD86F8168378 MEMORY:FFFFAD86F8168378
FFFFE20CF9B6ACB8 FFFFF8000693CC31 SeSetAuditParameter + 41
FFFFE20CF9B6ACC0 FFFFF8001C0 * * * F0 MEMORY:FFFFF8001C0 * * * F0
|
当我打开另一个IDA载入游戏驱动文件,跳到线程地址,发现直接就能F5(意外之喜)。
具体分析参考hzqst的帖子,不再多说。看完系统线程部分,自然还想看看其他好功能。再找一波,发现杰克王的帖子。杰克王大佬!
[原创]某企鹅xxx-base分析-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com
他的帖子里面对回调部分说得比较少,但是我对游戏驱动回调怎么写的确实感兴趣,开始尝试分析找到回调。
首先找一个能用的ark,看看游戏驱动创建的回调函数地址。同样的减去模块基址得到偏移,再去IDA看一看。
LoadImage回调函数地址0xFFFFF8001C0B4190去IDA看看。
好家伙,暗藏只因,趁着双机调试,去那个地址看一看实际的回调函数地址。
同样能直接F5,其他回调函数都是差不多的模式,不再重复。具体功能我也不再分析细说。
分析完回调,我也想看一看游戏驱动的通信函数,学习一下。跳过去一看,不能F5,直呼坏事。那么下一节浅说代码保护。
四、游戏驱动代码保护浅析
我对代码保护没什么了解,也没有基础。我只是根据双机动态调试,跟踪一下代码运行过程,做出一些判断和操作。
4.1 混淆
在调试过程中发现游戏驱动代码具有一些特别的跳转结构。
1 2 3 4 5 6 7 | push r8
mov r8, 0FFFFF8001C2C86C1h
pushfq
add r8, 0FFFFFFFFFFF63D07h
popfq
jmp r8
pop r8
|
其中最奇怪的就是 jmp r8会跳转到他的下一句指令pop r8,那么其实这一段代码什么也没做,就是干扰一下。简单对抗一下IDA-F5小子。发现了关键点,那么我们可以写一下去混淆脚本。同样的,网上找一波资料。找到了 逍遥m 发的帖子。逍遥m大佬!
[原创]IDA 驱动vmp变异 去花指令 IDC脚本-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com
那我也是抄一下代码,针对性的改一改。写好一个IDC脚本开干。脚本这里我就不发了,混淆的原理和如何写idc去混淆脚本都贴出来了。相信大家认真点分析都会写去混淆脚本的。毕竟自己动手丰衣足食。贴一下去混淆之后成功F5的截图。
4.2 vm
去混淆之后,大部分功能都能看了。但是还有些是被v了的,跑在虚拟机里面。我能力有限,只是简单分析一下。我拿驱动入口里调用的一个函数举例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | 在进入虚拟机之前需要保存上下文环境(参数,寄存器,原始堆栈等等),这一部分操作比较明显,直接跳过。
以下分析过程中省略了一些混淆代码。
lea r11, unk_183419
lea r10, [rbp + 88h ]
lea rsp, [rsp - 8 ]
mov r8b, [r9]
xor r8b, 5Dh
mov rdx, 5A3B32CF45F10D9Bh
not rdx
lea rdx, [r9 + rdx]
mov r9, 0A5C4CD30BA0EF262h
not r9
lea r9, [rdx + r9]
not rax
xchg rax, r9
mov [rbp + 8 ], rax
movzx r8, r8b
sub r8, 1
cmp r8, 0C8h
jnb loc_2295F6
lea r9, off_22B632
mov r8, [r9 + r8 * 8 ]
0x227E8F 4C 8D 0D 6A 81 DD FF lea r9, cs: 0
add r8, r9
jmp r8
0x227946 :
xchg r9, r10
mov r10, [rbp + 8 ]
not r10
xchg r10, r9
not r9
mov r8w, [r9]
xor r8w, 144Ch
mov rdx, 442E88550684B936h
not rdx
lea rdx, [r11 + rdx]
movzx r8, r8w
mov rcx, 0BBD177AAF97B46C8h
not rcx
add r8, rcx
lea r8, [rdx + r8]
lea r9, [r9 + 2 ]
mov dl, [r9]
mov [r8], dl
lea r9, [r9 + 1 ]
|
五、总结
在整个分析过程学到很多东西。从游戏运行环境搭建,到双机调试环境搭建,到动态调试游戏驱动,再到分析代码保护。涉及到的东西确实多,收获也多。
传统功夫,讲究点到为止,此贴到此结束。
[2022冬季班]《安卓高级研修班(网课)》月薪两万班招生中~