2021腾讯游戏安全技术竞赛 决赛PC客户端writeup

前言

最近忙了一些琐碎的事情,这两天又有闲功夫了,发现2021安全技术竞赛决赛赛题出来了,而且比赛都结束了....

虽然赛事已经结束,但题目还是能做。既然闲下来就继续做做,写个writeup吧。

PC决赛赛题下载

赛题

说明:

  1. shootgame是一个游戏,hack.exe是游戏shootergame的一个外挂程序。
  2. 运行shootgame游戏,运行hack.exe,成功执行外挂功能并分析外挂实现过程。

目标:

  1. 成功执行hack功能,给出外挂执行成功的flag
  2. 实现与hack.exe外挂功能相同,但原理不同的程序

很有意思的游戏外挂方向,算是我比较擅长的领域

先打开游戏试试,跑一下hack.exe,发现一闪而过,并没有执行外挂功能 看了一下无壳,所以直接拖入IDA分析

分析hack.exe

main函数里就是整个外挂程序的执行流程,先大体看一下流程

程序运行流程

一. 程序先解密了字符串,得到文件名hack.dat,并读取了这个文件

FileName解密
1
2
3
4
5
6
7
8
data = [0x06, 0x0E, 0x13, 0x1A, 0x5C, 0x17, 0x15, 0x1]

file_name = ''

for i in range(len(data)):
file_name += chr(data[i] ^ i + 110)

print(file_name)

这也解释了,直接打开外挂为什么会一闪而过

二. 程序从hack.dat里解密出游戏进程名,并打开游戏进程

decrypt函数解密出hack.dat的明文,通过下标构造出进程名,然后用CreateToolhelp32SnapshotProcess32First遍历进程,找到后打开进程。

代码中有一个for循环目前不太清楚是干嘛的、decrypt函数的解密算法也先放放。先继续往下看

三. 程序开始构造flag,并检查hack.dat的内容

  1. 第一个循环用于构造flag,脚本如下(与解密FileName的算法一致)
flag构造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
data = [92, 61, 35, 35, 26, 1, 27, 19, 25, 32, 
12, 53, 31, 55, 14, 55, 61, 44, 236, 213, 235,
241, 225, 255, 232, 245, 252, 241, 164, 228,
233, 193, 246, 250, 245, 249, 235, 234, 213,
226, 244, 231, 219, 214, 192, 234, 172, 233,
237, 204, 151, 236, 248, 218, 242, 193, 233,
242, 199, 236, 146]

flag = ''

for i in range(len(data)):
flag += chr(data[i] ^ i + 110)

print(flag)
  1. 第二个循环用于检查flag与hack.dat明文是否一致
  2. 如果一致,就解密出flag%s(此处省略解密脚本,与解密FileName的算法一致),调用printf输出flag

四. 注入DLL

  1. 解密DLL数据
  2. 远程申请内存,用于放DLL
  3. 解密ntdll.dll,以及各种DLL注入需要用到的函数(解密算法还是和上文一样,函数很多,不一一展示了)
  1. 使用WriteProcessMemory远程写入DLL内容、执行dll的执行器

到这里,就能解释上文步骤二中的for是用来干嘛的了,是用来计算dll执行器的shellcode的长度

  1. 使用CreateRemoteThreadEx创建远程线程执行DLL,实现外挂功能

decrypt函数分析

hack.exe中有两处调用了decrypt函数,分别是: 1. hack.dat文件的解密 2. DLL数据的解密

由于上文已经知道了hack.dat解密出来的明文会与flag做对比。所以我们只需要分析decrypt函数的加密算法就可以得到hack.dat文件,也就能正常启动外挂了,顺便也可以dump出DLL文件

decrypt函数是经过SSE指令集优化后的解密函数。

利用SSE指令集,可以实现一个CPU指令操作16byte的数据,所以此函数SSE部分每次循环解密64byte的数据。 当解密到不足64byte时候(或本身就不足64byte),就使用常规,一byte一byte的解密 解密算法如图已经分析的很清楚了,下面上解密脚本,将flag反向输出成hack.dat

输出hack.dat
1
2
3
4
5
6
7
8
9
10
flag = '2RSRhrofoWtLeLrJCSlTireznrtx.oeLxuehyyAwbpCOZq0tsS7MZyVdOUoE8'

data = [ord(c) for c in flag]

for i in range(len(data)):
data[i] = (data[i] + 0x13) ^ 0x3F

fp = open("hack.dat", 'wb')
fp.write(bytes(data))
fp.close()

把输出的hack.dat放在hack.exe目录下,运行hack.exe

成功输出flag

成功执行外挂功能,试了一下,鼠标右键可以自瞄

Dump外挂DLL

由于已经搞懂了decrypt函数的算法,将DLL给dump出来就很简单了,既可以动态调试、也可以静态的使用IDAPython脚本解密后输出

我这次使用脚本

dump DLL
1
2
3
4
5
6
7
8
9
10
11
start_addr = 0x14001DA40
len = 0xFA00
data = ida_bytes.get_bytes(start_addr, len)
dll = []

for i in range(len):
dll.append((data[i] ^ 0x3F) - 0x13 & 0xFF)

fp = open("D:\\dump.dll", 'wb')
fp.write(bytes(dll))
fp.close()

小总结

dump出来后,下面就可以分析外挂功能的具体实现

分析dump.dll

无壳,直接拖入IDA分析

程序运行流程

DllMain创建了一个线程,线程里放着主流程

一. 程序先获取游戏主模块,初始化各种全局变量

根据主模块+基址偏移,获得World指针、三维xyz转屏幕xy的函数地址、以及游戏tick函数地址等等

二. 然后Hooking游戏tick函数,将自瞄功能实现在Hook函数里

64位inline Hook,这里应该用了某Hook库,比较复杂。原理是修改原函数入口,JMP到Hook函数里,也实现了蹦床,这里就不具体分析了,看一下Hook修改原函数入口的核心代码就行,如下图

三. 最后实现自瞄功能

这里是本DLL的重点,自瞄功能的实现又分为以下几个步骤

  1. 根据基址偏移获取player_controller对象
  1. 判断鼠标右键是否按下,如果按下就调用get_target函数尝试匹配一个敌人

get_target函数分析如下

遍历对象数组,并排除自己。获取每个对象名字,并排除物品、武器等对象

计算每个敌人与准心的距离,取距离准心最近的一个敌人返回

  1. 获取自己和敌人的坐标
  1. 计算相对距离,求俯仰角和偏航角,然后将数据写入到玩家Carm里,实现自瞄

小总结

自此大概就明白了此DLL是如何实现自瞄的了,属于传统的内部攻击

实现与hack.exe外挂功能相同的程序

等有时间了再写...