qtfreet00 发表于 2017-6-16 17:34

某Xposed微信群发工具dex解密分析

本帖最后由 qtfreet00 于 2017-6-16 17:49 编辑

##### 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) {
            ...
            }
    }
!(http://i4.piimg.com/1949/bf5a46e0ba7f4c02.png)

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

!(http://i1.buimg.com/1949/d2e57127c165945f.png)

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打开

!(http://i2.muimg.com/1949/9e18b042813f24a4.png)

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

!(http://i2.muimg.com/1949/16a21100bb9a746d.png)

pwp 发表于 2017-6-16 19:17

武林高手就是不一样,像大学教授的论文一样,耐人寻味

wangxd 发表于 2017-7-11 14:49


DEX的16进制有明显的结构性,很容易猜到就是异或加密,而DEX中00出现的较多,你这个图中加密后的dex出现的较多的就是2D这个值,所以加密方式就是单字节的与0x2D异或运算。只看图就能看出来了,不需要分析代码啊

hobmg 发表于 2017-6-16 17:52

厉害 厉害 第一个吗{:1_912:}

歌中雅歌 发表于 2017-6-16 18:04

板凳看看

crack_pojie 发表于 2017-6-16 18:26

蓦留 发表于 2017-6-16 18:47

前排围观下 学习学习

豫黄河 发表于 2017-6-16 20:17

不明觉厉,{:301_993:} 我是码盲

cdeath 发表于 2017-6-16 20:43

   学习系啊   = =

疯疯传说 发表于 2017-6-16 21:32

围观下 学习学习

fengrui99 发表于 2017-6-16 21:42

7.0不支持框架了{:301_999:}
页: [1] 2 3 4 5 6
查看完整版本: 某Xposed微信群发工具dex解密分析