吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 11479|回复: 19
收起左侧

[Android 原创] 菜鸡诈尸水贴之unidbg学习笔记

  [复制链接]
Shutd0wn 发表于 2021-2-7 17:04
本帖最后由 Shutd0wn 于 2021-2-7 17:22 编辑

unidbg学习(一)

unidbg是什么?

unidbgunicorn的一个实现,它可以让你在电脑上跑arm的可执行文件或共享库文件。

以上是我的理解。

unidbg的优点?

最大的优点就是让你在电脑上可以调用apk/ipa中的native方法。

作者完成了对android/ios环境的模拟,提供了对方法hookapi接口,然后你就可以通过简单的几行代码,完成对native层函数的调用。

先把demo跑起来

电脑安装IntelliJ IDEA

git clone https://github.com/zhkl0228/unidbg.git

使用idea打开,等待他加载完毕,请保持网络畅通。

加载完成之后,随便打开一个例子,我们尝试跑一下unidbg-android/src/test/java/com/kanxue/test2/MainActivity.java

image-20210202175713315.png

等一等,这是个爆破题,过一会就有答案了。

load offset=1844ms
Found: XuE, off=190673ms

Process finished with exit code 0

第一个例子,让你自己写的例子跑起来

使用android studio开始第一次尝试:

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativetest_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */, jobject context) {
    std::string hello = "Hello from C++";
    LOGE("Hello from C++ => %s", hello.c_str());
    for (int i = 0; i < hello.length(); i++) {
        hello[i] = hello[i] ^ hello.length();
    }
    LOGE("Hello from C++ => %s", hello.c_str());
    return env->NewStringUTF(hello.c_str());
}

编译成so文件,我们来尝试让这个so在电脑上跑起来:

首先,把so文件从apk中拖出来,我们新建一个文件夹来保存它unidbg-android/src/test/resources/example

image-20210202175319254.png

编写第一个java代码,让你的so中的Java_com_example_nativetest_MainActivity_stringFromJNI方法跑起来。

unidbg-android/src/test/java/com/example/nativetest/Test01.java
package com.example.nativetest;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.linux.android.AndroidARM64Emulator;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.io.IOException;

/**
* 第一个例子,让so跑起来
*/
public class Test01 extends AbstractJni {

    private final VM vm;
    private final DvmClass dvmClass;
    private AndroidEmulator emulator;
    private String processName = "com.example.nativetest";
    private String className = "com/example/nativetest/MainActivity";
    private Memory memory;

    public Test01() {
        // 1. 创建一个Android虚拟机
        emulator = createAndroidEmulator();
        // 2. 获取虚拟机的内存
        memory = emulator.getMemory();
        // 3. 配置Android sdk api版本
        memory.setLibraryResolver(createLibraryResolver(23));
        // 4. 创建Dalvik虚拟环境
        vm = createVM(null);
        // 5. 设置jni
        vm.setJni(this);
        // 6. 设置log
        vm.setVerbose(true);
        // 7. 加载目标so文件
        DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/resources/example/arm64-v8a/libnative-lib01.so"), false);
        // 8. 配置native方法类名
        dvmClass = vm.resolveClass(className);
    }

    private LibraryResolver createLibraryResolver(int ver) {
        return new AndroidResolver(ver);
    }

    public static void main(String[] args) throws IOException {
        // 测试
        Test01 test01 = new Test01();
        test01.start();
        test01.stop();
    }

    private AndroidEmulator createAndroidEmulator() {
        if (emulator == null) {
            return new AndroidARM64Emulator(processName);
        }
        return emulator;
    }

    private VM createVM(File apk) {
        return emulator.createDalvikVM(apk);
    }

    private void start() {
        // 9. 调用jni方法
        DvmObject result = dvmClass.callStaticJniMethodObject(emulator, "Java_com_example_nativetest_MainActivity_stringFromJNI()Ljava/lang/String");
        // 10. 打印结果
        System.out.println("result = " + result.getValue());
    }

    private void stop() throws IOException {
        emulator.close();
    }
}

尝试跑一下,报错了:

Exception in thread "main" java.lang.IllegalArgumentException: find method failed: Java_com_example_nativetest_MainActivity_stringFromJNI()Ljava/lang/String
        at com.github.unidbg.linux.android.dvm.DvmClass.findNativeFunction(DvmClass.java:255)
        at com.github.unidbg.linux.android.dvm.DvmObject.callJniMethod(DvmObject.java:96)
        at com.github.unidbg.linux.android.dvm.DvmClass.callStaticJniMethodObject(DvmClass.java:297)
        at com.example.nativetest.Test01.start(Test01.java:68)
        at com.example.nativetest.Test01.main(Test01.java:51)

改一下源码unidbg-android/src/main/java/com/github/unidbg/linux/android/dvm/DvmClass.java

            String symbolName = "";
            if (method.startsWith("Java_")) {
                symbolName = method.substring(0, index);
            } else {
                symbolName = "Java_" + getClassName().replace("_", "_1").replace('/', '_') + "_" + method.substring(0, index);
            }

好了

Find native function Java_com_example_nativetest_MainActivity_Java_com_example_nativetest_MainActivity_stringFromJNI()Ljava/lang/String => RX@0x4001d6b8[libnative-lib.so]0x1d6b8
[main]E/TAG: Hello from C++ => Hello from C++
[main]E/TAG: Hello from C++ => Fkbba.h|ac.M%%
JNIEnv->NewStringUTF("Fkbba.h|ac.M%%") was called from RX@0x4001d98c[libnative-lib.so]0x1d98c
result = Fkbba.h|ac.M%%

Process finished with exit code 0

unidbg学习(二)

第二个例子,如果native中有java方法调用怎么办?

给你的代码加几行

