Android逆向实战


本文由 Luzhuo 编写,转发请保留该信息.
原文: https://blog.csdn.net/Rozol/article/details/88755130


反编译工具

相关工具

  • apktool: Java编写, 且开源. 用于反编译apk, 能得到smali(Android虚拟机执行指令, 与dex可相互转化)源码和资源文件
    • Github: https://github.com/iBotPeaches/Apktool
    • 最新版下载: https://ibotpeaches.github.io/Apktool
  • Jadx: 直接可视化apk的Java代码
    • Github: https://github.com/skylot/jadx
  • Xposed框架: hook应用, 能得到应用运行时数据, 并能修改运行时数据
    • 工具 Xposed框架
    • Xposed需要root权限, VirtualXposed不需要root权限
  • IDA: 能动态调试java 和 so

另外还需要一些基础知识: smali语法、arm指令、NDK开发

apktool

反编译:

使用该工具最好是去Github下去源码, 放到Eclipse里执行, 一旦反编译报错可以立即修改源码解决问题.

1.下载源码: https://github.com/iBotPeaches/Apktool

2.Eclipse安装Gradle插件

  • 安装Gradle: https://gradle.org
  • 安装Gradle插件: Help -> Eclipse Marketplace -> 输入buildship -> 搜索 -> Install -> 重启Eclipse
  • 配置Gradle: Eclipse -> Project -> Properties -> Gradle -> Gradle user home配置Gradle路径 -> OK

3.导入项目

  • File -> Import -> Existing Gradle Project(Gradle) -> 选择apktool -> Finish
  • 如果你还没有配置Gradle的情况下导入了apktool, 那么你只需右键项目 -> Gradle -> Refresh Gradle Project 即可.

4.运行apktool

在brut.apktool包下的Main类下的main入口函数手动构建一个命令, 然后运行即可.

args = new String[]{"d", "C:\\apk\\xxx.apk", "-o", "C:\\apk\\xxx"};

回编译:

1.进行回编译:

args = new String[]{"b", "C:\\apk\\xxx1", "-o", "C:\\apk\\xxx_build.apk"};

2.签名:

jarsigner -verbose -keystore "C:\apk\case.key.jks" -signedjar "C:\apk\xxx_build_ok.apk" "C:\apk\xxx_build.apk" key0

-signedjar参数:

  • 1.签名后apk存放路径
  • 2.未签名apk路径
  • 3.证书别名

3.安装

逆向操作

打开调试开关

想要调试应用, 打开调试开关这是破解步骤的第一步.

不反编译的情况下:

Android配置信息放在 /system/build.prop 文件里

C:\Users\LZLuz>adb shell[email protected]:/ $ su[email protected]:/ # cat /system/build.prop |grep ro# begin build propertiesro.build.id=KOT49Hro.build.display.id=VIBEUI_V2.0_1512_7.17.1_ST_S658tro.build.version.incremental=VIBEUI_V2.0_1512_7.17.1_ST_S658tro.custom.build.version=VIBEUI_V2.0_1512_7.17.1_ST_S658tro.build.version.sdk=19ro.build.version.codename=RELro.build.version.release=4.4.2ro.build.date=Mon Feb  9 21:38:28 CST 2015

ro开头的属性表示只读, 不可修改

可以通过命令单独获取某值

[email protected]:/ # getprop ro.product.modelLenovo S658t

默认配置信息

[email protected]:/ # cat default.prop## ADDITIONAL_DEFAULT_PROPERTIES#ro.secure=1ro.allow.mock.location=0persist.mtk.aee.aed=onro.debuggable=0ro.adb.secure=1

这里的ro.debuggable=0为0表示判断应用中的AndroidManifest.xml文件中manifest标签是否有android:debuggable="true"属性, 有则可以调试; 若为1, 表示所有程序均可调试.

接下来我们想办法修改这个值, 改为1:

我们使用ptrace注入到init进程, 修改内存中的属性值, 只要该进程不重启就会一直有效, 即使重启了, 再操作一遍即可.

1.下载mprop工具
2.拷贝到设备中

adb push C:\Code\mprop /sdcard/

3.执行命令

C:\Users\LZLuz>adb shell[email protected]:/ $ su[email protected]:/ # cd sdcard/1|[email protected]:/sdcard # ./mprop2 ro.debuggable 1properties map area: b6f7e000-b6f9e00000000000  84 e1 00 00 00 89 00 00 50 52 4f 50 ab d0 6e fc  .�......PROP��n�00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

4.查看是够修改成功

[email protected]:/sdcard # getprop ro.debuggable1

为1说明修改成功

5.使用DDMS查看可调试列表

AndroidStudio3.2找不到DDMS, 看来只能手动找了:
SDK -> tools -> monitor.bat

接着把adb进程重启下:

C:\Users\LZLuz>adb kill-serverC:\Users\LZLuz>adb start-server

DDMS显示了可调式的进程

并且可通过jdwp命令查看可调式进程id:

C:\Users\LZLuz>adb jdwp2215229162627727256

通过dumpsys package命令查看包信息:

adb shell dumpsys package me.luzhuo.hacker

反编译的情况下:

1.反编译
2.manifest标签添加 android:debuggable="true" 属性
3.回编译
4.签名
5.安装

以上步骤的具体执行, 见apktool介绍.

静态逆向

编写一个apk, 然后通过签名导出.

以下是用jadx-gui看到apk源码

package me.luzhuo.smalidemo;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.widget.TextView;public class MainActivity extends AppCompatActivity {    /* Access modifiers changed, original: protected */    public void onCreate(Bundle bundle) {        super.onCreate(bundle);        setContentView((int) R.layout.activity_main);        ((TextView) findViewById(R.id.tv_hello)).setText(getString("hello"));    }    private String getString(String str) {        StringBuilder stringBuilder = new StringBuilder();        stringBuilder.append(str);        stringBuilder.append("world!");        return stringBuilder.toString();    }}

我们的目的是针对getString(), 打印传入的参数, 打印运算后的结果, 返回我们期望的结果.

1.反编译apk生成smali代码

使用apktool进行反编译, 生成smali源码

.method private getString(Ljava/lang/String;)Ljava/lang/String;    .locals 1    .line 19    new-instance v0, Ljava/lang/StringBuilder;    invoke-direct {v0}, Ljava/lang/StringBuilder;->()V    invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;    const-string p1, "world!"    invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;    invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;    move-result-object p1    return-object p1.end method

2.修改smali代码

修改后的代码:
扩展了2个非参数寄存器, 用来存放"MyLog"和"AndroidSmali!",
然后打印了 参数p1 和 结果p1 的值,
最后定义了"AndroidSmali!"字符串给返回.

.method private getString(Ljava/lang/String;)Ljava/lang/String;    .locals 3    const-string v3, "MyLog"    # print param    invoke-static {v3, p1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I    .line 19    new-instance v0, Ljava/lang/StringBuilder;    invoke-direct {v0}, Ljava/lang/StringBuilder;->()V    invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;    const-string p1, "world!"    invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;    invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;    move-result-object p1    # print result    invoke-static {v3, p1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I    # change result    const-string v2, "AndroidSmali!"    return-object v2.end method

3.回编译, 签名, 安装

如果安装时被告知是非法包, 并且用jadx-gui查看APK signature时, 包空指针, 这说明你的包本身就不能被安装, 你得通过签名导出的方式获得, 或者通过app市场下载的方式获得.

4.查看日志输出

使用命令adb logcat -s MyLog即可.

C:\Users\LZLuz>adb logcat -s MyLog--------- beginning of crash--------- beginning of system--------- beginning of main03-09 19:35:41.829  5622  5622 I MyLog   : hello03-09 19:35:41.829  5622  5622 I MyLog   : helloworld!

动态逆向

smalidea地址

通过AndroidStudio3.2动态调试smali代码, 使用baksmali插件.

1.安装插件

目前最新的插件是smalidea-0.05.zip, 20170331更新的.

去Browse Repositories搜了下, 发现没有这个插件, 直接去官方下载然后安装即可.

2.打开调试

adb shellsucd sdcard/./mprop2 ro.debuggable 1getprop ro.debuggable

3.安装应用

安装apk, 不需要做任何修改

4.获得smali

用apk反编译apk, 得到smali代码, AndroidStudio中打开现有AndroidStudio项目(File -> Open -> 选择反编译后文件夹 -> New window)

右击smali目录, 把Mark Directory As 改成 Sources Root

5.配置调试

配置远程调试: Run -> Edit Configurations -> + -> Remote (如果8700被占, 可换成任意端口)

Host: localhostPort: 8700

获取目标进程

C:\Users\LZLuz>adb shell ps | findstr "com.example.simpleencryption"u0_a121   8938  140   558708 28800 ffffffff 00000000 S com.example.simpleencryption

可见目标进程为8938

启动应用并将JDWP服务转发到localhost (注:8700被占了, 我换成了8701)

adb forward tcp:8701 jdwp:8938

6.调试应用

打调试断点 -> 调试

调试结束清除端口绑定

adb forward --remove-all

动态调试比静态调试的好处是, 静态需要添加Log然后打包签名安装才能看到运算值, 而动态调试不需要修改apk直接运行调试就能看到值, 比静态调试简便了许多.

IDA逆向.so

IDA的基本使用的文章很早之前写过一篇

将ARM指令转成伪C

打开IDA -> new -> 选择.so文件打开

.text:000000000000059C ; jstring Java_me_luzhuo_idaso_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz).text:000000000000059C                 EXPORT Java_me_luzhuo_idaso_MainActivity_stringFromJNI.text:000000000000059C Java_me_luzhuo_idaso_MainActivity_stringFromJNI.text:000000000000059C                                         ; DATA XREF: LOAD:0000000000000330↑o.text:000000000000059C.text:000000000000059C var_18          = -0x18.text:000000000000059C var_10          = -0x10.text:000000000000059C env             = -8.text:000000000000059C var_s0          =  0.text:000000000000059C.text:000000000000059C ; __unwind {.text:000000000000059C                 SUB             SP, SP, #0x30.text:00000000000005A0                 STP             X29, X30, [SP,#0x20+var_s0].text:00000000000005A4                 ADD             X29, SP, #0x20.text:00000000000005A8                 ADRP            X8, #[email protected] ; "Hello from C!".text:00000000000005AC                 ADD             X8, X8, #[email protected] ; "Hello from C!".text:00000000000005B0                 STUR            X0, [X29,#env].text:00000000000005B4                 STR             X1, [SP,#0x20+var_10].text:00000000000005B8                 STR             X8, [SP,#0x20+var_18].text:00000000000005BC                 LDUR            X8, [X29,#env].text:00000000000005C0                 LDR             X8, [X8].text:00000000000005C4                 LDR             X8, [X8,#0x538].text:00000000000005C8                 LDUR            X0, [X29,#env].text:00000000000005CC                 LDR             X1, [SP,#0x20+var_18].text:00000000000005D0                 BLR             X8.text:00000000000005D4                 LDP             X29, X30, [SP,#0x20+var_s0].text:00000000000005D8                 ADD             SP, SP, #0x30.text:00000000000005DC                 RET.text:00000000000005DC ; } // starts at 59C.text:00000000000005DC ; End of function Java_me_luzhuo_idaso_MainActivity_stringFromJNI

按F5就成将其转成伪C代码, 可见其对jni.h的支持还是相当不错的

__int64 __fastcall Java_me_luzhuo_idaso_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz){  return ((__int64 (__fastcall *)(JNIEnv *, const char *))(*env)->NewStringUTF)(env, "Hello from C!");}

而原来的C代码是这样子的

jstring Java_me_luzhuo_idaso_MainActivity_stringFromJNI(JNIEnv* env, jobject thiz){    char* cstr = "Hello from C!";    return (*env)->NewStringUTF(env, cstr);}

调试.so步骤

目的: 通过调试获取方法的输入和输出内容.

1.打开调试

adb shellsucd sdcard/./mprop2 ro.debuggable 1getprop ro.debuggable

2.获取端口号

a.安装apk
b.运行apk
c.查看端口 (port:8700)(SDK -> tools -> monitor.bat)

3.IDA配置

a.把 IDA安装目录/dbgsrv/ 下的android_server文件拷贝手机/sdcard目录下

adb push "C:\DevelopSoftware\IDA 7.0\dbgsrv\android_server" /sdcard/

b.运行./android_server

adb shellsucd sdcard./android_server

