逆向初学者CrakeMe(1)

在《加密与解密》(第三版)第三章 3.2.16 小节中,使用 Delphi5.exe 这个小 CrakeMe 简单地讲解了一下 FLIRT 的知识,但没有对该程序做深入的讨论,这里用 OD 对其进行破解。


从程序名字可以知道它是用 Delphi 语言编写的,用 PEID 查看证实也的确如此。打开程序,随便输入注册信息,根据不同情况弹出以下几种错误提示窗口:


记住以上的提示信息,大概了解了一下程序的功能,但程序内部的正确的注册信息是已经常量存在的还是根据输入的注册信息来计算生成的,我们还不得而知。直接用 OD 载入,入口处如下:


可以看见,入口点后面跟着几个 call ,但它们的作用是什么,我们不需要知道,我们只需要分析关键部分。直接搜索字符串,找到刚刚弹出的错误提示信息,并双击 "请输入用户名和注册码“一行,转到该处指令处






在反汇编窗口注释一栏,我们可以找到所有的提示信息,并可以推知,当输入正确的注册信息时,程序将提示 “恭喜你,注册成功!“。接下来,我们要确定破解的大体思路。由于这是一个小程序,猜想功能并不复杂。因此猜想程序在接收用户输入的注册信息后,程序执行将执行到我们现在所处的地方,根据输入的注册信息,判断并选择一条相应的提示信息并输出。因此我们可以在现在所处的地址处下个断点,按 F9 让程序跑起来,然后随便输入注册信息,程序将在我们设立的断点处停下,接着我们单步执行,分析正确的注册信息是如何诞生的。


选择在地址 00441693 处下断,输入的注册信息如下,按以上步骤执行后,程序中断在我们设立的断点处



接下来 F8 单步执行,当执行到 004416AD 行时,程序碰到一个 cmp 和 jnz 指令,观察堆栈窗口中 local.3 处的数据,也就是 ebp-3*4 处的数据,发现是我们输入的用户名(实质上是用户名在内存中的存放地址,可在数据窗口中跟随观察)。




因为我们输入的用户名并不为空, jnz 指令将跳过输出 “请输入用户名和注册码”的 MessageBoxA 函数调用指令。

接着分析接下来的指令


同样可以发现一条 cmp 指令和 jge 指令。单步步过 004416DF 行,观察 eax 寄存器,存放着用户名的地址,紧接着执行 call 指令,再观察 eax 寄存器,发现其值为 6。联想该处的提示信息“用户名至少四个字符”以及我们输入的用户名 “ABCEEF”,推知该 call 指令的返回值是用户名的长度(跟踪进去证实的确如此),用 eax 存放。将用户名的长度与 4 比较后,若 大于等于4 ,则跳过输出 ”用户名至少四个字符!“ 的MessageBoxA 函数调用指令。


当然,不要忘了在我们分析过的 call 指令后面加上注释。


接下来我们面对一段没有注释的指令,过了这一段,将是提示输入注册信息正确与否的提示信息,因此这一段指令应该比较重要。不急,慢慢分析。一路 F8 单步步过,到地址 00441750 处,发现掉进一个循环中


仔细分析,容易得知,该处循环的作用是把用户名的 ASCII 码生成一个字符串,并压栈到堆栈 local.1 处,ebi 作为用户名相对地址偏移量,ebx 初始存放着用户名长度,用作循环计数器。输入的用户名 “ABCDEF" 的 ASCII 码对应生成的字符串即为 ”414243444546"。为了方便,接下来都把这个由用户名的 ASCII 码生成的字符串称作 用户名生成串。


记住这个生成的字符串,继续往下看。当执行到地址 0044176B 行时,观看堆栈窗口 local.7 处,出现了我们输入的注册码,说明该行指令上面的那个 call 的作用是把我们输入的注册码压栈到 local.7 处


继续往下分析,先不执行程序,用鼠标单击地址 00441776 这一行,看见有一个跳转,这个跳转反映了我们输入的注册信息的正确与否。


很明显,在该跳转前面的那个 call 指令里面,进行了注册信息正确与否的判断。爆破没有意义,我们在那个 call 指令上下个断点,方便重新调试,并加上注释,然后 F7 跟进去。进去以后,上个全图


