Unidbg补环境实战第一篇:补环境入门

本文介绍了Unidbg补环境实战的第一篇,重点讲解了补环境的重要性,通过一个Android逆向案例展示了如何模拟执行so,包括参数获取、代码初始化、目标函数调用和补环境说明。文章通过解决Unidbg运行时的环境问题,阐述了如何补全Java层函数以使Native函数正常执行,并给出了实战中的补环境步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • Unidbg补环境实战第一篇:补环境入门

    • 为什么要补环境

    • Unidbg补环境的案例情景复现

    • 模拟执行so

      • 参数获取

      • unidbg 代码初始化

      • 目标函数的调用

      • 补环境说明

      • 实战补环境

    • 本章小节

Unidbg补环境实战第一篇:补环境入门

Unidbg是一个基于unicorn的逆向工具,可以黑盒调用安卓和iOS中的so文件。这使得逆向人员可以在无需了解so内部算法原理的情况下,主动调用so中的函数,让其中的算法“为我所用”,只需要传入所需的参数、补全运行所需的环境,即可运行出所需要的结果。及由此衍生的辅助分析、算法还原、SO调试与逆向等等功能。

对于Android逆向来说,Unidbg的特点有以下几种:

  1. 模拟JNI调用的API,因此可以调用JNI_OnLoad函数。

  2. 支持JavaVMJNIEnv

  3. 支持模拟系统调用指令。

  4. 支持ARM32ARM64

  5. 支持基于Dobby的inline hook。

  6. 支持基于xHook的GOT hook。

  7. unicorn后端支持简单的控制台调试器,gdb stub,指令追踪和内存读写追踪。

  8. 支持dynarmic快速的后端。

为什么要补环境

使用unidbg最主要的问题就是补环境,补环境对于so的模拟执行太重要了,并且在这块很容易跌跟头。我们知道unidbg的作用是模拟执行so中的函数,也就是使用C/C++编写的函数,它处于Native层。而Native的函数是Java层的函数通过JNI调用起来的,
那么Native也可以通过JNI这座桥梁去调用Java层的函数。

在Native层调用Java层的函数的时候,unidbg中并没有这些函数的实现,那么这些so就无法正常的通过unidbg加载起来。所以我们需要手动的把Java层的函数补充起来,让Native层的函数去调用。

PS:以下所有分析均在r0env2022版安卓逆向环境下完成。r0env2022版集成了Unidbg,打开终端输入unidbg回车,就是一个安装好所有依赖包的可以直接跑项目的完整的Unidbg运行环境。

PS2:案例涉及的代码及附件会放在我的Github中,地址:https://github.com/r0ysue/AndroidSecurityStudy

Unidbg补环境的案例情景复现

为了给大家讲清本章的内容,笔者开发了一个样本APK构造了一些环境问题。首先我们先熟悉一下这个APP。APP的打开后的界面如图1所示:


图1 样本放置的目录

屏幕中央显示了一串字符,感觉像是16进制。然后,我们使用Jadx-gui反编译APK,其中MainActivity.java的代码如下所示:

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;

    // 检测文件
    public native void detectFile();

    // 检测是否有Hook
    public native void detectHookTool();

    // native函数,获取哈希结果
    public native String getHash(String str);

    // 加载 so
    static {
        System.loadLibrary("dogpro");
    }

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding inflate = ActivityMainBinding.inflate(getLayoutInflater());
        this.binding = inflate;
        setContentView(inflate.mo170getRoot());
        TextView tv = this.binding.sampleText;
        detectFile();
        detectHookTool();
        // 获取Hash结果
        String r1 = getHash(getApplicationContext().getPackageCodePath());
        tv.setText(r1);
    }
}

在onCreate中有两个检测,我们先不要理会,他暂时不会对我们本章的内容产生任何的影响。但是可以给大家看看他们做了什么事情:

  • detectFile

    int __fastcall Java_com_example_dogpro_MainActivity_detectFile(_JNIEnv *a1)
    {
    int v2; // [sp+8h] [bp-30h]
    int v3; // [sp+Ch] [bp-2Ch]
    int v4; // [sp+14h] [bp-24h]
    int MethodID; // [sp+18h] [bp-20h]
    int v6; // [sp+1Ch] [bp-1Ch]
    int v7; // [sp+20h] [bp-18h]
    int Class; // [sp+24h] [bp-14h]

    // 反射去Java层找File类
    Class = _JNIEnv::FindClass(a1, "java/io/File");
    v7 = _JNIEnv::AllocObject(a1, Class);
    // 检测的路径
    v6 = _JNIEnv::NewStringUTF(a1, "/sys/class/power_supply/battery/voltage_now");
    MethodID = _JNIEnv::GetMethodID(a1, Class, "<init>", "(Ljava/lang/String;)V");
    _JNIEnv::CallVoidMethod(a1, v7, MethodID, v6);
    v4 = _JNIEnv::GetMethodID(a1, Class, "exists", "()Z");
    if ( (unsigned __int8)_JNIEnv::CallBooleanMethod(a1, v7, v4) )
    _android_log_print(6, "lilac", byte_35D7);
    else
    _android_log_print(6, "lilac", byte_35F0);
    v3 = _JNIEnv::AllocObject(a1, Class);
    v2 = _JNIEnv::NewStringUTF(a1, "/data/local/tmp/nox");
    _JNIEnv::CallVoidMethod(a1, v3, MethodID, v2);
    if ( (unsigned __int8)_JNIEnv::CallBooleanMethod(a1, v3, v4) )
    return _android_log_print(6, "lilac", byte_361D);
    else
    return _android_log_print(6, "lilac", byte_3636);
    

    }

