移花接木巧妙Crack 1stOptv1.5 Demo
追本溯源轻松Keygen 1stOptv1.0

【文章标题】:  移花接木巧妙Crack 1stOptv1.5 Demo
追本溯源轻松Keygen 1stOptv1.0
【文章作者】:  winndy
【联系方式】:  CNwinndy@hotmail.com
【目    标】:  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