Pnmker 发表于 2015-6-21 03:16

160个CrackMe练手之038

本帖最后由 Pnmker 于 2015-6-21 03:22 编辑

160个CrackMe练手之038By   Pnmker本系列的主题帖已经有很多朋友在做,为了能够丰富这一系列的文章我也希望我能尽一点绵薄之力。038将是一个全新的挑战,不仅仅是因为这里用到了新的工具WKT和新的技术知识,而且也是因为从038开始不能再像前面那些一样从吾爱坛子里找到可以参考的破文。       我比较欣赏44018723兄的做法,他分享给大家的众多破文中形式统一思路清晰。但我不准备那么做,所以对于CM所需要的基本知识和分析步骤,请大家参照44018723兄的文章,这里提供一些他的主题帖链接: http://www.52pojie.cn/home.php?mod=space&uid=255224&do=thread&view=me&from=space下面开始正文。       经PEiD查壳,无壳且该CM编写语言为VB5,但OD载入提示代码可能被压缩或加密,由于之前014已经遇到过P-Code形式的VB程序,因此第一时间断定该CM应该是P-Code,不过还是先在OD继续运行浏览下汇编代码看是否有什么信息可以确认,可以发现有jmp.&MSVBVM50.MethCallEngine类似的代码 ,就可以确定是P-Code形式了。这种方法难度比较高,其实没必要,VB无非两种形式大家做多了几乎是瞬间就可以确定。
0040304B      00            db 00
0040304C   .B8 5C000000   mov eax,0x5C
00403051   .66:3D 33C0    cmp ax,0xC033
00403055   .BA 54DD4000   mov edx,CyberBla.0040DD54
0040305A   .68 42104000   push <jmp.&MSVBVM50.MethCallEngine>


其实呢,接下来最快的方法就是使用vb decompiler反编译直接看比较接近VB的源代码就可以了。这里不准备使用这种方式,原因有二:第一vb decompiler反编译的代码虽然可读性比较强,但反编译出来的代码质量有好也有坏;第二,vb decompiler属于静态分析,对于复杂度比较高的程序分析起来还是有些困难的,尽管本程序不是很复杂。因而,抱着新手练手的态度,为了多掌握一些分析技巧和工具的使用,这里选择了VB专用的动态调试工具WKT VB Debugger。此工具论坛里也是可以直接下载的(在这里要感谢下小生我怕怕大牛的无私奉献),链接为http://www.52pojie.cn/thread-46850-1-1.html。
       对于WKT调试器的基本功能这里就不再做介绍了,大家可以参考工具里附带的帮助文档。先将我们的CM程序载入进来,成功载入的界面如下所示:界面上按钮很多,看似复杂,其实很多都没有用到,上图中我把使用的主要窗口和按钮标记了出来。WKT真不愧是VB P-Code专用的调试器,我认为他比较强大的一个地方就是关于按钮事件下断,可以通过 “管理窗口(Ctrl+F)”功能直接定位,很是方便。非常好用的一个工具!当然它也有不足之处,比如代码窗口不能滚动导致调试时能查看的代码行数非常少,堆栈窗口一行显示了两个栈中的内存地址与栈本身的含义有冲突等等,其实我觉得完全可以参照OD的界面结构来强化WKT。
那么接下来我们就使用“管理窗口(Ctrl+F)”功能来定位CM中“Check“按钮的处理代码并下断。WKT中按Ctrl+F弹出”对象属性“窗口,然后在窗口最上面的下拉列表中选择”Crackmefrm”,这时窗口上的有许多按钮变亮了,这说明Crackmefrm窗体中含有该类型的控件或资源。“Check“按钮属于Command控件,所以我们点击对应按钮又弹出一个窗口,界面及操作步骤如下图所示:

哈哈,是不是很简单,就这样“Check“按钮处理的事件代码通过断点就很容易跟踪到了。当然此时,你也可以通过按”Ctrl+E”来查看你所设置的断点。
再下来,就是按F5让程序跑起来,输入用户名和序列号,点击”Check”。哈哈,程序果真被断了下来!
接下来,就是单步跟踪来分析程序了。由于WKT的代码窗口代码行数比较少,不能整个处理事件进行分析,因此我尽量按比较完整的一段一段的分析。另外,P-Code的指令看似吓人,其实只要你跟踪时利用好代码窗口、堆栈窗口和内存转存(类似OD的数据窗口)是完全可以看懂的,而且P-Code指令从名字上看都已经大概猜得到了。0040E26C: 04 FLdRfVar 0012F438h
0040E26F: 21 FLdPrThis 0014EA60h
0040E270: 0F VCallAd Crackmefrm.txtname       ;很明显此处显示的就是用户名控件的名称
0040E273: 19 FStAdFunc
0040E276: 08 FLdPr
0040E279: 0D VCallHresult get__ipropTEXTEDIT;这里也能判断出是获取输入的用户名,而且是存入了第一行给出的地址0012F438h中
0040E27E: 6C ILdRf 00000000h
0040E281: 1B LitStr: ''                                                                                                ;此处应该是加载了一个空的字符串
0040E284: 30 EqStr                                                                                                                ;判断用户名和空字符串是否相等,如果用户名为空则弹出提示,如最后一行
0040E286: 2F FFree1Str
0040E289: 1A FFree1Ad
0040E28C: 1C BranchF 0040E2C1 ?
0040E28F: 27 LitVar_Missing 0012F3B8h
0040E292: 27 LitVar_Missing 0012F3D8h
0040E295: 3A LitVarStr 'Error'
0040E29A: 4E FStVarCopyObj 0012F3F8h
0040E29D: 04 FLdRfVar 0012F3F8h
0040E2A0: F5 LitI4: -> 40h 64
0040E2A5: 3A LitVarStr 'You have to enter you name first.'   ;弹出的用户名为空提示
如果我们输入的用户名不为空,则可以单步跟踪至如下代码:
很显然这是在判断序列号是否为空,非常简单略过分析,继续往下走。

嗯,是在加强判断保证输入的序列号长度大于等于5,也非常简单,继续往下走。
0040E36B: 28 LitVarI2 0012F3E8h 1h , 1                           ;循环变量从1开始
0040E370: 04 FLdRfVar 0012F3A8h
0040E373: 04 FLdRfVar 0012F438h
0040E376: 21 FLdPrThis 0014EA60h
0040E377: 0F VCallAd Crackmefrm.txtname
0040E37A: 19 FStAdFunc
0040E37D: 08 FLdPr
0040E380: 0D VCallHresult get__ipropTEXTEDIT
0040E385: 6C ILdRf 00000000h
0040E388: 4A FnLenStr
0040E389: FD Lead2/CVarI4
0040E38D: 2F FFree1Str
0040E390: 1A FFree1Ad
0040E393: FE Lead3/ForVar                                                                                                 ;开始进行For循环
0040E399: 04 FLdRfVar 0012F438h
0040E39C: 21 FLdPrThis 0014EA60h
0040E39D: 0F VCallAd Crackmefrm.txtname
0040E3A0: 19 FStAdFunc
0040E3A3: 08 FLdPr
0040E3A6: 0D VCallHresult
0040E3AB: 04 FLdRfVar 0012F440h
0040E3AE: 28 LitVarI2 1h , 1
0040E3B3: 04 FLdRfVar 0012F3A8h
0040E3B6: FC Lead1/CI4Var
0040E3B8: 3E FLdZeroAd
0040E3BB: 46 CVarStr
0040E3BE: 04 FLdRfVar 0012F3D8h
0040E3C1: 0A ImpAdCallFPR4 rtcMidCharVar on address 7406306Eh    ;此处可以知是从用户名中取出第i个字符 Mid$(name, i, 1)
0040E3C6: 04 FLdRfVar 0012F3D8h
0040E3C9: FD Lead2/CStrVarVal
0040E3CD: 0B ImpAdCallI2 rtcAnsiValueBstr on address 7405C89Bh   ;取获得的字符的ASCII码
0040E3D2: 44 CVarI2                                                                                                                                                                                       ;转换长variant类型的整数
0040E3D5: EF ConcatVar                                                                                                                                                                         ;连接字符串,从这里可以分析得到该段代码的作用是将用户名各个字符的ASCII码整数转换成字符串连接起来
                                                                                                                                                                                                                                                               ;例如pnmker的ASCII码值分别为112 110 109 107 101 114,那么最终的字符串为 "112110109107101114"
