海天一色001 发表于 2019-4-7 13:15

160个CrakeMe程序之017练习

本帖最后由 海天一色001 于 2019-4-7 14:15 编辑

(由于各种原因断断续续,这个程序破解过程写了有一年多了!我是按照自己的练习过程来写的,很啰嗦,隔了一段时间发现中间有错误的地方,重新进行了修改,有的地方加上了新的内容,所以拖得时间很长,可能前后不连贯,敬请大家谅解!)

第17个CM程序,解压文件后有一个主程序和一个README文件,先打开这个README文本,提示不能用SmartCheck这个反编译器,只能找到正确的注册码。可见14、16、17这三个CM是逐步增加难度的程序,通过练习,我们确实能学习到新的知识。不过还是先试着爆破了,再找注册码。
先打开程序,点击“About”菜单,弹出信息框,大意是“这个CM程序制作出来是为了提高新手们在VB方面的R.C.E知识的!(R.C.E是什么意思?)希望你们能从中学习到一些知识!”,与第16个CM相比,除了版本号变成3之外,提示内容基本一样,没有可以利用的地方;

在注册码文本框中随意输入字符,点击“Check it”按钮,程序弹出错误对话框:

老规矩,查壳,程序用VB编写,未加壳:

第一步,爆破程序:
将程序载入OD中,智能搜索字符串,可以看出先判断是否用SmartCheck加载了程序,然后明显的两组文字说明,查了一下,“cheater”是作弊者的意思,下一句是说你在用SmartCheck加载了程序,先关闭调试程序重新再试。失败的在前面,成功的在后面。也就是说这个程序里对反编译进行了一定的限制,如何绕过这个限制,或者在限制之下找到注册算法,这是更进一步的知识点了。这可能是“About”里面说的R.C.E吧!
我先试着爆破程序,使它不管是否输入注册码都能弹出正确信息来,然后再试着将Anti-SmartCheck的限制去掉看看。

双击00404E6A地址,进入CPU窗口:

向上看,很轻松地看到00404E27地址处一个比较指令后面跟着跳转指令,跳过了正确信息来到了失败,所以可以先在00404E30地址将跳转nop掉试一试:

在00404E30处先下断点,然后F9运行程序,

程序失败,所以继续向下查看代码,很快看到00404ED9处的指令“jmp BJCM30A.00404F86”跳过了失败,但在这个跳转之后的下面00404EDE地址处的跳转来自0040459F, 00404941,这两个地址都在00404E30地址之前,使程序跳过了正确,所以要让这两个跳转失效;下一句00404EE3地址处的跳转来自00404E30,也使程序跳过了正确,已经被nop掉了:

要让00404EDE地址处的这条跳转命令失效,在信息窗口中“跳转来自0040459F, 00404941”一行上右键点击,先选“转到jnz来自0040459F”来到地址0040459F处将此处nop掉,

再Ctrl+G转到00404941处再nop掉这个跳转后,运行程序,点掉错误提示,再点击“Check it”按钮,程序中断,F9继续,程序弹出正确信息框!再点掉,随意输入字符后点击“Check it”按钮,程序中断,F9继续,又弹出正确信息框;反复试几次后,程序均弹出正确信息,说明爆破成功了,保存为BJCM30A_nop.EXE,试运行,均成功了。
第二步,去反调试:
VB程序,应该可以用SmartCheck这个软件进行调试,但本软件提示不能用它,先试一试看结果怎样:
用SmartCheck打开BJCM30A.EXE,运行,输入“1234567890”,点击“Check it”按钮,等了一会儿,跳出错误对话框,提示在用SmartCheck,但SmartCheck仍在不停运行,右下角的程序事件一直跳到76万多才停下,点掉错误提示,重新点击 “Check it”按钮,程序又提示了不能用SmartCheck!

点开Command1_Click事件,看到取了两次时间,然后就是错误提示框了!

看来真不能用SmartCheck啊。
那就用VB Decompiler来加载程序吧:

从前面几个程序的破解过程中可以轻松地判断出CrackmeV30a是主窗体,Command1_Click是“Check it”按钮。再选择Code -> CrackmeV30a -> Command1_Click_404230,右侧出现按钮事件的代码,将解析的代码与OD对照,可以慢慢地找到注册码的算法。
我一直以来都选中了“程序分析器和优化器”选项,所以代码解析后很简洁,利于迅速把握程序整体结构,但很多重要的参数都没有标明,有时真是看不懂。这个程序本来也是这样解析出来的,经过几天来断续分析,今天重新打开VB Decompiler时鼠标滑到了“程序分析器和优化器”选项上,将其取消了,再反编译后,解析后代码大不一样了!这次的解析可能很繁杂,我反而能明白很多东西了!特别是算法,应该是比较清楚了!

以后应该这两种方式都要运用啊!不知道这次分析还需要多少天,只要每一次都有进步,我就会坚持下来!!!
在OD中CPU窗口上按Ctrl+G键,输入“00404230”,来到计算注册码代码的段首处,在此下断,F9运行程序,输入字符“1234567890”,点击“Check it”按钮,程序中断。

F8单步向下运行,注意查看寄存器、信息栏、堆栈窗口的信息,可惜对VB的数据存储方法还是摸不到头脑,找不到什么可用的信息。
运行至00404270地址处,命令是call dword ptr ds:,OD自动注释的是“msvbvm60.Zombie_AddRef”,不知道是干什么用的,VB Decompiler中这个地址处的解析是var_eax = Me.AddRef,百度一下,也没有完全搞清楚什么意思,大概是调用函数前先AddRef增加引用计数,传给函数,使用完再Release,这样可防止COM对象使用期间被卸载了。
先不管它了,在OD中继续单步运行到00404320地址处,调用了一个和时间有关的函数!再查,这可能是以时间为种子来取随机数后作为注册码运算的参数吧!

继续单步向下进行,结合VB Decompiler中的解析,看到0040432C地址处,var_5C= CLng(Timer) ,取当前时间换算成一个长整型的数值存入var_5C中;再往下分析,从0040438B到00404461地址处是一个大循环包含一个小循环,还是要一点点地搞明白它们的具体作用:

从0040433B至0040438B处这一段里面的代码OD自动注释了一些内容,step是循环的步长或者是增量大小,end是循环结束的值,start是循环的初始值,TMPend是终值暂存的地方,TMPstep应该是步长暂存的地方,Counter是计数器,运行到00404391处时可以从上图中看出step=6,end=1,start=2,Tmpend=6,Tmpend=6,Counter=2,跟我想象中的结果一点也不一样,运行了好几次也不明白是如何储存及读取数值的,对SS:之类的堆栈值的查看始终没看明白,只知道这是一个循环的初始化(__vbaVarForInit)过程,下面仍有__vbaVarForNext与之相类似,所以只能先略过分析,看VB Decompiler解析出的注释“For var_80 = 1 To 1000 Step 1”,明白了start =1,end=1000, step =1就够了:
   
从VB Decompiler中这段解析来看,每次小循环结束后的值始终是var_24 = CInt(1),所以这个小循环可以简化成var_24 = CInt(1);再看大循环,结果还是这个var_24 = CInt(1)!在OD中单步执行,运行到小循环后就运行不下去了,难道真的运行250次吗?即使小循环完了,大循环还有1000次呢!所以感觉这段代码可能就是反调试用的,让人进入一个死循环吧。也不知道对不对。下面是对小循环的代码分析:
004043ED FF 15 38 call      dword ptr ds:[<&MSVBVM60.__vbaVarForInit>]   ;小循环开始:重复执行初始化
004043F3 3B C7    cmp       eax, edi                                 ; 比较eax,edi的大小,作为下一步跳转的依据
004043F5 74 4D    je      BJCM30A.00404444                        ; 如果eax=edi则跳至00404444地址处,跳出本循环

