Xposed主动调用开发

篇幅有限

完整内容及源码关注公众号:ReverseCode,发送

相比于Frida,Xposed在参数的构造上是更有优势的,因为它就是使用的Java进行的开发;

hook(想通过hook的方式得到一个obj的话需要hook一个实例方法)

  1. constructor.newInstance

  2. xposed.newInstance

案例Qualification

分析

adb install Qualification-1.3-easy-release.apk 目标就是找到pin的真实值

image-20210501160104595

jadx-gui Qualification-1.3-easy-release.apk 搜索not the 找不到可能的结果,弹窗可能存在于dialog_failure或者dialog_success

image-20210501155903377

查看strings.xml中定义的变量,<string name="dialog_failure">Unfortunately, not the right PIN :(</string>

image-20210501160334072

通过hookEvent.js进行hook所有动作触发的组件,找到android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener

image-20210501220605705

jadx中搜索android.support.v7.app.AppCompatViewInflater.DeclaredOnClickListener,使用了系统框架,无法准确定位最近的方法。

1
2
3
4
objection -g org.teamsik.ahe17.qualification.easy explore -P ~/.objection/plugins   AndroidManifest.xml拿到包名开始objection
android hooking search classes MainActivity
android hooking list class_methods org.teamsik.ahe17.qualification.MainActivity
android hooking watch class org.teamsik.ahe17.qualification.MainActivity 点击VERIFY PIN按钮触发事件

image-20210501221658530

根据org.teamsik.ahe17.qualification.MainActivity.verifyPasswordClick触发hook verifyPasswordClick

1
android hooking watch class_method org.teamsik.ahe17.qualification.MainActivity.verifyPasswordClick --dump-args --dump-backtrace --dump-return

image-20210501222041222

1
2
plugin wallbreaker objectsearch android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener 
plugin wallbreaker objectdump --fullname 0x227a 使用反射进行调用的框架

image-20210501222956442

破解

校验秘钥逻辑如下,通过调用逻辑改写verifyPassword实现解密。

image-20210501224643323

image-20210501224633987

1
android hooking search classes Verifier  最好从内存中捞这个类,而非根据静态反编译,hook方法也最好从内存中获取,不要从静态编译结果中获取不准确

反射调用encodePassword

f (v[i] != p[i])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (loadPackageParam.packageName.equals("org.teamsik.ahe17.qualification.easy")) {
// 安装xposed1点击button触发反射调用
XposedBridge.log("inner" + loadPackageParam.processName);
Class clazz = loadPackageParam.classLoader.loadClass("org.teamsik.ahe17.qualification.Verifier");
Method encodePassword = clazz.getDeclaredMethod("encodePassword", String.class);
encodePassword.setAccessible(true);
byte[] p = "09042ec2c2c08c4cbece042681caf1d13984f24a".getBytes();
// XposedBridge.log("result i is => " + new String(p)); 打印字节数组
String pStr = new String((p));
for (int i = 999; i < 10000; i++) {
byte[] v = (byte[]) encodePassword.invoke(null, String.valueOf(i));
if (v.length != p.length) {
break;
}
String vStr = new String(v);
if (vStr == pStr) {
XposedBridge.log("Current i is => " + String.valueOf(i));
}
}
}

反射调用verifyPassword

if (!Verifier.verifyPassword(this, this.txPassword.getText().toString()))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (loadPackageParam.packageName.equals("org.teamsik.ahe17.qualification.easy")) {
XposedBridge.log("inner" + loadPackageParam.processName);
Class clazz = loadPackageParam.classLoader.loadClass("org.teamsik.ahe17.qualification.Verifier");
// public 方法不需要getDeclaredMethod和setAccessible,反射拿到verifyPassword
Method verifyPassword = clazz.getMethod("verifyPassword", Context.class, String.class);
Context context = AndroidAppHelper.currentApplication();

for (int i = 999; i < 10000; i++) {
if ((boolean) verifyPassword.invoke(null, context, String.valueOf(i))) {
XposedBridge.log("Current i is => " + String.valueOf(i));
}
}

}

callStaticMethod verifyPassword

1
2
3
4
5
6
7
8
9
10
11
12
if (loadPackageParam.packageName.equals("org.teamsik.ahe17.qualification.easy")) {
XposedBridge.log("inner"+loadPackageParam.processName);
Class clazz = XposedHelpers.findClass("org.teamsik.ahe17.qualification.Verifier",loadPackageParam.classLoader);
// hook Verifier类,并直接调用静态方法verifyPassword
Context context = AndroidAppHelper.currentApplication();

for(int i = 999;i<10000;i++){
if((boolean) XposedHelpers.callStaticMethod(clazz,"verifyPassword",context,String.valueOf(i))){
XposedBridge.log("Current i is => "+ String.valueOf(i));
}
}
}

findAndHookMethod encodePassword

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (loadPackageParam.packageName.equals("org.teamsik.ahe17.qualification.easy")) {
XposedBridge.log("inner" + loadPackageParam.processName);
Class clazz = XposedHelpers.findClass("org.teamsik.ahe17.qualification.Verifier", loadPackageParam.classLoader);
XposedHelpers.findAndHookMethod(clazz, "encodePassword", String.class, new XC_MethodHook() {
// hook encodePassword后拿到所在类,调用该类的verifyPassword
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Context context = AndroidAppHelper.currentApplication();
for (int i = 999; i < 10000; i++) {
if ((boolean) XposedHelpers.callMethod(param.thisObject, "verifyPassword", context, String.valueOf(i))) {
XposedBridge.log("Current i is => " + String.valueOf(i));
}
}
Log.d("getResult is => ", (String) param.getResult());
}
});

}

