capacitor-dex-editor 0.0.1

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.
@@ -0,0 +1,1191 @@
1
+ package com.aetherlink.dexeditor;
2
+
3
+ import android.util.Log;
4
+
5
+ import com.getcapacitor.JSArray;
6
+ import com.getcapacitor.JSObject;
7
+
8
+ import org.jf.dexlib2.DexFileFactory;
9
+ import org.jf.dexlib2.Opcodes;
10
+ import org.jf.dexlib2.rewriter.DexRewriter;
11
+ import org.jf.dexlib2.rewriter.Rewriter;
12
+ import org.jf.dexlib2.rewriter.RewriterModule;
13
+ import org.jf.dexlib2.rewriter.Rewriters;
14
+ import org.jf.dexlib2.dexbacked.DexBackedClassDef;
15
+ import org.jf.dexlib2.dexbacked.DexBackedDexFile;
16
+ import org.jf.dexlib2.dexbacked.DexBackedField;
17
+ import org.jf.dexlib2.dexbacked.DexBackedMethod;
18
+ import org.jf.dexlib2.iface.ClassDef;
19
+ import org.jf.dexlib2.iface.DexFile;
20
+ import org.jf.dexlib2.iface.Field;
21
+ import org.jf.dexlib2.iface.Method;
22
+ import org.jf.dexlib2.iface.MethodImplementation;
23
+ import org.jf.dexlib2.iface.instruction.Instruction;
24
+ import org.jf.dexlib2.immutable.ImmutableClassDef;
25
+ import org.jf.dexlib2.immutable.ImmutableDexFile;
26
+ import org.jf.dexlib2.immutable.ImmutableField;
27
+ import org.jf.dexlib2.immutable.ImmutableMethod;
28
+ import org.jf.dexlib2.writer.io.FileDataStore;
29
+ import org.jf.dexlib2.writer.pool.DexPool;
30
+ import org.jf.baksmali.Baksmali;
31
+ import org.jf.baksmali.BaksmaliOptions;
32
+ import org.jf.smali.Smali;
33
+ import org.jf.smali.SmaliOptions;
34
+
35
+ import org.json.JSONArray;
36
+
37
+ import java.io.BufferedWriter;
38
+ import java.io.File;
39
+ import java.io.FileInputStream;
40
+ import java.io.FileWriter;
41
+ import java.io.IOException;
42
+ import java.io.StringWriter;
43
+ import java.util.ArrayList;
44
+ import java.util.HashMap;
45
+ import java.util.HashSet;
46
+ import java.util.List;
47
+ import java.util.Map;
48
+ import java.util.Set;
49
+ import java.util.UUID;
50
+ import java.util.regex.Pattern;
51
+
52
+ /**
53
+ * DexManager - 封装 dexlib2 全部功能
54
+ * 管理多个 DEX 会话,支持加载、编辑、保存 DEX 文件
55
+ */
56
+ public class DexManager {
57
+
58
+ private static final String TAG = "DexManager";
59
+
60
+ // 存储活跃的 DEX 会话
61
+ private final Map<String, DexSession> sessions = new HashMap<>();
62
+
63
+ /**
64
+ * DEX 会话 - 存储加载的 DEX 文件及其修改状态
65
+ */
66
+ private static class DexSession {
67
+ String sessionId;
68
+ String filePath;
69
+ DexBackedDexFile originalDexFile;
70
+ List<ClassDef> modifiedClasses;
71
+ Set<String> removedClasses;
72
+ boolean modified = false;
73
+
74
+ DexSession(String sessionId, String filePath, DexBackedDexFile dexFile) {
75
+ this.sessionId = sessionId;
76
+ this.filePath = filePath;
77
+ this.originalDexFile = dexFile;
78
+ this.modifiedClasses = new ArrayList<>();
79
+ this.removedClasses = new HashSet<>();
80
+ }
81
+ }
82
+
83
+ // ==================== DEX 文件操作 ====================
84
+
85
+ /**
86
+ * 加载 DEX 文件
87
+ */
88
+ public JSObject loadDex(String path, String sessionId) throws Exception {
89
+ if (path == null || path.isEmpty()) {
90
+ throw new IllegalArgumentException("Path is required");
91
+ }
92
+
93
+ File file = new File(path);
94
+ if (!file.exists()) {
95
+ throw new IOException("File not found: " + path);
96
+ }
97
+
98
+ // 生成或使用提供的 sessionId
99
+ String sid = (sessionId != null && !sessionId.isEmpty()) ? sessionId : UUID.randomUUID().toString();
100
+
101
+ // 加载 DEX 文件 (使用官方推荐的 DexFileFactory)
102
+ DexBackedDexFile dexFile = (DexBackedDexFile) DexFileFactory.loadDexFile(
103
+ file,
104
+ Opcodes.getDefault()
105
+ );
106
+
107
+ // 创建会话
108
+ DexSession session = new DexSession(sid, path, dexFile);
109
+ sessions.put(sid, session);
110
+
111
+ Log.d(TAG, "Loaded DEX: " + path + " with session: " + sid);
112
+
113
+ JSObject result = new JSObject();
114
+ result.put("sessionId", sid);
115
+ result.put("classCount", dexFile.getClasses().size());
116
+ result.put("dexVersion", dexFile.getOpcodes().api);
117
+ return result;
118
+ }
119
+
120
+ /**
121
+ * 保存 DEX 文件
122
+ */
123
+ public void saveDex(String sessionId, String outputPath) throws Exception {
124
+ DexSession session = getSession(sessionId);
125
+
126
+ // 创建新的 DEX 文件
127
+ DexPool dexPool = new DexPool(session.originalDexFile.getOpcodes());
128
+
129
+ // 添加所有类(排除已删除的,使用修改后的版本)
130
+ Set<String> modifiedClassTypes = new HashSet<>();
131
+ for (ClassDef modifiedClass : session.modifiedClasses) {
132
+ modifiedClassTypes.add(modifiedClass.getType());
133
+ dexPool.internClass(modifiedClass);
134
+ }
135
+
136
+ for (ClassDef classDef : session.originalDexFile.getClasses()) {
137
+ String type = classDef.getType();
138
+ if (!session.removedClasses.contains(type) && !modifiedClassTypes.contains(type)) {
139
+ dexPool.internClass(classDef);
140
+ }
141
+ }
142
+
143
+ // 写入文件 (使用官方推荐方式)
144
+ File outputFile = new File(outputPath);
145
+ if (outputFile.getParentFile() != null) {
146
+ outputFile.getParentFile().mkdirs();
147
+ }
148
+
149
+ // 创建临时 DexFile 用于写入
150
+ List<ClassDef> allClasses = new ArrayList<>();
151
+ for (ClassDef c : session.modifiedClasses) {
152
+ allClasses.add(c);
153
+ }
154
+ for (ClassDef c : session.originalDexFile.getClasses()) {
155
+ if (!session.removedClasses.contains(c.getType()) && !modifiedClassTypes.contains(c.getType())) {
156
+ allClasses.add(c);
157
+ }
158
+ }
159
+
160
+ ImmutableDexFile newDexFile = new ImmutableDexFile(session.originalDexFile.getOpcodes(), allClasses);
161
+ DexFileFactory.writeDexFile(outputPath, newDexFile);
162
+
163
+ Log.d(TAG, "Saved DEX to: " + outputPath);
164
+ }
165
+
166
+ /**
167
+ * 关闭 DEX 会话
168
+ */
169
+ public void closeDex(String sessionId) {
170
+ sessions.remove(sessionId);
171
+ Log.d(TAG, "Closed session: " + sessionId);
172
+ }
173
+
174
+ /**
175
+ * 获取 DEX 文件信息
176
+ */
177
+ public JSObject getDexInfo(String sessionId) throws Exception {
178
+ DexSession session = getSession(sessionId);
179
+ DexBackedDexFile dexFile = session.originalDexFile;
180
+
181
+ JSObject info = new JSObject();
182
+ info.put("sessionId", sessionId);
183
+ info.put("filePath", session.filePath);
184
+ info.put("classCount", dexFile.getClasses().size());
185
+ info.put("stringCount", dexFile.getStringCount());
186
+ info.put("typeCount", dexFile.getTypeCount());
187
+ info.put("methodCount", dexFile.getMethodCount());
188
+ info.put("fieldCount", dexFile.getFieldCount());
189
+ info.put("dexVersion", dexFile.getOpcodes().api);
190
+ info.put("modified", session.modified);
191
+ return info;
192
+ }
193
+
194
+ // ==================== 类操作 ====================
195
+
196
+ /**
197
+ * 获取所有类列表
198
+ */
199
+ public JSArray getClasses(String sessionId) throws Exception {
200
+ DexSession session = getSession(sessionId);
201
+ JSArray classes = new JSArray();
202
+
203
+ for (ClassDef classDef : session.originalDexFile.getClasses()) {
204
+ if (!session.removedClasses.contains(classDef.getType())) {
205
+ JSObject classInfo = new JSObject();
206
+ classInfo.put("type", classDef.getType());
207
+ classInfo.put("accessFlags", classDef.getAccessFlags());
208
+ classInfo.put("superclass", classDef.getSuperclass());
209
+ classes.put(classInfo);
210
+ }
211
+ }
212
+
213
+ return classes;
214
+ }
215
+
216
+ /**
217
+ * 获取类详细信息
218
+ */
219
+ public JSObject getClassInfo(String sessionId, String className) throws Exception {
220
+ DexSession session = getSession(sessionId);
221
+ ClassDef classDef = findClass(session, className);
222
+
223
+ if (classDef == null) {
224
+ throw new IllegalArgumentException("Class not found: " + className);
225
+ }
226
+
227
+ JSObject info = new JSObject();
228
+ info.put("type", classDef.getType());
229
+ info.put("accessFlags", classDef.getAccessFlags());
230
+ info.put("superclass", classDef.getSuperclass());
231
+
232
+ // 接口
233
+ JSArray interfaces = new JSArray();
234
+ for (String iface : classDef.getInterfaces()) {
235
+ interfaces.put(iface);
236
+ }
237
+ info.put("interfaces", interfaces);
238
+
239
+ // 方法数量
240
+ int methodCount = 0;
241
+ for (Method ignored : classDef.getMethods()) {
242
+ methodCount++;
243
+ }
244
+ info.put("methodCount", methodCount);
245
+
246
+ // 字段数量
247
+ int fieldCount = 0;
248
+ for (Field ignored : classDef.getFields()) {
249
+ fieldCount++;
250
+ }
251
+ info.put("fieldCount", fieldCount);
252
+
253
+ return info;
254
+ }
255
+
256
+ /**
257
+ * 添加类
258
+ */
259
+ public void addClass(String sessionId, String smaliCode) throws Exception {
260
+ DexSession session = getSession(sessionId);
261
+
262
+ // 将 Smali 代码编译为 ClassDef
263
+ ClassDef newClass = compileSmaliToClass(smaliCode, session.originalDexFile.getOpcodes());
264
+ session.modifiedClasses.add(newClass);
265
+ session.modified = true;
266
+
267
+ Log.d(TAG, "Added class: " + newClass.getType());
268
+ }
269
+
270
+ /**
271
+ * 删除类
272
+ */
273
+ public void removeClass(String sessionId, String className) throws Exception {
274
+ DexSession session = getSession(sessionId);
275
+ session.removedClasses.add(className);
276
+ session.modified = true;
277
+
278
+ Log.d(TAG, "Removed class: " + className);
279
+ }
280
+
281
+ /**
282
+ * 重命名类
283
+ */
284
+ public void renameClass(String sessionId, String oldName, String newName) throws Exception {
285
+ DexSession session = getSession(sessionId);
286
+ ClassDef originalClass = findClass(session, oldName);
287
+
288
+ if (originalClass == null) {
289
+ throw new IllegalArgumentException("Class not found: " + oldName);
290
+ }
291
+
292
+ // 创建重命名后的类
293
+ ImmutableClassDef renamedClass = new ImmutableClassDef(
294
+ newName,
295
+ originalClass.getAccessFlags(),
296
+ originalClass.getSuperclass(),
297
+ originalClass.getInterfaces(),
298
+ originalClass.getSourceFile(),
299
+ originalClass.getAnnotations(),
300
+ originalClass.getFields(),
301
+ originalClass.getMethods()
302
+ );
303
+
304
+ session.removedClasses.add(oldName);
305
+ session.modifiedClasses.add(renamedClass);
306
+ session.modified = true;
307
+
308
+ Log.d(TAG, "Renamed class: " + oldName + " -> " + newName);
309
+ }
310
+
311
+ // ==================== 方法操作 ====================
312
+
313
+ /**
314
+ * 获取类的所有方法
315
+ */
316
+ public JSArray getMethods(String sessionId, String className) throws Exception {
317
+ DexSession session = getSession(sessionId);
318
+ ClassDef classDef = findClass(session, className);
319
+
320
+ if (classDef == null) {
321
+ throw new IllegalArgumentException("Class not found: " + className);
322
+ }
323
+
324
+ JSArray methods = new JSArray();
325
+ for (Method method : classDef.getMethods()) {
326
+ JSObject methodInfo = new JSObject();
327
+ methodInfo.put("name", method.getName());
328
+ methodInfo.put("returnType", method.getReturnType());
329
+ methodInfo.put("accessFlags", method.getAccessFlags());
330
+
331
+ // 参数类型
332
+ JSArray params = new JSArray();
333
+ for (CharSequence param : method.getParameterTypes()) {
334
+ params.put(param.toString());
335
+ }
336
+ methodInfo.put("parameters", params);
337
+
338
+ // 方法签名
339
+ StringBuilder sig = new StringBuilder("(");
340
+ for (CharSequence param : method.getParameterTypes()) {
341
+ sig.append(param);
342
+ }
343
+ sig.append(")").append(method.getReturnType());
344
+ methodInfo.put("signature", sig.toString());
345
+
346
+ methods.put(methodInfo);
347
+ }
348
+
349
+ return methods;
350
+ }
351
+
352
+ /**
353
+ * 获取方法详细信息
354
+ */
355
+ public JSObject getMethodInfo(String sessionId, String className,
356
+ String methodName, String methodSignature) throws Exception {
357
+ DexSession session = getSession(sessionId);
358
+ Method method = findMethod(session, className, methodName, methodSignature);
359
+
360
+ if (method == null) {
361
+ throw new IllegalArgumentException("Method not found: " + methodName);
362
+ }
363
+
364
+ JSObject info = new JSObject();
365
+ info.put("name", method.getName());
366
+ info.put("returnType", method.getReturnType());
367
+ info.put("accessFlags", method.getAccessFlags());
368
+ info.put("definingClass", method.getDefiningClass());
369
+
370
+ // 参数
371
+ JSArray params = new JSArray();
372
+ for (CharSequence param : method.getParameterTypes()) {
373
+ params.put(param.toString());
374
+ }
375
+ info.put("parameters", params);
376
+
377
+ // 实现信息
378
+ MethodImplementation impl = method.getImplementation();
379
+ if (impl != null) {
380
+ info.put("registerCount", impl.getRegisterCount());
381
+ int instructionCount = 0;
382
+ for (Instruction ignored : impl.getInstructions()) {
383
+ instructionCount++;
384
+ }
385
+ info.put("instructionCount", instructionCount);
386
+ }
387
+
388
+ return info;
389
+ }
390
+
391
+ /**
392
+ * 获取方法的 Smali 代码
393
+ */
394
+ public JSObject getMethodSmali(String sessionId, String className,
395
+ String methodName, String methodSignature) throws Exception {
396
+ DexSession session = getSession(sessionId);
397
+
398
+ // 获取类的完整 Smali,然后提取方法部分
399
+ String classSmali = classToSmali(sessionId, className).getString("smali");
400
+
401
+ // 简化处理:返回整个类的 Smali(实际应该解析提取特定方法)
402
+ JSObject result = new JSObject();
403
+ result.put("className", className);
404
+ result.put("methodName", methodName);
405
+ result.put("methodSignature", methodSignature);
406
+ result.put("smali", extractMethodSmali(classSmali, methodName, methodSignature));
407
+ return result;
408
+ }
409
+
410
+ /**
411
+ * 设置方法的 Smali 代码
412
+ */
413
+ public void setMethodSmali(String sessionId, String className,
414
+ String methodName, String methodSignature,
415
+ String smaliCode) throws Exception {
416
+ DexSession session = getSession(sessionId);
417
+ ClassDef classDef = findClass(session, className);
418
+
419
+ if (classDef == null) {
420
+ throw new IllegalArgumentException("Class not found: " + className);
421
+ }
422
+
423
+ // 获取原类的 Smali
424
+ String classSmali = classToSmali(sessionId, className).getString("smali");
425
+
426
+ // 替换方法
427
+ String modifiedSmali = replaceMethodInSmali(classSmali, methodName, methodSignature, smaliCode);
428
+
429
+ // 重新编译
430
+ ClassDef modifiedClass = compileSmaliToClass(modifiedSmali, session.originalDexFile.getOpcodes());
431
+
432
+ // 更新会话
433
+ session.removedClasses.add(className);
434
+ session.modifiedClasses.add(modifiedClass);
435
+ session.modified = true;
436
+
437
+ Log.d(TAG, "Modified method: " + className + "->" + methodName);
438
+ }
439
+
440
+ /**
441
+ * 添加方法
442
+ */
443
+ public void addMethod(String sessionId, String className, String smaliCode) throws Exception {
444
+ DexSession session = getSession(sessionId);
445
+ ClassDef classDef = findClass(session, className);
446
+
447
+ if (classDef == null) {
448
+ throw new IllegalArgumentException("Class not found: " + className);
449
+ }
450
+
451
+ // 获取原类 Smali 并添加新方法
452
+ String classSmali = classToSmali(sessionId, className).getString("smali");
453
+ String modifiedSmali = insertMethodToSmali(classSmali, smaliCode);
454
+
455
+ // 重新编译
456
+ ClassDef modifiedClass = compileSmaliToClass(modifiedSmali, session.originalDexFile.getOpcodes());
457
+
458
+ session.removedClasses.add(className);
459
+ session.modifiedClasses.add(modifiedClass);
460
+ session.modified = true;
461
+ }
462
+
463
+ /**
464
+ * 删除方法
465
+ */
466
+ public void removeMethod(String sessionId, String className,
467
+ String methodName, String methodSignature) throws Exception {
468
+ DexSession session = getSession(sessionId);
469
+ ClassDef classDef = findClass(session, className);
470
+
471
+ if (classDef == null) {
472
+ throw new IllegalArgumentException("Class not found: " + className);
473
+ }
474
+
475
+ // 获取原类 Smali 并删除方法
476
+ String classSmali = classToSmali(sessionId, className).getString("smali");
477
+ String modifiedSmali = removeMethodFromSmali(classSmali, methodName, methodSignature);
478
+
479
+ // 重新编译
480
+ ClassDef modifiedClass = compileSmaliToClass(modifiedSmali, session.originalDexFile.getOpcodes());
481
+
482
+ session.removedClasses.add(className);
483
+ session.modifiedClasses.add(modifiedClass);
484
+ session.modified = true;
485
+ }
486
+
487
+ // ==================== 字段操作 ====================
488
+
489
+ /**
490
+ * 获取类的所有字段
491
+ */
492
+ public JSArray getFields(String sessionId, String className) throws Exception {
493
+ DexSession session = getSession(sessionId);
494
+ ClassDef classDef = findClass(session, className);
495
+
496
+ if (classDef == null) {
497
+ throw new IllegalArgumentException("Class not found: " + className);
498
+ }
499
+
500
+ JSArray fields = new JSArray();
501
+ for (Field field : classDef.getFields()) {
502
+ JSObject fieldInfo = new JSObject();
503
+ fieldInfo.put("name", field.getName());
504
+ fieldInfo.put("type", field.getType());
505
+ fieldInfo.put("accessFlags", field.getAccessFlags());
506
+ fields.put(fieldInfo);
507
+ }
508
+
509
+ return fields;
510
+ }
511
+
512
+ /**
513
+ * 获取字段详细信息
514
+ */
515
+ public JSObject getFieldInfo(String sessionId, String className, String fieldName) throws Exception {
516
+ DexSession session = getSession(sessionId);
517
+ ClassDef classDef = findClass(session, className);
518
+
519
+ if (classDef == null) {
520
+ throw new IllegalArgumentException("Class not found: " + className);
521
+ }
522
+
523
+ for (Field field : classDef.getFields()) {
524
+ if (field.getName().equals(fieldName)) {
525
+ JSObject info = new JSObject();
526
+ info.put("name", field.getName());
527
+ info.put("type", field.getType());
528
+ info.put("accessFlags", field.getAccessFlags());
529
+ info.put("definingClass", field.getDefiningClass());
530
+ return info;
531
+ }
532
+ }
533
+
534
+ throw new IllegalArgumentException("Field not found: " + fieldName);
535
+ }
536
+
537
+ /**
538
+ * 添加字段
539
+ */
540
+ public void addField(String sessionId, String className, String fieldDef) throws Exception {
541
+ DexSession session = getSession(sessionId);
542
+ ClassDef classDef = findClass(session, className);
543
+
544
+ if (classDef == null) {
545
+ throw new IllegalArgumentException("Class not found: " + className);
546
+ }
547
+
548
+ // 获取原类 Smali 并添加字段定义
549
+ String classSmali = classToSmali(sessionId, className).getString("smali");
550
+ String modifiedSmali = insertFieldToSmali(classSmali, fieldDef);
551
+
552
+ // 重新编译
553
+ ClassDef modifiedClass = compileSmaliToClass(modifiedSmali, session.originalDexFile.getOpcodes());
554
+
555
+ session.removedClasses.add(className);
556
+ session.modifiedClasses.add(modifiedClass);
557
+ session.modified = true;
558
+ }
559
+
560
+ /**
561
+ * 删除字段
562
+ */
563
+ public void removeField(String sessionId, String className, String fieldName) throws Exception {
564
+ DexSession session = getSession(sessionId);
565
+ ClassDef classDef = findClass(session, className);
566
+
567
+ if (classDef == null) {
568
+ throw new IllegalArgumentException("Class not found: " + className);
569
+ }
570
+
571
+ // 获取原类 Smali 并删除字段
572
+ String classSmali = classToSmali(sessionId, className).getString("smali");
573
+ String modifiedSmali = removeFieldFromSmali(classSmali, fieldName);
574
+
575
+ // 重新编译
576
+ ClassDef modifiedClass = compileSmaliToClass(modifiedSmali, session.originalDexFile.getOpcodes());
577
+
578
+ session.removedClasses.add(className);
579
+ session.modifiedClasses.add(modifiedClass);
580
+ session.modified = true;
581
+ }
582
+
583
+ // ==================== Smali 操作 ====================
584
+
585
+ /**
586
+ * 将类转换为 Smali 代码
587
+ */
588
+ public JSObject classToSmali(String sessionId, String className) throws Exception {
589
+ DexSession session = getSession(sessionId);
590
+ ClassDef classDef = findClass(session, className);
591
+
592
+ if (classDef == null) {
593
+ throw new IllegalArgumentException("Class not found: " + className);
594
+ }
595
+
596
+ // 使用 baksmali 转换
597
+ StringWriter writer = new StringWriter();
598
+ BaksmaliOptions options = new BaksmaliOptions();
599
+
600
+ // 创建临时 DEX 只包含该类
601
+ List<ClassDef> singleClass = new ArrayList<>();
602
+ singleClass.add(classDef);
603
+ ImmutableDexFile singleDex = new ImmutableDexFile(
604
+ session.originalDexFile.getOpcodes(),
605
+ singleClass
606
+ );
607
+
608
+ // 使用临时目录输出
609
+ File tempDir = File.createTempFile("smali_", "_temp");
610
+ tempDir.delete();
611
+ tempDir.mkdirs();
612
+
613
+ try {
614
+ Baksmali.disassembleDexFile(singleDex, tempDir, 1, options);
615
+
616
+ // 读取生成的 smali 文件
617
+ String smaliPath = className.substring(1, className.length() - 1) + ".smali";
618
+ File smaliFile = new File(tempDir, smaliPath);
619
+
620
+ if (smaliFile.exists()) {
621
+ String smali = readFileContent(smaliFile);
622
+ JSObject result = new JSObject();
623
+ result.put("className", className);
624
+ result.put("smali", smali);
625
+ return result;
626
+ } else {
627
+ throw new IOException("Failed to generate smali for: " + className);
628
+ }
629
+ } finally {
630
+ deleteRecursive(tempDir);
631
+ }
632
+ }
633
+
634
+ /**
635
+ * 将 Smali 代码编译为类并添加到 DEX
636
+ */
637
+ public void smaliToClass(String sessionId, String smaliCode) throws Exception {
638
+ DexSession session = getSession(sessionId);
639
+ ClassDef newClass = compileSmaliToClass(smaliCode, session.originalDexFile.getOpcodes());
640
+ session.modifiedClasses.add(newClass);
641
+ session.modified = true;
642
+ }
643
+
644
+ /**
645
+ * 反汇编整个 DEX 到目录
646
+ */
647
+ public void disassemble(String sessionId, String outputDir) throws Exception {
648
+ DexSession session = getSession(sessionId);
649
+ File outDir = new File(outputDir);
650
+ outDir.mkdirs();
651
+
652
+ BaksmaliOptions options = new BaksmaliOptions();
653
+ Baksmali.disassembleDexFile(session.originalDexFile, outDir, 4, options);
654
+
655
+ Log.d(TAG, "Disassembled to: " + outputDir);
656
+ }
657
+
658
+ /**
659
+ * 汇编 Smali 目录为 DEX
660
+ */
661
+ public JSObject assemble(String smaliDir, String outputPath) throws Exception {
662
+ File inputDir = new File(smaliDir);
663
+ File outputFile = new File(outputPath);
664
+
665
+ if (!inputDir.exists() || !inputDir.isDirectory()) {
666
+ throw new IllegalArgumentException("Invalid smali directory: " + smaliDir);
667
+ }
668
+
669
+ outputFile.getParentFile().mkdirs();
670
+
671
+ SmaliOptions options = new SmaliOptions();
672
+ options.outputDexFile = outputPath;
673
+
674
+ List<File> smaliFiles = collectSmaliFiles(inputDir);
675
+ List<String> filePaths = new ArrayList<>();
676
+ for (File f : smaliFiles) {
677
+ filePaths.add(f.getAbsolutePath());
678
+ }
679
+
680
+ boolean success = Smali.assemble(options, filePaths);
681
+
682
+ JSObject result = new JSObject();
683
+ result.put("success", success);
684
+ result.put("outputPath", outputPath);
685
+ return result;
686
+ }
687
+
688
+ // ==================== 搜索操作 ====================
689
+
690
+ /**
691
+ * 搜索字符串
692
+ */
693
+ public JSArray searchString(String sessionId, String query,
694
+ boolean regex, boolean caseSensitive) throws Exception {
695
+ DexSession session = getSession(sessionId);
696
+ JSArray results = new JSArray();
697
+
698
+ Pattern pattern = null;
699
+ if (regex) {
700
+ int flags = caseSensitive ? 0 : Pattern.CASE_INSENSITIVE;
701
+ pattern = Pattern.compile(query, flags);
702
+ }
703
+
704
+ for (int i = 0; i < session.originalDexFile.getStringCount(); i++) {
705
+ String str = session.originalDexFile.getStringSection().get(i);
706
+ boolean match;
707
+
708
+ if (regex) {
709
+ match = pattern.matcher(str).find();
710
+ } else if (caseSensitive) {
711
+ match = str.contains(query);
712
+ } else {
713
+ match = str.toLowerCase().contains(query.toLowerCase());
714
+ }
715
+
716
+ if (match) {
717
+ JSObject item = new JSObject();
718
+ item.put("index", i);
719
+ item.put("value", str);
720
+ results.put(item);
721
+ }
722
+ }
723
+
724
+ return results;
725
+ }
726
+
727
+ /**
728
+ * 搜索代码
729
+ */
730
+ public JSArray searchCode(String sessionId, String query, boolean regex) throws Exception {
731
+ DexSession session = getSession(sessionId);
732
+ JSArray results = new JSArray();
733
+
734
+ Pattern pattern = regex ? Pattern.compile(query) : null;
735
+
736
+ for (ClassDef classDef : session.originalDexFile.getClasses()) {
737
+ if (session.removedClasses.contains(classDef.getType())) continue;
738
+
739
+ try {
740
+ String smali = classToSmali(sessionId, classDef.getType()).getString("smali");
741
+ boolean match = regex ? pattern.matcher(smali).find() : smali.contains(query);
742
+
743
+ if (match) {
744
+ JSObject item = new JSObject();
745
+ item.put("className", classDef.getType());
746
+ item.put("matchCount", countMatches(smali, query, regex));
747
+ results.put(item);
748
+ }
749
+ } catch (Exception e) {
750
+ Log.w(TAG, "Failed to search class: " + classDef.getType(), e);
751
+ }
752
+ }
753
+
754
+ return results;
755
+ }
756
+
757
+ /**
758
+ * 搜索方法
759
+ */
760
+ public JSArray searchMethod(String sessionId, String query) throws Exception {
761
+ DexSession session = getSession(sessionId);
762
+ JSArray results = new JSArray();
763
+ String queryLower = query.toLowerCase();
764
+
765
+ for (ClassDef classDef : session.originalDexFile.getClasses()) {
766
+ if (session.removedClasses.contains(classDef.getType())) continue;
767
+
768
+ for (Method method : classDef.getMethods()) {
769
+ if (method.getName().toLowerCase().contains(queryLower)) {
770
+ JSObject item = new JSObject();
771
+ item.put("className", classDef.getType());
772
+ item.put("methodName", method.getName());
773
+ item.put("returnType", method.getReturnType());
774
+ results.put(item);
775
+ }
776
+ }
777
+ }
778
+
779
+ return results;
780
+ }
781
+
782
+ /**
783
+ * 搜索字段
784
+ */
785
+ public JSArray searchField(String sessionId, String query) throws Exception {
786
+ DexSession session = getSession(sessionId);
787
+ JSArray results = new JSArray();
788
+ String queryLower = query.toLowerCase();
789
+
790
+ for (ClassDef classDef : session.originalDexFile.getClasses()) {
791
+ if (session.removedClasses.contains(classDef.getType())) continue;
792
+
793
+ for (Field field : classDef.getFields()) {
794
+ if (field.getName().toLowerCase().contains(queryLower)) {
795
+ JSObject item = new JSObject();
796
+ item.put("className", classDef.getType());
797
+ item.put("fieldName", field.getName());
798
+ item.put("fieldType", field.getType());
799
+ results.put(item);
800
+ }
801
+ }
802
+ }
803
+
804
+ return results;
805
+ }
806
+
807
+ // ==================== 工具操作 ====================
808
+
809
+ /**
810
+ * 修复 DEX 文件
811
+ */
812
+ public void fixDex(String inputPath, String outputPath) throws Exception {
813
+ // 读取并重新写入 DEX 来修复格式问题
814
+ File inputFile = new File(inputPath);
815
+ DexBackedDexFile dexFile = (DexBackedDexFile) DexFileFactory.loadDexFile(
816
+ inputFile,
817
+ Opcodes.getDefault()
818
+ );
819
+
820
+ DexPool dexPool = new DexPool(dexFile.getOpcodes());
821
+ for (ClassDef classDef : dexFile.getClasses()) {
822
+ dexPool.internClass(classDef);
823
+ }
824
+
825
+ File outputFile = new File(outputPath);
826
+ outputFile.getParentFile().mkdirs();
827
+ dexPool.writeTo(new FileDataStore(outputFile));
828
+
829
+ Log.d(TAG, "Fixed DEX: " + inputPath + " -> " + outputPath);
830
+ }
831
+
832
+ /**
833
+ * 合并多个 DEX 文件
834
+ */
835
+ public void mergeDex(JSONArray inputPaths, String outputPath) throws Exception {
836
+ DexPool dexPool = new DexPool(Opcodes.getDefault());
837
+
838
+ for (int i = 0; i < inputPaths.length(); i++) {
839
+ String path = inputPaths.getString(i);
840
+ DexBackedDexFile dexFile = (DexBackedDexFile) DexFileFactory.loadDexFile(
841
+ new File(path),
842
+ Opcodes.getDefault()
843
+ );
844
+
845
+ for (ClassDef classDef : dexFile.getClasses()) {
846
+ dexPool.internClass(classDef);
847
+ }
848
+ }
849
+
850
+ File outputFile = new File(outputPath);
851
+ outputFile.getParentFile().mkdirs();
852
+ dexPool.writeTo(new FileDataStore(outputFile));
853
+
854
+ Log.d(TAG, "Merged " + inputPaths.length() + " DEX files to: " + outputPath);
855
+ }
856
+
857
+ /**
858
+ * 拆分 DEX 文件
859
+ */
860
+ public JSArray splitDex(String sessionId, int maxClasses) throws Exception {
861
+ DexSession session = getSession(sessionId);
862
+ JSArray outputFiles = new JSArray();
863
+
864
+ List<ClassDef> allClasses = new ArrayList<>();
865
+ for (ClassDef classDef : session.originalDexFile.getClasses()) {
866
+ if (!session.removedClasses.contains(classDef.getType())) {
867
+ allClasses.add(classDef);
868
+ }
869
+ }
870
+
871
+ int dexIndex = 0;
872
+ for (int i = 0; i < allClasses.size(); i += maxClasses) {
873
+ DexPool dexPool = new DexPool(session.originalDexFile.getOpcodes());
874
+
875
+ int end = Math.min(i + maxClasses, allClasses.size());
876
+ for (int j = i; j < end; j++) {
877
+ dexPool.internClass(allClasses.get(j));
878
+ }
879
+
880
+ String outputPath = session.filePath.replace(".dex", "_" + dexIndex + ".dex");
881
+ dexPool.writeTo(new FileDataStore(new File(outputPath)));
882
+ outputFiles.put(outputPath);
883
+ dexIndex++;
884
+ }
885
+
886
+ return outputFiles;
887
+ }
888
+
889
+ /**
890
+ * 获取字符串常量池
891
+ */
892
+ public JSArray getStrings(String sessionId) throws Exception {
893
+ DexSession session = getSession(sessionId);
894
+ JSArray strings = new JSArray();
895
+
896
+ for (int i = 0; i < session.originalDexFile.getStringCount(); i++) {
897
+ JSObject item = new JSObject();
898
+ item.put("index", i);
899
+ item.put("value", session.originalDexFile.getStringSection().get(i));
900
+ strings.put(item);
901
+ }
902
+
903
+ return strings;
904
+ }
905
+
906
+ /**
907
+ * 修改字符串
908
+ */
909
+ public void modifyString(String sessionId, String oldString, String newString) throws Exception {
910
+ DexSession session = getSession(sessionId);
911
+
912
+ // 需要遍历所有类,替换字符串引用
913
+ for (ClassDef classDef : session.originalDexFile.getClasses()) {
914
+ if (session.removedClasses.contains(classDef.getType())) continue;
915
+
916
+ try {
917
+ String smali = classToSmali(sessionId, classDef.getType()).getString("smali");
918
+ if (smali.contains(oldString)) {
919
+ String modifiedSmali = smali.replace(oldString, newString);
920
+ ClassDef modifiedClass = compileSmaliToClass(modifiedSmali, session.originalDexFile.getOpcodes());
921
+
922
+ session.removedClasses.add(classDef.getType());
923
+ session.modifiedClasses.add(modifiedClass);
924
+ }
925
+ } catch (Exception e) {
926
+ Log.w(TAG, "Failed to modify string in class: " + classDef.getType(), e);
927
+ }
928
+ }
929
+
930
+ session.modified = true;
931
+ }
932
+
933
+ // ==================== 辅助方法 ====================
934
+
935
+ private DexSession getSession(String sessionId) throws Exception {
936
+ DexSession session = sessions.get(sessionId);
937
+ if (session == null) {
938
+ throw new IllegalArgumentException("Session not found: " + sessionId);
939
+ }
940
+ return session;
941
+ }
942
+
943
+ private ClassDef findClass(DexSession session, String className) {
944
+ // 先检查修改后的类
945
+ for (ClassDef classDef : session.modifiedClasses) {
946
+ if (classDef.getType().equals(className)) {
947
+ return classDef;
948
+ }
949
+ }
950
+
951
+ // 再检查原始类
952
+ if (!session.removedClasses.contains(className)) {
953
+ for (ClassDef classDef : session.originalDexFile.getClasses()) {
954
+ if (classDef.getType().equals(className)) {
955
+ return classDef;
956
+ }
957
+ }
958
+ }
959
+
960
+ return null;
961
+ }
962
+
963
+ private Method findMethod(DexSession session, String className,
964
+ String methodName, String methodSignature) {
965
+ ClassDef classDef = findClass(session, className);
966
+ if (classDef == null) return null;
967
+
968
+ for (Method method : classDef.getMethods()) {
969
+ if (method.getName().equals(methodName)) {
970
+ StringBuilder sig = new StringBuilder("(");
971
+ for (CharSequence param : method.getParameterTypes()) {
972
+ sig.append(param);
973
+ }
974
+ sig.append(")").append(method.getReturnType());
975
+
976
+ if (sig.toString().equals(methodSignature)) {
977
+ return method;
978
+ }
979
+ }
980
+ }
981
+
982
+ return null;
983
+ }
984
+
985
+ private ClassDef compileSmaliToClass(String smaliCode, Opcodes opcodes) throws Exception {
986
+ // 创建临时文件
987
+ File tempDir = File.createTempFile("smali_compile_", "_temp");
988
+ tempDir.delete();
989
+ tempDir.mkdirs();
990
+
991
+ try {
992
+ // 写入 smali 文件
993
+ File smaliFile = new File(tempDir, "temp.smali");
994
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(smaliFile))) {
995
+ writer.write(smaliCode);
996
+ }
997
+
998
+ // 编译
999
+ File outputDex = new File(tempDir, "output.dex");
1000
+ SmaliOptions options = new SmaliOptions();
1001
+ options.outputDexFile = outputDex.getAbsolutePath();
1002
+
1003
+ List<String> files = new ArrayList<>();
1004
+ files.add(smaliFile.getAbsolutePath());
1005
+
1006
+ if (!Smali.assemble(options, files)) {
1007
+ throw new Exception("Failed to compile smali code");
1008
+ }
1009
+
1010
+ // 读取编译后的类
1011
+ DexBackedDexFile compiledDex = DexBackedDexFile.fromInputStream(
1012
+ opcodes,
1013
+ new FileInputStream(outputDex)
1014
+ );
1015
+
1016
+ for (ClassDef classDef : compiledDex.getClasses()) {
1017
+ return classDef; // 返回第一个类
1018
+ }
1019
+
1020
+ throw new Exception("No class found in compiled smali");
1021
+ } finally {
1022
+ deleteRecursive(tempDir);
1023
+ }
1024
+ }
1025
+
1026
+ private String extractMethodSmali(String classSmali, String methodName, String signature) {
1027
+ // 简化实现:查找方法定义并提取
1028
+ String methodStart = ".method";
1029
+ String methodEnd = ".end method";
1030
+
1031
+ int searchStart = 0;
1032
+ while (true) {
1033
+ int start = classSmali.indexOf(methodStart, searchStart);
1034
+ if (start == -1) break;
1035
+
1036
+ int end = classSmali.indexOf(methodEnd, start);
1037
+ if (end == -1) break;
1038
+
1039
+ String methodBlock = classSmali.substring(start, end + methodEnd.length());
1040
+ if (methodBlock.contains(methodName)) {
1041
+ return methodBlock;
1042
+ }
1043
+
1044
+ searchStart = end + methodEnd.length();
1045
+ }
1046
+
1047
+ return "";
1048
+ }
1049
+
1050
+ private String replaceMethodInSmali(String classSmali, String methodName,
1051
+ String signature, String newMethodCode) {
1052
+ String methodStart = ".method";
1053
+ String methodEnd = ".end method";
1054
+
1055
+ int searchStart = 0;
1056
+ while (true) {
1057
+ int start = classSmali.indexOf(methodStart, searchStart);
1058
+ if (start == -1) break;
1059
+
1060
+ int end = classSmali.indexOf(methodEnd, start);
1061
+ if (end == -1) break;
1062
+
1063
+ String methodBlock = classSmali.substring(start, end + methodEnd.length());
1064
+ if (methodBlock.contains(methodName)) {
1065
+ return classSmali.substring(0, start) + newMethodCode +
1066
+ classSmali.substring(end + methodEnd.length());
1067
+ }
1068
+
1069
+ searchStart = end + methodEnd.length();
1070
+ }
1071
+
1072
+ return classSmali;
1073
+ }
1074
+
1075
+ private String insertMethodToSmali(String classSmali, String methodCode) {
1076
+ // 在类结束前插入方法
1077
+ int endClass = classSmali.lastIndexOf(".end class");
1078
+ if (endClass != -1) {
1079
+ return classSmali.substring(0, endClass) + "\n" + methodCode + "\n\n" +
1080
+ classSmali.substring(endClass);
1081
+ }
1082
+ return classSmali + "\n" + methodCode;
1083
+ }
1084
+
1085
+ private String removeMethodFromSmali(String classSmali, String methodName, String signature) {
1086
+ String methodStart = ".method";
1087
+ String methodEnd = ".end method";
1088
+
1089
+ int searchStart = 0;
1090
+ while (true) {
1091
+ int start = classSmali.indexOf(methodStart, searchStart);
1092
+ if (start == -1) break;
1093
+
1094
+ int end = classSmali.indexOf(methodEnd, start);
1095
+ if (end == -1) break;
1096
+
1097
+ String methodBlock = classSmali.substring(start, end + methodEnd.length());
1098
+ if (methodBlock.contains(methodName)) {
1099
+ return classSmali.substring(0, start) + classSmali.substring(end + methodEnd.length());
1100
+ }
1101
+
1102
+ searchStart = end + methodEnd.length();
1103
+ }
1104
+
1105
+ return classSmali;
1106
+ }
1107
+
1108
+ private String insertFieldToSmali(String classSmali, String fieldDef) {
1109
+ // 在第一个方法前或类结束前插入字段
1110
+ int methodPos = classSmali.indexOf(".method");
1111
+ if (methodPos != -1) {
1112
+ return classSmali.substring(0, methodPos) + fieldDef + "\n\n" +
1113
+ classSmali.substring(methodPos);
1114
+ }
1115
+
1116
+ int endClass = classSmali.lastIndexOf(".end class");
1117
+ if (endClass != -1) {
1118
+ return classSmali.substring(0, endClass) + fieldDef + "\n\n" +
1119
+ classSmali.substring(endClass);
1120
+ }
1121
+
1122
+ return classSmali + "\n" + fieldDef;
1123
+ }
1124
+
1125
+ private String removeFieldFromSmali(String classSmali, String fieldName) {
1126
+ // 简化实现:移除包含字段名的 .field 行
1127
+ String[] lines = classSmali.split("\n");
1128
+ StringBuilder result = new StringBuilder();
1129
+
1130
+ for (String line : lines) {
1131
+ if (!(line.trim().startsWith(".field") && line.contains(fieldName))) {
1132
+ result.append(line).append("\n");
1133
+ }
1134
+ }
1135
+
1136
+ return result.toString();
1137
+ }
1138
+
1139
+ private List<File> collectSmaliFiles(File dir) {
1140
+ List<File> files = new ArrayList<>();
1141
+ File[] children = dir.listFiles();
1142
+ if (children != null) {
1143
+ for (File child : children) {
1144
+ if (child.isDirectory()) {
1145
+ files.addAll(collectSmaliFiles(child));
1146
+ } else if (child.getName().endsWith(".smali")) {
1147
+ files.add(child);
1148
+ }
1149
+ }
1150
+ }
1151
+ return files;
1152
+ }
1153
+
1154
+ private String readFileContent(File file) throws IOException {
1155
+ StringBuilder content = new StringBuilder();
1156
+ try (java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader(file))) {
1157
+ String line;
1158
+ while ((line = reader.readLine()) != null) {
1159
+ content.append(line).append("\n");
1160
+ }
1161
+ }
1162
+ return content.toString();
1163
+ }
1164
+
1165
+ private void deleteRecursive(File file) {
1166
+ if (file.isDirectory()) {
1167
+ File[] children = file.listFiles();
1168
+ if (children != null) {
1169
+ for (File child : children) {
1170
+ deleteRecursive(child);
1171
+ }
1172
+ }
1173
+ }
1174
+ file.delete();
1175
+ }
1176
+
1177
+ private int countMatches(String text, String query, boolean regex) {
1178
+ int count = 0;
1179
+ if (regex) {
1180
+ java.util.regex.Matcher matcher = Pattern.compile(query).matcher(text);
1181
+ while (matcher.find()) count++;
1182
+ } else {
1183
+ int index = 0;
1184
+ while ((index = text.indexOf(query, index)) != -1) {
1185
+ count++;
1186
+ index += query.length();
1187
+ }
1188
+ }
1189
+ return count;
1190
+ }
1191
+ }