首页
社区
课程
招聘
[原创]菜鸟挑战VB程序 - 跟踪经验和实例(之一)
2005-11-11 01:46 11863

[原创]菜鸟挑战VB程序 - 跟踪经验和实例(之一)

hud 活跃值
2
2005-11-11 01:46
11863
VB程序对于很多人初学者来说都比较难跟踪,对于我一个菜鸟来说,更是如此。个人感觉到难点主要在:

1. 断点难下

VB程序内部用的很函数和别的程序都不同,比如取注册码时别的程序常用GetDlgItemTextA,可是VB里却断不下来。VB中常调用MSVBVM60中的函数,常用的有__vbavartstne,__vbaR8Str,__vbastrcmp,
rtcGetPresentDate, rtcMsgBox等。

如果是明码比较的,一般可以用__vbaStrCmp可以断下。但这里断下时已经是结果比较,注册码的验证就跟踪不到了。

2. 代码分散

我们都知道,VB的运行效率在流行的几种编程语言中是比较低的,从代码跟踪中也可以看出来,汇编、C系列、Pascal语言等都很紧凑、连贯,VB程序中却非常分散。常常是一些简单动作,在VB反汇编中处理的代码经常重复冗长、跳转多、相距很远、夹杂很多不相关代码,这些都给代码跟踪分析带来很多困难。

3. 没有耐心和细心,在VB程序前将会很痛苦

(以上几点都是针对我这样的初学者而言)

我学习破解有一个多月了,通过了一些简单练习程序,最近成功拆解了好几个程序,尤其是happytown大侠的Crackme No. 6和No.7,受益非浅,于是决定再找个VB程序练练手。