newInstance Verifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (loadPackageParam.packageName.equals("org.teamsik.ahe17.qualification.easy")) {

XposedBridge.log("inner" + loadPackageParam.processName);
Class clazz = XposedHelpers.findClass("org.teamsik.ahe17.qualification.Verifier", loadPackageParam.classLoader);
// newInstance拿到Verifier类并调用verifyPassword
Object Verifier = XposedHelpers.newInstance(clazz);
Context context = AndroidAppHelper.currentApplication();

for (int i = 999; i < 10000; i++) {
if ((boolean) XposedHelpers.callMethod(Verifier, "verifyPassword", context, String.valueOf(i))) {
XposedBridge.log("Current i is => " + String.valueOf(i));
}
}

}

findConstructorExact Verifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (loadPackageParam.packageName.equals("org.teamsik.ahe17.qualification.easy")) {

XposedBridge.log("inner" + loadPackageParam.processName);
// Constructor获取构造器newInstance拿到Verifier并调用verifyPassword
Constructor cons = XposedHelpers.findConstructorExact("org.teamsik.ahe17.qualification.Verifier",loadPackageParam.classLoader);
Object Verifier = cons.newInstance();
Context context = AndroidAppHelper.currentApplication();
for (int i = 999; i < 10000; i++) {
if ((boolean) XposedHelpers.callMethod(Verifier, "verifyPassword", context, String.valueOf(i))) {
XposedBridge.log("Current i is => " + String.valueOf(i));
}
}

}

hookAllMethods onCreate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (loadPackageParam.packageName.equals("org.teamsik.ahe17.qualification.easy")) {

XposedBridge.log("inner" + loadPackageParam.processName);

Class clazz = loadPackageParam.classLoader.loadClass("org.teamsik.ahe17.qualification.MainActivity");
// 直接调用verifyPassword成功后回调的方法showSuccessDialog
XposedBridge.hookAllMethods(clazz, "onCreate",new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Object mMainAciticity = param.thisObject;
XposedHelpers.callMethod(mMainAciticity,"showSuccessDialog");

}
});
}

案例demoso1

1
2
3
4
5
6
7
./fs1426arm64 
pyenv local 3.9.0
objection -g com.example.demoso1 explore
android hooking search classes MainActivity
android hooking list class_methods com.example.demoso1.MainActivity 查看该类中所有函数
android hooking watch class com.example.demoso1.MainActivity
android hooking watch class_method com.example.demoso1.MainActivity.$init 查看构造函数的参数

image-20210503174025839

  1. 给android hooking list class_methods 加上构造函数
1
2
3
4
5
cd ~/.pyenv
tree -NCfhl|grep objection
cd ./versions/3.9.0/lib/python3.9/site-packages
tree -NCfhl |grep -i agent.js
vi ./objection/agent.js

image-20210503182639000

//android hooking list class_methods com.example.demoso1.MainActivity
//protected void com.example.demoso1.MainActivity.onCreate(android.os.Bundle)
//public native int com.example.demoso1.MainActivity.init()
//public native int com.example.demoso1.MainActivity.myfirstjni()
//public native java.lang.String com.example.demoso1.MainActivity.method02(java.lang.String)
//public static native java.lang.String com.example.demoso1.MainActivity.method01(java.lang.String)
//public static native java.lang.String com.example.demoso1.MainActivity.myfirstjniJNI(java.lang.String)
//public static native java.lang.String com.example.demoso1.MainActivity.stringFromJNI()
//public static native java.lang.String com.example.demoso1.MainActivity.stringFromJNI2()
//public void com.example.demoso1.MainActivity.testField()
//public void com.example.demoso1.MainActivity.testMethod()
// Found 10 method(s)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (loadPackageParam.packageName.equals("com.example.demoso1")) {
XposedBridge.log("inner" + loadPackageParam.processName);
final Class clazz = loadPackageParam.classLoader.loadClass("com.example.demoso1.MainActivity");
//得到对象:hook(想通过hook的方式得到一个obj的话得hook一个实例方法) onCreate 循环调用method01和method02,onCreate执行后再触发
XposedBridge.hookAllMethods(clazz, "onCreate", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Object mMainAciticity = param.thisObject;
String cipherText = (String) XposedHelpers.callMethod(mMainAciticity, "method01", "roysue");
String clearText = (String) XposedHelpers.callMethod(mMainAciticity, "method02", "47fcda3822cd10a8e2f667fa49da783f");
XposedBridge.log("Cipher text is => " + cipherText);
XposedBridge.log("Clear text is => " + clearText);
}
});

//xposed.newInstance获取对象 毕竟在hook中,进行主动调用的话,触发条件需要将mMainActivity进行newInstance,包只要打开就会触发
Object newMainActivity = XposedHelpers.newInstance(clazz);
String cipherText = (String) XposedHelpers.callMethod(newMainActivity, "method01", "roysue");
String clearText = (String) XposedHelpers.callMethod(newMainActivity, "method02", "47fcda3822cd10a8e2f667fa49da783f");
XposedBridge.log("Cipher text 2 is => " + cipherText);
XposedBridge.log("Clear text 2 is => " + clearText);
}

image-20210503183616411

Nanohttpd

利用NanoHTTPD反射调用Android APP加密函数

build.gradle添加依赖implementation 'org.nanohttpd:nanohttpd:2.3.1'

AndroidManifest.xml添加网络权限声明<uses-permission android:name="android.permission.INTERNET"/>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
Object mMainActivity = null;

public void setActivity(Object obj) {
mMainActivity = obj;
}

public Object getActivity() {
return mMainActivity;
}

if (loadPackageParam.packageName.equals("com.example.demoso1")) {
XposedBridge.log("inner" + loadPackageParam.processName);
final Class clazz = loadPackageParam.classLoader.loadClass("com.example.demoso1.MainActivity");
//得到对象:hook(想通过hook的方式得到一个obj的话得hook一个实例方法) onCreate 循环调用method01和method02
XposedBridge.hookAllMethods(clazz, "onCreate", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Object mMainAciticity = param.thisObject;
String cipherText = (String) XposedHelpers.callMethod(mMainAciticity, "method01", "roysue");
String clearText = (String) XposedHelpers.callMethod(mMainAciticity, "method02", "47fcda3822cd10a8e2f667fa49da783f");
XposedBridge.log("Cipher text is => " + cipherText);
XposedBridge.log("Clear text is => " + clearText);
// setActivity(mMainAciticity);

}
});

//xposed.newInstance获取对象 毕竟在hook中,进行主动调用的话,触发条件需要将mMainActivity进行newInstance
Object newMainActivity = XposedHelpers.newInstance(clazz);
String cipherText = (String) XposedHelpers.callMethod(newMainActivity, "method01", "roysue");
String clearText = (String) XposedHelpers.callMethod(newMainActivity, "method02", "47fcda3822cd10a8e2f667fa49da783f");
XposedBridge.log("Cipher text 2 is => " + cipherText);
XposedBridge.log("Clear text 2 is => " + clearText);
setActivity(newMainActivity);


