游戏逆向FPS游戏玩家对象数据分析

Posted douluo998

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了游戏逆向FPS游戏玩家对象数据分析相关的知识,希望对你有一定的参考价值。

目标(Objective)

Ÿ Health

Ÿ Rifle Ammo

Ÿ Pistol Ammo

Ÿ Player Pointer

0x01 玩家健康

查找玩家健康值,玩家健康值是100,但是我们并不知道数值类型,我们可以使用精确搜索方式搜索100-所有类型

CE搜索

结果很多,我们可以使用手雷来减少血量

我们会得到两个结果


我们可以去尝试改变数值,最终发现一个是我们的客户端健康值,一个是服务器健康值,我们分析关注的重点在客户端这边,这是单机模式下修改服务端的可以达到无敌的效果但并不是分析的重点。

右键改地址选择寻找是什么改写了该地址,进入游戏继续让手雷改变健康值。

让我们记住这个地址,OD附加游戏然后转到该地址(很不幸游戏中途奔溃了,我们从头来了一遍,所以后面地址肯定不同了)

0x03 基地址与周边数据分析

现在我们开始寻找基地址,这样在下次打开游戏就不怕存放玩家健康值的地址改变啦。

[edx+0xF8]就是玩家健康值地址,edx来源于eax

eax来源于上面的call

call内运行逻辑还是比较多的,我们F2断点调试一下运行逻辑

根据调试分析,eax来源于[0x50F4F4]

最终表达式如下

[0x50F4F4+0xF8]

打开CE数据结构分析,我们填入0x50F4F4,第一个就很像我们玩家对象了

我们添加几个BOT

刚刚好七个对象指针

初步分析可能是地图玩家坐标

偏移150步枪弹药

偏移13C手枪弹药

偏移128步枪备用弹药

偏移114手枪备用弹药

到此整个数据分析差不多了,基本上都在附近。

《有趣的二进制:软件安全与逆向分析》读书笔记:在射击游戏中防止玩家作弊

目录

前言

本篇继续阅读学习《有趣的二进制:软件安全与逆向分析》,本章是在射击游戏中防止玩家作弊,学习内存转储和如何保护软件不被破解

一、内存转储

借用一个小游戏进行学习内存转储的知识

1、射击游戏的规则

游戏是 chap02\\shooting 中的 shooting.exe


规则如下:

  • 空格键:射击
  • ←键:向左移动
  • →键:向右移动
  • ↑键:填充能量(以当前得分为上限)
  • ↓键:时间停止(消费能量)

击中敌人可以增加得分,被敌人击中则减少得分。得分越高,敌人越强,子弹的追踪性能也会提高

2、修改 4 个字节就能得高分

书中用的是兔耳旋风,是个日本专有的

