某棋牌游戏大厅座位内存分析过程

今天有业务,需要分析某棋牌游戏房间内的座位内存。

目标:通过内存判断每个座位上是否有玩家。所以,要找到每个桌子、每个座位相关内存的排布规律及其内存基址。

游戏大厅截图:

风雷游戏大厅
风雷游戏大厅

首先,查找座位上玩家信息的内存地址。查找方法:坐到一个座位上,任何用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的来源:

桌子内存基址分析:1
桌子内存基址分析:1

EBP来源于此处的EDX+0x2FC:

02ECA88A 8DAA FC020000 lea ebp,dword ptr ds:[edx+0x2FC]

EDX来源于此处的ECX:02ECA875 8BD1 mov edx,ecx

接下来查找ECX的来源。此处下断后按CTRL+F9返回到外面一层代码。

ECX来源
ECX来源

发现ECX来自EBX,然后向上找EBX来源,发现:

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等,应该有类似的内存分布,改天仔细研究后再来分享。

此条目发表在CE, OD, 反汇编分类目录,贴了, , , 标签。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据