class App extends NanoHTTPD {

public App() throws IOException {
super(8899);
// 内部新起线程实现http,无法直接调用当前类的变量,需要在上面定义setActivity拿到callMethod里的对象
start(NanoHTTPD.SOCKET_READ_TIMEOUT, true);
XposedBridge.log("\nRunning! Point your browsers to http://localhost:8899/ \n");
}

@Override
public NanoHTTPD.Response serve(IHTTPSession session) {

Method method = session.getMethod();
String uri = session.getUri();
String RemoteIP = session.getRemoteIpAddress();
String RemoteHostName = session.getRemoteHostName();
Log.i("r0ysue nanohttpd ","Method => "+method + " ;Url => " + uri + "' ");
Log.i("r0ysue nanohttpd ","Remote IP => "+RemoteIP + " ;RemoteHostName => " + RemoteHostName + "' ");

String paramBody = "";
Map<String, String> files = new HashMap<>();
try {
session.parseBody(files);
paramBody = session.getQueryParameterString();
} catch (IOException e) {
e.printStackTrace();
} catch (ResponseException e) {
e.printStackTrace();
}
Log.i("Nano_post_param => ", paramBody);
//
//
// String msg = "<html><body><h1>Hello server</h1>\n";
// Map<String, String> parms = session.getParms();
// if (parms.get("username") == null) {
// msg += "<form action='?' method='get'>\n <p>Your name: <input type='text' name='username'></p>\n" + "</form>\n";
// } else {
// msg += "<p>Hello, " + parms.get("username") + "!</p>";
// }
String result = "";
result = (String) XposedHelpers.callMethod(getActivity(), "method01", "r0ysue");
return newFixedLengthResponse(Response.Status.OK, NanoHTTPD.MIME_PLAINTEXT, result);
}
}
new App();

}

curl -s -X POST “http://192.168.0.103:8899/" -d ‘{“data”:onejane}’

python调用实现

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests

def encrypt(enParam):
// xposed1 app所在手机ip
url = "http://192.168.0.103:8899"
param = enParam
headers = {"Content-Type":"application/x-www-form-urlencoded"}
r = requests.post(url = url ,data=param,headers = headers)
print(r.content)
return r.content

if __name__ == '__main__' :
encrypt("onejane")

image-20210503191723155

apt install siege https://www.jianshu.com/p/74c465ff136f

1
siege -c5 -r10 "http://192.168.0.103:8899 POST <./iloveroysue.json "   压测

iloveroysue.json

{

“data”:”iloveroysue”

}

adb forward tcp:8899 tcp:8899 将手机8899转发到电脑8899

siege -c5 -r10 “http://127.0.0.1:8899 POST <./iloveroysue.json “

1
2
3
4
5
6
7
if(uri.contains("encrypt")){
result = (String) XposedHelpers.callMethod(getActivity(), "method01", paramBody);
}else if (uri.contains("decrypt")){
result = (String) XposedHelpers.callMethod(getActivity(), "method02", paramBody);
}else{
result = paramBody;
}

curl -s -X POST “http://192.168.0.103:8899/encrypt" -d ‘{“data”:onejane}’

1
2
3
objection -g com.example.demoso1 explore -P ~/.objection/plugins
plugin wallbreaker classsearch Session
android hooking watch class fi.iki.elonen.NanoHTTPD$IHTTPSession 该类不在目标进程中,在xposed的classloader中,可以hook但是objection没有切classloader的功能

image-20210503211850723

暴露公网

手机:npc file npc 查看该文件格式基本信息,执行客户端命令暴露一个公网ip出去,端口为58899

服务器:nps

image-20210503221235226

image-20210503220423867

curl -s -X POST “http://118.126.66.193:58899/encrypt" -d ‘{“data”:onejane}’

siege -c5 -r10 “http://118.126.66.193:8899 POST <./iloveroysue.json “

案例movetv

