CelestialCosmic / themeblog

blog articles by Celestial_Cosmic,source code by chanshiyucx
0 stars 0 forks source link

roguelike 逆向(一) #40

Open CelestialCosmic opened 1 year ago

CelestialCosmic commented 1 year ago

去年用 CE 稍微搞过的游戏,现在拿 IDA 继续熟悉

寻找函数的位置,并对齐内存

太长不看版

如果我没讲明白就慢慢看吧

x64dbg 与 IDA 对齐

  1. 用 x64dbg 转到“符号”页中的程序/链接库,打开后查看开头的部分以获取起始地址,记为 x64Start
  2. 在 x64dbg 中获取代码的内存编号,记为 x64Memory
  3. 偏移量记为 offsetoffset = x64Memory - x64Start
  4. 在 IDA 中获取程序的起始地址,记为 idaStart
  5. 要跳转的地址记为 idaJumpidaJump = idaStart + offset
  6. 在 IDA 中用 g 键跳转到 idaJump

CE 与 IDA 对齐

  1. 在 CE 的 memory viewer 中获取代码的偏移量,记为 offset
  2. 要跳转的地址记为 idaJumpidaJump = 400000 + offset
  3. 在 IDA 中用 g 键跳转到 idaJump

IDA 部分

以一个不知名的函数为例

因为程序 (game.exe) 没有加壳,所以直接拿 IDA 开拆

首先要找出代码段的位置

CE 给的是这样的信息:00004098-game.exe

前面这个值是 PID,每次启动游戏都会刷新;后者即程序名,绝大多数情况不变

又用 CE 发现 game.exe 起始于 00F70000(也可以用 x64dbg 在符号页中发现),这个值每次启动都会改变,虽然一般情况应当起始于 00400000,但这里暂时用不到

在IDA中我需要的函数的第一条汇编.text:005E0950 push ebx,其地址也就是 .text 后面的东西:005E0950

翻到最顶部,又知道了其起始地址为 00401000

必须要注意的地方

IDA 的图表中,一整张图为一个函数,上文斜体部分“我需要的函数的第一条汇编”是指双击 sub_xxxxxx 后的那行汇编所对应的地址

根据如下方式计算:

函数基址 = 动态链接库/游戏程序本体(dll / exe)的起始地址 + 偏移量(offset),代入得到

其在文件中的偏移地址为:005E0950 = 00401000 + offset

打开计算器,算得 offset = 001DF950

x64dbg 部分

打开程序,并附加到 game.exe ,打开“符号页”得到 game.exe 的基址为 00F70000

双击打开,发现其内部的起始地址为 00F71000,取这个值计算

00F71000 + 001DF950 = address

计算得 01150950

与 IDA 不同,x64dbg 用 ctrl + g 来跳转地址

跳转 game.1150950 ,如果发现 IDA 的操作码和寄存器与 x64dbg 中的一致,说明已对齐

push    ebx
mov     ebx, esp
sub     esp, 8
and     esp, 0FFFFFFF0h
add     esp, 4
push    ebp
PUSH EBX
MOV EBX,ESP
SUB ESP,8
AND ESP,FFFFFFF0
ADD ESP,4
PUSH EBP

这一部分对上了,现在又怎么和 CE 对上呢

CE 部分

以血量为例,与前面两个小节使用的例子不同!

CE 里面说得很清楚:game.exe + 8B8F3

前面又提到过,程序的标准启动地址为 400000

这里不管实际启动地址是什么,想要在 IDA 中找到这段代码,一律使用如下方式计算:

400000 + 8B8F3 = address

address = 48B8F3

在 IDA 中 g 键召出跳转,输入 48B8F3

mov [ebx+64],ecx
mov eax,ecx
mov edi,[game.exe+BEB6E0]
test esi,esi
jng game.exe+8B9F8
test edi,edi
mov     [ebx+64h], ecx
mov     eax, ecx
mov     edi, dword_FEB6E0
test    esi, esi
jle     loc_48B9F8
test    edi, edi