public native String stringFromJNI(Context context);
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativetest_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */, jobject context) {
    std::string hello = "Hello from C++";
    LOGE("Hello from C++ => %s", hello.c_str());
    for (int i = 0; i < hello.length(); i++) {
        hello[i] = hello[i] ^ hello.length();
    }
    LOGE("Hello from C++ => %s", hello.c_str());
    const char *pkgname = get_packagename(env, context);

    if (strcmp(pkgname, "com.example.nativetest") != -1) {
        LOGE("pkg name => %s", pkgname);
    }
    return env->NewStringUTF(hello.c_str());
}

const char *get_packagename(JNIEnv *env, jobject context) {
    jclass content_class = env->FindClass("android/content/Context");
    if (content_class == nullptr) {
        LOGE("find class error");
        return "";
    }
    jmethodID getPackageName_method = env->GetMethodID(content_class, "getPackageName",
                                                       "()Ljava/lang/String;");
    if (getPackageName_method == nullptr) {
        LOGE("find method error");
        return "";
    }
    if (env->ExceptionCheck()) {
        return "";
    }
    jstring pkgname_string = (jstring) env->CallObjectMethod(context, getPackageName_method);
    const char *ret = env->GetStringUTFChars(pkgname_string,
                                             JNI_FALSE);
    LOGE("pkgname => %s", ret);
    return ret;
}

老样子,我们编译成so文件,尝试调用一下unidbg-android/src/test/java/com/example/nativetest/Test02.java

package com.example.nativetest;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.linux.android.AndroidARM64Emulator;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.IOException;

/**
* 第二个例子,so中有java方法调用,如何处理
*/
public class Test02 extends AbstractJni {

    private final VM vm;
    private final DvmClass dvmClass;
    private AndroidEmulator emulator;
    private String processName = "com.example.nativetest";
    private String className = "com/example/nativetest/MainActivity";
    private Memory memory;

    public Test02() {
//        Logger.getRootLogger().setLevel(Level.DEBUG);
        emulator = createAndroidEmulator();
        memory = emulator.getMemory();
        memory.setLibraryResolver(createLibraryResolver(23));
        vm = createVM(null);
        vm.setJni(this);
        vm.setVerbose(true);

        DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/resources/example/arm64-v8a/libnative-lib02.so"), false);
        dvmClass = vm.resolveClass(className);
//        emulator.traceCode();
    }

    private LibraryResolver createLibraryResolver(int ver) {
        return new AndroidResolver(ver);
    }

    public static void main(String[] args) throws IOException {
        Test02 Test02 = new Test02();
        Test02.start();
        Test02.stop();
    }

    private AndroidEmulator createAndroidEmulator() {
        if (emulator == null) {
            return new AndroidARM64Emulator(processName);
        }
        return emulator;
    }

    private VM createVM(File apk) {
        return emulator.createDalvikVM(apk);
    }

    private void start() {
        // native方法有个参数,我们在这里创建一个context
        DvmObject result = dvmClass.callStaticJniMethodObject(emulator,
                "Java_com_example_nativetest_MainActivity_stringFromJNI(Landroid/content/Context;)Ljava/lang/String",
                vm.resolveClass("android/content/Context").newObject(null));
        System.out.println("result = " + result.getValue());
    }

    private void stop() throws IOException {
        emulator.close();
    }
}

报错了

JNIEnv->FindClass(android/content/Context) was called from RX@0x4001df3c[libnative-lib.so]0x1df3c
[10:44:14 688]  WARN [com.github.unidbg.linux.ARM64SyscallHandler] (ARM64SyscallHandler:275) - handleInterrupt intno=2, NR=-1073744360, svcNumber=0x113, PC=unidbg@0xfffe01c4, LR=RX@0x4001e084[libnative-lib.so]0x1e084, syscall=null
java.lang.UnsupportedOperationException: android/content/Context->getPackageName()Ljava/lang/String;
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:330)
        at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:197)
        at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethodV(DvmMethod.java:85)
        at com.github.unidbg.linux.android.dvm.DalvikVM64$20.handle(DalvikVM64.java:376)
        at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:82)
        at com.github.unidbg.arm.backend.UnicornBackend$6.hook(UnicornBackend.java:291)
        at unicorn.Unicorn$NewHook.onInterrupt(Unicorn.java:128)
        at unicorn.Unicorn.emu_start(Native Method)
        at com.github.unidbg.arm.backend.UnicornBackend.emu_start(UnicornBackend.java:316)
        at com.github.unidbg.AbstractEmulator.emulate(AbstractEmulator.java:398)
        at com.github.unidbg.AbstractEmulator.eFunc(AbstractEmulator.java:475)
        at com.github.unidbg.arm.AbstractARM64Emulator.eFunc(AbstractARM64Emulator.java:219)
        at com.github.unidbg.Module.emulateFunction(Module.java:154)
        at com.github.unidbg.linux.android.dvm.DvmObject.callJniMethod(DvmObject.java:128)
        at com.github.unidbg.linux.android.dvm.DvmClass.callStaticJniMethodObject(DvmClass.java:297)
        at com.example.nativetest.Test02.start(Test02.java:63)
        at com.example.nativetest.Test02.main(Test02.java:47)
[10:44:14 688]  WARN [com.github.unidbg.AbstractEmulator] (AbstractEmulator:417) - emulate RX@0x4001daa8[libnative-lib.so]0x1daa8 exception sp=unidbg@0xbffff510, msg=android/content/Context->getPackageName()Ljava/lang/String;, offset=36ms
        at com.example.nativetest.Test02.start(Test02.java:66)
        at com.example.nativetest.Test02.main(Test02.java:47)

从错误信息可以看出,他没找到android/content/Context->getPackageName()Ljava/lang/String;这个方法,我们需要重写一下这个方法,让他返回正确的值,这样应该就不报错了。