1
2
3
4
5
6
7
objection -g com.cz.babySister explore
android hooking search classes MainActivity
android hooking list class_methods com.cz.babySister.activity.MainActivity
android hooking watch class com.cz.babySister.activity.MainActivity 将所有MainActivity的方法进行hook,做任意操作打印调用方法
android hooking search classes loadedapk 搜索类名包括loadedapk的类
plugin wallbreaker objectsearch android.app.LoadedApk 内存漫游查找android.app.LoadedApk
plugin wallbreaker objectdump 0x240a 可视化内存中类的信息

image-20210504123451127

jadx-gui Desktop/movetv.apk 腾讯加固

image-20210504124146972

1
2
3
4
5
6
7
8
9
10
11
12
if (loadPackageParam.packageName.equals("com.cz.babySister")) {
Class clazz = loadPackageParam.classLoader.loadClass("com.cz.babySister.activity.MainActivity");

XposedHelpers.findAndHookMethod(clazz, "getJiFen", float.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
XposedBridge.log("float is "+ String.valueOf( (float) param.args[0]));

}
});
}

image-20210504125110937

脱壳

使用frida来hook加固的Android应用的java层APP的启动过程Android万能脱壳机FART:ART环境下基于主动调用的自动化脱壳方案

1
2
3
4
plugin wallbreaker objectsearch android.app.ActivityThread 
plugin wallbreaker objectdump --fullname 0x2a6a 取mInitialApplication的内存地址
plugin wallbreaker objectdump --fullname 0x10094e
android hooking list class_methods java.lang.ClassLoader 有findClass方法

首先,对于获取Classloader的时机点的选择。在第一节的App启动流程以及第三节中APP加壳原理和执行流程的介绍中,可以看到,APP中的Application类中的attachBaseContext和onCreate函数是app中最先执行的方法。壳都是通过替换APP的Application类并自己实现这两个函数,并在这两个函数中实现dex的解密加载,hook系统中Class和method加载执行流程中的关键函数,最后通过反射完成关键变量如最终的Classloader,Application等的替换从而完成执行权的交付。因此,我们可以选在任意一个在Application的onCreate函数执行之后才开始被调用的任意一个函数中。众所周知,对于一个正常的应用来说,最终都要由一个个的Activity来展示应用的界面并和用户完成交互,那么我们就可以选择在ActivityThread中的performLaunchActivity函数作为时机,来获取最终的应用的Classloader。选择该函数还有一个好处在于该函数和应用的最终的application同在ActivityThread类中,可以很方便获取到该类的成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......

Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
//下面通过application的getClassLoader()获取最终的Classloader,并开启线程,在新线程中完成内存中的dex的dump以及主动调用过程,由于该过程相对耗时,为了防止应用出现ANR,从而开启新线程,在新线程中进行,主要的工作都在getDexFilesByClassLoader_23
//addstart
packagename=r.packageInfo.getPackageName();
//mInitialApplication
//final java.lang.ClassLoader finalcl=cl
if(mInitialApplication!=null){
final java.lang.ClassLoader finalcl=mInitialApplication.getClassLoader();
new Thread(new Runnable() {
@Override
public void run() {
getDexFilesByClassLoader_23(finalcl);
}
}).start();

}

//addend
}
}

