1 前言
最近博主在学习Android逆向的时候,参照吾爱破解论坛的《教我兄弟学Android逆向系列课程》
学习的时候,学到第8章《教我兄弟学Android逆向08 IDA爆破签名验证》
的时候,开始上手 ida pro 反汇编 so 库,在动手修改 so 库指令的时候遇到了困难,经过一番研究,终于搞懂了在 ida pro 中修改 so 库中 arm 汇编指令的方法,并完成了课后习题:
爆破李华Demo中的用户名和密码 要求输入任意用户名和密码 会提示登陆成功
于是写下这篇博客记录研究过程,并把被作者一笔带过的最核心问题如何修改so库讲清楚。
附上原吾爱破解教程地址:
《教我兄弟学Android逆向系列课程+附件导航帖》
《教我兄弟学Android逆向08 IDA爆破签名验证》
2 准备
(1) 下载 ida pro 7.0
原贴中使用的是ida pro 6.6,博主使用的是 ida pro 7.0,这一版是免安装的绿色版,下载地址:
IDA Pro 7.0 绿色版
(2) 下载 Android Killer
AndroidKiller_v1.3.
(3) 下载夜神模拟器
如果有真机也可以使用真机,这里我们使用夜神模拟器:
夜神模拟器官网
(4) 下载demo
原贴中的demo:黑宝宝.apk
下载地址:
链接:/s/1h6pX2ARE3qtiKiYbcnJ-3g 密码:duv5
(5) 下载
下载链接:/s/1n16NEx67zLHfGtVpU-CKAA 密码:7xg6
(6) Arm指令手册
/download/fuchaosz/12243691
(7) arm指令转换网站
Arm汇编指令转机器码网站:/
3 SO库的选择
首先把 黑宝宝.apk
拖进 Android Killer 中反编译,我们可以看到在 lib 中有三个 :
armeabi : 32位 arm cpu 库,几乎所有手机都支持
armeabi-v7a : 64位 arm cpu 库,现在我们买的手机基本上都是64位的cpu了
x86 : 在电脑上运行的模拟器或者基于Intel x86的平板电脑上用的
- 1
- 2
- 3
这里要说一下历史,在电脑CPU芯片领域,Intel独霸天下,x86
代表32位cpu,x86_64
代表64位的cpu,cpu位数增加不仅意味着运算速度更快,同时还代表着可以使用的内存更大,例如32位系统最大只能使用4G内存,所以windos xp上装8G内存条完全是浪费;但在手机领域arm才是龙头老大,可以说垄断了市场,因为arm制定了标准,所以,我们经常听说的高通骁龙芯片,虽然芯片是高通生产的,但却是arm的架构和指令集,因为我们的app到底层最终都被翻译成了arm指令。
这次,我们选择 armeabi
目录下的 :
因为这是几乎所有手机都支持的,而且网上资料是最多的,并且这三个版本的库在指令上有很多差别,例如:在 armeabi 中跳转指令是 BNE:
但在 armeabi-v7a 中跳转指令却是 CBNZ:
在 x86 中跳转指令则是 jnz:
所以,作为初学者,我们选择 armeabi 下的 库作为研究对象,便于在网上查找资料。
另外,值得一提的是,以前很多 app 只提供 armeabi 版本的 so 库,所以在安卓模拟器上很多app运行不了,尤其是很多游戏无法在模拟器上运行,好在现在主流安卓模拟器已经可以运行 armeabi 库了,例如夜神模拟器,所以,这次我们修改后的 armeabi 下的 是可以运行在夜神模拟器上的。
4 引出问题
参照 《教我兄弟学Android逆向08 IDA爆破签名验证》
这篇文章一步一步来,先将 黑宝宝.apk
拖入 Android Killer 中反编译,然后再将 armeabi
目录下的 用 ida pro 7.0 打开,接着在 Export 窗口中搜索 check 函数,接着导入
,接着替换
_JniEnv
和 env
两个参数,然后就到了关键的地方,在函数流程图中,作者要改变函数流程,要将 BNE 改为 BEQ,如下图:
看关键的代码:
cmp R0,#0
BNE loc_F62
- 1
- 2
cmp 是比较两个数是否相等
loc_F62 代表的函数是 “签名不一致,退出程序”
BNE 指不相等的时候跳转(branch not equal)
BEQ 指相等的时候跳转(branch equal)
结合反汇编后的C代码和函数流程图,我们可以很明显的知道这段代码的含义就是:如果签名不相等,则跳转到loc_F62,即直接退出程序。很明显我们回编译后的apk签名和作者预设的签名是绝对不可能一致的,所以会直接退出程序。
搞清楚了逻辑,那么爆破这个apk的签名其实就是把 BNE loc_F62
改为 BEQ loc_F62
,即签名一致则跳转到 loc_F62退出程序,我们的签名和作者签名一定不同,所以程序不会直接退出,爆破成功。
接下来就是核心的地方:如何将 BNE 改为 BEQ ?
参照原贴方法,鼠标点击BNE那一行,然后View->Open subviews->Hex dump 查看那一行指令对应的机器码:
在10 D1
上点右键Edit,修改为10 D0
:
接着右键apply changes:
再回到函数流程图,可以看到 BNE
已经变为BEQ
:
那么问题来了:为什么要这样改?
我们可以看到上图第2个BEQ loc_F74
命令对应的机器码是09 D0
:
铺垫结束,问题来了:我们要修改操作码而不是操作数,为什么是把10 D1
改为10 D0
,而不是改为09 D1
?
这个问题是最核心的问题,但是原帖里面作者一带而过了,加入现在我要把BNE
指令改为无条件跳转指令 B
又该怎么改呢?
博主在这里卡了一天,最后经过实践得出了结论,所以写了这篇博客。下面我们来深入探讨这个问题。
5 深入问题
先来回顾一下《汇编》和《计算机组成原理》的内容,计算机只认识0和1,所有的操作听歌、看视频、打游戏等等,归根结底都是对0和1进行操作,在Intel x86架构中,一条指令是32位,因为32位CPU一次必须读入32位,不多不少,这一次读入的32个0或1就是一条指令,那么一秒钟可以读多少次呢,这就是cpu的主频决定的,所以理论上,cpu主频越高,一秒钟执行的指令越多,性能越高。接着思考,cpu是如何知道这32个数字(一条指令)中哪些数字代表操作,哪些数字代表被操作的数据呢,这就是指令格式,x86指令格式如下:
一条指令(32位) = 操作码(前16位):操作数(后16位)
操作码代表做哪些运算,例如:ADD、MOV、B 等等
操作数就代表被操作的数据,即可以是数据也可以是地址。
前面16位操作码排列组合起来就是指令集,每家厂商对指令集是不同,arm指令集也是同理,这就是汇编指令和机器码的关系。当然,实际指令格式不一定是这样平分的,但都是由操作码和操作数两部分组成的。
回到正题,我们梳理一下:
BNE loc_F62 对应机器码 10 D1 对应二进制 0001 0000 1101 0001
BEQ loc_F74 对应机器码 09 D0 对应二进制 0000 1001 1101 0000
- 1
- 2
根据上面的知识,我们要改的是操作码,所以应该是把 操作码 10
改为 09
才对呀,即改为 09 D1
而不是 10 D0
,博主被这个问题深深的卡住了,于是查阅资料,搜到了arm的官方指令手册,查看了官方给的机器码表,结果更懵逼了。
打开arm指令手册,并没有BNE、BEQ指令,再看B指令的解释,翻到A6-3.1:
对照下面的条件码表:
通过官方手册和条件码表,我们组合起来就得到BNE
指令的机器码:
1101 + 0001 + 8_bit_signed_offset
可是ida pro里面 BNE loc_F62
对应的正确的机器码明明是:
0001 0000 1101 0001
这就把博主整崩溃了,经过一天的谷歌百度,再经过反复实践,博主得出以下结论(敲黑板、划重点、记笔记),该结论没有找到确切资料,但抓住老鼠就是好猫。
6 结论
ida pro 反汇编 Android SO库(armeabi版本) ,一条汇编指令如果由4个十六进制数组成的,则前面2个(十六进制数)是操作数,后面2个(十六进制数)才是操作码,即so库的指令格式如下:
一条SO的汇编指令(16位) = 操作数(前8位) :操作码(后8位)
划重点:
1、一条指令是4个十六进制数
2、前面2个是操作数,后面2个才是操作码
3、如果你发现一条指令是8个十六进制数(例如BL),那么以上结论不适用
4、SO库的字节数是不能变的,我们只能替换,不能增删
注意:4位十六进制机器码的指令都遵循上述规律,但是8位十六进制的指令规律还没有总结出来,如果你知道了,欢迎给我留言。
7 解决问题
根据以上结论,我们很容易就能理解为什么要把 D1
改为 D0
了,因为操作码是后面两位,根据arm手册和条件码表,我们很容易组合出来BNE
的操作码是D1
,BEQ
的操作码是 D0
,所以,正确的操作是把 10 D1
改为 10 D0
,问题解决。
如果我们每次修改指令都要去查arm手册的话,那是very dan疼的,那英文太酸爽,arm生怕多写几句浪费纸,所以,我们在实际操作中有以下两种方法来修改指令:
(1) 方法一:照葫芦画瓢
既然我们已经知道改指令就是改后面2个数,那就so easy了,我们只要在ida pro里面查看目标指令的机器码后面2个数,然后照着改就可以了,例如:我们接着要把BEQ loc_F62
改为 B loc_F62
呢,B指令是无条件跳转指令,首先鼠标点击到其他 B
指令上,View->Open subviews->Hex dump 查看机器码为14 E0
:
接着我们将 10 D0
改为 10 E0
,这样 BEQ loc_F62
就又被改为了 B loc_F62
改动成功,成功应用了我们总结的知识,这里只是个演示,改动完后记得还原。
(2) 方法二:在线转换机器码
我们还可以打开以下网站:
/
直接输入我们想要的指令,直接转换为机器码,一个小技巧,如果需要操作数的话可以用#0
,例如我们想知道B
指令的机器码,则可以构造一条语句 B #0
就可以了
从这个网站我们也可以发现一点,就是ida pro反编译SO库用的是 Thumb-2 指令集,这个指令集是 arm 指令集的子集,标准arm指令集是操作码后跟操作数,这个thumb-2指令集反过来了,坑啊,关键是网上还查不到资料。
8 完成课后练习
理论知识学习完了,接着我们来完成课后习题:
爆破李华Demo中的用户名和密码 要求输入任意用户名和密码 会提示登陆成功
通过第4节的C语言代码我们可以看到,check这个函数很简单,除了检验签名外,就是当你输入用户名koudai
和密码black
后显示登陆成功,只要一个输入不对,就登陆失败,博主看了网上很多破解教程,都是把原来等于koudai 和 等于black的判断逻辑改为不等于,这样有个很明显的问题,虽然输入其他用户名密码可以登陆成功,但当我们用户名输入koudai 或 密码输入 black就会登陆失败,不符合题意:要求输入任意用户名和密码 会提示登陆成功。
所以,这里我们要改函数调用逻辑,要求不管输入什么,都要跳转到正确的函数中,还记得上节提到的B
指令吗,这就是无条件跳转指令,下面开始。
首先解决输入任意用户名调到正确的函数,先看流程图:
很明显不管输入什么,最后必须无条件跳转到loc_F74
所在的函数,那就简单了,直接BEQ loc_F74
改为B loc_F74
,还记得怎么改为B指令吗,这里把09 D0
改为09 E0
:
改完后我们发现,无论输入什么都会进入校验密码的函数模块,那么用户名输入问题解决,下面处理密码输入。
仔细看上面的流程图,校验密码的函数有个函数首地址loc_F74
,但是登陆成功的函数块没有首地址,反而是登陆失败的函数有首地址loc_F8C
,所以照葫芦画瓢行不通了,怎么办呢?
博主在这里也卡了一下,后来想了一下,BNE loc_F8C
这条指令真是多余,要是没有这条指令不就可以直接进入登陆成功的函数块了吗,那么问题又来了:怎么清除一条指令?
前面说了,so库只能替换,不能增删,网上搜了一下,说是有个nop指令用于清除目标指令,就是00 00
,于是直接把这条指令的4个数全部改为0,即04 D1
改为00 00
,然后就可以了,看改完后的流程图:
看流程图,不管用户名和密码输入什么,都会显示登陆成功,至此,作业完成,接着把ida pro中的改动保存到so文件:
关闭ida pro,将修改后的文件替换掉原来armeabi目录下的so文件,接着删除armeabi-v7a和x86目录,再回编译后安装到夜神模拟器,输入任意字符串都可以提示登陆成功:
9 总结
通过本篇文章的学习,我们掌握了4个十六进制数指令的格式和修改方法,同时我们学会了修改 B系列
跳转指令,并且明白了为什么要这样改,以后就可以随心所欲的修改函数的流程了,所以,很多高级语言的语法和保护措施在底层都失效了,这就是无法无天的底层,也是搞底层的乐趣。
当然,8个十六进制数的指令(例如BL指令)我们还没有弄清楚规律,但是目前已经够用了,所以,以后再来研究,如果你弄清楚了,欢迎在下方给我留言。
10 补充
如果你在实践中遇到了 ida pro 7.0 无法显示中文的问题,请参照下面这篇博客解决:
ida pro 7.0 无法显示中文的问题解决方法
如果你在用 Android Killer 回编译的时候遇到错误导致回编译失败,请参照下面这篇文章解决:
Android Killer反编译失败:No resource identifier found for attribute 问题解决方法
11 转载请注明来自“梧桐那时雨”的博客:/fuchaosz/article/details/104804026
Tips:
如果觉得这篇博客对你有帮助或者喜欢博主的写作风格,就关注一下博主或者给博主留个言呗,鼓励博主创作出更多优质博客,Thank you.