说明

作者学生一个,技术水平有限,而且学习方向也不是破解逆向相关的,有错误的地方还望大佬指点。
教程以交流技术为主要目的,只学技术。

关于教程

有很多东西作者是不懂的,毕竟作者当前水平也是处于[hello world!]级别

学习基础

本篇默认您有 CE 基础,可以将 CE 自带的 Tutorial 轻松破解,文中有很多东西都是用破解 Tutorial 的操作来说明的。

涉及内容

  • 查找数值、查找未知数值,以及相关技巧
  • 指针查找的技巧
  • 代码注入
  • 调试技巧
  • Cheat Table 的一些功能

教程中会夹杂大量的CE使用技巧以及一些破解的常识,这些技巧和常识的作用可能比使用 CE 的熟练度更大(就像电子竞技中的意识与操作一样)。

使用什么游戏?

版权问题

仅仅分享本文章的链接的话,您可以随意分享。
如果要全文转载的话,本文使用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 许可证发布,具体注意事项请阅读协议内容(可以参考此协议的中文翻译)。
转载的话最好在下面回帖告诉我一声。
请注意,本文仍有可能更新,全文转载的话可能需要考虑更新的问题。


所需软件

  1. Ghidra
  2. Cheat Engine 7.2
  3. x64dbg(Scylla Plugin)
  4. Visual Studio
  5. Processhacker
  6. BattlefieldV
  7. ……

基础知识(此处以6.7版本为例,实际应安装7.2)

简介

Cheat Engine:作弊引擎,简称为 CE。

  • 通常用于单机游戏的内存数据修改,可以搜索游戏中的内存数据,并进行修改、锁定等操作
  • 内置调试器,可以进行反汇编调试、断点跟踪、代码注入等诸多高级功能
  • 支持 lua 语言,可以实现自己定义的逻辑功能,而不仅仅是简单的锁定数据。也可以在代码注入的同时注入 lua 插件,使游戏进程与 CE 进程进行交互。 CE 的大部分功能都可以通过 lua 来操作
  • 它还支持 D3D Hook 功能,可以在游戏中显示十字准星,也可以绘制功能菜单,同时也可以对 D3D 的调用栈进行跟踪调试
  • 自带变速功能,通过 Hook 游戏相关函数改变游戏速度
  • 自带了一个 Trainer 的功能,可以将自己在 CE 中实现的功能单独生成一个 exe 文件,并可以使用 lua 创建比默认样式更加复杂的窗体或功能
  • 除了 CE 界面上提供的功能,可以 lua 引擎使用更多隐藏功能,具体就要看帮助文档了

    安装 CE

    (安装过程略)
    Cheat Engine 6.7 在 Windows 10 下的默认安装位置为:C:\Program Files (x86)\Cheat Engine 6.7,可以使用其他安装目录,不过记着把程序放在哪了就行(记不住其实也无所谓…),之后可能要到这个目录去找一些东西。

    使用中文翻译文件

    官方翻译文件的下载链接在文章末尾。
    首先解压到类似 C:\Program Files (x86)\Cheat Engine 6.7\languages\ch_cn 的文件夹
    战地V寻找游戏人物指针解密方法 - 图2
    然后到 CE 的设置中,语言选择 ch_cn
    战地V寻找游戏人物指针解密方法 - 图3
    重启 CE 即可
    战地V寻找游戏人物指针解密方法 - 图4

    打开 CE

    最新版的 CE 有两个主程序,一个 32 位的,一个 64 位的,基本没什么区别,不存在其他一些调试器 32 位无法调试 64 位程序的问题,通常直接通过开始菜单 > Cheat Engine 6.7 > Cheat Engine 6.7 打开就可以了,不必区分 32 位或者 64 位。
    战地V寻找游戏人物指针解密方法 - 图5
    打开 CE 会触发 UAC(User Account Control,用户账户控制),就是用管理员权限打开咯,单纯的搜索内存是不用这个权限的,只有调试的时候某些特殊的功能才可能用到管理员权限,肯定点“是”啊(如果点否的话,你可以顺便关闭这个帖子了…)

    界面简介

    战地V寻找游戏人物指针解密方法 - 图6
    上面是菜单栏,暂时用不到。
    然后有个一直在闪的按钮是进程选择器,旁边两个是打开和保存 CE 的存档文件的按钮,记得随时保存哦,改内存数据很可能导致游戏崩溃的哦,如果调试的话连 CE 也会一起崩溃。
    然后是当前选择的进程的名称。
    下面是一个进度条,显示搜索进度用的。
    右边有个设置按钮。
    然后左边是搜索到的内存地址,右边是搜索的类型、内容、条件、内存区域等等,下面有个↘右下箭头,是把选中的地址添加到下方的区域里。
    下方称之为 Cheat Table(作弊表),就是我们得到的内存地址、数据,以及自己添加的功能等等。
    表格上方三个按钮:显示内存窗口、清空作弊列表、手动添加地址。下方两个按钮:高级选项和附加信息。
    内存窗口用于调试,手动添加地址通常是手动添加带指针的地址,高级选项中储存了一些指令,还可以暂停当前进程,附加信息可以附带一些使用方法、作者信息等等,高级选项中的指令与附加信息会一同保存在存档中。