首先检测了电池的相关信息,我们尝试去/sys/class/power_supply/battery/voltage_now下查看,如图2所示:


图21-2 电池属性

其它文件表示的含义如下所示:

//电池充电状态
cat /sys/class/power_supply/battery/status

//电池电量
cat /sys/class/power_supply/battery/capacity

//电池运行状况
cat /sys/class/power_supply/battery/health

//显示电池温度
cat /sys/class/power_supply/battery/temp

//电池电压 mV
cat /sys/class/power_supply/battery/voltage_now

第二处检测的是/data/local/tmp/nox,nox是夜神模拟器,模拟器创建的时候会在此路径有文件的创建。幸运的是,当检测到的时候,并不会有任何的操作,所以我们不予理会,这里只带领大家看看它是如何做检测的。

  • detectHookTool

    int __fastcall Java_com_example_dogpro_MainActivity_detectHookTool(_JNIEnv *a1)
    {
    int v1; // r0
    int v2; // r0
    const char *StringUTFChars; // [sp+28h] [bp-A0h]
    int ObjectClass; // [sp+34h] [bp-94h]
    int ObjectArrayElement; // [sp+38h] [bp-90h]
    int i; // [sp+3Ch] [bp-8Ch]
    int ArrayLength; // [sp+40h] [bp-88h]
    int v9; // [sp+44h] [bp-84h]
    int v10; // [sp+48h] [bp-80h]
    int v11; // [sp+4Ch] [bp-7Ch]
    int MethodID; // [sp+50h] [bp-78h]
    int Class; // [sp+54h] [bp-74h]
    size_t n; // [sp+6Ch] [bp-5Ch]
    size_t v16; // [sp+7Ch] [bp-4Ch]
    char v17[24]; // [sp+80h] [bp-48h] BYREF
    char v18[36]; // [sp+98h] [bp-30h] BYREF

    // 反射找到 Throwable => 异常处理
    Class = _JNIEnv::FindClass(a1, "java/lang/Throwable");
    MethodID = _JNIEnv::GetMethodID(a1, Class, "<init>", "()V");
    v11 = _JNIEnv::NewObject(a1, Class, MethodID);
    // 获取异常堆栈
    v10 = _JNIEnv::GetMethodID(a1, Class, "getStackTrace", "()[Ljava/lang/StackTraceElement;");
    // 调用方法
    v9 = _JNIEnv::CallObjectMethod(a1, v11, v10);
    ArrayLength = _JNIEnv::GetArrayLength(a1, v9);
    // 复制值,检测 Xposed 框架
    strcpy(v18, "de.robv.android.xposed.XposedBridge");
    // 复制值,检测 substrate 框架
    strcpy(v17, "com.saurik.substrate");
    for ( i = 0; i < ArrayLength; ++i )
    {
    ObjectArrayElement = _JNIEnv::GetObjectArrayElement(a1, v9, i);
    ObjectClass = _JNIEnv::GetObjectClass(a1, ObjectArrayElement);
    // 每个堆栈中的信息反射获取类名
    v1 = _JNIEnv::GetMethodID(a1, ObjectClass, "getClassName", "()Ljava/lang/String;");
    v2 = _JNIEnv::CallObjectMethod(a1, ObjectArrayElement, v1);
    StringUTFChars = (const char *)_JNIEnv::GetStringUTFChars(a1, v2, 0);
    n = _strlen_chk(v18, 0x24u);
    // 比对
    if ( !strncmp(StringUTFChars, v18, n) )
    {
        _android_log_print(6, "lilac", "%s", StringUTFChars);
        _android_log_print(6, "lilac", byte_389E);
    }
    v16 = _strlen_chk(v17, 0x15u);
    if ( !strncmp(StringUTFChars, v17, v16) )
    {
        _android_log_print(6, "lilac", "%s", StringUTFChars);
        _android_l
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值