Richor 发表于 2019-8-29 11:07

【申精】DEX动态加载分析与Frida进阶hook(一)

本帖最后由 Richor 于 2019-8-29 11:12 编辑

最近分析YY直播TCP协议的时候发现不管用任何方法都找不到发送接收位置,用DDMS又看到一些不存在与反编译文件里的类,冥思苦想无解,最后看到ghostmazeW大佬的文章才知道有动态加载的,以下用demo分析动态加载过程
1.静态分析:
分别用AK,JADX,JEB加载文件看看
(1)JADX

(2)AK

(3)JEB


jadx和AK明显分析难度很高,果断放弃,选用JEB查看
2.分析oncreat函数
public void onCreate(Bundle arg13) {
      int v4 = 3;
      Object[] v0 = new Object;
      v0 = arg13;
      ChangeQuickRedirect v2 = MainActivity.changeQuickRedirect;
      Class[] v5 = new Class;
      Class v1 = Bundle.class;
      v5 = v1;
      Class v6 = Void.TYPE;
      MainActivity v1_1 = this;
      boolean v0_1 = PatchProxy.isSupport(v0, v1_1, v2, false, v4, v5, v6);
      if(v0_1) {
            v0 = new Object;
            v0 = arg13;
            v2 = MainActivity.changeQuickRedirect;
            v5 = new Class;
            v1 = Bundle.class;
            v5 = v1;
            v6 = Void.TYPE;
            v1_1 = this;
            PatchProxy.accessDispatch(v0, v1_1, v2, false, v4, v5, v6);
      }
      else {
            int v0_2 = 2;
            String v9 = this.Joseph(1, v0_2);
            super.onCreate(arg13);
            v0_2 = 0x7F09001B;
            this.setContentView(v0_2);
            this.runRobust();
            String v0_3 = "1B:D0:4A:9D:B5:A9:84:93:7E:79:27:9C:6C:C4:14:AB:DD:B0:75:7F";
            SignCheck v10 = new SignCheck(this, ((Context)this), v0_3);
            v10.check();
            Debug.isDebuggerConnected();
            v0_2 = 0x7F07003D;
            View v8 = this.findViewById(v0_2);
            v0_2 = 0x7F070026;
            View v7 = this.findViewById(v0_2);
            cn.chaitin.geektan.crackme.MainActivity$1 v0_4 = new View$OnClickListener(((EditText)v8), v9) {
                public static ChangeQuickRedirect changeQuickRedirect;

                public void onClick(View arg9) {
                  Toast v0_6;
                  MainActivity v0_5;
                  String v1_3;
                  int v4 = 18;
                  Object[] v0 = new Object;
                  v0 = arg9;
                  ChangeQuickRedirect v2 = cn.chaitin.geektan.crackme.MainActivity$1.changeQuickRedirect;
                  Class[] v5 = new Class;
                  Class v1 = View.class;
                  v5 = v1;
                  Class v6 = Void.TYPE;
                  cn.chaitin.geektan.crackme.MainActivity$1 v1_1 = this;
                  boolean v0_1 = PatchProxy.isSupport(v0, v1_1, v2, false, v4, v5, v6);
                  if(v0_1) {
                        v0 = new Object;
                        v0 = arg9;
                        v2 = cn.chaitin.geektan.crackme.MainActivity$1.changeQuickRedirect;
                        v5 = new Class;
                        v1 = View.class;
                        v5 = v1;
                        v6 = Void.TYPE;
                        v1_1 = this;
                        PatchProxy.accessDispatch(v0, v1_1, v2, false, v4, v5, v6);
                  }
                  else {
                        EditText v0_2 = this.val$input_text;
                        Editable v0_3 = v0_2.getText();
                        v0_1 = TextUtils.isEmpty(((CharSequence)v0_3));
                        if(!v0_1) {
                            v0_2 = this.val$input_text;
                            v0_3 = v0_2.getText();
                            String v0_4 = v0_3.toString();
                            StringBuilder v1_2 = new StringBuilder();
                            String v2_1 = "DDCTF{";
                            v1_2 = v1_2.append(v2_1);
                            v2_1 = this.val$result;
                            v1_2 = v1_2.append(v2_1);
                            v2_1 = "}";
                            v1_2 = v1_2.append(v2_1);
                            v1_3 = v1_2.toString();
                            v0_1 = v0_4.equals(v1_3);
                            if(v0_1) {
                              v0_5 = MainActivity.this;
                              v1_3 = "恭喜大佬!密码正确!";
                              v0_6 = Toast.makeText(((Context)v0_5), ((CharSequence)v1_3), 0);
                              v0_6.show();
                              return;
                            }
                        }

                        v0_5 = MainActivity.this;
                        v1_3 = "大佬莫急!再试试!";
                        v0_6 = Toast.makeText(((Context)v0_5), ((CharSequence)v1_3), 0);
                        v0_6.show();
                  }
                }
            };
            ((Button)v7).setOnClickListener(((View$OnClickListener)v0_4));
      }
    }
