老师布置的一个作业,算法其实不难,但逆向起来比较复杂,看懂了就好理解了。简单的做个记录。

Intro

程序是一个对对碰游戏,每过一段时间,程序会检测注册表中是否有注册信息,如果没有会弹出注册窗口。

Bruteforce

先看看注册向导中会提供什么有用的信息:

通过字符串搜索,定位到关键的几处代码:

然后单步走出这个函数,在 004063CA 处,对前面函数的结构做了一个判断,并跳有一个跳转,往下的代码,可以看出是在使用 RegSetValueA 往注册表里写东西,说明判断注册码成功后进行注册。那么只需要把这个跳转 nop 掉,或者是改成强制的 JMP 004063D0(即跳到下一条指令)即可:

将修改保存到文件之后随便输入什么内容都能注册了。

Algorithm Analysis

先尝试随机输入一些数据,然后根据前面的分析,可以发现在 004063B2 处进行了判断,前面的函数应该生成了注册码:

进入 00406780 函数,通过函数将两个长为 32 的字符串转为了整型,整型的字长被记录在最低字上:

接着在在 0040686C 处发现两个字符串以及我们输入的字符串被作为参数传入:

进入 00406570 函数之后,在 004065C0 处先对字符串的长度判断是否小于 0x100,接着判断字符串的长度是否大于零:

然后对字符串每个字符进行判断是否在 0 ~ F 的范围内,即是否是一个十六进制字符串:

显然如果不是的话就会出错,所以在这里设下断点,重新输入一个十六进制数 1234 作为注册码。接着继续往下,在 0040663A 处将我们的输入字符串转成了整型:

接着往下跟进 00401AE0 函数,其中也传入了三个数字。在 00401B39 到 00401B48 处,计算 8231FC324594496514663D91E6C19989 共有多少位:

接着将得到的位数 0x80 减去 2,开始进入下面的一个大循环:

接着在循环中多次调用了 00410630 函数,其中将我们的输入自己相乘,并将结果返回:

然后调用了 00410A40 函数,具体的汇编个人觉得是被编译器优化过后的结果,通过推测判断可以发现这个函数的运行结果为之前的乘积模 CFBCC6EC474AE5CD0F7BC8DBBA353A11 的结果:

接着在后面取出了 8231FC324594496514663D91E6C19989,去最高的字并右移 0x1E 位,判断最低位是否为 1,如果为 1 进行下面的一堆乘法操作;反之跳过这段内容。看到这段内容,回想起以前做 ACM 的时候接触过的快速幂算法,按比特判断是否加上对应的次方:

如果为 0,跳转到最下面,将迭代的数字减一,并开始下一个循环:

在接下来的循环中,如果判断相乘的数大于一个字,就会分成几个字分别和目标相乘,依次由最高字开始,每次将乘积左移 32 位后加上下一次的乘积:

这样下来基本清楚 00401AE0 函数是将我们的输入乘上 8231FC324594496514663D91E6C19989 次方再模去 CFBCC6EC474AE5CD0F7BC8DBBA353A11 的结果,推测应该是 RSA 了。后面有一些乱七八糟的函数,调试了很多遍后没发现有什么作用。最后下断点在 00401E34 这里,最后 EAX 存储的地址即为指向 0x1234 经过上面操作的结果:

然后在 00406570 函数的最后面 0040675F 这边停下来,发现最后将结果转成了字符串,并在 EAX 里存储了这个字符串的地址:

最后调试的时候是将结果和 0 进行比较,这样的话注册码写 0 就行了,0 的几次方结果都是 0。测试了一下注册码写 0 的话是可以写入注册表的,但是感觉不太可能这么简单,不过前面的 RSA 算法分析应该是没有什么问题了:

整体看下来发现动态调试确实相比静态分析能更加深入地熟悉一个程序,连续分析了两天,是一次痛苦但收获颇多的经历。

References

https://bbs.pediy.com/thread-38901.htm
https://oi-wiki.org/math/quick-pow/


re

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

初探Python沙箱逃逸
Use SROP with ret2VDSO