安卓逆向-Anti Unidbg之JNI层检测

sana
7月15日发布 /正在检测是否收录...

JNI层检测

1. Findclass的anti

  • 新建一个项目,写一个简单的findclass程序;
extern "C" JNIEXPORT jstring JNICALL
Java_com_sana_antifindclass_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    jclass MainActivity = env->FindClass("com/sana/antifindclass/MainActivity2");
    bool exception = env->ExceptionCheck();
    if (exception) {
        hello.append("Exception!!");
        env->ExceptionClear();
    }else {
        hello.append("No Exception");
    }
    return env->NewStringUTF(hello.c_str());
}
  • build一下,在真机和unidbg分别跑起来这个方法;
  • 真机:

image-20250714104831051

  • Unidbg:

image-20250714105020205

  • 可以很明显的发现,走了不同的分支;这里的原理是通过找一个不存在的类,以此来产生不同的差异;
  • 来看看产生这个差异的原因,去看unidbg如何实现fiadclass的,位置:DalvikVM64.java
Pointer _FindClass = svcMemory.registerSvc(new Arm64Svc() {
    @Override
    public long handle(Emulator<?> emulator) {
        RegisterContext context = emulator.getContext();
        Pointer env = context.getPointerArg(0);
        Pointer className = context.getPointerArg(1);
        String name = className.getString(0);

        boolean notFound = notFoundClassSet.contains(name);
        if (verbose) {
            if (notFound) {
                System.out.printf("JNIEnv->FindNoClass(%s) was called from %s%n", name, context.getLRPointer());
            } else {
                System.out.printf("JNIEnv->FindClass(%s) was called from %s%n", name, context.getLRPointer());
            }
        }

        if (notFound) {
            throwable = resolveClass("java/lang/NoClassDefFoundError").newObject(name);
            return 0;
        }

        DvmClass dvmClass = resolveClass(name);
        long hash = dvmClass.hashCode() & 0xffffffffL;
        if (log.isDebugEnabled()) {
            log.debug("FindClass env=" + env + ", className=" + name + ", hash=0x" + Long.toHexString(hash));
        }
        return hash;
    }
});
  • 有几个关键点,由于unidbg对于java层是没有感知的,所以我们看不到关于类存不存在的代码,我们发现,不管怎样,他都会执行到DvmClass dvmClass = resolveClass(name),这就是产生差异的根本原因;当然也有其他转机,这里有一个notFoundClassSet,他是一个集合,目的就是规避掉这种检测,使用方式如下:
private void stringFromJNI() {
    vm.addNotFoundClass("com/sana/antifindclass/MainActivity2");
    DvmObject<?> obj = vm.resolveClass("com/sana/antifindclass/MainActivity").newObject(null);
    DvmObject<?> dvmObject = obj.callJniMethodObject(emulator, "stringFromJNI()Ljava/lang/String;");
    Object value = dvmObject.getValue();
    System.out.println(value);
}
  • 再次运行,即可发现输出与真机一致了;
Find native function Java_com_sana_antifindclass_MainActivity_stringFromJNI => RX@0x400244e0[libantifindclass.so]0x244e0
JNIEnv->FindNoClass(com/sana/antifindclass/MainActivity2) was called from RX@0x400246dc[libantifindclass.so]0x246dc
JNIEnv->NewStringUTF("Hello from C++Exception!!") was called from RX@0x40024768[libantifindclass.so]0x24768
Hello from C++Exception!!
  • 还输出了 FindNoClass 这个信息,也算是规避掉了这个检测;

2. Methodid的anti

  • 新建一个项目,创建两个java类,一个Person,一个ZhangSan;
public class Person {

    public String getName() {
        return "Sana";
    }

    public int getAge() {
        return 18;
    }

}

public class ZhangSan extends Person {

    @Override
    public String getName() {
        return "张三";
    }

    @Override
    public int getAge() {
        return 23;
    }
}
  • 随后在MainActivity里进行调用,如下:
public class MainActivity extends AppCompatActivity {

    // Used to load the 'antimethodid' library on application startup.
    static {
        System.loadLibrary("antimethodid");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        ZhangSan zhangSan = new ZhangSan();

        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI(zhangSan));
    }

    public native String stringFromJNI(ZhangSan zhangSan);
}
  • stringFromJNI方法的定义如下:
extern "C" JNIEXPORT jstring JNICALL
Java_com_sana_antimethodid_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */,
        jobject person) {
    jclass Person = env->FindClass("com/sana/antimethodid/Person");
    jmethodID getName = env->GetMethodID(Person, "getName", "()Ljava/lang/String;");

    jobject name = env->CallObjectMethod(person, getName);
    char* name_str = (char*) env->GetStringUTFChars((jstring) name, nullptr);


    return env->NewStringUTF(name_str);
}
  • 真机运行:

image-20250714143348742

  • Unidbg运行:

image-20250714145312190

  • 这是典型的methodid问题,先看看源码怎么回事,分析分析出现这个问题的原因是什么?
  • 首先,我们的调用是这样的:取到Person类,拿到它的方法getName,传进来的是zhangsan类,所以我们最终调用的是张三类下的getName方法,实际上我们是继承的Person类,所以是能找到被重写的这个方法的;依旧回顾到那个话题,unidbg对java环境是没有感知的,他并不知道这组继承关系,说了这么多,看看它是怎么处理的;