战地V

你们看到的,教程里写的,都是我真实经历过的,但这不代表我仅仅经历了这些,实际付出的心血至少是描述出来的好几倍。教程里只写了尝试成功了的部分,尝试失败了的话只有继续尝试。

我们要做什么形式的外挂?

游戏的目的是打架,要是能提前知道敌人的位置就可以了嘛。
我目前只研究出来了第一个,而且还不是很完善。
外挂是 1% 的灵感加上 99% 的汗水。
我能想到的辅助方法有这些,你们有什么想法可以自己尝试。

搜索健康值(血量)

首先,我们猜测,储存健康值数值的是 Float 类型。
其次,一般来说满血量时健康值为100.0f,但是我们默认按未知的数值处理。
想偷懒也可以,直接看人物血量数值,游戏UI有提示。
好的开始搜索。
使用 Scan Type: Unknown initial value、Value Type: Float 方式进行 First scan。
然后回到游戏,拿手雷丢到身旁,或让敌人打自己几枪。尽量健康值变化大一些,注意不要死亡,避免因为变化太小,导致 CE 忽略了变化量。
如果血量增加就用 CE 搜索 Increased value。
如果血量减少就用 CE 搜索 Decreased value。
经过了数轮的查询,我这里还剩下 若干个数值。这就是当今的游戏引擎,实在烦人,弄出这一堆没用的中间变量,改了也没用,还得自己手动筛选,你如果研究老游戏的时候根本不会出现这么多数值。
image.png
此处我剩余18个。一般这其中前16个不会是我们所需要的信息。所以我们从后两个排查。
由于190D9DD90与190D9DDB0相差不大所以应该在一个结构体中
此时我们对190D9DD90右键找出什么访问了这个地址
image.png
可以明显的看到血量的偏移是20
RCX=0000000190D9DD70
此时搜索
image.png
现在逐个使用右键找出什么访问了这个地址
image.png
反复再次扫描 让自己减少健康值,升高健康值
image.png
逐个使用右键找出什么访问了这个地址
删除健康值变化后无访问的地址
image.png
image.png
得到如图
image.png

  1. bfv.exe+6B98370 - 40 57 - push rdi
  2. bfv.exe+6B98372 - 48 83 EC 40 - sub rsp,40 { 64 }
  3. bfv.exe+6B98376 - 48 8B 41 58 - mov rax,[rcx+58]
  4. bfv.exe+6B9837A - 48 89 CF - mov rdi,rcx
  5. bfv.exe+6B9837D - 48 85 C0 - test rax,rax
  6. bfv.exe+6B98380 - 0F84 BB020000 - je bfv.exe+6B98641
  7. bfv.exe+6B98386 - 48 8B 08 - mov rcx,[rax]
  8. bfv.exe+6B98389 - 48 85 C9 - test rcx,rcx
  9. bfv.exe+6B9838C - 0F84 AF020000 - je bfv.exe+6B98641
  10. bfv.exe+6B98392 - 48 89 6C 24 60 - mov [rsp+60],rbp
  11. bfv.exe+6B98397 - 48 8D 69 F8 - lea rbp,[rcx-08]
  12. bfv.exe+6B9839B - 48 85 ED - test rbp,rbp
  13. bfv.exe+6B9839E - 0F84 98020000 - je bfv.exe+6B9863C
  14. bfv.exe+6B983A4 - 48 89 5C 24 58 - mov [rsp+58],rbx
  15. bfv.exe+6B983A9 - 48 8B 9D E8020000 - mov rbx,[rbp+000002E8]
  1. mov rax,[rcx+58]
  2. mov rcx,[rax]
  3. lea rbp,[rcx-08] // mov rbp,rcx-08
  4. mov rbx,[rbp+000002E8] // mov rbx,[rcx+000002E0]
  1. void FUN_146b98370(longlong param_1,undefined8 param_2,undefined8 param_3,undefined8 param_4)
  2. {
  3. longlong lVar1;
  4. char cVar2;
  5. longlong lVar3;
  6. code **ppcVar4;
  7. float fVar5;
  8. undefined4 in_XMM6_Da;
  9. undefined4 in_XMM6_Db;
  10. undefined4 in_XMM6_Dc;
  11. undefined4 in_XMM6_Dd;
  12. undefined4 in_XMM8_Da;
  13. float fVar6;
  14. undefined4 in_XMM8_Db;
  15. undefined4 in_XMM8_Dc;
  16. undefined4 in_XMM8_Dd;
  17. float afStackX8 [2];
  18. undefined auVar7 [16];
  19. undefined auVar8 [16];
  20. if (((*(longlong **)(param_1 + 0x58) != (longlong *)0x0) &&
  21. (lVar1 = **(longlong **)(param_1 + 0x58), lVar1 != 0)) && (lVar1 + -8 != 0)) {
  22. ppcVar4 = *(code ***)(lVar1 + 0x2e0);
  23. auVar8 = CONCAT412(in_XMM6_Dd,CONCAT48(in_XMM6_Dc,CONCAT44(in_XMM6_Db,in_XMM6_Da)));
  24. auVar7 = CONCAT412(in_XMM8_Dd,CONCAT48(in_XMM8_Dc,CONCAT44(in_XMM8_Db,in_XMM8_Da)));
  25. if ((ppcVar4 == (code **)0x0) ||
  26. (lVar3 = (**(code **)*ppcVar4)(),
  27. (uint)DAT_144c563aa - (uint)DAT_144c563a8 <
  28. (uint)*(ushort *)(lVar3 + 0x48) - (uint)DAT_144c563a8)) {
  29. ppcVar4 = (code **)0x0;
  30. }
  31. afStackX8[0] = *(float *)(ppcVar4 + 0x22);
  32. lVar3 = *(longlong *)(param_1 + 0x70);
  33. fVar6 = 0.0;
  34. if (afStackX8[0] <= 0.0) {
  35. afStackX8[0] = 0.0;
  36. }
  37. if ((lVar3 != 0) &&
  38. ((((byte)*(undefined4 *)(lVar3 + 0x48) & 0x60) != 0x60 ||
  39. (afStackX8[0] != **(float **)(lVar3 + 0x38))))) {
  40. thunk_FUN_145d5fb60((longlong *)(param_1 + 0x70),afStackX8,1,param_4,auVar7,auVar8);
  41. }
  42. afStackX8[0] = (float)thunk_FUN_1466077f0(ppcVar4);
  43. lVar3 = *(longlong *)(param_1 + 0x80);
  44. if ((lVar3 != 0) &&
  45. ((((byte)*(undefined4 *)(lVar3 + 0x48) & 0x60) != 0x60 ||
  46. (afStackX8[0] != **(float **)(lVar3 + 0x38))))) {
  47. thunk_FUN_145d5fb60(param_1 + 0x80,afStackX8,1,param_4,auVar7,auVar8);
  48. }
  49. afStackX8[0] = (float)thunk_FUN_1466077c0(ppcVar4);
  50. lVar3 = *(longlong *)(param_1 + 0x78);
  51. if (afStackX8[0] <= fVar6) {
  52. afStackX8[0] = fVar6;
  53. }
  54. if ((lVar3 != 0) &&
  55. ((((byte)*(undefined4 *)(lVar3 + 0x48) & 0x60) != 0x60 ||
  56. (afStackX8[0] != **(float **)(lVar3 + 0x38))))) {
  57. thunk_FUN_145d5fb60(param_1 + 0x78,afStackX8);
  58. }
  59. afStackX8[0] = (float)thunk_FUN_1466077c0(ppcVar4);
  60. lVar3 = *(longlong *)(param_1 + 0x88);
  61. if ((lVar3 != 0) &&
  62. ((((byte)*(undefined4 *)(lVar3 + 0x48) & 0x60) != 0x60 ||
  63. (afStackX8[0] != **(float **)(lVar3 + 0x38))))) {
  64. thunk_FUN_145d5fb60(param_1 + 0x88,afStackX8);
  65. }
  66. afStackX8[0] = (float)FUN_140970b10(ppcVar4);
  67. lVar3 = *(longlong *)(param_1 + 0x98);
  68. if ((lVar3 != 0) &&
  69. ((((byte)*(undefined4 *)(lVar3 + 0x48) & 0x60) != 0x60 ||
  70. (afStackX8[0] != **(float **)(lVar3 + 0x38))))) {
  71. thunk_FUN_145d5fb60(param_1 + 0x98,afStackX8);
  72. }
  73. lVar3 = *(longlong *)(param_1 + 0x90);
  74. afStackX8[0] = *(float *)((longlong)ppcVar4 + 0x114);
  75. if (*(float *)((longlong)ppcVar4 + 0x114) <= fVar6) {
  76. afStackX8[0] = fVar6;
  77. }
  78. if ((lVar3 != 0) &&
  79. ((((byte)*(undefined4 *)(lVar3 + 0x48) & 0x60) != 0x60 ||
  80. (afStackX8[0] != **(float **)(lVar3 + 0x38))))) {
  81. thunk_FUN_145d5fb60((longlong *)(param_1 + 0x90),afStackX8);
  82. }
  83. afStackX8[0] = *(float *)(ppcVar4 + 0x24);
  84. lVar3 = *(longlong *)(param_1 + 0xb0);
  85. if ((lVar3 != 0) &&
  86. ((((byte)*(undefined4 *)(lVar3 + 0x48) & 0x60) != 0x60 ||
  87. (**(float **)(lVar3 + 0x38) != afStackX8[0])))) {
  88. thunk_FUN_145d5fb60((longlong *)(param_1 + 0xb0),afStackX8,
  89. CONCAT71((uint7)(uint3)((uint)afStackX8[0] >> 8),1));
  90. }
  91. cVar2 = thunk_FUN_1466ba870(lVar1 + -8);
  92. if (cVar2 == '\0') {
  93. fVar5 = (float)(**(code **)(*ppcVar4 + 0x170))(ppcVar4);
  94. afStackX8[0] = (float)(**(code **)(*ppcVar4 + 0x178))(ppcVar4);
  95. afStackX8[0] = fVar5 / afStackX8[0];
  96. }
  97. else {
  98. afStackX8[0] = 1.0;
  99. }
  100. lVar1 = *(longlong *)(param_1 + 0xa0);
  101. if (afStackX8[0] <= fVar6) {
  102. afStackX8[0] = fVar6;
  103. }
  104. if (1.0 <= afStackX8[0]) {
  105. afStackX8[0] = 1.0;
  106. }
  107. if ((lVar1 != 0) &&
  108. ((((byte)*(undefined4 *)(lVar1 + 0x48) & 0x60) != 0x60 ||
  109. (afStackX8[0] != **(float **)(lVar1 + 0x38))))) {
  110. thunk_FUN_145d5fb60(param_1 + 0xa0,afStackX8,1);
  111. }
  112. }
  113. return;
  114. }

