首页
论坛
课程
招聘
[原创]分析某游戏驱动保护的学习历程
2022-12-3 00:19 13245

[原创]分析某游戏驱动保护的学习历程

2022-12-3 00:19
13245

分析某游戏驱动保护的学习历程

一、前言

一直都对游戏保护感兴趣,最近想看一看游戏驱动是怎么写的。于是便尝试逆向分析一下。在这个过程中学到很多。

二、驱动调试环境的搭建

由于驱动运行在系统内核层,所以对驱动的调试一般采用双机调试。物理机对物理机,或者物理机对虚拟机。因为手上设备限制,就一个笔记本。所以首先想到的是开虚拟机(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. 修改虚拟机配置文件
1
2
3
debugStub.listen.guest64 = "TRUE"
debugStub.hideBreakpoints = "TRUE"
monitor.debugOnStartGuest64 = "TRUE"
  1. IDA启动gdb调试器

    启动调试器
    gdb

虚拟机在本地运行,则hostname填localhost,目标系统是64位,则port填8864(32位是8832)在这个窗口等待备用。

 

去VMware启动虚拟机后,立刻回到IDA单击OK,那就成功附加上了。

 

idaok

2.2.2 ntoskrnl.exe调试符号加载

IDA-gdb成功附加之后,默认是没有符号。没有符号的情况下,调试内核并不方便。所以得想办法加载上微软的巨硬PDB。内核ntoskrnl.exe的pdb符号文件先下载好备用。IDA在调试时是可以加载pdb文件。

 

loadpdb
pdb

 

所以只需要得到内核模块ntoskrnl.exe的符号文件和内存加载地址就可以了。而IDA附加之后,断下的位置还在系统启动阶段,并没有加载ntoskrnl.exe。那么可以寻找到载入内核模块,并调用内核入口函数的时机。就能获取到KiSystemStartup函数地址减去函数偏移就能得到ntoskrnl的基址。简单逆向winload.exe(有符号)找到一个关键函数OslArchTransferToKernel。

 

entrypoint2
entrypoint3

 

那么只需要在这个位置下断点,即可跟踪到内核入口。IDA搜索一下OslArchTransferToKernel函数末尾指令对应的字节码:

 

49 8B CC 56 6A 10 41 55 48 CB

 

就能确定OslArchTransferToKernel函数位置。

 

retfq

 

在单步执行跳过去之前需要先映射一下内存区域。

 

memory

 

然后单步执行跳到KiSystemStartup函数。

 

startup1

 

那么当前的KiSystemStartup函数地址为0xFFFFF80006F90010,KiSystemStartup的偏移是0x990010

1
ntoskrnl.base=0xFFFFF80006F90010-0x990010=0xFFFFF80006600000

现在终于获得了基址,配和之前下载备用的pdb,IDA再加载一下,就可以有符号了。

 

startup2

三、驱动分析过程

目前为止,我们已经有了可以运行游戏的虚拟机环境和能带微软符号调试内核的调试环境。那么就可以做一些感兴趣且有意思的事情了。前人栽树,后人乘凉。先找一下有没有分析过该游戏保护的,拿过来看一看,找一找思路。hzqst大佬!

 

[原创]某南极动物厂新版XX分析——系统线程部分(新瓶旧酒)-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

 

看完他的分析,我也想定位一下游戏驱动创建的隐藏了入口的系统线程。那就在启动游戏之前,IDA里在PsCreateSystemThread函数处下断点,看第六个参数StartRoutine是不是SeSetAuditParameter函数内部jmp rcx所在地址。

 

thread1

 

一共确定了五个线程的地址,再减去游戏驱动的加载地址(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(意外之喜)。

 

ace_base1

 

具体分析参考hzqst的帖子,不再多说。看完系统线程部分,自然还想看看其他好功能。再找一波,发现杰克王的帖子。杰克王大佬!

 

[原创]某企鹅xxx-base分析-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

 

他的帖子里面对回调部分说得比较少,但是我对游戏驱动回调怎么写的确实感兴趣,开始尝试分析找到回调。

 

首先找一个能用的ark,看看游戏驱动创建的回调函数地址。同样的减去模块基址得到偏移,再去IDA看一看。

 

winark1

 

LoadImage回调函数地址0xFFFFF8001C0B4190去IDA看看。

 

imageroutine1

 

好家伙,暗藏只因,趁着双机调试,去那个地址看一看实际的回调函数地址。

 

imageroutine2
imageroutine3

 

同样能直接F5,其他回调函数都是差不多的模式,不再重复。具体功能我也不再分析细说。

 

分析完回调,我也想看一看游戏驱动的通信函数,学习一下。跳过去一看,不能F5,直呼坏事。那么下一节浅说代码保护。

 

vm1

四、游戏驱动代码保护浅析

我对代码保护没什么了解,也没有基础。我只是根据双机动态调试,跟踪一下代码运行过程,做出一些判断和操作。

4.1 混淆

vm2

 

在调试过程中发现游戏驱动代码具有一些特别的跳转结构。

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的截图。

 

vm3

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                 
# unk_183419: 0xEF 0xDC 0x14 6 0xE8 0xBE 0x3F 2 ........
# 这里有一个表,后续代码有从里面读取数据
lea     r10, [rbp+88h]
lea     rsp, [rsp-8]
# ...
# 继续跟踪
mov     r8b, [r9] #r9=unk_183419
# 从表里读取一个字节 0xEF
xor     r8b, 5Dh
# 与0x5D异或 得到 0xB2
mov     rdx, 5A3B32CF45F10D9Bh
not     rdx
lea     rdx, [r9+rdx]
mov     r9, 0A5C4CD30BA0EF262h
not     r9
lea     r9, [rdx+r9]  # r9=18341A
not     rax
xchg    rax, r9
mov     [rbp+8], rax
# 上面是简单的混淆,实际上只是把表 r9(unk_183419)+1 了
# 然后将表的指针存放到 [rbp+8]的位置
# ...
# 继续跟踪
movzx   r8, r8b  # r8 = 0xB2
sub     r8, 1    # r8 = 0xB1
cmp     r8, 0C8h
jnb     loc_2295F6 
# 如果 r8 > 0xc8 则提到 执行 int 3的地方
lea     r9, off_22B632
# 这里又出现一个表,里面存放了handler(就先这么叫吧)偏移
# 0x22B632:dq offset loc_2295F6
# dq offset loc_2295F6
# dq offset loc_2295F6
# dq offset loc_2295F6
# ...
# dq offset loc_229265
 
mov     r8, [r9+r8*8]
# 这里就根据从第一个表拿到的序号,取出要执行的handler偏移
# 0x22B632+0xB1*8=0x22BBBA
# 0x22BBBA: dq offset loc_227946
 
0x227E8F 4C 8D 0D 6A 81 DD FF          lea     r9, cs:0
# 这里我把汇编字节码带上了,是因为这里很有意思
# ida将汇编写成lea     r9, cs:0,我最初是不理解的
# 通过查阅Intel手册,它应该是
# 4c 8d 0d 6a 81 dd ff    lea    r9,[rip+0xffffffffffdd816a]
# 后四个字节代表偏移,0xFFDD816A,是负数,代表目标在当前位置0x227E8F之前。
# 最后取到的值=0x227E8F+0xFFDD816A+7(指令长度)= 1  00000000
# 溢出,只取后面的,所以最后r9的结果是0
# 再因为我在IDA中给模块rebase到0x0了,所以这一句实际上是获取当前模块的加载地址
 
# ...
# 继续跟踪
add     r8, r9
# r8=handler偏移  0x227946
# r9=模块的加载地址
# add运算后,就得到了内存中要执行的handler地址
jmp     r8
# 跳到handler执行
 
0x227946
xchg    r9, r10
mov     r10, [rbp+8] # 取出前面保存的表r9(unk_183419)+1
not     r10
xchg    r10, r9
not     r9
mov     r8w, [r9]   # 读取unk_18341A: 0x14DC
xor     r8w, 144Ch  # 异或  r8w=0x90
 
mov     rdx, 442E88550684B936h
not     rdx
lea     rdx, [r11+rdx]   # r11= vm_rsp 我将vm_rsp假定为0x1000参与计算
movzx   r8, r8w
mov     rcx, 0BBD177AAF97B46C8h
not     rcx
add     r8, rcx
lea     r8, [rdx+r8]
# 最后得到 r8= vm_rsp+0x90 (r8w)
 
lea     r9, [r9+2] # 表(unk_183419)+1 再往后 +2
mov     dl, [r9] # 从表里读数据,得到 0x6
mov     [r8], dl # [vm_rsp+0x90]=0x6
lea     r9, [r9+1] # 表(unk_183419)+1 +2 +1
 
# 再往后就是重复从表(unk_183419)继续读取一个字节,从存放hander偏移的表里得到要执行那一个
# 取参数?
# 取值
# ...
# unk_183419: 0xEF 0xDC 0x14 6 
#              0xE8 0xBE 0x3F 2 0 0 0 0 0 0 0
#              0xE8 0x86 0x3f 1 0 0 0 0 0 0 0
# ...
# 那么小结一下:
# 0xEF 0xDC 0x14 6表示执行[vm_rsp+0x90]=0x6

五、总结

在整个分析过程学到很多东西。从游戏运行环境搭建,到双机调试环境搭建,到动态调试游戏驱动,再到分析代码保护。涉及到的东西确实多,收获也多。

 

传统功夫,讲究点到为止,此贴到此结束。


[2023春季班]《安卓高级研修班(网课)》月薪两万班招生中~

收藏
点赞15
打赏
分享
最新回复 (13)
雪    币: 28
活跃值: 活跃值 (52)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xjjds 活跃值 2022-12-3 07:53
2
0
厉害啊,我的个。都是自学的吗
雪    币: 2247
活跃值: 活跃值 (964)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
shun其自然 活跃值 2022-12-3 07:54
3
0

还原了下

雪    币: 4709
活跃值: 活跃值 (2207)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
大鲤鱼 活跃值 2022-12-3 09:05
4
0
winark能分享下吗?
雪    币: 1001
活跃值: 活跃值 (1904)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小希希 活跃值 2022-12-3 09:55
5
0
mark
雪    币: 1264
活跃值: 活跃值 (987)
能力值: ( LV3,RANK:27 )
在线值:
发帖
回帖
粉丝
CanineTooth 活跃值 2022-12-3 12:39
6
0
大鲤鱼 winark能分享下吗?
与Winark作者 VirtualCC 沟通了,他说不分享
雪    币: 1361
活跃值: 活跃值 (1274)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
killleer 活跃值 2022-12-3 17:34
7
0
CanineTooth 与Winark作者 VirtualCC 沟通了,他说不分享

你们呐,搞事情!


雪    币: 1570
活跃值: 活跃值 (1807)
能力值: ( LV3,RANK:37 )
在线值:
发帖
回帖
粉丝
qqzxc 活跃值 2022-12-3 21:01
8
0
路爷牛逼
雪    币: 63
活跃值: 活跃值 (626)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_kppjdiod 活跃值 2022-12-6 01:48
9
0
tp检测
1:了共享内存kddebuggerenbled,处理下kdcom复位包,让其写其他值,然后写入0为未启动保护模式即可
2:kddebuggernotprosent,也在kdcom,复位包写入了0,在exitdebugger,恢复内核那重新写入为1即可
3dbeuggerpitch,直接写1,
最后断链表kdcom.dll
双机就过了
雪    币: 3968
活跃值: 活跃值 (4744)
能力值: ( LV7,RANK:116 )
在线值:
发帖
回帖
粉丝
VirtualCC 活跃值 2022-12-7 14:36
10
0
路总强无敌!
雪    币: 21
活跃值: 活跃值 (335)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
靴子 活跃值 2022-12-9 22:32
11
0
不错!
雪    币: 669
活跃值: 活跃值 (2828)
能力值: ( LV6,RANK:98 )
在线值:
发帖
回帖
粉丝
还我六千雪币 活跃值 2023-1-3 09:43
12
0
路总强无敌!
雪    币: 2165
活跃值: 活跃值 (2076)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
fengyunabc 活跃值 1 2023-1-3 10:13
13
0
感谢分享/
雪    币: 1001
活跃值: 活跃值 (1904)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小希希 活跃值 2023-1-3 11:04
14
0
基本功很强,可以
游客
登录 | 注册 方可回帖
返回