0040E3D9: FC Lead1/FStVar
0040E3DD: 2F FFree1Str
0040E3E0: 1A FFree1Ad
0040E3E3: 36 FFreeVar -> 3
0040E3EC: 04 FLdRfVar 0012F3A8h
0040E3EF: FE Lead3/NextStepVar

接下来这段代码稍微复杂了一点也比较长,当仔细跟踪和分析之后其功能也非常简单,也无非是将用户名做了一个运算:用户名各个字符的ASCII码整数转换成字符串连接起来例如pnmker的ASCII码值分别为112 110 109 107 101 114,那么最终的字符串为"112110109107101114",这个从WKT的内存转存界面中查看内存数据可以得到确认:(这里说一下,运算后的结果保存的地址是0x0012F440保存的是Variant变体类型的字符串,至于Variant变体类型的内存结构布局下面涉及到双精度浮点数时一并给出)
继续往下分析:0040E3F5: 04 FLdRfVar 0012F440h
0040E3F8: EB FnLenVar                                                      ;计算前一段由用户名计算出来的结果的长度
0040E3FC: 28 LitVarI2 9h , 9                        ;将长度与9比较
0040E401: 5D HardType
0040E402: 74 GtVarBool                                                ;进行大于比较
040E404: 1C BranchF 0040E425 ?          ;如果不大于9则跳转到0040E425,否则继续执行下一行
0040E407: 04 FLdRfVar 0012F440h   ;(注意此处将variant变体类型的运算结果处理成了双精度浮点数)
0040E40A: FE Lead3/LitVarR8      
0040E416: BC DivVar                                                                ;除以了某个数,只从代码窗口跟踪,分析出是除以了什么数非常困难
0040E41A: E1 FnFixVar                                                      ;得到的商取整
0040E41E: FC Lead1/FStVar                                        ;再保存回0012F440h
0040E422: 1E Branch 0040E3F5 ?    ;返回最上面一行继续进行判断取整后结果的长度是否大于9

这一段更是短小精悍,但也并不容易。大概的意识就是将前一段代码中由用户名运算得来的字符串进行了再加工,如果其长度大于9的情况下转化成双精度浮点数循环不断的除以某个数直到长度小于等于9。难点就是在这个除数到底是一个怎么样的数字呢?
这时候WKT的堆栈窗口和内存转存就发挥关键性作用了,通过跟踪堆栈窗口发现那个神秘的除数被存放在了0012F428的内存地址中,再次使用内存转存窗口查看0012F428里存放的内容:

啊?通过内存转存窗口,得到0012F428处的除数又是Variant变体类型,要把它转成数字可就有点困难喽。不过不用担心,我们还有搜索引擎呢,直接上Google搜索VB Variant Memory layout之类的关键字,嗯哼还真找到了一篇,链接地址放在这里,有兴趣的自己研究:http://www.codeguru.com/vb/gen/vb_misc/algorithms/article.php/c7495/How-Visual-Basic-6-Stores-Data.htm
通过那篇文章里的叙述,我们知道Variant变体类型的数据结构总共占据了16个字节,其中前两个字节表示实际的数据类型,接下来6个字节为预留,后面8个字节根据不同的数据类型使用情况各有不同。而0012F428的前两个字节为0008,查表知这是一个双精度浮点数,最后8个字节全部使用了,并且存储为了IEEE 32位浮点格式和小端存储。嗯嗯,了解清楚了就好了,这时候浮点转换工具IEE上场了,将最后8个字节的数据输入进去就可以得到对应的十进制数了:
呵呵,轻松得到那个神秘的除数原来就是圆周率3.141592654啊!!!OK,这一步也搞定了,继续往下分析。0040E425: 04 FLdRfVar 0012F440h;除以了好多个圆周率过后的结果
0040E428: FE Lead3/LitVarI4                         ;一个整数
0040E430: 17 XorVar                                                         ;进行异或
0040E434: FC Lead1/FStVar                                 ;保存回0012F440h
0040E438: 04 FLdRfVar 0012F440h;异或后的结果
0040E43B: 08 FLdPr
0040E43E: 8A MemLdStr
0040E441: FD Lead2/CVarI4                                 ;又一个整数
0040E445: 9C SubVar                                                         ;相减
0040E449: FC Lead1/FStVar      ;保存回0012F440h

这一段也很简单,就是将被处理过后的长度小于等于9的数字与某一个数字进行了异或,然后再减去另外一个数字,这两个数字的分析同神秘的除数一样,有兴趣的自己分析这里直接给出:异或的数字为821581432,减去的数字为55475。继续分析:0040E44D: 28 LitVarI2 1h , 1
0040E452: 04 FLdRfVar 0012F3A8h
0040E455: 28 LitVarI2 Ah , 1
00040E45A: FE Lead3/ForVar
0040E460: 04 FLdRfVar 0012F438h
0040E463: 21 FLdPrThis 0014EA60h
0040E464: 0F VCallAd Crackmefrm.txtkey
0040E467: 19 FStAdFunc
0040E46A: 08 FLdPr
0040E46D: 0D VCallHresult get__ipropTEXTEDIT
0040E472: 6C ILdRf 0017B464h
0040E475: 04 FLdRfVar 0012F3A8h
0040E478: FC Lead1/CI4Var
0040E47A: 08 FLdPr
0040E47D: 06 MemLdRfVar
0040E480: 9E Ary1LdI4
0040E481: 30 EqStr
0040E483: 2F FFree1Str
0040E486: 1A FFree1Ad
0040E489: 1C BranchF 0040E48C ?
0040E48C: 04 FLdRfVar 0012F3A8h
0040E48F: FE Lead3/NextStepVar

这一段有点长,是对序列号做了某些事情,不过这些都无关紧要了,这一段纯粹是用来迷惑人的。这一段主要是将序列号与10个迷惑人的伪列序号做比较,无论相等与否都没啥动作可完全忽视,这10个伪序列号为373703670、684708686、698673531、391184533、329528230、654824169、557168731、387375850、212298498、851143730。接着分析:
0040E495: 04 FLdRfVar 0012F438h
0040E498: 21 FLdPrThis 0014EA60h
0040E499: 0F VCallAd Crackmefrm.txtkey
0040E49C: 19 FStAdFunc
0040E49F: 08 FLdPr
0040E4A2: 0D VCallHresult get__ipropTEXTEDIT;;去序列号
0040E4A7: 3E FLdZeroAd
0040E4AA: 46 CVarStr
0040E4AD: 04 FLdRfVar 0012F440h               ;取与821581432进行异或并减去55475的结果
0040E4B0: 9C SubVar                                                                                                                ;序列号减去0012F440h的值
0040E4B4: 04 FLdRfVar 0012F384h
0040E4B7: 21 FLdPrThis 0014EA60h
0040E4B8: 0F VCallAd Crackmefrm.txtname
0040E4BB: 19 FStAdFunc
0040E4BE: 08 FLdPr
0040E4C1: 0D VCallHresult get__ipropTEXTEDIT;                取用户名
0040E4C6: 6C ILdRf 00000000h
0040E4C9: 4A FnLenStr
0040E4CA: FD Lead2/CVarI4                     ;取用户名长度
0040E4CE: 5D HardType
0040E4CF: 33 EqVarBool                        ;与序列号减去0012F440h的值进行比较
0040E4D1: 2F FFree1Str
0040E4D4: 29 FFreeAd:
0040E4DB: 35 FFree1Var
0040E4DE: 1C BranchF 0040E55B ?               ;不等则跳转,相等则是正确的序列号,即序列号减去0012F440h的值等于用户名的长度!!!
0040E4E1: 27 LitVar_Missing 0012F3B8h
0040E4E4: 27 LitVar_Missing 0012F3D8h
0040E4E7: 3A LitVarStr 'Correct key'
................
.................
0040E55B: 08 FLdPr 0014EA60h
0040E55E: 89 MemLdI20
040E561: F4 LitI2_Byte: -> 1h 1
0040E563: A9 AddI2
0040E564: 08 FLdPr
0040E567: 8E MemStI2
0040E56A: 08 FLdPr
0040E56D: 89 MemLdI2
0040E570: 70 FStI2 0012F35E
0040E573: 6B FLdI2
0040E576: F4 LitI2_Byte: -> 6h 6
0040E578: C6 EqI2
0040E579: 1C BranchF 0040E5F6 ?
0040E57C: 27 LitVar_Missing 0012F3B8h
0040E57F: 27 LitVar_Missing 0012F3D8h
0040E582: 3A LitVarStr 'I can't stand it anymore'
0040E587: 4E FStVarCopyObj 0012F3F8h
0040E58A: 04 FLdRfVar 0012F3F8h
0040E58D: F5 LitI4: -> 24h 36