观察一个这个方法,没有参数,返回string,所以我们需要在我们的调用代码中添加如下代码:

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        if (signature.equals("android/content/Context->getPackageName()Ljava/lang/String;")) {
            //添加代码,让他返回一个争取的值。
            return new StringObject(vm, processName);
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

现在就正常了。

[main]E/TAG: Hello from C++ => Hello from C++
[main]E/TAG: Hello from C++ => Fkbba.h|ac.M%%
[main]E/TAG: pkgname => com.example.nativetest
[main]E/TAG: pkg name => com.example.nativetest
JNIEnv->FindClass(android/content/Context) was called from RX@0x4001df3c[libnative-lib.so]0x1df3c
JNIEnv->CallObjectMethodV(android.content.Context@192d3247, getPackageName() => "com.example.nativetest") was called from RX@0x4001e084[libnative-lib.so]0x1e084
JNIEnv->GetStringUtfChars("com.example.nativetest") was called from RX@0x4001e0f4[libnative-lib.so]0x1e0f4
JNIEnv->NewStringUTF("Fkbba.h|ac.M%%") was called from RX@0x4001df04[libnative-lib.so]0x1df04
result = Fkbba.h|ac.M%%

从这个例子中,我们可以看到,unidbg会拦截java层的方法调用,如果没有重写这些方法调用的话,对报错退出,所以我们要自己分析native函数调用,如果遇到java层方法调用的话,需要自己实现部分逻辑的。

unidbg学习(三)

第三个例子,尝试hook函数

为你的native代码加几行代码

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGE("unhook");
    ptrace(PTRACE_TRACEME, 0, 0, 0);
    JNIEnv *env;

    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    return JNI_VERSION_1_6;
}

编译成so,开始尝试hook代码

翻看unidbg介绍,我们发现

  • Inline hook, thanks to HookZz.
  • Android import hook, thanks to xHook.

那么我们先用xHook做第一次尝试:

package com.example.nativetest;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.xhook.IxHook;
import com.github.unidbg.linux.android.AndroidARM64Emulator;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.XHookImpl;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.sun.jna.Pointer;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.IOException;

/**
* 第三个例子,第一次尝试hook so方法
*/
public class Test03 extends AbstractJni {

    private final VM vm;
    private final DvmClass dvmClass;
    private AndroidEmulator emulator;
    private String processName = "com.example.nativetest";
    private String className = "com/example/nativetest/MainActivity";
    private Memory memory;

    public Test03() {
//        Logger.getRootLogger().setLevel(Level.DEBUG);
        emulator = createAndroidEmulator();
        memory = emulator.getMemory();
        memory.setLibraryResolver(createLibraryResolver(23));
        vm = createVM(null);
        vm.setJni(this);
        vm.setVerbose(true);

        DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/resources/example/arm64-v8a/libnative-lib03.so"), false);

        // 支持got表hook
        IxHook xHook = XHookImpl.getInstance(emulator); // 加载xHook,支持Import hook,文档看https://github.com/iqiyi/xHook
        xHook.register("libnative-lib03.so", "ptrace", new ReplaceCallback() { // hook libttEncrypt.so的导入函数strlen
            @Override
            // 类似与xposed的beforehookmethod
            public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
                int ptrace_args0 = context.getIntArg(0);
                System.out.println("ptrace=" + ptrace_args0);
                context.push(ptrace_args0);
                return HookStatus.RET(emulator, originFunction);
            }

            @Override
             // 类似与xposed的afterhookmethod
            public void postCall(Emulator<?> emulator, HookContext context) {
                System.out.println("ptrace=" + context.pop() + ", ret=" + context.getIntArg(0));
            }
        }, true);
        xHook.refresh(); // 使Import hook生效
        dalvikModule.callJNI_OnLoad(emulator);
        dvmClass = vm.resolveClass(className);
//        emulator.traceCode();
    }

    private LibraryResolver createLibraryResolver(int ver) {
        return new AndroidResolver(ver);
    }

    public static void main(String[] args) throws IOException {
        Test03 Test02 = new Test03();
        Test02.start();
        Test02.stop();
    }

    private AndroidEmulator createAndroidEmulator() {
        if (emulator == null) {
            return new AndroidARM64Emulator(processName);
        }
        return emulator;
    }

    private VM createVM(File apk) {
        return emulator.createDalvikVM(apk);
    }

    private void start() {
        DvmObject result = dvmClass.callStaticJniMethodObject(emulator,
                "Java_com_example_nativetest_MainActivity_stringFromJNI(Landroid/content/Context;)Ljava/lang/String",
                vm.resolveClass("android/content/Context").newObject(null));
        System.out.println("result = " + result.getValue());
    }

    private void stop() throws IOException {
        emulator.close();
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        if (signature.equals("android/content/Context->getPackageName()Ljava/lang/String;")) {
            return new StringObject(vm, processName);
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }
}

好了,测试一下:

ptrace=0
[11:13:25 135]  INFO [com.github.unidbg.linux.ARM64SyscallHandler] (ARM64SyscallHandler:1017) - ptrace request=0x0, pid=0, addr=null, data=null
ptrace=0, ret=0

unidbg学习(四)

第四个例子,尝试hook函数

为你的native代码添加几行

void function_check() {
    int pid = getpid();
    std::string file_name = "/proc/pid/status";
    std::string line;
    file_name.replace(file_name.find("pid"), 3, to_string(pid));
    LOGE("replace file name => %s", file_name.c_str());
    ifstream myfile(file_name, ios::in);
    if (myfile.is_open()) {
        while (getline(myfile, line)) {
            size_t TracerPid_pos = line.find("TracerPid");
            if (TracerPid_pos == 0) {
                line = line.substr(line.find(":") + 1);
                LOGE("file line => %s", line.c_str());
                if (std::stoi(line.c_str()) != 0) {
                    LOGE("trace pid => %s, i want to exit.", line.c_str());
//                    kill(pid, 9);
                    break;
                }
            }
        }
        myfile.close();
    }
}

