周围对象及其属性分析
在网络游戏中,我们角色周围对象数组及其对象的属性,如怪物 ID、名称、坐标、血量、阵营等,是非常重要的数据,如果需要做一个自动攻击的程序,那么这些数据是必不可少的。
我们需要搜索周围对象数组,我们就需要找一个切入点,在我们周围的对象一般都是可以选中的,我们可以使用 CE 工具来搜索人物是否处于被选中状态这个数据,一般来说,当人物被选中,值为人物 ID,没有被选中时值为 0,通过这种方法筛选出人物是否被选中的状态数据。
一、笑傲江湖周围对象及其属性分析(x32)
1 通过硬件断点分析周围对象数组
我们之前寻找血量的的时候有根据自身血量找到了自身的人物结构基址:
[[[15282D8]+24]+8C]
通过比对分析很容易得知,人物是否被选中状态数据=人物基址+19E8,因此可以推断出,此处存放着我们当前选中的npc或物体,当没有选中任何东西时,此处为0,可以得出一个数据偏移:
+19E8:当前被选中的对象(可以通过改写该值来选中特定对象)
因此我们需要看时哪里向此处写入了数据,往上追到存放周围对象的数组,对是否有选中物体虚拟地址下硬件写入断点,在以下代码处断下:
00749406 | 8988 E8190000 | mov dword ptr ds:[eax+19E8],ecx | 1.追ecx来源
0074940C | 8990 EC190000 | mov dword ptr ds:[eax+19EC],edx |
00749412 | 837E 08 00 | cmp dword ptr ds:[esi+8],0 |
007493EA | 8B47 08 | mov eax,dword ptr ds:[edi+8] |
007493ED | 8B0E | mov ecx,dword ptr ds:[esi] | 2.esi==0019EF94
007493EF | 8B98 E8190000 | mov ebx,dword ptr ds:[eax+19E8] |
007493B1 | 8B7424 08 | mov esi,dword ptr ss:[esp+8] | 3.esi为第一个参数
00751ECC | 8D5424 54 | lea edx,dword ptr ss:[esp+54] | 5.edx为临时变量
00751ED0 | 52 | push edx | 4.
00751E79 | 8BCF | mov ecx,edi | 7.
00751E7B | 894C24 54 | mov dword ptr ss:[esp+54],ecx | 6.
00751DAB | 8BBD 40010000 | mov edi,dword ptr ss:[ebp+140] | 8.ebp==640A8870,不断变化
00751DB1 | 8B9D 3C010000 | mov ebx,dword ptr ss:[ebp+13C] |
00751DB7 | 83F8 03 | cmp eax,3 |
00751DBA | 8B85 44010000 | mov eax,dword ptr ss:[ebp+144] |
追到此处,我们发现写入我们人物结构中当前选中对象的值是来自该 ebp
偏移 140
的位置,而且选中不同的对象,ebp
值也不同,我们推测这个 ebp
值就是对象基址,于是我们用 CE 的分析数据/遍历
功能填入 640A8870
查看,很容易发现 x、z、y 的偏移都是相同的,而且当怪物移动的时候,它的坐标也会发生变化,因此我们可以断定这个 ebp
其实是用来临时存储选中的对象地址的。
为什么不同物体(角色、npc、怪物、其他可选物体等)它们的 x、z、y 坐标偏移是相同的呢?其实是因为在编写游戏的时候这些种类都是继承自同一个顶级的物品类,对于一些通用的属性(比如坐标、血量、名称等)他们都是继承自同一个父类的,因此偏移也是一样,一般来说各种物体结构的前段部分属性由于是继承而来的,因此偏移相同,而每个物体各自的特性则是属于自己这个类的,因此偏移会有所不同。
既然知道了 ebp
存放的是所选物体的地址,那么我们需要继续追踪这个 ebp
的值来源于哪里:
00751D45 | 8B6C24 74 | mov ebp,dword ptr ss:[esp+74] | 9.ebp为第6个参数
007540DF | 8B57 40 | mov edx,dword ptr ds:[edi+40] | 11.edi==3ABA1A18
007540E2 | 8B4424 10 | mov eax,dword ptr ss:[esp+10] |
007540E6 | 8B4C24 14 | mov ecx,dword ptr ss:[esp+14] |
007540EA | 52 | push edx | 10.
007540EB | 50 | push eax |
007540EC | 51 | push ecx |
007540ED | 8D5424 2C | lea edx,dword ptr ss:[esp+2C] |
007540F1 | 52 | push edx |
007540F2 | 53 | push ebx |
007540F3 | 55 | push ebp |
007540F4 | 8BCE | mov ecx,esi |
007540F6 | E8 45DCFFFF | call xajh.751D40 |
追到这里 edi
的值固定不变了,而 [edi+40]
中保存的物体地址不断变动,由于我们不是追对象是否被选中状态的基址,所以不需要往上继续追 edi
了,而是要看哪里向 [edi+40]
写入了对象地址,这点非常的重要!!!
我们继续对 edi+40
下硬件写入断点后发现,当我们鼠标移动到游戏界面的时候就会断下,而断下后 [edi+40]
里面的值也没有变动,说明当鼠标没有指向物体的时候是一直往 [edi+40]
里面写入 0,我们可以将鼠标放到 npc 身上,然后一直 F9
跳过断点,直到 [edi+40]
里面写入了 npc 的值:
在准备对 edi+40 下硬件写入断点的时候发现,当鼠标指向物体的时候,[edi+40] 会存放物体地址,而当鼠标移动开时,[edi+40] 会被清零,但经过测试,只有当鼠标点击物体后,该物体的 ID 才会被存进我们角色的当前选中物体值里面。

然后查看断点处代码:
007AFE68 | 8B4C24 5C | mov ecx,dword ptr ss:[esp+5C] |
007AFE6C | 8B41 54 | mov eax,dword ptr ds:[ecx+54] |
007AFE6F | C740 18 0A000000 | mov dword ptr ds:[eax+18],A | A:'\n'
007AFE76 | 8B51 54 | mov edx,dword ptr ds:[ecx+54] |
007AFE79 | 895A 38 | mov dword ptr ds:[edx+38],ebx | 12.ebx为人物结构地址
007AFE7C | 8B93 40010000 | mov edx,dword ptr ds:[ebx+140] |
007AFE82 | 8B41 54 | mov eax,dword ptr ds:[ecx+54] |
007AFE85 | 8950 20 | mov dword ptr ds:[eax+20],edx |
007AFE88 | 8B93 44010000 | mov edx,dword ptr ds:[ebx+144] |
007AFE8E | 8950 24 | mov dword ptr ds:[eax+24],edx |
007AFE91 | 8B41 50 | mov eax,dword ptr ds:[ecx+50] | ecx+50:"脥I"
007AFD25 | 8BD9 | mov ebx,ecx | 13.
007B0168 | 8D4424 2C | lea eax,dword ptr ss:[esp+2C] |
007B016C | 50 | push eax |
007B016D | 8D4C24 24 | lea ecx,dword ptr ss:[esp+24] |
007B0171 | 51 | push ecx |
007B0172 | 51 | push ecx |
007B0173 | D91C24 | fstp dword ptr ss:[esp],st(0) |
007B0176 | 6A 00 | push 0 |
007B0178 | 55 | push ebp |
007B0179 | 8BCE | mov ecx,esi | 14.
007B017B | E8 A0FBFFFF | call xajh.7AFD20 |
我们继续往上追踪 esi 到函数头部:
007AFF10 | 81EC A4000000 | sub esp,A4 |
007AFF16 | 53 | push ebx |
007AFF17 | 55 | push ebp |
007AFF18 | 56 | push esi |
007AFF19 | 8BF1 | mov esi,ecx | 15.此处代码频繁断下,下条件断点 ecx==5DB54198,ecx 的值为怪物基址
下条件断点后,一直断下,说明此处处于一个循环之中,频繁且重复的遍历到 ecx
中的怪物地址,我们下一个软件断点,发现 ecx
的值一直变化,但是值又非常的接近,推断此处的 ecx
为周围对象的地址,说明上层有一个循环在一直传入对象的地址。
而且通过分析发现当鼠标处于游戏窗口内就一直断下,而当鼠标移出游戏窗口就不断,推测当鼠标处于游戏窗口内时,游戏会循环的遍历周围对象,结合前面第 11.
步中的 [edi+40]
可知,此处是循环遍历周围对象并和鼠标位置进行比较,如果鼠标指向对象则将对象地址写入 [edi+40]
,否则将 0 写入 [edi+40]
。
所以我们继续往上层追踪 ecx
:
00617470 | 83EC 10 | sub esp,10 |
00617473 | 8B4424 14 | mov eax,dword ptr ss:[esp+14] |
00617477 | 8B40 20 | mov eax,dword ptr ds:[eax+20] |
0061747A | 53 | push ebx |
0061747B | 55 | push ebp |
0061747C | 56 | push esi |
0061747D | C1E8 02 | shr eax,2 |
00617480 | 8BE9 | mov ebp,ecx |
00617482 | 24 01 | and al,1 |
00617484 | 57 | push edi |
00617485 | 8D7D 14 | lea edi,dword ptr ss:[ebp+14] |
00617488 | 884424 13 | mov byte ptr ss:[esp+13],al |
0061748C | 8BC7 | mov eax,edi | 18.固定值edi==42B3CB54
0061748E | 8B48 08 | mov ecx,dword ptr ds:[eax+8] |
00617491 | 8B70 14 | mov esi,dword ptr ds:[eax+14] |
00617494 | 8BD9 | mov ebx,ecx |
00617496 | 33D2 | xor edx,edx |
00617498 | 8D34B3 | lea esi,dword ptr ds:[ebx+esi*4] | ebx+esi*4:"荄$ "
0061749B | 3BCE | cmp ecx,esi |
0061749D | 885424 12 | mov byte ptr ss:[esp+12],dl |
006174A1 | 894424 14 | mov dword ptr ss:[esp+14],eax |
006174A5 | 895424 1C | mov dword ptr ss:[esp+1C],edx |
006174A9 | 894C24 18 | mov dword ptr ss:[esp+18],ecx |
006174AD | 74 0C | je xajh.6174BB |
006174AF | 8B11 | mov edx,dword ptr ds:[ecx] |
006174B1 | 85D2 | test edx,edx |
006174B3 | 895424 1C | mov dword ptr ss:[esp+1C],edx |
006174B7 | 75 08 | jne xajh.6174C1 |
006174B9 | EB 5B | jmp xajh.617516 |
006174BB | 33C9 | xor ecx,ecx |
006174BD | 894C24 18 | mov dword ptr ss:[esp+18],ecx |
006174C1 | 85C0 | test eax,eax |
006174C3 | 74 08 | je xajh.6174CD |
006174C5 | 85FF | test edi,edi |
006174C7 | 74 04 | je xajh.6174CD |
006174C9 | 3BC7 | cmp eax,edi |
006174CB | 75 60 | jne xajh.61752D |
006174CD | 85C9 | test ecx,ecx |
006174CF | 75 60 | jne xajh.617531 |
006174D1 | 85D2 | test edx,edx |
006174D3 | 74 61 | je xajh.617536 |
006174D5 | 33C0 | xor eax,eax |
006174D7 | 807C24 13 00 | cmp byte ptr ss:[esp+13],0 | 循环开始
006174DC | 8B30 | mov esi,dword ptr ds:[eax] | 17.变动值eax=5DD68DEC 5DD68E7C 5DD69344
006174DE | 74 10 | je xajh.6174F0 |
006174E0 | 8B86 38010000 | mov eax,dword ptr ds:[esi+138] |
006174E6 | 83F8 19 | cmp eax,19 |
006174E9 | 74 2B | je xajh.617516 |
006174EB | 83F8 1C | cmp eax,1C |
006174EE | 74 26 | je xajh.617516 |
006174F0 | 8B5C24 24 | mov ebx,dword ptr ss:[esp+24] |
006174F4 | 8B4B 18 | mov ecx,dword ptr ds:[ebx+18] |
006174F7 | 6A 00 | push 0 |
006174F9 | 51 | push ecx |
006174FA | 8BCE | mov ecx,esi |
006174FC | E8 5F7C1900 | call xajh.7AF160 | 固定值eax=1
00617501 | 84C0 | test al,al |
00617503 | 74 11 | je xajh.617516 |
00617505 | 53 | push ebx |
00617506 | 8BCE | mov ecx,esi | 16.ecx来源于此
00617508 | E8 038A1900 | call xajh.7AFF10 | 固定值eax=00193900
0061750D | 84C0 | test al,al |
0061750F | 74 05 | je xajh.617516 |
00617511 | C64424 12 01 | mov byte ptr ss:[esp+12],1 |
00617516 | 8D4C24 14 | lea ecx,dword ptr ss:[esp+14] |
0061751A | E8 C1369100 | call xajh.F2ABE0 | 固定值eax=0019EE8C 21.[esp+1C]的值在此call被写入
0061751F | 8B5424 1C | mov edx,dword ptr ss:[esp+1C] | 20.edx为临时变量
00617523 | 8B4C24 18 | mov ecx,dword ptr ss:[esp+18] |
00617527 | 8B4424 14 | mov eax,dword ptr ss:[esp+14] | 固定值eax=42B3CB54
0061752B | EB 94 | jmp xajh.6174C1 |
0061752D | 85C9 | test ecx,ecx |
0061752F | 74 A4 | je xajh.6174D5 |
00617531 | 8D42 04 | lea eax,dword ptr ds:[edx+4] | 19.变动值eax=5DD68DEC 5DD68E7C 5DD69344
00617534 | EB A1 | jmp xajh.6174D7 | 循环结束
00617536 | 8B55 4C | mov edx,dword ptr ss:[ebp+4C] |
00617539 | 8B3A | mov edi,dword ptr ds:[edx] |
0061753B | 83C5 48 | add ebp,48 |
0061753E | BB 05000000 | mov ebx,5 |
00617543 | 8BC5 | mov eax,ebp |
00617545 | 3BE8 | cmp ebp,eax |
00617547 | 8B48 04 | mov ecx,dword ptr ds:[eax+4] | [eax+4]:"果\\甩投前冲直线刀光.gfx"
我们追踪发现 17.
处的代码处于循环之中,继续往前追溯发现 eax
的值不来源于 18.
处,而最后改变 eax
值的是第 19.
处,追踪到 edx
被 call xajh.F2ABE0
里面被改变,此时 存放着当前正在遍历的对象地址==[edx+4],继续 call 里面追踪 edx
:
00F2ABE0 | 8BC1 | mov eax,ecx | eax为一个变化堆栈地址,指向的值测试多次不变(其实也是变化的)
00F2ABE2 | 8378 04 00 | cmp dword ptr ds:[eax+4],0 |
00F2ABE6 | 74 47 | je xajh.F2AC2F | [eax+4]==0则退出函数
00F2ABE8 | 57 | push edi | 保存edi寄存器
00F2ABE9 | 8DA424 00000000 | lea esp,dword ptr ss:[esp] |
00F2ABF0 | 8B48 08 | mov ecx,dword ptr ds:[eax+8] |
00F2ABF3 | 85C9 | test ecx,ecx |
00F2ABF5 | 75 23 | jne xajh.F2AC1A | [eax+8]!=0则退出函数
00F2ABF7 | 8340 04 04 | add dword ptr ds:[eax+4],4 |
00F2ABFB | 8B08 | mov ecx,dword ptr ds:[eax] | ecx==[eax]
00F2ABFD | 8B79 14 | mov edi,dword ptr ds:[ecx+14] | [[eax]+14]为数组元素个数
00F2AC00 | 8B50 04 | mov edx,dword ptr ds:[eax+4] | edx==[eax+4]=正在遍历的地址
00F2AC03 | 83C1 08 | add ecx,8 | ecx=[eax]+8
00F2AC06 | 8B09 | mov ecx,dword ptr ds:[ecx] | [[eax]+8]为数组首地址
00F2AC08 | 8D0CB9 | lea ecx,dword ptr ds:[ecx+edi*4] | 25.[ecx+edi*4] 此处ecx为数组首地址,edi为数组元素个数
00F2AC0B | 3BD1 | cmp edx,ecx | 24.edx在遍历的数组地址,ecx为数组最大地址
00F2AC0D | 74 18 | je xajh.F2AC27 | 数组越界则退出函数
00F2AC0F | 8B12 | mov edx,dword ptr ds:[edx] | 23.[edx]里面的edx每次循环+4进行取值
00F2AC11 | 85D2 | test edx,edx |
00F2AC13 | 8950 08 | mov dword ptr ds:[eax+8],edx | 22.[edx+4]存放着当前正在遍历的对象地址,追溯edx
00F2AC16 | 75 16 | jne xajh.F2AC2E | 如果[eax+8]取到了对象地址则退出函数
00F2AC18 | EB 05 | jmp xajh.F2AC1F |
00F2AC1A | 8B11 | mov edx,dword ptr ds:[ecx] |
00F2AC1C | 8950 08 | mov dword ptr ds:[eax+8],edx |
00F2AC1F | 8378 08 00 | cmp dword ptr ds:[eax+8],0 |
00F2AC23 | 74 CB | je xajh.F2ABF0 | 如果[eax+8]没有取到对象地址则返回继续取
00F2AC25 | 5F | pop edi |
00F2AC26 | C3 | ret |
00F2AC27 | C740 04 00000000 | mov dword ptr ds:[eax+4],0 |
00F2AC2E | 5F | pop edi |
00F2AC2F | C3 | ret |
跟进来发现是一个循环,由于该函数体非常简短,我们逆向分析到第 25.
步后正向分析该函数体的功能,发现该函数体的作用是不断地从遍历数组并从中取出不为 0 的对象地址,我们分析得出:
[[ecx]+8]:数组首地址
[[ecx]+C]:数组尾地址
[[ecx]+14]:数组个数
分析出来后我们在头部下段获取 ecx
的值,并转到 [ecx]
地址:
0B54EC14 3FC7F5F4 ôõÇ?
0B54EC18 00000000 ....
0B54EC1C 2AADCEE0 àÎ.*
0B54EC20 2AADDAE4 äÚ.*
0B54EC24 00000301 ....
0B54EC28 00000301 ....
继续转到数组首地址 2AADCEE0
,惊奇的发现这个数组竟然全是 0,但是我们之前追踪的对象地址确实是这段代码里面传出来的,那只有一个可能,就是我们数组地址分析错了,往回检查,我们在头部下一个条件断点,果然发现 ecx
所指向的值是会变化的!通过这个变化的值,我们成功的定位到了周围对象数组,至于那个错误的空数组是用来做什么的就不得而知了,当然对我们来说无关紧要。
接下来我们还有一件事情没有完成,那就是追溯该函数头部的 ecx
是哪里来的,用条件断点筛选出正确的 ecx
值往上追踪:
00617511 | C64424 12 01 | mov byte ptr ss:[esp+12],1 |
00617516 | 8D4C24 14 | lea ecx,dword ptr ss:[esp+14] | 26.esp+14为临时变量
0061751A | E8 C1369100 | call xajh.F2ABE0 | 固定值eax=0019EE8C 21.[esp+1C]的值在此call被写入
0061749D | 885424 12 | mov byte ptr ss:[esp+12],dl |
006174A1 | 894424 14 | mov dword ptr ss:[esp+14],eax | 27.eax==43082AFC 包含人物地址数组的结构体地址
006174A5 | 895424 1C | mov dword ptr ss:[esp+1C],edx |
006174A9 | 894C24 18 | mov dword ptr ss:[esp+18],ecx |
00617480 | 8BE9 | mov ebp,ecx | 30.下一个条件断点ecx==43082AE8,上层是虚函数call edx==00617470
00617482 | 24 01 | and al,1 |
00617484 | 57 | push edi |
00617485 | 8D7D 14 | lea edi,dword ptr ss:[ebp+14] | 29.ebp==43082AE8
00617488 | 884424 13 | mov byte ptr ss:[esp+13],al |
0061748C | 8BC7 | mov eax,edi | 18. 28.固定值edi==43082AFC
0061748E | 8B48 08 | mov ecx,dword ptr ds:[eax+8] | ecx:"_G幘fh黾樗u?", eax+8:"砓幕"
00617491 | 8B70 14 | mov esi,dword ptr ds:[eax+14] |
0086C4B0 | E8 2B1FC4FF | call xajh.4AE3E0 | 37.eax来源于此call
0086C4B5 | 85C0 | test eax,eax |
0086C4B7 | 75 05 | jne xajh.86C4BE |
0086C4B9 | 32C0 | xor al,al |
0086C4BB | C2 0800 | ret 8 |
0086C4BE | 53 | push ebx |
0086C4BF | 55 | push ebp |
0086C4C0 | 8B6C24 10 | mov ebp,dword ptr ss:[esp+10] |
0086C4C4 | 56 | push esi |
0086C4C5 | 57 | push edi |
0086C4C6 | 32DB | xor bl,bl |
0086C4C8 | 33F6 | xor esi,esi | esi:&"琅s"
0086C4CA | 8D78 6C | lea edi,dword ptr ds:[eax+6C] | 36 edi==0B930FF4 eax==0B930F88
0086C4CD | 8D49 00 | lea ecx,dword ptr ds:[ecx] | 设置edi==0B930FFC条件断点不会断下
0086C4D0 | B8 01000000 | mov eax,1 | 33.设置edi==0B930FFC条件断点会断下,推测其他地址跳过来的,查看引用
0086C4D5 | 8BCE | mov ecx,esi |
0086C4D7 | D3E0 | shl eax,cl |
0086C4D9 | 85C5 | test ebp,eax |
0086C4DB | 74 18 | je xajh.86C4F5 |
0086C4DD | 8B0F | mov ecx,dword ptr ds:[edi] | 32.edi==0B930FFC
0086C4DF | 85C9 | test ecx,ecx |
0086C4E1 | 74 12 | je xajh.86C4F5 |
0086C4E3 | 8B11 | mov edx,dword ptr ds:[ecx] |
0086C4E5 | 8B4424 14 | mov eax,dword ptr ss:[esp+14] |
0086C4E9 | 8B52 54 | mov edx,dword ptr ds:[edx+54] |
0086C4EC | 50 | push eax |
0086C4ED | FFD2 | call edx | 31.edx==00617470,追踪ecx==43082AE8
0086C4EF | 84C0 | test al,al |
0086C4F1 | 74 02 | je xajh.86C4F5 |
0086C4F3 | B3 01 | mov bl,1 |
0086C4F5 | 83C6 01 | add esi,1 |
0086C4F8 | 83C7 04 | add edi,4 | 35.
0086C4FB | 83FE 09 | cmp esi,9 | 9:'\t'
0086C4FE | 7C D0 | jl xajh.86C4D0 | 34.此处跳过去的,原来是一个循环
0086C500 | 5F | pop edi |
0086C501 | 5E | pop esi | esi:&"p髨"
0086C502 | 5D | pop ebp |
0086C503 | 8AC3 | mov al,bl |
0086C505 | 5B | pop ebx |
0086C506 | C2 0800 | ret 8 |
进入 call 里面继续找 eax
:
004AE3E0 | A1 D8825201 | mov eax,dword ptr ds:[15282D8] | 40.追踪到基址
004AE3E5 | 85C0 | test eax,eax |
004AE3E7 | 74 0B | je xajh.4AE3F4 |
004AE3E9 | 8B40 24 | mov eax,dword ptr ds:[eax+24] | 39.eax==01529788
004AE3EC | 85C0 | test eax,eax |
004AE3EE | 74 04 | je xajh.4AE3F4 |
004AE3F0 | 8B40 0C | mov eax,dword ptr ds:[eax+C] | 38.eax==0BA66940
004AE3F3 | C3 | ret |
004AE3F4 | 33C0 | xor eax,eax |
004AE3F6 | C3 | ret |
组合出包含周围对象数组的结构体基址:[[[[15282D8]+24]+C]+6C+8]+14
+8:周围对象数组首地址
+C:周围对象数组尾地址
+14:周围对象数组总大小
+18:周围对象数组当前元素个数
得到周围怪物数组后,我们根据里面的存放的怪物地址来分析一些怪物的重要属性,如怪物 ID、名称、坐标、血量、阵营等。
1.1 怪物坐标、ID
我们之前分析过人物的相关属性,对比一下很容易得知:
+3C:怪物 x 坐标
+40:怪物 z 坐标
+4C:怪物 y 坐标
+140:怪物 ID(该游戏怪物 ID 为八个字节,将两个四字节数据进行拼接得到)
1.2 怪物名称
但是怪物名称和血量偏移和角色结构不一样,我们首先来分析怪物名称,用 CE 搜索怪物名称的 UNICODE
编码的字节数组,并在尾部添加 00 00
(UNICODE 字符串以这个标识结尾),通过改动确定关键怪物名称,然后我们可以将该值与怪物结构中的值进行比较,虽然没有找到这个关键怪物名称值,但是也找到了三个可以标识怪物名称的偏移:
+898:怪物名称1
+89C:怪物名称2
+8A4:怪物名称3
用 CE 筛选的时候怪物太多且名称大量重复,可以在地图中找 npc,搜索特定的 npc 名称。
1.3 怪物血量
接下来我们继续分析对象的血量偏移,首先通过攻击怪物使得血量减少,然后筛选出一个血量百分比(搜不到具体血量)的虚拟地址,然后对其下访问断点,断下后追踪基址流程如下:
00AEA46C | 8B7E 1C | mov edi,dword ptr ds:[esi+1C] | 1.edi=64 esi==61650328
00AEA440 | 8BF1 | mov esi,ecx | 2.
00A4C147 | 50 | push eax |
00A4C148 | 8D8E 90020000 | lea ecx,dword ptr ds:[esi+290] | 3.esi==61650098
00A4C14E | E8 DDE20900 | call xajh.AEA430 |
00A4C007 | 8BF1 | mov esi,ecx | 4
00E8F6DD | 8BCE | mov ecx,esi | 5.
00E8F4B6 | 8BF1 | mov esi,ecx | 6
00EA11B0 | 8B86 C8000000 | mov eax,dword ptr ds:[esi+C8] | 9.esi==540FE578
00EA11B6 | 8B1C03 | mov ebx,dword ptr ds:[ebx+eax] | 8.ebx==194 eax==5CA87220
00EA11B9 | 8B13 | mov edx,dword ptr ds:[ebx] |
00EA11BB | 8B42 14 | mov eax,dword ptr ds:[edx+14] |
00EA11BE | 8BCB | mov ecx,ebx | 7.
00EA11C0 | FFD0 | call eax |
00EA104E | 8BF1 | mov esi,ecx | 10.
00EE4674 | 8BCE | mov ecx,esi | 11.
00EE459A | 8BF1 | mov esi,ecx | 12.
0088C403 | 8BCE | mov ecx,esi | 13.
0088B81B | 8BF1 | mov esi,ecx | 14.
008C83F9 | 8B8C86 20030000 | mov ecx,dword ptr ds:[esi+eax*4+320] | 15.esi==298F2020 eax==1
008C8400 | 8B11 | mov edx,dword ptr ds:[ecx] |
008C8402 | 8B4424 0C | mov eax,dword ptr ss:[esp+C] |
008C8406 | 8B52 20 | mov edx,dword ptr ds:[edx+20] |
008C8409 | 50 | push eax |
008C840A | FFD2 | call edx |
008C83E2 | 8BF1 | mov esi,ecx | 16.
004ACADB | 8B4D 08 | mov ecx,dword ptr ss:[ebp+8] | 17.ebp==0BF16C18
004ACADE | 8B11 | mov edx,dword ptr ds:[ecx] |
004ACAE0 | 8B42 20 | mov eax,dword ptr ds:[edx+20] |
004ACAE3 | 57 | push edi |
004ACAE4 | FFD0 | call eax |
004AC60D | 8BE9 | mov ebp,ecx | 18.
0048A760 | 8B4B 24 | mov ecx,dword ptr ds:[ebx+24] | 19.ebx==xajh.01529788
0048A763 | 50 | push eax |
0048A764 | E8 871E0200 | call xajh.4AC5F0 |
根据以前的多次分析 [15282D8]==01529788,因此组合出当前所选对象血量百分比值:
[[[[[[15282D8]+24]+8]+1*4+320]+C8]+194]+290+1C
1.4 怪物阵营
接下来我们分析阵营,大概有以下几种思路:
- 通过 CE 的数据结构对比功能,将自己、npc 、怪物和其他物体的基址放进去进行对比
- 右键怪物的时候会攻击,而右键 npc 不会攻击,这两个操作最开始触发的肯定是相同的call,在里面会通过阵营判断是否需要攻击,通过这样来找到阵营数据
- 一般游戏按 Tab 键会自动切换周围怪物的选中状态
- 鼠标指向不同阵营的角色会有不同的显示,但是这种鼠标不能保持移动,使用 CE 快捷键来搜索,这种过于麻烦,不推荐使用
1.4.1 通过 CE 的数据结构对比功能分析阵营
我们通过添加两个同阵营的怪物(最好不是同类型的怪物)、和其他阵营的对象(角色、npc、物体等)的基址,通过差异化的思想进行对比,一般阵营数据值很小,而两个怪物是同阵营,其他的阵营各不相同,容易筛选出阵营数据:
由于 CE 对比功能的缺陷,并没有列出所有的偏移对比,此处并不准确,但是也可以作为阵营参考
1.4.2 通过是否能被攻击分析阵营
游戏内,怪物阵营是比较重要的,该阵营能够被我们进行攻击,当我们对一个友方和敌方阵营对象发起相同的操作时,友方阵营对象会攻击失败,因此在发送攻击封包之前,会根据阵营信息对攻击操作进行判断,如果是友方则不发起攻击。
所以我们可以在 send
处下断,然后对敌方阵营发起攻击,一直标记返回(10-15层左右,可能更多),随后依次对每个返回 call 进行下断并攻击友方单位,直到找到能断下的 call 为止。
因为此处不管是攻击友方还是敌方都会断下,在此层函数体里面对攻击对象的阵营进行了判断,如果是友方单位,则不会继续往下调用,自然不会触发断点。
首先在之前找到的主线程发包函数处下断(之前找过),然后右键怪物触发攻击(先左键选中人物),断下后一直返回,直到第 12 层返回:
00752082 | 50 | push eax |
00752083 | 8B4424 3C | mov eax,dword ptr ss:[esp+3C] |
00752087 | 51 | push ecx |
00752088 | 8B4C24 34 | mov ecx,dword ptr ss:[esp+34] |
0075208C | 52 | push edx |
0075208D | 6A 01 | push 1 |
0075208F | 50 | push eax |
00752090 | E8 5BF4FFFF | call xajh.7514F0 | 12
在此处下断点后,右键除了怪物的其他对象也会断下,而 11 层返回及其以前的 call 只有右键怪物触发攻击才会断下,因此推测第 12 层返回的函数体进行了阵营判断,如果是怪物则触发攻击,我们往上找关键跳转代码,看是哪一段代码跳过了第 12 层返回的 call,找到代码如下:
00751FF9 | 83FE 0A | cmp esi,A | A:'\n'
00751FFC | 8B40 08 | mov eax,dword ptr ds:[eax+8] |
00751FFF | 8BB8 24030000 | mov edi,dword ptr ds:[eax+324] |
00752005 | 0F87 F9030000 | ja xajh.752404 | esi>A则跳过攻击代码
0075200B | 0FB68E D8267500 | movzx ecx,byte ptr ds:[esi+7526D8] | ecx==[esi+7526D8]
00752012 | FF248D C8267500 | jmp dword ptr ds:[ecx*4+7526C8] | ecx==0 1 2 3,其中 0 1 3 会跳过攻击call
我们从内存窗口看 esi 的取值范围如下:
通过观察容易得知,ecx 的取值范围为:0,1,2,3,经过测试,ecx==0,1,3 时会跳过攻击 call,而 ecx==2 时表示是怪物,因此会调用攻击 call。
我们就可以通过 ecx
的值来标识怪物的阵营,因此我们需要追溯 ecx
的来源:
0075200B | 0FB68E D8267500 | movzx ecx,byte ptr ds:[esi+7526D8] | 1.ecx==[esi+7526D8],ecx==2,追溯esi==9
00751F6D | 8B5424 48 | mov edx,dword ptr ss:[esp+48] |
00751F71 | 8D4424 28 | lea eax,dword ptr ss:[esp+28] |
00751F75 | 50 | push eax |
00751F76 | 8D8C24 80000000 | lea ecx,dword ptr ss:[esp+80] |
00751F7D | 51 | push ecx |
00751F7E | 8B4C24 1C | mov ecx,dword ptr ss:[esp+1C] |
00751F82 | 55 | push ebp |
00751F83 | 52 | push edx |
00751F84 | 57 | push edi |
00751F85 | E8 667AFFFF | call xajh.7499F0 | 3.eax来源于此call
00751F8A | 8BF0 | mov esi,eax | 2.
00749C02 | 8BC6 | mov eax,esi | 4.鼠标指向对象会断下
00749AAA | 6A 07 | push 7 |
00749AAC | 57 | push edi | 对象基址
00749AAD | 8BC8 | mov ecx,eax |
00749AAF | E8 9CA1FBFF | call xajh.703C50 | 6.追踪eax来源
00749AB4 | 33DB | xor ebx,ebx |
00749AB6 | 3BC3 | cmp eax,ebx |
00749AB8 | 75 1F | jne xajh.749AD9 | eax==0可以攻击 eax==2不可以攻击则跳过
00749ABA | 8B45 08 | mov eax,dword ptr ss:[ebp+8] | ebp==5D635558
00749ABD | 8B88 D8030000 | mov ecx,dword ptr ds:[eax+3D8] | eax==540B97B0 ecx==0
00749AC3 | C1E9 02 | shr ecx,2 |
00749AC6 | 80E1 01 | and cl,1 |
00749AC9 | F6D9 | neg cl |
00749ACB | 1BC9 | sbb ecx,ecx |
00749ACD | F7D9 | neg ecx | ecx:"sqrt"
00749ACF | 83C1 09 | add ecx,9 | ecx:&"p迀"
00749AD2 | 8BF1 | mov esi,ecx | 5.
继续往 call 里面追:
00703E23 | 8B85 50010000 | mov eax,dword ptr ss:[ebp+150] | 11.ebp==对象基址
00703E29 | 8B4C24 10 | mov ecx,dword ptr ss:[esp+10] |
00703E2D | 8B91 50010000 | mov edx,dword ptr ds:[ecx+150] |
00703E33 | 8B4C24 14 | mov ecx,dword ptr ss:[esp+14] |
00703E37 | 50 | push eax | 10.怪物则eax==10 其他eax==8
00703E38 | 52 | push edx | edx==1
00703E39 | E8 0258FFFF | call xajh.6F9640 | 9.怪物则eax==34327001 其他eax==34327100
00703E3E | F6D8 | neg al |
00703E40 | 5F | pop edi |
00703E41 | 5D | pop ebp |
00703E42 | 5B | pop ebx |
00703E43 | 5E | pop esi |
00703E44 | 1BC0 | sbb eax,eax | 8.怪物则eax==343270FF 其他eax==34327100
00703E46 | 83E0 FE | and eax,FFFFFFFE |
00703E49 | 83C0 02 | add eax,2 | 7.怪物则eax==0 其他eax==2
00703E4C | 83C4 08 | add esp,8 |
00703E4F | C2 0800 | ret 8 |
最终发现 11.
处的 ebp
为对象基址,那偏移就很明白了:
+150:对象阵营