好的,我们发现了几个比较可疑的函数,PatchProxy,changeQuickRedirect,runrobust;其中runrobust有lmp标识,这个是动态加载的特征

3.分析runrobust
private void runRobust() {
      int v4 = 4;
      Object[] v0 = new Object;
      ChangeQuickRedirect v2 = MainActivity.changeQuickRedirect;
      Class[] v5 = new Class;
      Class v6 = Void.TYPE;
      MainActivity v1 = this;
      boolean v0_1 = PatchProxy.isSupport(v0, v1, v2, false, v4, v5, v6);
      if(v0_1) {
            v0 = new Object;
            v2 = MainActivity.changeQuickRedirect;
            v5 = new Class;
            v6 = Void.TYPE;
            v1 = this;
            PatchProxy.accessDispatch(v0, v1, v2, false, v4, v5, v6);
      }
      else {
            Context v1_1 = this.getApplicationContext();
            PatchManipulateImp v2_1 = new PatchManipulateImp();
            PatchExecutor v0_2 = new PatchExecutor(v1_1, ((PatchManipulate)v2_1), new GeekTanCallBack());
            v0_2.start();
      }
    }
}
可以看到此函数实例化了PatchManipulateImp(),我们跟进去看看;

看几个函数,应该可以看到fetchpatchlist,fetch就是取的意思,我们就可以怀疑这个函数就是取出动态加载的dex
我们继续找到有什么文件

看到了一个 v10 = arg17.getAssets().open("GeekTan.BMP");,奇怪怎么是BMP?

找到这个文件放进010看看

看到抬头是PK...就知道是压缩文件,在看后面的lasses.dex,就可以确定这个实际就是dex的压缩文件
Robust是美团出的一款热修复框架,可以在github上面它的最新的源码,地址:https://github.com/Meituan-Dianping/Robust
Robust为每个class增加了个类型为ChangeQuickRedirect的静态成员,而在每个方法前都插入了使用changeQuickRedirect相关的逻辑,当changeQuickRedirect不为null时,会执行到accessDispatch方法从而替换掉之前老的逻辑,达到修复的目的
4.分析PatchExecutor
上一步我们大体知道了PatchManipulateImp这个对象,接下来我们回到mainactivity看下一步实例对象PatchExecutor

我们看看run函数

    public void run() {
      try {
            this.applyPatchList(this.fetchPatchList());
      }
      catch(Throwable v1) {
            String v2 = "robust";
            String v3 = "PatchExecutor run";
            Log.e(v2, v3, v1);
            RobustCallBack v2_1 = this.robustCallBack;
            v3 = "class:PatchExecutor,method:run,line:36";
            v2_1.exceptionNotify(v1, v3);
      }
    }