c. 连接端口

adb forward tcp:23946 tcp:23946

(这里的端口是固定的)

调试结束清除端口绑定

adb forward --remove-all

d.打开IDA -> Go -> Debugger -> Attach -> Remote ARMLinux/Android debugger -> 填写信息 -> OK

Hostname: 127.0.0.1Port: 23946

e.然后会弹出一个Choose process to attach to对话框, android_server会帮你把所有进程信息给列出来, 按Ctrl+F进行搜索, 然后OK

4.开始调试
a.查找.so文件的基地址:

按Ctrl+S弹出choose segment to jump, 然后按Ctrl+F搜索so文件名, 可见基地址为:5C035000.

b.获取函数地址:

再开一个IDA, 为了方便区分, 我们就叫他IDA2吧(之前那个就叫IDA), 选择new, 并打开.so文件.

找到要分析的函数Java_me_luzhuo_idaso_MainActivity_stringFromJNI, 可以看到函数相对地址为:778

c.得到函数的绝对地址为:5C035778, 在IDA使用G键快速跳到这个绝对地址, 然后点击左边的小绿点下断点(F2), 再然后点击左上角的绿色三角形按钮运行(F9)

d.触发native代码运行, 按F8单步调试, 可以看到寄存器里的内容.

可见输入值为:hello from java!

长度为0x10, 换成十进制就是16.

可以点寄存器地址后面的跳转按钮, 跳到该数据所在的内存地址.
输出的结果为:ifmmp!gspn!kbwb"

解决反调试

当你用IDA调试时, 打了断点, 然后点击绿色小三角准备开始调试, 结果应用却退出了, 这说明该应用被加了反调试.

这是未被调试前, TracerPid: 0

C:\Users\LZLuz>adb shell[email protected]:/ $ ps | grep com.yaou0_a220   13387 140   560248 27988 ffffffff 00000000 S com.yaotong.crackme[email protected]:/ $ cat /proc/13387/statusName:   yaotong.crackmeState:  S (sleeping)Tgid:   13387Pid:    13387PPid:   140TracerPid:      0Uid:    10220   10220   10220   10220Gid:    10220   10220   10220   10220

这是被调试后, TracerPid: 11868

[email protected]:/ $ cat /proc/13387/statusName:   yaotong.crackmeState:  t (tracing stop)Tgid:   13387Pid:    13387PPid:   140TracerPid:      11868Uid:    10220   10220   10220   10220Gid:    10220   10220   10220   10220

可见反调试就是不断的监测自己应用进程的状态值(/proc/[pid]/status), 如果TracerPid非0则说明被调试了, 就结束应用.

我们要解决这个问题就要从以下两个方法找解决方案:

  • .init_array: so最先加载段信息, 一般用来so解密
  • JNI_OnLoad: System.loadLibrary调用时执行, 没.init_array

1.打开调试

2.debug启动应用

用am的debug方式启动应用 (D要大写), 这样就不会执行static方法

adb shell am start -D -n com.yaotong.crackme/com.yaotong.crackme.MainActivity

3.设置Debugger Option

启动IDA(Go), 然后进行连接(前面连过很多次了, 这里就不细说了)

需要使它暂停在load前, 通过 Debugger -> Debugger Option 设置

4.运行IDA

a.点绿色小三角运行(F9)

b.运行attach命令

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

如果8700出现无法附加到目标 VM。, 那么就选8700前面的那个端口号

5.下断点

a.在JNI_OnLoad方法下断点

按Ctrl+S获得libcrackme.so的地址为:5F707000.
JNI_OnLoad的相对地址为:00001B9C
JNI_OnLoad的绝对地址为:5F708B9C

按G跳到该位置, 并打上断点.

b.在libc的fopen方法下断点

按Ctrl+S找到libc.so, 打开它,

按Alt+T搜索fopen关键字, 然后下断点 (会自动转为代码形式)

6.调试

进行单步调试, 我们发现BLX(执行) R7寄存器创建了一个线程,

这个线程执行的函数体是unk_5F62A6A4.

这里的unk_5F62A30C被重复执行, 这很可能就是反调试代码.

然后按F9运行到fopen处, LR是5F62A420, 我们点进去看看

