Android 逆向实战

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Rozol/article/details/88755130

Android逆向实战


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


反编译工具

相关工具

另外还需要一些基础知识: 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
shell@S658t:/ $ su
root@S658t:/ # cat /system/build.prop |grep ro
# begin build properties
ro.build.id=KOT49H
ro.build.display.id=VIBEUI_V2.0_1512_7.17.1_ST_S658t
ro.build.version.incremental=VIBEUI_V2.0_1512_7.17.1_ST_S658t
ro.custom.build.version=VIBEUI_V2.0_1512_7.17.1_ST_S658t
ro.build.version.sdk=19
ro.build.version.codename=REL
ro.build.version.release=4.4.2
ro.build.date=Mon Feb  9 21:38:28 CST 2015

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

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

root@S658t:/ # getprop ro.product.model
Lenovo S658t

默认配置信息

root@S658t:/ # cat default.prop
#
# ADDITIONAL_DEFAULT_PROPERTIES
#
ro.secure=1
ro.allow.mock.location=0
persist.mtk.aee.aed=on
ro.debuggable=0
ro.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
shell@S658t:/ $ su
root@S658t:/ # cd sdcard/
1|root@S658t:/sdcard # ./mprop2 ro.debuggable 1
properties map area: b6f7e000-b6f9e000
00000000  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.查看是够修改成功

root@S658t:/sdcard # getprop ro.debuggable
1

为1说明修改成功

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

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

接着把adb进程重启下:

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

DDMS显示了可调式的进程
在这里插入图片描述

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

C:\Users\LZLuz>adb jdwp
2215
22916
26277
27256

通过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;-><init>()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;-><init>()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 main
03-09 19:35:41.829  5622  5622 I MyLog   : hello
03-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 shell
su
cd sdcard/
./mprop2 ro.debuggable 1

getprop 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: localhost
Port: 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, #aHelloFromC@PAGE ; "Hello from C!"
.text:00000000000005AC                 ADD             X8, X8, #aHelloFromC@PAGEOFF ; "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 shell
su
cd sdcard/
./mprop2 ro.debuggable 1

getprop 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 shell
su
cd 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.1
Port: 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
shell@S658t:/ $ ps | grep com.yao
u0_a220   13387 140   560248 27988 ffffffff 00000000 S com.yaotong.crackme
shell@S658t:/ $ cat /proc/13387/status
Name:   yaotong.crackme
State:  S (sleeping)
Tgid:   13387
Pid:    13387
PPid:   140
TracerPid:      0
Uid:    10220   10220   10220   10220
Gid:    10220   10220   10220   10220

这是被调试后, TracerPid: 11868

shell@S658t:/ $ cat /proc/13387/status
Name:   yaotong.crackme
State:  t (tracing stop)
Tgid:   13387
Pid:    13387
PPid:   140
TracerPid:      11868
Uid:    10220   10220   10220   10220
Gid:    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

猜你喜欢

转载自blog.csdn.net/Rozol/article/details/88755130