移花接木:破解闪客精灵 MX 2002演示版
移花接木:破解闪客精灵 MX 2002演示版
为了破解一个Flash动画,下载了几个工具,觉得这个最好,可以
导出所有的资源,包括脚本。可惜是个演示版。从其文档看,如
果要买,会提供另外一个下载URL。估计是个真正的演示版。
安装完后,没有提供任何注册方式。从FileMon,RegMon的结果看,
也没发现什么可疑之处。演示版有何限制?
试用版的限制:
* 30天试用期
* 只能分析动画的前5帧
* 只支持导出每组资源文件中的前2个文件
?!
看来正式版与演示版并无质的不同,只有量的差别,完全可能
做出一个自己的"正式版"。
对于这种需要到处打补丁的程序,最好每做一次patch,就备份一次,
以便需要的时候退回去。下面的文字忽略了退回去的过程。
程序用MFC写的,没有加壳。
1. 解除试用期限制
这个比较简单,从对GetLocalTime的调用就可以找到。也没有对
系统时间回调的检测(应该学学Flexlm)。:-)
.text:0042E3B3 call f_GetDaysUsed
.text:0042E3B8 mov edi, eax ;->xor edi,edi(0x33 0xFF)
.text:0042E3BA test edi, edi
.text:0042E3BC jl short loc_42E3F2
.text:0042E3BE cmp edi, 1Eh ; here,继续试用
关闭对应Nag Screen(还有多少天可用)
.text:00432AAB call sub_47DF8F ;关闭
.text:00432AB0 cmp eax, 1
.text:00432AB3 jz short loc_432B2B ;jmp
2. 解除对动作分析(即脚本)的数目限制
既然上面用edi作比较,先偷个懒,直接在IDA中查找:
cmp edi,2h
找到多处。挑几个可疑的一试,还行:-)
.text:00436737 loc_436737:
.text:00436737 mov ecx, [esi+1Ch]
.text:0043673A push eax
.text:0043673B push 2
.text:0043673D push 110Ah
.text:00436742 push ecx
.text:00436743 inc edi ; <-NOP
.text:00436744 call ebx ; SendMessageA ; 注意,是以SendMessage来做的
.text:00436744 ; 且均用edi做比较
.text:00436746 test eax, eax
.text:00436748 jz short loc_436751
.text:0043674A cmp edi, 2 ; 只能导出前两个动作脚本.
.text:0043674D jl short loc_436737
3.解除只分析影片前五帧限制(实际上右侧CTreeCtrl上几乎每个item
都多了很多东西):-)
用和上一步同样的办法,这次找"cmp edi,5h",没有。转念一想,也
许是在0-4间取值呢?试试"cmp edi,4h",Bingo!运气不错啊。
.text:0041E131 loc_41E131: ; CODE XREF: sub_41DF50+1C5
.text:0041E131 mov edi, [esp+50h+var_4]
.text:0041E135 push ebp
.text:0041E136 lea ecx, [esp+54h+var_38]
.text:0041E13A push edi
.text:0041E13B push ecx
.text:0041E13C mov ecx, esi
.text:0041E13E call sub_415F90
.text:0041E143 inc edi ;不要NOP这句,会导致动画帧资源命名错误
.text:0041E143 ;(总为"frame 1")。这是后面发现问题后改的
.text:0041E144 cmp edi, 4
.text:0041E147 mov [esp+50h+var_4], edi
.text:0041E14B jbe loc_41E688 ;改这里为jmp
不要急于关闭"演示版限制: 只分析影片的前五帧。"和
"演示版限制每类资源中只有前两个资源可以被导出。"
两个Nag Screen。这个程序有用的代码和Nag Screen搅在
一起,修改的位置不当,冒然去掉Nag Screen会造成问题,
而且留着Nag可以帮助寻找线索。等所有的功能实现以后再
去除Nag。
4.解除每种资源只能导出两个文件的限制
这回运气用完了,前面的花招不管用了。得读读代码了。
注意到在导出资源时,如果在用于显示可用资源的树型控件上
只选择前两项之外的item,会报没有选择资源。以此为线索,可
找到下面的代码。共9种资源(矢量图,图片,声音,文字等等),
在导出时对每种资源调用一次。返回值为该类型的资源被
选中的item数。如果没有选前两项,返回0。
函数传入的参数是指向第1级节点(矢量图,图片...)TreeItem的指针。
TreeItem结构中有对应item的显示文本,在调试时可以用来确认判断。
可以看到这里用了硬编码(而不是循环),只会返回0-2间的值。
或许是为了隐蔽,代码在不少地方没有直接使用CTreeCtrl的成员
函数,而是用兵SendMessage的方式来完成对应的操作。
.text:00437E40 f_GetNumOfItems proc near ; CODE XREF: f_GetNumOfEntry+C
.text:00437E40 ; sub_437EE0+32
.text:00437E40
.text:00437E40 arg_nIndex = dword ptr 4
.text:00437E40
.text:00437E40 mov eax, [esp+arg_nIndex]
.text:00437E44 push ebp
.text:00437E45 push esi
.text:00437E46 mov esi, ecx
.text:00437E48 and eax, 0FFh
.text:00437E4D push edi
.text:00437E4E xor ebp, ebp
.text:00437E50 mov edi, [esi+eax*4+60h] ; 这里存的是第1级TREEITEM
.text:00437E50 ; 的句柄(9项)
.text:00437E54 test edi, edi
.text:00437E56 jnz short loc_437E60
.text:00437E58 pop edi
.text:00437E59 pop esi
.text:00437E5A xor eax, eax
.text:00437E5C pop ebp
.text:00437E5D retn 4
.text:00437E60 ; ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
.text:00437E60
.text:00437E60 loc_437E60: ; CODE XREF: f_GetNumOfItems+16
.text:00437E60 push ebx
.text:00437E61 push edi ; 指向第1级的节点:
.text:00437E61 ; 矢量图,图片...
.text:00437E62 mov ecx, esi ; 用__thiscall,this指针从
.text:00437E62 ; ecx传
.text:00437E64 call CTreeCtrl::ItemHasChildren(_TREEITEM *) ; 有子item?
.text:00437E69 test eax, eax
.text:00437E6B jz short loc_437EB7
.text:00437E6D mov ecx, [esi+1Ch]
.text:00437E70 mov ebx, ds:SendMessageA
.text:00437E76 push edi ; lParam
.text:00437E77 push 4 ; wParam
.text:00437E79 push 110Ah ; Msg
.text:00437E79 ;
.text:00437E79 ; 应该是等价于CTreeCtrl::GetChildItem
.text:00437E7E push ecx ; hWnd
.text:00437E7E ;
.text:00437E7E ; 向TreeView控件发消息
.text:00437E7F call ebx ; SendMessageA
.text:00437E81 mov edi, eax ; 这里会返回一个指向子TreeItem
.text:00437E81 ; 的句柄,第1个子item
.text:00437E83 test edi, edi
.text:00437E85 jz short loc_437EB7
.text:00437E87 push edi
.text:00437E88 mov ecx, esi
.text:00437E8A call f_IsItemChecked ; 节点是否被选中
.text:00437E8F test eax, eax
.text:00437E91 jz short loc_437E98
.text:00437E93 mov ebp, 1 ; ebp计数
.text:00437E93 ;
.text:00437E93 ; 可以从这里开始patch
.text:00437E93 ; (最后是在437EAA处patch的)
.text:00437E98
.text:00437E98 loc_437E98: ; CODE XREF: f_GetNumOfItems+51
.text:00437E98 mov edx, [esi+1Ch]
.text:00437E9B push edi ; lParam
.text:00437E9C push 1 ; wParam 这个值与上一个不同
.text:00437E9E push 110Ah ; Msg
.text:00437EA3 push edx ; hWnd
.text:00437EA4 call ebx ; SendMessageA ; 等价于CTreeCtrl::GetNextSiblingItem
.text:00437EA4 ;
.text:00437EA4 ; 查看pTreeItem指向的内容,
.text:00437EA4 ; 两次SendMessage分别(对矢量图
.text:00437EA4 ; 节点)返回"shape1","shape2"
.text:00437EA6 test eax, eax
.text:00437EA8 jz short loc_437EB7
.text:00437EAA push eax ; 指向子TreeItem的句柄,
.text:00437EAA ; 第2个子item
.text:00437EAA ;
.text:00437EAA ; 可见只能输出两项是硬
.text:00437EAA ; 编码实现的,没有用循环
.text:00437EAA ;
.text:00437EAA ; 要加循环代码,这里空间不够
.text:00437EAB mov ecx, esi
.text:00437EAD call f_IsItemChecked
.text:00437EB2 test eax, eax
.text:00437EB4 jz short loc_437EB7
.text:00437EB6 inc ebp ; ebp计数
.text:00437EB6 ;
.text:00437EB6 ; 如果在这里硬返回到
.text:00437EB6 ; 437E98,需要使edi指向
.text:00437EB6 ; 当前item
.text:00437EB7
.text:00437EB7 loc_437EB7: ; CODE XREF: f_GetNumOfItems+2B
.text:00437EB7 ; f_GetNumOfItems+45
.text:00437EB7 ; f_GetNumOfItems+68
.text:00437EB7 ; f_GetNumOfItems+74
.text:00437EB7 pop ebx
.text:00437EB8 pop edi
.text:00437EB9 mov eax, ebp
.text:00437EBB pop esi
.text:00437EBC pop ebp
.text:00437EBD retn 4
.text:00437EBD f_GetNumOfItems endp
这里的代码很明显,直接用了前两个item,不存在循环。的确是个
演示版。我们只要修改一下代码,加上循环,使其访问对应资源item
下的所有item即可。
这里空间有限,只好把代码加到.text section的末尾。从PEditor可以
看到:
.text section:
VirtualSize=0x98A04
RawSize=0x99000
可用空间=RawSize - VirtualSize=0x99000 - 0x98A04=0x5FC
=1532字节,足够了。
VirtualOffset=0x1000
可用的起始地址=ImageBase + VitualOffset + VirtualSize
=0x400000 + 0x1000 + 0x98A04
=0x499A04
对应的RawOffset=0x99A04
在SoftIce中用a命令输入代码,即可得到需要的opcode。需要注意
的是,我一开始从437E98开始改,在输入
push 110Ah
时,在SoftIce中生成的opcode有误,总是把立即数110A作为一个WORD
而不是DWORD来压栈,导致堆栈被破坏而崩溃,不知什么原因。这句不
能用a命令,必须直接编辑内存把opcode输进去。后来干脆把代码改
到437EAA处(即找到该资源下的第2个item后)。
改为:
.text:00437EAA jmp 499A04
在499A04处增加代码:
.text:00499A04 push eax ;保存eax即指向当前item的TreeItem
;结构的指针
push eax
mov ecx, esi
call 426A00
test eax,eax ;是否选中
pop edi ;弹出保存值,下一轮使用
jz 437E98
inc ebp ;增加计数
jmp 437E98
此时当仅选前两项之后的item时,不再报"未选择资源...",上述函数
正确返回了选择的所有资源的总item数目。结束循环的出口为437EA8,即
没有剩余的同级item。
5.解除最后的限制:每类只能输出2个文件
可以从f_IsItemChecked(426A00)入手,每种资源都应该会用这个
函数判断item是否被选中。在IDA中检查其caller,注意附近是否
有类似计数动作的代码。
(4385C0处的函数有点象,实际上只是检查矢量图资源的格式是否
符合"闪客巫师"的要求,下面会用到)。"闪客巫师"为另一个搭配
使用的软件。
很容易地定位到:
.text:00437DA0 f_ExportResource proc near ; CODE XREF: sub_437EE0+8C
.text:00437DA0
.text:00437DA0 arg_0 = dword ptr 4
.text:00437DA0 arg_4 = dword ptr 8
.text:00437DA0
.text:00437DA0 mov eax, [esp+arg_4]
.text:00437DA4 push ebx
.text:00437DA5 push ebp
.text:00437DA6 push esi
.text:00437DA7 mov esi, ecx
.text:00437DA9 and eax, 0FFh
.text:00437DAE push edi
.text:00437DAF mov ecx, [esi+eax*4+84h]
.text:00437DB6 mov edi, [esi+eax*4+60h]
.text:00437DBA test edi, edi
.text:00437DBC lea edx, [ecx+eax*2]
.text:00437DBF mov eax, dword_4B65A0[edx*4]
.text:00437DC6 mov [esp+10h+arg_4], eax
.text:00437DCA jz short loc_437E32
.text:00437DCC push edi
.text:00437DCD mov ecx, esi
.text:00437DCF call CTreeCtrl::ItemHasChildren(_TREEITEM *)
.text:00437DD4 test eax, eax
.text:00437DD6 jz short loc_437E32
.text:00437DD8 mov ecx, [esi+1Ch]
.text:00437DDB mov ebx, ds:SendMessageA
.text:00437DE1 push edi ; lParam
.text:00437DE2 push 4 ; wParam
.text:00437DE4 push 110Ah ; Msg
.text:00437DE9 push ecx ; hWnd
.text:00437DEA call ebx ; SendMessageA
.text:00437DEC mov edi, eax
.text:00437DEE test edi, edi
.text:00437DF0 jz short loc_437E32
.text:00437DF2 push edi
.text:00437DF3 mov ecx, esi
.text:00437DF5 call f_IsItemChecked
.text:00437DFA mov ebp, [esp+10h+arg_0]
.text:00437DFE test eax, eax
.text:00437E00 jz short loc_437E0A
.text:00437E02 push edi
.text:00437E03 push ebp
.text:00437E04 mov ecx, esi
.text:00437E06 call [esp+18h+arg_4]
.text:00437E0A
.text:00437E0A loc_437E0A: ; CODE XREF: f_ExportResource+60
.text:00437E0A mov edx, [esi+1Ch]
.text:00437E0D push edi ; lParam
.text:00437E0E push 1 ; wParam
.text:00437E10 push 110Ah ; Msg
.text:00437E15 push edx ; hWnd
.text:00437E16 call ebx ; SendMessageA
.text:00437E18 mov edi, eax
.text:00437E1A test edi, edi
.text:00437E1C jz short loc_437E32;是否找到下一个同级的item
.text:00437E1E push edi
.text:00437E1F mov ecx, esi
.text:00437E21 call f_IsItemChecked
.text:00437E26 test eax, eax
.text:00437E28 jz short loc_437E32
.text:00437E2A push edi
.text:00437E2B push ebp
.text:00437E2C mov ecx, esi
.text:00437E2E call [esp+18h+arg_4] ;传入的是函数指针
.text:00437E32
.text:00437E32 loc_437E32: ; CODE XREF: f_ExportResource+2A
.text:00437E32 ; f_ExportResource+36
.text:00437E32 ; f_ExportResource+50
.text:00437E32 ; f_ExportResource+7C
.text:00437E32 ; f_ExportResource+88
.text:00437E32 pop edi
.text:00437E33 pop esi
.text:00437E34 pop ebp
.text:00437E35 pop ebx
.text:00437E36 retn 8
.text:00437E36 f_ExportResource endp
与前面判断有多少item被选中的代码如出一辙。如果在此设断点,
在按下"导出"后中断9次,每种资源一次。很明显,传入的2个参数,
一个是指向第1级节点TVITEM的指针,另一个是用于导出对应类型资
源的函数指针。
更好的一点是,在437E18处,取到与当前item同一层的下一个item(类
似CTreeCtrl::GetNextSiblingItem)指针后,有
.text:00437E18 mov edi, eax
这正是重复执行437E16访问下一个item需要的。这样方便了我们建
立循环。而437E39处还有因为对齐边界而剩余的8个字节:
这次不必跳到.text的末尾去写代码了,只要简单地修改几个字节:
.text:00437E32 jmp 437E0A ;循环
.text:00437E34 pop edi ;原来的出口代码顺序后移
pop esi
pop ebp
pop ebx
ret 8
另外,修改437E1C的跳转:
.text:00437E1C jz 437E34 ;这是唯一的出口
运行一下,这次导出的文件很多了。可仍然有问题:
对照在CTreeCtrl控件中显示的数据和实际得到的文件:
显示的数据 得到的文件数
____________________________________________
矢量图 28 26(缺1,7)
图片 2 2
声音 9 9
字体 5 5
文字 135 135
动画 8 8
按钮 72 72
动画帧 187 187
动作 73 73
上面提到过,4385C0处的函数检查矢量图资源的格式是否符
合"闪客巫师"的要求。当按"导出"按钮时,报了shape 1不能
输出为指定格式。显然,这里还缺一个。
在这里:
.text:00438720 mov eax, [esi+1Ch]
.text:00438723 push edi ; lParam
.text:00438724 push 1 ; wParam
.text:00438726 push 110Ah ; Msg
.text:0043872B push eax ; hWnd
.text:0043872C call ds:SendMessageA
.text:00438732 inc ebx
.text:00438733 mov edi, eax
.text:00438735 cmp ebx, 1 ;只检查前2项
.text:00438738 jg short loc_438742 ;
.text:0043873A test edi, edi
.text:0043873C jnz loc_438682
关闭438732的inc指令。这次在"导出"时报shape1,shape7不符合
格式要求。
6.解除两个Nag Screen
去掉"演示版限制: 只分析影片的前五帧。"
.text:004361AB push 0FFFFFFFFh
.text:004361AD push 40h
.text:004361AF push 0F2A5h
.text:004361B4 call sub_481725 ;关闭
"演示版限制每类资源中只有前两个资源可以被导出。"
.text:00428BEE push 0FFFFFFFFh ;函数的第1个参数
......
.text:00428C0F push 41h
.text:00428C11 push 12Eh
.text:00428C16 call sub_481725 ;
.text:00428C1B cmp eax, 1
.text:00428C1E jnz loc_428D9B ;
NOP掉上述全部代码。
7.OK,终于结束了。打开卜桦的《猫》试试,在程序的窗口中,
点击item,看不到有图形显示?!导出来瞧瞧,可以。虚惊
一场...... :-)