this.applyPatchList(this.fetchPatchList());就是打补丁的操作,跟进去看看
protected void applyPatchList(List arg9) {
      RobustCallBack v4_4;
      boolean v0;
      String v6;
      StringBuilder v5_2;
      if(arg9 != null) {
            boolean v3 = arg9.isEmpty();
            if(v3) {
                return;
            }

            String v3_1 = "robust";
            StringBuilder v4 = new StringBuilder();
            String v5 = " patchManipulate list size is ";
            v4 = v4.append(v5);
            int v5_1 = arg9.size();
            v4 = v4.append(v5_1);
            String v4_1 = v4.toString();
            Log.d(v3_1, v4_1);
            Iterator v3_2 = arg9.iterator();
      label_98:
            boolean v4_2 = v3_2.hasNext();
            if(!v4_2) {
                return;
            }

            Object v1 = v3_2.next();
            v4_2 = ((Patch)v1).isAppliedSuccess();
            if(v4_2) {
                v4_1 = "robust";
                v5_2 = new StringBuilder();
                v6 = "p.isAppliedSuccess() skip ";
                v5_2 = v5_2.append(v6);
                v6 = ((Patch)v1).getLocalPath();
                v5_2 = v5_2.append(v6);
                v5 = v5_2.toString();
                Log.d(v4_1, v5);
                goto label_98;
            }

            PatchManipulate v4_3 = this.patchManipulate;
            v4_2 = v4_3.ensurePatchExist(((Patch)v1));
            if(!v4_2) {
                goto label_98;
            }

            try {
                v0 = this.patch(this.context, ((Patch)v1));
            }
            catch(Throwable v2) {
                v4_4 = this.robustCallBack;
                v5 = "class:PatchExecutor method:applyPatchList line:69";
                v4_4.exceptionNotify(v2, v5);
            }

            if(v0) {
                ((Patch)v1).setAppliedSuccess(true);
                v4_4 = this.robustCallBack;
                v4_4.onPatchApplied(true, ((Patch)v1));
            }
            else {
                v4_4 = this.robustCallBack;
                v4_4.onPatchApplied(false, ((Patch)v1));
            }

            v4_1 = "robust";
            v5_2 = new StringBuilder();
            v6 = "patch LocalPath:";
            v5_2 = v5_2.append(v6);
            v6 = ((Patch)v1).getLocalPath();
            v5_2 = v5_2.append(v6);
            v6 = ",apply result ";
            v5_2 = v5_2.append(v6);
            v5_2 = v5_2.append(v0);
            v5 = v5_2.toString();
            Log.d(v4_1, v5);
            goto label_98;
      }
    }
再定位到载入的位置
try {
                v0 = this.patch(this.context, ((Patch)v1));
            }
            catch(Throwable v2) {
                v4_4 = this.robustCallBack;
                v5 = "class:PatchExecutor method:applyPatchList line:69";
                v4_4.exceptionNotify(v2, v5);
            }
有个path函数,点进去看看
String v18_3 = arg25.getTempPath();
      File v19_2 = arg24.getCacheDir();
      v19_1 = v19_2.getAbsolutePath();
      v20 = null;
      Class v21 = PatchExecutor.class;
      ClassLoader v21_1 = v21.getClassLoader();
      String v0_3 = v18_3;
      String v1_1 = v19_1;
      String v2_1 = v20;
      ClassLoader v3 = v21_1;
      DexClassLoader v5 = new DexClassLoader(v0_3, v1_1, v2_1, v3);
      v18_3 = arg25.getTempPath();
      Patch v0_4 = arg25;
      v1_1 = v18_3;
      v0_4.delete(v1_1);
      try {
            Log.d("robust", "PatchsInfoImpl name:" + arg25.getPatchesInfoImplClassFullName());
            v15 = v5.loadClass(arg25.getPatchesInfoImplClassFullName()).newInstance();
            Log.d("robust", "PatchsInfoImpl ok");
      }
      catch(Throwable v17) {
            v0 = this;
            v0_2 = v0.robustCallBack;
            v18_2 = v0_2;
            v19_1 = "class:PatchExecutor method:patch line:108";
            v0_2 = v18_2;
            v1_2 = v17;
            v2_1 = v19_1;
            v0_2.exceptionNotify(v1_2, v2_1);
            v18_3 = "robust";
            v19 = new StringBuilder();
            v20 = "PatchsInfoImpl failed,cause of";
            v19 = v19.append(v20);
            v20 = v17.toString();
            v19 = v19.append(v20);
            v19_1 = v19.toString();
            Log.e(v18_3, v19_1);
            v17.printStackTrace();
      }
然后可以看到DexClassLoader,这就是动态加载类了
5.分析动态加载的dex文件
我们刚才已经得到了dex文件,把文件名改一下GeekTan.jar,丢到jadx看看

结构就长这样了,我们看一下PatchesInfoImpl看一下什么类是需要被修复的
就是这两个
cn.chaitin.geektan.crackme.MainActivityPatchControl
cn.chaitin.geektan.crackme.MainActivity$1PatchControl

先分析一下MainActivityPatchControl


我们可以看到onCreate,Joseph,onclick会被重载
我们点进onclick看看

一堆的invokeReflectStaticMethod,invokeReflectMethod,我们找到重点
String str = "DDCTF{";
str = (String) EnhancedRobustUtils.invokeReflectMethod("Joseph", obj32, getRealParameter(new Object[]{new Integer(7), new Integer(8)}), new Class[]{Integer.TYPE, Integer.TYPE}, MainActivity.class);
            if (obj4 == this) {
                obj4 = ((MainActivity$1Patch) obj4).originClass;
            }

