capacitor-dex-editor 0.0.74 → 0.0.76
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/cpp/CMakeLists.txt +7 -0
- 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 +8 -0
- package/package.json +1 -1
|
@@ -51,6 +51,13 @@ target_compile_options(dex_cpp PRIVATE
|
|
|
51
51
|
-fvisibility=hidden
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
+
# 链接选项 - 16KB 页面对齐 (Android 15+ 兼容性)
|
|
55
|
+
# 参考: https://developer.android.com/guide/practices/page-sizes
|
|
56
|
+
target_link_options(dex_cpp PRIVATE
|
|
57
|
+
-Wl,-z,max-page-size=16384
|
|
58
|
+
-Wl,-z,common-page-size=16384
|
|
59
|
+
)
|
|
60
|
+
|
|
54
61
|
# 定义宏
|
|
55
62
|
target_compile_definitions(dex_cpp PRIVATE
|
|
56
63
|
ANDROID
|
|
@@ -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
|
*/
|