一、目標
有一段時間沒有寫unidbg相關的文章了,這個樣本挺合適,難度適中,還適當給你挖個小坑。所以后面是一個系列文章,包含 unidbg補環境,Trace Block 對比流程,Trace Code定位差異。掌握好這一系列套路,Native分析可以算入門了。
這次先來把so用unidbg跑通
v6.1.0
二、步驟
Dump so
IDA打開 libencrypt.so 去到我們要分析的兩個函數 checkcode 和 decheckcode 對應的偏移地址 0x24424 , 0x2B1BC 。會發現一個奇怪的問題,這兩個地址上沒有匯編代碼,都是 0x00。
估計是殼給我們加戲了,他把這兩個關鍵函數的部分代碼給抽取了,等到運行的時候才會補回去,這樣就阻礙你去靜態分析這個so。
不過殼怎么加戲,運行的時候是一定會在內存中存在完整的代碼的,否則App是跑不起來的。
所以我們也加戲,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 基本框架
dump出so的完整代碼了,我們開始撘unidbg run so的基本框架。unidbg的庫代碼可以從原作者的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/fenfei/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(); // 創建模擬器實例,要模擬32位或者64位,在這里區分
//*/
// 多線程處理 true的情況下 在 faccessat 的時候就卡死, 所以關掉, 這個樣本暫時也不需要多線程
emulator.getSyscallHandler().setEnableThreadDispatcher(false);
final Memory memory = emulator.getMemory(); // 模擬器的內存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 設置系統類庫解析
vm = emulator.createDalvikVM(new File(apkFilePath)); // 創建Android虛擬機
vm.setJni(this);
vm.setVerbose(true); // 設置是否打印Jni調用細節
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();
}
}
}
一個unidbg的Hello World就是這樣,先別著急跑它,之前我們說過,so的關鍵代碼被抽取了,所以不能直接跑so。得換成我們dump出來的結果。
// DalvikModule dm = vm.loadLibrary("encrypt", false);
DalvikModule dm = vm.loadLibrary(new File("/Users/fenfei/Desktop/work/blogCode/xxx/libencrypt.so_0x7634ee7000_0x1d6000.so"), false);
一步一步補unidbg run so環境
先看第一個錯誤
[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)
unidbg的報錯提示非常清晰,說明在 callStaticObjectMethod 中調用 ActivityThread 類的靜態方法 currentActivityThread,并且返回值是 ActivityThread 類型。
我們在CaranywhereDemo.java中重載 callStaticObjectMethod 函數來解決這個問題:
@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);
}
我們先不關心這個 currentActivityThread 要被做什么用,直接返回一個空的類就行。
繼續跑,下一個錯誤還是在 callStaticObjectMethod 里面,看上去像是獲取 一些系統信息
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.fenfei.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)
補這中有入參的函數可以簡單粗暴的給他返回一個空字符串,但是講究人先要把他的入參打印出來。
@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 入參:" + 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:
unidbg庫函數formatArgs提示不是公有函數,在VarArg類里面改下 public final String formatArgs()
輸出
android/os/SystemProperties->get 入參:"ro.serialno", "unknown"
原來是為了獲取Android序列號,隨便給他編一個就好, 由于本樣本只調用一次,所以就懶得判斷入參了。
Tip:
adb shell getprop ro.serialno 可以獲取到Android序列號
這個報錯和第一個報錯類似,不過它是在 callObjectMethod 函數里面
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)
重載 callObjectMethod 函數,然后 直接返回 ContextImpl 類型就行
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);
}
繼續報錯,這個是獲取包管理類
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.fenfei.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 構造 PackageManager類返回
case "android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;":
return vm.resolveClass("android/content/pm/PackageManager").newObject(null);
下一個報錯。
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.fenfei.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)
這個函數有參數,老規矩,打印下參數看看。
case "android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":
System.out.println("android/app/ContextImpl->getSystemService 入參:" + varArg.formatArgs());
return vm.resolveClass("java/lang/Object").newObject(null);
參數打印出來是
android/app/ContextImpl->getSystemService 入參:"wifi"
估摸是wifi相關,先給他返回一個空的 Object再說
后面兩個報錯就是獲取wifi Mac地址相關的,直接補上
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);
到這里終于把 JNI_OnLoad 給跑通了, 可以干一杯了。
call checkcode
搞了半天才開始進入正題,我們來調用 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);
}
又有新的報錯了
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)
這次要重載 getStaticObjectField 類了
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);
}
這幾個值比較簡單,都是字符串型,我們給他賦值一把。
耶,大功告成了
call checkcode: FDAEKCAcOAQcNBgkEAwoCDQgEDQ4JBAgAAA4ODgcJBgkKBwYPBwwEBw4LBwsODQcFDQMMCAUJDAcEDQsADwEDDAIGDgQJAQYNDggNDQsCAQcNAwwIBQkMBwQNCwAPAQMMDQYCDAgBBQwGBAUIAwULBAoHBg8HDAQHDgsHCw4NBwUPAwEADAkPBQcODAcDDgYCDwMJCQUEAAgHDAUIBwEDBwMKDgcGBg4NDAgLBAAEAw8PAwEADAkPBQcODAcDDgYCCAQNDgkECAAADg4OBwkGCQIIAwkLBgACCgoGAgcCAwEMAQoIBw4BBw0GCQQDCgINDwMBAAwJDwUHDgwHAw4GAggHBwEMBAAAAwMJDQUDDQECBg4ECQEGDQ4IDQ0LAgEHDw0ADwMJDgQICAsJAgILBw8NAA8DCQ4ECAgLCQICCwcNBgIMCAEFDAYEBQgDBQsEAgYOBAkBBg0OCA0NCwIBBw==
先別高興的太早了,這個結果怎么看都有點不對勁,和我們hook的結果相差有點大。
怎么判斷結果是對是錯?怎么和app對比來拿到正確的結果? 等待下次的 Trace Block 和 Trace Code 教程吧。
三、總結
unidbg補環境實際是考驗你的Android編程能力。
谷歌一下關鍵字 unidbg + 報錯信息,一般都有同道趟過坑。
什么?你打不開谷歌?我現在勸你改行還來得及嗎?
1:ffshow
好味止園葵,大歡止稚子。平生不止酒,止酒情無喜。