Shutd0wn 发表于 2021-2-7 17:04

菜鸡诈尸水贴之unidbg学习笔记

本帖最后由 Shutd0wn 于 2021-2-7 17:22 编辑

# unidbg学习(一)

## `unidbg`是什么?

(https://github.com/zhkl0228/unidbg)是`unicorn`的一个实现,它可以让你在电脑上跑`arm`的可执行文件或共享库文件。

以上是我的理解。

## `unidbg`的优点?

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

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

## 先把demo跑起来

电脑安装`IntelliJ IDEA`

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

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

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



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

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

Process finished with exit code 0

```



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

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

```c
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 = hello ^ 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`:



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

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

```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();
    }
}
```

尝试跑一下,报错了:

```java
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`:

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

```

好了

```log
Find native function Java_com_example_nativetest_MainActivity_Java_com_example_nativetest_MainActivity_stringFromJNI()Ljava/lang/String => RX@0x4001d6b80x1d6b8
E/TAG: Hello from C++ => Hello from C++
E/TAG: Hello from C++ => Fkbba.h|ac.M%%
JNIEnv->NewStringUTF("Fkbba.h|ac.M%%") was called from RX@0x4001d98c0x1d98c
result = Fkbba.h|ac.M%%

Process finished with exit code 0
```

# unidbg学习(二)

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

给你的代码加几行

```java
public native String stringFromJNI(Context context);
```

```c
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 = hello ^ 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`:

```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();
    }
}

```

报错了

```log
JNIEnv->FindClass(android/content/Context) was called from RX@0x4001df3c0x1df3c
WARN (ARM64SyscallHandler:275) - handleInterrupt intno=2, NR=-1073744360, svcNumber=0x113, PC=unidbg@0xfffe01c4, LR=RX@0x4001e0840x1e084, 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)
WARN (AbstractEmulator:417) - emulate RX@0x4001daa80x1daa8 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`,所以我们需要在我们的调用代码中添加如下代码:

```java
    @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);
    }
```

现在就正常了。

```log
E/TAG: Hello from C++ => Hello from C++
E/TAG: Hello from C++ => Fkbba.h|ac.M%%
E/TAG: pkgname => com.example.nativetest
E/TAG: pkg name => com.example.nativetest
JNIEnv->FindClass(android/content/Context) was called from RX@0x4001df3c0x1df3c
JNIEnv->CallObjectMethodV(android.content.Context@192d3247, getPackageName() => "com.example.nativetest") was called from RX@0x4001e0840x1e084
JNIEnv->GetStringUtfChars("com.example.nativetest") was called from RX@0x4001e0f40x1e0f4
JNIEnv->NewStringUTF("Fkbba.h|ac.M%%") was called from RX@0x4001df040x1df04
result = Fkbba.h|ac.M%%
```

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

# unidbg学习(三)

## 第三个例子,尝试`hook`函数

为你的`native`代码加几行代码

```c
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 (https://github.com/jmpews/Dobby).
> - Android import hook, thanks to (https://github.com/iqiyi/xHook).

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

```java
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);
    }
}