void system_getproperty_check() {
    char man[256], mod[156];
    /* A length 0 value indicates that the property is not defined */
    int lman = __system_property_get("ro.product.manufacturer", man);
    int lmod = __system_property_get("ro.product.model", mod);
    int len = lman + lmod;
    char *pname = NULL;
    if (len > 0) {
        pname = static_cast<char *>(malloc(len + 2));
        snprintf(pname, len + 2, "%s/%s", lman > 0 ? man : "", lmod > 0 ? mod : "");
    }
    LOGE("[device]: [%s]\n", pname ? pname : "N/A");
    if (pname) free(pname);
}

void create_thread_check_traceid() {
    pthread_t t_id;
    int err = pthread_create(&t_id, NULL, reinterpret_cast<void *(*)(void *)>(function_check),
                             NULL);
    if (err != 0) {
        LOGE("create thread fail: %s\n", strerror(err));
    }
}

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGE("unhook");
    create_thread_check_traceid();
    ptrace(PTRACE_TRACEME, 0, 0, 0);
    system_getproperty_check();
    JNIEnv *env;

    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    return JNI_VERSION_1_6;
}

我们来看看unidbg调用so的方法怎么写

package com.example.nativetest;

import com.github.unidbg.*;
import com.github.unidbg.Module;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.DebuggerType;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.*;
import com.github.unidbg.hook.whale.IWhale;
import com.github.unidbg.hook.whale.Whale;
import com.github.unidbg.linux.android.AndroidARM64Emulator;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.io.IOException;

/**
* 第四个例子,学习hookso中的native方法
*/
public class Test04 extends AbstractJni {

    private final VM vm;
    private final DvmClass dvmClass;
    private final Module module;
    private AndroidEmulator emulator;
    private String processName = "com.example.nativetest";
    private String className = "com/example/nativetest/MainActivity";
    private Memory memory;

    public Test04() {
//        Logger.getRootLogger().setLevel(Level.DEBUG);
        emulator = createAndroidEmulator();
        memory = emulator.getMemory();
        memory.setLibraryResolver(createLibraryResolver(23));
        vm = createVM(null);
        vm.setJni(this);
        vm.setVerbose(true);

        DalvikModule dalvikModule = vm.loadLibrary(new File("unidbg-android/src/test/resources/example/arm64-v8a/libnative-lib04.so"), false);
        module = dalvikModule.getModule();

        // 1. 获取HookZz对象
        IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
        // 2. enable hook
        hookZz.enable_arm_arm64_b_branch(); // 测试enable_arm_arm64_b_branch,可有可无
        // 3. 尝试hook ptrace
        hookZz.wrap(module.findSymbolByName("ptrace"), new WrapCallback<RegisterContext>() { // inline wrap导出函数
            @Override
            // 4. 方法执行前
            public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
                int ptrace_args0 = ctx.getIntArg(0);
                System.out.println("ptrace=" + ptrace_args0);
            }

            @Override
            // 5. 方法执行后
            public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
                System.out.println("ptrace ret=" + ctx.getIntArg(0));
            }
        });
        hookZz.disable_arm_arm64_b_branch();
        // 1. 尝试hook
        Dobby dobby = Dobby.getInstance(emulator);
        // 2. 使用ida pro查看导出方法名,尝试hook
        dobby.replace(module.findSymbolByName("_Z27create_thread_check_traceidv"), new ReplaceCallback() { // 使用Dobby inline hook导出函数
            @Override
            // 3. contextk可以拿到参数,originFunction是原方法的地址
            public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
                System.out.println("create_thread_check_traceid.onCall function address => 0x" + Long.toHexString(originFunction));
                return HookStatus.RET(emulator, originFunction);
            }

            @Override
            // 4. 这里可以修改返回值
            public void postCall(Emulator<?> emulator, HookContext context) {
                System.out.println("create_thread_check_traceid.postCall ");
            }
        }, true);

        // 1. 尝试使用whale
        IWhale whale = Whale.getInstance(emulator);
        Symbol system_property_get = module.findSymbolByName("__system_property_get");
        // 2. 只是简单的hook打印了参数
        whale.inlineHookFunction(system_property_get, new ReplaceCallback() {
            @Override
            public HookStatus onCall(Emulator<?> emulator, long originFunction) {
                System.out.println("WInlineHookFunction system_property_get = " + emulator.getContext().getPointerArg(0).getString(0));
                return HookStatus.RET(emulator, originFunction);
            }
        });

        // 调用so文件的JNI_OnLoad方法
        dalvikModule.callJNI_OnLoad(emulator);
        dvmClass = vm.resolveClass(className);
    }

    private LibraryResolver createLibraryResolver(int ver) {
        return new AndroidResolver(ver);
    }

    public static void main(String[] args) throws IOException {
        Test04 Test02 = new Test04();
        Test02.start();
        Test02.stop();
    }

    private AndroidEmulator createAndroidEmulator() {
        if (emulator == null) {
            return new AndroidARM64Emulator(processName);
        }
        return emulator;
    }

    private VM createVM(File apk) {
        return emulator.createDalvikVM(apk);
    }

    private void start() {
        DvmObject result = dvmClass.callStaticJniMethodObject(emulator,
                "Java_com_example_nativetest_MainActivity_stringFromJNI(Landroid/content/Context;)Ljava/lang/String",
                vm.resolveClass("android/content/Context").newObject(null));
        System.out.println("result = " + result.getValue());
    }

    private void stop() throws IOException {
        emulator.close();
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        if (signature.equals("android/content/Context->getPackageName()Ljava/lang/String;")) {
            return new StringObject(vm, processName);
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }
}

测试一下

