kuaidui作業(yè)sign逆向及unidbg實現(xiàn)
Java層
apk用frida_dump
脫殼后不见,重新打包,jadx搜索sign=
nativeGetSign
這個名字就值得點進(jìn)去看看
com.zuoyebang.baseutil.a.b
com.zuoyebang.baseutil.NativeHelper.
nativeGetSign
找到函數(shù)了程癌,hook驗證一下
android hooking watch class_method com.zuoyebang.baseutil.NativeHelper.nativeGetSign --dump-args --dum-return
輸入是一個base64字符串纷妆,解碼后是請求參數(shù)做個拼接亦镶。
接下來找找函數(shù)在哪個so里面摹芙,nativeInitBaseUtil
看這函數(shù)名灼狰,應(yīng)該是初始化的,查看用例
com.zuoyebang.baseutil.a.a
看來應(yīng)該是在libbaseutil.so
so層
ida查看JNI_OnLoad
幾個函數(shù)都找到了
看看CRYMd5
hook一下CRYMd5
function hook_md5() {
var bptr = Module.findBaseAddress("libbaseutil.so");
Interceptor.attach(bptr.add(0x2ae8), {
onEnter: function(args) {
console.log("md5-arg0: ", args[0].readCString());
},
onLeave: function(retval) {
console.log("md5-ret:", retval.readCString(32));
}
})
}
cyberchef驗證是不是標(biāo)準(zhǔn)MD5
多請求幾次浮禾,發(fā)現(xiàn)objSpamServer.random_number
伏嗜,也就是cdAgblSOFM
不變坛悉,說明它是個相對固定的值。
卸載app重裝后承绸,發(fā)現(xiàn)還是不變,說明和會話無關(guān)挣轨。
更換設(shè)備后军熏,發(fā)現(xiàn)值改變了,說明該值和設(shè)備相關(guān)卷扮。
一個比較取巧也比較無奈的辦法就是建立設(shè)備號和字符串的映射表荡澎,計算sign時選用對應(yīng)的字符串即可。
unidbg實現(xiàn)
由于app只提供了64位的so晤锹,所以此次運行的是64位so摩幔。依舊是先搭個框架
public class Kuaidui extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public static String pkgName = "com.kuaiduizuoye.scan";
public static String apkPath = "unidbg-android/src/test/java/com/kuaidui/kuaidui540.apk";
public static String soPath = "";
public Kuaidui() {
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName(pkgName).build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File(apkPath));
vm.setJni(this);
vm.setVerbose(true);
new AndroidModule(emulator, vm).register(memory);
DalvikModule dm = vm.loadLibrary("baseutil", true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
public static void main(String[] args) {
Kuaidui test = new Kuaidui();
}
}
罕見的沒有報錯,那就開始調(diào)用鞭铆。
public void call_sign() {
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(vm.addLocalObject(new StringObject(vm, "everhu")));
Number ret = module.callFunction(emulator, 0x1500, list.toArray());
System.out.println(vm.getObject(ret.intValue()).getValue());
}
public static void main(String[] args) {
Kuaidui test = new Kuaidui();
test.call_sign();
}
調(diào)用出結(jié)果了或衡,只是不是正確結(jié)果。說明環(huán)境不對车遂,可能有些參數(shù)沒有設(shè)置封断。
native函數(shù)里面應(yīng)該有設(shè)置環(huán)境的函數(shù),hook看看哪個先被調(diào)用了舶担。
objection.exe -g com.kuaiduizuoye.scan explore --startup-command="android hooking watch class com.zuoyebang.baseutil.NativeHelper"
可以看到nativeSetToken
被調(diào)用了坡疼,再hook看看入?yún)?/p>
objection -g com.kuaiduizuoye.scan explore --startup-command="android hooking watch class_method com.zuoyebang.baseutil.NativeHelper.nativeSetToken --dump-args --dump-return"
在unidbg補(bǔ)上。
public void call_token() {
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(vm.addLocalObject(vm.resolveClass("android/content/Context").newObject(null)));
String devid = "F5D53AD5A66144B57783C7C67611F0F7|0";
list.add(vm.addLocalObject(new StringObject(vm, devid)));
String request = "03090b0106000807080e00080201090708060f00040700020d0503070c0606000c01000a080e07050808020d00060b0d070f0f080306060b00000f0d04070d0d02070b0f020a01010c0d030a09080e090b040f02080c0b040c0e0e060c0d0201020f0e02030d0107020d000e0e02090401070505030a0c0605080303040e0803020c020e0d020408010b030e0b090f060302000e0f0902010706040c00080d0e060d000f0805040b0e07000d020b0f07";
list.add(vm.addLocalObject(new StringObject(vm, request)));
String response = "0a040609080d020b0a03090e010306050d0d050c0c060207070105010b0b01090801000a020d0c0b03030209030e0d0a";
list.add(vm.addLocalObject(new StringObject(vm, response)));
Number ret = module.callFunction(emulator, 0x1264, list.toArray());
}
public static void main(String[] args) {
Kuaidui test = new Kuaidui();
test.call_token();
test.call_sign();
}
需要返回int剪况,用objection + Wallbreaker
查看是64
@Override
public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "android/content/pm/PackageManager->GET_SIGNATURES:I": {
return 64;
}
}
return super.getStaticIntField(vm, dvmClass, signature);
}
然后就出結(jié)果了拯欧。
完整實現(xiàn)
public class Kuaidui extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public static String pkgName = "com.kuaiduizuoye.scan";
public static String apkPath = "unidbg-android/src/test/java/com/kuaidui/kuaidui540.apk";
public static String soPath = "";
public Kuaidui() {
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName(pkgName).build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File(apkPath));
vm.setJni(this);
vm.setVerbose(true);
new AndroidModule(emulator, vm).register(memory);
DalvikModule dm = vm.loadLibrary("baseutil", true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
@Override
public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "android/content/pm/PackageManager->GET_SIGNATURES:I": {
return 64;
}
}
return super.getStaticIntField(vm, dvmClass, signature);
}
public void call_sign() {
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(vm.addLocalObject(new StringObject(vm, "everhu")));
Number ret = module.callFunction(emulator, 0x1500, list.toArray());
System.out.println(vm.getObject(ret.intValue()).getValue());
}
public void call_token() {
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(vm.addLocalObject(vm.resolveClass("android/content/Context").newObject(null)));
String devid = "F5D53AD5A66144B57783C7C67611F0F7|0";
list.add(vm.addLocalObject(new StringObject(vm, devid)));
String request = "03090b0106000807080e00080201090708060f00040700020d0503070c0606000c01000a080e07050808020d00060b0d070f0f080306060b00000f0d04070d0d02070b0f020a01010c0d030a09080e090b040f02080c0b040c0e0e060c0d0201020f0e02030d0107020d000e0e02090401070505030a0c0605080303040e0803020c020e0d020408010b030e0b090f060302000e0f0902010706040c00080d0e060d000f0805040b0e07000d020b0f07";
list.add(vm.addLocalObject(new StringObject(vm, request)));
String response = "0a040609080d020b0a03090e010306050d0d050c0c060207070105010b0b01090801000a020d0c0b03030209030e0d0a";
list.add(vm.addLocalObject(new StringObject(vm, response)));
Number ret = module.callFunction(emulator, 0x1264, list.toArray());
}
public static void main(String[] args) {
Kuaidui test = new Kuaidui();
test.call_token();
test.call_sign();
}
}
nativeSetToken
nativeSetToken
入?yún)⒌?個字符串也是會隨著設(shè)備而改變该贾,分析一下它的來源杨蛋。
可以看出是從Preference
里取值。
不過,當(dāng)apk剛安裝的時候执隧,shared_prefs
里面是沒有這兩個值的户侥。
此時是從上圖處取值的蕊唐,可以看出調(diào)用了native的nativeInitBaseUtil
函數(shù),之后發(fā)了個請求钓试,抓包可以看到
可以看出耙替,post的請求數(shù)據(jù)就是第二個字符串俗扇,post的響應(yīng)數(shù)據(jù)就是第三個字符串铜幽。
其他
通過com.zuoyebang.baseutil.NativeHelper
這個類看到了另一個app的名字zuoyebang
除抛,很自然認(rèn)為二者用的是同一個簽名方案到忽。jadx打開zuoyebang的apk查看下喘漏,發(fā)現(xiàn)了同樣的接口。
再查看下so文件
稍微不同的是zuoyebang只提供了32位so持灰,而kuaidui作業(yè)只提供64位so堤魁。
ida查看
可以看到kuaidui作業(yè)的函數(shù)名全是顯式的椭微,能夠通過函數(shù)名知道函數(shù)的作用盲链;而zuoyebang則全都是sub_*
形式,很難直觀的了解函數(shù)的作用匈仗。二者都是用相同的方案,逆向的難度卻相差好幾倍逢慌,看來逆向的時候除了看看舊版本的apk,還可以看看其他使用相同方案的產(chǎn)品攻泼。