我就用CE了(教程见Cheat Engine(CE)教程


这一小节就是要找到分数的内存位置,然后修改它

简单不断搜索找到并修改即可,如下


这两小节在Cheat Engine(CE)教程中有更多的内容

3、获取内存转储

内存转储”(memory dump):将内存数据保存成文件

  • 打开任务管理器
  • 右键点击目标进程名称
  • 选择“创建转储文件”

4、通过转储文件寻找出错原因

当程序崩溃时,最好能够第一时间启动调试器,但有些情况下无法做到这一点。不过,即便在这样的情况下,只要我们留下了转储文件,也能够通过它来找到出错的原因

用 WinDbg 来分析一下 chap02\\guitest2 中的 guitest2.exe 的转储文件 user.dmp


启动之后只有一个 Command 窗口,调出其他窗口:

  • Alt+6:显示 Call Stack(调用栈)窗口

  • Alt+7:显示 Disassembly(反汇编)窗口

Disassembly 窗口这里本来应该显示出反汇编之后的代码,但由于 EIP 的值为 00000000,因此现在只显示一堆问号,这就表示“出于某些原因,程序跳转到了 00000000 这个地址”,我们要找到这个原因

从 Call Stack 窗口中我们可以看到这样一行

0012f8f0 77cf8734 000b0144 00000111 00000001 guitest2+0x12d0

双击,Disassembly 窗口会显示


这条前面有个call eax,按 Alt+4 可以查看寄存器的值


可以看到eax值为0,也就是说,004012ce 的这条 call eax 指令调用了 00000000 这个地址,这就是引发崩溃的原因

那eax的值哪来的呢?

上一条命令,地址 004012c8 处也执行了一条 call 指令,由于返回值会存放在 eax 中,因此我们可以推测,eax 的 00000000 是从这里来的

那么,这里调用的又是什么函数呢?按 Alt+5 打开 Memory(内存)窗 口,在显示 Virtual 的地方输入00402004,地址 00402004 的值为 04 24 00 00(=00002404), 这里显示的值是相对于基地址的偏移量,因此我们再输入 00400000+2404=00402404,这时会显示出调用的函数名称,即 GetProcAddress


类似的,找出每个函数参数,可以改写反汇编:

004012b7 6844214000 	push 	"kernel31.dll" 
004012bc ff1500204000	call 	LoadLibraryW 
004012c2 6860214000 	push 	"GetCurrentProcessId" 
004012c7 50 			push 	eax 
004012c8 ff1504204000 	call 	GetProcAddress 
004012ce ffd0 			call 	eax 
004012d0 8b4d08 		mov 	ecx,dword ptr [ebp+8] 运行停止处 
004012d3 0fb7c6 		movzx 	eax,si

于是我们发现了 bug 的原因:

  • LoadLibraryW 函数的参数为 kernel31.dll,但实际上系统中没有 kernel31.dll 这个 DLL 文件,因此 LoadLibraryW 函数会调用失败
  • 到这里程序还没有崩溃,但后面的 GetProcAddress 函数也会调用失败
  • 随后,失败的 GetProcAddress 函数返回了 00000000,于是 call eax 时进程就异常终止了

这两小节就是用内存转储文件来看内容

二、防止软件被别人分析

1、反调试技术

这里说的反调试技术,主要是检测是否挂载了调试器,以及书里只给出了几种技术,并没有做解释

最初级的一种反调试技术是 IsDebuggerPresent: 一种能够检测是否挂载了调试器的 API 函数,通过返回值是否为 0 可以判断调试器的挂载状态

#include <Windows.h> 
#include <stdio.h> 
int main()  
	if(IsDebuggerPresent()) 
		// 在调试器上运行 
		printf("on debugger\\n"); 
	else
		// 在调试器上不运行 
		printf("not on debugger\\n"); 
	
getchar(); 
return 0; 

此外还有API函数,如CheckRemoteDebuggerPresent

BOOL WINAPI CheckRemoteDebuggerPresent( 
	_In_ HANDLE hProcess, 
	_Inout_ PBOOL pbDebuggerPresent 
);

还有其他技术,如用 popf 和 SINGLE_STEP 异常来检测调试器的方法:当返回值为 0 时为正常,为 1 则表示挂载了调试器

__declspec(naked) int __stdcall antidebugger1(void)  
	__asm
		pushad 
		push ok 
		push dword 
		ptr fs:[0] 
		mov dword 
		ptr fs:[0], esp 
		mov buff, esp 
		push 100h 用push将100h入栈 
		popf 用pop将100h取出至标志 
		jmp error 
ok: 
		mov esp, buff 
		pop dword 
		ptr fs:[0] 
		add esp, 4 
		popad 
		xor eax, eax 
		ret 
error: 
		mov esp, buff 
		pop dword 
		ptr fs:[0] 
		add esp, 4 
		popad 
		xor eax, eax 
		inc eax
		ret 
	 

这些技术,用反汇编器进行静态分析,找到检测调试器的逻辑(例如调用 IsDebuggerPresent 的地方),就可以轻易破解

2、通过代码混淆来防止分析

如何防止代码被分析呢?有一种方法被称为“混淆”

例子:
调用 IsDebuggerPresent 的部分,其机器语言代码为FF 15 00 20 40 00 85 C0 74 17(截止到 jz 指令)

00401000 					main proc near 
00401000 FF 15 00 20 40 00 		call 	ds:__imp__IsDebuggerPresent@0 
00401006 85 C0 					test 	eax, eax 
00401008 74 17 					jz 		short loc_401021

如果我们在前面增加一个 EB,即变成 EB FF 15 00 20 40 00 85 C0 74 17,在 IDA 中显示出的代码就会变成下面这样

00401000 				main: 
00401000 EB FF 				jmp short near ptr main+1 
00401002 15 00 20 40 00 	adc eax, offset __imp__ 
IsDebuggerPresent@0 
00401007 85 C0 				test eax, eax 
00401009 74 17 				jz short loc_401022

可以看到,这里的指令变成了 jmp、adc、test、jz,而 call 指令消失了,然而这段机器语言的实际功能却没有发生变化,因为 EB FF 相当于向前跳转 1 个字节,也就是跳转到 00401001

这里的关键点在于 00401001 处的 FF,它可以当作前面 jmp 指令的一部 分,也可以当作后面 call 指令的一部分。而 IDA 会从前往后按顺序进 行反汇编,因此显示出的代码可能会和实际执行的代码不同

这就是个简单的混淆

3、将可执行文件进行压缩

打包器(packer):能够将可执行文件进行压缩,压缩后得到的文件依然可以直接运行

原理:将原本可执行文件中的代码和数据进行压缩,然后将解压缩用的代码附加在前面;运行的时候先将原本的可执行数据解压缩出来,然后再运行解压缩后的数据

常见打包器:

例子:

#include <Windows.h>
#include <stdio.h>

int main(int argc, char *argv[])

	if(argc < 2)
		fprintf(stderr, "$packed.exe <password>\\n");
		return 1;
	
	if(IsDebuggerPresent())
		// 在调试器上运行
		printf("on debugger\\n");
		return -1;
	else
		// 未在调试器上运行
		if(strcmp(argv[1], "unpacking") == 0)
			printf("correct!\\n");
		else
			printf("auth error\\n");
			return -1;
		
	
	getchar();
	return 0;

  • 首先它会调用 IsDebuggerPresent 检测调试器是否存在
  • 然后,如果向程序传递的参数为 xxxxxxxxx 这个字符串,则显示 correct!,否则显示 auth error

编译之后IDA看



可以看到逻辑非常清晰

但是UPX打包后,就看不懂了;即使用二进制编辑器打开可执行文件,我们也无法找到 correct!、auth error 等字符串

4、将压缩过的可执行文件解压缩:解包

对应的,解包器(unpacker)将用打包器压缩的可执行文件解压缩 ,比如UPX有-d命令解包

手动解包:用调试器和反汇编器跟踪可执行文件解压缩的逻辑,并将位于内存中的解压缩后的可执行数据导出到文件

  • 关键是“找到解压缩程序结束的瞬间(位置)”
  • 将打包器添加的用于解压缩的那部分代码在 OllyDbg 上运行,然后将解压缩到内存中的可执行数据用 OllyDump 转储到文件中

结语

主要是介绍了内存修改、内存转储、反调试技术、混淆技术、打包和解包技术
都很粗浅,可以发现这本书的内容就是浅尝辄止

以上是关于游戏逆向FPS游戏玩家对象数据分析的主要内容,如果未能解决你的问题,请参考以下文章

UE4/UE5引擎 FPS游戏逆向工程

《有趣的二进制:软件安全与逆向分析》读书笔记:在射击游戏中防止玩家作弊

《有趣的二进制:软件安全与逆向分析》读书笔记:在射击游戏中防止玩家作弊

《有趣的二进制:软件安全与逆向分析》读书笔记:在射击游戏中防止玩家作弊

x86游戏逆向之实战游戏线程发包与普通发包的逆向

游戏逆向-D3D9绘制