jng 和 jle

jng 和 jle 都是表示“小于等于时跳转”,只是其概念不完全相同:

jng:小于等于时跳转,即 jump if not greater 的缩写

jle:不大于时跳转,即 jump if less or equal 的缩写

除了比较的操作码有一点点不同之外,对上了

CE 搜索的结果

之前游玩的时候就已经得出来的东西,虽然不是基址,但至少是固定的

数据描述 内存地址 数据类型
level 012CE4D8(game.exe+BEE4D8) 4 Bytes
mp 012CE5FC(game.exe+BEE5FC) 4 Bytes
hp 012CE4DC(game.exe+BEE4DC) 4 Bytes
ep 012CE608(game.exe+BEE608) 4 Bytes
ep 示数 012CE680(game.exe+BEE680) 4 Bytes

血量追踪

前面的部分,交代了我所发现的地址,现在看看是什么函数导致了血量 (hp) 的下降

CE 选择 "find out what writes to this address"

几番操作,我操作角色碰上了对手,并受到了伤害

CE 显示这几个操作对角色的血量有影响

0055A54E - 89 47 64  - mov [edi+64],eax
0056B8D5 - 89 43 64  - mov [ebx+64],eax
0056B8F3 - 89 4B 64  - mov [ebx+64],ecx
game.exe+A5806 - sub [esi+64],ebx

看操作码就知道第四个最有可能是扣血的操作

用 CE 将操作码删除后,发现如果继续被打,伤害示数存在但是血量不会下降

但是,此时我方再怎么输出也同样不会对对方造成伤害

断定造成伤害的函数是共用的,需要换一个方向使角色无敌,或者注入代码来让我方不受伤害(也就是 CE 教程 step9 的内容)

CE step9 的部分原文与翻译

Often when you've found health of a unit or your own player, you will find that if you remove the code, it affects enemies as well. 你经常会找到我方单位或者你自己的血量,然后发现如果你把伤害输出的代码删了,对面也不受伤害了 In these cases you must find out how to distinguish between your and the enemies objects. Sometimes this is as easy as checking the first 4 bytes (Function pointer table) which often point to a unique location for the player, and sometimes it's a team number, or a pointer to a pointer to a pointer to a pointer to a pointer to a playername. It all depends on the complexity of the game, and your luck 这些情况下,你必须去找出如何分辨敌我双方对象的方式,有时候这事情就很简单,比如找出指向玩家多个属性所在地址的前四个字节(也就是函数指针表的地址),又有些时候,所属方是一个团队代码,或者一个指向玩家名的多级指针,这取决于游戏的复杂度,也有运气的成分在里面 The easiest method is finding what addresses the code you found writes to and then use the dissect data feature to compare against two structures. (Your unit(s)/player and the enemies) And then see if you can find out a way to distinguish between them. 最简单的方式就是去找出哪里的代码写入了你找出来的地址,然后用解析出来的数据特征将我方和敌方所属的两个结构体进行比较,然后看看你能不能找出什么方式来辨识双方 When you have found out how to distinguish between you and the computer you can inject an assembler script that checks for the condition and then either do not execute the code or do something else. (One hit kills for example) 如果你已经找到了能够辨识你和电脑玩家的方式,你可以植入一个汇编脚本并看看情况,或者不修改这部分代码,而是做点别的(比如说让我方一击必杀) Alternatively, you can also use this to build a so called "Array of byte" string which you can use to search which will result in a list of all your or the enemies players 同样地,你也可以用这个方式来构建一个叫做“字节数组”的字符串,一个用来列出所有敌方或者我方的列表,供伤害函数搜索所属方

又通过一些正常的游玩,发现第一个操作码是初始化血量;第二个则是与血量的恢复,包括自然恢复和使用道具的恢复有关;第三个,似乎与使用道具和休息的恢复有关

判断完了操作血量的函数的大意,转到 IDA ,先看看伤害函数

