|
欢迎您注册加入!这里有您将更精采!
您需要 登录 才可以下载或查看,没有账号?注册
x
【文章标题】: ZProtect脱壳无KEY解码总结
【文章作者】: A.D.
【软件名称】: 某ZP加壳的程序
【使用工具】: OllyDBG 1.0原版 + StrongOD + ODbgScript
【操作平台】: WinXP SP3
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
【首发论坛】: 一蓑烟雨www.unpack.cn 转帖请注明
【文章目的】: ZProtect旧版加壳的程序,如果使用了注册框和试用功能,可以通过y3y3y3大大的LPK.DLL来绕过。但是如果是ZP 1.49或者企业版,又或者没有使用试用功能,程序本身虽然依然可以由LPK.DLL绕过,可绕过后的程序并不能正常运行。这是因为用ZP加壳时,程序的部分代码用密钥进行了加密,在绕过注册框后,必须正确地使这些代码解密,程序才可以正常运行。而本文的目的就是探讨如何在没有注册码的情况下使之正确解码,使程序可以正常运行,进而可以脱壳。
【致谢人员】: hyperchem、ximo、Hmily、ffzy
【无KEY解码原理】
在正确理解上面hyperchem大大给出的提示的情况下,对ZP加壳的程序进行研究。
了解到ZProtect加壳程序的运行流程如下:
OD初始断点处=>待输入注册码的注册框=>程序代码整体解码=>特定区域KEY解码=>到达真正OEP
而特定区域KEY解码的流程,通过我之前的疑问贴ZProtect 1.4.9脱壳练习遇到的疑问二 10楼的帖子(http://www.unpack.cn/redirect.ph ... 67&fromuid=7163)已经解释过了,如下:
第一次解码=>第二次解码=>第三次解码
事实上,我并不很确切地知道三次解码分别进行的是什么操作,是用的KEY的MD5,还是其他什么,我只是通过自己加壳的计算器的两种状态下的运行,知道了前两次解码的结果不管是输入正确注册码还是绕过注册框其结果都是一样的,而第三次解码的结果则不同。
之后我尝试在第三次解码前,把解码的对象XOR数值改成正确的数值,发现解码成功,到达真正OEP时程序可以运行。
所以要想实现无KEY解码,其原理就是在第三次解码前把用以解码的XOR数值改成正确的数值,而问题也转化为如何在没有注册码的情况下猜解出正确的解码XOR数值。
【无KEY解码流程】
============================
1、准备工作:
在整个猜解的过程当中,我们需要记录下一些信息,首先建立一个TXT文档,内容如下:
1、真正OEP:
2、IATSTART:
3、密钥位置:
数值:
4、解码代码段:
5、正确解码:
6、IATEND:
而整个猜解的流程,我通过自己编写的ODbgScript脚本来实现,首先建立一个脚本模板:
var oep //ESP定律 用到的断点,并不是真正的OEP,只是用以判断已经到真正的OEP了
//清空断点
bphwcall
bpmc
//获取OEP
FoundOEP:
mov oep,eip
cmp [oep],60,1
je GetOEP
sti
jmp FoundOEP
GetOEP:
sti
mov oep,esp
//绕过注册框
BypassRegist:
bphws 77D56D7D //注册对话框断点
run
mov eax,232c
mov eip,77D56D99
bphwcall
//到达真正OEP
GoOEP:
bphws oep,"r"
run
bphwcall
sti //这里有多少个sti在于ESP定律断点断下来后F7几次到达真正OEP
sti
//退出函数
Exit:
ret
以上准备工作做好后,就可以正式开始无KEY解码。
============================
2、载入程序,运行上面的原始模板脚本。运行完后,程序停在真正OEP处。记录下来:
1、真正OEP:esp定律=>12FFA0=>46F610 //其中12FFA0就是脚本中的oep
============================
3、在真正OEP上下文中的CALL里找IAT表的位置。
0046F610 55 PUSH EBP
0046F611 8BEC MOV EBP,ESP
0046F613 83C4 F0 ADD ESP,-10
0046F616 B8 B8F34600 MOV EAX,程序.0046F3B8
0046F61B E8 D068F9FF CALL 程序.00405EF0 ☆跟进
00405EF0 53 PUSH EBX
00405EF1 8BD8 MOV EBX,EAX
00405EF3 33C0 XOR EAX,EAX
00405EF5 A3 A4004700 MOV DWORD PTR DS:[4700A4],EAX
00405EFA 6A 00 PUSH 0
00405EFC E8 2BFFFFFF CALL 程序.00405E2C ☆跟进
00405E2C -FF25 F8314700 JMP DWORD PTR DS:[4731F8] //这里的4731F8应该就是IAT表当中的一项了
00405E32 8BC0 MOV EAX,EAX
00405E34 -E9 8B470A00 JMP 程序.004AA5C4
00405E39 90 NOP
00405E3A 8BC0 MOV EAX,EAX
00405E3C -E9 FF430A00 JMP 程序.004AA240
00405E41 90 NOP
然后就在数据窗口中去到4731F8,转成长型-地址看看,上下文如下:
00472FF0 00000000
00472FF4 00000000
00472FF8 00000000
00472FFC 00000000
00473000 640D72D8
00473004 2852F1E9
00473008 685733E4
0047300C 343B2735
...
004731D8 685733E4
004731DC 00D000EE
004731E0 6447DD5C
004731E4 00D000FC
004731E8 685733E4
004731EC 3476B475
004731F0 00D0010A
004731F4 2818542D
004731F8 00A73E10
通过这里可以判断出IATSTART的开始位置可能为00473000 ,记录如下:
2、IATSTART:473000=> //因为可能所以先空着
============================
4、接下来,通过找到的IAT表,反过来找解码的时候。
在脚本模板中相应位置添加如下代码,使得在绕过注册框后,停在解码IAT表的时候。
//根据找到的IAT表首地址+1F,反找解码XOR数值位置
FoundXor:
bphws 47301F,"w"
run
jmp Exit
重载程序,运行脚本,脚本结束时,程序停在473000第一次解码的时候。
通过内存窗口找到473000所在区段401000,右键数据,HEX-16位,转到473000的地址。
这时,从内存窗口中,我们可以看到IAT表第一次解码的情况,按一下F9,到达第二次解码的时候,再按一下F9,到达第三次解码的时候,这时,按着F7不放,我们可以看到内存中IAT表正在一位一位地解码,而解码的汇编代码段如下:
00A745C3 8BD0 MOV EDX,EAX
00A745C5 83E2 0F AND EDX,0F
00A745C8 8A0C14 MOV CL,BYTE PTR SS:[ESP+EDX] //XOR对象的位置
00A745CB 300C38 XOR BYTE PTR DS:[EAX+EDI],CL
00A745CE 83C0 01 ADD EAX,1
00A745D1 3BC3 CMP EAX,EBX
00A745D3 ^7C EE JL SHORT 00A745C3
通过反复观察,可以知道XOR对象的位置和XOR对象的数值,填表如下:
3、密钥位置:12FEE0~12FEEF
数值:0012FEE0 F1 D3 FF 84 43 29 77 32 86 2D F2 1D C4 E5 72 62
============================
5、在知道了解码时XOR对象的位置,和错误的解码数值是多少后,我们就可以反过来找有多少个区段被解码,以方便后文无KEY解码脚本的书写。
解码的时候,这个位置的数值必然是错误的解码XOR数值,所以我们只要在这个位置写入这个错误数值的时候把程序断下来,然后看看程序解码了哪里,就可以知道确切的区段数和每个区段的起始位置了。
在脚本模板里,把FoundXor函数段去掉,替换为:
FoundDecodeSection:
bphws 12FEEF,"w" //填写密钥终止位置
bphws oep,"r"
FoundDecodeSection2:
run
cmp eip,oep
je FoundDecodeSection3
find 12FEE0,#F1D3FF8443297732862DF21DC4E57262# //填写密钥起始位置和错误密钥数值
cmp $RESULT,0
je FoundDecodeSection2
msg "请对内存中的程序代码段按F2断点,F9一次后找到的操作的地址即为解码段起始处,再回到脚本按空格继续"
pause
jmp FoundDecodeSection2
FoundDecodeSection3:
jmp GoOEP
重载程序,运行脚本。
每当脚本弹出对话框的时候,按确定,然后在内存区段窗口,对加壳程序的代码段按F2,下内存访问断点,F9继续。
程序断在如下代码:
00A745CB 300C38 XOR BYTE PTR DS:[EAX+EDI],CL
00A745CE 83C0 01 ADD EAX,1
00A745D1 3BC3 CMP EAX,EBX
00A745D3 ^7C EE JL SHORT 00A745C3
00A745D5 83C4 14 ADD ESP,14
这时,可以看到
CL=F1
DS:[00401000]=2D ('-')
现在我们可以知道程序解码的第一个区段的起始处是00401000了,记录下来。重新回到脚本窗口,空格让脚本继续运行。
反复进行以上步骤,直到脚本运行结束。把刚才所有的结果记录下来:
4、解码代码段:401000、470000、473000、477000、47F000
============================
6、在知道了解码段后,我们要做的就是把所有解码段在运行到第三次解码前的内容记录下来。每一个解码段的内容,大概只需要记录一个屏幕就够用了。我记录的长度是37F。如果解码段的长度不够长,就把这个长度改短点。记录这些内容的目的是用来猜解正确的解码XOR数值。
重新编辑模板脚本,把刚才的FoundDecodeSection 3个函数去掉,按解码段数量,添加下面代码,每个解码段一一对应:
//根据找到的解码地址+37F,反找二次解码后的结果,推最后结果
GuessXor1:
bphws 40137F,"w" //解码代码段401000后37F长
run
run
msg "401000解码结果"
bphwcall
GuessXor2:
bphws 47037F,"w" //解码代码段470000后37F长
run
run
msg "470000解码结果"
bphwcall
GuessXor3:
bphws 47337F,"w" //解码代码段473000后37F长
run
run
msg "473000解码结果"
bphwcall
GuessXor4: //因为+37F断不下来,观察发现这个解码段很短,只能+1FF
bphws 4771FF,"w" //解码代码段477000后1FF长
run
run
msg "477000解码结果"
bphwcall
GuessXor5:
bphws 47F37F,"w" //解码代码段47F000后37F长
run
run
msg "47F000解码结果"
bphwcall
|
|