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,然后再去操作内存,应该会喜提爆炸吧。