create_thread_check_traceid.onCall function address => 0x400d4000
[11:41:19 433]  INFO [com.github.unidbg.linux.ARM64SyscallHandler] (ARM64SyscallHandler:372) - pthread_clone child_stack=RW@0x4076e438, thread_id=1, fn=RX@0x40287c70[libc.so]0x67c70, arg=unidbg@0x4076e44000000000, flags=[CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND, CLONE_THREAD, CLONE_SYSVSEM, CLONE_SETTLS, CLONE_PARENT_SETTID, CLONE_CHILD_CLEARTID]
create_thread_check_traceid.postCall 
ptrace=0
[11:41:19 447]  INFO [com.github.unidbg.linux.ARM64SyscallHandler] (ARM64SyscallHandler:1017) - ptrace request=0x0, pid=0, addr=null, data=null
ptrace ret=0
WInlineHookFunction system_property_get = ro.product.manufacturer
WInlineHookFunction system_property_get = ro.product.model
Find native function Java_com_example_nativetest_MainActivity_Java_com_example_nativetest_MainActivity_stringFromJNI(Landroid/content/Context;)Ljava/lang/String => RX@0x4005f5b0[libnative-lib.so]0x5f5b0
JNIEnv->FindClass(android/content/Context) was called from RX@0x4005fa74[libnative-lib.so]0x5fa74
JNIEnv->CallObjectMethodV(android.content.Context@32a068d1, getPackageName() => "com.example.nativetest") was called from RX@0x4005fbbc[libnative-lib.so]0x5fbbc
[main]E/NativeTestNative: system_getproperty_check: [device]: [LGE/Nexus 5X]

unidbg学习(五)

第五个例子,尝试调用native中的非jni函数

我们的目标是尝试调用__system_property_get函数

先看看native代码:

#include <jni.h>
#include <string>
#include <android/log.h>
#include <sys/ptrace.h>
#include <pthread.h>
#include <unistd.h>
#include <fstream>
#include <sys/syscall.h>

using namespace std;
#define TAG "NativeTestNative"
#define LOGE(fmt, ...)  ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, "%s: " fmt, __FUNCTION__, ## __VA_ARGS__))

const char *get_packagename(JNIEnv *pEnv, jobject pJobject);

void system_getproperty_check();

void function_check();

void create_thread_check_traceid();

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativetest_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */, jobject context) {
    std::string hello = "Hello from C++";
    LOGE("Hello from C++ => %s", hello.c_str());
    for (int i = 0; i < hello.length(); i++) {
        hello[i] = hello[i] ^ hello.length();
    }
    LOGE("Hello from C++ => %s", hello.c_str());
    const char *pkgname = get_packagename(env, context);

    if (strcmp(pkgname, "com.example.nativetest") != -1) {
        LOGE("pkg name => %s", pkgname);
    }
    return env->NewStringUTF(hello.c_str());
}

const char *get_packagename(JNIEnv *env, jobject context) {
    jclass content_class = env->FindClass("android/content/Context");
    if (content_class == nullptr) {
        LOGE("find class error");
        return "";
    }
    jmethodID getPackageName_method = env->GetMethodID(content_class, "getPackageName",
                                                       "()Ljava/lang/String;");
    if (getPackageName_method == nullptr) {
        LOGE("find method error");
        return "";
    }
    if (env->ExceptionCheck()) {
        return "";
    }
    jstring pkgname_string = (jstring) env->CallObjectMethod(context, getPackageName_method);
    const char *ret = env->GetStringUTFChars(pkgname_string,
                                             JNI_FALSE);
    LOGE("pkgname => %s", ret);
    return ret;
}

void system_getproperty_check() {
    char man[256], mod[156];
    /* A length 0 value indicates that the property is not defined */
    int lman = __system_property_get("ro.product.manufacturer", man);
    int lmod = __system_property_get("ro.product.model", mod);
    int len = lman + lmod;
    char *pname = NULL;
    if (len > 0) {
        pname = static_cast<char *>(malloc(len + 2));
        snprintf(pname, len + 2, "%s/%s", lman > 0 ? man : "", lmod > 0 ? mod : "");
    }
    LOGE("[device]: [%s]\n", pname ? pname : "N/A");
    if (pname) free(pname);
}

void function_check() {
    int pid = getpid();
    std::string file_name = "/proc/pid/status";
    std::string line;
    file_name.replace(file_name.find("pid"), 3, to_string(pid));
    LOGE("replace file name => %s", file_name.c_str());
    ifstream myfile(file_name, ios::in);
    if (myfile.is_open()) {
        while (getline(myfile, line)) {
            size_t TracerPid_pos = line.find("TracerPid");
            if (TracerPid_pos == 0) {
                line = line.substr(line.find(":") + 1);
                LOGE("file line => %s", line.c_str());
                if (std::stoi(line.c_str()) != 0) {
                    LOGE("trace pid => %s, i want to exit.", line.c_str());
//                    kill(pid, 9);
                    break;
                }
            }
        }
        myfile.close();
    }
}

void create_thread_check_traceid() {
    pthread_t t_id;
    int err = pthread_create(&t_id, NULL, reinterpret_cast<void *(*)(void *)>(function_check),
                             NULL);
    if (err != 0) {
        LOGE("create thread fail: %s\n", strerror(err));
    }
}

jboolean Native_doTest(JNIEnv *env, jobject thiz) {
    pid_t pid = syscall(__NR_getpid);
    LOGE("pid => %d", pid);
    return JNI_FALSE;
}

static JNINativeMethod gMethods[] = {
        {"doTest", "()Z", (void *) Native_doTest}
};

int register_android_native_method(JNIEnv *env) {
    jclass mainActivity_class = env->FindClass("com/example/nativetest/MainActivity");
    return env->RegisterNatives(mainActivity_class, gMethods,
                                sizeof(gMethods) / sizeof(JNINativeMethod));
}

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGE("unhook");
    create_thread_check_traceid();
//    ptrace(PTRACE_TRACEME, 0, 0, 0);
    system_getproperty_check();
    JNIEnv *env;

    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    register_android_native_method(env);
    return JNI_VERSION_1_6;
}

