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分别跑起来这个方法;
- 真机:


- 可以很明显的发现,走了不同的分支;这里的原理是通过找一个不存在的类,以此来产生不同的差异;
- 来看看产生这个差异的原因,去看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;
}
}
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);
}
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);
}


- 这是典型的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;
}
}

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

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

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

- 正常来说确实是这样,但是不要忘了,真机不是这样的,它的输出应该是张三才对,这里就是另一个需要注意的点,这里返回的结果可能是错的,而我们对于这个错误却可能毫不知情,解决办法就是打印出对应的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
return new StringObject(vm, "张三");
3. 未实现jni方法

- 这种调用了就会直接抛异常,也是可以作为一个anti的点;
- 还有一个字节对齐相关,暂时没有实现;