[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞7
打赏
分享
最新回复 (26)
雪    币: 221
活跃值: (151)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
hud 2 2005-11-11 01:49
2
0
破解对象:sunson Crackme(Crack-No.1.exe)
         详见:本论坛【原创】本人写的第一个Crackme(http://bbs.pediy.com/showthread.php?s=&threadid=17508)
站内下载:http://bbs.pediy.com/upload/2005/37/files/crackme.rar_196.rar
相关信息:VB程序,没加壳
破解工具:Smartcheck 6.20, OllyDbg 1.10聆风听雨汉化第二版 

------------------------------------------------------------------

这个sunson Crackme我10来天前也试过1个多钟,找不到要点。我想既然是他的第一个Crackme,我想难度应该可以作为我的练习吧,加上我自己对VB编程还有些经验和信心,于是决定从这个程序入手。

我破解这个程序的方法比较笨(既然是菜鸟,我多花点时间我也没话说),希望高手能够指点更好些的方法。

先用Smartcheck打开,F5运行,用户名键入hud,注册码键入123123,点击“注册”按钮。返回SmartCheck中查看Event,可以看到在Form_Load中,调入一个常数字串sunson,在Click事件中:

        - 取得试练码及长度
        - 取得用户名及长度
        - 逐位和常数字串"POJE"比较

好!我们现在用Ollydbg打开,F9运行,输入同样的用户名hud和试练码123123,试验了几个常用断点都没成功,于是准备用笨办法:在OD中对“注册按钮”下消息断点(让大虾见笑了)!

在OD中选择察看窗口,在“注册”那一行点右键,选择“消息断点设置在ClassProc”,跳出消息选择对话框,在消息下拉框中,选择“202 WM_LBUTTONUP”,也就是在注册按钮上点击左键,当左键弹起时的消息上下断点。
雪    币: 221
活跃值: (151)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
hud 2 2005-11-11 01:52
3
0
返回sunson Crackme。点击“注册”按钮,OD中断在下面地址:

7343FF3F > $  5>push ebp                           ;  这里是ProcClass的入口

这里是消息处理函数,所以不要步入,否则掉进无穷无尽的循环中难以脱身。一路按F8步过,密切观察OD中央的信息窗口和右上的奇存器窗口,重点要关注以下几个字串:hud, 123123, POJE,我们输入的用户名、试练码以及SmartCheck侦查出来的对照字符串,然后来到这里(到这里多少都需要点耐心):
......
0040289F   > \8>mov ecx,dword ptr ss:[ebp-20]      ;  试练码出来了
004028A2   .  5>push ecx                           ;  下面取得试练码长度
004028A3   .  F>call dword ptr ds:[<&MSVBVM60.__vb>;  MSVBVM60.__vbaLenBstr
004028A9   .  8>mov edx,dword ptr ss:[ebp-1C]      ;  用户名出来了
004028AC   .  3>xor ebx,ebx
004028AE   .  8>test eax,eax
004028B0   .  0>setle bl
004028B3   .  5>push edx
004028B4   .  F>neg ebx                            ;  下面取得用户名长度
004028B6   .  F>call dword ptr ds:[<&MSVBVM60.__vb>;  MSVBVM60.__vbaLenBstr

上面代码意义不大,但我们通过一些蛛丝马迹可以知道:猎物已经不远了!继续按F8,来到这里:

00402937   .  F>call dword ptr ds:[<&MSVBVM60.__vb>;  MSVBVM60.__vbaStrMove
0040293D   .  8>mov ecx,dword ptr ds:[esi]
0040293F   .  8>lea edx,dword ptr ss:[ebp-19C]
00402945   .  8>lea eax,dword ptr ss:[ebp-20]
00402948   .  5>push edx
00402949   .  5>push eax
0040294A   .  5>push esi
0040294B   .  F>call dword ptr ds:[ecx+6FC]        ;  Crack-No.00402453,这里F7进入

在这里先下一个断点,以便有需要时重新再试时断下,然后按F7步入Call内。

如果有和我水平差不多的初学者或许会问:为什么这里要步进呢?

注意右边有OD提示的这个Call的名称:Crack-No.00402453,这个名称是第一个以我们调试的程序名开始的,而上面其它的Call注释的名称都是以MSVBVM60、USER32之类开始的系统函数的调用。既然是用户程序的代码,我们当然要进去看看。

以下也是一样,前面可以先用F8全部走一遍摸摸大体的情况。然后回头再跟,如果是系统的Call,一般用F8步过,如果发现有异常(比如:hud, 123123, POJE等字串),看情况决定是否步入;如果是用户代码,一般要进去看看。

进入Call内部代码后,继续按F8,密切观察OD中央的信息窗口和右上的奇存器窗口,继续关注hud, 123123, POJE字串及其变形字串。

00403303   . >call dword ptr ds:[<&MSVBVM60.__>;  MSVBVM60.__vbaStrVarMove
00403309   . >mov ebx,dword ptr ds:[<&MSVBVM60>;  这里字符串常数POJE出现在了eax中

继续按F8,下面就是关键的第一部分注册码验证了:

0040335B   > >cmp esi,dword ptr ss:[ebp-104]      ;  从1到4("POJE"长度)逐位循环比较,ESI是当前比较的位数
00403361   . >jg Crack-No.004034F0                ;  小于或等于4则继续
00403367   . >mov edi,dword ptr ss:[ebp+C]
0040336A   . >mov eax,dword ptr ds:[edi]          ;  载入用户名hud到eax
0040336C   . >push eax
0040336D   . >call dword ptr ds:[<&MSVBVM60.__vba>;  MSVBVM60.__vbaLenBstr
00403373   . >mov ecx,eax                         ;  上面取得用户名长度到ecx
00403375   . >mov eax,esi                         ;  eax=当前循环到的位
00403377   . >cdq
00403378   . >idiv ecx                            ;  除以用户名长度
0040337A   . >mov dword ptr ss:[ebp-64],3
00403381   . >mov dword ptr ss:[ebp-4C],1
00403388   . >mov dword ptr ss:[ebp-54],2
0040338F   . >mov dword ptr ss:[ebp-5C],edx
00403392   . >mov edx,dword ptr ds:[edi]
00403394   . >push edx
00403395   . >call dword ptr ds:[<&MSVBVM60.__vba>;  MSVBVM60.__vbaLenBstr
0040339B   . >mov ecx,eax
0040339D   . >mov eax,esi
0040339F   . >cdq
004033A0   . >idiv ecx                            ;  当前位除以用户名长度,取其余数

......

004033F7   . >call dword ptr ds:[<&MSVBVM60.__vba>;  MSVBVM60.__vbaI4Var
004033FD   . >mov edx,dword ptr ds:[edi]          ; |将用户名(hud)放入edx
004033FF   . >push eax                            ; |Arg2
00403400   . >push edx                            ; |Arg1
00403401   . >call dword ptr ds:[<&MSVBVM60.#631>>; \rtcMidCharBstr
00403407   . >mov edx,eax
00403409   . >lea ecx,dword ptr ss:[ebp-30]
0040340C   . >call ebx
0040340E   . >push eax                            ; /Arg1
0040340F   . >call dword ptr ds:[<&MSVBVM60.#516>>; \从用户名中取出一位字符
00403415   . >mov ecx,dword ptr ss:[ebp-28]       ;  "POJE"放入eax
00403418   . >movsx edi,ax                        ;  将刚才取到的用户名字符放到edi
0040341B   . >lea eax,dword ptr ss:[ebp-44]
0040341E   . >push eax                            ; /Arg3
0040341F   . >push esi                            ; |Arg2
00403420   . >push ecx                            ; |Arg1
00403421   . >call dword ptr ds:[<&MSVBVM60.#631>>; \rtcMidCharBstr
00403427   . >mov edx,eax
00403429   . >lea ecx,dword ptr ss:[ebp-2C]
0040342C   . >call ebx
0040342E   . >push eax                            ; /Arg1
0040342F   . >call dword ptr ds:[<&MSVBVM60.#516>>; \从POJE中取一字符
00403435   . >movsx edx,ax                        ;  将POJE中取到的字符放到edx
00403438   . >lea eax,dword ptr ss:[ebp-30]
0040343B   . >lea ecx,dword ptr ss:[ebp-2C]
0040343E   . >push eax
0040343F   . >push ecx
00403440   . >push 2
00403442   . >xor edi,edx                         ;  用户名中字符和POJE中逐字符XOR
00403444   . >call dword ptr ds:[<&MSVBVM60.__vba>;  MSVBVM60.__vbaFreeStrList
0040344A   . >lea edx,dword ptr ss:[ebp-84]
00403450   . >lea eax,dword ptr ss:[ebp-74]
00403453   . >push edx
00403454   . >lea ecx,dword ptr ss:[ebp-64]
00403457   . >push eax
00403458   . >push ecx
00403459   . >lea edx,dword ptr ss:[ebp-54]
0040345C   . >lea eax,dword ptr ss:[ebp-C4]
00403462   . >push edx
00403463   . >lea ecx,dword ptr ss:[ebp-44]
00403466   . >push eax
00403467   . >push ecx
00403468   . >push 6
0040346A   . >call dword ptr ds:[<&MSVBVM60.__vba>;  MSVBVM60.__vbaFreeVarList
00403470   . >mov edx,dword ptr ss:[ebp-20]
00403473   . >mov eax,edi
00403475   . >mov dword ptr ss:[ebp-AC],edx
0040347B   . >mov ecx,1A
00403480   . >cdq
00403481   . >idiv ecx                            ;  XOR后的结果除以1A
00403483   . >add esp,28
00403486   . >mov dword ptr ss:[ebp-B4],8
00403490   . >add edx,41                          ;  edx(除后余数)+41
00403493   . >jo Crack-No.00403600
00403499   . >push edx                            ; /Arg2
0040349A   . >lea edx,dword ptr ss:[ebp-44]       ; |
0040349D   . >push edx                            ; |Arg1
0040349E   . >call dword ptr ds:[<&MSVBVM60.#608>>; \rtcVarBstrFromAnsi
004034A4   . >lea eax,dword ptr ss:[ebp-B4]
004034AA   . >lea ecx,dword ptr ss:[ebp-44]
004034AD   . >push eax                            ; /Arg3
004034AE   . >lea edx,dword ptr ss:[ebp-54]       ; |
004034B1   . >push ecx                            ; |Arg2
004034B2   . >push edx                            ; |Arg1
004034B3   . >call dword ptr ds:[<&MSVBVM60.__vba>; \__vbaVarCat
004034B9   . >push eax                            ;  将结果字符相连
004034BA   . >call dword ptr ds:[<&MSVBVM60.__vba>;  MSVBVM60.__vbaStrVarMove
004034C0   . >mov edx,eax                         ;  edx是连接后的字符
004034C2   . >lea ecx,dword ptr ss:[ebp-20]
004034C5   . >call ebx
004034C7   . >lea eax,dword ptr ss:[ebp-54]
004034CA   . >lea ecx,dword ptr ss:[ebp-44]
004034CD   . >push eax
004034CE   . >push ecx
004034CF   . >push 2
004034D1   . >call dword ptr ds:[<&MSVBVM60.__vba>;  MSVBVM60.__vbaFreeVarList
004034D7   . >mov eax,1
004034DC   . >add esp,0C
004034DF   . >add eax,esi
004034E1   . >jo Crack-No.00403600
004034E7   . >mov esi,eax
004034E9   . >xor edi,edi
004034EB   .^>jmp Crack-No.0040335B               ;  循环回去

上面一段的算法是:
1. 从1到4循环(设为i),用i除用户名长,商如果大于0就取商,否则取余数(以便应付用户名不足4位时的情况),设这里所取数为j;
2. 依次取POJE(i)和用户名(j)进行XOR,设结果为x;
3. x 除以1A后取余数,设为y;
4. y + 0x41就是前4位注册码的每位。

用户名为hud时前4位正确的注册码是EGIT,在这里设个断点,按F9,然后回到Crackme,将试练码改为EGIT,按F9一直到刚才设下的断点处,然后F8单步跳转到这里:

0040352E   > >mov eax,dword ptr ss:[ebp-20]    ;  这里是前面根据用户名算出来的字符串EGIT
00403531   . >mov ecx,dword ptr ss:[ebp-2C]    ;  这是是输入的试练码
00403534    .  50    push eax
00403535    .  6A 04 push 4
00403537    .  51    push ecx
00403538    .  FF15 >call dword ptr ds:[<&MSVBVM60.#616>;  MSVBVM60.rtcLeftCharBstr
0040353E    .  8BD0  mov edx,eax                        ;  上面取试练码前4位
00403540    .  8D4D >lea ecx,dword ptr ss:[ebp-30]
00403543    .  FFD3  call ebx
00403545    .  50    push eax
00403546    .  FF15 >call dword ptr ds:[<&MSVBVM60.__vb>;  MSVBVM60.__vbaStrCmp
0040354C    .  F7D8  neg eax                            ;  上面比较前4位,如果相同则eax=0
0040354E    .  1BC0  sbb eax,eax
00403550    .  8D55 >lea edx,dword ptr ss:[ebp-30]
00403553    .  40    inc eax
00403554    .  52    push edx
00403555    .  F7D8  neg eax
00403557    .  8945 >mov dword ptr ss:[ebp-24],eax
0040355A    .  8D45 >lea eax,dword ptr ss:[ebp-2C]
0040355D    .  50    push eax
0040355E    .  6A 02 push 2
00403560    .  FF15 >call dword ptr ds:[<&MSVBVM60.__vb>;  MSVBVM60.__vbaFreeStrList
00403566    .  83C4 >add esp,0C
00403569    .  8D4D >lea ecx,dword ptr ss:[ebp-34]
0040356C    .  FF15 >call dword ptr ds:[<&MSVBVM60.__vb>;  MSVBVM60.__vbaFreeObj
00403572    .  68 D7>push Crack-No.004035D7
00403577    .  EB 4D jmp short Crack-No.004035C6

从这里一直退出当前Call(第一部分比较),一路F8下去:
......
00402981    .  66:3B>cmp bx,di                          ;  如果前4位相等,这里为0

如果不相等时di=0000, bx=FFFF,就会跳过第二部分的验证,要爆破这里可以作为第一个爆破点。

00402984    . /0F85 >jnz Crack-No.00402D22        ; 这里不能跳,跳就over了
雪    币: 221
活跃值: (151)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
hud 2 2005-11-11 01:54
4
0
一路F8下去:
.......
004029C5    > |8B45 >mov eax,dword ptr ss:[ebp-1C]      ;  这里再次取出用户名,开始处理后面部分
004029C8    .  8D55 >lea edx,dword ptr ss:[ebp-38]
004029CB    .  52    push edx                           ; /Arg3
004029CC    .  6A 05 push 5                             ; |Arg2 = 00000005,表示从第5位开始取
004029CE    .  50    push eax                           ; |Arg1
004029CF    .  C745 >mov dword ptr ss:[ebp-30],80020004 ; |
004029D6    .  C745 >mov dword ptr ss:[ebp-38],0A       ; |
004029DD    .  8D5E >lea ebx,dword ptr ds:[esi+38]      ; |
004029E0    .  FF15 >call dword ptr ds:[<&MSVBVM60.#631>; \rtcMidCharBstr
004029E6    .  8BD0  mov edx,eax                        ;  上面取出用户名第5位以后的字符

一路F8下去:
.......
00402A55    .  FF15 >call dword ptr ds:[<&MSVBVM60.__vb>;  MSVBVM60.__vbaHresultCheckObj
00402A5B    >  8B55 >mov edx,dword ptr ss:[ebp-1C]      ;  这里取出试练码
00402A5E    .  8D4D >lea ecx,dword ptr ss:[ebp-38]
00402A61    .  51    push ecx                           ; /Arg3
00402A62    .  6A 05 push 5                             ; |Arg2 = 00000005
00402A64    .  52    push edx                           ; |Arg1
00402A65    .  C745 >mov dword ptr ss:[ebp-30],80020004 ; |
00402A6C    .  C745 >mov dword ptr ss:[ebp-38],0A       ; |
00402A73    .  FF15 >call dword ptr ds:[<&MSVBVM60.#631>; \rtcMidCharBstr
00402A79    .  8BD0  mov edx,eax                        ;  上面将第5位度练码以后部分取出

一路F8下去:
......
00402AB0    .  FF91 >call dword ptr ds:[ecx+700]        ;  Crack-No.00402460,这里要F7步入

进去后:

0040373B    .  FF15 >call dword ptr ds:[<&MSVBVM60.__vb>        ;  这里出现一串字符zfzhui
00403741    .  8BD0  mov edx,eax                        
......

0040378F    .  6A 0B push 0B
00403791    .  FF15 >call dword ptr ds:[<&MSVBVM60.__vb>;  MSVBVM60.__vbaFreeVarList
00403797    .  83C4 >add esp,30
0040379A    .  6A 01 push 1
0040379C    .  FF15 >call dword ptr ds:[<&MSVBVM60.__vb>;  MSVBVM60.__vbaOnError
004037A2    .  8B75 >mov esi,dword ptr ss:[ebp+10]
004037A5    .  8B0E  mov ecx,dword ptr ds:[esi]
004037A7    .  51    push ecx
004037A8    .  8B1D >mov ebx,dword ptr ds:[<&MSVBVM60._>;  MSVBVM60.__vbaLenBstr
004037AE    .  FFD3  call ebx                           ;  <&MSVBVM60.__vbaLenBstr>
004037B0    .  83F8 >cmp eax,4                          ;  上面得到第5位后试练码的长度,和4比较
004037B3    . /0F8C >jl Crack-No.00403C50               ;  小于4位则跳,好像不能跳,一跳就结束了

在这里先下个断点,以便等下回来。如果小于4位就到下面:

00403C50    > \6>push 0                            ; /Arg1 = 00000000
00403C52    .  F>call dword ptr ds:[<&MSVBVM60.__v>; \__vbaStrBool
00403C58    .  8>mov edx,eax                       ;  这里将Bool转为字符,如果是False就Over了

所以刚才的试练码不行,除前4位(EGIT)的后面至少还要4位,所以将试练码改为EGIT1234(用1234方便跟踪位置)。为了说明简便,下面将用户名第5位以后部分简称"后用户名",将试练(注册)码第5位以后部分简称"后试练(注册)码"(有些别扭,请原谅)。

按F9一直到上边断点(004037B3)处,然后F8单步:

004037B9    .  6>push -1                           ; /Arg1 = FFFFFFFF
004037BB    .  F>call dword ptr ds:[<&MSVBVM60.__v>; \__vbaStrBool
004037C1    .  8>mov edx,eax                       ;  先载入-1,化为字符"True"
004037C3    .  8>lea ecx,dword ptr ss:[ebp-28]
004037C6    .  F>call edi
004037C8    .  8>mov edx,dword ptr ds:[esi]
004037CA    .  5>push edx                          ;  1234,后试练码
004037CB    .  F>call ebx
004037CD    .  9>cdq
004037CE    .  2>sub eax,edx
004037D0    .  D>sar eax,1
004037D2    .  8>mov dword ptr ss:[ebp-168],eax
004037D8    .  B>mov esi,1
004037DD    >  3>cmp esi,eax                       ;  循环位数大过"后试练码"就停止循环
004037DF    .  0>jg Crack-No.00403C63
004037E5    .  8>mov eax,dword ptr ss:[ebp+8]
004037E8    .  8>mov ecx,dword ptr ds:[eax+38]     ;  取出(第5位)后用户名
004037EB    .  5>push ecx
004037EC    .  F>call ebx                          ;  取得后用户名位长
004037EE    .  8>test eax,eax
004037F0    .  C>mov dword ptr ss:[ebp-50],1
004037F7    .  C>mov dword ptr ss:[ebp-58],2
004037FE    .  8>lea edx,dword ptr ss:[ebp-58]
00403801    .  5>push edx                          ; /Arg3
00403802    .  8>mov eax,esi                       ; |
00403804    .  0>jg Crack-No.00403A1D              ; |
0040380A    .  6>imul eax,eax,2                    ; |eax*2
0040380D    .  0>jo Crack-No.00403D1E              ; |
00403813    .  8>sub eax,1                         ; |eax -= 1;
00403816    .  0>jo Crack-No.00403D1E              ; |
0040381C    .  5>push eax                          ; |Arg2
0040381D    .  8>mov ecx,dword ptr ss:[ebp+10]     ; |
00403820    .  8>mov edx,dword ptr ds:[ecx]        ; |
00403822    .  5>push edx                          ; |Arg1
00403823    .  F>call dword ptr ds:[<&MSVBVM60.#63>; \rtcMidCharBstr
00403829    .  8>mov edx,eax
0040382B    .  8>lea ecx,dword ptr ss:[ebp-34]
0040382E    .  F>call edi
00403830    .  5>push eax
00403831    .  F>call dword ptr ds:[<&MSVBVM60.#58>; \
00403837    .  D>fstp qword ptr ss:[ebp-160]        ;  取后试练码循环位字符,转化为浮点       
0040383D    .  8>mov eax,dword ptr ss:[ebp-24]    ;  载入常数字串"zfzhui"

如果用户名第5位到第10位(共6位)不足6位的部分就用常数字串"zfzhui"相应位填充,用户名固定为10位长。

00403840    .  5>push eax
00403841    .  F>call ebx                          ;  "zfzhui"长度=6
00403843    .  8>mov ecx,eax

F8单步......

004038B6    .  B>mov eax,1                         ;  这里eax先置为1(True)
004038BB    .  8>mov dword ptr ss:[ebp-40], eax     ;  Bool = True

通过前面00403C58、004037C1类似情况,我们可以推测,堆栈中应该是VB中的一个Boolean变量,在注册码各个位长的判断中,如果不符合条件,就会设为False,也就game over了,有编程经验的朋友应该都容易了解。这里先设置为True。

004038BE    .  B>mov ecx,2
004038C3    .  8>mov dword ptr ss:[ebp-48],ecx
004038C6    .  8>mov dword ptr ss:[ebp-90],eax
004038CC    .  8>mov dword ptr ss:[ebp-98],ecx
004038D2    .  8>lea ecx,dword ptr ss:[ebp-48]
004038D5    .  5>push ecx                          ; /Arg3
004038D6    .  8>mov edx,esi                       ; |esi为循环变量,移入edx
004038D8    .  6>imul edx,edx,2                    ; |edx*2,  edx循环位,这是在"后试练码"中取字符的位置
004038DB    .  0>jo Crack-No.00403D1E              ; |
004038E1    .  5>push edx                          ; |Arg2
004038E2    .  8>mov eax,dword ptr ss:[ebp+10]     ; |
004038E5    .  8>mov ecx,dword ptr ds:[eax]        ; |后试练码
004038E7    .  5>push ecx                          ; |Arg1
004038E8    .  F>call dword ptr ds:[<&MSVBVM60.#63>; \rtcMidCharBstr
004038EE    .  8>mov edx,eax
004038F0    .  8>lea ecx,dword ptr ss:[ebp-30]
004038F3    .  F>call edi
004038F5    .  5>push eax                          ; /Arg1
004038F6    .  F>call dword ptr ds:[<&MSVBVM60.#51>; \rtcAnsiValueBstr,取“后试练码”的循环位+1位
004038FC    .  6>sub ax,41                         ;  减41

取“后试练码”的循环位+1位ASCII码值,然后减0x41。

00403900    . /0>jo Crack-No.00403D1E
00403906    . |0>movsx edx,ax
00403909    . |8>mov dword ptr ss:[ebp-17C],edx
0040390F    . |D>fild dword ptr ss:[ebp-17C]       ;  “后试练码”的(循环位+1)位 - 0x41 的结果
00403915    . |D>fstp qword ptr ss:[ebp-184]
0040391B    . |D>fld qword ptr ss:[ebp-160]        ;  循环位,这里决定要乘几个26
00403921    . |D>fmul qword ptr ds:[401160]        ;  循环位*26.
00403927    . |D>fadd qword ptr ss:[ebp-184]       ;  -15. + 循环位*26. = 11.
0040392D    . |D>fstsw ax
0040392F    . |A>test al,0D
00403931    . |0>jnz Crack-No.00403D19
00403937    . |F>call dword ptr ds:[<&MSVBVM60.__v>;  MSVBVM60.__vbaFpI4
0040393D    . |8>mov ebx,eax                       ;  化为整数
0040393F    . |8>lea eax,dword ptr ss:[ebp-98]
00403945    . |5>push eax
00403946    . |8>lea ecx,dword ptr ss:[ebp-88]
0040394C    . |5>push ecx
0040394D    . |F>call dword ptr ds:[<&MSVBVM60.__v>;  MSVBVM60.__vbaI4Var
00403953    . |5>push eax                          ; |Arg2
00403954    . |8>mov edx,dword ptr ss:[ebp-24]     ; |
00403957    . |5>push edx                          ; |"zfzhui"入栈
00403958    . |F>call dword ptr ds:[<&MSVBVM60.#63>; \rtcMidCharBstr
0040395E    . |8>mov edx,eax
00403960    . |8>lea ecx,dword ptr ss:[ebp-38]
00403963    . |F>call edi
00403965    . |5>push eax                          ; /Arg1
00403966    . |F>call dword ptr ds:[<&MSVBVM60.#51>; \rtcAnsiValueBstr
0040396C    . |0>movsx eax,ax                      ;  取zfzhui的第一位z
0040396F    . |3>xor ebx,eax                       ;  7A('z') XOR 0B = 71('q')

这一段取从1到"后试练码"长度循环,设循环位为i,i * 26 + "后试练码"[i+1]位 - 0x41,与"后用户名"[i]进行XOR,得到结果字符Target string。
继续F8 ......

004039FF    .  8>mov eax,dword ptr ds:[edx+34]     ;  载入常数字串"sunson"
00403A02    .  5>push eax
00403A03    .  8>mov ecx,dword ptr ss:[ebp-2C]
00403A06    .  5>push ecx
00403A07    .  6>push 0
00403A09    .  F>call dword ptr ds:[<&MSVBVM60.__v>;  MSVBVM60.__vbaInStr
00403A0F    .  8>cmp eax,1                         ;  上面函数看Target string是否是"sunson"前面相应位
00403A12    .  0>jnz Crack-No.00403C50             ;  这里eax要=1,否则跳转就over了

我们可以再次用SmartCheck打开Crackme No.1,输入hud, EGIT1234,F5运行,然后点击Event中的Click,可以看到下面的反编译:

Double(11)--> Long(11)        ; CLong
Mid$(String str = 00153A4C
        = "zfzhui"
 Long start = 1 0x00000001
ASC returns integer: 122
 String string = 0014DB3C
 = "z"
Chr
 Integer charcode = 113 0x0071
Instr returns LONG: 0
 Long start = 1 0x00000001
 String string1 = 0014DB14
        = "sunson"
 String string2 = 0014DAD4
        = "q"
 Integer compare = 0 0x0000
Boolean(False)--> String("False")
String("False")--> Boolean(False)

上面这一段SmartCheck的反编译代码应该很清楚,和上面是一致的,可以帮助我们更加清楚地了解上面代码。

先在上面(00403A12这里)下个断点,等会还要回来。F8继续就跳转了:

00403C50    > \6>push 0                            ; /Arg1 = 00000000
00403C52    .  F>call dword ptr ds:[<&MSVBVM60.__v>; \__vbaStrBool
00403C58    .  8>mov edx,eax                       ;  这里将Bool转为字符,如果是False就Over了

上面一段判断计算出来的'q'在不是字串'sunson'第1个字母,翻译成VB,应该类似于:

str = "sq"
If Instr("sunson", str)<>1 Then
 Bool = True
 Continue!
Else
 Bool = False
 Game over!
End If

第二部分的算法到这里已经明了:

1. 用户名Name共10位后6位不足的用"zfzhui"填充;
2. 注册码最少8位;
3. 循环变量(设为 i )从1循环到(注册码长度-4);
4. 每次循环取2位,前面1位化为浮点,后面一位是取"后注册码"字符的位置;
5. SNo[i+1] - 0x41 + i * 0x26,设为x;
5. Name[i] XOR x, 设为y;
6. 如果y[1]y[2]y[x]等于"sunson"前x位就继续,否则over。

后面我们做注册机时就取最小位数4位。
雪    币: 221
活跃值: (151)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
hud 2 2005-11-11 02:13
5
0
下面是算法总结及C语言注册机:

/*********************************************************************

sunson's Crackme No.1 C语言注册机 (Win-TC)

By hud, November 12, 2005

-----------------------------------------------------------------------
算法:

前4位算法是:

1. 从1到4循环(设为i);
2. 用i除用户名长;
3. 商如果大于0就取商,否则取余数(如果用户名小于4位),设这个数为j;
4. 依次取POJE(i)和用户名(j)进行XOR,设结果为x;
3. x 除以1A后取余数,设为y;
4. 依次得到的y+41相连即是前4位注册码,设为Z。

后6位算法是

第二部分的算法到这里已经明了:

1. 用户名Name共10位后6位不足的用zfzhui填充;
2. 注册码最少8位;
3. 变量(设为 i )从1循环到(注册码长度-4);
4. 每次循环取2位,前面1位化为浮点,后面一位是取"后注册码的位数";
5. SNo[i+1] - 0x41 + i * 0x26,设为x;
5. (后6位)Name[i/2] XOR x, 设为y;
6. 如果各位相连的y在"sunson"中,继续,否则over。

后面我们就取最小位数4位。

*********************************************************************/               
#include <stdio.h>               

void get_num(char sn[9], int i, char name)               
{               
    char c, s1[] = "sunson";               
    int k=33, pos, x;                      /* k从显示字符开始 */               

    for (pos=0; pos<10; ++pos)             /* pos确定乘几个26 */               
    {               
        while (k<127)               
            {       
            x = k - 0x41 + pos * 26;               
            c = x ^ name;               
            if (s1[i/2]==c)               
                    {
                sn[4+i] = pos + 0x30;                               
                sn[4+i+1] = k;                               
                    return;                       
                    }               
                else                       
                ++k;                               
            }                       
    }                               
}                               

void main()                               
{                               
    char n1[11], sn1[9];                    /* 用户名,注册码        */                       
    char n2[7];                                 /* 用户名后6位                */       
    char s1[] = "POJE",                                
         s3[] = "zfzhui";                       /* 几个常数                        */
    int len, pos;
    int i;

    printf("Input name:\n");
    scanf("%s", n1);
    len = strlen(n1);

    if (len==0)
        return;

    for (i=0; i<4; ++i)                     /* 前4位判断        */
    {
        pos = i/(len-1) > 0 ? i/(len-1)-1 : i%len;
        sn1[i] = (n1[pos] ^ s1[i]) % 0x1A + 0x41;
    }
   
    for (i=0; i<6; ++i)                     /* 如不足10位填充s3  */
        if (i+4>=len)
            n2[i] = s3[i];
        else
            n2[i] = n1[i+4];

    n2[i] = '\0';

    for (i=0; i<4; ++i)                     /* 后面最小4位判断   */
    {
        get_num(sn1, i, n2[i/2]);
        ++i;
    }
    sn1[8] = '\0';
    printf("SN is: %s\n", sn1);
    getch();
}

????????????????????????????????????????

终于破解完成并且写完破文,好累!

也许有初学者想问问,我总共花了多少时间(嗨,真是那壶不开提那壶)?

不过,不多,5个晚上,每晚5个钟左右!(晕倒,这还不多)

比起一些高手来,十几分钟破解一个软件的算法,那是太多了!不过我是在菜鸟阶段,我知道不比别人多花些时间是很难进步的,我原来准备用一个月的时间攻下来,还好,只用了6分之一,对我来说自然不多。

想到像我这样的初学者,破解一个软件如此辛苦,我尽量详细一些把这个过程写下来,如果对部分初学者能有所帮助,我也没算白熬几个晚上了。

还有很多地方不尽人意的地方,比如开始部分,下消息断点效率实在太低,按了好长时间的F8也没动静,又怕有什么重要的地方漏掉了。中间也是笨办法,一步步地走,应该有更有效率的方法吧。如果朋友们有对付VB程序的好办法,请多多指教。

还有个感觉就是:汇编基础偏弱,很多地方似是而非,浪费了不少时间。

另外,用户名如果超出10位,注册码超出8位等情况没去验证。
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ggosgg 2005-11-11 08:38
6
0
顶下慢慢看..........
雪    币: 234
活跃值: (370)
能力值: ( LV9,RANK:530 )
在线值:
发帖
回帖
粉丝
lnn1123 13 2005-11-11 09:23
7
0
雪    币: 2367
活跃值: (756)
能力值: (RANK:410 )
在线值:
发帖
回帖
粉丝
小虾 10 2005-11-11 10:11
8
0
不错,写得很详细。
雪    币: 214
活跃值: (15)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
ljy3282393 1 2005-11-11 18:38
9
0
不错,写得很详细。非常适合俺等菜鸟学习
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
bluelf 2005-11-12 15:18
10
0
好好学习。
楼主让我感动,奋战了5个晚上
想我弄个vb cracke me才三个钟头就没信心了,惭愧~~~
雪    币: 221
活跃值: (151)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
hud 2 2005-11-12 17:09
11
0
多谢朋友们的支持!既然有朋友们的鼓励,我正准备再写个续篇出来。
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
msvcp60 2005-11-12 19:07
12
0
请问LZ,他的CRACK NO 2你研究出来了吗?比这个难啊
雪    币: 245
活跃值: (55)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
matali 2005-11-12 23:59
13
0
最初由 hud 发布
多谢朋友们的支持!既然有朋友们的鼓励,我正准备再写个续篇出来。


双手双脚支持
雪    币: 271
活跃值: (196)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
sunson 2005-11-14 09:07
14
0
算法:

前4位算法是:

1. 从1到4循环(设为i);
2. 用i除用户名长;
3. 商如果大于0就取商,否则取余数(如果用户名小于4位),设这个数为j;
4. 依次取POJE(i)和用户名(j)进行XOR,设结果为x;
3. x 除以1A后取余数,设为y;
4. 依次得到的y+41相连即是前4位注册码,设为Z。

后6位算法是

第二部分的算法到这里已经明了:

1. 用户名Name共10位后6位不足的用zfzhui填充;
2. 注册码最少8位;
3. 变量(设为 i )从1循环到(注册码长度-4);
4. 每次循环取2位,前面1位化为浮点,后面一位是取"后注册码的位数";
5. SNo[i+1] - 0x41 + i * 0x26,设为x;
5. (后6位)Name[i/2] XOR x, 设为y;
6. 如果各位相连的y在"sunson"中,继续,否则over。

后面我们就取最小位数4位。

*********************************************************************/


总的来说分析的很不错,只是第二部分存在一点点错误
第一,用户名没有长度限制
第二,如果不足四位用户名,不是用zfzhui填充,而是用sunson填充,与zfzhui异或
第三,如果用户名大于四位,则是与sunson异或,得到的结果与用户名四位以后的字符进行比较
雪    币: 221
活跃值: (151)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
hud 2 2005-11-15 01:33
15
0
谢谢sunson的点评!

因为是我的第1个VB破解练习,又花了不少时间,当时知道还有些地方还没有验证完毕,也没去进一步深究。

这两天太忙,有时间我修正一下。再次感谢你,给我一个很好的Crackme练习!
雪    币: 221
活跃值: (151)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
hud 2 2005-11-15 13:10
16
0
现在做一些修改,将上次的错误修正并将上次没判断的情况补上,还有上次的注册机也有些问题,一并改过。

断点修改一下,下到vbaLenBStr,再输入1位Name和试练码(根据前面的注册机做):
Name:  h
Serial: ENIT0J0T

断在下面:

004028A3    .  F>call dword ptr ds:[<&MSVBVM60.__vbaLenBstr>;  MSVBVM60.__vbaLenBstr

发现寄存器中已经有试练码,改换到前面2句下断就行了:

0040289F    > \8>mov ecx,dword ptr ss:[ebp-20]              ;  试练码出来了
004028A2    .  5>push ecx                                   ;  下面取得试练码长度
004028A3    .  F>call dword ptr ds:[<&MSVBVM60.__vbaLenBstr>;  MSVBVM60.__vbaLenBstr
004028A9    .  8>mov edx,dword ptr ss:[ebp-1C]

后面的和前面分析差不多,只是Name长度是小于5和5位以上有不同的判断,一并在下面的算法和注册机中一次性改过。

/*********************************************************************

sunson's Crackme No.1 C语言注册机 (Win-TC)

By hud, November 12, 2005 (Rev-A)

By hud, November 15, 2005 (Rev-B)

-----------------------------------------------------------------------

算法:

前4位算法是:

1. 从1到4循环(设为i);
2. 如果Name只有1位,1到4的循环都取这1位;
3. 如果Name多于1位,取 i Mod len位。设3、4两步取的位为j;
4. POJE(i) XOR Name(j),设结果为x;
3. x Mod 1A,设为y;
4. 依次得到的y+41相连即是前4位注册码,设为Z。

后6位算法是(用户名Name小于5位情况):

1. 用户名至少1位,Name小于5位的后6位用"sunson"填充;
2. 注册码后面部分长度最少4位,多的不计;
3. 变量(设为 i )从1循环到4;
4. 每次循环注册码后面部分取2位,前面1位化为浮点,后面一位化为ASCII值;
5. SNo[i+1] - 0x41 + i * 0x26,设为x;
5. 如果Name小于5位,x和"zfzhui"前2位XOR, 5位以上则和"sunson"前2位XOR。
  设为y;
6. 如果Name小于5位,[y1][y2] 应是"su"; Name5位以上[y1][y2]则应是"on";

假如不是作练习,这个算法有些缺陷:
1. 用户名实际只取前3位,在小于5位的情况下,hud0, hud1, hud...z是一样
的;
2. 所有大于4位的用户名,也是实际检测前4位,只不过后面用sonsun填充,
也就是说,hud01, hud02, hud03..., hud0abcde...,只要前4位一样,注册
码全是一样的。

*********************************************************************/
#include <stdio.h>

int len;

void get_num(char sn[9], int i, char name)
{
    char c, s1[] = "zf", s2[]="on";
    int k=33, pos, x;                      /* k从显示字符开始 */

    for (pos=0; pos<10; ++pos)             /* pos确定乘几个26 */                                       
    {
        while (k<127)
            {
            x = k - 0x41 + pos * 26;
            c = x ^ name;
            if ((len<5 && s1[i/2]==c) |
                                (len>4 && s2[i/2]==c))
                    {
                sn[4+i] = pos + 0x30;
                sn[4+i+1] = k;
                    return;
                    }
                else
                ++k;
            }
    }
}

void main()
{
    char n1[11], sn1[9];                    /* 用户名,注册码        */
    char n2[7];                                 /* 用户名后6位                */
    char s1[] = "POJE",
         s2[] = "sunson";                       /* 几个常数                        */
    int pos;
    int i;

    printf("Input name:\n");
    scanf("%s", n1);
    len = strlen(n1);

    if (len==0)
        return;

        for (i=0; i<4; ++i)                     /* 前4位判断        */
        {
                if (len==1)                                                        /* 之前没加这句,len=1时要出错 */
                        pos = 0;                                                /* 固定取第1位      */
                else
                        pos = i % ( len - 1 );
                sn1[i] = (n1[pos] ^ s1[i]) % 0x1A + 0x41;
        }

        /* 前4位已经确定,下面计算后面4位 */

    for (i=0; i<6; ++i)                     /* 如不足10位填充s2  */
        if (i+4>=len)
            n2[i] = s2[i];
        else
            n2[i] = n1[i+4];

    n2[i] = '\0';

    for (i=0; i<4; ++i)                     /* 后面最小4位判断   */
    {
                if (len<5)                                 /* 根据位长作不同选择  */
                        get_num(sn1, i, n2[i/2]);
                else
                        get_num(sn1, i, s2[i/2]);
        ++i;
    }
    sn1[8] = '\0';
    printf("SN is: %s\n", sn1);
    getch();
}

附C语言注册机:附件:keygen.zip
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
云风 2005-11-15 14:59
17
0
楼主是我们学习的榜样。
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
chinarrr 2005-11-16 09:36
18
0
最初由 hud 发布
返回sunson Crackme。点击“注册”按钮,OD中断在下面地址:

7343FF3F > $ 5>push ebp ; 这里是ProcClass的入口
........


我有一事不明,我按照楼主的方法,中断是在
6605F626 [>  55              push ebp
不解,请问为什么?
6605F626 是系统的地址吗?
雪    币: 221
活跃值: (151)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
hud 2 2005-11-16 10:17
19
0
不会啊,因为你提到的地址在程序中应该没有分配。

你再看看,是不是消息选得不对?应该是"202 WM_LBUTTONUP"。在选择消息时,左下角有个选项,将它勾上,再到消息框里去找可以按消息的字母顺序查找,就会方便些了。

另外,照我在16楼更新的,用__vbaStrCopy下断更好些。
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
chinarrr 2005-11-16 10:42
20
0
我试了很多次了,还是 6605F626 ,郁闷了
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wzwzwzzz 2005-11-16 11:19
21
0
谢谢指点
在本论坛发表留言
雪    币: 221
活跃值: (151)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
hud 2 2005-11-16 11:59
22
0
To: chanarrr:

我做了一个录像,你再看看。如果还有问题,那么你看看,你会不会用到另外一个文件了(不是sunson的crackme No.1)?

附件:sunson-breakpoint.zip
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
chinarrr 2005-11-16 13:44
23
0
谢谢热心的hud
可能是其它的什么原因吧
代码都是一样的(除了地址),
6605F626 W>  55       push ebp
6605F627     8BEC     mov ebp,esp
6605F629     83EC 30  sub esp,30
6605F62C     53       push ebx
6605F62D     8B5D 08  mov ebx,dword ptr ss:[ebp+8]
6605F630     56       push esi
6605F631     57       push edi
6605F632     53       push ebx
6605F633     E8 CCBCF>call MSVBVM60.6600B304
6605F638     8B7D 0C  mov edi,dword ptr ss:[ebp+C]
6605F63B     8BF0     mov esi,eax
6605F63D     85F6     test esi,esi
6605F63F     75 40    jnz short MSVBVM60.6605F681
6605F641     83FF 24  cmp edi,24
雪    币: 221
活跃值: (151)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
hud 2 2005-11-16 14:08
24
0
哦,会不会是系统不同呢?

我只在XP下试过,可能其它系统地址会不同吧。
雪    币: 381
活跃值: (130)
能力值: ( LV13,RANK:330 )
在线值:
发帖
回帖
粉丝
HSQ 8 2005-11-22 17:52
25
0
不错,写得很详细,向楼主学习。。。
游客
登录 | 注册 方可回帖
返回