移花接木巧妙Crack 1stOptv1.5 Demo
追本溯源轻松Keygen 1stOptv1.0
【文章标题】: 移花接木巧妙Crack 1stOptv1.5 Demo
追本溯源轻松Keygen 1stOptv1.0
【文章作者】: winndy
【联系方式】: [email]CNwinndy@hotmail.com[/email]
【目 标】: 1stOptv1.5 Demo : http://www.7d-soft.com/cn/index.htm
1stOptv1.0
Auto2Fit3.0
【使用工具】: OllyICE、DEDE、OllyDump插件、CodeHelper插件
【操作平台】: Windows2003 Server
【作者声明】: 本文纯属技术交流,只作学习使用,不得用于商业用途。转载请注明作者并
保持文章的完整。失误之处敬请诸位大侠赐教!
【文章结构】: 一、揭开迷雾:1stOptv1.5 是个 Demo
: 二、移花接木:1stOptv1.0功能齐全
: 三、追本溯源:Auto2Fit v3.0和THKStreams Delphi Component
: 四、修复Bug:帮作者修复,同时学习Inline Patch
【详细过程】:
一、揭开迷雾:1stOptv1.5 是个 Demo
1.脱壳:Aspack 压缩,到达OEP后直接用OllyDump(缺省参数)脱壳,可以运行。
2.未运行版本的限制:
①关于窗口上显示“未注册版”。
②右键复制结果时,弹出MessageBox“未注册版不能进结果复制操作!”。
③代码本上右键粘贴,弹出MessageBox“未注册版不能进行粘贴操作!”。
④双击左侧文件浏览器中的Example文件,窗口标题后[]内是文件的全路径,
修改Example文件后,点保存,弹出MessageBox“未注册版不能保存为‘.mff’格式!”。
⑤若新建一个代码文件,则窗口标题后[]内是Untitled1,点保存,弹出保存对话框,save后不弹出任何对话框(1.0中会弹出MessageBox“未注册版不能保存为‘.mff’格式!”),但实际上文件没有保存,而且窗口标题不含路径。
⑥在新建一个代码文件前,双击左侧文件浏览器中的文件,窗口标题随着改变,但在新建一个代码文件后,也就是窗口标题变为[Untitled1]后,再双击左侧文件浏览器中的文件,窗口标题不再改变。注意:在1.0版的使用版中不存在这个现象。
⑦点“编辑”,再“插入”,再点“文件数据”,弹出MessageBox “未注册版不能进行数据插入操作!”。
⑧算出结果后,右键点击保存,可以看到可以保存为文本文件,选择文件名后,弹出MessageBox “未注册版不能保存结果内容!”
3.载入1stOpt_unpacked,右键查找参考字符串,搜索“未注册”,在本段代码起始处设好断点:
第一处:
0068E23C push ebx ;下断
[省略…]
0068E2C8 mov eax, 0068E2E0 ; 未注册版不能进结果复制操作!
0068E2CD call 0046E9B8 ; D7.Dialogs.ShowMessage(AnsiString);
0068E2D2 pop esi
0068E2D3 pop ebx
0068E2D4 retn
第二处:
0068E300 mov eax, 0068E314 ; 未注册版不能进行粘贴操作!
0068E305 call 0046E9B8 ; D7.Dialogs.ShowMessage(AnsiString);
0068E30A retn
第三处:
0068E330 push ebp
[省略…]
0068E341 mov eax, 0068E36C ; 未注册版不能保存为.mff文件!
0068E346 call 0046E9B8 ; D7.Dialogs.ShowMessage(AnsiString);
[省略…]
第四处:
0069F404 mov eax, 0069F418 ; 未注册版不能进行插入数据操作!
0069F409 call 0046E9B8 ; D7.Dialogs.ShowMessage(AnsiString);
0069F40E retn
在进行调试的时候,根本找不到一个可以跳到实际执行程序功能的地方,即使是向上面的call追,也还是毫无头绪。开始怀疑这就是Demo版。干脆把1.0的拿来看看,对比一下,看有什么值得借鉴的参考的地方。
在网眼天下,有篇爆破的文章:
完美爆破1stOpt1.0数学优化分析综合工具软件包
【破文作者】luzhmu
跟了一下,关键点在于
0062A954 mov eax, ebx
前面则有:
0062A8E9 xor ebx, ebx //改为mov bl, 1就可以了
0062A8EB jmp short 0062A8EF
0062A8ED mov bl, 1
0062A8EF xor eax, eax
想要爆破的话,将xor ebx, ebx改为mov bl, 1就可以了,都是2个bytes。
爆破后,发现一个奇怪的现象,运行完爆破后的exe文件,1stOpt.dll就变成正式注册文件了,即再运行原文件,仍显示注册成功。看来,程序在校验完1stOpt.dll后,重新写了注册文件。这就激起了我写注册机的想法。别急。
在跟踪1.0的时候,我看到解码出很奇怪的字符串:
0062A7E0 call [ecx+5C]
这句执行之后,d [edx],可以在内存中看到:
00CB8030 ?0?DCHTTVVUPNWORWQVDAFGM
00CB8050 G..BHASQMMPQMJOMYAFKATXV..SYUWVC
00CB8070 TWHIHIUDABLMKWP..TFLDDBEQKSXWVLJ
[省略后面的…]
仔细看看这些字符串,夹杂着:用户名(xycheng),CPUID,HardDiskID,1.0(谁都猜得到是版本号).
转念一想,要是我把1.5的1stOpt.dll拿来,把版本号改为1.5,那不就可以得到一个1.5的正式版注册文件吗?会出现MessageBox“版本号不同”,很容易找到lstrcmp,把它跳过,得到1.5版的正式注册文件。
满怀激动的心情,把1stOpt.dll放到1.5目录下去,仍然是未注册。难道算法改变了?那你至少也得读这个1stOpt.dll文件吧。搜索字符串,未找到;干脆删了这个1stOpt.dll,看你报错不,结果它不理睬。在1.0下,则会报“缺少库文件”的错。
再比较一下1.0和1.5弹出“未注册版不能…”的代码,发现1.5根本就不包含正式功能的代码。
晕死了!就无法破解了吗?
二、移花接木:1stOptv1.0功能齐全
哈哈,幸好1.0的代码功能是齐全的,何不把1.0的代码拿到1.5中来使用呢?这两个版本差别不大,应该是没问题的。最后是移植成功,幸亏不要我去修改Import Table,.
上面,已经得到注册文件了,有了正式版1.0。分别用OD打开1.0和1.5,找到相对应的地方,用你最喜欢的十六进制编辑工具(我用winhex,shooo教我用的,)把1.0的代码复制到1.5里面去。1.5的代码很短,空间不够,跳到一个空白地方在进行复制。复制完一个功能,就打开OD进行调试,知道这个功能调试好,再复制下一个功能。
第一处:
1.5修改前
0068E2C8 mov eax, 0068E2E0 ; 未注册版不能进结果复制操作!
0068E2CD call 0046E9B8 ; D7.Dialogs.ShowMessage(AnsiString);
0068E2D2 pop esi
0068E2D3 pop ebx
0068E2D4 retn
修改后:
0068E2C8 mov eax, [esi+394]
0068E2CE call 005262A0
0068E2D3 pop esi
0068E2D4 pop ebx
0068E2D5 retn
1.0中是:
00683530 mov eax, [esi+394]
00683536 call 0050D820
0068353B pop esi
0068353C pop ebx
0068353D retn
第二处:
1.5修改后:
0068E300 push ebx
0068E301 push esi
0068E302 mov ebx, eax
[省略…]
0068E329 jmp 006D0156 ;空间不够,跳到其他地方去
006D0156 jge short 006D0173
[省略…]
006D017B pop ebx
006D017C retn
1.5修改前:
0068E300 mov eax, 0068E314 ; 未注册版不能进行粘贴操作!
0068E305 call 0046E9B8 ; D7.Dialogs.ShowMessage(AnsiString);
0068E30A retn
1.0中:
00683540 . 53 push ebx ; Paste
00683541 . 56 push esi
[省略…]
006835A5 . 5B pop ebx
006835A6 . C3 retn
第三处:
1.5修改后:
0068E330 jmp 006D0180 ; 空间不够,jmp to save .mff
006D0180 push ebp ; save .mff file fixed code
006D0181 .mov ebp, esp
006D0183 add esp, -28
[省略…]
006D01AD push ebp
006D01AE push 006D03FF
006D01B3 push dword ptr fs:[eax]
006D01B6 mov fs:[eax], esp
006D01B9 mov eax, [6D91D8]
[省略…]
006D01D9 mov eax, [41712C]
006D01DE call 004036EC ; D7.System.TObject.Create(TObject;Boolean);
[省略…]
006D01F3 mov edx, 006D0418 ; ASCII "1stOpt File"
006D0202 mov edx, [6D88CC] ; 1.5
006D0208 . E8 7B45D3FF call 00404788
[省略…]
006D03CE mov eax, [6D91D8]
006D03D3 mov eax, [eax]
006D03D5 xor edx, edx
006D03D7 call 004629AC
006D03DC xor eax, eax
006D03DE pop edx
006D03DF pop ecx
006D03E0 pop ecx
006D03E1 mov fs:[eax], edx
006D03E4 push 006D0406
006D03E9 lea eax, [ebp-28]
006D03EC mov edx, 4
006D03F1 call 00404588
006D03F6 lea eax, [ebp-8]
006D03F9 call 00404564
006D03FE retn
006D03FF jmp 00403E9C ;跳到==D7.System.@HandleFinally;
006D0404 jmp short 006D03E9
006D0406 pop edi
006D0407 pop esi
006D0408 pop ebx
006D0409 mov esp, ebp
006D040B pop ebp
006D040C retn 0C
1.5修改前:
0068E330 push ebp
0068E331 mov ebp, esp
0068E333 xor eax, eax
0068E335 push ebp
0068E336 push 0068E359
0068E33B push dword ptr fs:[eax]
0068E33E mov fs:[eax], esp
0068E341 mov eax, 0068E36C ; 未注册版不能保存为.mff文件!
[省略…]
1.0中:
006835A8 push ebp ; 保存.mff文件
006835A9 mov ebp, esp
006835AB add esp, -28
[省略…]
第四处:
在1.5中修改后:
0069F404 jmp 006D04C0 ;空间不够,跳到别处
006D04C0 push ebp ; InsertFileData
006D04C1 mov ebp, esp
006D04C3 xor ecx, ecx
[省略…]
006D053D |. 90 nop ;在1.0中校验注册没,nop掉
[省略…]
006D054A |. 90 nop
[省略…]
1.5修改前:
0069F404 mov eax, 0069F418 ; 未注册版不能进行插入数据操作!
0069F409 call 0046E9B8 ; D7.Dialogs.ShowMessage(AnsiString);
0069F40E retn
在1.0中:
00693E7C push ebp ; InsertFileData
00693E7D mov ebp, esp
00693E7F xor ecx, ecx
[省略…]
00693EF9 mov eax, 2
00693EFE call 0062A538 ;校验注册没有
00693F03 test al, al
00693F05 je short 00693F76
比较1.0和1.5的代码,复制过来后,要注意修改call,还有一些变量,push的常量。
修改的方法为:
1.找到在1.5中与1.0中相同的函数,进入1.0的call,复制几行能唯一标识这个函数的代码,Ctrl+S,在1.5中搜索,找到后,找到函数起始地址,修正call。
2. 举个例子[例子举的都是第三处的]:
006D01AE push 006D03FF
006D03FF jmp 00403E9C ;跳到==D7.System.@HandleFinally;
006D01AE附近的这段代码是安装SEH处理函数的,push的这个常量是跳到D7.System.@HandleFinally的代码的地址。006D03FF 处的jmp则是跳到HandleFinally。
这两处都要修复。找00403E9C的方法通call。Push 006D03FF,这个常量就是006D03FE retn后面的那句。
3.例子:
006D01F3 mov edx, 006D0418 ; ASCII "1stOpt File"
这个好修复,观察1.0的代码,可以知道,mov进edx的常量指向一个字符串。
4.例子:
006D0202 mov edx, [6D88CC] ; 1.5
这种不太好修复。
通过调试1.0,可以看到执行006D0202后,d [edx],看到字符串1.0。等我们修复后,可以来验证。
具体怎么找到6D88CC的,记不太清了,好像运气比较好,^_^。
对于1.5种的006D03CE mov eax, [6D91D8]
在1.0种对应的代码是:006837F6 mov eax, [6C9028]
通过在1.0中ctrl+s,搜索mov eax, [6C9028],可以得到好几处,在通过某一处很有特点的代码,在1.5中搜索到这段代码,进而找到1.5中相对应的mov eax, [6D91D8]。
这样,四个基本功能都修复完毕。还有几个小地方。
可以利用DeDe,找到结果面板中保存为文本文件所对应的过程的起始地址:
反编译后,找到所有的save*事件,然后全部下断点,再点保存,中断在哪里就是哪里了。
第五处:
1.5中修改后:
006986BC jmp 006D0670 ; 跳到其他地方去,Save2textClick
006986C1 nop
006D0670 push ebp ; ResultSave2TxtClick
006D0671 |. 8BEC mov ebp, esp
[省略…]
修复的方法同上。
1.5修改前:
006986BC push ebx
006986BD mov ebx, [eax+380]
[省略…]
006986F3 je short 006986FF
006986F5 mov eax, 00698750 ; 未注册版不能保存结果内容!
006986FA call 0046E9B8 ; D7.Dialogs.ShowMessage(AnsiString);
006986FF pop ebx
00698700 retn
1.0中:
0068D01C push ebp ; Save2textClick
0068D01D mov ebp, esp
[省略…]
第六处:解决限制⑥
DeDe反编译,找到RzShellList1DblClick事件。
1.5中:
0063338C push ebp ; RzShellList1DblClick
1.0中:
0062C964 push ebp ; RzShellList1DblClick
同时运行1.0和1.5,观察程序流程,容易找到关键地方。
1.0中:
0062CAC5 mov edx, 0062CCC8 ; ASCII ".mff"
0062CACA call 00404910 ; D7.System.@LStrCmp;
0062CACF je short 0062CAE3
0062CAD1 mov eax, 2
0062CAD6 call 0062A538 ;校验注册没有
0062CADB test al, al
0062CADD je 0062CC54
0062CAE3 push 0062CCD8 ; ASCII "1stOpt - ["
0062CAE8 push dword ptr [ebp-4]
1.5修改前:
006334D7 mov edx, 006336C8 ; ASCII ".mff"
006334DC call 00404910 ; D7.System.@LStrCmp;
006334E1 je short 0063350C
006334E3 push 006336D8 ; ASCII "1stOpt - ["
006334E8 push dword ptr [ebp-4]
在1.0中0062CACF je short 0062CAE3直接跳到了
0062CAE3 push 0062CCD8 ; ASCII "1stOpt - ["
而在1.5中,006334E1 je short 0063350C
把006334E3 push 006336D8 ; ASCII "1stOpt - ["
这句跳过了。
清楚了,把 je short 0063350C 直接nop掉。
保存后运行,OK!
第七处:解决新建文件后不能保存文件(窗口标题已带绝对路径的可以保存)。
新建文件,点工具栏的保存图标,中断在下面:
1.5中:
0068E754 push ebp ; SaveActionExecute
1.0中:
00683C40 push ebp ; SaveActionExecute
同时调试1.0和1.5,比较流程,这个还比较难找:
1.5中,跟到这里:
006908A0 push ebp ; savefile
006908A1 mov ebp, esp
006908A3 mov ecx, 0D
[省略…]
00690995 . 84C0 test al, al
00690997 . 0F84 EA020000 je 00690C87
0069099D . EB 31 jmp short 006909D0 ;这是修改后的代码
0069099F 90 nop
006909A0 . 8BC3 mov eax, ebx
1.5修改前的代码:
00690995 test al, al
00690997 je 00690C87
0069099D lea edx, [ebp-20] //这段代码要跳过
006909A0 mov eax, ebx
006909A2 call 0046CC0C
006909A7 mov eax, [ebp-20]
1.0中:
00685DE8 push ebp ; save2file
00685DE9 mov ebp, esp
00685DEB mov ecx, 0D
[省略…]
00685EDD test al, al
00685EDF je 006861DD
00685EE5 mov eax, 3
00685EEA call 0062A538 ;检验注册没有
00685EEF test al, al
00685EF1 jnz short 00685F26
00685EF3 lea edx, [ebp-20]
00685EF6 mov eax, ebx
00685EF8 call 0046CC0C
对比1.0和1.5的流程,可以找到关键点。
新建文件后,窗口标题能变为绝对路径,可以保存文件。
第八处:修改关于窗口中的“未注册版”
用ultraedit,搜索到“未注册版”,改为“ winndy”。
不太会修改资源,未注册版有8个bytes,所以也该成8个bytes了,在winndy前还加了两个空格。
到这里,1stOptv1.5修复完毕。没什么技巧,完全是体力活啊!
1.0的注册机还没做出来呢。
三、追本溯源:Auto2Fit v3.0和THKStreams Delphi Component
在跟踪1stOptv1.5的时候,用DFMEditor查看资源,可以发现一个TREGFORM,打开一看,“Auto2Fit Reristration”,“Send Mail to CPC-X Software”,于是google了个Auto2Fit v3来玩玩。安装后,界面和1stOpt没啥两样,只是Auto2Fit是英文界面而已。Auto2Fit是1stOpt的前身?在Auto2Fit中看到了很多参考字符串,这个好像更适合做注册机。下面就跟踪Auto2Fit。
点关于,停在下面:
005FB4A4 push ebp
[省略…]
005FB4F8 mov ecx, 005FBD60 ; ASCII "CPUHDID.txt"
005FB4FD mov edx, [ebp-38]
005FB500 call 00404808
005FB505 mov eax, [ebp-214]
005FB50B call 0040CB2C ; D7.SysUtils.FileExists(AnsiString):Boolean;
005FB510 test al, al
005FB512 jnz short 005FB588
005FB514 lea eax, [ebp-218]
005FB51A mov ecx, 005FBD60 ; ASCII "CPUHDID.txt"
检查exe的目录中是否存在CPUHDID.txt,若不存在则创建,第一行是CPUID,第二行是HardDiskID。很容易得到生成CPUID和HardDiskID的算法。写注册机时会用到。
005FB9ED cmp dword ptr [6A77A8], 1
005FB9F4 je short 005FB9FF
005FB9F6 cmp dword ptr [6A77A8], 5
005FB9FD jnz short 005FBA0E
005FB9FF lea eax, [ebp-34]
005FBA02 mov edx, 005FBDEC ; ASCII " (Single User License)"
005FBA07 call 004045B8
005FBA0C jmp short 005FBA6F
005FBA0E cmp dword ptr [6A77A8], 2
005FBA15 je short 005FBA20
005FBA17 cmp dword ptr [6A77A8], 6
005FBA1E jnz short 005FBA2F
005FBA20 lea eax, [ebp-34]
005FBA23 mov edx, 005FBE0C ; ASCII " (2-4 Users License)"
005FBA28 call 004045B8 ; D7.System.@LStrLAsg(void;void;void;void);
005FBA2D jmp short 005FBA6F
[省略…]
很明显,[6A77A8]是注册的类型。
al=0 trial user
al=1 ,5 (Single User License)
al=2 ,6 (2-4 Users License)
al=3 ,7 (5-10 Users License)
al=4 ,8 (Site License)
1<=al<=4 "Standard Version "
5<=al<=8 "Professional Version "
往上面看,有两处call:
005FB98C call 005FAC00
005FB9B8 call 005FB290
都很重要。
第一个call读注册表中的键值(有两处),检查Auto2Fit.Lic;
第二个call检查AFCorelib.dll。
跟进后会发现:首先读
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\OfficeAF
下的几个值:
1. eu_id:
“95-120-124-111-113-115-125-126-111-124-111-110-42-96-111-124-125-115-121-110”
2. gc_id:6
3. st_id:
64位浮点数:
00 00 00 00 E0 06 E3 40
38967.000000000000000
4. vs_id:0
注意只有在gc_id=0的情况下,采取读Auto2Fit.Lic。
其中eu_id经过一个函数解密为用户名:
005FADA5 call 005FA7A0 ; 用户名解密
“95-120-124-111-113-115-125-126-111-124-111-110-42-96-111-124-125-115-121-110”
就是Unregistered Version。
然后会压入这些参数,进入一个call:
005FADAA mov eax, [ebp-120] ; 用户名
005FADB0 mov edx, [ebp+8] ; p_vs_id
005FADB3 mov edx, [edx] ; vs_id
005FADB5 call 005FA8C4 ; 校验licensefile
005FADBA mov [ebp-10], eax
005FADBD cmp dword ptr [ebp-10], 0
005FADC1 jnz short 005FADDA ; 跳
005FADC3 push 0 ; /Arg1 = 00000000
005FADC5 mov cx, [5FB210] ; |
005FADCC mov dl, 2 ; |
005FADCE mov eax, 005FB21C ; |ASCII "Missing Auto2Fit License File!"
005FADD3 call 0046E004 ; \Auto2Fit.0046E004
005FADD8 jmp short 005FADF8
Auto2Fit.Lic是没有的,得自己构造一个假Auto2Fit.Lic。
难点就在于这里了,难得构造一个格式符合要求的Auto2Fit.Lic,能通过这段验证,每次都说“Auto2Fit.Lic已损坏”。一层一层的跟进去,头都大了,仅知道第3个byte开始,必须是01 02 …08。
在这里卡了。
后来,查看DeDe反汇编出来的代码的时候,看到:
* Reference to class THKStreams
|
005FA9F8 A1DC255300 mov eax, dword ptr [$005325DC]
* Reference to : THKStreams._PROC_005327CC()
|
005FA9FD E8CA7DF3FF call 005327CC
005FAA02 8945EC mov [ebp-$14], eax
005FAA05 8B45EC mov eax, [ebp-$14]
* Reference to field THKStreams.OFFS_0024
|
005FAA08 C6402401 mov byte ptr [eax+$24], $01
005FAA0C 8B45EC mov eax, [ebp-$14]
不禁产生好奇感,这个THKStreams究竟是什么类啊!?
Google一下,原来是个delphi 流文件加密的组件,开源的,太好了,有救了。
下了1.7的:THKStreams v1.7 by Harry Kakoulidis 1/2002
里面还有个demo,演示THKStreams的使用。
用Delphi打开组件源码和demo 工程,组件采用了blowfish加密和LHA压缩算法。学习了一下这些源码。一边跟踪,一边对照源码,很容易就识别出了Auto2Fit中的那些call。用demo工程中的一个memo,生成了Auto2Fit.Lic。但还是出错,然后又google,恶补了一下TStringList的用法。又仔细看了看demo的代码。
HKS.AddStream('MEMO1',ms); //Add it to THKStreams with ID 'MEMO1'
关键在上面这句,'MEMO1'相当于流文件中的一个标签了,在我们的Auto2Fit.Lic中对应的标签是什么呢。后来跟踪Auto2Fit,发现标签是'AFLicenseFile'。
重新伪造Auto2Fit.Lic,继续调试。
参考HKStreams的源码和demo工程的代码,以及DeDe反汇编出来的代码,还有CodeHelper插件,可以给出很好的注释:
005FAAAC mov edx, 005FABA4 ; ASCII "AFLicenseFile"
005FAAB1 mov eax, [ebp-14]
005FAAB4 call 00532858
; procedure THKStreams.GetStream(const ID: string; Dest: TStream);
005FAAB9 mov edx, [ebp-10]
005FAABC mov eax, ebx
005FAABE mov ecx, [eax]
005FAAC0 call [ecx+5C] ; TStringList.LoadFromStream(TStream)
005FAAC3 mov eax, ebx
005FAAC5 mov edx, [eax]
005FAAC7 call [edx+14] ; TStringList.GetCount()
005FAACA cmp eax, 0D
005FAACD je short 005FAAD6
005FAACF mov esi, 2
005FAAD4 jmp short 005FAB2E
005FAAD6 lea ecx, [ebp-3C]
005FAAD9 xor edx, edx
005FAADB mov eax, ebx
005FAADD mov edi, [eax]
005FAADF call [edi+C]
005FAAE2 mov eax, [ebp-3C]
005FAAE5 mov edx, 005FABBC ; ASCII "auto2fit_license_file"
005FAAEA call 004048CC ; D7.System.@LStrCmp;
005FAAEF jnz short 005FAB29
005FAAF1 lea ecx, [ebp-40]
005FAAF4 mov edx, 3
005FAAF9 mov eax, ebx
005FAAFB mov edi, [eax]
005FAAFD call [edi+C]
005FAB00 mov eax, [ebp-40]
005FAB03 mov edx, [ebp-4]
005FAB06 call 004048CC ; D7.System.@LStrCmp;
005FAB0B jnz short 005FAB29
005FAB0D lea ecx, [ebp-44]
005FAB10 mov edx, 7
005FAB15 mov eax, ebx
005FAB17 mov edi, [eax]
005FAB19 call [edi+C]
005FAB1C mov eax, [ebp-44]
005FAB1F mov edx, [ebp-C]
005FAB22 call 004048CC ; D7.System.@LStrCmp;
005FAB27 je short 005FAB2E
上面代码中有4处关键地方,用红色标记出来。
第一处:005FAACA cmp eax, 0D
这是告诉我们,TStringList里有0D(13)行。
第二处:005FAAD9 xor edx, edx
这是告诉我们,第0个(即第1行)是"auto2fit_license_file"。
第三处:005FAAF4 mov edx, 3
第四处:005FAB10 mov edx, 7
第三和第四处暂时看不出来,随便填,伪造Auto2Fit.Lic,继续跟踪。
后来可以跟踪出,第三处是用户名,与注册表中的eu_id解密出来的用户名要一样。
第四处是密码,这个密码是由用户名,经过3处主要的变换生成的。
Fish Blowfish加密的密码的地方:
00532A74 mov edx, [ebp-14] ; key
00532A77 mov eax, [ebp-8]
00532A7A call 00530338
; Procedure DecryptStream(ms : TmemoryStream; Const Key : string);
00532A7F xor eax, eax
可以看到,key就是第四处的密码。
于是很快就生成了用户名为“Unregistered Version”的Auto2Fit.Lic文件,过关。
继续跟踪,又检查另一处注册表:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared\OfficeAF
里面的键值同:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\OfficeAF
修改注册表,顺利通过。
下面就到了验证AFCorelib.dll的函数中。
005FB2D0 mov ecx, 005FB468 ; ASCII "AFCorelib.dll"
005FB2D5 call 00404808
005FB2DA mov eax, [ebp-10]
005FB2DD call 0040CB2C ; D7.SysUtils.FileExists(AnsiString):Boolean;
005FB2E2 test al, al
005FB2E4 jnz short 005FB2F2
密码在这里找到:
005FB34B mov edx, 005FB480 ; ASCII "auto2fitneuralpower"
005FB350 . E8 1F92E0FF call 00404574
流标签仍然是:'AFLicenseFile'。
验证里面的信息的代码为:
005FB39A lea edx, [ebp-18]
005FB39D lea eax, [ebp-14]
005FB3A0 call 0067AA08 ; GetDiskID,CPUID
005FB3A5 lea ecx, [ebp-28]
005FB3A8 mov edx, 0C
005FB3AD mov eax, [ebp-8]
005FB3B0 mov esi, [eax]
005FB3B2 call [esi+C]
005FB3B5 mov eax, [ebp-28]
005FB3B8 mov edx, 005FB49C ; ASCII "False"
005FB3BD call 004048CC ; D7.System.@LStrCmp;
005FB3C2 jnz short 005FB404
005FB3C4 lea ecx, [ebp-2C]
005FB3C7 mov edx, 0A
005FB3CC mov eax, [ebp-8]
005FB3CF mov ebx, [eax]
005FB3D1 call [ebx+C]
005FB3D4 mov edx, [ebp-2C]
005FB3D7 mov eax, [ebp-14] ; 比较CPUID
005FB3DA call 004048CC ; D7.System.@LStrCmp;
005FB3DF je short 005FB402 ;goodboy
005FB3E1 lea ecx, [ebp-30]
005FB3E4 mov edx, 0B
005FB3E9 mov eax, [ebp-8]
005FB3EC mov ebx, [eax]
005FB3EE call [ebx+C]
005FB3F1 mov edx, [ebp-30] ;比较HardDiskID
005FB3F4 mov eax, [ebp-18]
005FB3F7 call 004048CC ; D7.System.@LStrCmp;
005FB3FC je short 005FB402 ;goodboy
TStringList中index为0C:"False"
TStringList中index为0A:CPUID
TStringList中index为0B:HardDiskID
其他任意。
再伪造AFCorelib.dll。
还是显示未注册。
后来跟踪由用户名生成key的过程。有个地方很可疑。
ASCII "131938520"
由13193852和0连接而来。
13193852又是怎么来的呢?
005FB956 cmp dword ptr [6A2BE4], 1 ; 00C9527C(13193852)
005FB95D jnz short 005FB9A5
不可能。
后来把vs_id改成步为0的数,便于识别和跟踪。
005FA931 mov eax, ebx ; vs_id
005FA933 dec eax
005FA934 sub eax, 4
005FA937 jnb short 005FA943 ; >=5
005FA939 mov edi, 1 ; *****
005FA93E mov [ebp-8], ebx ; vs_id
005FA941 jmp short 005FA958
005FA943 mov eax, ebx
005FA945 add eax, -5
005FA948 sub eax, 4
005FA94B jnb short 005FA958 ; >=9
005FA94D mov edi, 2 ; edi=00C9527C
005FA952 sub ebx, 4 ; 5<vs_id<9
005FA955 mov [ebp-8], ebx ; vs_id-4
005FA958 lea edx, [ebp-20]
005FA95B mov eax, edi
005FA95D call 0040C76C ; Hex2Decimal
005FA962 lea eax, [ebp-20] ; 00C9527C
005FA965 push eax ; d [eax]:00C95958 13193852
005FA966 lea edx, [ebp-24]
005FA969 mov eax, [ebp-8]
005FA96C call 0040C76C
005FA971 mov edx, [ebp-24] ; d edx "0"
005FA974 pop eax ; d [eax] 13193852
005FA975 call 004047C4 ; D7.System.@LStrCat;
005FA97A mov eax, [ebp-20]
005FA97D call 0040C84C ; D7.SysUtils.StrToInt(AnsiString):Integer;
005FA982 imul ebx, eax, 2710 ; 317C7580,eax=0018
00C9527C(13193852)是保存在edi中,上面有两处改变edi的地方,用红色标记出来了。
后来发现这个字符串是由vs_id得来,vs_id来决定注册版本的类型。
标准版:mov edi, 1
专业版:mov edi, 2
如果vs_id>=9,那么edi就是00C9527C(13193852)。
下面再看用户名生成key,共有三处变换:
第一处:
005FA982 imul ebx, eax, 2710 ; 317C7580,eax=0018
005FA988 push 0
005FA98A push 1317BEB ; Hex2Dec(1317BEB)= 20020203
005FA98F lea edx, [ebp-C]
005FA992 mov eax, [ebp-4]
005FA995 call 005FC364 ; 用户名变换
第二处:
005FA9BB push edx
005FA9BC push eax ; 版本号3.0*1000
005FA9BD lea edx, [ebp-28]
005FA9C0 mov eax, [ebp-C]
005FA9C3 call 005FC364 ; ***
第三处:
005FA9D3 mov eax, ebx
005FA9D5 cdq
005FA9D6 push edx
005FA9D7 push eax ;Int(vs_id得来的字符串)*0x2710
005FA9D8 lea edx, [ebp-30]
005FA9DB mov eax, [ebp-C]
005FA9DE call 005FC364 ; get key
再看看005FC364,这个call也不复杂,可以去看注册机源码:
function TFrmKeygen.NameTransform(var UserName:String;dwNumber:DWORD):String;
什么都清楚了。
还有几处小地方:
1.用户名长度区间 :[4,25]。
2.“95-120-124-111-113-115-125-126-111-124-111-110-42-96-111-124-125-115-121-110”
就是Unregistered Version。
由用户名的ascii的十进制加上10,但最后一个字符除外。
3.gc_id是使用次数
4.st_id是安装时间
下面就是写注册机了,
还有一点要说明的是:对于TStringList中不要求的字符串,更完美的办法是随机生成,为了简便起见,我没有这样写,大家可以看注册机源码。
这样,Auto2Fit v3.0的注册机就写出来了,哈哈,
我们再来写1stOptv1.0的注册机吧。
关键点就是找到key和流文件标签,以及TstringList中的结构。1stOpt.dll不用我们伪造,太好了!
在这里fish key:
00535850 mov edx, [ebp-14] ; key
00535853 mov eax, [ebp-8]
00535856 call 0053310C
; Procedure DecryptStream(ms : TmemoryStream; Const Key : string);
0053585B xor eax, eax
会发现key=mfit,是常量。
流文件标签在这里找到:
0062A7CB mov edx, 0062A978 ; ASCII "licensefile"
0062A7D0 mov eax, [ebp-8]
0062A7D3 call 00535634
; procedure THKStreams.GetStream(const ID: string; Dest: TStream);
TStringList的结构在这里看:
0062A7E6 mov edx, 64 ; version 1.0
0062A7EB mov eax, [ebp-18]
0062A7EE mov ebx, [eax]
0062A7F0 call [ebx+C]
0062A7F3 mov edx, [ebp-28]
0062A7F6 mov eax, 006CD8F8
0062A7FB call 004045B8
0062A800 lea ecx, [ebp-24]
0062A803 mov edx, 0A
0062A808 mov eax, [ebp-18]
0062A80B mov ebx, [eax]
0062A80D call [ebx+C]
0062A810 lea edx, [ebp-1C]
0062A813 mov eax, [ebp-24]
0062A816 call 00403360 ; StrtoInt
0062A81B mov ebx, eax ; 17(23)
0062A81D lea ecx, [ebp-24]
0062A820 mov edx, 14
0062A825 mov eax, [ebp-18]
0062A828 mov esi, [eax]
0062A82A call [esi+C]
0062A82D lea edx, [ebp-20]
0062A830 mov eax, [ebp-24]
0062A833 call 00403360 ; StrtoInt
0062A838 mov esi, eax ; 22(34)
0062A83A cmp dword ptr [ebp-1C], 0
0062A83E jnz short 0062A846
0062A840 cmp dword ptr [ebp-20], 0
0062A844 je short 0062A84D
0062A846 xor ebx, ebx
0062A848 jmp 0062A8EF
0062A84D lea ecx, [ebp-2C]
0062A850 mov edx, ebx
0062A852 mov eax, [ebp-18]
0062A855 mov ebx, [eax]
0062A857 call [ebx+C]
0062A85A mov eax, [ebp-2C]
0062A85D lea edx, [ebp-1C]
0062A860 call 00403360 ; StrtoInt
0062A865 mov [ebp-20], eax
0062A868 cmp dword ptr [ebp-1C], 0
0062A86C jnz short 0062A876
0062A86E mov eax, [ebp-20]
0062A871 mov [6CD8FC], eax
0062A876 cmp dword ptr [6CD8FC], 1E ; 过期,30天
0062A87D setg [6C8160]
0062A884 lea ecx, [ebp-30]
0062A887 mov edx, esi
0062A889 mov eax, [ebp-18]
0062A88C mov ebx, [eax]
0062A88E call [ebx+C]
0062A891 mov edx, [ebp-30]
0062A894 mov eax, 006CD8F4
0062A899 call 004045B8
0062A89E lea edx, [ebp-10]
0062A8A1 lea eax, [ebp-C]
0062A8A4 call 0069BEB0 ; GetCPUID and HardDiskID
0062A8A9 cmp dword ptr [ebp-1C], 0
0062A8AD jnz short 0062A8CC
0062A8AF lea ecx, [ebp-34]
0062A8B2 mov edx, 96
0062A8B7 mov eax, [ebp-18]
0062A8BA mov ebx, [eax]
0062A8BC call [ebx+C]
0062A8BF mov edx, [ebp-34] ;比较CPUID
0062A8C2 mov eax, [ebp-C]
; CPUID EDX 00CE8F48 ASCII "00000F29-0001080A-00004400-BFEBFBFF"
0062A8C5 call 00404910 ; D7.System.@LStrCmp;
0062A8CA je short 0062A8ED ; 要 jump
0062A8CC lea ecx, [ebp-38]
0062A8CF mov edx, 97
0062A8D4 mov eax, [ebp-18]
0062A8D7 mov ebx, [eax]
0062A8D9 call [ebx+C]
0062A8DC mov edx, [ebp-38] ;比较HardDiskID
0062A8DF mov eax, [ebp-10]
0062A8E2 call 00404910 ; D7.System.@LStrCmp;
0062A8E7 je short 0062A8ED
0062A8E9 xor ebx, ebx
0062A8EB jmp short 0062A8EF
0062A8ED mov bl, 1 ;置标志
总结出来TStringList的结构为:
1.版本号:Index为0x64
0062A7E6 mov edx, 64 ; version 1.0
2.使用天数index的指针在0A
0062A803 mov edx, 0A ; Ascii 23
3.用户名的index的指针的0x14
0062A820 mov edx, 14 ; Ascii 34
4. index:ox23,字符串0 ,是使用天数 <=30
5.index:0x34,字符串"xycheng",是用户名
6.index:0x96,指向CPUID
7.index:0x97,指向HardDiskID
其中,只要CPUID和HardDiskID之一与本机的相同,就认为是注册成功。
0A和0x14处的字符串应该是随机的(在范围之内),指向使用天数和用户名的index。
在注册机的编写中,为简便,固定了。详细算法见注册机源码。
1stOpt v1.0的注册机就这样轻松搞定,还没有Auto2Fit v3.0复杂。
要是能拿到v1.5的正式文件,呵护,1.5的注册文件也可以生成。
几近完美了,但程序中似乎有个bug,作者留给我们的,在Auto2Fitv3.0,1stOptv1.0和v1.5中都存在。列在下面一节中。
四、修复Bug:帮作者修复,同时学习Inline Patch
在1stOptv1.5、1.0和Auto2Fit v3.0中,算法设置,选项里:结果保存和参数值保存,只能设置一个,如图:
但是用键盘,可以在文本框中输入路径,运行后,可生成结果保存文件和参数值保存文件。但点旁边的文件按钮,却不能同时设置两个文本框中的路径。正式注册版也如此,看来是个bug了。
既然1stOptv1.5 被整成这个样子,何不把这个bug也修复一下。
0068F47A mov edx, [ebp-38]
0068F47D pop eax
0068F47E call 00404910 ; D7.System.@LStrCmp;
0068F483 jnz short 0068F4D6
0068F485 push 0
0068F487 push 0068F5E8 ; 文件”
0068F48C lea edx, [ebp-44]
0068F48F mov eax, ebx
0068F491 call 0046CC0C
0068F496 push dword ptr [ebp-44]
0068F499 push 0068F62C ; “已被用于保存结果文件,请试另一文件名!
0068F49E lea eax, [ebp-40]
0068F4A1 mov edx, 3
0068F4A6 call 004048C0
0068F4AB mov eax, [ebp-40] ; |
0068F4AE cx, [68F620] ; |
0068F4B5 xor edx, edx ; |
0068F4B7 call 0046E8C0 ; \1stOpt_u.0046E8C0
0068F4BC jmp short 0068F4D6
0068F4BE lea edx, [ebp-48]
0068F4C1 mov eax, ebx
0068F4C3 call 0046CC0C
0068F4C8 mov edx, [ebp-48]
0068F4CB mov eax, [esi+5C8]
0068F4D1 call 0044540C ; D7.Controls.TControl.SetText(TControl;TCaption);
0068F4D6 xor eax, eax
关键在这里:0068F483 jnz short 0068F4D6
跳到了 0068F4D6,正好把上面那个TControl.SetText跳过去了。
0068F47E处的比较,是看结果文件名和参数文件名相同否。
修改方法,是跳到0068F4BE,
0068F483 jnz short 0068F4BE
保存,运行,OK!
jnz short 0068F4D6 的机器码是75 51
jnz short 0068F4BE 的机器码是75 39
只改动一个byte就ok了。
1stOpt v1.0中:
006849C2 call 00404910 ; D7.System.@LStrCmp;
006849C7 jnz short 00684A1A ;应该跳到00684A02
[省略…]
00684A02 lea edx, [ebp-48]
00684A05 mov eax, ebx
[省略…]
00684A15 call 0044540C
00684A1A xor eax, eax
006849C7处的机器码由75 51改成75 39。
很简单,就一个byte,下面学着对v1.0来Inline patch一下,越是简单,用来学习入门越是有效果。
脱壳后的V1.0的EP是
006C0608 > $ 55 push ebp
基地址为00400000。
006C0608-00400000=002C0608
下面用ultraedit打开未脱壳的1stOpt.exe,搜索08 06 2C 00,找到唯一一处。
0010c4d2h: 08 06 2C 00 ; ..,.
将其改成:0077114B(code patch到的地方)-00400000=0037114B
0010c4d2h: 4B 11 37 00 ; ..,.
三个byte。这样程序解压后,首先跳到我们patch的地方0077114B。
然后把006849C7处的机器码由75 51改成75 39。
再跳到原来的入口006C0608。
在没有脱壳的v1.0文件中写下下面的汇编代码,找到机器码:
0077114B C605 C8496800 39 mov byte ptr [6849C8], 39
00771152 68 08066C00 push 006C0608
00771157 C3 retn
在rva=0077114B-00400000(Imagebase)=0037114B所对应的raw offset处写上上面的机器码。
用LordPE打开未脱壳的1stOpt.exe,可以看到002C06D0在.aspack段。
.aspack段的VOffset是00371000,VSize是00002000,ROffset是0010C400,RSize是00001A00。
Roffset=(0037114B-00371000)+ 0010C400=0010C54B
用LordPE的FLC可以验证。
用ultraedit跳到offset:0010C54B,然后写下上面的机器码:
C605C8496800396808066C00C3
保存之。Inline Patch成功。
参考了:http://www.pediy.com/bbshtml/BBS2/FORUM260.HTM
下面再用DUP来作个patcher玩玩。
DUP很好用。有兴趣的还可以借助the aPE来玩玩Inline patch。
【经验总结】:
1.尽管1stOptv1.5是个demo,但是v1.0和v1.5的关于这些基本功能的代码应该没有改变,所以可以直接把v1.0中的代码移植到v1.5中去。幸运的是修复call的时候,可以在1.5中找到与1.0中相对应的函数,主要也是根据函数的特征代码查找到的,要不然的话,要我去修改Import Table,再导入其他函数,累死我去,也许还不一定做得出来。
2.通过追本溯源,找到文件加密的核心组建HKStreams,这样就把主要精力集中在分析Auto2Fit v3.0的注册机制上,而不是把精力耗费在逆向HKStreams上了,这可是个开源的组件啊,.否则,让我去分析HKStreams的blowfish算法和LHA算法,那我磨掉我的意志力的,. 这也算是一种从大局着手的思想,也是代码复用的折射。另外,会编程,对于逆向具有很大的帮助,如DFCG的”我要”所说的,正向和逆向从来就不是对立的。
【致 谢】: Pediy,Unpack.cn,FCG,DFCG,PYG,FST,Exetools,ARTeam,Tuts4you,0wei的朋友
【杀青时间】:2006.09.21
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)