不断的点上一层, 最后你会发现就是unk_5F62A30C调的他, 现在可以大概率认定它就是反调试代码.

然后继续单步执行, 看看fopen打开的是哪个文件

现在不用怀疑了, 他就是反调试代码.

7.修改so

我们发现BLX R7创建的线程调用unk_5F62A6A4函数就是用来反调试的, 那么只需不让它执行就行了.

5F708C58 BLX R7 的相对地址为: 5F708C58 - 5F707000 = 1C58,
打开IDA2, 按G跳到1C58.

切换为Hex View窗口, 将代码NOP, 也就是把1C58位置的值 37 FF 2F E1 改为 00 00 A0 E1, 然后右键applay change

改完之后 edit -> patch program -> apply patches to input file…, 最后覆盖so文件重新打包签名运行即可.

脱壳逆向

加固分为 apk加固 和 so加固.

apk加固脱壳

apk被加固后, 代码在jadx-gui里都看不到了, 只有一个继承Application的壳, 虽然代码看不到了, 但是AndroidManifest.xml清单里的申明都还在.

解决思路: 不管源码被如何加固, 最终都需要被加载到内存中运行, 只需要找到dex在内存中的位置, dump出来即可.

1.打开调试

2.获取libdvm.so文件

libdvm.so文件在/system/lib目录下

adb pull /system/lib/libdvm.so C:\apk\libdvm.so

3.debug启动应用

adb shell am start -D -n com.ali.tg.testapp/com.ali.tg.testapp.MainActivity

4.设置Debugger Option

启动IDA(Go), 然后连接, 再去设置Debugger Option

5.运行IDA

a.点绿色小三角运行(F9)

b.运行attach命令

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

6.下断点

a.打开IDA2获取libdvm.so的dvmDexFileOpenPartial()相对地址.

dvmDexFileOpenPartial(void const*, int, DvmDex **)

第1个参数为dex内存起始地址, 第2个参数为dex大小.

dvmDexFileOpenPartial的相对地址为: 00044F1C
地址为: 417CF000 + 00044F1C = 41813F1C

b.IDA按G, 输入地址打上断点

7.调试

按F9运行到断点出, 然后单步执行PUSH指令.

dump脚本代码块:

static main(void){  auto fp, dex_addr, end_addr;  fp = fopen("C:\\apk\\dump.dex", "wb");  end_addr = r0 + r1;  for ( dex_addr = r0; dex_addr < end_addr; dex_addr++ )    fputc(Byte(dex_addr), fp);}

按Shift+F2调出脚本, 把上面dump代码粘贴进去, run之后, 指定目录下生成dump.dex文件, 直接用jadx-gui打开就能看到源码了.

也可以使用baksmali工具生成smali代码

java -jar baksmali-2.2.6.jar disassemble -o C:\apk\dexout\ dump.dex

本文总结

主要断点处 作用 位于so库
JNI_OnLoad 反调试 xxx.so
fopen 反调试 libc.so
fgets 反调试 libc.so
dvmDexFileOpenPartial apk脱壳 libdvm.so

提供练习的资源文件在这: https://download.csdn.net/download/rozol/11051587

更多相关文章

  1. android中String与InputStream之间的相互转换方式
  2. Android(安卓)打测试包
  3. 工欲善其事必先利其器-Android(安卓)Studio技巧与插件(1)
  4. Android(安卓)如何检测一个服务是否还在运行?
  5. Android(安卓)源代码编后的目录分析
  6. Haisi3716C (海思)源代码 编译并烧写
  7. android 信息(mms)的故事(五)-- 发彩信
  8. 总结 使用PHP作为中间介来实现android链接远程数据库。
  9. Android(安卓)UI之代码动态设置ImageView的宽度和高度

随机推荐

  1. html预习,请老师审批,
  2. 实例演示flex容器与项目中常用的属性
  3. 三行三列的定位布局中演示QQ客服的固定定
  4. 对于模板字面量与标签函数、解构赋值于对
  5. js中常用的字符串函数、数组函数、分支与
  6. 表单,框架,选择器,css的三种插入样式(内联,外
  7. 字体样式调用、布局原则、盒模型等
  8. 0701学习实战作业
  9. 跨域-前后端分离
  10. three.js 入门详解(一)