今天有业务,需要分析某棋牌游戏房间内的座位内存。
目标:通过内存判断每个座位上是否有玩家。所以,要找到每个桌子、每个座位相关内存的排布规律及其内存基址。
游戏大厅截图:
首先,查找座位上玩家信息的内存地址。查找方法:坐到一个座位上,任何用CE搜索大于0的内存(4字节),然后离开座位,再搜值为0的内存。然后重复刚才的2个步骤,期间也可以换个座位坐下,依然搜值为0的内存。最后找到一个内存值,自己坐到那个座位会变为自己的金币值,离开座位变为0。预期中指向玩家数据内存地址的值却找不到。看来这个棋牌大厅的内存数据设计和其他棋牌不一样。暂无其他办法,先分析找到的这个内存。观察后发现,每桌5个座位,该内存块连续存储5个玩家的金币值,每个座位的金币值内存间隔为8。然后测试邻桌的内存,发现每桌内存间隔为0x80。暂时记下这2个数据。
然后查看是什么代码访问了此内存,并把找到的代码及地址复制下来保存:
观察发现第一处代码坐下时会执行1次,第2、3处代码离座时会分别执行1次。
接下来关掉CE, 打开OD,附加游戏进程。先定位到第一处代码查看。
可以看到,座位金币值的内存地址EAX来自他上面的那个call:call ClassicS.02ECA330
在这个CALL上下断,然后尝试坐不同的座位,发现此call上面的push ecx是代表座位号,ESI+0x68是桌子地址。接下来的任务是找到ESI来源,找到第一个桌子的内存基址,其他所有桌子地址就都有了,因为之前已发现每个桌子内存间隔为0x80。
逐行向上翻阅代码,查找ESI来源,找到如下结果:
ESI来源为:02ECA918 8BF0 mov esi,eax,其中eax来源为它前面的:02ECA90E E8 DDFAFFFF call ClassicS.02ECA3F0。这个call应该就是用来获得桌子内存地址的。在此处下断,如何尝试坐不同桌子、不同座位分析,发现这个call的参数1:push esi就是代表桌号,第一个桌子值为0,后面的桌子递增1。根据经验猜测,其后的mov ecx,ebp是基址,这个call会根据ecx和桌号得出桌子的内存地址。暂且不管这个call内部的计算过程,先找找ebp的来源。同样向上逐行阅读代码,找到ebp的来源:
EBP来源于此处的EDX+0x2FC:
02ECA88A 8DAA FC020000 lea ebp,dword ptr ds:[edx+0x2FC]
EDX来源于此处的ECX:02ECA875 8BD1 mov edx,ecx
接下来查找ECX的来源。此处下断后按CTRL+F9返回到外面一层代码。
发现ECX来自EBX,然后向上找EBX来源,发现:
EBX最终来源为这个call:02EDBDD7 . E8 D4C1FFFF call ClassicS.02ED7FB0。在此处下断,坐不同位置,发现不论坐哪,这个call的2个参数都是固定的:eax=1,ebx=0。
因为这里的关键数据ECX来源是个固定内存地址,所以这里应该就是找到桌子内存基址的关键了。这个call内部的代码比较多,有70多行:
02ED7FB0 /$ 53 push ebx
02ED7FB1 |. 55 push ebp
02ED7FB2 |. 8B6C24 0C mov ebp,dword ptr ss:[esp+0xC]
02ED7FB6 |. 56 push esi
02ED7FB7 |. 57 push edi
02ED7FB8 |. 33FF xor edi,edi
02ED7FBA |. 8BF1 mov esi,ecx
02ED7FBC |. 33DB xor ebx,ebx
02ED7FBE |. 8BFF mov edi,edi
02ED7FC0 |> 8B86 78030000 /mov eax,dword ptr ds:[esi+0x378]
02ED7FC6 |. 85C0 |test eax,eax
02ED7FC8 |. 0F84 A8000000 |je ClassicS.02ED8076
02ED7FCE |. 8B8E 7C030000 |mov ecx,dword ptr ds:[esi+0x37C]
02ED7FD4 |. 2BC8 |sub ecx,eax
02ED7FD6 |. B8 93244992 |mov eax,0x92492493
02ED7FDB |. F7E9 |imul ecx
02ED7FDD |. 03D1 |add edx,ecx
02ED7FDF |. C1FA 05 |sar edx,0x5
02ED7FE2 |. 8BC2 |mov eax,edx
02ED7FE4 |. C1E8 1F |shr eax,0x1F
02ED7FE7 |. 03C2 |add eax,edx
02ED7FE9 |. 3BF8 |cmp edi,eax
02ED7FEB |. 0F83 85000000 |jnb ClassicS.02ED8076
02ED7FF1 |. 8B86 78030000 |mov eax,dword ptr ds:[esi+0x378]
02ED7FF7 |. 85C0 |test eax,eax
02ED7FF9 |. 74 1F |je XClassicS.02ED801A
02ED7FFB |. 8B8E 7C030000 |mov ecx,dword ptr ds:[esi+0x37C]
02ED8001 |. 2BC8 |sub ecx,eax
02ED8003 |. B8 93244992 |mov eax,0x92492493
02ED8008 |. F7E9 |imul ecx
02ED800A |. 03D1 |add edx,ecx
02ED800C |. C1FA 05 |sar edx,0x5
02ED800F |. 8BC2 |mov eax,edx
02ED8011 |. C1E8 1F |shr eax,0x1F
02ED8014 |. 03C2 |add eax,edx
02ED8016 |. 3BF8 |cmp edi,eax
02ED8018 |. 72 06 |jb XClassicS.02ED8020
02ED801A |> FF15 444AF902 |call dword ptr ds:[<&MSVCR80._invalid_p>; msvcr80._invalid_parameter_noinfo
02ED8020 |> 8B86 78030000 |mov eax,dword ptr ds:[esi+0x378]
02ED8026 |. 396C18 34 |cmp dword ptr ds:[eax+ebx+0x34],ebp
02ED802A |. 74 08 |je XClassicS.02ED8034
02ED802C |. 83C7 01 |add edi,0x1
02ED802F |. 83C3 38 |add ebx,0x38
02ED8032 |.^ EB 8C \jmp XClassicS.02ED7FC0
02ED8034 |> 85C0 test eax,eax
02ED8036 |. 74 1F je XClassicS.02ED8057
02ED8038 |. 8B8E 7C030000 mov ecx,dword ptr ds:[esi+0x37C]
02ED803E |. 2BC8 sub ecx,eax
02ED8040 |. B8 93244992 mov eax,0x92492493
02ED8045 |. F7E9 imul ecx
02ED8047 |. 03D1 add edx,ecx
02ED8049 |. C1FA 05 sar edx,0x5
02ED804C |. 8BC2 mov eax,edx
02ED804E |. C1E8 1F shr eax,0x1F
02ED8051 |. 03C2 add eax,edx
02ED8053 |. 3BF8 cmp edi,eax
02ED8055 |. 72 06 jb XClassicS.02ED805D
02ED8057 |> FF15 444AF902 call dword ptr ds:[<&MSVCR80._invalid_pa>; msvcr80._invalid_parameter_noinfo
02ED805D |> 8B96 78030000 mov edx,dword ptr ds:[esi+0x378]
02ED8063 |. 8D0CFD 000000>lea ecx,dword ptr ds:[edi*8]
02ED806A |. 2BCF sub ecx,edi
02ED806C |. 5F pop edi
02ED806D |. 5E pop esi
02ED806E |. 5D pop ebp
02ED806F |. 8D04CA lea eax,dword ptr ds:[edx+ecx*8]
02ED8072 |. 5B pop ebx
02ED8073 |. C2 0400 retn 0x4
02ED8076 |> 5F pop edi
02ED8077 |. 5E pop esi
02ED8078 |. 5D pop ebp
02ED8079 |. 33C0 xor eax,eax
02ED807B |. 5B pop ebx
02ED807C \. C2 0400 retn 0x4
这些代码前面的计算过程乘法+位移什么的, 他的真实意图本菜鸟看不懂。但最后的返回值eax却是固定的来自这几行代码:
02ED805D |> 8B96 78030000 mov edx,dword ptr ds:[esi+0x378]
02ED8063 |. 8D0CFD 000000>lea ecx,dword ptr ds:[edi*8]
02ED806A |. 2BCF sub ecx,edi
02ED806C |. 5F pop edi
02ED806D |. 5E pop esi
02ED806E |. 5D pop ebp
02ED806F |. 8D04CA lea eax,dword ptr ds:[edx+ecx*8]
其中esi就是这个call外面的ecx,edi坐任何桌子任何座位时都固定是0(开头代码xor edi,edi的作用),所以最后的计算结果eax就是[esi+0x378]的值。放到这个call外面看,这个call的返回值=[ecx+0x378]=[[0x2FCBC38]+0x378]。这就是座位金币内存的基址了。再算上下面ebx的来源代码:02EDBDE7 > \8B18 mov ebx,dword ptr ds:[eax],可以确定02ECA90E E8 DDFAFFFF call ClassicS.02ECA3F0这处代码的ecx=[[[0x2FCBC38]+0x378]]+0x2FC,其中的0x2FC来自:02ECA88A 8DAA FC020000 lea ebp,dword ptr ds:[edx+0x2FC]这行。
查看并分析 call ClassicS.02ECA3F0(获取指定桌号的内存地址)内部代码:
02ECA3F0 /$ 83EC 08 sub esp,0x8
02ECA3F3 |. 53 push ebx
02ECA3F4 |. 56 push esi
02ECA3F5 |. 57 push edi
02ECA3F6 |. 8BF9 mov edi,ecx ; 基址=[[[0x2FCBC38]+0x378]]
02ECA3F8 |. 8B77 04 mov esi,dword ptr ds:[edi+0x4] ; 第一个桌子的地址:起始内存地址
02ECA3FB |. 85F6 test esi,esi
02ECA3FD |. 74 12 je XClassicS.02ECA411
02ECA3FF |. 8B4F 08 mov ecx,dword ptr ds:[edi+0x8] ; 最后一个桌子的地址
02ECA402 |. 8B5C24 18 mov ebx,dword ptr ss:[esp+0x18] ; 参数:桌号
02ECA406 |. 8BC1 mov eax,ecx
02ECA408 |. 2BC6 sub eax,esi ; 所有桌子占的内存长度
02ECA40A |. C1F8 07 sar eax,0x7 ; 2^7=128=0x80,除以此数得到桌子数量
02ECA40D |. 3BC3 cmp eax,ebx
02ECA40F |. 77 05 ja XClassicS.02ECA416
02ECA411 |> E8 1ABC0400 call ClassicS.02F16030
02ECA416 |> 3BF1 cmp esi,ecx
02ECA418 |. 55 push ebp
02ECA419 |. 8B2D 444AF902 mov ebp,dword ptr ds:[<&MSVCR80._invalid>; msvcr80._invalid_parameter_noinfo
02ECA41F |. 76 02 jbe XClassicS.02ECA423
02ECA421 |. FFD5 call ebp ; <&MSVCR80._invalid_parameter_noinfo>
02ECA423 |> C1E3 07 shl ebx,0x7 ; 桌号*0x80=要获取的桌号地址内存偏移量
02ECA426 |. 897424 14 mov dword ptr ss:[esp+0x14],esi
02ECA42A |. 03F3 add esi,ebx ; 起始内存+偏移量=要获取的桌号内存地址
02ECA42C |. 3B77 08 cmp esi,dword ptr ds:[edi+0x8] ; 桌号内存和结束内存地址比较
02ECA42F |. 77 05 ja XClassicS.02ECA436
02ECA431 |. 3B77 04 cmp esi,dword ptr ds:[edi+0x4]
02ECA434 |. 73 02 jnb XClassicS.02ECA438
02ECA436 |> FFD5 call ebp
02ECA438 |> 3B77 08 cmp esi,dword ptr ds:[edi+0x8] ; 重复比来比去真鸡儿烦
02ECA43B |. 72 02 jb XClassicS.02ECA43F
02ECA43D |. FFD5 call ebp
02ECA43F |> 5D pop ebp
02ECA440 |. 5F pop edi
02ECA441 |. 8BC6 mov eax,esi ; 这就是返回值=起始内存+桌号*0x80=[[[[0x2FCBC38]+0x378]]+0x2FC+4]+桌号*0x80
02ECA443 |. 5E pop esi
02ECA444 |. 5B pop ebx
02ECA445 |. 83C4 08 add esp,0x8
02ECA448 \. C2 0400 retn 0x4
所以,每个桌子数据的内存地址=[[[[0x2FCBC38]+0x378]]+0x2FC+4]+桌号*0x80
然后回到获取座位内存地址的call:02ECA9BC |. E8 6FF9FFFF call ClassicS.02ECA330 ; 获取座位内存地址,其内部代码是:
02ECA330 /$ 83EC 08 sub esp,0x8
02ECA333 |. 53 push ebx
02ECA334 |. 56 push esi
02ECA335 |. 57 push edi
02ECA336 |. 8BF9 mov edi,ecx ; 数据地址=[[[[0x2FCBC38]+0x378]]+0x2FC+4]+桌号*0x80+0x68
02ECA338 |. 8B77 04 mov esi,dword ptr ds:[edi+0x4] ; 座位起始地址
02ECA33B |. 85F6 test esi,esi
02ECA33D |. 74 12 je XClassicS.02ECA351
02ECA33F |. 8B4F 08 mov ecx,dword ptr ds:[edi+0x8] ; 座位结束地址
02ECA342 |. 8B5C24 18 mov ebx,dword ptr ss:[esp+0x18] ; 参数1:座位号
02ECA346 |. 8BC1 mov eax,ecx
02ECA348 |. 2BC6 sub eax,esi
02ECA34A |. C1F8 03 sar eax,0x3 ; 除以2的3次方:8,得到座位数,每个座位间隔是8
02ECA34D |. 3BC3 cmp eax,ebx
02ECA34F |. 77 05 ja XClassicS.02ECA356
02ECA351 |> E8 DABC0400 call ClassicS.02F16030
02ECA356 |> 3BF1 cmp esi,ecx
02ECA358 |. 55 push ebp
02ECA359 |. 8B2D 444AF902 mov ebp,dword ptr ds:[<&MSVCR80._invalid>; msvcr80._invalid_parameter_noinfo
02ECA35F |. 76 02 jbe XClassicS.02ECA363
02ECA361 |. FFD5 call ebp ; <&MSVCR80._invalid_parameter_noinfo>
02ECA363 |> 897424 14 mov dword ptr ss:[esp+0x14],esi
02ECA367 |. 8D34DE lea esi,dword ptr ds:[esi+ebx*8] ; 座位地址=[起始地址+座位号*8]=[[[[[0x2FCBC38]+0x378]]+0x2FC+4]+桌号*0x80+0x68+4]+座位号*8
02ECA36A |. 3B77 08 cmp esi,dword ptr ds:[edi+0x8]
02ECA36D |. 77 05 ja XClassicS.02ECA374
02ECA36F |. 3B77 04 cmp esi,dword ptr ds:[edi+0x4]
02ECA372 |. 73 02 jnb XClassicS.02ECA376
02ECA374 |> FFD5 call ebp
02ECA376 |> 3B77 08 cmp esi,dword ptr ds:[edi+0x8]
02ECA379 |. 72 02 jb XClassicS.02ECA37D
02ECA37B |. FFD5 call ebp
02ECA37D |> 5D pop ebp
02ECA37E |. 5F pop edi
02ECA37F |. 8BC6 mov eax,esi
02ECA381 |. 5E pop esi
02ECA382 |. 5B pop ebx
02ECA383 |. 83C4 08 add esp,0x8
02ECA386 \. C2 0400 retn 0x4
最后的结果:座位号内存(金币)地址:[[[[[0x2FCBC38]+0x378]]+0x2FC+4]+桌号*0x80+0x68+4]+座位号*8
座位数量:([[[[[0x2FCBC38]+0x378]]+0x2FC+4]+桌号*0x80+0x68+8]-[[[[[0x2FCBC38]+0x378]]+0x2FC+4]+桌号*0x80+0x68+4])/8
其中的固定地址0x2FCBC38是在模块classicskin.dll内的,值为:classicskin.dll+12BC38
至此,找到了确定每个座位上玩家金币的内存基址。其他的玩家信息,例如:昵称、id等,应该有类似的内存分布,改天仔细研究后再来分享。
最新评论