daojue 发表于 2017-6-18 15:17:52

破解某安卓微信群发APP(dex解密分析)



破解某安卓微信群发APP(dex解密分析)
jadx载入

寻觅xposed_init文件中定义的xposed程序的入口,发现主体只要如下三个函数,那猜测真正的hook函数被加密存储了,执行时经过dexClassloader动态加载执行

public class XposedEntry implements IXposedHookLoadPackage {
      private static final String enDexName = "appcompat_v4.dex";
      private static final String gsonDexName = "gson.dex";
    public static String pkgName = "wechat.simpleforwarder";
    private static final String soName = "libJpush.so";      

      public void copyFileFromAssets(InputStream inputStream, String str) {
          ...
      }

      String getCurProcessName(Context context) {
          ...
      }

      public void handleLoadPackage(LoadPackageParam loadPackageParam) {
          ...
      }
}

在程序的assets下发现了如下几个后缀为dex的文件,直接尝试了运用jadx去反编译,发现反编译不胜利,拖入010Editor

dex被作者停止了加密,那就得去代码中寻觅解密执行代码
直接看ui的入口并没有发现任何的解密中央,猜测既然是xposed插件,那一定会有findAndHookMethod的中央,以及beforeHook和afterHook,直接去查找,找到如下代码


protected void afterHookedMethod(MethodHookParam methodHookParam) {
    super.afterHookedMethod(methodHookParam);
    Context context = (Context) methodHookParam.thisObject;
    String curProcessName = this.ʼ.getCurProcessName(context);
    XposedBridge.log("processName = " + curProcessName);
    if (context.getPackageName().equalsIgnoreCase(curProcessName)) {
      File dir = context.getDir("forward_so", 0);
      File dir2 = context.getDir("forward_dex", 0);
      String absolutePath = new File(dir, "libJpush.so").getAbsolutePath();
      Context createPackageContext = context.createPackageContext(XposedEntry.pkgName, 2);
      this.ʼ.copyFileFromAssets(createPackageContext.getAssets().open("libJpush.so"), absolutePath);
      this.ʼ.copyFileFromAssets(createPackageContext.getAssets().open("appcompat_v4.dex"), new File(dir2, "appcompat_v4.dex").getAbsolutePath());
      this.ʼ.copyFileFromAssets(createPackageContext.getAssets().open("gson.dex"), new File(dir2, "gson.dex").getAbsolutePath());
      System.load(absolutePath);
      Class cls = (Class) JniUtil.getXClass(context, dir.getAbsolutePath(), dir2.getAbsolutePath());
      cls.getMethod(JniUtil.getXMethodName(), new Class[]{LoadPackageParam.class, Context.class}).invoke(cls.newInstance(), new Object[]{this.ʻ, context});
    }
}
程序读取assets中的文件,并加载了assets下的so文件,调用了一个名为JniUtil的getXClass函数,传入了三个参数,分别是context和两个途径,此处没有看到DexClassLoader,猜测这里的context是用作后面classloader运用的,IDA载入libJpush.so(竟然用极光推送的称号)

if ( ((int (__fastcall *)(JavaVM *, JNIEnv **, signed int))(*v2)->GetEnv)(v2, &env, 0x10004)
    || (v3 = env, (v4 = (*env)->FindClass(env, "wechat/simpleforwarder/util/JniUtil")) == 0)
    || ((int (__fastcall *)(JNIEnv *, jclass, JNINativeMethod *, signed int))(*v3)->RegisterNatives)(
         v3,
         v4,
         gMethods,
         2) < 0 )
{
    result = 0xFFFFFFFF;
}
在jni_Onload处看到动态动态了函数,直接双击gMethods跳过去
int __fastcall getXClass(JNIEnv *env, jclass jls, jobject context, jstring soDir, jstring dexDir)
{
jobject v5; // ST08_4@1
jclass v6; // r7@1
JNIEnv *v7; // r4@1
jstring v8; // r6@1
unsigned __int8 *v9; // r5@3
unsigned __int8 *v10; // r0@3
int v11; // r5@3
unsigned __int8 *v12; // r6@3
unsigned __int8 *v13; // r0@3
jstring dexPath; // @1
jstring gsonDexPath; // @1
jstring soDira; // @1

v5 = context;
v6 = jls;
soDira = soDir;
v7 = env;
dexPath = appendCharStr(env, dexDir, string27);
v8 = appendCharStr(v7, dexDir, string28);
gsonDexPath = appendCharStr(v7, dexDir, string29);
if ( getSignatureHashCode(v7, v6, v5) != 0x962F5B7 )
    killSelf(v7);
v9 = (unsigned __int8 *)((int (__fastcall *)(JNIEnv *, jstring, _DWORD))(*v7)->GetStringUTFChars)(v7, v8, 0);
v10 = (unsigned __int8 *)((int (__fastcall *)(JNIEnv *, jstring, _DWORD))(*v7)->GetStringUTFChars)(v7, dexPath, 0);
decryptFun(v9, v10);
v11 = DexClassLoader(v7, dexPath, gsonDexPath, soDira);
v12 = (unsigned __int8 *)((int (__fastcall *)(JNIEnv *, jstring, _DWORD))(*v7)->GetStringUTFChars)(v7, v8, 0);
v13 = (unsigned __int8 *)((int (__fastcall *)(JNIEnv *, jstring, _DWORD))(*v7)->GetStringUTFChars)(v7, dexPath, 0);
copyFun(v12, v13);
return v11;
}
这里就来到了getXClass处,这里函数并没有做混杂,所以很好剖析,看到一个DecryptFun函数,跟进去
void __fastcall decryptFun(unsigned __int8 *path, unsigned __int8 *dePath)
{
unsigned __int8 *v2; // r4@1
FILE *v3; // r0@1
FILE *v4; // r5@1
unsigned int v5; // r6@2
FILE *v6; // r7@2
unsigned int i; // r6@2
int v8; // r4@3

v2 = dePath;
v3 = j_j_fopen((const char *)path, "r");
v4 = v3;
if ( v3 )
{
    v5 = j_j_fgetc(v3) << 0x18;
    v6 = j_j_fopen((const char *)v2, "w");
    for ( i = v5 >> 0x18; ; j_j_fputc(v8 ^ i, v6) )
    {
      v8 = (unsigned __int8)j_j_fgetc(v4);
      if ( j_j_feof(v4) )
      break;
    }
    j_j_fclose(v4);
    j_j_fclose(v6);
}
}

这里就能够看到两个传入的途径,分别是文件所在途径和保管的解密文件的途径,而且包括下面一个DexClassLoader函数都没有做remove文件操作,所以也能够在程序执行后运用re把真实的dex拖出来

上面的DecryptFun函数,能够看到翻开了加密dex文件,并停止异或后保管在v6中,fgetc会每次读取一个字符,这里v5即是该文件的第一个字符,0x2D-->45 ,所以能够推导出该文件的每个字符会和45停止异或,


public static void main(String[] args) throws Exception {
   FileInputStream fis = new FileInputStream(new File("appcompat_v4.dex"));
   byte[] b = new byte;
   fis.read(b);
   int len = b.length;
   for(int i=0;i<len;i++){
         b= (byte) (b^45);

   }
   FileOutputStream fos = new FileOutputStream("appcompat_v43333.dex");
   fos.write(b);

}
异或完成后运用010翻开

去除掉第一个00字符,然后重新运用jadx翻开



页: [1]
查看完整版本: 破解某安卓微信群发APP(dex解密分析)