硬编码寻找序列号(六)
使用工具
- OllyDbg 1.10原版,简称
OD
; OD
插件:中文搜索引擎和ApiBreak;OD
汉化
和插件
均来自互联网;- CrackMe【歪坑牌 CM2.2】来自互联网,仅供学习使用;
- 文中特殊数字均是
HEX
,为了书写方便采用DEC
;
逆向思路
首先,打开软件到处点点,逆向一个软件起码要会用吧,不会用就没必要说逆向了;
这个软件的操作有点不同寻常,填写内容之后会提示注册成功,并且通过重启来验证是否正确:
倒入
OD
开始分析:首先按下
Ctrl + A
让OD
分析一下代码;接着按下
Ctrl + N
查看使用了哪些API
,翻了翻还真不少,搜索一下,GetDlgItemTextA
没找到,那就试试GetWindowTextA
,还真有,在GetWindowTextA
上设置断点,然后去断点窗口
双击断点进入反汇编窗口
给断点设置备注,这是一个好的习惯,请保持;F9
运行程序,输入并点击登录之后,还是熟悉的弹窗,点击弹窗的确定按钮之后,嘿,又回到了解放前,这让我很困惑,难道它是在弹窗之后运行吗?毫无头绪;TranslateMessage
呢?没有 Buffer,无从下手;给
MessageBox
设置断点?皮,它都重启了,断点有什么*用,逆推呢?设置断点跟随之后发现没有什么有价值的内容;试一试
内存断点
,暂停的地方不重要而且在断点位置无限循环,根本连登录按钮都点不了,既然都没有登录肯定没有验证,断点的地方当然不重要了;试试
消息断点
,暂停了,但没有 Buffer,无从下手;试试
中文搜索引擎
:中文搜索引擎
不只是找关键字的,还可以找到其它有用的信息,而恰恰这才是重点:如果看到
software
说明这个程序使用了注册表,而关键信息可能就存储在注册表中:既然它将输入的内容存储在注册表中,那么验证的时候一定会去注册表取回内容,所以这个节点才是关键的环节,重载程序,删除之前设置的无用断点,接下来就是重点了;
至于如何设置断点呢,那就要使用今天的主角儿了
ApiBreak
,点击菜单栏的插件选项并选择ApiBreak >> API断点
,然后选择注册表
以及RegQueryValueExA
选项之后,点击确定;(翻译一下RegQueryValueExA
:Reg
是注册表,Query
是查询,Value
是值,ExA
是 16 进制,连起来应该是设置查询注册表16进制值的断点
,不知道对不对)当然,也可以直接使用系统 API
RegQueryValueExA 或 RegQueryValueExW
来设置断点,这个插件就是简化了操作步骤,不用我们去寻找系统 API;设置好断点之后,运行程序,程序会中断多次,应该是查询了很多次,但我们的关注点只是两个:
用户名
和密码
,那就继续运行程序,直到在堆栈窗口看到需要的信息:用户名
出现了两次,第一次Buffer
中没有地址,第二次Buffer
中才有地址,第二次才是我们关注的重点, 在Buffer
上右键数据窗口中跟随
,然后Ctrl + F9
执行到返回,果然,它获取了我们输入的用户名:同样的情况也会出现在
密码
身上,继续运行程序,获取密码:既然它已经获取了用户名和密码,那就上正菜吧:
获取密码后,在Buffer
上右键数据窗口中跟随
,然后Ctrl + F9
执行到返回,果然,它获取了我们输入的密码,在数据窗口
选中密码数据右键断点 >> 内存访问
设置内存访问断点
:然后继续运行程序,程序中断了,但看了看代码发现这里不是重点,而且继续运行还会中断,数一数,中断的次数刚好是密码的长度,并且每运行一次,
ESI寄存器
中存储的密码都会向后移动一位;继续运行程序,程序再次中断,重点来了,开始分析:
分析完毕,逻辑似曾相识的感觉,没错,和
逆向分析之硬编码寻找序列号(五)
中的逻辑一模一样:- 首先,假码 和 可疑字符串 的前 4 个字节的机器码进行比较,不相等跳转到下一步;
- 接着,假码 和 可疑字符串 的第 1 个字节的机器码进行比较,不相等跳转到下一步;
- 最后,恢复寄存器的内容并
RETN
,然后弹窗,最后回到解放前;
分析完了,那真正的密码是什么呢?当然就是“可疑字符串”:一串看起来杂乱无章的字符,那密码的计算规则又是什么呢?
RETN
返回的地方永远是调用它的位置的下一行,那如果跟着RETN
是不是就能找到调用它的地方,结果是肯定的:如果
JNZ
是返回的位置,那上面一行的CALL
就是调用的地方,如何证明呢?很简单,JNZ
根据ZF 标志位
跳转,如果跳转肯定就注册失败了,那不跳转是不是就成功了?试一试,将ZF 标志位
置 1,然后运行程序,正如所料,注册成功:这就说明,这个
CALL
很重要,给CALL
的位置设置断点,重载程序,程序在CALL
的位置暂停,既然CALL
内部是判断,那上面很可能就是获取并处理了,既然正确的密码是一串杂乱无章的字符,那么它应该需要一个CALL
去处理,所以,大胆的猜测一下,上面那个CALL
可能就是处理程序,给上面那个CALL
也设置断点:再次重载程序,程序中断,
F8
向下执行,查看中间那两行代码干了什么,很有可能就是判断CALL
(也就是函数)的参数:- 两行代码执行后,
EAX
的数据在寄存器窗口
看的很清楚,就是我们输入的假码; - 而
EDX
的数据是什么呢?选中EDX
右键数据窗口中跟随
,数据一目了然,正是比较时用的可疑字符串; - 这也就证明了,上面那个
CALL
就是处理数据用的; - 而密码的规则:在好多个
CALL
之间跳来跳去分析之后,发现是一串固定的字符串加上用户名的 16 进制表示; - 如:
yang
转换成 16 进制是79616E67
,那么密码就是€队蠹易仿耄79616E67
;
- 两行代码执行后,
测试一下: