【逆文作者】moon
【所用工具】flyODBG1.10,eXeScope6.30
【逆向对象】WinXP附带的蜘蛛纸牌游戏spider.exe
【逆文目标】给蜘蛛纸牌增加撤消发牌功能,并对原撤消功能进行改进
【逆文正文】
看到xdkui大侠的《蜘蛛纸牌分析与简单DIY》想要照着做一下。先找到了一个“蜘蛛.exe”,反汇编的结果和他的文章中的不一样,于是自己改了一下。后来又找到一个“spider.exe”,反汇编结果和xdkui大侠的又一样了,可他没有提供修改程序的方法,我试着再改一次吧。
要想撤消发牌,理论上讲就是把发牌后程序所有操作全部撤消。但要想知道程序都做了哪些事情,实在是一件麻烦的事情,所以偷个懒,利用一下xdkui大侠的成果。根据xdkui的贴子《蜘蛛纸牌分析与简单DIY》可知,实现撤消发牌,程序需要做的事情有:
a. 把下一次要发的牌的序号减小10
b. 把发牌次数值减小一
c. 去掉10个链表的最后一个节点
d. 把10个链表的节点数各减一
e. 刷新蜘蛛窗体
要让程序实现这些操作,我们需要做的事情有:
1. 找到一块空白区域,用来存放我们的代码
2. 找到要操作的各数据的存放位置
3. 添加一个菜单项“撤消发牌”
4. 在原窗口过程中增加一个分支,跳到我们的代码处
5. 编写我们的代码,实现上面的a~e
【1. 找空白区】
用flyODBG1.10打开spider.exe后,上下翻看代码区,可知0100FAE8-0100FFFF是一块足够大的空白段,可以放自己的代码。
【2. 找数据位置】
要找到数据的存放位置,没有什么太好的办法,跟踪程序吧。
用eXeScope打开spider.exe,在“资源”-->“菜单”中点开菜单101,可以看到各菜单项的相关信息,“新一轮发牌”的ID为40007,“发牌”的ID为40016。(eXeScope的操作很简单,顺手增加一个菜单项,ID用40008,提示信息写“撤消发牌”。)
在od中下断:bpx RegisterClassW或bpx DefWindowProcW,都可以找到窗口过程。通过分析窗口过程,可知点击“新一轮发牌”或“发牌”后,程序的实质执行体为:
0100692C mov ecx,esi ; Cases 9C47,9C50 of switch 01006884
0100692E call spider.01006604
进入这个call,看到:
0100660A cmp dword ptr ds:[esi+58],5 5是发牌次数的限定值,那么ecx+58就是发牌次数的地址啦
0100660E jge spider.0100680F
01006687 /mov ecx,dword ptr ds:[esi+4]
0100668A |mov ebp,dword ptr ds:[ecx+10]
0100668D |call spider.010070A8
01006692 |mov ecx,dword ptr ds:[esi+4]
01006695 |push 1
01006697 |push ebp
01006698 |call spider.01006F91
0100669D |mov ecx,dword ptr ds:[esi+8]
010066A0 |push 1 ; /Arg3 = 00000001
010066A2 |push ebp ; |Arg2
010066A3 |push ebx ; |Arg1
010066A4 |call spider.01007A75 ; \spider.01007A75
esi中为01010FC8,是窗口句柄,也是数据总的起始点。到[esi+4]、[esi+8]去看看,跟踪一下发牌过程,可知:
1. 01010FC8是数据总的起点,
2. [[01010FC8+4]+10]是将要发的牌的序号,
3. [01010FC8+8]连续存放着十个链表的表头的地址的地址,
4. [[01010FC8+8]+列数*4+28]是十个链表的长度,即每列牌的张数。
5. [01010FC8+58]是已发牌的次数
【3. 改代码】
分析可知下面是根据菜单项ID值执行操作的起始点:
01006884 add eax,FFFF63BE ; Switch (cases 9C42..9C51)
01006889 cmp eax,0F
把01006884一行改为:
01006884 jmp 0100FAE8
以执行我们编写的代码。
【4. 编写代码】
在0100FAE8处编写代码,打算稿如下:
cmp eax,9c48
jnz j1 首先看是否选了菜单项“撤消发牌”,没点这一项,去执行原来的代码
pushad
mov esi,01010FC8
mov ecx,esi
cmp dword ptr [ecx+58],0
jz j2 看是否发过牌,如没发过牌,也不做任何处理
dec dword ptr [ecx+58] 第1件事,发牌次数减1
mov ecx,[ecx+4]
sub dword ptr ds:[ecx+10],0A 第2件事,将要发的牌的序号减10
mov ecx,esi
mov ecx,[ecx+8]
xor edi,edi
j4:
mov ebx,[ecx+edi*4+28]
dec ebx
mov [ecx+edi*4+28],ebx 第3件事,链表节点数减1
mov eax,[ecx+edi*4]
mov eax,[eax]
dec ebx
j3:
mov eax,[eax+8]
dec ebx
jnz j3
xor edx,edx
mov [eax+8],edx 第4件事,链表尾去掉
inc edi
cmp edi,0a
jnz j4
(
push [01010FC8]
call GetMenu
mov edi,eax
push 0
push 9C50
push edi
call EnableMenuItem 后来发现的第6件事,使“发牌”菜单项可用,注解见下文
push 0
push 9C4A
push edi
call EnableMenuItem 后来发现的第7件事,使“撤消”菜单项可用,注解见下文
)
push 1
push 0
push 0
call InvalidateRect 第5件事,刷新蜘蛛窗体
j2:
popad
j1:
sub eax,9C42
jmp 01006889
调试过程中发现,按“call GetMenu”这种格式写,存在到另一台机器上不能用的问题。观察了一下,原来要写输入表中的地址,如“call GetMenu”要写成:call [0100126C]。共用到如下四个函数:
GetMenu 0100126C > . BEEAD377 dd USER32.GetMenu
InvalidateRect 01001184 > . 9DB4D177 dd USER32.InvalidateRect
EnableMenuItem 01001180 > . 3CFCD177 dd USER32.EnableMenuItem
GetSubMenu 010011DC > . 5A35D277 dd USER32.GetSubMenu
【5. 初步完善】
粗略测试,经以上修改后的BUG有:
1. 移动牌后仍可以撤消发牌
2. 发过5次牌后,发牌菜单项不可用,撤消发牌后仍不可用
3. 发牌后撤消功能不可用,撤消发牌后问题仍不能解决
根据这些BUG,可以编程实现如下功能:
a. 移动牌后使“撤消发牌”菜单项不可用
b. 发牌后使“撤消发牌”菜单项可用
c. 撤消发牌后使“发牌”菜单项可用
d. 使撤消发牌后“撤消”功能可用
1. 功能a
因为移动牌后程序会使“撤消”菜单项可用,因此很容易找到实现a的代码所放的位置。下断:bpx EnableMenuItem,移动一张牌后断于:
01003A10 push dword ptr ds:[edi] ; /hWnd
01003A12 call dword ptr ds:[<&USER32.GetMen>; \GetMenu
01003A18 push esi ; /Flags
01003A19 push 9C4A ; |ItemID = 9C4A (40010.)
01003A1E push eax ; |hMenu
01003A1F call dword ptr ds:[<&USER32.Enable>; \EnableMenuItem 断于此
01003A25 cmp esi,96
把01003A18一行改为:
01003A18 jmp 0100FB6F
在0100FB6F处汇编如下代码:
push eax
push 0
push 9C4A
push eax
call EnableMenuItem
pop eax
push 1
push 9C48
push eax
call EnableMenuItem
push [01010FC8]
call DrawMenuBar
push 1
push 0
push 0
call InvalidateRect
jmp 01003A25
2. 功能b
发牌后程序会使“撤消”菜单项不可用,因此很容易找到实现b的代码所放的位置。
0100311A push dword ptr ds:[ecx] ; /hWnd
0100311C and dword ptr ds:[ecx+F0C],0 ; |
01003123 call dword ptr ds:[<&USER32.GetMe>; \GetMenu
01003129 push 1 ; /Flags = MF_BYCOMMAND|MF_GRAYED|MF_STRING
0100312B push 9C4A ; |ItemID = 9C4A (40010.)
01003130 push eax ; |hMenu
01003131 call dword ptr ds:[<&USER32.Enabl>; \EnableMenuItem
01003137 retn
把01003129一行改为:
01003129 jmp 0100FB9E
在0100FB9E处汇编如下代码:
push eax
push 1
push 9C4A
push eax
call EnableMenuItem
pop eax
push 0
push 9C48
push eax
call EnableMenuItem
push [01010FC8]
call DrawMenuBar
push 1
push 0
push 0
call InvalidateRect
jmp 01003137
3. 功能c
看一下程序中发牌5次后改变菜单项使能状态的代码,参考一下方法,然后在0100FAE8处的代码增补上第6件事的代码:
010067AA push dword ptr ds:[esi] ; /hWnd
010067AC call dword ptr ds:[<&USER>; \GetMenu
010067B2 push 1 ; /Flags = MF_BYCOMMAND|MF_GRAYED|MF_STRING
010067B4 push 9C47 ; |ItemID = 9C47 (40007.)
010067B9 mov ebx,eax ; |
010067BB push ebp ; |/Pos
010067BC push ebx ; ||hMenu
010067BD call dword ptr ds:[<&USER>; |\GetSubMenu
010067C3 mov edi,dword ptr ds:[<&U>; |USER32.EnableMenuItem
010067C9 push eax ; |hMenu
010067CA call edi ; \EnableMenuItem
010067CC push 1 ; /Flags = MF_BYCOMMAND|MF_GRAYED|MF_STRING
010067CE push 9C50 ; |ItemID = 9C50 (40016.)
010067D3 push ebx ; |hMenu
010067D4 call edi ; \EnableMenuItem
010067D6 push dword ptr ds:[esi] ; /hWnd
010067D8 call dword ptr ds:[<&USER>; \DrawMenuBar
4. 功能d
粗略跟踪一下点“撤消”后程序都做了什么,从窗口过程看出点“撤消”后的程序分支为:
01006923 mov ecx,esi ; Case 9C4A of switch 01006884
01006925 call spider.01004C15
跟进这个call,试验几次就可以看出[esi+F0C]是移动牌的次数:
01004C15 push esi
01004C16 mov esi,ecx
01004C18 dec dword ptr ds:[esi+F0C] 这里记录着移动牌的次数
01004C1E mov eax,dword ptr ds:[esi+F0C] 取次数
01004C24 lea eax,dword ptr ds:[eax+eax*4] 次数乘以5
01004C27 lea eax,dword ptr ds:[esi+eax*4+354] 数据起点为[esi+354],偏移量为次数*20字节
01004C2E test eax,eax
01004C30 je short spider.01004C5B
再跟进点“发牌”后的代码,改变菜单状态前有个call 0100311A:
01006798 call spider.0100311A
0100679D mov ecx,esi
0100679F call spider.01002D90
010067A4 cmp dword ptr ds:[esi+58],5
010067A8 jnz short spider.010067DE
010067AA push dword ptr ds:[esi] ; /hWnd
010067AC call dword ptr ds:[<&USER32.GetMenu>] ; \GetMenu 改变菜单状态
跟进call 0100311A:
0100311A push dword ptr ds:[ecx] ; /hWnd
0100311C and dword ptr ds:[ecx+F0C],0 ; |
01003123 call dword ptr ds:[<&USER32.GetMenu>] ; \GetMenu
0100311C一行把移动牌次数清零,这里如果为零,撤消操作就无法继续了,因此把这一行nop掉,或把and改为or:
0100311C or dword ptr ds:[ecx+F0C],0 ;
然后在撤消发牌后的代码中加入使“撤消”菜单项可用的代码。
【6. 更改总结】
所有改变后的代码总结如下:
1.
01006884 jmp 0100FAE8
2.
01003A18 jmp 0100FB6F
3.
01003129 jmp 0100FB9E
4.
0100311C or dword ptr ds:[ecx+0F0C],0
5.
0100FAE8 cmp eax,9C48
0100FAED jnz short spider1.0100FB65
0100FAEF pushad
0100FAF0 mov esi,spider1.01010FC8
0100FAF5 mov ecx,esi
0100FAF7 cmp dword ptr ds:[ecx+58],0
0100FAFB je short spider1.0100FB64
0100FAFD dec dword ptr ds:[ecx+58]
0100FB00 mov ecx,dword ptr ds:[ecx+4]
0100FB03 sub dword ptr ds:[ecx+10],0A
0100FB07 mov ecx,esi
0100FB09 mov ecx,dword ptr ds:[ecx+8]
0100FB0C xor edi,edi
0100FB0E mov ebx,dword ptr ds:[ecx+edi*4+28]
0100FB12 dec ebx
0100FB13 mov dword ptr ds:[ecx+edi*4+28],ebx
0100FB17 mov eax,dword ptr ds:[ecx+edi*4]
0100FB1A mov eax,dword ptr ds:[eax]
0100FB1C dec ebx
0100FB1D mov eax,dword ptr ds:[eax+8]
0100FB20 dec ebx
0100FB21 jnz short spider1.0100FB1D
0100FB23 xor edx,edx
0100FB25 mov dword ptr ds:[eax+8],edx
0100FB28 inc edi
0100FB29 cmp edi,0A
0100FB2C jnz short spider1.0100FB0E
0100FB2E push dword ptr ds:[1010FC8] ; /hWnd = NULL
0100FB34 call dword ptr ds:[<&USER32.GetMenu>>; \GetMenu
0100FB3A mov edi,eax
0100FB3C push 0 ; /Flags = MF_BYCOMMAND|MF_ENABLED|MF_STRING
0100FB3E push 9C50 ; |ItemID = 9C50 (40016.)
0100FB43 push edi ; |hMenu
0100FB44 call dword ptr ds:[<&USER32.EnableMe>; \EnableMenuItem
0100FB4A push 0 ; /Flags = MF_BYCOMMAND|MF_ENABLED|MF_STRING
0100FB4C push 9C50 ; |ItemID = 9C50 (40016.)
0100FB51 push edi ; |hMenu
0100FB52 call dword ptr ds:[<&USER32.EnableMe>; \EnableMenuItem
0100FB58 push 1 ; /Erase = TRUE
0100FB5A push 0 ; |pRect = NULL
0100FB5C push 0 ; |hWnd = NULL
0100FB5E call dword ptr ds:[<&USER32.Invalida>; \InvalidateRect
0100FB64 popad
0100FB65 sub eax,9C42
0100FB6A jmp spider1.01006889
0100FB6F push eax
0100FB70 push 0 ; /Flags = MF_BYCOMMAND|MF_ENABLED|MF_STRING
0100FB72 push 9C4A ; |ItemID = 9C4A (40010.)
0100FB77 push eax ; |hMenu
0100FB78 call dword ptr ds:[<&USER32.EnableMe>; \EnableMenuItem
0100FB7E pop eax
0100FB7F push 1 ; /Flags = MF_BYCOMMAND|MF_GRAYED|MF_STRING
0100FB81 push 9C48 ; |ItemID = 9C48 (40008.)
0100FB86 push eax ; |hMenu
0100FB87 call dword ptr ds:[<&USER32.EnableMe>; \EnableMenuItem
0100FB8D push 1 ; /Erase = TRUE
0100FB8F push 0 ; |pRect = NULL
0100FB91 push 0 ; |hWnd = NULL
0100FB93 call dword ptr ds:[<&USER32.Invalida>; \InvalidateRect
0100FB99 jmp spider1.01003A25
0100FB9E push eax
0100FB9F push 1 ; /Flags = MF_BYCOMMAND|MF_GRAYED|MF_STRING
0100FBA1 push 9C4A ; |ItemID = 9C4A (40010.)
0100FBA6 push eax ; |hMenu
0100FBA7 call dword ptr ds:[<&USER32.EnableMe>; \EnableMenuItem
0100FBAD pop eax
0100FBAE push 0 ; /Flags = MF_BYCOMMAND|MF_ENABLED|MF_STRING
0100FBB0 push 9C48 ; |ItemID = 9C48 (40008.)
0100FBB5 push eax ; |hMenu
0100FBB6 call dword ptr ds:[<&USER32.EnableMe>; \EnableMenuItem
0100FBBC push 1 ; /Erase = TRUE
0100FBBE push 0 ; |pRect = NULL
0100FBC0 push 0 ; |hWnd = NULL
0100FBC2 call dword ptr ds:[<&USER32.Invalida>; \InvalidateRect
0100FBC8 jmp spider1.01003137
【6. 遗留问题】
经以上修改,已初步具备撤消发牌的功能,还有遗留问题,比如:在经过类似“发牌”--“移动牌”--“发牌”后,可以撤消发牌两次,结果就不对了;撤消发牌后“撤消”仍不可用,等等。目前我还没有完全修改好这些BUG,先将阶段性成果在第一时间里呈报给大家,其中错误和不足之处希大家批评指正。
附件:spider.rar
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!