编译成so文件

我们来看看unidbg的测试代码怎么写:

        // 1. 先找到目标函数
        Symbol system_property_get_native = module.findSymbolByName("__system_property_get", true);
        // 2. int __system_property_get(const char* __name, char* __value);
        // 观察函数原型,第一个参数是要取的值,第二个参数是返回值,所以我们要调用后拿到第二个值
        // 这里创建一个buf用来存储返回值。
        MemoryBlock block = null;
        try {
            // 设置buf大小
            block = memory.malloc(0x100, true);
            // 3. 调用system_property_get函数
            Number ret = system_property_get_native.call(emulator, "ro.build.fingerprint", block.getPointer())[0];
            // 4. 从内存中把结果取出来
            System.out.println("Number => " + ret.intValue() + ", ro.build.fingerprint => " + block.getPointer().getString(0));
        } finally {
            if (block != null) {
                // 5. 释放内存
                block.free();
            }
        }

测试一下:

Number => 61, ro.build.fingerprint => google/bullhead/bullhead:6.0/MDA89E/2296692:user/release-keys

好了,看起来没问题,一切OK

unidbg学习(六)

第六个例子,尝试模拟ida protrace功能

用过ida pro trace功能的人,都知道,ida pro可以帮你把每一步汇编执行后寄存器的变化记录下来,方便后期分析算法。

unidbg是基于unicorn的,他天生就支持trace,我们是否可以通过unidbg来模拟ida protrace功能呢?

感谢unidbg的开发者,已经提供了相关api接口

// trace保存到文件
emulator.redirectTrace(new File("out.log"));
// trace 读
emulator.traceRead();
// trace 写
emulator.traceWrite();
// trace insn
emulator.traceCode();

另外,提一下开启日志模式的方法,方便出错了调试

Logger.getRootLogger().setLevel(Level.DEBUG);

先随便找一个例子尝试一下,看一下输出

### Trace Instruction [         libc.so] [0x017b64] [ 11 f6 40 f9 ] 0x40237b64: ldr x17, [x16, #0x1e8]
### Trace Instruction [         libc.so] [0x017b68] [ 10 a2 07 91 ] 0x40237b68: add x16, x16, #0x1e8
### Trace Instruction [         libc.so] [0x017b6c] [ 20 02 1f d6 ] 0x40237b6c: br x17

标准unicorn的输出。

我们想看汇编执行后寄存器的值,看来还需要改一下代码:

先解决第一个问题,我们希望每次trace时创建一个日志文件,所以需要先改改代码:

unidbg-api/src/main/java/com/github/unidbg/AbstractEmulator.java

    @Override
    public void redirectTrace(File outFile) {
        if(outFile.exists() && outFile.isFile()){
            outFile.delete();
        }
        this.traceOutFile = outFile;
    }

下面全局搜一下### Trace Instruction找到打印汇编代码的地方。

unidbg-api/src/main/java/com/github/unidbg/arm/AbstractARM64Emulator.java

修改代码添加打印

image-20210203122230611.png

我们先再打印汇编下面添加一个函数用来打印寄存器的值

unidbg-api/src/main/java/com/github/unidbg/arm/ARM.java中创建一个函数

// 配置汇编开始执行时打印所有寄存器的值
private static boolean showAllRegs = true;    
public static String showArm64RegsInfo(Emulator<?> emulator, Capstone.CsInsn ins) {
        if (showAllRegs) {
            showAllRegs = false;
            // 配置汇编开始执行时打印所有寄存器的值
            return showRegs64(emulator, ARM64_REGS);
        } else if ("br".equals(ins.mnemonic)
                || "blr".equals(ins.mnemonic)
                || "bl".equals(ins.mnemonic)
                || "blx".equals(ins.mnemonic)
                || "bx".equals(ins.mnemonic)
                || "ret`".equals(ins.mnemonic)) {
            showAllRegs = true;
            return showRegs64(emulator, new int[]{getArm64Regs(ins)});
        } else {
            // 配置打印特定的寄存器的值
            return showRegs64(emulator, new int[]{getArm64Regs(ins)});
        }
    }

showRegs64中添加打印W寄存器的方法:

......                
case Arm64Const.UC_ARM64_REG_W4:
    number = backend.reg_read(reg);
    value = number.longValue();
    builder.append(String.format(Locale.US, " w4=0x%x", value));
    break;
......  

改一下showRegs64让他返回string

改一下unidbg-api/src/main/java/com/github/unidbg/AssemblyCodeDumper.java

   private long startAddr = -1;
   // 修改逻辑打印寄存器的值
   @Override
    public void hook(Backend backend, long address, int size, Object user) {
        if (canTrace(address)) {
            try {
                PrintStream out = System.out;
                if (redirect != null) {
                    out = redirect;
                }

                if (startAddr != -1) {
                    Capstone.CsInsn[] insns = emulator.printAssemble(out, startAddr, size);
                    if (listener != null) {
                        if (insns == null || insns.length != 1) {
                            throw new IllegalStateException("insns=" + Arrays.toString(insns));
                        }
                        listener.onInstruction(emulator, startAddr, insns[0]);
                    }

                }
                startAddr = address;

            } catch (BackendException e) {
                throw new IllegalStateException(e);
            }
        }
    }

测试一下:

### Trace Instruction [libnative-lib.so] [0x05f7b8] [ ff 03 03 d1 ] 0x4005f7b8: sub sp, sp, #0xc0 >>> x0=0xfffe0d30(-127696) x1=0x1e04791a x2=0x3891771e x3=0x1 x4=0x3 x5=0x5 x6=0x6 x7=0x402c6000 x8=0x10006 x9=0x0 x10=0x0 x11=0x1010 x12=0x0 x13=0x40306000 x14=0x0>>> x15=0x402f8000 x16=0x400ce8a0 x17=0x40060a58 x18=0x40309f50 x19=0x0 x20=0x0 x21=0x0 x22=0x0 x23=0x0 x24=0x0 x25=0x0 x26=0x0 x27=0x0 x28=0x0 fp=0x0>>> q0=0xbffff47800000000bffff510 q1=0xffffff80ffffffe000000000bffff450 q2=0x63202c745f72616863773c656d616e79 q3=0x20000000200002 q4=0x0 q5=0x0 q6=0x0 q7=0x80200802802008028020080280200802 q8=0x0 q9=0x0 q10=0x0 q11=0x0 q12=0x0 q13=0x0 q14=0x0 q15=0x0>>> q16=0x40100401401004014010040140100401 q17=0x2022 q18=0x0 q19=0x0 q20=0x0 q21=0x0 q22=0x0 q23=0x0 q24=0x0 q25=0x0 q26=0x0 q27=0x0 q28=0x0 q29=0x0 q30=0x0 q31=0x0LR=unidbg@0x7ffff0000SP=0xbffff6b0PC=RX@0x4005f7bc[libnative-lib.so]0x5f7bcnzcv: N=0, Z=1, C=1, V=0, EL0, use SP_EL0
### Memory WRITE at 0xbffff760, data size = 8, data value = 0x0 pc=RX@0x4005f7bc[libnative-lib.so]0x5f7bc lr=unidbg@0x7ffff0000
### Memory WRITE at 0xbffff768, data size = 8, data value = 0x7ffff0000 pc=RX@0x4005f7bc[libnative-lib.so]0x5f7bc lr=unidbg@0x7ffff0000
### Trace Instruction [libnative-lib.so] [0x05f7bc] [ fd 7b 0b a9 ] 0x4005f7bc: stp x29, x30, [sp, #0xb0] >>> fp=0x0
### Trace Instruction [libnative-lib.so] [0x05f7c0] [ fd c3 02 91 ] 0x4005f7c0: add x29, sp, #0xb0 >>> fp=0xbffff760
### Trace Instruction [libnative-lib.so] [0x05f7c4] [ 48 d0 3b d5 ] 0x4005f7c4: mrs x8, tpidr_el0 >>> x8=0xbffff778
### Memory READ at 0xbffff7a0, data size = 8, data value = 0000000000000000 pc=RX@0x4005f7c8[libnative-lib.so]0x5f7c8 lr=unidbg@0x7ffff0000
### Trace Instruction [libnative-lib.so] [0x05f7c8] [ 08 15 40 f9 ] 0x4005f7c8: ldr x8, [x8, #0x28] >>> x8=0x0
### Memory WRITE at 0xbffff758, data size = 8, data value = 0x0 pc=RX@0x4005f7cc[libnative-lib.so]0x5f7cc lr=unidbg@0x7ffff0000
### Trace Instruction [libnative-lib.so] [0x05f7cc] [ a8 83 1f f8 ] 0x4005f7cc: stur x8, [x29, #-8] >>> x8=0x0
### Memory WRITE at 0xbffff738, data size = 8, data value = 0xfffe0d30 pc=RX@0x4005f7d0[libnative-lib.so]0x5f7d0 lr=unidbg@0x7ffff0000
### Trace Instruction [libnative-lib.so] [0x05f7d0] [ a0 83 1d f8 ] 0x4005f7d0: stur x0, [x29, #-0x28] >>> x0=0xfffe0d30(-127696)
### Memory WRITE at 0xbffff730, data size = 8, data value = 0x1e04791a pc=RX@0x4005f7d4[libnative-lib.so]0x5f7d4 lr=unidbg@0x7ffff0000
### Trace Instruction [libnative-lib.so] [0x05f7d4] [ a1 03 1d f8 ] 0x4005f7d4: stur x1, [x29, #-0x30] >>> x1=0x1e04791a
### Memory WRITE at 0xbffff728, data size = 8, data value = 0x3891771e pc=RX@0x4005f7d8[libnative-lib.so]0x5f7d8 lr=unidbg@0x7ffff0000
### Trace Instruction [libnative-lib.so] [0x05f7d8] [ a2 83 1c f8 ] 0x4005f7d8: stur x2, [x29, #-0x38] >>> x2=0x3891771e
### Trace Instruction [libnative-lib.so] [0x05f7dc] [ 41 ff ff 90 ] 0x4005f7dc: adrp x1, #0x40047000 >>> x1=0x40047000
### Trace Instruction [libnative-lib.so] [0x05f7e0] [ 21 d8 14 91 ] 0x4005f7e0: add x1, x1, #0x536 >>> x1=0x40047536
### Trace Instruction [libnative-lib.so] [0x05f7e4] [ a8 83 00 d1 ] 0x4005f7e4: sub x8, x29, #0x20 >>> x8=0xbffff740
### Trace Instruction [libnative-lib.so] [0x05f7e8] [ e0 03 08 aa ] 0x4005f7e8: mov x0, x8 >>> x0=0xbffff740(-1073744064)
### Memory WRITE at 0xbffff708, data size = 8, data value = 0xbffff740 pc=RX@0x4005f7ec[libnative-lib.so]0x5f7ec lr=unidbg@0x7ffff0000
### Trace Instruction [libnative-lib.so] [0x05f7ec] [ e8 2f 00 f9 ] 0x4005f7ec: str x8, [sp, #0x58] >>> x8=0xbffff740
### Trace Instruction [libnative-lib.so] [0x05f7f0] [ c4 97 01 94 ] 0x4005f7f0: bl #0x400c5700 >>>
### Trace Instruction [libnative-lib.so] [0x0c5700] [ 50 00 00 b0 ] 0x400c5700: adrp x16, #0x400ce000 >>> x0=0xbffff740(-1073744064) x1=0x40047536 x2=0x3891771e x3=0x1 x4=0x3 x5=0x5 x6=0x6 x7=0x402c6000 x8=0xbffff740 x9=0x0 x10=0x0 x11=0x1010 x12=0x0 x13=0x40306000 x14=0x0>>> x15=0x402f8000 x16=0x400ce000 x17=0x40060a58 x18=0x40309f50 x19=0x0 x20=0x0 x21=0x0 x22=0x0 x23=0x0 x24=0x0 x25=0x0 x26=0x0 x27=0x0 x28=0x0 fp=0xbffff760>>> q0=0xbffff47800000000bffff510 q1=0xffffff80ffffffe000000000bffff450 q2=0x63202c745f72616863773c656d616e79 q3=0x20000000200002 q4=0x0 q5=0x0 q6=0x0 q7=0x80200802802008028020080280200802 q8=0x0 q9=0x0 q10=0x0 q11=0x0 q12=0x0 q13=0x0 q14=0x0 q15=0x0>>> q16=0x40100401401004014010040140100401 q17=0x2022 q18=0x0 q19=0x0 q20=0x0 q21=0x0 q22=0x0 q23=0x0 q24=0x0 q25=0x0 q26=0x0 q27=0x0 q28=0x0 q29=0x0 q30=0x0 q31=0x0LR=RX@0x4005f7f4[libnative-lib.so]0x5f7f4SP=0xbffff6b0PC=RX@0x400c5704[libnative-lib.so]0xc5704nzcv: N=0, Z=1, C=1, V=0, EL0, use SP_EL0
### Memory READ at 0x400ce788, data size = 8, data value = d4f9054000000000 pc=RX@0x400c5704[libnative-lib.so]0xc5704 lr=RX@0x4005f7f4[libnative-lib.so]0x5f7f4

