前言
入职公司一周,完成了一个小项目,与Android逆向有关,具体就不能透露了,在逆向过程中遇到了不少的坑,本篇做一个小的经验总结,在以后进行Android逆向时,能有更好的切入手段。
分析思路
逆向分析时采用的几个主要手段(这里就不区分静态分析与动态分析了):
- 分析第三方so库
- 动态调试
- Smali代码注入
- Frida Hook
第三方so库的分析
一般来说,将apk经过Apktool反编译后,在lib\arm(通常这里有多个版本)目录下会存放该app应用到的第三方so库。在分析经过反汇编得到的Java代码后,有些类可能会直接调用Native方法,如果这个Native方法实现了重要的功能,这时候就需要分析这个Native方法所在的so库了。so库拖进IDA就是Arm/Thumb指令,IDA 7.5版本的F5现在还挺好用,可以结合的看。
这里总结一下常见的几个寄存器,和x86类比的看:
- R0通常存返回值,相对于EAX
- SP相对于ESP
- PC相对于EIP
- B相对于jmp
- BL相对于call
注意事项:不要轻易相信so库中的函数名,第三方库往往为了安全性,故意弄出一些欺骗性的函数名影响你的判断,因此函数名只可作参考,切勿全信。
动态调试
Android的动态调试分为Java层的动态调试和so层的动态调试,Java层主要以smali汇编为主,so层则以arm/thumb汇编为主;然而很多App都有反调试机制,加上IDA Pro的名气又那么大,所以动态调试很容易失败。这里只简单说明一下调试过程以及注意事项。这里以IDA Pro为例进行演示。
So层
在IDA Pro7.5\dbgsrv的目录下,可以用于调试其它操作系统的调试服务器,若想调试Android手机,需要先通过指令
adb push android_server /data/local/tmp
将调试用的服务器(注:安卓模拟器的属于x86架构)push到真实机里。然后转发端口
adb forward tcp:23946 tcp:23946
(23946是IDA的默认端口)修改android_server权限(一般为755),并执行
./data/local/tmp/android-server &
,来启动服务器。这时命令行中可以看到正在对端口进行监听。进入IDA->Debugger->Attach->Remote ARM Linux/Android debugger。这里主机名和端口号,看命令行中,监听的主机名和端口号是啥,就填啥
完了,就可以Attach吧,大部分app都有反调试,一下断点就死。Run的方法,我没成功过。
Java层
Java层动态调试好像并不需要监听,所以只要将需要调试的apk文件,从手机里pull一份出来就行。一般系统软件在
system/app
文件夹下,预装软件在data/app
文件夹下。拿到apk文件后,直接拖入IDA,选择dex文件进行加载。设置debugger选项,Debugger->Debugger options->Set specific options,在Package Name和Activity中分别填入包名和第一个启动的Activitiy。
这里介绍两种找包名和Activity的方式:
设置Debugger->Process options,主要就是改这个端口号,网上教程一般会给它设置为8700,我就用的默认的23946,但是运行后会给你校正到23915,这个问题不大,在命令行中执行
adb forward tcp:23915 tcp:23915
给它转发一下就能用了。最后Debugger->Start process或者点击上方的绿色小三角启动应用,如果手机屏幕出现Waiting For Debugger的界面就说明设置成功了,可以进行动态调试了。此时屏幕上应为smali汇编,如果界面停留在一堆数据的页面也不要慌,View->Open subview->Functions进入函数界面,随便点进去就是smali汇编代码。接下来就可以搜自己想要分析的smali函数进行下断和调试了。
Smali代码注入
smali注入是我这次项目实现的关键。这里介绍几种适合注入的代码。
log插桩
Python代码(命令行):
1 | >>>import frida |
Smai代码:
1 | const-string v0, "HOOK POINT" |
作用:
- 打印程序执行时一些字段的值(变量,参数,返回值等)
- 帮助分析程序完整的执行流程
Toast弹窗
1 | const-string v0, "Hello Toast" |
- p0 :this(Context类型)
- v0:弹出的字段(可更改类型,但是要和makeText中的一致)
- v1:弹窗时长
其它Smali代码
- 将Java代码经过j2s2j转成smali
- 手动调用未执行代码
注意事项
- 寄存器:寄存器一定不能有误,否则程序必定Crush。如果一个寄存器保存的值,接下来会被使用,那么最好别用这个寄存器。转而借用下面的代码即将重新赋值的寄存器。如果赋值不是非常麻烦的话,也可以先借用这个寄存器打印log或者用toast弹窗,完了再给这个寄存器重新赋值。(补充move指令对寄存器长度和编号的限制)
- 类型:类型一定要匹配,例如log的参数一定要是String类型,Toast的参数类型要和寄存器中的参数匹配。
- 日志类型:一般被逆的应用基本上都是release版的,这意味着只能打印info级别的日志,像在smali中添加调用栈打印的函数就很难被捕捉到,或者可以找到对Log类型的作判断的地方进行修改,让release版本也能打印出debug/warn/error级别的日志。
Frida Hook
这里简要列举一下,用js调用Frida接口的几个常见模板:
Hook so库中的函数
1 | //Hook Native函数 |
Hook Java层函数
1 | //Hook Java层函数(例:拦截Activity类的onResume函数) |
打印调用栈
1 | //对Hook到的函数,打印调用栈 |
Frida的局限性
通常来说,在编写好js脚本后,会通过编写python代码启动app并将js脚本注入。这也意味着,js实际上是在app加载之后才注入的,因此,有些想要拦截的so库函数,已经执行完了,就无法拦截到;如果想要在app启动的同时注入js脚本,则会因为so库未加载,导致报错。这种情况下,需要通过smali插桩拦截较早执行的函数,Frida Hook更适用于在app启动以后,对一些控件进行操作时,拦截用户的输入信息以及返回值等等。
常见反编译工具功能
dex2jar:dex->jar
Apktool:apk <-> smali
baksmali/smali:dex<->smali
unzip(常规解压):apk->dex
J2S2J:java<->smali
JEB/jadx:apk->java(直接拖进去看即可)
注意事项:反编译工具有一定的局限性,并不能100%还原真实的Java代码,因此只能作为参考,不能全信,要分析真正的执行流程,推荐用smali插入log来进行分析。
阶段小结
参考资料
参考书籍:
- 《Android软件安全权威指南》—— 丰生强
参考连接: