An analysis of the encryption of the communication protocol of a car networking app (Part 2) Unidbg step by step
1. Objectives
It has been a while since I wrote an article related to unidbg. This sample is quite suitable, with moderate difficulty and a small pit for you. So the following is a series of articles, including unidbg environment supplement, Trace Block comparison process, Trace Code location difference. Mastering this series of routines, Native analysis can be considered an entry.
This time, let’s run so with unidbg
v6.1.0
2. Steps
Dump so
IDA openslibencrypt.soGo to the offset addresses 0x24424 and 0x2B1BC corresponding to the two functions checkcode and decheckcode we want to analyze. We will find a strange problem that there is no assembly code at these two addresses, both are 0x00.
I guess the shell is playing tricks on us. It extracts part of the code of these two key functions and fills it back when running, which prevents you from statically analyzing the so.
However, no matter how the shell is added, the complete code must exist in the memory when running, otherwise the App will not run.
So we also added drama, Dump
function dumpSo(){
var libxx = Process.getModuleByName("libencrypt.so");
console.log("*****************************************************");
console.log(TAG + "name: " +libxx.name);
console.log(TAG + "base: " +libxx.base);
console.log(TAG + "size: " +ptr(libxx.size));
var file_path = "/data/data/com.xxx.aeri.caranywhere/" + libxx.name + "_" + libxx.base + "_" + ptr(libxx.size) + ".so";
console.log(TAG + file_path);
var file_handle = new File(file_path, "wb");
if (file_handle && file_handle != null) {
Memory.protect(ptr(libxx.base), libxx.size, 'rwx');
var libso_buffer = ptr(libxx.base).readByteArray(libxx.size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log(TAG + "[dump]:", file_path);
}
}
unidbg run so basic framework
After dumping the complete code of so, we start to build the basic framework of unidbg run so. The latest unidbg library code can be downloaded from the original author’s github.
public class CaranywhereDemo extends AbstractJni {
public AndroidEmulator emulator;
public VM vm;
public Module module;
public DvmClass dvmClass;
public static void main(String[] args) throws DecoderException, IOException {
String apkPath = "/Users/h1yx/Desktop/xxx/6.1.0.apk";
CaranywhereDemo carObj = new CaranywhereDemo(apkPath);
carObj.destroy();
}
public CaranywhereDemo(String apkFilePath) throws DecoderException, IOException {
//*
emulator = AndroidEmulatorBuilder.for64Bit()
.setProcessName("com.xxx.aeri.caranywhere")
.addBackendFactory(new Unicorn2Factory(true))
.build();
//*/
emulator.getSyscallHandler().setEnableThreadDispatcher(false);
final Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File(apkFilePath));
vm.setJni(this);
vm.setVerbose(true);
new JniGraphics(emulator, vm).register(memory);
new AndroidModule(emulator, vm).register(memory);
dvmClass = vm.resolveClass("com/bangcle/comapiprotect/CheckCodeUtil");
DalvikModule dm = vm.loadLibrary("encrypt", false);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
private void destroy() {
try {
emulator.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
This is the Hello World of unidbg. Don’t rush to run it. As we said before, the key code of so has been extracted, so we can’t run so directly. We have to replace it with the result of our dump.
// DalvikModule dm = vm.loadLibrary("encrypt", false);
DalvikModule dm = vm.loadLibrary(new File("/Users/h1yx/Desktop/work/blogCode/xxx/libencrypt.so_0x7634ee7000_0x1d6000.so"), false);
Step by step to complete the unidbg run so environment
Let’s look at the first error first.
[07:17:09 068] WARN [com.github.unidbg.linux.ARM64SyscallHandler] (ARM64SyscallHandler:384) - handleInterrupt intno=2, NR=30, svcNumber=0x16e, PC=unidbg@0xfffe0774, LR=RX@0x40018c9c[libencrypt.so]0x18c9c, syscall=null
java.lang.UnsupportedOperationException: android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:432)
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:421)
at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticObjectMethod(DvmMethod.java:59)
at com.github.unidbg.linux.android.dvm.DalvikVM64$111.handle(DalvikVM64.java:1723)
at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:130)
The error message of unidbg is very clear, indicating that the static method currentActivityThread of the ActivityThread class is called in callStaticObjectMethod, and the return value is of ActivityThread type.
We overload the callStaticObjectMethod function in CaranywhereDemo.java to solve this problem:
@Override
public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":
return vm.resolveClass("android/app/ActivityThread").newObject(null);
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
We don’t care what this currentActivityThread is going to be used for, just return an empty class.
Keep running, the next error is still in callStaticObjectMethod, it seems to be getting some system information
java.lang.UnsupportedOperationException: android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:432)
at com.h1yx.test.CaranywhereDemo.callStaticObjectMethod(CaranywhereDemo.java:78)
at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:421)
at com.github.unidbg.linux.android.dvm.DvmMethod.callStaticObjectMethod(DvmMethod.java:59)
at com.github.unidbg.linux.android.dvm.DalvikVM64$111.handle(DalvikVM64.java:1723)
at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:130)
In this case, the function with input parameters can simply return an empty string, but people should print out the input parameters first.
@Override
public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":
System.out.println("android/os/SystemProperties->get params:" + varArg.formatArgs());
return new StringObject(vm, "705KPGS001091");
case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":
return vm.resolveClass("android/app/ActivityThread").newObject(null);
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
Tip:
The unidbg library function formatArgs prompts that it is not a public function. Change it to public final String formatArgs() in the VarArg class.
Output
android/os/SystemProperties->get params:"ro.serialno", "unknown"
It turns out that the purpose is to obtain the Android serial number, so just make one up at random. Since this sample is only called once, I am too lazy to judge the input parameter.
Tip:
adb shell getprop ro.serialno can get the Android serial number
This error is similar to the first one, but it is in the callObjectMethod function.
java.lang.UnsupportedOperationException: android/app/ActivityThread->getSystemContext()Landroid/app/ContextImpl;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:921)
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:855)
at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethod(DvmMethod.java:74)
at com.github.unidbg.linux.android.dvm.DalvikVM64$31.handle(DalvikVM64.java:504)
at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:130)
Overload the callObjectMethod function and return the ContextImpl type directly
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "android/app/ActivityThread->getSystemContext()Landroid/app/ContextImpl;":
return vm.resolveClass("android/app/ContextImpl").newObject(null);
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}
Continue to report errors, this is to obtain the package management class
java.lang.UnsupportedOperationException: android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:921)
at com.h1yx.test.CaranywhereDemo.callObjectMethod(CaranywhereDemo.java:91)
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:855)
at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethod(DvmMethod.java:74)
at com.github.unidbg.linux.android.dvm.DalvikVM64$31.handle(DalvikVM64.java:504)
at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:130)
callObjectMethod Constructs the PackageManager class and returns
case "android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;":
return vm.resolveClass("android/content/pm/PackageManager").newObject(null);
Next error.
java.lang.UnsupportedOperationException: android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:921)
at com.h1yx.test.CaranywhereDemo.callObjectMethod(CaranywhereDemo.java:93)
at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:855)
at com.github.unidbg.linux.android.dvm.DvmMethod.callObjectMethod(DvmMethod.java:74)
This function has parameters. As usual, print out the parameters.
case "android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":
System.out.println("android/app/ContextImpl->getSystemService params:" + varArg.formatArgs());
return vm.resolveClass("java/lang/Object").newObject(null);
The parameters printed out are
android/app/ContextImpl->getSystemService :"wifi"
I guess it’s wifi related. Let’s return an empty Object first.
The following two errors are related to obtaining the wifi Mac address.
case "android/net/wifi/WifiInfo->getMacAddress()Ljava/lang/String;":
return new StringObject(vm, "00:00:00:00:00:00");
case "java/lang/Object->getConnectionInfo()Landroid/net/wifi/WifiInfo;":
return vm.resolveClass("android/net/wifi/WifiInfo").newObject(null);
Now that we have finally gotten JNI_OnLoad working, we can have a drink.
call checkcode
After a long time, we started to get to the point. Let’s call checkcode
public void callA() {
String strA = "F{\"appInnerVersion\":\"125\",\"appOutVersion\":\"6.1.0\",\"deviceType\":0,\"imeiMD5\":\"EE6431DEBB1E02FE469FA5E8467CD693\",\"mobileModel\":\"GOOGLE PIXEL 2 XL\",\"softType\":\"0\"}";
String strC = "1662109202156";
String methodName = "checkcode(Ljava/lang/String;ILjava/lang/String;)Ljava/lang/String;";
DvmObject ret = dvmClass.callStaticJniMethodObject(emulator, methodName,strA,1,strC);
String strOut = (String)ret.getValue();
System.out.println("call checkcode: " + strOut);
}
There is a new error
java.lang.UnsupportedOperationException: android/os/Build->MODEL:Ljava/lang/String;
at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)
at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:53)
at com.github.unidbg.linux.android.dvm.DvmField.getStaticObjectField(DvmField.java:106)
at com.github.unidbg.linux.android.dvm.DalvikVM64$142.handle(DalvikVM64.java:2228)
This time we need to overload the getStaticObjectField class
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature){
case "android/os/Build$VERSION->SDK:Ljava/lang/String;":
return new StringObject(vm, "23");
case "android/os/Build->MANUFACTURER:Ljava/lang/String;":
return new StringObject(vm, "Google");
case "android/os/Build->MODEL:Ljava/lang/String;":
return new StringObject(vm, "pixel");
}
return super.getStaticObjectField(vm,dvmClass,signature);
}
These values are relatively simple, all of string type, so we assign them a value.
Yeah, it’s done.
call checkcode: FDAEKCAcOAQcNBgkEAwoCDQgEDQ4JBAgAAA4ODgcJBgkKBwYPBwwEBw4LBwsODQcFDQMMCAUJDAcEDQsADwEDDAIGDgQJAQYNDggNDQsCAQcNAwwIBQkMBwQNCwAPAQMMDQYCDAgBBQwGBAUIAwULBAoHBg8HDAQHDgsHCw4NBwUPAwEADAkPBQcODAcDDgYCDwMJCQUEAAgHDAUIBwEDBwMKDgcGBg4NDAgLBAAEAw8PAwEADAkPBQcODAcDDgYCCAQNDgkECAAADg4OBwkGCQIIAwkLBgACCgoGAgcCAwEMAQoIBw4BBw0GCQQDCgINDwMBAAwJDwUHDgwHAw4GAggHBwEMBAAAAwMJDQUDDQECBg4ECQEGDQ4IDQ0LAgEHDw0ADwMJDgQICAsJAgILBw8NAA8DCQ4ECAgLCQICCwcNBgIMCAEFDAYEBQgDBQsEAgYOBAkBBg0OCA0NCwIBBw==
Don’t be too happy yet, this result seems a bit wrong no matter how you look at it, and it is quite different from the result of our hook.
How do I determine whether the result is right or wrong? How do I compare it with the app to get the correct result?Wait for the next Trace Block and Trace Code tutorial.
Conclusion
The unidbg environment actually tests your Android programming ability.
Google the keyword unidbg + error message, and you will usually find someone who has experienced the same problem.
What? You can’t open Google? Is it too late for me to persuade you to change your career?