好了,现在看起来可以用来,以后有啥问题再修改。

unidbg学习(七)

娱乐之尝试ida debug so

unidbg提供了对ida android_server的支持,只需要在测试代码中添加如下

emulator.attach(DebuggerType.ANDROID_SERVER_V7).addBreakPoint(module.base + 0xAC8); // 可以通过addBreakPoint配置断点

原版本支持的是ida 7.4我们来修改一下,让他支持ida 7.5

打开ida7.5,配置ip and port,尝试连接,报错:

image-20210207162229752.png

我们来修改一下DebugServer.java配置:

byte IDA_PROTOCOL_VERSION_V7 = 0x1A; // IDA Pro v7.x

还是连不上,ida也没报啥错误,debug发现跑到AndroidServer.java如下代码后就gg

            case 0xa: {
                long value = Utils.unpack_dd(buffer);
                long b = Utils.unpack_dd(buffer);
                if (log.isDebugEnabled()) {
                    log.debug("processCommand value=0x" + Long.toHexString(value) + ", b=" + b);
                }
                sendAck((byte) 0x5);
                break;
            }

发现这里是向ida pro发送tcp数据的,于是最好的方法就是抓一下正常的tcp数据包,对比一下,看看到底哪里出了问题。

安装wireshark,安装Npcap勾选

image-20210207163520357.png

打开wireshark选网卡

image-20210207163614075.png

配置过滤条件tcp.port == 23946

手机上传入android_serverida pro尝试连接,抓取tcp数据流量,然后抓取unidbgida pro通讯数据流量,对比

image-20210207163826883.png

发现这一条tcp消息不同,查看上下报文,发现应该是ida pro请求server给他发送正在运行的程序列表,

尝试分析一下unidbg中的实现,修改一下AndroidServer.java中发送数据

            case 0xa: {
                long value = Utils.unpack_dd(buffer);
                long b = Utils.unpack_dd(buffer);
                if (log.isDebugEnabled()) {
                    log.debug("processCommand value=0x" + Long.toHexString(value) + ", b=" + b);
                }
                sendAck(new byte[]{0x1, 0x5});
                break;
            }

重新测试:

image-20210207164135113.png

OK

然后使用ida pro尝试debug,然后

image-20210207164258276.png

修改了archarm还是识别不正常,被自己蠢哭了:confused:

真是个悲伤的故事。

image-20210207163326055.png

免费评分

参与人数 13吾爱币 +22 热心值 +13 收起 理由
CoderYanKang + 1 + 1 我很赞同!
Shocker + 2 + 1 非常有用,谢谢了!
jason903 + 1 + 1 谢谢@Thanks!
feng504x + 1 + 1 很好
小范 + 3 + 1 谢谢@Thanks!
hzzheyang + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
wzy1984 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
JMBQ + 1 + 1 我很赞同!
笙若 + 1 + 1 谢谢@Thanks!
漁滒 + 3 + 1 我很赞同!
Li1y + 1 + 1 我很赞同!
细水流长 + 3 + 1 热心回复!
逍遥一仙 + 3 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

漁滒 发表于 2021-2-9 20:26
大佬,我还是想问之前的一个问题。当so中存在系统读写文件时,应该要如何处理,例如猿人学的第十一题【https://pan.baidu.com/s/1u4OjtUMN_1vMhLP5t_8RKA 提取码:fxns】。即使我自己已经设置了jni,但是依然存在以下报错
213358xrsx3gy0bhms2szs.jpg
 楼主| Shutd0wn 发表于 2021-2-7 17:27
qujf 发表于 2021-2-7 18:19
Li1y 发表于 2021-2-7 18:35
QQ拼音截图20210207183526.png
之后怎么搞。。。
xixicoco 发表于 2021-2-8 03:05
牛逼 ,顶你的
liuqinglong326 发表于 2021-2-9 00:17
牛逼 ,顶你的
JMBQ 发表于 2021-2-9 09:34
厉害,把寄存器打印了遍
159753qwe 发表于 2021-2-11 22:47
这个都有人科普?!
碎步流年 发表于 2021-3-4 14:49
感谢分享,学习了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 12:21

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表