可以看见,接下来的指令充满了各种跳转。慢慢分析,前面几条语句没有太大价值,一直到地址 004403C6F 行。这里实现了一个功能,判断出注册码和用户名生成串两者中长度较短的长度,把这个长度压栈后,再把该长度右移 0x2,存放到 ebx 中。这里右移 0x2,也就是除以 4,为什么要这样做?这个下面再说


此时 edx 的值为 1。继续分析,在地址 004403C81 行碰到一个循环。分析得知,该循环以 ebx 为循环计数器,每一次循环,都将从注册码和用户名生成串中依次取出四个字节长度的十六进制 ASCII码 数据,作为 DWORD 类型的整型数据进行比较,若不相等,程序将跳转到后面的指令,并影响 ZF 标志位,跳出该 call 后,程序将输出 ”注册码不对呀,请再试试!“ 的错误提示信息。否则,在循环终止后,继续比较注册码和用户名生成串中还没比较过的部分,并且这部分将小于或等于 3 个字节。


由此可知,将较短的长度除以 4 的作用是,求得注册码和用户名生成串在循环中需要比较的次数,每次比较四个字节。同样可得,如果循环是正常终止的话,说明注册码和用户名生成串中已比较过的部分必然是相同的。

很明显,我们输入的注册信息中,注册码和用户名生成串并不相同,若继续执行循环,程序将输出错误提示信息。为了继续分析接下来的指令,我们在循环判断出注册信息不正确时,修改 ZF 标志位的值,使程序能够继续执行下去。



OK,到这里我们已经能够大概猜测出程序判断注册信息正确与否的算法是这个样子的,将用户名的 十六进制ASCII 码生成一个字符串,再把这个字符串与注册码的十六进制 ASCII 码比较,若相等,则注册成功,否则失败。也就是说,当我们输入的注册码,与用户名的 十六进制 ASCII 码相同,并且用户名不少于 4 个字符时,程序注册成功。继续分析下面的指令,发现与我们的猜测相符,这一部分就不分析了。


当然还没结束,离最终结论还差一点,当我们的用户名是 “ABCD” 而注册码是 “41424344” 时,程序将注册成功。但是当用户名是 “ABCD" 而注册码是  ”4142434445“ 时,也就是说,保证用户名生成串是注册码的子串,或者倒过来时,程序能注册成功吗?并不能,仅当我们的注册码和用户名生成串完全相等时,程序才会注册成功。那么是程序的哪一部分剔除了这种可能呢?让我们想想,在我们的关键 call 后面,紧跟着的就是输出注册成功与否信息的 jnz 指令,这是一条条件跳转指令,依赖于 ZF 标志位的值进行判断,ZF = 0,则跳转。好,我们以用户名是 “ABCD" 而注册码是  ”4142434445“ 为例,去到 call 指令里面最后一条能够影响 ZF 标志位的指令分析,也就是下面这条指令


这是一条 add 指令,“ add eax,eax ”,操作对象是 eax 寄存器,eax 的值最后一次改变,是在找出用户名生成串和注册码两者长度中较短的长度那里


分析一下,如果用户名生成串和注册码长度不相同,则 eax 不为 0,则 add eax,eax 指令执行后,ZF 标志位置 0,跳出 call 后,jnz 跳转成功,输出注册失败信息。否则,若两者长度相同,eax 为 0,则最后 ZF 标志位置 1,jnz 跳转不成功,输出注册成功信息。


弄明白程序判断注册信息的算法后,要注册该程序就很容易了。要注意的是,从用户名生成用户名生成串的时候,使用的是十六进制 ASCII 码,并且字母是大写的,所以注册码中也需要大写的字母



END.



程序下载:http://pan.baidu.com/s/1b6X1me 密码:lrcn



题外话:

        博主目前还是一个逆向的初学者,在逆向这个领域,几乎什么都还不会,还有很长的路要走。

        这篇博客是我的第一篇博客,因为没写过博客,不知道怎么写,都是截图了事。也许其中还有许多错误,或者用词不恰当,说的不够好的地方,希望读者多多包涵,也欢迎直接指明或交流。当然,这是我很用心写的一篇博客。

        这篇博客,没有什么技术可言,面向的读者群体自然很小,只是希望像我一样的逆向初学者,在阅读了这篇博客之后,增添一点搞逆向的兴趣。

        同时,在计划中,这篇博客将是我的【逆向初学者CrakeMe】系列的第一篇文章。接下来的,就一边学一边写吧。


  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值