Unidbg - 检测Unidbg的小方法

检测unidbg的一些方法,因为方法非网上泛滥的方法,未保证我未来不会遇到这种检测,故不展示出来,如有需要发送邮件获取密码哦。

JavaVM实现错误

UnidbgPointer _JNIInvokeInterface = svcMemory.allocate(emulator.getPointerSize() * 8, "_JNIInvokeInterface");
for (int i = 0; i < emulator.getPointerSize() * 8; i += emulator.getPointerSize()) {
    _JNIInvokeInterface.setLong(i, i);
}

_JNIInvokeInterface.setPointer(emulator.getPointerSize() * 3L, _DestroyJavaVM);
_JNIInvokeInterface.setPointer(emulator.getPointerSize() * 4L, _AttachCurrentThread);
_JNIInvokeInterface.setPointer(emulator.getPointerSize() * 5L, _DetachCurrentThread);
_JNIInvokeInterface.setPointer(emulator.getPointerSize() * 6L, _GetEnv);
_JNIInvokeInterface.setPointer(emulator.getPointerSize() * 7L, _AttachCurrentThreadAsDaemon);
_JavaVM.setPointer(0, _JNIInvokeInterface);

上面的代码是unidbg实现JavaVM的具体代码,

struct _JNIInvokeInterface;
typedef const struct _JNIInvokeInterface *JavaVM;

struct _JNIInvokeInterface {
    void *reserved0;
    void *reserved1;
    void *reserved2;

    jint (*DestroyJavaVM)(JavaVM *vm);
    jint (*AttachCurrentThread)(JavaVM *vm, void **penv, void *args);
    jint (*DetachCurrentThread)(JavaVM *vm);
    jint (*GetEnv)(JavaVM *vm, void **penv, jint version);
    jint (*AttachCurrentThreadAsDaemon)(JavaVM *vm, void **penv, void *args);
};

// 以下为Openjdk的实现
const struct JNIInvokeInterface_ jni_InvokeInterface = {
    nullptr,
    nullptr,
    nullptr,

    jni_DestroyJavaVM,
    jni_AttachCurrentThread,
    jni_DetachCurrentThread,
    jni_GetEnv,
    jni_AttachCurrentThreadAsDaemon
};

// Android实现4.4+
static const struct JNIInvokeInterface gInvokeInterface = {
    NULL,
    NULL,
    NULL,
    DestroyJavaVM,
    AttachCurrentThread,
    DetachCurrentThread,
    GetEnv,
    AttachCurrentThreadAsDaemon,
};

显而易见?unidbg的_JNIInvokeInterface 是否有些规律呢?

因为实现不一样,该操作不一定作为真正的检测标准。

Futex实现缺失

在unidbg开启了多线程支持的情况下,多线程调用futex(WAIT) 会触发错误。

fstat实现错误

int mystat64(const char *path, struct stat64 *buf) {
    auto fd = open(path, O_RDONLY);
    struct stat64 st{};
    int ret = syscall(80, fd, &st);
    if (ret == -1) {
        std::cout << "syscall stat64 error: " << strerror(errno) << std::endl;
        return -1;
    }
    *buf = st;
    return 0;
}

int main() {
    struct stat64 buf{};
    mystat64("/data/app/~~01yxZRkvdJCDqAxGjq8ymA==/com.tencent.qqmusic-Kq0CsVMK-tCcjlYRh2o9-w==/base.apk", &buf);

    if (buf.st_uid == 0) {
        std::cout << "uid is 0, unidbg?\n";
    }

    return 0;
}

mmap错误

unidbg在为open的fd进行mmap的时候,不会校验open出来的fd具有什么权限,一个READ_ONLY无法mmap一个可写的内存。

int main() {
    const char* filePath = "/data/local/tmp/test";
    int fd = open(filePath, O_RDONLY);
    if (fd == -1) {
        perror("Error opening file");
        return 1;
    }
    struct stat fileInfo{};
    if (fstat(fd, &fileInfo) == -1) {
        perror("Error getting the file size");
        close(fd);
        return 1;
    }
    char* mapped = (char*) mmap(NULL, fileInfo.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mapped != MAP_FAILED) {
        std::cout << "MMAP SUCCESS?So, you are unidbg!" << std::endl;
        return 1;
    }
    mapped[0] = 'H';
    mapped[1] = 'e';
    mapped[2] = 'l';
    mapped[3] = 'l';
    mapped[4] = 'o';

    return 0;
}

[和谐] 向JNI获取不存在的类

JNI_ONLOAD

0x6F248

部分JNI实现异常

 template <typename ArrayT, typename ElementT, typename ArtArrayT>
  static ElementT* GetPrimitiveArray(JNIEnv* env, ArrayT java_array, jboolean* is_copy) {
    CHECK_NON_NULL_ARGUMENT(java_array);
    ScopedObjectAccess soa(env);
    ObjPtr<ArtArrayT> array = DecodeAndCheckArrayType<ArrayT, ElementT, ArtArrayT>(
        soa, java_array, "GetArrayElements", "get");
    if (UNLIKELY(array == nullptr)) {
      return nullptr;
    }
    // Only make a copy if necessary.
    if (Runtime::Current()->GetHeap()->IsMovableObject(array)) {
      if (is_copy != nullptr) {
        *is_copy = JNI_TRUE;
      }
      const size_t component_size = sizeof(ElementT);
      size_t size = array->GetLength() * component_size;
      void* data = new uint64_t[RoundUp(size, 8) / 8];
      memcpy(data, array->GetData(), size);
      return reinterpret_cast<ElementT*>(data);
    } else {
      if (is_copy != nullptr) {
        *is_copy = JNI_FALSE;
      }
      return reinterpret_cast<ElementT*>(array->GetData());
    }
  }

上方代码是android的jni对于获取一个java数组的本地元素实现,其中的new表明了获取出来的数据在libc堆区,这意味着,我们可以让java放弃这段数据的内存管理,全部移交给我们,让我们本地去free这段内存。

即调用ReleaseByteArrayElements然后指定模式为JNI_COMMIT。然后因为unidbg的这个实现内存是mmap出来的,我们直接执行munmap,然后再去操作内存,应该会喜提爆炸吧。