逆向一个老的双升扑克游戏,并完成外挂
逆向一个老的双升扑克游戏,并完成外挂
【文章作者】: cdanlover
【软件名称】: 古今大战80分
【加壳方式】: 无
【保护方式】: Name/Serial
【编写语言】: Delphi
【使用工具】: peid、DeDeDark、RadASM、MASM32、OD、WINDOWS计算器
【操作平台】: WINXP
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
好久没有逆向过程序了,手都有点生了,今天有空就找了一个简单的来练练手,原来写过这个游戏的注册机,算法挺简单,估计软件设计也不会太复杂,于是就有了这篇笔记。
这个游戏是用Delphi编程的,于是找来DeDeDark对其反了一下,很容易找到菜单的调用过程地址:
Caption = 开局(K)
OnClick = MIShuffleClick 00464A58
Caption = 摸牌(M)
OnClick = MISettleClick 00464A60
Caption = 扣牌(D)
OnClick = MIDiscardClick 00464A70
Caption = 出牌(C)
OnClick = MIShowClick 00464A78
Caption = 作弊模式
OnClick = MIShowCardClick 004649FC
Caption = 察看
Caption = 底牌
OnClick = N14Click 00466CAC
首先是对“开局”进行跟踪的,发现在发牌的过程中,在寄存器中反复出现同一个地址,我就想到有可能是程序申请到的一块内存的开始地址,用来存放牌面信息,在我的办公电
脑上是00B53250,后来在另一台电脑上跟踪时发现这个地址变了,于是就想找出是什么时间开始取的这块内存,发现在程序一开始就进行了申请:
004678E4 > $ 55 push ebp
004678E5 . 8BEC mov ebp,esp
004678E7 . 83C4 F4 add esp,-0C
004678EA . B8 1C774600 mov eax,0046771C
004678EF . E8 B8DEF9FF call 004057AC ; 申请内存
004678F4 . A1 408E4600 mov eax,dword ptr ds:[468E40]
004678F9 . 8B00 mov eax,dword ptr ds:[eax]
004678FB . E8 746AFCFF call 0042E374
00467900 . 8B0D 348D4600 mov ecx,dword ptr ds:[468D34] ; 古今大战.00469980
00467906 . A1 408E4600 mov eax,dword ptr ds:[468E40]
0046790B . 8B00 mov eax,dword ptr ds:[eax]
0046790D . 8B15 74614500 mov edx,dword ptr ds:[456174] ; 古今大战.004561B4
00467913 . E8 746AFCFF call 0042E38C ; 在call之前,内存已申请好,在call过后,地址指针等已经分配好了,开始出界面
00467918 > . A1 408E4600 mov eax,dword ptr ds:[468E40]
0046791D . 8B00 mov eax,dword ptr ds:[eax]
0046791F . E8 F46AFCFF call 0042E418 ; 已完全运行
就开始找这个00B53250,最早出现的地方,是在
mov ecx,dword ptr ds:[468D34]
[468D34] 保存一个地址:00469980,程序正常运行后,在[00469980]保存了基址:------>00B53250
在00467918处下条件记录断点,可直接定位到分配的内存基址。
在后来的分析中,发现以这个基址有很多可能关注的地方:
+F34 [00B54184] 代表本局中出的第几张牌。
+4E4 [00B53734] 比较是否为庄家,4为庄家,1为最后出牌(分析结果:1代表左边的,2代表对家,3代表右边的,4代表玩家自己)
一开始没有找到存放牌信息的地方,后来就对“扣牌”过程进行分析,其中有段:
004641E8 |. B8 14000000 mov eax,14 ; 20个元素
004641ED |. 8D55 B0 lea edx,[local.20] ; 初始化一个数组,用于保存出的牌是手中的第几张牌
004641F0 |> 33C9 /xor ecx,ecx
004641F2 |. 890A |mov dword ptr ds:[edx],ecx
004641F4 |. 83C2 04 |add edx,4
004641F7 |. 48 |dec eax
004641F8 |.^ 75 F6 jnz short 004641F0
004641FA |. B8 01000000 mov eax,1
004641FF |> 8B9483 4C0400>/mov edx,dword ptr ds:[ebx+eax*4+44C]
00464206 |. 8B52 34 |mov edx,dword ptr ds:[edx+34]
00464209 |. 8B8B 18030000 |mov ecx,dword ptr ds:[ebx+318]
0046420F |. 83E9 0A |sub ecx,0A
00464212 |. 3BD1 |cmp edx,ecx
00464214 |. 75 05 |jnz short 0046421B
00464216 |. 46 |inc esi
00464217 |. 8944B5 AC |mov dword ptr ss:[ebp+esi*4-54],eax ; 一个数组,记录出的第几张牌
0046421B |> 40 |inc eax
0046421C |. 83F8 1A |cmp eax,1A ; 与26比较,每个玩家手中25张牌
0046421F |.^ 75 DE jnz short 004641FF
发现在mov edx,dword ptr ds:[ebx+eax*4+44C]这个地方计算是哪张牌,查看这块内存发现挺有规律的,有很多4SD开始的,继续向上翻,找到第一个4SD的单元,重新跟踪,对其
下内存写入断点,就找到了这块保存牌面信息的地方,当eax=1时,即可以算出玩家手中第一张牌保存的地方了,每个双字保存一个地址指针,指向每一张牌:
左手边玩家的:
00B53574 94 99 B5 00 70 BA B5 00 50 BC B5 00 48 BE B5