软编码寻找序列号(三)
使用工具
- OllyDbg 1.10原版,简称
OD
; OD
汉化
和插件
均来自互联网;- CrackMe来自互联网,仅供学习使用;
- 文中特殊数字均是
HEX
,为了书写方便采用DEC
;
逆向思路
首先,打开软件到处点点,逆向一个软件起码要会用吧,不会用就没必要说逆向了:
这是一个类似于蜘蛛纸牌的程序,需要安装,安装完成打开之后会提示输入注册码:
输入用户名和 6 位数字的注册码之后,发现
Ok
按钮无法点击:有点意思,看来只有输入正确的序列号按钮才能点击,至于为啥注册码是 6 位数字,它不是已经说明了吗?
倒入
OD
开始分析:首先按下
Ctrl + A
让OD
分析一下代码;猜测一下它的工作原理,如果在输入正确的注册码之后按钮可以点击,那就说明它会实时获取我们输入的内容并进行判断,这样的话,有用的应该就是
内存断点
了;说干就干,既然要去内存中找我们输入的内容,那输入就一定得个性一点,起码不是内存中常见的,不然一找一大堆就无从下手了:
然后去
内存窗口
按下Ctrl + B
或者右键选择查找
,输入我们的序列号点击确定开始查找,记得勾选区分大小写
:输入的还算奇葩,只找到了一个:
设置
内存访问断点
,既然它都告诉我们是 6 位了,那就设置 6 个字节:然后去程序界面再输入一个字符后,发现程序中断了,回到
OD
看看,嘿,它居然在转移数据,在数据窗口跟随一下EDI
,果然是我们输入的假序列号:既然数据都挪窝儿了,那之前的断点也就没用了,选择
EDI
的数据右键断点 >> 内存访问
重新设置内存访问断点
:断点设置好之后,
F9
继续运行程序,程序再次中断,发现又在挪窝儿?怎么回事儿,挪窝儿这么快乐吗?重新设置
内存访问断点
,然后继续运行程序;程序再次中断,并且有比较有跳转,这儿可能就是重点了,分析一下:
第 1 行是:
EAX
的前 4 字节数据拷贝到EDX
,F7
单步执行,然后查看一下是什么东西:原来是我们输入的假序列号,前面还多了个
04
,应该是长度吧;第 2 行是:
EDX
的前 4 字节数据拷贝到EBX
,F7
单步执行,然后查看一下是什么东西:嘿嘿,一串可疑的数字,而且是 6 位,和序列号的规则很吻合,而且前面多了个
06
,应该也是长度;接着往后分析,逻辑很常规,比较完前四个字节比较长度,都不相等就返回了:
结局没有悬念,用户名对应的序列号就是那串数字,不过,序列号是如何计算出来的呢?我们接着分析:
我们都知道
RETN
返回的是调用位置的下一行,那跟着RETN
去看看:按照程序的流程,既然这里是判断,那上面的就是获取和处理了,上面
CALL
不少,哪一个才是我们想要的那个呢,按照先获取后处理的流程来看,离我们最近的那个CALL
应该是处理用的,但CALL
上面那行代码看着也不像是给它传递参数,那就在这个CALL
上设置断点,进去看看它是干嘛的;设置好断点,重载程序并运行程序之后,程序都没有加载完成就中断了,既然都没有输入,那肯定不行啦,
F9
运行程序,起码运行到我们能输入内容;然后,我们发现,输入用户名的时候,每输入一个字符都会中断一次,那就说明断点有效,虽然不知道这个
CALL
具体是干嘛的,但肯定和用户名有关,既然每次都中断,那就输入一次运行一次呗,直到用户名输入完成,还没有完,为了防止它没有完全获取用户名(鬼知道它是怎么获取的,万一是运行之后获取呢?),在输入完用户名最后一个字符之后,程序会再次中断,我们再运行程序一次,在序列号一栏中,输入一个字符,发现它中断了;管它呢,反正用户名输入完了,也在序列号一栏输入字符了,能保证它获取了完整用户名就行,至于它是干啥的,
F7
进去看看不就知道了:刚分析了几行,发现它用同样的方法转移用户名,既然要知道序列号是怎么计算的,那肯定要跟着用户名喽,给
EDI
中的用户名数据设置内存访问断点
:接着,
F9
运行程序,程序跳转到了这里,果然,这里就是用来生成序列号的:这里是一个完整的小循环,用来运算用户名的每一个字符,并且把结果累加,至于运算规则:
字节下标从 1 开始;
字节机器码和下标相乘;
IMUL DWORD PTR DS:[EDX*4+4EB5D8]
这行代码中的所有特殊字符:1 2 3 4 5 6 7 8 9 10 D9 63 58 22 3E 93 F0 08 34 62 11 12 13 14 15 16 17 18 19 20 1B BF D7 B9 6F 4A 5A B2 84 24 从上面的表格可以看出,用户名最大不能超过 16 进制的 14 位,也就是 10 进制的 20 位,代码中也一直和 14 做比较,当然,这是我的猜测,有兴趣的同学可以试试;
然后字节机器码和下标相乘的结果,和表格中对应下标的字符相乘;
接着,把每个字符运算的结果累加;
就这?这么简单?想多了;
接着往下看:
在上一步,也就是那个小循环中,按照特定的规则运算了用户名的每一个字节,并把每个字节的运算结果进行累加,然后才是这一步,规则如下:
上一步累加的最终结果和
A 的 6 次方
进行求余运算;如果余数大于
A 的 5 次方
,进行下一步,也就是开始计算序列号,至于为什么要有这么个规定,如果你还记得序列号的规则,那就不会有什么疑惑了,因为只有余数大于A 的 5 次方
,才能计算出 6 位数的序列号,看到这句话,是不是已经想到如何计算序列号了,追随你的心,是的,就是那样计算的;如果余数小于
A 的 5 次方
,那么就重复执行上一步中的小循环,只不过,这一次的计算结果会累加在上一次计算的最终结果上,也就是,你只需要计算一个完整小循环,如果计算的结果求余A 的 6 次方
小于A 的 5 次方
,只需要把你计算的最终结果乘以 2,就是下次循环的计算结果,如果乘以 2 的结果求余A 的 6 次方
还是小于A 的 5 次方
,那就乘以 3 呗,以此类推;不要急,下面就开始计算序列号了;
接着,就到了计算序列号的
CALL
:- 这一步很简单,用累加结果求余
A 的 6 次方
,然后就是计算序列号的函数了; - 到重点了,当然是
F7
进去看看喽;
- 这一步很简单,用累加结果求余
进
CALL
之后,你会很惊讶,因为真正的计算就简简单单几行代码:- 用累加结果求余
A 的 6 次方
后的余数对A
进行求余运算; - 然后判断商是否为 0,不是 0 ,继续对
A
进行求余运算,直到商为 0; - 把每次对
A
求余运算的余数加上 30 存起来;
- 用累加结果求余
还没有结束,最后一步就是把上一步的结果逆序,然后就是真正的序列号了:
啰哩啰嗦终于分析完了,总结一下注册码的生成规则:
用户名必须小于 10 进制的 20 位,我也不确定啊,没试;
用户名字节的下标从 1 开始;
取用户名每个字节和它对应的下标进行乘法运算;
用每个字节运算的结果乘以对应下标的给定字符,给定字符如下,序号就是下标:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 D9 63 58 22 3E 93 F0 08 34 62 1B BF D7 B9 6F 4A 5A B2 84 24 然后把每个字节乘以给定字符的结果相加;
用相加的结果求余
A 的 6 次方
,如果余数大于A 的 5 次方
,向下执行;如果余数小于A 的 5 次方
,用累加的最终结果乘以它的自然数倍数,如 2 倍或者 3 倍,直到最终结果求余A 的 6 次方
的余数大于A 的 5 次方
;然后用上一步中的余数,也就是求余
A 的 6 次方
后的余数,对A
进行求余运算,直到对A
的求余运算商为 0;把每次对
A
求余运算的余数加 30;把余数加 30 后的数字逆序排列;
转为 10 进制就是用户名对应的序列号;
说人话:
- 16 进制的
A
是 10 进制的10
; A 的 6 次方
是1000000
,也就是一百万;A 的 5 次方
是100000
,也就是十万;- 也就是说,用户名累加的结果大于一百万的时候,这条规则才真正有意义,否则,对
A 的 6 次方
求余的余数永远是它自己; - 至于为啥要大于
A 的 5 次方
,十万不就是最小的 6 位数吗?你不大于A 的 5 次方
也就是不够 6 位,肯定不行喽; - 至于对 10 求余的结果是什么,不用说了吧?
- 16 进制的
30
是什么,是 ASCII 码的0
,所以,求余后加 0 的意义是什么? - 总结:如果你输入的用户名运算的最终结果,转换为 10 进制是 6 位数字,那这个 6 位的数字就是注册码;如果小于 6 位,就用最终结果乘以 2 或 3 或 N,以此类推,直到是 6 位或更多位,当然,6 位最好,因为更多位需要求余 😊;
- 16 进制的
无图无真相:
例如:
crackme
的机器码是63 72 61 63 6B 6D 65
;每个字节和它对应的下标进行乘法运算:
63 * 1 72 * 2 61 * 3 63 * 4 6B * 5 6D * 6 65 * 7 63 E4 123 18C 217 28E 2C3 用每个字节运算的结果乘以对应下标的给定字符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 D9 63 58 22 3E 93 F0 08 34 62 1B BF D7 B9 6F 4A 5A B2 84 24 63 * D9 E4 * 63 123 * 58 18C * 22 217 * 3E 28E * 93 2C3 * F0 53EB 582C 6408 3498 8192 1778A 296D0 把每个字节乘以给定字符的结果相加:
53EB + 582C + 6408 + 3498 + 8192 + 1778A + 296D0 >> 5D4A3
;对 10 求余再加 0 之类的操作就没必要了吧;
将
5D4A3
转换成 10 进制是382115
;输入用户名和注册码: