avatar

Catalog
Android逆向思路及注意事项

前言

入职公司一周,完成了一个小项目,与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层

  1. 在IDA Pro7.5\dbgsrv的目录下,可以用于调试其它操作系统的调试服务器,若想调试Android手机,需要先通过指令adb push android_server /data/local/tmp 将调试用的服务器(注:安卓模拟器的属于x86架构)push到真实机里。

  2. 然后转发端口adb forward tcp:23946 tcp:23946(23946是IDA的默认端口)

  3. 修改android_server权限(一般为755),并执行./data/local/tmp/android-server &,来启动服务器。这时命令行中可以看到正在对端口进行监听。

  4. 进入IDA->Debugger->Attach->Remote ARM Linux/Android debugger。这里主机名和端口号,看命令行中,监听的主机名和端口号是啥,就填啥

  5. 完了,就可以Attach吧,大部分app都有反调试,一下断点就死。Run的方法,我没成功过。

Java层

  1. Java层动态调试好像并不需要监听,所以只要将需要调试的apk文件,从手机里pull一份出来就行。一般系统软件在system/app文件夹下,预装软件在data/app文件夹下。拿到apk文件后,直接拖入IDA,选择dex文件进行加载。

  2. 设置debugger选项,Debugger->Debugger options->Set specific options,在Package Name和Activity中分别填入包名和第一个启动的Activitiy。

    这里介绍两种找包名和Activity的方式:

    • 反编译apk文件后得到AndroidManifest.xml,打开该文件后查找包名和Activity,在找Activity时找intent-filter元素中action元素值为android.intent.action.MAIN的Activiy,它就是当前应用的主activity。

    • 先启动app,然后在命令行中执行Frida-ps -U列出所有进程,这里看到的实际上就是各个应用的包名;关掉app,执行dumpsys | grep 包名来抓取app启动时运行的各种服务(Activity、Package、Window等)的信息,再启动app,就可以找到第一个启动的Activity的页面了。

  3. 设置Debugger->Process options,主要就是改这个端口号,网上教程一般会给它设置为8700,我就用的默认的23946,但是运行后会给你校正到23915,这个问题不大,在命令行中执行adb forward tcp:23915 tcp:23915给它转发一下就能用了。

  4. 最后Debugger->Start process或者点击上方的绿色小三角启动应用,如果手机屏幕出现Waiting For Debugger的界面就说明设置成功了,可以进行动态调试了。此时屏幕上应为smali汇编,如果界面停留在一堆数据的页面也不要慌,View->Open subview->Functions进入函数界面,随便点进去就是smali汇编代码。接下来就可以搜自己想要分析的smali函数进行下断和调试了。

Smali代码注入

smali注入是我这次项目实现的关键。这里介绍几种适合注入的代码。

log插桩

Python代码(命令行):

python
1
2
3
4
5
6
>>>import frida
>>>device = frida,get_usb_device()
>>>device #这里是检查一下是否获取到device,测试机较旧不稳定
>>>pid = device.spawn('com.miui.home')
>>>pid #打印pid的值,在另一个窗口中用logcat | grep pid对该进程产生的日志进行捕捉
>>>device.resume(pid)

Smai代码:

smali
1
2
3
const-string v0, "HOOK POINT"
const-string v1, "Hello Smali"
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I

作用:

  1. 打印程序执行时一些字段的值(变量,参数,返回值等)
  2. 帮助分析程序完整的执行流程

Toast弹窗

smali
1
2
3
4
5
const-string v0, "Hello Toast" 
const/4 v1, 0x0
invoke-static {p1, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/String;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {p1}, Landroid/widget/Toast;->show()V
  • p0 :this(Context类型)
  • v0:弹出的字段(可更改类型,但是要和makeText中的一致)
  • v1:弹窗时长

其它Smali代码

  1. 将Java代码经过j2s2j转成smali
  2. 手动调用未执行代码

注意事项

  • 寄存器:寄存器一定不能有误,否则程序必定Crush。如果一个寄存器保存的值,接下来会被使用,那么最好别用这个寄存器。转而借用下面的代码即将重新赋值的寄存器。如果赋值不是非常麻烦的话,也可以先借用这个寄存器打印log或者用toast弹窗,完了再给这个寄存器重新赋值。(补充move指令对寄存器长度和编号的限制)
  • 类型:类型一定要匹配,例如log的参数一定要是String类型,Toast的参数类型要和寄存器中的参数匹配。
  • 日志类型:一般被逆的应用基本上都是release版的,这意味着只能打印info级别的日志,像在smali中添加调用栈打印的函数就很难被捕捉到,或者可以找到对Log类型的作判断的地方进行修改,让release版本也能打印出debug/warn/error级别的日志。

Frida Hook

这里简要列举一下,用js调用Frida接口的几个常见模板:

Hook so库中的函数

javascript
1
2
3
4
5
6
7
8
9
//Hook Native函数
interceptor.attach(Module.getExportByName('库名.so', '函数名'),{
onEnter: function(args){
//
},
onLeave: function(retval){

}
});

Hook Java层函数

javascript
1
2
3
4
5
6
7
//Hook Java层函数(例:拦截Activity类的onResume函数)
Java.perform(function(){
var Activity = Java.use('android.app.Activity');
Activity.onResume.implementation = function() {
console.log('Hello Frida!');
};
});

打印调用栈

javascript
1
2
3
//对Hook到的函数,打印调用栈
var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
console.log("\nBacktrace:\n" + bt);

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来进行分析。

承上启下

似乎很久没有更新博客了,确实,但也只有20天而已,显示的时间不过是刻意呈现出来的假象,过去的事也不必刻意去弥补了。

月初,入职了新公司,分配到了Android逆向的岗位,对于进入Android逆向领域,在我的计划之中,但没想到来的这么快,这也让我不得不调整计划,对Web领域的学习只能延后了。现在入职也有一周半了,各方面的体验还都不错。

当然,工作后,通勤时间变长,日程安排也紧凑了,每晚只能挤出一小时左右,用于弥补目前工作上的欠缺。博客的更新进度,又要停滞了。但现在有份比较满意的岗位,博客的更新,就不那么迫切了。目前,对于Android逆向的学习,进入了”愚昧山峰”的阶段,因此目前在这个领域的能力还是远远不够的。后续仍需努力去沉淀已经掌握的知识。

参考资料

参考书籍:

  • 《Android软件安全权威指南》—— 丰生强

参考连接:

Author: cataLoc
Link: http://cataloc.gitee.io/blog/2021/03/21/Android%E9%80%86%E5%90%91%E6%80%9D%E8%B7%AF%E5%8F%8A%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