这一段完全就将序列号的面纱揭了下来,通过以上分析可知用户名和序列号要满足以下条件:1.       用户名和序列号不能为空,并且序列号长度要大于等于5;2.       将用户名各个字符的ASCII码整数转换成字符串连接起来得到一个数sk3.       若sk的长度大于9,把上面这个数除以圆周率并取整直到sk的长度小于等于9;
4.       经过3处理的sk与821581432异或后减去55475然后再加上用户名的长度就是序列号!
   OK!接下来就是进行注册机的编写了,为了快速以及浮点数处理上不出问题,本注册机使用VBA在Excel里完成:
   在B1单元格输入用户名,然后点击按钮便会在B2单元格出现正确的序列号!计算代码如下:   Private Sub CommandButton1_Click()
Dim u As String   '取用户名
Dim uk As String    '用户名ASCII码值连接的字符串
Dim i As Long
Dim pi As Double    '圆周率
Dim sk As Variant   '除以pi后的保存结果
Dim serial As Long

pi = 3.141592654

'将用户名各个字符的ASCII码整数转换成字符串连接起来
'例如pnmker的ASCII码值分别为112 110 109 107 101 114,那么最终的字符串
'uk = "112110109107101114"
u = ActiveSheet.Cells(1, 2)
uk = ""
For i = 1 To Len(u) Step 1
uk = uk & CStr(Asc(Mid$(u, i, 1)))
Next

'将连接起来的字符串当做双精度浮点数处理,如果长度大于9,则一直除以PI
sk = CDbl(uk)
While Len(sk) > 9
sk = Fix(sk / pi)
Wend

serial = CLng(sk) Xor 821581432
serial = serial - 55475 + Len(u)

ActiveSheet.Cells(2, 2) = serial

End Sub





Double_Z 发表于 2015-6-21 10:15

本帖最后由 Double_Z 于 2015-6-21 10:27 编辑

楼主帖子好细致,我是没有耐心贴那多图的。我是用VB Decompiler 结合OD调试。参考的是这两篇文章
1 使用OllyDbg从零开始Cracking 第三十章-P-CODE-Part2:http://bbs.pediy.com/showthread.php?p=1301559
2 VISUAL BASIC 逆向工程-反编译步骤:http://past.pediy.com/showthread.php?p=13295

灵光丶Fiycix 发表于 2015-6-21 06:42

拿个沙发。此贴很精彩

Hmily 发表于 2015-6-21 09:37

vb pcode都是静态看,动态也很有意思,学习。

(り帕克先森 发表于 2015-6-21 10:02

谢谢分享。

Leimax 发表于 2015-6-21 10:17

一级赞,可惜我看不懂

ljhfrank 发表于 2015-6-21 14:22

学习学习

1151425395 发表于 2015-6-21 15:35

        高度学习了。{:301_992:}

qiyuzhilu 发表于 2015-6-21 16:22

高度学习了。{:301_997:}

wanttobeno 发表于 2015-6-21 16:42

感谢分享!
页: [1] 2 3 4 5 6
查看完整版本: 160个CrackMe练手之038