@Override
public long handle(Emulator<?> emulator) {
    RegisterContext context = emulator.getContext();
    UnidbgPointer object = context.getPointerArg(1);
    UnidbgPointer jmethodID = context.getPointerArg(2);
    UnidbgPointer va_list = context.getPointerArg(3);
    if (log.isDebugEnabled()) {
        log.debug("CallObjectMethodV object=" + object + ", jmethodID=" + jmethodID + ", va_list=" + va_list + ", lr=" + context.getLRPointer());
    }
    DvmObject<?> dvmObject = getObject(object.toIntPeer());
    DvmClass dvmClass = dvmObject == null ? null : dvmObject.getObjectType();
    DvmMethod dvmMethod = dvmClass == null ? null : dvmClass.getMethod(jmethodID.toIntPeer());
    if (dvmMethod == null) {
        throw new BackendException("dvmObject=" + dvmObject + ", dvmClass=" + dvmClass + ", jmethodID=" + jmethodID);
    } else {
        VaList vaList = new VaList64(emulator, DalvikVM64.this, va_list, dvmMethod);
        DvmObject<?> obj = dvmMethod.callObjectMethodV(dvmObject, vaList);
        if (verbose || verboseMethodOperation) {
            System.out.printf("JNIEnv->CallObjectMethodV(%s, %s(%s) => %s) was called from %s%n", dvmObject, dvmMethod.methodName, vaList.formatArgs(), obj, context.getLRPointer());
        }
        return addLocalObject(obj);
    }
}
final DvmMethod getMethod(int hash) {
    DvmMethod method = methodMap.get(hash);
    if (method == null && superClass != null) {
        method = superClass.getMethod(hash);
    }
    if (method == null) {
        for (DvmClass interfaceClass : interfaceClasses) {
            method = interfaceClass.getMethod(hash);
            if (method != null) {
                break;
            }
        }
    }
    return method;
}
  • 我们发现方法是怎么找的?他是在一个methodMap里取的,那么methodMap在哪里赋值的?
int getMethodID(String methodName, String args) {
    String signature = getClassName() + "->" + methodName + args;
    int hash = signature.hashCode();
    if (log.isDebugEnabled()) {
        log.debug("getMethodID signature=" + signature + ", hash=0x" + Long.toHexString(hash));
    }
    if (vm.jni == null || vm.jni.acceptMethod(this, signature, false)) {
        if (!methodMap.containsKey(hash)) {
            methodMap.put(hash, new DvmMethod(this, methodName, args, false));
        }
        return hash;
    } else {
        return 0;
    }
}
  • 我们调试一下,看看是否是我们demo那样;

image-20250714150736859

  • 确实是将getName这个方法放进去了,这里需要解释,unidbg中,每个类都有一个methodMap,执行时去对应的methodMap里取即可,这里就能够发现端倪了;我们的getName方法放在了Person这个类的methodMap里了,那zhangsan这个类去找肯定就是空了,也就找不到这个方法;

image-20250714151028874

  • 可以发现方法是null,这里的解决之法也就在这个截图里,他下面有一句判断,superClass,也就是父类,同样可以得到getMethod,这里也符合我们出现问题的根本原因,unidbg不知道这个继承关系,所以我们需要做的就是告诉他这组继承关系;
DvmObject<?> obj2 = vm.resolveClass("com/sana/antimethodid/ZhangSan",
                                    vm.resolveClass("com/sana/antimethodid/Person")).newObject(null);
  • 这就是一组继承关系的声明,再次运行发现没有methodid问题了;

image-20250714152345301

  • 这里是anti点之一,引用龙哥的话,大部分新手看到这个错误是没有能力解决的,因此龙哥认为算是一种Anti-Unidbg 的手段,接下来看另一个anti点;
  • 将上面的环境补完,其实就是返回一个字符串,那么正常他的逻辑,应该返回Person类下的getName方法,按照我们常规的逻辑肯定是这样,所以我们补上;

image-20250714153634243

  • 正常来说确实是这样,但是不要忘了,真机不是这样的,它的输出应该是张三才对,这里就是另一个需要注意的点,这里返回的结果可能是错的,而我们对于这个错误却可能毫不知情,解决办法就是打印出对应的dvmObject,看看究竟应该是哪个对象;
@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature){
        case "com/sana/antimethodid/Person->getName()Ljava/lang/String;":{
            System.out.println(dvmObject.toString());
            return new StringObject(vm, "Sana");
        }
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}
  • 查看输出;
com.sana.antimethodid.ZhangSan@7006c658
···
Sana
  • 所以我们应该返回ZhangSan类下的值;
return new StringObject(vm, "张三");
  • 这个算是第二个anti点;

3. 未实现jni方法

  • 找一些未实现的方法,如:

image-20250714155639357

  • 这种调用了就会直接抛异常,也是可以作为一个anti的点;
  • 还有一个字节对齐相关,暂时没有实现;
© 版权声明
THE END
喜欢就支持一下吧
点赞 0 分享 收藏
评论 抢沙发
OωO
取消