根据尝试 rbp-50应该是局部变量
所以我们将目标锁定到
+2E8或+2E0上,根据老外的命名我们暂且将
跑到ghidra 可以看到ppcVar4 = (code **)(lVar1 + 0x2e0);
现在知道本地健康血量数值=[190D7A4B0+2E8]+20=[190D7A4B8+2E0]+20
继续搜索190D7A4B0
image.png
有点多换190D7A4B8
image.png

先从地址长的分析 发现没有访问
因此我们将目标锁定在B4AB06E0
image.png
找到计数次数最多的
image.png
RAX=[[RCX+00001D50]]

  1. longlong GetClientSoldier(longlong param_1)
  2. {
  3. longlong lVar1;
  4. if ((*(longlong **)(&DAT_00001d50 + param_1) != (longlong *)0x0) &&
  5. (lVar1 = **(longlong **)(&DAT_00001d50 + param_1), lVar1 != 0)) {
  6. return lVar1 + -8;
  7. }
  8. return 0;
  9. }

image.png
RCX=0000000102E4C050
未完待续。。。。
现在找到了本地健康血量数值=[[[ClientPlayer+1D48]]+2E8]+20=[[[102E4C050+1d50]]+2e0]+20

搜索谁改写了ClientPlayer

首先,由于ClientPlayer在人物活动状态下,一般是不会改变ClientPlayer*的数值。根据习惯猜测
所以,我们用ProcessHack先暂停游戏。
image.png

