补环境需要详细的调试信息,建议开启详细日志:vm.setVerbose(true)

系统库函数依赖

扩展点:SyscallHandler.java
eg:

//替换默认处理器
AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(true) {  
    @Override  
    public AndroidEmulator build() {  
        return new AndroidARM64Emulator(processName, rootDir, backendFactories) {  
            @Override  
            protected UnixSyscallHandler<AndroidFileIO> createSyscallHandler(SvcMemory svcMemory) {  
                return new MySyscallHandler(svcMemory);//MySyscallHandler为自定义类
            }  
        };  
    }  
};  
builder.setProcessName("进程名");  
emulator = builder.build();

....

public class MySyscallHandler extends ARM64SyscallHandler {  
    public MySyscallHandler(SvcMemory svcMemory) {  
        super(svcMemory);  
        setVerbose(true); // 可按需开启详细日志  
    }  
    // 在这里重写或添加你的处理逻辑  
    
    ......
    
}  

补JNI,Java层的调用

继承:AbstractJni.java
eg:

...//创建DalvikVM实例之后
vm.setjni(this)
...//后面继承与重写建议在同路径下新建JAVA文件再写
//xxxx可以是`Object`, `Boolean`, `Int`, `Void` 等,根据报错判断
public class MyJNI extends AbstractJni{
	@Override//模拟静态方法
	public DvmObject<?> callStaticxxxxxxMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {  
            switch (signature) {  
                case "com/example/MyClass->getAppContext()Landroid/content/Context;":  
                    // 返回一个模拟的 Context 对象  
                    return vm.resolveClass("android/content/Context").newObject(null);  
                // 可以添加更多 case 来处理其他需要补全的 Java 方法  
                default:  
                    // 对于未处理的方法,务必调用父类的默认实现  
                    return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);  
            }  
        }
        @Override  //模拟构造方法
	public DvmObject<?> newObejectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {  
	    switch (signature) {  
	        case "java/util/HashMap-><init>()V": {  
	            return ProxyDvmObject.createObject(vm, new HashMap<>());  
	        }  
  
	        case "com/zj/wuaipojie/ui/ChallengeTen$UserInfo-><init>(Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Lcom/zj/wuaipojie/ui/ChallengeTen$AccountStatus;Ljava/util/Map;)V": {  
	            System.out.println("【补环境 Level 3】拦截到 UserInfo 构造方法");  
	            Map<String, DvmObject<?>> userInfoData = new HashMap<>();  
	            userInfoData.put("status", vaList.getObjectArg(4));  
	            userInfoData.put("properties", vaList.getObjectArg(5));  
	            return dvmClass.newObject(userInfoData);  
	        }  
	    }  
	    return super.newObjectV(vm, dvmClass, signature, vaList);  
	}
	@Override  //模拟字段访问
	public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {  
	    // 匹配枚举类的 PREMIUM 静态字段  
	    if ("com/zj/wuaipojie/ui/ChallengeTen$AccountStatus->PREMIUM:Lcom/zj/wuaipojie/ui/ChallengeTen$AccountStatus;".equals(signature)) {  
	        System.out.println("【补环境】拦截到获取 AccountStatus.PREMIUM 静态字段");  
	        // 创建一个枚举实例(用字符串"PREMIUM"作为其值,方便后续name()方法返回正确结果)  
	        DvmObject<?> premium = dvmClass.newObject("PREMIUM");  
	        return premium;  
	    }  
	    return super.getStaticObjectField(vm, dvmClass, signature);  
	}  
  
	@Override  
	public void setIntField(BaseVM vm, DvmObject<?> dvmObject, String signature, int value) {  
	    // signature的格式是:com/example/User->age:I  
	    if ("com/example/User->age:I".equals(signature)) {  
	        System.out.println("SO 正在设置 User 对象的 age 字段,值为: " + value);  
	        // 你可以在这里记录值,或者什么都不做  
	        return; // 注意set方法是void返回  
	    }  
	    super.setIntField(vm, dvmObject, signature, value);  
	}
}

补文件访问

unidbg 有责任机制:优先是用户定义的文件处理器 ->unidbg 默认文件处理器
添加文件处理器(在模拟执行之前):emulator.getSyscallHandler().addIOResolver()
1、继承:IOResolver.java

emulator.getSyscallHandler().addIOResolver(this);//注册IOResolver

......

public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {
	System.out.println("[IOResolver] Intercepted file access -> Path: '" + pathname + "', Flags: " + oflags);
	switch(pathname){
		case "xxxx":
			return FileResult.success(new ByteArrayFileIO(oflags, pathname, 字符串.getBytes()))//动态文件,只能读
		case "xxxxxx":
			return  FileResult.success(new SimpleFileIO(oflags, new File("unidbg-android/src/test/resources/cpu/boot_id"), pathname));//物理文件,可读写
		case "xxxxx":
			return FileResult.failed(UnixEmulator.EACCES);//访问失败
	}
}

2、rootDir:

emulator = AndroidEmulatorBuilder
                .for64Bit()
                //虚拟目录
                .setRootDir(new File("unidbg-android/src/test/resources/FileDemo/VFS"))
                .build();//创建仿真器的时候创建根目录
//直接操作VFS文件夹,so产生的所有文件访问最后兜底的就是这个文件系统,

优先级:IOResolver > rootDir,均属于 VFS 系统。可以同时使用。

如何看报错

判断缺失数据类型:

signature 的组成一般是:(类路径)->(字段名):(字段类型签名);
例如com/zj/wuaipojie/util/SecurityUtil$Config->deviceId:Ljava/lang/String;:
com/zj/wuaipojie/util/SecurityUtil$Config: 表示在 config 这个子类中;
deviceId: 表示缺的是 config 类中的 deviceId 成员变量;
Ljava/lang/String;: 表示这个变量的类型是一个字符串。
由此可知我们在补环境时,补的是一个字符串变量。

一个较完整补环境模板

MainActivity.java:

package com.example;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.linux.android.AndroidARM64Emulator;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.linux.file.SimpleFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.unix.UnixEmulator;
import com.github.unidbg.unix.UnixSyscallHandler;
import com.example.MySyscallHandler;
import net.dongliu.apk.parser.Main;

import java.io.File;
import java.io.FileNotFoundException;

public class MainActivity extends AbstractJni implements IOResolver<AndroidFileIO> {

    private final  AndroidEmulator emulator ;
    private final  VM vm;
    private final Module module;

    public MainActivity(){
        //替换默认处理器
        AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(true) {
            @Override
            public AndroidEmulator build() {
                return new AndroidARM64Emulator(processName, rootDir, backendFactories) {
                    @Override
                    protected UnixSyscallHandler<AndroidFileIO> createSyscallHandler(SvcMemory svcMemory) {
                        return new MySyscallHandler(svcMemory);
                    }
                };
            }
        };
        builder.setProcessName("进程名");
        emulator = builder.setRootDir(new File("unidbg/unidbg-android/src/test/resources/example")).build();//创建模拟器,设置模拟文件系统(补文件访问环境)
        emulator.getSyscallHandler().setVerbose(true);//开启详细日志
        emulator.getSyscallHandler().addIOResolver(this);//注册IOResolver
        final Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM();

        MyJni myjni=new MyJni(vm);
        vm.setJni(myjni);
        vm.setVerbose(true);

        File soFile = new File("unidbg-android/src/test/so路径");
        DalvikModule dm = vm.loadLibrary(soFile, true);
        module = dm.getModule();
        dm.callJNI_OnLoad(emulator);
    }

    //补文件访问
    @Override
    public FileResult<AndroidFileIO> resolve(Emulator<AndroidFileIO> emulator, String pathname, int oflags) {
        // 无论是否处理,都打印出来,这是发现未知文件访问的关键。
        System.out.println("[IOResolver] Intercepted file access -> Path: '" + pathname + "', Flags: " + oflags);
        switch(pathname){
            case "xxxxxxx":{
                String statusContent="xxxxxxxxxxxx";
                return FileResult.success(new ByteArrayFileIO(oflags, pathname, statusContent.getBytes()));//成功返回,动态,仅可读,不可写
            }
            case "xxxxxx": {
                //.......
                return FileResult.success(new SimpleFileIO(oflags, new File("unidbg-android/src/test/resources/cpu/boot_id"), pathname));//物理文件,可读写
            }
            case "xxxxx": {
                //........
                return FileResult.failed(UnixEmulator.EACCES);//访问失败,权限不足
            }

        }
        return null;//return null表示交给下一个处理器
    }

    public void run(){
        DvmClass securityUtilClass = vm.resolveClass("");
        StringObject result = securityUtilClass.callStaticJniMethodObject(emulator, "");
    }

    public static void main(String[] args) throws FileNotFoundException {
        MainActivity myinstance=new MainActivity();
        myinstance.run();
    }
}

MyJni.java:

package com.example;

import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.VM;

public class MyJni extends AbstractJni {
    private final VM vm;
    public MyJni(VM vm) {
        this.vm=vm;
        super();
    }
//    补JNI环境
//    @Override
//    public .......

}

MySyscallHandler:

package com.example;

import com.github.unidbg.Emulator;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.linux.ARM64SyscallHandler;
import com.github.unidbg.memory.SvcMemory;
import java.util.Random;


public class MySyscallHandler extends ARM64SyscallHandler{
    private final Random rng = new Random();

    public MySyscallHandler(SvcMemory svcMemory) {
        super(svcMemory);
        setVerbose(true); // 可按需开启详细日志
    }
    @Override
    protected boolean handleUnknownSyscall(Emulator<?> emulator, int NR) {
        System.err.println(">>> MySyscallHandler is processing syscall NR = " + NR);
        Backend backend = emulator.getBackend();
        switch (NR) {
            case 1: {
                //To Do
            }
            case 2: {
                //To do
            }

        }
        return super.handleUnknownSyscall(emulator, NR);
    }
}

学习资源:《安卓逆向这档事》