int __thiscall sub_4A5750(int *this, int a2)
{
  int v3; // edi
  int v4; // eax
  int v5; // eax
  int v6; // ecx

  if ( this[7] != 3001 )
    goto LABEL_7;
  if ( dword_FEE444 > *(_DWORD *)(*(_DWORD *)NtCurrentTeb()->ThreadLocalStoragePointer + 4) )
  {
    _Init_thread_header(&dword_FEE444);
    if ( dword_FEE444 == -1 )
    {
      memset(&dword_FEE478, 0, 0x218u);
      sub_479B80();
      atexit(sub_7FB340);
      _Init_thread_footer(&dword_FEE444);
    }
  }
  if ( dword_FEE4BC == 18 )
    v3 = 1;
  else
LABEL_7:
    v3 = 0;
  if ( a2 > 0 )
  {
    v4 = *this;
    this[25] -= a2; //CE 找出了这一行!
    v5 = (*(int (__thiscall **)(int *))(v4 + 64))(this);
    v6 = this[25];
    if ( v6 < v3 )
    {
      this[25] = v3;
      v6 = v3;
    }
    if ( v6 > v5 )
      this[25] = v5;
  }
  return sub_4A5920(a2, 0);
}

大致的逻辑就是先判断这个对象的某个属性码(this[7])是不是什么东西(可能是属于中立方的商人),不是就造成伤害并输出。虽然最后的条件判断没看懂

几番操作(瞎想),伤害函数大概长这个样:

int __thiscall damage(int *targetObject, int output)
{
  int newHealth; // edi
  int v4; // eax
  int newHealth2; // eax
  int oldHealth; // ecx

  if ( targetObject[7] != 3001 )
    goto LABEL_7;
  if ( dword_FEE444 > *(_DWORD *)(*(_DWORD *)NtCurrentTeb()->ThreadLocalStoragePointer + 4) )
  {
    _Init_thread_header(&dword_FEE444);
    if ( dword_FEE444 == -1 )
    {
      memset(&dword_FEE478, 0, 0x218u);
      sub_479B80();
      atexit(sub_7FB340);
      _Init_thread_footer(&dword_FEE444);
    }
  }
  if ( dword_FEE4BC == 18 )
    newHealth = 1;
  else
LABEL_7:
    newHealth = 0;
  if ( output > 0 )
  {
    v4 = *targetObject;                         // v4 是解引用了受到伤害的对象,但是该命名成什么呢?
    targetObject[25] -= output;
    newHealth2 = (*(int (__thiscall **)(int *))(v4 + 64))(targetObject);
    oldHealth = targetObject[25];
    if ( oldHealth < newHealth )
    {
      targetObject[25] = newHealth;             // 这里是将扣血应用到玩家/敌人
      oldHealth = newHealth;
    }
    if ( oldHealth > newHealth2 )
      targetObject[25] = newHealth2;            // 这里似乎是加血
  }
  return printOutput(targetObject, output, 0);
}

这么看,有四种方式无敌:

  1. 让我方成为中立单位
  2. 修改内存,让对面对我方单位造成 0 伤害甚至回血
  3. 一击必杀
  4. 利用回血的函数,每次行动都会大量回复血量

游戏中,中立方在敌方踏入她所属的房间时,会直接消失。所以首先排除第一种

一击必杀不是我想要的效果,pass

buff 与 debuff

因为它的状态栏是一个条,而且没有数值,只能找敌方上/下 buff,用 CE 不停地扫。花了一点点功夫(怪真是赏脸啊),也确实给我找着了我需要的地址和进行操作的代码

game.exe+84855 - movss [edi+00000164],xmm1 ;操作 buff 等级(加减)
game.exe+8AA3C - movss [edi+00000164],xmm1 ;操作 buff 持续时间(加减)

movss 是单精度赋值,与之类似的还有 movsd 用于双精度赋值,由此确定这个状态条的数据类型是一个 float

