搭架子
代码语言:javascript复制public class Main implements IOResolver {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
Main(){
// 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
emulator = AndroidEmulatorBuilder.for32Bit().setRootDir(new File("target/rootfs")).setProcessName("com.ashenone.demo").build();
// emulator.getSyscallHandler().addIOResolver(this);
// 获取模拟器的内存操作接口
Memory memory = emulator.getMemory();
// 设置系统类库解析
memory.setLibraryResolver(new AndroidResolver(23));
// 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
vm = emulator.createDalvikVM(new File("apkpath"));
// vm.setDvmClassFactory(new ProxyClassFactory());
vm.setJni(new MyJni());
//开启log
vm.setVerbose(true);
//最好调整一下log级别
Logger.getLogger("com.github.unidbg.linux.ARM32SyscallHandler").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.unix.UnixSyscallHandler").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.AbstractEmulator").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm.DalvikVM").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm.BaseVM").setLevel(Level.DEBUG);
Logger.getLogger("com.github.unidbg.linux.android.dvm").setLevel(Level.DEBUG);
// 加载目标SO
DalvikModule dm = vm.loadLibrary(new File("elfpath"), true);
//获取本SO模块的句柄,后续需要用它
module = dm.getModule();
//调用JNI OnLoad
dm.callJNI_OnLoad(emulator);
}
}
补环境
Jni
代码语言:javascript复制public class MyJni extends AbstractJni {
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "android/os/Environment->getExternalStorageDirectory()Ljava/io/File;":
return new StringObject(vm,"/sdcard/");
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature){
case "java/lang/String->getAbsolutePath()Ljava/lang/String;":
return new StringObject(vm,(String) dvmObject.getValue());
}
return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}
}
主动调用
Java层
static函数
代码语言:javascript复制public String y2(boolean b1, String configData,boolean b2,String AinfoKey) {
DvmClass clz = vm.resolveClass("com/ashenone/demo/MyClass");
String methodSign = "y2(ZLjava/lang/String;ZLjava/lang/String;)Ljava/lang/String;";
StringObject obj = clz.callStaticJniMethodObject(emulator, methodSign, b1, configData,b2,AinfoKey);
return obj.getValue();
}
native层
通过地址调用JNI函数
代码语言:javascript复制public String _y2(boolean b1, String configData,boolean b2,String AinfoKey){
// args list
List<Object> list = new ArrayList<>(10);
// arg1 env
list.add(vm.getJNIEnv());
// arg2 jobject/jclazz 一般用不到,直接填0
list.add(0);
list.add(vm.addLocalObject(DvmBoolean.valueOf(vm,b1)));
list.add(vm.addLocalObject(new StringObject(vm,configData)));
list.add(vm.addLocalObject(DvmBoolean.valueOf(vm,b2)));
list.add(vm.addLocalObject(new StringObject(vm,AinfoKey)));
// 参数准备完成
// call function
Number number = module.callFunction(emulator,
0x3f7ad,
list.toArray());
String result = vm.getObject(number.intValue()).getValue().toString();
return result;
}
通过地址调用C函数
代码语言:javascript复制public String decodeString(String input){
MemoryBlock block=memory.malloc(input.length(),false);
UnidbgPointer str_ptr=block.getPointer();
str_ptr.write(input.getBytes());
String content= str_ptr.getString(0);
System.out.println("decodeString:" str_ptr.getString(0));
Number number = module.callFunction(emulator,
0x79ec 1,
str_ptr);
UnidbgPointer result = memory.pointer(number.longValue());
return result.getString(0);
}
参数构造
通过list来存放每个参数,最后调用toArray()传入callFunction中,但是各种类型的参数在传入list之前需要进行包装
Env
代码语言:javascript复制vm.getJNIEnv()
String
代码语言:javascript复制vm.addLocalObject(new StringObject(vm,"Hello"))
Boolean
代码语言:javascript复制vm.addLocalObject(DvmBoolean.valueOf(vm,true))
Context
代码语言:javascript复制DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);// context
list.add(vm.addLocalObject(context));
byte[]
代码语言:javascript复制ByteArray inputByteArray = new ByteArray(vm,inputByte);
list.add(vm.addLocalObject(inputByteArray));
Patch
直接写bytecode
代码语言:javascript复制int patchCode = 0x4FF00100;
emulator.getMemory().pointer(module.base offset1).setInt(offset2,patchCode);
或者用Keystone,参考SO逆向入门实战教程二:calculateS
代码语言:javascript复制Pointer pointer = UnidbgPointer.pointer(emulator, module.base offset);
assert pointer != null;
byte[] code = pointer.getByteArray(0, 4);
if (!Arrays.equals(code, new byte[]{ (byte)0xFF, (byte) 0xF7, (byte) 0xEB, (byte) 0xFE })) { // BL sub_1C60
throw new IllegalStateException(Inspector.inspectString(code, "patch32 code=" Arrays.toString(code)));
}
try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) {
KeystoneEncoded encoded = keystone.assemble("mov r0,1");
byte[] patch = encoded.getMachineCode();
if (patch.length != code.length) {
throw new IllegalStateException(Inspector.inspectString(patch, "patch32 length=" patch.length));
}
pointer.write(0, patch, 0, patch.length);
}
Hook
有多种Hook框架,只看HookZz
代码语言:javascript复制public void HookMDStringold(){
// 加载HookZz
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.wrap(module.base offset, new WrapCallback<HookZzArm32RegisterContext>() { // inline wrap导出函数
@Override
// 类似于 frida onEnter
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
// 类似于Frida args[0]
Pointer input = ctx.getPointerArg(0);
System.out.println("input:" input.getString(0));
};
@Override
// 类似于 frida onLeave
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
Pointer result = ctx.getPointerArg(0);
System.out.println("input:" result.getString(0));
}
});
}
trace
代码语言:javascript复制//trace指令执行
emulator.traceCode(long begin, long end);
//trace内存读写
emulator.traceRead(long begin, long end);
emulator.traceWrite(long begin, long end);