```

好了,测试一下:

```log
ptrace=0
INFO (ARM64SyscallHandler:1017) - ptrace request=0x0, pid=0, addr=null, data=null
ptrace=0, ret=0
```

# unidbg学习(四)

## 第四个例子,尝试`hook`函数

为你的`native`代码添加几行

```c
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, mod;
    /* 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(": [%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`的方法怎么写

```java
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);
    }
}

```

测试一下

```c
create_thread_check_traceid.onCall function address => 0x400d4000
INFO (ARM64SyscallHandler:372) - pthread_clone child_stack=RW@0x4076e438, thread_id=1, fn=RX@0x40287c700x67c70, arg=unidbg@0x4076e44000000000, flags=
create_thread_check_traceid.postCall
ptrace=0
INFO (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@0x4005f5b00x5f5b0
JNIEnv->FindClass(android/content/Context) was called from RX@0x4005fa740x5fa74
JNIEnv->CallObjectMethodV(android.content.Context@32a068d1, getPackageName() => "com.example.nativetest") was called from RX@0x4005fbbc0x5fbbc
E/NativeTestNative: system_getproperty_check: :
```

# unidbg学习(五)

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

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

先看看`native`代码:

```c
#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 = hello ^ 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, mod;
    /* 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(": [%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`的测试代码怎么写:

```java
      // 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());
            // 4. 从内存中把结果取出来
            System.out.println("Number => " + ret.intValue() + ", ro.build.fingerprint => " + block.getPointer().getString(0));
      } finally {
            if (block != null) {
                // 5. 释放内存
                block.free();
            }
      }
```

测试一下:

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

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

# unidbg学习(六)

## 第六个例子,尝试模拟`ida pro`的`trace`功能

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

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

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

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

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

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

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

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

标准`unicorn`的输出。

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

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

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

```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`

修改代码添加打印



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

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

```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`寄存器的方法:

```java
......               
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`

```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);
                  }

                }
                startAddr = address;

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

测试一下:

```log
### Trace Instruction [ 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@0x4005f7bc0x5f7bcnzcv: N=0, Z=1, C=1, V=0, EL0, use SP_EL0
### Memory WRITE at 0xbffff760, data size = 8, data value = 0x0 pc=RX@0x4005f7bc0x5f7bc lr=unidbg@0x7ffff0000
### Memory WRITE at 0xbffff768, data size = 8, data value = 0x7ffff0000 pc=RX@0x4005f7bc0x5f7bc lr=unidbg@0x7ffff0000
### Trace Instruction [ fd 7b 0b a9 ] 0x4005f7bc: stp x29, x30, >>> fp=0x0
### Trace Instruction [ fd c3 02 91 ] 0x4005f7c0: add x29, sp, #0xb0 >>> fp=0xbffff760
### Trace Instruction [ 48 d0 3b d5 ] 0x4005f7c4: mrs x8, tpidr_el0 >>> x8=0xbffff778
### Memory READ at 0xbffff7a0, data size = 8, data value = 0000000000000000 pc=RX@0x4005f7c80x5f7c8 lr=unidbg@0x7ffff0000
### Trace Instruction [ 08 15 40 f9 ] 0x4005f7c8: ldr x8, >>> x8=0x0
### Memory WRITE at 0xbffff758, data size = 8, data value = 0x0 pc=RX@0x4005f7cc0x5f7cc lr=unidbg@0x7ffff0000
### Trace Instruction [ a8 83 1f f8 ] 0x4005f7cc: stur x8, >>> x8=0x0
### Memory WRITE at 0xbffff738, data size = 8, data value = 0xfffe0d30 pc=RX@0x4005f7d00x5f7d0 lr=unidbg@0x7ffff0000
### Trace Instruction [ a0 83 1d f8 ] 0x4005f7d0: stur x0, >>> x0=0xfffe0d30(-127696)
### Memory WRITE at 0xbffff730, data size = 8, data value = 0x1e04791a pc=RX@0x4005f7d40x5f7d4 lr=unidbg@0x7ffff0000
### Trace Instruction [ a1 03 1d f8 ] 0x4005f7d4: stur x1, >>> x1=0x1e04791a
### Memory WRITE at 0xbffff728, data size = 8, data value = 0x3891771e pc=RX@0x4005f7d80x5f7d8 lr=unidbg@0x7ffff0000
### Trace Instruction [ a2 83 1c f8 ] 0x4005f7d8: stur x2, >>> x2=0x3891771e
### Trace Instruction [ 41 ff ff 90 ] 0x4005f7dc: adrp x1, #0x40047000 >>> x1=0x40047000
### Trace Instruction [ 21 d8 14 91 ] 0x4005f7e0: add x1, x1, #0x536 >>> x1=0x40047536
### Trace Instruction [ a8 83 00 d1 ] 0x4005f7e4: sub x8, x29, #0x20 >>> x8=0xbffff740
### Trace Instruction [ e0 03 08 aa ] 0x4005f7e8: mov x0, x8 >>> x0=0xbffff740(-1073744064)
### Memory WRITE at 0xbffff708, data size = 8, data value = 0xbffff740 pc=RX@0x4005f7ec0x5f7ec lr=unidbg@0x7ffff0000
### Trace Instruction [ e8 2f 00 f9 ] 0x4005f7ec: str x8, >>> x8=0xbffff740
### Trace Instruction [ c4 97 01 94 ] 0x4005f7f0: bl #0x400c5700 >>>
### Trace Instruction [ 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@0x4005f7f40x5f7f4SP=0xbffff6b0PC=RX@0x400c57040xc5704nzcv: N=0, Z=1, C=1, V=0, EL0, use SP_EL0
### Memory READ at 0x400ce788, data size = 8, data value = d4f9054000000000 pc=RX@0x400c57040xc5704 lr=RX@0x4005f7f40x5f7f4
```

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

# unidbg学习(七)

## 娱乐之尝试`ida debug so`

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

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

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

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



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

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

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

```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((byte) 0x5);
                break;
            }
```

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

安装`wireshark`,安装`Npcap`勾选



打开`wireshark`选网卡



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

手机上传入`android_server`,`ida pro`尝试连接,抓取`tcp`数据流量,然后抓取`unidbg`与`ida pro`通讯数据流量,对比



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

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

```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;
            }
```

重新测试:



OK

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



修改了`arch`为`arm`还是识别不正常,被自己蠢哭了:confused:

真是个悲伤的故事。

漁滒 发表于 2021-2-9 20:26

大佬,我还是想问之前的一个问题。当so中存在系统读写文件时,应该要如何处理,例如猿人学的第十一题【https://pan.baidu.com/s/1u4OjtUMN_1vMhLP5t_8RKA 提取码:fxns】。即使我自己已经设置了jni,但是依然存在以下报错

Shutd0wn 发表于 2021-2-7 17:27

论坛的markdown一言难尽啊

qujf 发表于 2021-2-7 18:19

遇到技术贴先顶为上

Li1y 发表于 2021-2-7 18:35


之后怎么搞。。。

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

感谢分享,学习了
页: [1] 2
查看完整版本: 菜鸡诈尸水贴之unidbg学习笔记