capacitor-dex-editor 0.0.73 → 0.0.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/aetherlink/dexeditor/CppApkHelper.java +255 -0
- package/android/src/main/java/com/aetherlink/dexeditor/CppDexHelper.java +368 -0
- package/android/src/main/java/com/aetherlink/dexeditor/DexEditorPluginPlugin.java +57 -0
- package/android/src/main/java/com/aetherlink/dexeditor/DexManager.java +60 -12
- package/package.json +1 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
package com.aetherlink.dexeditor;
|
|
2
|
+
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
import com.getcapacitor.JSArray;
|
|
5
|
+
import com.getcapacitor.JSObject;
|
|
6
|
+
import org.json.JSONArray;
|
|
7
|
+
import org.json.JSONObject;
|
|
8
|
+
|
|
9
|
+
import java.io.File;
|
|
10
|
+
import java.io.FileInputStream;
|
|
11
|
+
import java.io.FileOutputStream;
|
|
12
|
+
import java.util.zip.ZipEntry;
|
|
13
|
+
import java.util.zip.ZipFile;
|
|
14
|
+
import java.io.InputStream;
|
|
15
|
+
import java.io.ByteArrayOutputStream;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* CppApkHelper - C++ APK/资源操作的封装类
|
|
19
|
+
* 处理 AndroidManifest.xml 和 resources.arsc 的解析和编辑
|
|
20
|
+
*/
|
|
21
|
+
public class CppApkHelper {
|
|
22
|
+
private static final String TAG = "CppApkHelper";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 检查 C++ 库是否可用
|
|
26
|
+
*/
|
|
27
|
+
public static boolean isAvailable() {
|
|
28
|
+
return CppDex.isAvailable();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ==================== AndroidManifest.xml 操作 ====================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 解析 AndroidManifest.xml
|
|
35
|
+
*/
|
|
36
|
+
public static JSObject parseManifest(byte[] axmlBytes) throws Exception {
|
|
37
|
+
String jsonResult = CppDex.parseAxml(axmlBytes);
|
|
38
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
39
|
+
throw new Exception("C++ parseAxml failed");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
43
|
+
JSObject result = new JSObject();
|
|
44
|
+
|
|
45
|
+
result.put("packageName", cppResult.optString("packageName", ""));
|
|
46
|
+
result.put("versionCode", cppResult.optInt("versionCode", 0));
|
|
47
|
+
result.put("versionName", cppResult.optString("versionName", ""));
|
|
48
|
+
result.put("minSdkVersion", cppResult.optInt("minSdkVersion", 0));
|
|
49
|
+
result.put("targetSdkVersion", cppResult.optInt("targetSdkVersion", 0));
|
|
50
|
+
|
|
51
|
+
// 解析 activities
|
|
52
|
+
JSONArray activities = cppResult.optJSONArray("activities");
|
|
53
|
+
if (activities != null) {
|
|
54
|
+
JSArray activityArray = new JSArray();
|
|
55
|
+
for (int i = 0; i < activities.length(); i++) {
|
|
56
|
+
activityArray.put(activities.getString(i));
|
|
57
|
+
}
|
|
58
|
+
result.put("activities", activityArray);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 解析 permissions
|
|
62
|
+
JSONArray permissions = cppResult.optJSONArray("permissions");
|
|
63
|
+
if (permissions != null) {
|
|
64
|
+
JSArray permArray = new JSArray();
|
|
65
|
+
for (int i = 0; i < permissions.length(); i++) {
|
|
66
|
+
permArray.put(permissions.getString(i));
|
|
67
|
+
}
|
|
68
|
+
result.put("permissions", permArray);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
result.put("engine", "cpp");
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 从 APK 文件解析 AndroidManifest.xml
|
|
77
|
+
*/
|
|
78
|
+
public static JSObject parseManifestFromApk(String apkPath) throws Exception {
|
|
79
|
+
byte[] axmlBytes = readFileFromApk(apkPath, "AndroidManifest.xml");
|
|
80
|
+
return parseManifest(axmlBytes);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 编辑 AndroidManifest.xml 属性
|
|
85
|
+
* @param action 操作: set_package, set_version_name, set_version_code, set_min_sdk, set_target_sdk
|
|
86
|
+
*/
|
|
87
|
+
public static byte[] editManifest(byte[] axmlBytes, String action, String value) throws Exception {
|
|
88
|
+
byte[] newAxmlBytes = CppDex.editManifest(axmlBytes, action, value);
|
|
89
|
+
if (newAxmlBytes == null) {
|
|
90
|
+
throw new Exception("C++ editManifest failed for action: " + action);
|
|
91
|
+
}
|
|
92
|
+
return newAxmlBytes;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 在 AndroidManifest.xml 中搜索
|
|
97
|
+
*/
|
|
98
|
+
public static JSArray searchManifest(byte[] axmlBytes, String attrName, String value, int limit) throws Exception {
|
|
99
|
+
String jsonResult = CppDex.searchXml(axmlBytes, attrName, value, limit);
|
|
100
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
101
|
+
throw new Exception("C++ searchXml failed");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
105
|
+
JSONArray cppResults = cppResult.optJSONArray("results");
|
|
106
|
+
JSArray results = new JSArray();
|
|
107
|
+
if (cppResults != null) {
|
|
108
|
+
for (int i = 0; i < cppResults.length(); i++) {
|
|
109
|
+
JSONObject r = cppResults.getJSONObject(i);
|
|
110
|
+
JSObject item = new JSObject();
|
|
111
|
+
item.put("element", r.optString("element"));
|
|
112
|
+
item.put("attribute", r.optString("attribute"));
|
|
113
|
+
item.put("value", r.optString("value"));
|
|
114
|
+
results.put(item);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return results;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ==================== resources.arsc 操作 ====================
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 解析 resources.arsc
|
|
124
|
+
*/
|
|
125
|
+
public static JSObject parseArsc(byte[] arscBytes) throws Exception {
|
|
126
|
+
String jsonResult = CppDex.parseArsc(arscBytes);
|
|
127
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
128
|
+
throw new Exception("C++ parseArsc failed");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
132
|
+
JSObject result = new JSObject();
|
|
133
|
+
|
|
134
|
+
result.put("packageCount", cppResult.optInt("packageCount", 0));
|
|
135
|
+
result.put("stringCount", cppResult.optInt("stringCount", 0));
|
|
136
|
+
result.put("typeCount", cppResult.optInt("typeCount", 0));
|
|
137
|
+
|
|
138
|
+
// 解析资源类型
|
|
139
|
+
JSONArray types = cppResult.optJSONArray("types");
|
|
140
|
+
if (types != null) {
|
|
141
|
+
JSArray typeArray = new JSArray();
|
|
142
|
+
for (int i = 0; i < types.length(); i++) {
|
|
143
|
+
typeArray.put(types.getString(i));
|
|
144
|
+
}
|
|
145
|
+
result.put("types", typeArray);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
result.put("engine", "cpp");
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 从 APK 文件解析 resources.arsc
|
|
154
|
+
*/
|
|
155
|
+
public static JSObject parseArscFromApk(String apkPath) throws Exception {
|
|
156
|
+
byte[] arscBytes = readFileFromApk(apkPath, "resources.arsc");
|
|
157
|
+
return parseArsc(arscBytes);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 搜索 ARSC 字符串
|
|
162
|
+
*/
|
|
163
|
+
public static JSArray searchArscStrings(byte[] arscBytes, String pattern, int limit) throws Exception {
|
|
164
|
+
String jsonResult = CppDex.searchArscStrings(arscBytes, pattern, limit);
|
|
165
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
166
|
+
throw new Exception("C++ searchArscStrings failed");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
170
|
+
JSONArray cppResults = cppResult.optJSONArray("results");
|
|
171
|
+
JSArray results = new JSArray();
|
|
172
|
+
if (cppResults != null) {
|
|
173
|
+
for (int i = 0; i < cppResults.length(); i++) {
|
|
174
|
+
JSONObject r = cppResults.getJSONObject(i);
|
|
175
|
+
JSObject item = new JSObject();
|
|
176
|
+
item.put("value", r.optString("value"));
|
|
177
|
+
item.put("index", r.optInt("index"));
|
|
178
|
+
results.put(item);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return results;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 搜索 ARSC 资源
|
|
186
|
+
*/
|
|
187
|
+
public static JSArray searchArscResources(byte[] arscBytes, String pattern, String type, int limit) throws Exception {
|
|
188
|
+
String jsonResult = CppDex.searchArscResources(arscBytes, pattern, type != null ? type : "", limit);
|
|
189
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
190
|
+
throw new Exception("C++ searchArscResources failed");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
194
|
+
JSONArray cppResults = cppResult.optJSONArray("results");
|
|
195
|
+
JSArray results = new JSArray();
|
|
196
|
+
if (cppResults != null) {
|
|
197
|
+
for (int i = 0; i < cppResults.length(); i++) {
|
|
198
|
+
JSONObject r = cppResults.getJSONObject(i);
|
|
199
|
+
JSObject item = new JSObject();
|
|
200
|
+
item.put("name", r.optString("name"));
|
|
201
|
+
item.put("type", r.optString("type"));
|
|
202
|
+
item.put("value", r.optString("value"));
|
|
203
|
+
item.put("id", r.optString("id"));
|
|
204
|
+
results.put(item);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return results;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 从 APK 搜索 ARSC 字符串
|
|
212
|
+
*/
|
|
213
|
+
public static JSArray searchArscStringsFromApk(String apkPath, String pattern, int limit) throws Exception {
|
|
214
|
+
byte[] arscBytes = readFileFromApk(apkPath, "resources.arsc");
|
|
215
|
+
return searchArscStrings(arscBytes, pattern, limit);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 从 APK 搜索 ARSC 资源
|
|
220
|
+
*/
|
|
221
|
+
public static JSArray searchArscResourcesFromApk(String apkPath, String pattern, String type, int limit) throws Exception {
|
|
222
|
+
byte[] arscBytes = readFileFromApk(apkPath, "resources.arsc");
|
|
223
|
+
return searchArscResources(arscBytes, pattern, type, limit);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ==================== 辅助方法 ====================
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 从 APK 中读取文件
|
|
230
|
+
*/
|
|
231
|
+
public static byte[] readFileFromApk(String apkPath, String entryName) throws Exception {
|
|
232
|
+
ZipFile zipFile = null;
|
|
233
|
+
try {
|
|
234
|
+
zipFile = new ZipFile(apkPath);
|
|
235
|
+
ZipEntry entry = zipFile.getEntry(entryName);
|
|
236
|
+
if (entry == null) {
|
|
237
|
+
throw new Exception("Entry not found in APK: " + entryName);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
InputStream is = zipFile.getInputStream(entry);
|
|
241
|
+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
242
|
+
byte[] buffer = new byte[8192];
|
|
243
|
+
int len;
|
|
244
|
+
while ((len = is.read(buffer)) != -1) {
|
|
245
|
+
baos.write(buffer, 0, len);
|
|
246
|
+
}
|
|
247
|
+
is.close();
|
|
248
|
+
return baos.toByteArray();
|
|
249
|
+
} finally {
|
|
250
|
+
if (zipFile != null) {
|
|
251
|
+
try { zipFile.close(); } catch (Exception ignored) {}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
package com.aetherlink.dexeditor;
|
|
2
|
+
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
import com.getcapacitor.JSArray;
|
|
5
|
+
import com.getcapacitor.JSObject;
|
|
6
|
+
import org.json.JSONArray;
|
|
7
|
+
import org.json.JSONObject;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* CppDexHelper - C++ DEX 操作的封装类
|
|
11
|
+
* 提供高层 API,自动处理 JSON 解析和错误处理
|
|
12
|
+
*/
|
|
13
|
+
public class CppDexHelper {
|
|
14
|
+
private static final String TAG = "CppDexHelper";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 检查 C++ 库是否可用
|
|
18
|
+
*/
|
|
19
|
+
public static boolean isAvailable() {
|
|
20
|
+
return CppDex.isAvailable();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ==================== DEX 信息获取 ====================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 获取 DEX 文件信息
|
|
27
|
+
*/
|
|
28
|
+
public static JSObject getDexInfo(byte[] dexBytes) throws Exception {
|
|
29
|
+
String jsonResult = CppDex.getDexInfo(dexBytes);
|
|
30
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
31
|
+
throw new Exception("C++ getDexInfo failed");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
35
|
+
JSObject info = new JSObject();
|
|
36
|
+
info.put("classCount", cppResult.optInt("classCount", 0));
|
|
37
|
+
info.put("methodCount", cppResult.optInt("methodCount", 0));
|
|
38
|
+
info.put("fieldCount", cppResult.optInt("fieldCount", 0));
|
|
39
|
+
info.put("stringCount", cppResult.optInt("stringCount", 0));
|
|
40
|
+
info.put("version", cppResult.optInt("version", 35));
|
|
41
|
+
info.put("engine", "cpp");
|
|
42
|
+
return info;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 列出类
|
|
47
|
+
*/
|
|
48
|
+
public static JSArray listClasses(byte[] dexBytes, String packageFilter, int offset, int limit) throws Exception {
|
|
49
|
+
String jsonResult = CppDex.listClasses(dexBytes, packageFilter, offset, limit);
|
|
50
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
51
|
+
throw new Exception("C++ listClasses failed");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
55
|
+
JSONArray cppClasses = cppResult.optJSONArray("classes");
|
|
56
|
+
JSArray classes = new JSArray();
|
|
57
|
+
if (cppClasses != null) {
|
|
58
|
+
for (int i = 0; i < cppClasses.length(); i++) {
|
|
59
|
+
classes.put(cppClasses.getString(i));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return classes;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 列出方法
|
|
67
|
+
*/
|
|
68
|
+
public static JSArray listMethods(byte[] dexBytes, String className) throws Exception {
|
|
69
|
+
String jsonResult = CppDex.listMethods(dexBytes, className);
|
|
70
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
71
|
+
throw new Exception("C++ listMethods failed");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
75
|
+
JSONArray cppMethods = cppResult.optJSONArray("methods");
|
|
76
|
+
JSArray methods = new JSArray();
|
|
77
|
+
if (cppMethods != null) {
|
|
78
|
+
for (int i = 0; i < cppMethods.length(); i++) {
|
|
79
|
+
JSONObject m = cppMethods.getJSONObject(i);
|
|
80
|
+
JSObject methodInfo = new JSObject();
|
|
81
|
+
methodInfo.put("name", m.optString("name"));
|
|
82
|
+
methodInfo.put("signature", m.optString("prototype"));
|
|
83
|
+
methodInfo.put("accessFlags", m.optInt("accessFlags"));
|
|
84
|
+
methods.put(methodInfo);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return methods;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 列出字段
|
|
92
|
+
*/
|
|
93
|
+
public static JSArray listFields(byte[] dexBytes, String className) throws Exception {
|
|
94
|
+
String jsonResult = CppDex.listFields(dexBytes, className);
|
|
95
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
96
|
+
throw new Exception("C++ listFields failed");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
100
|
+
JSONArray cppFields = cppResult.optJSONArray("fields");
|
|
101
|
+
JSArray fields = new JSArray();
|
|
102
|
+
if (cppFields != null) {
|
|
103
|
+
for (int i = 0; i < cppFields.length(); i++) {
|
|
104
|
+
JSONObject f = cppFields.getJSONObject(i);
|
|
105
|
+
JSObject fieldInfo = new JSObject();
|
|
106
|
+
fieldInfo.put("name", f.optString("name"));
|
|
107
|
+
fieldInfo.put("type", f.optString("type"));
|
|
108
|
+
fieldInfo.put("accessFlags", f.optInt("accessFlags"));
|
|
109
|
+
fields.put(fieldInfo);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return fields;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 列出字符串池
|
|
117
|
+
*/
|
|
118
|
+
public static JSObject listStrings(byte[] dexBytes, String filter, int limit) throws Exception {
|
|
119
|
+
String jsonResult = CppDex.listStrings(dexBytes, filter != null ? filter : "", limit);
|
|
120
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
121
|
+
throw new Exception("C++ listStrings failed");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
125
|
+
JSObject result = new JSObject();
|
|
126
|
+
|
|
127
|
+
JSONArray cppStrings = cppResult.optJSONArray("strings");
|
|
128
|
+
JSArray strings = new JSArray();
|
|
129
|
+
if (cppStrings != null) {
|
|
130
|
+
for (int i = 0; i < cppStrings.length(); i++) {
|
|
131
|
+
strings.put(cppStrings.getString(i));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
result.put("strings", strings);
|
|
135
|
+
result.put("total", cppResult.optInt("total", strings.length()));
|
|
136
|
+
result.put("engine", "cpp");
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ==================== Smali 操作 ====================
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 获取类的 Smali 代码
|
|
144
|
+
*/
|
|
145
|
+
public static String getClassSmali(byte[] dexBytes, String className) throws Exception {
|
|
146
|
+
String jsonResult = CppDex.getClassSmali(dexBytes, className);
|
|
147
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
148
|
+
throw new Exception("C++ getClassSmali failed");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
152
|
+
return cppResult.optString("smali", "");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 获取方法的 Smali 代码
|
|
157
|
+
*/
|
|
158
|
+
public static String getMethodSmali(byte[] dexBytes, String className,
|
|
159
|
+
String methodName, String methodSignature) throws Exception {
|
|
160
|
+
String jsonResult = CppDex.getMethodSmali(dexBytes, className, methodName, methodSignature);
|
|
161
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
162
|
+
throw new Exception("C++ getMethodSmali failed");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
166
|
+
return cppResult.optString("smali", "");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Smali 转 Java 伪代码
|
|
171
|
+
*/
|
|
172
|
+
public static String smaliToJava(byte[] dexBytes, String className) throws Exception {
|
|
173
|
+
String jsonResult = CppDex.smaliToJava(dexBytes, className);
|
|
174
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
175
|
+
throw new Exception("C++ smaliToJava failed");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
179
|
+
return cppResult.optString("java", "");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Smali 编译为 DEX
|
|
184
|
+
*/
|
|
185
|
+
public static byte[] smaliToDex(String smaliCode) throws Exception {
|
|
186
|
+
byte[] dexBytes = CppDex.smaliToDex(smaliCode);
|
|
187
|
+
if (dexBytes == null || dexBytes.length == 0) {
|
|
188
|
+
throw new Exception("C++ smaliToDex failed");
|
|
189
|
+
}
|
|
190
|
+
return dexBytes;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ==================== 搜索操作 ====================
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 搜索字符串
|
|
197
|
+
*/
|
|
198
|
+
public static JSArray searchStrings(byte[] dexBytes, String query,
|
|
199
|
+
boolean caseSensitive, int maxResults) throws Exception {
|
|
200
|
+
String jsonResult = CppDex.searchInDex(dexBytes, query, "string", caseSensitive, maxResults);
|
|
201
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
202
|
+
throw new Exception("C++ searchStrings failed");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
206
|
+
JSONArray cppResults = cppResult.optJSONArray("results");
|
|
207
|
+
JSArray results = new JSArray();
|
|
208
|
+
if (cppResults != null) {
|
|
209
|
+
for (int i = 0; i < cppResults.length(); i++) {
|
|
210
|
+
JSONObject r = cppResults.getJSONObject(i);
|
|
211
|
+
JSObject item = new JSObject();
|
|
212
|
+
item.put("value", r.optString("value"));
|
|
213
|
+
item.put("index", r.optInt("index"));
|
|
214
|
+
results.put(item);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return results;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 搜索方法
|
|
222
|
+
*/
|
|
223
|
+
public static JSArray searchMethods(byte[] dexBytes, String query, int maxResults) throws Exception {
|
|
224
|
+
String jsonResult = CppDex.searchInDex(dexBytes, query, "method", false, maxResults);
|
|
225
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
226
|
+
throw new Exception("C++ searchMethods failed");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
230
|
+
JSONArray cppResults = cppResult.optJSONArray("results");
|
|
231
|
+
JSArray results = new JSArray();
|
|
232
|
+
if (cppResults != null) {
|
|
233
|
+
for (int i = 0; i < cppResults.length(); i++) {
|
|
234
|
+
JSONObject r = cppResults.getJSONObject(i);
|
|
235
|
+
JSObject item = new JSObject();
|
|
236
|
+
item.put("className", r.optString("className"));
|
|
237
|
+
item.put("methodName", r.optString("name"));
|
|
238
|
+
item.put("returnType", r.optString("returnType", ""));
|
|
239
|
+
results.put(item);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return results;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 搜索字段
|
|
247
|
+
*/
|
|
248
|
+
public static JSArray searchFields(byte[] dexBytes, String query, int maxResults) throws Exception {
|
|
249
|
+
String jsonResult = CppDex.searchInDex(dexBytes, query, "field", false, maxResults);
|
|
250
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
251
|
+
throw new Exception("C++ searchFields failed");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
255
|
+
JSONArray cppResults = cppResult.optJSONArray("results");
|
|
256
|
+
JSArray results = new JSArray();
|
|
257
|
+
if (cppResults != null) {
|
|
258
|
+
for (int i = 0; i < cppResults.length(); i++) {
|
|
259
|
+
JSONObject r = cppResults.getJSONObject(i);
|
|
260
|
+
JSObject item = new JSObject();
|
|
261
|
+
item.put("className", r.optString("className"));
|
|
262
|
+
item.put("fieldName", r.optString("name"));
|
|
263
|
+
item.put("fieldType", r.optString("type", ""));
|
|
264
|
+
results.put(item);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return results;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ==================== 交叉引用分析 ====================
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 查找方法交叉引用
|
|
274
|
+
*/
|
|
275
|
+
public static JSObject findMethodXrefs(byte[] dexBytes, String className, String methodName) throws Exception {
|
|
276
|
+
String jsonResult = CppDex.findMethodXrefs(dexBytes, className, methodName);
|
|
277
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
278
|
+
throw new Exception("C++ findMethodXrefs failed");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
282
|
+
JSObject result = new JSObject();
|
|
283
|
+
result.put("className", className);
|
|
284
|
+
result.put("methodName", methodName);
|
|
285
|
+
|
|
286
|
+
JSONArray xrefs = cppResult.optJSONArray("xrefs");
|
|
287
|
+
JSArray xrefArray = new JSArray();
|
|
288
|
+
if (xrefs != null) {
|
|
289
|
+
for (int i = 0; i < xrefs.length(); i++) {
|
|
290
|
+
JSONObject x = xrefs.getJSONObject(i);
|
|
291
|
+
JSObject xref = new JSObject();
|
|
292
|
+
xref.put("callerClass", x.optString("callerClass"));
|
|
293
|
+
xref.put("callerMethod", x.optString("callerMethod"));
|
|
294
|
+
xref.put("offset", x.optInt("offset"));
|
|
295
|
+
xrefArray.put(xref);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
result.put("xrefs", xrefArray);
|
|
299
|
+
result.put("count", xrefArray.length());
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* 查找字段交叉引用
|
|
305
|
+
*/
|
|
306
|
+
public static JSObject findFieldXrefs(byte[] dexBytes, String className, String fieldName) throws Exception {
|
|
307
|
+
String jsonResult = CppDex.findFieldXrefs(dexBytes, className, fieldName);
|
|
308
|
+
if (jsonResult == null || jsonResult.contains("\"error\"")) {
|
|
309
|
+
throw new Exception("C++ findFieldXrefs failed");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
JSONObject cppResult = new JSONObject(jsonResult);
|
|
313
|
+
JSObject result = new JSObject();
|
|
314
|
+
result.put("className", className);
|
|
315
|
+
result.put("fieldName", fieldName);
|
|
316
|
+
|
|
317
|
+
JSONArray xrefs = cppResult.optJSONArray("xrefs");
|
|
318
|
+
JSArray xrefArray = new JSArray();
|
|
319
|
+
if (xrefs != null) {
|
|
320
|
+
for (int i = 0; i < xrefs.length(); i++) {
|
|
321
|
+
JSONObject x = xrefs.getJSONObject(i);
|
|
322
|
+
JSObject xref = new JSObject();
|
|
323
|
+
xref.put("accessorClass", x.optString("accessorClass"));
|
|
324
|
+
xref.put("accessorMethod", x.optString("accessorMethod"));
|
|
325
|
+
xref.put("accessType", x.optString("accessType"));
|
|
326
|
+
xrefArray.put(xref);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
result.put("xrefs", xrefArray);
|
|
330
|
+
result.put("count", xrefArray.length());
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ==================== DEX 修改操作 ====================
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 添加类
|
|
338
|
+
*/
|
|
339
|
+
public static byte[] addClass(byte[] dexBytes, String smaliCode) throws Exception {
|
|
340
|
+
byte[] newDexBytes = CppDex.addClass(dexBytes, smaliCode);
|
|
341
|
+
if (newDexBytes == null) {
|
|
342
|
+
throw new Exception("C++ addClass failed");
|
|
343
|
+
}
|
|
344
|
+
return newDexBytes;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* 删除类
|
|
349
|
+
*/
|
|
350
|
+
public static byte[] deleteClass(byte[] dexBytes, String className) throws Exception {
|
|
351
|
+
byte[] newDexBytes = CppDex.deleteClass(dexBytes, className);
|
|
352
|
+
if (newDexBytes == null) {
|
|
353
|
+
throw new Exception("C++ deleteClass failed");
|
|
354
|
+
}
|
|
355
|
+
return newDexBytes;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* 修改类
|
|
360
|
+
*/
|
|
361
|
+
public static byte[] modifyClass(byte[] dexBytes, String className, String newSmali) throws Exception {
|
|
362
|
+
byte[] newDexBytes = CppDex.modifyClass(dexBytes, className, newSmali);
|
|
363
|
+
if (newDexBytes == null) {
|
|
364
|
+
throw new Exception("C++ modifyClass failed");
|
|
365
|
+
}
|
|
366
|
+
return newDexBytes;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
@@ -358,6 +358,63 @@ public class DexEditorPluginPlugin extends Plugin {
|
|
|
358
358
|
result.put("data", dexManager.getStrings(params.getString("sessionId")));
|
|
359
359
|
break;
|
|
360
360
|
|
|
361
|
+
case "listStrings":
|
|
362
|
+
// C++ 实现的字符串列表
|
|
363
|
+
if (CppApkHelper.isAvailable()) {
|
|
364
|
+
byte[] dexBytes = dexManager.getSessionDexBytes(params.getString("sessionId"));
|
|
365
|
+
if (dexBytes != null) {
|
|
366
|
+
result.put("data", CppDexHelper.listStrings(
|
|
367
|
+
dexBytes,
|
|
368
|
+
params.optString("filter", ""),
|
|
369
|
+
params.optInt("limit", 100)
|
|
370
|
+
));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
break;
|
|
374
|
+
|
|
375
|
+
// ============ C++ APK/资源操作 ============
|
|
376
|
+
case "parseManifestCpp":
|
|
377
|
+
result.put("data", CppApkHelper.parseManifestFromApk(
|
|
378
|
+
params.getString("apkPath")
|
|
379
|
+
));
|
|
380
|
+
break;
|
|
381
|
+
|
|
382
|
+
case "searchManifestCpp":
|
|
383
|
+
byte[] axmlBytes = CppApkHelper.readFileFromApk(
|
|
384
|
+
params.getString("apkPath"),
|
|
385
|
+
"AndroidManifest.xml"
|
|
386
|
+
);
|
|
387
|
+
result.put("data", CppApkHelper.searchManifest(
|
|
388
|
+
axmlBytes,
|
|
389
|
+
params.optString("attrName", ""),
|
|
390
|
+
params.optString("value", ""),
|
|
391
|
+
params.optInt("limit", 50)
|
|
392
|
+
));
|
|
393
|
+
break;
|
|
394
|
+
|
|
395
|
+
case "parseArscCpp":
|
|
396
|
+
result.put("data", CppApkHelper.parseArscFromApk(
|
|
397
|
+
params.getString("apkPath")
|
|
398
|
+
));
|
|
399
|
+
break;
|
|
400
|
+
|
|
401
|
+
case "searchArscStrings":
|
|
402
|
+
result.put("data", CppApkHelper.searchArscStringsFromApk(
|
|
403
|
+
params.getString("apkPath"),
|
|
404
|
+
params.getString("pattern"),
|
|
405
|
+
params.optInt("limit", 50)
|
|
406
|
+
));
|
|
407
|
+
break;
|
|
408
|
+
|
|
409
|
+
case "searchArscResources":
|
|
410
|
+
result.put("data", CppApkHelper.searchArscResourcesFromApk(
|
|
411
|
+
params.getString("apkPath"),
|
|
412
|
+
params.getString("pattern"),
|
|
413
|
+
params.optString("type", ""),
|
|
414
|
+
params.optInt("limit", 50)
|
|
415
|
+
));
|
|
416
|
+
break;
|
|
417
|
+
|
|
361
418
|
case "modifyString":
|
|
362
419
|
dexManager.modifyString(
|
|
363
420
|
params.getString("sessionId"),
|
|
@@ -273,6 +273,14 @@ public class DexManager {
|
|
|
273
273
|
Log.d(TAG, "Closed session: " + sessionId);
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
/**
|
|
277
|
+
* 获取会话的 DEX 字节数据(用于 C++ 操作)
|
|
278
|
+
*/
|
|
279
|
+
public byte[] getSessionDexBytes(String sessionId) {
|
|
280
|
+
DexSession session = sessions.get(sessionId);
|
|
281
|
+
return session != null ? session.dexBytes : null;
|
|
282
|
+
}
|
|
283
|
+
|
|
276
284
|
/**
|
|
277
285
|
* 获取 DEX 文件信息(优先使用 C++ 实现)
|
|
278
286
|
*/
|
|
@@ -641,28 +649,39 @@ public class DexManager {
|
|
|
641
649
|
}
|
|
642
650
|
|
|
643
651
|
/**
|
|
644
|
-
* 设置方法的 Smali
|
|
652
|
+
* 设置方法的 Smali 代码(优先使用 C++ 实现)
|
|
645
653
|
*/
|
|
646
654
|
public void setMethodSmali(String sessionId, String className,
|
|
647
655
|
String methodName, String methodSignature,
|
|
648
656
|
String smaliCode) throws Exception {
|
|
649
657
|
DexSession session = getSession(sessionId);
|
|
658
|
+
|
|
659
|
+
// 获取原类的 Smali 并替换方法
|
|
660
|
+
String classSmali = classToSmali(sessionId, className).getString("smali");
|
|
661
|
+
String modifiedSmali = replaceMethodInSmali(classSmali, methodName, methodSignature, smaliCode);
|
|
662
|
+
|
|
663
|
+
// 优先使用 C++ 实现
|
|
664
|
+
if (CppDex.isAvailable() && session.dexBytes != null) {
|
|
665
|
+
try {
|
|
666
|
+
byte[] newDexBytes = CppDex.modifyClass(session.dexBytes, className, modifiedSmali);
|
|
667
|
+
if (newDexBytes != null) {
|
|
668
|
+
session.dexBytes = newDexBytes;
|
|
669
|
+
session.modified = true;
|
|
670
|
+
Log.d(TAG, "Modified method via C++: " + className + "->" + methodName);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
} catch (Exception e) {
|
|
674
|
+
Log.w(TAG, "C++ modifyClass failed, fallback to Java", e);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Java 回退实现
|
|
650
679
|
ClassDef classDef = findClass(session, className);
|
|
651
|
-
|
|
652
680
|
if (classDef == null) {
|
|
653
681
|
throw new IllegalArgumentException("Class not found: " + className);
|
|
654
682
|
}
|
|
655
|
-
|
|
656
|
-
// 获取原类的 Smali
|
|
657
|
-
String classSmali = classToSmali(sessionId, className).getString("smali");
|
|
658
683
|
|
|
659
|
-
// 替换方法
|
|
660
|
-
String modifiedSmali = replaceMethodInSmali(classSmali, methodName, methodSignature, smaliCode);
|
|
661
|
-
|
|
662
|
-
// 重新编译
|
|
663
684
|
ClassDef modifiedClass = compileSmaliToClass(modifiedSmali, session.originalDexFile.getOpcodes());
|
|
664
|
-
|
|
665
|
-
// 更新会话
|
|
666
685
|
session.removedClasses.add(className);
|
|
667
686
|
session.modifiedClasses.add(modifiedClass);
|
|
668
687
|
session.modified = true;
|
|
@@ -933,7 +952,7 @@ public class DexManager {
|
|
|
933
952
|
}
|
|
934
953
|
|
|
935
954
|
/**
|
|
936
|
-
* 汇编 Smali 目录为 DEX
|
|
955
|
+
* 汇编 Smali 目录为 DEX(优先使用 C++ 实现)
|
|
937
956
|
*/
|
|
938
957
|
public JSObject assemble(String smaliDir, String outputPath) throws Exception {
|
|
939
958
|
File inputDir = new File(smaliDir);
|
|
@@ -944,7 +963,35 @@ public class DexManager {
|
|
|
944
963
|
}
|
|
945
964
|
|
|
946
965
|
outputFile.getParentFile().mkdirs();
|
|
966
|
+
|
|
967
|
+
// 优先使用 C++ 实现
|
|
968
|
+
if (CppDex.isAvailable()) {
|
|
969
|
+
try {
|
|
970
|
+
// 读取所有 smali 文件并合并
|
|
971
|
+
List<File> smaliFiles = collectSmaliFiles(inputDir);
|
|
972
|
+
StringBuilder allSmali = new StringBuilder();
|
|
973
|
+
for (File f : smaliFiles) {
|
|
974
|
+
allSmali.append(readFileContent(f));
|
|
975
|
+
allSmali.append("\n\n");
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
byte[] dexBytes = CppDex.smaliToDex(allSmali.toString());
|
|
979
|
+
if (dexBytes != null && dexBytes.length > 0) {
|
|
980
|
+
try (java.io.FileOutputStream fos = new java.io.FileOutputStream(outputFile)) {
|
|
981
|
+
fos.write(dexBytes);
|
|
982
|
+
}
|
|
983
|
+
JSObject result = new JSObject();
|
|
984
|
+
result.put("success", true);
|
|
985
|
+
result.put("outputPath", outputPath);
|
|
986
|
+
result.put("engine", "cpp");
|
|
987
|
+
return result;
|
|
988
|
+
}
|
|
989
|
+
} catch (Exception e) {
|
|
990
|
+
Log.w(TAG, "C++ smaliToDex failed, fallback to Java", e);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
947
993
|
|
|
994
|
+
// Java 回退实现
|
|
948
995
|
SmaliOptions options = new SmaliOptions();
|
|
949
996
|
options.outputDexFile = outputPath;
|
|
950
997
|
|
|
@@ -959,6 +1006,7 @@ public class DexManager {
|
|
|
959
1006
|
JSObject result = new JSObject();
|
|
960
1007
|
result.put("success", success);
|
|
961
1008
|
result.put("outputPath", outputPath);
|
|
1009
|
+
result.put("engine", "java");
|
|
962
1010
|
return result;
|
|
963
1011
|
}
|
|
964
1012
|
|