仔细分析,因为在004043F3处的edi始终为0,eax为1,所以无法跳出;
(如果此时跳至00404444地址处,那么从下面的代码分析也就很可能跳出了大循环,跳过反调试的内容,进入注册算法代码段中。)
004043F7 68 34 2A push      BJCM30A.00402A34                           ;Unicode "IS SMARTCHECK LOADED???"
004043FC 68 34 2A push      BJCM30A.00402A34                           ;Unicode "IS SMARTCHECK LOADED???"
00404401 FF 15 68 call      dword ptr ds:[<&MSVBVM60.__vbaStrCmp>]       ;字符串比较,结果存入eax中,相等为0,不等为1;
00404407 85 C0    test      eax, eax                                     ; 测试eax是否为0,
00404409 75 1F    jne       BJCM30A.0040442A                        ; 不为0则跳
因为00404401处的字符串比较的内容是一样的,所以下面跳出循环也不能实现,但这一段应该没什么作用吧
0040443B 51       push      ecx                                          
0040443C FF 15 E8 call      dword ptr ds:[<&MSVBVM60.__vbaVarForNext>]   ; 重复执行小循环结构, For... Next...(Loop)
00404442 EB AF    jmp       BJCM30A.004043F3                         ; 跳回小循环开始   
同样分析0040438B到00404461地址处的大循环,
0040438B   .FF15 38104000 call dword ptr ds:[<&MSVBVM60.__vbaVarForInit>]   ; \__vbaVarForInit ‘For var_80 = 1 To 1000 Step 1
00404391   >3BC7          cmp eax,edi
00404393   .0F84 C8000000 je BJCM30A.00404461                               ;‘If var_14C Then
00404391地址处的eax为1,edi为0,所以00404393处的跳转不能实现;如果实现了,那么也应该是跳出了反调试代码,进入注册算法代码中。
继续向下分析OD代码至00404461处,又一次取时间,转化成整数形式:
00404461   > \FF15 94104000 call dword ptr ds:[<&MSVBVM60.#535>]            ;msvbvm60.rtcGetTimer 随机数发生器
00404467   .FF15 D0104000 call dword ptr ds:[<&MSVBVM60.__vbaFpI4>]         ;msvbvm60.__vbaFpI4 vbaFpI4:是VB中的一个内部函数,它的功能是将一个浮点数(float)转4字节有符号整数(int)
0040446D   .2B45 A4       sub eax,dword ptr ss:                   ;‘CLng(Timer) = CLng(Timer) - var_5C
00404470   .0F80 340C0000 jo BJCM30A.004050AA                               ;’If Err.Number <> 0 Then
00404476   .83F8 05       cmp eax,0x5
00404479   .0F8E AD000000 jle BJCM30A.0040452C                              ;‘If CLng(Timer) > 5 Then失败
到0040446D地址处,解析是 CLng(Timer) = CLng(Timer) - var_5C,当前时间减去第一次取值的时间差,这个差值小于5秒时,程序跳转至注册算法,
如果大于5秒,则程序调用错误对话信息框,结果走向失败(GoTo loc_00404F86)。

所以说从00404320地址至0040452C处就是反调试的内容!因为进行调试的话大多步骤是单步进行,如果真的单步运行了(250X1000=)25万次循环,这样下来时间差绝对大于5秒,程序会显示“You have SmartCheck loaded!...Close it and try again!!!”的错误提示,那么反反调试那就可以直接从00404320跳到0040452C处,直接进入注册算法代码中去!试一试,将00404320处代码改为 jmp 0040452C,另存为可执行文件BJCM30A_antismack.EXE,运行程序,没问题。
用SmartCheck载入BJCM30A_antismack.EXE,运行程序,点击“Check it”按钮后马上跳出来错误对话框,而且点开Command1_Click事件,看到了和原程序截然不同的代码,说明已跳过了反调试的程序段。

第三步,追注册码:
OD中点击0040452C地址这一行,F4键运行到此处,对照VB Decompiler的解析,继续单步运行至0040454F处,
0040454F   .FF90 A0000000             call dword ptr ds:                           ;var_84 = Text1.Text
00404555   .3BC7                      cmp eax,edi
00404557   .DBE2                      fclex                                                ;在检查未决的无掩码浮点异常之后,清除浮点异常标志。
00404559   .7D 12                     jge short BJCM30A.0040456D                           ;大于等于时跳转至0040456D

跳转成功,继续向下:
0040456D   >8B95 7CFFFFFF             mov edx,dword ptr ss:                        ;取假码
00404573   .52                        push edx                                             ; /String = “1234567890”
00404574   .FF15 14104000             call dword ptr ds:[<&MSVBVM60.__vbaLenBstr>]         ; \eax=假码长度(0xA)
0040457A   .33DB                      xor ebx,ebx
0040457C   .83F8 05                   cmp eax,0x5                                          ;注册码长度与5比较,eax>5,zf=0,sf=0,of=0
0040457F   .0F9CC3                  setl bl                                                ;setl,即set when less,测试一个有符号比较。(指令:setl D效果是D<-SF^OF也就是将符号标志位和溢出标志位异或的结果给D寄存器)setl bl; SF==1 或者 OF==1 时 bl=1,否则bl=0
00404582   .8D8D 7CFFFFFF             lea ecx,dword ptr ss:
00404588   .F7DB                      neg ebx                                                ;ebx求补,即0-ebx
0040458A   .FF15 F0104000             call dword ptr ds:[<&MSVBVM60.__vbaFreeStr>]         ;msvbvm60.__vbaFreeStr
00404590   .8D8D 5CFFFFFF             lea ecx,dword ptr ss:
00404596   .FF15 F4104000             call dword ptr ds:[<&MSVBVM60.__vbaFreeObj>]         ;msvbvm60.__vbaFreeObj
0040459C   .66:3BDF                   cmp bx,di
0040459F   .0F85 39090000             jnz BJCM30A.00404EDE                                 ;If ebx<> 0 Then 跳到失败
具体分析这一段,其实就是注册码长度要不小于5位,0040457C处一个比较就可以了,后面的代码我感觉没什么用处,也可能是反汇编后程序自动加的内容吧。
继续向下进行,004045F8----00404623处与0040454F----00404574的代码作用完全一样,得到了输入的假码与假码长度:
004045F8   .FF92 A0000000             call dword ptr ds:                           ;var_84 = Text1.Text
004045FE   .3BC7                      cmp eax,edi
00404600   .DBE2                      fclex                                              ;浮点检查错误清除
00404602   .7D 12                     jge short BJCM30A.00404616                           ;Ifvar_84 < 0 Then
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
00404616   >8B8D 7CFFFFFF             mov ecx,dword ptr ss:
0040461C   .51                        push ecx                                             ; /String = "1234567890"
0040461D   .FF15 14104000             call dword ptr ds:[<&MSVBVM60.__vbaLenBstr>]         ; \__vbaLenBstr(eax=0xA)
00404623   .8985 00FFFFFF             mov dword ptr ss:,eax                     ;var_100 = Len(var_84)

从自动注释中可以看出从00404635到0040466E是“for...next”循环中“for”条件的一些参数设置, 00404880到0040488C是“for...next”循环中“next” 条件的一些参数设置,结合VB Decompiler的解析,可以看出这又是一个循环:
loc_0040466E:   For var_6C = 1 To Len(var_84) Step 1
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
loc_00404886:   Next var_6C
以“11111”为例进行跟踪分析,运行到00404616,初始化循环的相关参数,
00404616 > > \8B8D 7CFFFFFF mov ecx,dword ptr ss:               ;ecx=假码
到0040466E,开始大循环:
0040466E > .FF15 38104000 call dword ptr ds:[<&MSVBVM60.__vbaVarForInit>; \For var_6C = 1 To Len(var_84) Step 1
00404674   .8D8D 7CFFFFFF lea ecx,dword ptr ss:               ;将假码地址传给ecx///指向假码地址的指针的地址
0040467A > .8985 30FEFFFF mov dword ptr ss:,eax            ;var_1D0 = var_18C(第一次循环时var_1D0 = 0x1)
到00404698,跳转前的比较:
00404698 > >39BD 30FEFFFF cmp dword ptr ss:,edi            ;CMP(var_1D0,edi)///第一次1D0=1
0040469E > .0F84 F5010000 je <BJCM30A.loc_404899>                     ;If var_1D0 = 0 Then 跳走
继续向下, 从00404779到00404787处,得到第n次循环第n个位置的1个字符:
00404779   .8B8D 7CFFFFFF mov ecx,dword ptr ss:               ;ecx=假码///eax=n
0040477F   .8B3D 54104000 mov edi,dword ptr ds:[<&MSVBVM60.#631>]       ;msvbvm60.rtcMidCharBstr
00404785   .50            push eax
00404786   .51            push ecx
00404787   .FFD7          call edi                                    ;eax=假码字符串中的第n个(n为第n次循环的当次)///edx=2,ecx=0; <&MSVBVM60.#631>
00404789   .8BD0          mov edx,eax                                 ;edx=eax///eax为返回的字符的地址
0040478B   .8D8D 74FFFFFF lea ecx,dword ptr ss:
00404791 > .FFD3          call ebx                                    ;msvbvm60.__vbaStrMove var_8C = Mid$(var_84, var_ret_1, var_B8)//将刚刚返回的字符的地址copy到ebp-0x8c///var_8C=假码字符串中的第n个

到00404791地址处,解析是var_8C = Mid$(var_84, var_ret_1, var_B8),代入相应的值为Mid$(text1.text,1,2),我的理解是从假码字符串的第1位开始取2个字符,应该就是“12”,但我在寄存器查看的结果var_8C(SS:)地址为0012F584,显示的值为UNICODE的字符“1”,是VB Decompiler的解析发生错误了,还是在什么地方重新给var_B8赋值为1了,更可能是我对Mid$()这个函数理解错误?迫切希望有哪位大神帮助我解答一下这个疑问。

继续向下,从004047A4到004047CC,作用是得到假码字符串中的第n+1个字符,var_90=假码字符串中的第n+1个
004047A4   .50            push eax                                    ; /var18 = 001867AC
004047A5   .8D95 38FFFFFF lea edx,dword ptr ss:               ; |
004047AB   .51            push ecx                                    ; |var28 = 0012F580
004047AC   .52            push edx                                    ; |saveto8 = 001867AC
004047AD   .FF15 C8104000 call dword ptr ds:[<&MSVBVM60.__vbaVarAdd>]   ; \__vbaVarAdd
004047B3   .50            push eax                                    ;上句运行后eax=ecx=0012F548,edx=9
004047B4   .FF15 C4104000 call dword ptr ds:[<&MSVBVM60.__vbaI4Var>]    ;var_ret_2 = CLng(var_6C + 1)///var_ret_2=n+1 ;eax=n+1[估计是varadd的作用]
004047BA   .50            push eax                                    ;eax=循环次数+1
004047BB   .8B85 78FFFFFF mov eax,dword ptr ss:               ;eax=假码
004047C1   .50            push eax
004047C2   .FFD7          call edi                                    ;msvbvm60.rtcMidCharBstr///运行后eax为后一个位置的字符,
004047C4   .8BD0          mov edx,eax                                 ;
004047C6   .8D8D 70FFFFFF lea ecx,dword ptr ss:
004047CC   .FFD3          call ebx                                    ;msvbvm60.__vbaStrMove ///var_90 = Mid$(var_88, var_ret_2, 1)="2"

接下来比较var_8C,var_90的值,结果存入edi中,
004047CF   .FF15 68104000 call dword ptr ds:[<&MSVBVM60.__vbaStrCmp>]   ;msvbvm60.__vbaStrCmp///cmp(var_8C,var_90):eax=-1,说明两个字符串不等,8C是上一次取的值,90是本次取的值
004047D5   .8BF8          mov edi,eax                                 ;将比较结果存到edi,相同返回0,不同返回-1(0xFFFFFFFF)
再经过一堆计算,到00404838处,var_8C,var_90不等就跳过,var_48=0;var_8C,var_90相等则记入ss: (实际位置却是在ss:)中一次,var_48=0+n:
00404838   .66:85FF       test di,di                                    ;比较edi是否为0,di=0,test di,di的结果是CF=0,ZF=1
0040483B > .74 37         je short <BJCM30A.loc_404874>               ;If (var_8C = var_90) + 1 = 0 Then///跳转成立
0040483D   .8D4D B8       lea ecx,dword ptr ss:               ;var_8C,var_90相等则记录1次???????????
实际上运行0040486E的命令后,var_40的值才发生了变化:
0040486E > .FF15 08104000 call dword ptr ds:[<&MSVBVM60.__vbaVarMove>];var_48 = var_48 + 1/////???从寄存器中看var_48的值无变化,var_40=1
继续到00404894,跳回00404698进行循环:
00404894 > .^\E9 FFFDFFFF   jmp <BJCM30A.loc_404698>                      ;GoTo`loc_00404698
循环10次后,此时var_48=0,也就是说注册码每个字符都不相同;此时继续向下,可以看出这个循环的作用是判断注册码是否每一位都相同,相同则var_48 = var_48 + 1:
从004048E4到00404941处,将假码的长度减1存入var_F0中,再与上面的var_48比较,比较的结果存入var_134中,如果相等,到00404941处跳到注册失败,不相等则开始计算验证注册码的真伪:
004048E4 > > \8B95 7CFFFFFF mov edx,dword ptr ss:               ;取假码
004048EA   .52            push edx                                    ; /String = "1234567890"//// String = "11111"
004048EB   .FF15 14104000 call dword ptr ds:[<&MSVBVM60.__vbaLenBstr>]; \__vbaLenBstr
004048F1 > .83E8 01       sub eax,0x1                                 ;Len(var_84) = Len(var_84) - 00000001h////eax=eax-1
004048F4   .8D8D 08FFFFFF lea ecx,dword ptr ss:
004048FA > .0F80 AA070000 jo <BJCM30A.loc_4050AA>                     ;If Err.Number <> 0 Then
00404900 > .8985 10FFFFFF mov dword ptr ss:,eax               ;var_F0 = Len(var_84)
00404906   .8D45 B8       lea eax,dword ptr ss:
00404909   .50            push eax                                    ; /eax=假码长度-1?///eax=var_48=记录的循环次数吧?
0040490A   .51            push ecx                                    ; |ecx=记录的循环次数////ecx=假码长度-1????????????
0040490B   .C785 08FFFFFF>mov dword ptr ss:,0x8003            ; |下步的比较很重要??????
00404915   .FF15 6C104000 call dword ptr ds:[<&MSVBVM60.__vbaVarTstEq>] ; \__vbaVarTstEq
0040491B   .8D8D 7CFFFFFF lea ecx,dword ptr ss:               ;上步运行后eax=0,ecx=3,edx=10
00404921 > .66:8985 CCFEF>mov word ptr ss:,ax                ;var_134 = (var_48 = Len(var_84))
00404928   .FF15 F0104000 call dword ptr ds:[<&MSVBVM60.__vbaFreeStr>];msvbvm60.__vbaFreeStr
0040492E   .8D8D 5CFFFFFF lea ecx,dword ptr ss:
00404934   .FF15 F4104000 call dword ptr ds:[<&MSVBVM60.__vbaFreeObj>];msvbvm60.__vbaFreeObj
0040493A   .66:39BD CCFEF>cmp word ptr ss:,di
00404941 > .0F85 97050000 jnz <BJCM30A.loc_404EDE>                      ;If var_134 = True Then 注册失败

因为此时假码为“1234567890”, 所以var_48=0,Len(var_84)-1=9,var_134=false,所以不跳向注册失败
【我后面又用“11111”试了一次,到00404941处var_48 = Len(var_84)-1=4,则var_134 = True,直接失败;用“11112”试,到00404941处var_48 = 3,Len(var_84)-1=4,则var_134 =False,不跳向失败】
继续向下,从004049C5处开始到00404D46处又是一个“for...next”大循环,还得一步一步向下走,弄明白其作用:
004049FE   .FF15 38104000 call dword ptr ds:[<&MSVBVM60.__vbaVarForInit>]   ; \For var_6C = 1 To Len(var_84) Step 1
.........
00404D3A   .FF15 E8104000 call dword ptr ds:[<&MSVBVM60.__vbaVarForNext>]   ; \Next var_6C

到00404A79处,取假码,到00404A80处,得到假码的长度为“0xA”:
00404A80   .FF15 14104000 call dword ptr ds:[<&MSVBVM60.__vbaLenBstr>]   ; \__vbaLenBstr
再向下到00404A9D处,将假码的长度的16进制值“0xA”转换成字符unicode "A":
00404A9D   .FF15 A8104000 call dword ptr ds:[<&MSVBVM60.#572>]   ;msvbvm60.rtcHexBstrFromVar
到00404ABE处,取假码;
00404ABE > .FF15 40104000 call dword ptr ds:[<&MSVBVM60.__vbaObjSet>];Set var_A8 = CrackmeV30a.Text1
00404AE0 > .8985 40FFFFFF mov dword ptr ss:,eax            ;var_C0 = var_A8///假码存入var_C0
00404AE6   .C785 38FFFFFF>mov dword ptr ss:,0x9
00404AF0 > .FF15 D4104000 call dword ptr ds:[<&MSVBVM60.#617>]         ;var_D8 = Left(var_A8, 1)////EBP-D8=8???实际上取字符串左边第一位存入EBP-D0中
00404B04 > .FF15 90104000 call dword ptr ds:[<&MSVBVM60.__vbaStrVarVal>; \var_88 = CStr(var_D8) 假码第一位字符的16进制Asc值31
00404B0B   .FF15 28104000 call dword ptr ds:[<&MSVBVM60.#516>]         ; \rtcAnsiValueBstr
00404B11   .8D95 18FFFFFF lea edx,dword ptr ss:               ; 上一步得到假码第一位字符的16进制Asc值31
00404B17 > .66:8985 20FFF>mov word ptr ss:,ax                ;var_E0 = Asc(var_88)存入var_E0
00404B2F   .8BD0          mov edx,eax                                  ;上步将数值31转换成UNICode文本31,此时eax=UNICode “31”
.....................
00404B5C > .FFD3          call ebx                                     ;var_90 = Hex$(Asc(var_88))///转成16进制数值,var_90 = UNICode “31”
.....................
00404B70 > .FFD3          call ebx                                     ;var_8C = Hex$(Len(var_84))///转成16进制数值,var_8C = UNICode “A”
00404B82   .8D8D 70FFFFFF lea ecx,dword ptr ss:
00404B88   .8D95 74FFFFFF lea edx,dword ptr ss:
00404B8E   .51            push ecx                                     ;假码第一个字符的ASCII码值
00404B8F   .52            push edx                                     ;假码长度
继续向下至00404B91处,代码call dword ptr ds:不知道是什么意思,注释var_eax = TextBox.1784更不知道是什么意思!
00404B91 > .FF90 F8060000 call dword ptr ds:            ;var_eax = TextBox.1784
运行这一命令后,这时从图中看到EBP-98地址的注释变成了“1EA”,用计算器的十六进制进行计算,正好是31*A=1EA!那么这一句命令的作用就是一个计算表达式: Serial * Serial的长度。

00404B91 > .FF90 F8060000 call dword ptr ds:                ;var_eax = TextBox.1784:::计算表达式:(Serial * Serial的长度)
00404B97   .3BC7          cmp eax,edi                                  ;比较谁的结果?
继续运行到00404BBC处,注释是var_38 = var_98,即将运算结果复制到ebp - 0x38处,也就是0012F5D8处,这时从堆栈窗口中可以看到ebp - 0x38的数值是“1EA”;
      
继续向下单步运行到00404CF1处,
00404CF1 > .FF15 08104000 call dword ptr ds:[<&MSVBVM60.__vbaVarMo>;var_34 = var_34 + Asc(var_88)
注释是将结果保存到ebp - 0x34处,可在堆栈窗口中查看时,EBP-34的数值从00000001变成了00000002!非常郁闷的结果!而在下面的EBP-2C处的数值却从0变成了31,这正是字符“1”的16进制AscII码,这是怎么回事?

继续运行下去看看:第二次运行到00404BBC处,EBP-38的数值由0018601C变成了00186034,注释仍是UNICODE ‘1EA’;继续到00404CF1处,EBP-2C处的数值却从31变成了00D20063,63应该是31+32的结果;第三次运行到到00404CF1处,EBP-2C处的数值由63变成了96(63+33);第四次时为CA(96+34),一直到第十次,EBP-2C处的数值成了20D。
耐心的循环了10次,取了10次假码及假码的长度,再用它们相乘得到一个结果,存入堆栈中(我这里是0x31*0xA=0x1EA);循环中分别取假码字符串中每一位字符的AscII值进行累加,存入堆栈中(而实际存储位置却是在?)新的疑问等待高人解惑。
循环结束,来到00404D4B处,将堆栈地址传给ecx,下一句取出结果(将的值传给ecx):
00404D4B > > \8D4D CC       lea ecx,dword ptr ss:          ;loc_404D4B
00404D4E   .51            push ecx                                 ;取出结果(Serial的16进制ASCII码总和)
00404D94   .8D85 7CFFFFFF lea eax,dword ptr ss:
00404D9A   .8D4D C8       lea ecx,dword ptr ss:
00404D9D   .50            push eax                                 ;Serial中的16进制ASCII码和
00404D9E   .51            push ecx                                 ;Serial * Serial的长度
00404D9F   .56            push esi
00404DA0 > .FF92 F8060000 call dword ptr ds:            ;var_eax = TextBox.1784///验证 Serial * Serial的长度 = Serial中的16进制ASCII码和 是否成立
从这里大概猜测,注册算法应该是Serial * Serial的长度 = Serial中的16进制ASCII码和了。
先从最小位数开始试一试:假设注册码是“1****”,那么Serial * Serial的长度=0x31*0x5=0xF5,转换成10进制是245;而0至9的10进制ASCII码分别为48至57,而(245-49)/4=49,所以后四位字符可以先用chr(49)=“1”,又因为不能每一位字符都相等,所以随意将第二位减1得到“0”;第三位“1”不变,第四位再减1得到“0”,最后一位就要加上减去的两个1得到“3”。所以注册码是“10103”。打开程序试一试,成功了!继续试:12101,也成功了!
编写注册机,先是纯数字的运算,再扩展到AscII打印字符的运算:
先尝试了输入一串假码,然后进行检验,如果不正确,则从最后一位更改,尽量保持输入的字符串最少的改动的思路;然后又想了输入注册码长度,随机生成字符串再检测生成注册码;还有过不要任何输入内容,点击命令按钮根据算法直接生成注册码的想法等等。
编写中不断出错,遇到了各种各样的问题,如中文字符、控制字符等,甚至是找不到问题所在,整个破解陷入停滞之中。。。。。。
好在,终于完成了注册机!!!
在练习过程中,我感到自己还有许多不到位及分析错误的地方,迫切希望能有大神指正!
附件,含CM原程序、爆破后的程序及017注册机。百度链接是:http://pan.baidu.com/s/1skMkJY9密码: 86pm,160个CM、我已练习过的前17个crackme程序(不含012)都在里面。

海天一色001 发表于 2019-4-8 14:15

灰太狼大王 发表于 2019-4-8 06:39
你一直发布了18,第12个怎么没有

012是NE格式的16位程序,不是常见的PE程序,在OD中没办法调试,IDA猜测可以调试(不过我不懂),网上有大神说要构建系统环境Win95系统+TRW2000,我试了试,还是不会,所以一直没有做这个练习。

wi5788 发表于 2019-4-7 18:18

谢谢楼主讲解很明细

N.A.T 发表于 2019-4-8 02:04

讲解得很细致,谢谢分享

灰太狼大王 发表于 2019-4-8 06:39

你一直发布了18,第12个怎么没有

轩可雅 发表于 2019-4-8 09:40

多谢楼主分享

大海萌萌 发表于 2019-4-8 10:02

膜拜大神!

lovejoan 发表于 2019-4-8 11:02

很详细,也很厉害了

xinzaixin 发表于 2019-4-8 13:14

膜拜大神,厉害厉害。。。。。。。。。。。

13790439860 发表于 2019-4-8 18:17

楼主是个大神,辛苦了
页: [1] 2
查看完整版本: 160个CrakeMe程序之017练习