if (((Boolean) EnhancedRobustUtils.invokeReflectMethod("equals", obj2, getRealParameter(new Object[]{str2}), new Class[]{Object.class}, String.class)).booleanValue()) {
                if (this instanceof MainActivity$1Patch) {
                  obj2 = this.originClass;
                } else {
                  obj2 = this;
                }
                obj2 = (Toast) EnhancedRobustUtils.invokeReflectStaticMethod("makeText", Toast.class, getRealParameter(new Object[]{(MainActivity) EnhancedRobustUtils.getFieldValue("this$0", obj2, 1.class), "恭喜大佬!密码正确!", new Integer(0)}), new Class[]{Context.class, CharSequence.class, Integer.TYPE});
                if (obj2 == this) {
                  obj2 = ((MainActivity$1Patch) obj2).originClass;
                }
                EnhancedRobustUtils.invokeReflectMethod("show", obj2, new Object, null, Toast.class);
                return;
            }

结果就是Joseph(int,int)
我们可以通过HOOK EnhancedRobustUtils.invokeReflectMethod来得到结果
public static Object invokeReflectMethod(String arg5, Object arg6, Object[] arg7, Class[] arg8, Class arg9) {
      Object v2;
      try {
            v2 = EnhancedRobustUtils.getDeclaredMethod(arg6, arg5, arg8, arg9).invoke(arg6, arg7);
      }
      catch(Exception v0) {
            v0.printStackTrace();
            boolean v2_1 = EnhancedRobustUtils.isThrowable;
            if(v2_1) {
                StringBuilder v3 = new StringBuilder();
                String v4 = "invokeReflectMethod error ";
                v3 = v3.append(v4);
                v3 = v3.append(arg5);
                v4 = "   parameter   ";
                v3 = v3.append(v4);
                v3 = v3.append(arg7);
                v4 = " targetObject ";
                v3 = v3.append(v4);
                v4 = arg6.toString();
                v3 = v3.append(v4);
                v4 = "args";
                v3 = v3.append(v4);
                v3 = v3.append(arg8);
                String v3_1 = v3.toString();
                RuntimeException v2_2 = new RuntimeException(v3_1);
                throw v2_2;
            }

            v2 = null;
      }

      return v2;
    }
最后一步,HOOK

打开Pycharm:


import frida, sys


def on_message(message, data):
    if message['type'] == 'send':
      print(" {0}".format(message['payload']))
    else:
      print(message)


js_code = '''
    Java.perform(function(){
      var robust = Java.use("com.meituan.robust.utils.EnhancedRobustUtils");
      robust.invokeReflectMethod.implementation = function(v1,v2,v3,v4,v5){
            var result = this.invokeReflectMethod(v1,v2,v3,v4,v5);
            if(v1=="Joseph"){
                console.log("functionName:"+v1);
                console.log("functionArg3:"+v3);
                console.log("functionArg4:"+v4);
                send(v4);
                console.log("return:"+result);
                console.log("-----------------------------------------------------")
            }
            else if(v1=="equals"){
                console.log("functionName:"+v1);
                console.log("functionArg3:"+v3);
                console.log("functionArg4:"+v4);
                send(v4);
                console.log("return:"+result);
            }
            return result;
      }
});
'''

process = frida.get_usb_device().attach("cn.chaitin.geektan.crackme")
print("找到目标包")
script = process.create_script(js_code)
script.on('message', on_message)
script.load()
sys.stdin.read()



结果就是DDCTF{2936012829362176}

下一章学一下Frida HOOK动态DEX函数

okxiaobo 发表于 2019-8-29 11:20

xixicoco 发表于 2019-8-29 11:24

沙发一个

S.K 发表于 2019-8-29 11:33

不觉明厉

yanmingming 发表于 2019-8-29 11:34

感谢发布原创

yiwai2012 发表于 2019-8-29 11:48

精品分析 膜拜大佬

FleTime 发表于 2019-8-29 12:59

你这表情包用的真不少。。。

dyh8283221 发表于 2019-8-29 13:15

厉害👍,佩服佩服,虽然看不懂{:1_908:}

清炒藕片丶 发表于 2019-8-29 14:36


感谢发布原创,谢谢楼主

mzycy 发表于 2019-8-29 15:26

完全看不懂嘤嘤嘤
页: [1] 2 3
查看完整版本: 【申精】DEX动态加载分析与Frida进阶hook(一)