moji天氣逆向及unidbg實現
Java層
還挺多結果的,不過忽略掉meizu,huawei這些第三方sdk里面的東西后,還是很容易看出來的。
com.moji.requestcore.encrypt.DefaultEncryptParamImpl.encrypt
com.moji.requestcore.encrypt.DefaultEncryptParamImpl.a
com.moji.mjweather.library.Digest.encodeParams
lte.NCall.IL
其實libGameVMP.so
對我來說不陌生,之前在看某物 dewu app so newSign 參數分析破解這篇文章的時候,因為它奇葩的特性所以記住了。所以在moji這個app是不是也一樣?
先hook一下com.moji.mjweather.library.Digest
這個類
android hooking watch class com.moji.mjweather.library.Digest
可以看到每次調用encodeParams
的時候,nativeEncodeParams
都被調用了。
再hook一下nativeEncodeParams
這個函數。
android hooking watch class_method com.moji.mjweather.library.Digest.nativeEncodeParams --dump-args --dump-return
從hook結果可以看到最后確實是調用它生成sign。
這么多個so,哪個才是調用加密的呢?從名字看libencrypt.so
和libEncryptor.so
最可疑。
jnitrace看看。
jnitrace.exe -l libencrypt.so -l libEncryptor.so com.moji.mjweather|tee trace-moji.txt
然后嘗試在記錄里搜索某個簽名
可以看到最終是在libencrypt.so
生成sign的。
so層
從地址可以看出so是64位的,因為手機和apk都支持64位指令,接下來的分析也是針對64位。
打開arm64-v8a
下的libencrypt.so
,函數窗口搜索java
說明是動態注冊的,看看JNI_OnLoad
跳轉過去
繼續跳轉
所以真實函數名是parseParams
第二種方法是frida_hook_libart
frida -U -f com.moji.mjweather -l hook_RegisterNatives.js --no-pause
然后跳轉到0x3d1a0
另一種方法就是用jnitrace
jnitrace -l libencrypt.so -i RegisterNatives com.moji.mjweather
函數地址0x728b2861a0
減去so基地址0x728b249000
等于0x3d1a0
,跳轉過去同樣也是parseParams
。
算法其實挺簡單的,就是后面拼接個字符串,然后做個MD5就出結果了,可以自行hook MD5Update
函數進行驗證,這里就省略了。
unidbg實現
由于unidbg可以自由選擇32還是64位,這里選擇的是32位的。先搭個框架
public class Moji extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public static String pkgName = "com.moji.mjweather";
public static String apkPath = "unidbg-android/src/test/java/com/moji/moji90300.apk";
public static String soPath = "";
public Moji() {
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(pkgName).build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File(apkPath));
vm.setJni(this);
vm.setVerbose(true);
DalvikModule dm = vm.loadLibrary("encrypt", true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
public static void main(String[] args) {
Moji test = new Moji();
}
}
開始報錯和補函數。
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "com/moji/tool/AppDelegate->getAppContext()Landroid/content/Context;": {
return vm.resolveClass("android/content/Context").newObject(null);
}
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}
@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "android/app/ActivityThread->sCurrentActivityThread:Landroid/app/ActivityThread;": {
return vm.resolveClass("android/app/ActivityThread").newObject(null);
}
}
return super.getStaticObjectField(vm, dvmClass, signature);
}
不知道怎么補,開始擺爛
public void setStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature, DvmObject<?> value) {
switch (signature) {
case "android/app/ActivityThread->sPackageManager:Landroid/content/pm/IPackageManager;": {
}
}
}
case "android/os/ServiceManager->getService(Ljava/lang/String;)Landroid/os/IBinder;": {
return vm.resolveClass("android/os/IBinder").newObject(null);
}
越來越離譜,一怒之下,決定不補了,嘗試直接patch。
跳轉到之前獲取PackageManager
的地方,也就是0x11315
可以看出getPublicKey
這個函數主要起獲取簽名的作用,X
查看查看引用
看看checkPubKey
可以看出校驗成功會返回1,失敗返回0。
查看checkPubKey
的引用
可以看到在關鍵函數中都有調用,先看看checkKeyOnLoad
(因為對它查看引用的話,可以看到它在JNI_OnLoad
被調用了)
直接修改0x117A2
這條指令,把返回值,也就是r0
的值改為1
public void patchVerify(){
emulator.getMemory().pointer(module.base+0x117A2).setInt(0, 0x4ff00100);
}
public Moji() {
// ...
this.patchVerify();
dm.callJNI_OnLoad(emulator);
}
再次跑起來。
JNI_OnLoad
完成了,接下來是正式調用。
public void call_sign() {
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(vm.addLocalObject(new StringObject(vm, "1")));
Number ret = module.callFunction(emulator, 0x21a9d, list.toArray());
System.out.println(vm.getObject(ret.intValue()).getValue().toString());
}
public static void main(String[] args) {
Moji test = new Moji();
test.call_sign();
}
又報錯了,這是因為之前的checkPubKey
在parseParams
中也被調用了
同樣的,patch這條指令
public void patchVerify(){
emulator.getMemory().pointer(module.base+0x117A2).setInt(0, 0x4ff00100);
emulator.getMemory().pointer(module.base+0x21AF4).setInt(0, 0x4ff00100);
}
重新運行,結果就出來了
完整代碼
package com.moji;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
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.memory.Memory;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class Moji extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public static String pkgName = "com.moji.mjweather";
public static String apkPath = "unidbg-android/src/test/java/com/moji/moji90300.apk";
public static String soPath = "";
public Moji() {
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(pkgName).build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File(apkPath));
vm.setJni(this);
vm.setVerbose(true);
DalvikModule dm = vm.loadLibrary("encrypt", true);
module = dm.getModule();
this.patchVerify();
dm.callJNI_OnLoad(emulator);
}
public void patchVerify(){
emulator.getMemory().pointer(module.base+0x117A2).setInt(0, 0x4ff00100);
emulator.getMemory().pointer(module.base+0x21AF4).setInt(0, 0x4ff00100);
}
@Override
public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "com/moji/tool/AppDelegate->getAppContext()Landroid/content/Context;": {
return vm.resolveClass("android/content/Context").newObject(null);
}
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}
public void call_sign() {
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(vm.addLocalObject(new StringObject(vm, "1")));
Number ret = module.callFunction(emulator, 0x21a9d, list.toArray());
System.out.println(vm.getObject(ret.intValue()).getValue().toString());
}
public static void main(String[] args) {
Moji test = new Moji();
test.call_sign();
}
}
總結
- patch一時爽,一直patch一直爽。不過有哪位補函數的高手能完整補出來嗎,想學習學習
- 有沒有方法能讓64位手機,64位app運行在32位環境,習慣了32位環境,分析64位不太習慣
Update 2022-02-09
Android adb安裝時強制應用App以32位或者64位運行
指定應用在64位終端下以32位方式模式下運行
adb install --abi armeabi-v7a <path to apk>