类推可得类似的状态条都是单精度浮点数,之后扫地址能用得上。

又走了几十步,buff 消失了,追踪的值变成了 0 ,新的代码也出现了

0056AA3C - F3 0F11 8F 64010000  - movss [edi+00000164],xmm1
00564855 - F3 0F11 8F 64010000  - movss [edi+00000164],xmm1
00564551 - F3 0F11 8F 64010000  - movss [edi+00000164],xmm1
00564564 - C6 87 68010000 01 - mov byte ptr [edi+00000168],01
0056AA1E - F3 0F11 8F 64010000  - movss [edi+00000164],xmm1
0056AA28 - C6 87 68010000 00 - mov byte ptr [edi+00000168],00
0056AA49 - C7 87 64010000 00000000 - mov [edi+00000164],00000000

我首先尝试了一下在 CE 中直接修改状态的值,但是 buff 并没有出现,预估可能需要与buff 等级同时有效才会出现,但是我已经没有回头的机会了

IDA 跳转过去,看到的其所对应的代码也验证了我的想法

*(float *)(v4 + 356) = v42;

又花了一个小时边玩边找,我终于发现了 buff 的初始化函数(长代码警告!)

int __cdecl sub_5B2270(
        int a1,
        int a2,
        int *a3,
        int a4,
        int a5,
        int *a6,
        int a7,
        int a8,
        int a9,
        int a10,
        int a11,
        int a12,
        int *a13,
        int a14)
{
  int *v14; // edx
  int *v15; // eax
  float v16; // xmm2_4
  int v17; // ecx
  float v18; // xmm1_4
  int v19; // xmm4_4
  int v20; // xmm5_4
  float v21; // xmm3_4
  float v22; // xmm2_4
  float v23; // xmm1_4
  int v24; // xmm3_4
  int v25; // xmm2_4
  float v26; // xmm0_4
  int *v27; // eax
  float v28; // xmm1_4
  float v29; // xmm6_4
  float v30; // xmm0_4
  float v31; // xmm7_4
  float v32; // xmm1_4
  int v33; // xmm0_4
  float v34; // xmm3_4
  float v35; // xmm2_4
  int v36; // eax
  float v37; // xmm0_4
  int v38; // eax
  int v39; // eax
  int v40; // eax
  int v41; // eax
  int v43; // [esp+20h] [ebp-280h]
  int v44; // [esp+24h] [ebp-27Ch]
  int v45; // [esp+28h] [ebp-278h]
  char *v46; // [esp+30h] [ebp-270h]
  int v47; // [esp+34h] [ebp-26Ch]
  int v48; // [esp+38h] [ebp-268h]
  int v49[3]; // [esp+40h] [ebp-260h] BYREF
  int v50; // [esp+4Ch] [ebp-254h] BYREF
  int v51; // [esp+50h] [ebp-250h] BYREF
  int v52; // [esp+54h] [ebp-24Ch] BYREF
  unsigned int v53; // [esp+58h] [ebp-248h] BYREF
  unsigned int v54; // [esp+5Ch] [ebp-244h]
  int v55[11]; // [esp+84h] [ebp-21Ch] BYREF
  unsigned int v56; // [esp+B0h] [ebp-1F0h] BYREF
  unsigned int v57; // [esp+B4h] [ebp-1ECh]
  int v58[6]; // [esp+DCh] [ebp-1C4h] BYREF
  int v59[4]; // [esp+F4h] [ebp-1ACh] BYREF
  int v60; // [esp+104h] [ebp-19Ch] BYREF
  int v61; // [esp+108h] [ebp-198h] BYREF
  int v62; // [esp+10Ch] [ebp-194h]
  unsigned int v63; // [esp+110h] [ebp-190h]
  unsigned int v64; // [esp+114h] [ebp-18Ch]
  int v65; // [esp+118h] [ebp-188h] BYREF
  int v66[2]; // [esp+11Ch] [ebp-184h] BYREF
  __int128 v67; // [esp+124h] [ebp-17Ch]
  __int128 v68; // [esp+134h] [ebp-16Ch]
  __int128 v69; // [esp+144h] [ebp-15Ch]
  __int128 v70; // [esp+154h] [ebp-14Ch]
  float v71; // [esp+164h] [ebp-13Ch]
  float v72; // [esp+168h] [ebp-138h]
  __int128 v73; // [esp+16Ch] [ebp-134h]
  __int128 v74; // [esp+17Ch] [ebp-124h]
  __int128 v75; // [esp+18Ch] [ebp-114h]
  __int128 v76; // [esp+19Ch] [ebp-104h]
  float v77; // [esp+1ACh] [ebp-F4h]
  float v78; // [esp+1B0h] [ebp-F0h]
  __int128 v79; // [esp+1B4h] [ebp-ECh]
  __int128 v80; // [esp+1C4h] [ebp-DCh]
  __int128 v81; // [esp+1D4h] [ebp-CCh]
  __int128 v82; // [esp+1E4h] [ebp-BCh]
  float v83; // [esp+1F4h] [ebp-ACh]
  float v84; // [esp+1F8h] [ebp-A8h]
  __int128 v85; // [esp+1FCh] [ebp-A4h]
  __int128 v86; // [esp+20Ch] [ebp-94h]
  __int128 v87; // [esp+21Ch] [ebp-84h]
  __int128 v88; // [esp+22Ch] [ebp-74h]
  int v89[4]; // [esp+23Ch] [ebp-64h] BYREF
  float v90; // [esp+24Ch] [ebp-54h]
  float v91; // [esp+250h] [ebp-50h]
  int v92; // [esp+254h] [ebp-4Ch]
  int v93; // [esp+258h] [ebp-48h]
  int v94; // [esp+25Ch] [ebp-44h]
  int v95; // [esp+260h] [ebp-40h]
  int v96; // [esp+264h] [ebp-3Ch]
  float v97; // [esp+268h] [ebp-38h]
  int v98; // [esp+26Ch] [ebp-34h]
  int v99; // [esp+270h] [ebp-30h]
  int v100; // [esp+274h] [ebp-2Ch]
  int v101; // [esp+278h] [ebp-28h]
  float v102; // [esp+27Ch] [ebp-24h]
  float v103; // [esp+280h] [ebp-20h]
  int v104; // [esp+284h] [ebp-1Ch]
  int v105; // [esp+288h] [ebp-18h]
  int v106; // [esp+28Ch] [ebp-14h]
  int v107; // [esp+290h] [ebp-10h]
  int v108; // [esp+294h] [ebp-Ch]
  float v109; // [esp+298h] [ebp-8h]

  v50 = a2;
  v51 = a5;
  v52 = a12;
  memset(v55, 0, sizeof(v55));
  v65 = 0;
  sub_5843E0(0);
  if ( dword_D4AC94 )
    sub_593BD0();
  (*(void (__thiscall **)(int, int, unsigned int *))(*(_DWORD *)a1 + 40))(a1, a1, &v53);
  (*(void (__stdcall **)(int, unsigned int *))(*(_DWORD *)a4 + 40))(a4, &v56);
  if ( a11 )
    (*(void (__stdcall **)(int, int *))(*(_DWORD *)a11 + 40))(a11, v55);
  v14 = a3;
  if ( !a3 )
  {
    v59[2] = v53;
    v14 = v59;
    v59[0] = 0;
    v59[1] = 0;
    v59[3] = v54;
  }
  v15 = a6;
  if ( !a6 )
  {
    v61 = 0;
    v62 = 0;
    v63 = v56;
    v64 = v57;
    v15 = &v61;
  }
  if ( a14 )
  {
    v34 = (float)v56;
    *(float *)v66 = (float)((float)(*(float *)a14 / v34) + (float)(*(float *)a14 / v34)) - 1.0;
    v35 = (float)v57;
    *(float *)&v66[1] = -(float)((float)((float)(*(float *)(a14 + 4) / v35) + (float)(*(float *)(a14 + 4) / v35)) - 1.0);
    v67 = *(_OWORD *)(a14 + 20);
    v68 = *(_OWORD *)(a14 + 36);
    v69 = *(_OWORD *)(a14 + 52);
    v70 = *(_OWORD *)(a14 + 68);
    v71 = (float)((float)(*(float *)(a14 + 84) / v34) + (float)(*(float *)(a14 + 84) / v34)) - 1.0;
    v72 = -(float)((float)((float)(*(float *)(a14 + 88) / v35) + (float)(*(float *)(a14 + 88) / v35)) - 1.0);
    v73 = *(_OWORD *)(a14 + 104);
    v74 = *(_OWORD *)(a14 + 120);
    v75 = *(_OWORD *)(a14 + 136);
    v76 = *(_OWORD *)(a14 + 152);
    v77 = (float)((float)(*(float *)(a14 + 168) / v34) + (float)(*(float *)(a14 + 168) / v34)) - 1.0;
    v78 = -(float)((float)((float)(*(float *)(a14 + 172) / v35) + (float)(*(float *)(a14 + 172) / v35)) - 1.0);
    v79 = *(_OWORD *)(a14 + 188);
    v80 = *(_OWORD *)(a14 + 204);
    v81 = *(_OWORD *)(a14 + 220);
    v82 = *(_OWORD *)(a14 + 236);
    v83 = (float)((float)(*(float *)(a14 + 252) / v34) + (float)(*(float *)(a14 + 252) / v34)) - 1.0;
    v84 = -(float)((float)((float)(*(float *)(a14 + 256) / v35) + (float)(*(float *)(a14 + 256) / v35)) - 1.0);
    v85 = *(_OWORD *)(a14 + 272);
    v86 = *(_OWORD *)(a14 + 288);
    v87 = *(_OWORD *)(a14 + 304);
    v88 = *(_OWORD *)(a14 + 320);
  }
  else
  {
    v16 = (float)v56;
    v17 = a11;
    v18 = (float)v57;
    *(float *)&v19 = (float)((float)((float)*v15 / v16) + (float)((float)*v15 / v16)) - 1.0;
    *(float *)&v20 = (float)((float)((float)v15[2] / v16) + (float)((float)v15[2] / v16)) - 1.0;
    v21 = (float)v15[1] / v18;
    v22 = (float)v15[3] / v18;
    v23 = (float)v53;
    *(float *)&v48 = (float)*v14 / v23;
    *(float *)&v24 = -(float)((float)(v21 + v21) - 1.0);
    *(float *)&v25 = -(float)((float)(v22 + v22) - 1.0);
    v26 = (float)v54;
    *(float *)&v44 = (float)v14[1] / v26;
    *(float *)&v47 = (float)v14[2] / v23;
    *(float *)&v43 = (float)v14[3] / v26;
    if ( a11 )
    {
      v27 = a13;
      if ( !a13 )
      {
        v63 = v55[0];
        v27 = &v61;
        v61 = 0;
        v62 = 0;
        v64 = v55[1];
      }
      v17 = a11;
      v28 = (float)(unsigned int)v55[0];
      v29 = (float)*v27 / v28;
      v30 = (float)(unsigned int)v55[1];
      *(float *)&v45 = (float)v27[2] / v28;
      v31 = (float)v27[1] / v30;
      v32 = (float)v27[3] / v30;
      v33 = v45;
    }
    else
    {
      v32 = 0.0;
      v33 = 0;
      v31 = 0.0;
      v29 = 0.0;
    }
    v89[0] = v19;
    v89[1] = v24;
    v93 = v24;
    v89[2] = v48;
    v94 = v47;
    v89[3] = v44;
    v95 = v44;
    v99 = v25;
    v100 = v48;
    v105 = v25;
    v92 = v20;
    v98 = v19;
    v101 = v43;
    v104 = v20;
    v106 = v47;
    v107 = v43;
    if ( v17 )
    {
      v90 = v29;
      v91 = v31;
      v96 = v33;
      v97 = v31;
      v102 = v29;
      v103 = v32;
      v108 = v33;
      v109 = v32;
    }
    else
    {
      v90 = 0.0;
      v91 = 0.0;
      v96 = 0;
      v97 = 0.0;
      v102 = 0.0;
      v103 = 0.0;
      v108 = 0;
      v109 = 0.0;
    }
  }
  v36 = 96;
  if ( a14 )
    v36 = 288;
  sub_59B360(v36);
  sub_5C8650(dword_E92230, 0, 4, 0, v49, 0);
  if ( a14 )
    sub_590CC0(v49[0], v66, 288);
  else
    sub_590CC0(v49[0], v89, 96);
  sub_5C8860(dword_E92230, 0, 0);
  sub_5C8750(1, &v51, 0);
  sub_5C87D0(0, 1, &v50);
  if ( v52 )
    sub_5C87D0(1, 1, &v52);
  v58[0] = 0;
  v58[1] = 0;
  v58[4] = 0;
  v58[5] = 1065353216;
  v37 = (float)v56;
  *(float *)&v58[2] = v37;
  *(float *)&v58[3] = (float)v57;
  sub_5C8810(1, v58);
  v46 = (char *)&dword_E7CE84 + (a7 != 0 ? 4 : 0);
  sub_5C8790(0, 1, v46);
  if ( a11 )
    sub_5C8790(1, 1, v46);
  sub_5C87F0(dword_E7CE8C);
  sub_5C8730(dword_E7CE90, 0);
  sub_5C8710(*(int *)((char *)&dword_E7CE94 + (a8 != 0 ? 4 : 0)), &unk_EBE398, -1);
  sub_5C85F0(5);
  v38 = dword_E82184;
  if ( a14 )
    v38 = dword_E82188;
  sub_5C85D0(v38);
  v39 = dword_E7CEA4;
  if ( a10 )
    v39 = a10;
  sub_5C87B0(v39, 0, 0);
  v40 = a9;
  if ( !a9 )
  {
    v40 = dword_E7CE9C;
    if ( a14 )
      v40 = dword_E7CEA0;
  }
  sub_5C8A20(v40, 0, 0);
  v41 = 24;
  if ( a14 )
    v41 = 72;
  v60 = v41;
  sub_5C8610(0, 1, &dword_E92230, &v60, &v65);
  sub_5C8530(4, 0);
  ++dword_AF5F4C;
  sub_5C8610(0, 1, &dword_E9E6A8, &dword_E9E6AC, &v65);
  sub_5C8A20(dword_E9E68C, 0, 0);
  sub_5C87B0(dword_E9E698, 0, 0);
  sub_5C85D0(dword_E9E6A4);
  sub_5C85F0(dword_E9E5DC);
  sub_5C8710(dword_E9E2D4, &unk_EBE398, -1);
  sub_5C8730(dword_E9E128, 0);
  sub_5C87F0(dword_E9DFE8);
  sub_5C8790(0, 1, &dword_E928E4);
  if ( a11 )
    sub_5C8790(1, 1, &unk_E928E8);
  sub_59E1D0();
  sub_5C8810(1, &xmmword_E9DFA4);
  sub_5C87D0(0, 1, &dword_E9E3BC);
  if ( v52 )
    sub_5C87D0(1, 1, &unk_E9E3C0);
  return 0;
}

我回忆了一下,会显示 buff 且能够在右上角显示的有大概 10 种,而且有一种来自地形,一种不会随时间下降,因此判断此处的 8 种 buff 均为随时间下降的 buff ,有了这个,获取 buff 相对就没那么抓瞎了,除了变量令人疑惑的 6 个 float ,2 个 int

下一回

因为我暂时没有掌握如何修改,这些东西可能下一次再讲,也可能......没有下次

参考依据

IDA pro与 x64dbg 地址对齐