【申精】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函数
沙发一个 不觉明厉 感谢发布原创 精品分析 膜拜大佬 你这表情包用的真不少。。。 厉害👍,佩服佩服,虽然看不懂{:1_908:}
感谢发布原创,谢谢楼主 完全看不懂嘤嘤嘤