image.png
image.png

image.png

image.png
image.png
image.png

  1. bfv.exe+1629BA5 - 49 8B 85 E0000000 - mov rax,[r13+000000E0]
  2. bfv.exe+1629BAC - 49 33 44 24 30 - xor rax,[r12+30]
  3. bfv.exe+1629BB1 - 48 89 85 C0020000 - mov [rbp+000002C0],rax
  4. bfv.exe+1629BB8 - 4C 8D 85 C0020000 - lea r8,[rbp+000002C0]
  5. bfv.exe+1629BBF - 48 8D 95 48060000 - lea rdx,[rbp+00000648]
  6. bfv.exe+1629BC6 - 49 8D 4D 10 - lea rcx,[r13+10]
  7. bfv.exe+1629BCA - E8 0178FFFF - call bfv.exe+16213D0
  8. bfv.exe+1629BCF - 41 8B 4D 20 - mov ecx,[r13+20]
  9. bfv.exe+1629BD3 - 49 8B 45 18 - mov rax,[r13+18]
  10. bfv.exe+1629BD7 - 4C 8B 85 48060000 - mov r8,[rbp+00000648]
  11. bfv.exe+1629BDE - 4C 3B 04 C8 - cmp r8,[rax+rcx*8]
  12. bfv.exe+1629BE2 - 74 11 - je bfv.exe+1629BF5
  13. bfv.exe+1629BE4 - 49 8B 48 08 - mov rcx,[r8+08]
  14. bfv.exe+1629BE8 - 48 8B 01 - mov rax,[rcx]
  15. bfv.exe+1629BEB - 4C 8B 00 - mov r8,[rax]
  16. bfv.exe+1629BEE - 49 8B D4 - mov rdx,r12
  17. bfv.exe+1629BF1 - FF D0 - call rax

看到xor应该是加密了
image.png