wget https://github.com/hanbinglengyue/FART/blob/master/FART_6.0_sourcecode.zip 套用FART_6.0_sourcecod/frameworks/base/core/java/android/app/ActivityThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
if (loadPackageParam.packageName.equals("com.cz.babySister")) {

XposedBridge.log(" has Hooked!");
XposedBridge.log("inner => " + loadPackageParam.processName);

Class ActivityThread = XposedHelpers.findClass("android.app.ActivityThread",loadPackageParam.classLoader);
XposedBridge.hookAllMethods(ActivityThread, "performLaunchActivity", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Object mInitialApplication = (Application) XposedHelpers.getObjectField(param.thisObject,"mInitialApplication");
ClassLoader finalCL = (ClassLoader) XposedHelpers.callMethod(mInitialApplication,"getClassLoader");
XposedBridge.log("found classload is => "+finalCL.toString());
Class BabyMain = (Class)XposedHelpers.callMethod(finalCL,"findClass","com.cz.babySister.activity.MainActivity");
XposedBridge.log("found final class is => "+BabyMain.getName().toString());
fart(finalCL);

}
});
}
public static Object invokeStaticMethod(String class_name,
String method_name, Class[] pareTyple, Object[] pareVaules) {

try {
Class obj_class = Class.forName(class_name);
Method method = obj_class.getMethod(method_name, pareTyple);
return method.invoke(null, pareVaules);
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;

}
public static Object getFieldOjbect(String class_name, Object obj,
String filedName) {
try {
Class obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
return field.get(obj);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;

}
public static Field getClassField(ClassLoader classloader, String class_name,
String filedName) {

try {
Class obj_class = classloader.loadClass(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
return field;
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;

}
public static ClassLoader getClassloader() {
ClassLoader resultClassloader = null;

Object currentActivityThread = invokeStaticMethod(
"android.app.ActivityThread", "currentActivityThread",
new Class[]{}, new Object[]{});
Object mBoundApplication = getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mBoundApplication");
Object loadedApkInfo = getFieldOjbect(
"android.app.ActivityThread$AppBindData",
mBoundApplication, "info");
Application mApplication = (Application) getFieldOjbect("android.app.LoadedApk", loadedApkInfo, "mApplication");
resultClassloader = mApplication.getClassLoader();
return resultClassloader;
}
public static Object getClassFieldObject(ClassLoader classloader, String class_name, Object obj,
String filedName) {

try {
Class obj_class = classloader.loadClass(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
Object result = null;
result = field.get(obj);
return result;
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;

}
public static void fart(ClassLoader clzloader) throws IOException {
ClassLoader appClassloader = clzloader;
List<Object> dexFilesArray = new ArrayList<Object>();
Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");
Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");
Object[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");
Field dexFile_fileField = null;
try {
dexFile_fileField = (Field) getClassField(appClassloader, "dalvik.system.DexPathList$Element", "dexFile");
} catch (Exception e) {
e.printStackTrace();
}
Class DexFileClazz = null;
try {
DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");
} catch (Exception e) {
e.printStackTrace();
}
Method getClassNameList_method = null;
Method defineClass_method = null;
Method dumpDexFile_method = null;
Method dumpMethodCode_method = null;

for (Method field : DexFileClazz.getDeclaredMethods()) {
if (field.getName().equals("getClassNameList")) {
getClassNameList_method = field;
getClassNameList_method.setAccessible(true);
}
if (field.getName().equals("defineClassNative")) {
defineClass_method = field;
defineClass_method.setAccessible(true);
}
if (field.getName().equals("dumpMethodCode")) {
dumpMethodCode_method = field;
dumpMethodCode_method.setAccessible(true);
}
}
Field mCookiefield = getClassField(appClassloader, "dalvik.system.DexFile", "mCookie");
for (int j = 0; j < ElementsArray.length; j++) {
Object element = ElementsArray[j];
Object dexfile = null;
try {
dexfile = (Object) dexFile_fileField.get(element);
} catch (Exception e) {
e.printStackTrace();
}
if (dexfile == null) {
continue;
}
if (dexfile != null) {
dexFilesArray.add(dexfile);
Object mcookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mCookie");
if (mcookie == null) {
continue;
}
String[] classnames = null;
try {
classnames = (String[]) getClassNameList_method.invoke(dexfile, mcookie);
} catch (Exception e) {
e.printStackTrace();
continue;
} catch (Error e) {
e.printStackTrace();
continue;
}
if (classnames != null) {
File file = new File("/sdcard/Download/dumpClass1122.txt");
if (!file.exists()) {
Log.d("TestFile", "Create the file:" );
file.createNewFile();
}
RandomAccessFile raf = new RandomAccessFile(file, "rw");
raf.seek(file.length());

for (String eachclassname : classnames) {
String log = "ClassNameis::" +eachclassname +" :: "+ dumpMethodCode_method +"::"+appClassloader.toString() +"\n";
raf.write(log.getBytes());
Log.i("classes=>",log);
}
raf.close();
}

}
}
return;
}

cat dumpClass1122.txt |grep MainActivity 脱壳拿到类包括子类,完成trace到classloader所有类的功能

image-20210504161102667

文章作者: J
文章链接: http://onejane.github.io/2021/05/01/Xposed主动调用开发/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