capacitor-dex-editor 0.0.77 → 0.0.79
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/build.gradle
CHANGED
|
@@ -77,9 +77,9 @@ dependencies {
|
|
|
77
77
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
78
78
|
|
|
79
79
|
// dexlib2 & smali - DEX文件编辑核心库 (使用 api 暴露给主项目)
|
|
80
|
+
// baksmali 已移除 - 使用 C++ 实现
|
|
80
81
|
api 'com.android.tools.smali:smali-dexlib2:3.0.3'
|
|
81
82
|
api 'com.android.tools.smali:smali:3.0.3'
|
|
82
|
-
api 'com.android.tools.smali:smali-baksmali:3.0.3'
|
|
83
83
|
|
|
84
84
|
// Guava - dexlib2 依赖
|
|
85
85
|
api 'com.google.guava:guava:32.1.3-android'
|
|
@@ -121,112 +121,168 @@ Java_com_aetherlink_dexeditor_CppDex_listClasses(JNIEnv* env, jclass, jbyteArray
|
|
|
121
121
|
return string_to_jstring(env, result.dump());
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
dex::DexParser parser;
|
|
133
|
-
if (!parser.parse(data)) {
|
|
134
|
-
json error = {{"error", "Failed to parse DEX"}};
|
|
135
|
-
return string_to_jstring(env, error.dump());
|
|
124
|
+
// Helper: Safe ASCII lowercase (UTF-8 safe - only converts ASCII a-z)
|
|
125
|
+
static std::string safe_tolower_ascii(const std::string& s) {
|
|
126
|
+
std::string result = s;
|
|
127
|
+
for (char& c : result) {
|
|
128
|
+
if (c >= 'A' && c <= 'Z') {
|
|
129
|
+
c = c + ('a' - 'A');
|
|
130
|
+
}
|
|
136
131
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
std::transform(q_lower.begin(), q_lower.end(), q_lower.begin(), ::tolower);
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Helper: Safe string contains check (UTF-8 safe)
|
|
136
|
+
static bool safe_contains(const std::string& haystack, const std::string& needle, bool caseSensitive) {
|
|
137
|
+
if (caseSensitive) {
|
|
138
|
+
return haystack.find(needle) != std::string::npos;
|
|
145
139
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
140
|
+
// Only lowercase ASCII for case-insensitive search
|
|
141
|
+
std::string h_lower = safe_tolower_ascii(haystack);
|
|
142
|
+
std::string n_lower = safe_tolower_ascii(needle);
|
|
143
|
+
return h_lower.find(n_lower) != std::string::npos;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Helper: Sanitize string for JSON (replace invalid UTF-8 sequences)
|
|
147
|
+
static std::string sanitize_utf8(const std::string& s) {
|
|
148
|
+
std::string result;
|
|
149
|
+
result.reserve(s.size());
|
|
150
|
+
size_t i = 0;
|
|
151
|
+
while (i < s.size()) {
|
|
152
|
+
unsigned char c = s[i];
|
|
153
|
+
if (c < 0x80) {
|
|
154
|
+
// ASCII
|
|
155
|
+
result += c;
|
|
156
|
+
i++;
|
|
157
|
+
} else if ((c & 0xE0) == 0xC0 && i + 1 < s.size()) {
|
|
158
|
+
// 2-byte UTF-8
|
|
159
|
+
if ((s[i+1] & 0xC0) == 0x80) {
|
|
160
|
+
result += s[i];
|
|
161
|
+
result += s[i+1];
|
|
162
|
+
i += 2;
|
|
163
|
+
} else {
|
|
164
|
+
result += '?';
|
|
165
|
+
i++;
|
|
160
166
|
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
} else if ((c & 0xF0) == 0xE0 && i + 2 < s.size()) {
|
|
168
|
+
// 3-byte UTF-8
|
|
169
|
+
if ((s[i+1] & 0xC0) == 0x80 && (s[i+2] & 0xC0) == 0x80) {
|
|
170
|
+
result += s[i];
|
|
171
|
+
result += s[i+1];
|
|
172
|
+
result += s[i+2];
|
|
173
|
+
i += 3;
|
|
174
|
+
} else {
|
|
175
|
+
result += '?';
|
|
176
|
+
i++;
|
|
171
177
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
} else if ((c & 0xF8) == 0xF0 && i + 3 < s.size()) {
|
|
179
|
+
// 4-byte UTF-8
|
|
180
|
+
if ((s[i+1] & 0xC0) == 0x80 && (s[i+2] & 0xC0) == 0x80 && (s[i+3] & 0xC0) == 0x80) {
|
|
181
|
+
result += s[i];
|
|
182
|
+
result += s[i+1];
|
|
183
|
+
result += s[i+2];
|
|
184
|
+
result += s[i+3];
|
|
185
|
+
i += 4;
|
|
186
|
+
} else {
|
|
187
|
+
result += '?';
|
|
188
|
+
i++;
|
|
176
189
|
}
|
|
190
|
+
} else {
|
|
191
|
+
// Invalid UTF-8, replace with ?
|
|
192
|
+
result += '?';
|
|
193
|
+
i++;
|
|
177
194
|
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
JNIEXPORT jstring JNICALL
|
|
200
|
+
Java_com_aetherlink_dexeditor_CppDex_searchInDex(JNIEnv* env, jclass, jbyteArray dexBytes,
|
|
201
|
+
jstring query, jstring searchType,
|
|
202
|
+
jboolean caseSensitive, jint maxResults) {
|
|
203
|
+
try {
|
|
204
|
+
auto data = jbyteArray_to_vector(env, dexBytes);
|
|
205
|
+
std::string q = jstring_to_string(env, query);
|
|
206
|
+
std::string type = jstring_to_string(env, searchType);
|
|
207
|
+
|
|
208
|
+
dex::DexParser parser;
|
|
209
|
+
if (!parser.parse(data)) {
|
|
210
|
+
json error = {{"error", "Failed to parse DEX"}};
|
|
211
|
+
return string_to_jstring(env, error.dump());
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
json results = json::array();
|
|
215
|
+
int count = 0;
|
|
216
|
+
|
|
217
|
+
if (type == "string") {
|
|
218
|
+
for (const auto& s : parser.strings()) {
|
|
219
|
+
if (count >= maxResults) break;
|
|
220
|
+
|
|
221
|
+
if (safe_contains(s, q, caseSensitive)) {
|
|
222
|
+
// Sanitize string for JSON
|
|
223
|
+
results.push_back({{"type", "string"}, {"value", sanitize_utf8(s)}});
|
|
224
|
+
count++;
|
|
225
|
+
}
|
|
187
226
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
{"
|
|
195
|
-
|
|
196
|
-
|
|
227
|
+
} else if (type == "class") {
|
|
228
|
+
for (const auto& cls : parser.classes()) {
|
|
229
|
+
if (count >= maxResults) break;
|
|
230
|
+
std::string class_name = parser.get_class_name(cls.class_idx);
|
|
231
|
+
|
|
232
|
+
if (safe_contains(class_name, q, caseSensitive)) {
|
|
233
|
+
results.push_back({{"type", "class"}, {"name", sanitize_utf8(class_name)}});
|
|
234
|
+
count++;
|
|
235
|
+
}
|
|
197
236
|
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
237
|
+
} else if (type == "method") {
|
|
238
|
+
auto methods = parser.get_methods();
|
|
239
|
+
for (const auto& m : methods) {
|
|
240
|
+
if (count >= maxResults) break;
|
|
241
|
+
|
|
242
|
+
if (safe_contains(m.method_name, q, caseSensitive)) {
|
|
243
|
+
results.push_back({
|
|
244
|
+
{"type", "method"},
|
|
245
|
+
{"class", sanitize_utf8(m.class_name)},
|
|
246
|
+
{"name", sanitize_utf8(m.method_name)},
|
|
247
|
+
{"prototype", sanitize_utf8(m.prototype)}
|
|
248
|
+
});
|
|
249
|
+
count++;
|
|
250
|
+
}
|
|
208
251
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
{
|
|
216
|
-
|
|
217
|
-
|
|
252
|
+
} else if (type == "field") {
|
|
253
|
+
auto fields = parser.get_fields();
|
|
254
|
+
for (const auto& f : fields) {
|
|
255
|
+
if (count >= maxResults) break;
|
|
256
|
+
|
|
257
|
+
if (safe_contains(f.field_name, q, caseSensitive)) {
|
|
258
|
+
results.push_back({
|
|
259
|
+
{"type", "field"},
|
|
260
|
+
{"class", sanitize_utf8(f.class_name)},
|
|
261
|
+
{"name", sanitize_utf8(f.field_name)},
|
|
262
|
+
{"fieldType", sanitize_utf8(f.type_name)}
|
|
263
|
+
});
|
|
264
|
+
count++;
|
|
265
|
+
}
|
|
218
266
|
}
|
|
219
267
|
}
|
|
268
|
+
|
|
269
|
+
json result = {
|
|
270
|
+
{"query", sanitize_utf8(q)},
|
|
271
|
+
{"searchType", type},
|
|
272
|
+
{"results", results},
|
|
273
|
+
{"count", results.size()}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
return string_to_jstring(env, result.dump());
|
|
277
|
+
} catch (const std::exception& e) {
|
|
278
|
+
LOGE("searchInDex exception: %s", e.what());
|
|
279
|
+
json error = {{"error", std::string("Search failed: ") + e.what()}};
|
|
280
|
+
return string_to_jstring(env, error.dump());
|
|
281
|
+
} catch (...) {
|
|
282
|
+
LOGE("searchInDex unknown exception");
|
|
283
|
+
json error = {{"error", "Search failed: unknown error"}};
|
|
284
|
+
return string_to_jstring(env, error.dump());
|
|
220
285
|
}
|
|
221
|
-
|
|
222
|
-
json result = {
|
|
223
|
-
{"query", q},
|
|
224
|
-
{"searchType", type},
|
|
225
|
-
{"results", results},
|
|
226
|
-
{"count", results.size()}
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
return string_to_jstring(env, result.dump());
|
|
230
286
|
}
|
|
231
287
|
|
|
232
288
|
JNIEXPORT jstring JNICALL
|
|
@@ -27,10 +27,6 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableField;
|
|
|
27
27
|
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod;
|
|
28
28
|
import com.android.tools.smali.dexlib2.writer.io.FileDataStore;
|
|
29
29
|
import com.android.tools.smali.dexlib2.writer.pool.DexPool;
|
|
30
|
-
import com.android.tools.smali.baksmali.Baksmali;
|
|
31
|
-
import com.android.tools.smali.baksmali.BaksmaliOptions;
|
|
32
|
-
import com.android.tools.smali.baksmali.Adaptors.ClassDefinition;
|
|
33
|
-
import com.android.tools.smali.baksmali.formatter.BaksmaliWriter;
|
|
34
30
|
import com.android.tools.smali.smali.Smali;
|
|
35
31
|
import com.android.tools.smali.smali.SmaliOptions;
|
|
36
32
|
|
|
@@ -111,6 +107,7 @@ public class DexManager {
|
|
|
111
107
|
long lastModified;
|
|
112
108
|
Map<String, ClassDef> classDefMap; // type -> ClassDef
|
|
113
109
|
int dexVersion;
|
|
110
|
+
byte[] dexBytes; // DEX 原始字节用于 C++ 操作
|
|
114
111
|
|
|
115
112
|
ApkDexCache(String apkPath, String dexPath) {
|
|
116
113
|
this.apkPath = apkPath;
|
|
@@ -882,49 +879,12 @@ public class DexManager {
|
|
|
882
879
|
}
|
|
883
880
|
}
|
|
884
881
|
} catch (Exception e) {
|
|
885
|
-
Log.w(TAG, "C++ classToSmali failed
|
|
882
|
+
Log.w(TAG, "C++ classToSmali failed", e);
|
|
883
|
+
throw new Exception("C++ classToSmali failed: " + e.getMessage());
|
|
886
884
|
}
|
|
887
885
|
}
|
|
888
886
|
|
|
889
|
-
|
|
890
|
-
ClassDef classDef = findClass(session, className);
|
|
891
|
-
if (classDef == null) {
|
|
892
|
-
throw new IllegalArgumentException("Class not found: " + className);
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
StringWriter writer = new StringWriter();
|
|
896
|
-
BaksmaliOptions options = new BaksmaliOptions();
|
|
897
|
-
|
|
898
|
-
List<ClassDef> singleClass = new ArrayList<>();
|
|
899
|
-
singleClass.add(classDef);
|
|
900
|
-
ImmutableDexFile singleDex = new ImmutableDexFile(
|
|
901
|
-
session.originalDexFile.getOpcodes(),
|
|
902
|
-
singleClass
|
|
903
|
-
);
|
|
904
|
-
|
|
905
|
-
File tempDir = File.createTempFile("smali_", "_temp");
|
|
906
|
-
tempDir.delete();
|
|
907
|
-
tempDir.mkdirs();
|
|
908
|
-
|
|
909
|
-
try {
|
|
910
|
-
Baksmali.disassembleDexFile(singleDex, tempDir, 1, options);
|
|
911
|
-
|
|
912
|
-
String smaliPath = className.substring(1, className.length() - 1) + ".smali";
|
|
913
|
-
File smaliFile = new File(tempDir, smaliPath);
|
|
914
|
-
|
|
915
|
-
if (smaliFile.exists()) {
|
|
916
|
-
String smali = readFileContent(smaliFile);
|
|
917
|
-
JSObject result = new JSObject();
|
|
918
|
-
result.put("className", className);
|
|
919
|
-
result.put("smali", smali);
|
|
920
|
-
result.put("engine", "java");
|
|
921
|
-
return result;
|
|
922
|
-
} else {
|
|
923
|
-
throw new IOException("Failed to generate smali for: " + className);
|
|
924
|
-
}
|
|
925
|
-
} finally {
|
|
926
|
-
deleteRecursive(tempDir);
|
|
927
|
-
}
|
|
887
|
+
throw new UnsupportedOperationException("C++ library not available for classToSmali");
|
|
928
888
|
}
|
|
929
889
|
|
|
930
890
|
/**
|
|
@@ -978,15 +938,12 @@ public class DexManager {
|
|
|
978
938
|
}
|
|
979
939
|
}
|
|
980
940
|
} catch (Exception e) {
|
|
981
|
-
Log.w(TAG, "C++ disassemble failed
|
|
941
|
+
Log.w(TAG, "C++ disassemble failed", e);
|
|
942
|
+
throw new Exception("C++ disassemble failed: " + e.getMessage());
|
|
982
943
|
}
|
|
983
944
|
}
|
|
984
945
|
|
|
985
|
-
|
|
986
|
-
BaksmaliOptions options = new BaksmaliOptions();
|
|
987
|
-
Baksmali.disassembleDexFile(session.originalDexFile, outDir, 4, options);
|
|
988
|
-
|
|
989
|
-
Log.d(TAG, "Disassembled to: " + outputDir);
|
|
946
|
+
throw new UnsupportedOperationException("C++ library not available for disassemble");
|
|
990
947
|
}
|
|
991
948
|
|
|
992
949
|
/**
|
|
@@ -2272,20 +2229,11 @@ public class DexManager {
|
|
|
2272
2229
|
}
|
|
2273
2230
|
|
|
2274
2231
|
/**
|
|
2275
|
-
* 获取类的 Smali
|
|
2232
|
+
* 获取类的 Smali 代码(内部方法)- 使用 C++ 实现
|
|
2276
2233
|
*/
|
|
2277
2234
|
private String getSmaliForClass(DexBackedDexFile dexFile, ClassDef classDef) {
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
ClassDefinition classDefinition = new ClassDefinition(options, classDef);
|
|
2281
|
-
java.io.StringWriter stringWriter = new java.io.StringWriter();
|
|
2282
|
-
BaksmaliWriter writer = new BaksmaliWriter(stringWriter, null);
|
|
2283
|
-
classDefinition.writeTo(writer);
|
|
2284
|
-
writer.close();
|
|
2285
|
-
return stringWriter.toString();
|
|
2286
|
-
} catch (Exception e) {
|
|
2287
|
-
return "";
|
|
2288
|
-
}
|
|
2235
|
+
// 此方法已弃用,使用 C++ 实现
|
|
2236
|
+
return "";
|
|
2289
2237
|
}
|
|
2290
2238
|
|
|
2291
2239
|
|
|
@@ -3115,6 +3063,9 @@ public class DexManager {
|
|
|
3115
3063
|
dexInputStream.close();
|
|
3116
3064
|
byte[] dexBytes = baos.toByteArray();
|
|
3117
3065
|
|
|
3066
|
+
// 保存 DEX 字节用于 C++ 操作
|
|
3067
|
+
cache.dexBytes = dexBytes;
|
|
3068
|
+
|
|
3118
3069
|
// 解析 DEX 文件并缓存所有 ClassDef
|
|
3119
3070
|
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dexBytes);
|
|
3120
3071
|
cache.dexVersion = 35; // 默认 DEX 版本
|
|
@@ -3146,7 +3097,7 @@ public class DexManager {
|
|
|
3146
3097
|
}
|
|
3147
3098
|
|
|
3148
3099
|
/**
|
|
3149
|
-
* 从 APK 中的 DEX 文件获取类的 Smali
|
|
3100
|
+
* 从 APK 中的 DEX 文件获取类的 Smali 代码(使用 C++ 实现)
|
|
3150
3101
|
*/
|
|
3151
3102
|
public JSObject getClassSmaliFromApk(String apkPath, String dexPath, String className) throws Exception {
|
|
3152
3103
|
JSObject result = new JSObject();
|
|
@@ -3161,26 +3112,22 @@ public class DexManager {
|
|
|
3161
3112
|
String targetType = convertClassNameToType(className);
|
|
3162
3113
|
|
|
3163
3114
|
try {
|
|
3164
|
-
//
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3115
|
+
// 使用 C++ 实现获取 Smali
|
|
3116
|
+
if (CppDex.isAvailable()) {
|
|
3117
|
+
ApkDexCache cache = getOrCreateDexCache(apkPath, dexPath);
|
|
3118
|
+
if (cache.dexBytes != null) {
|
|
3119
|
+
String smaliJson = CppDex.getClassSmali(cache.dexBytes, targetType);
|
|
3120
|
+
if (smaliJson != null && !smaliJson.contains("\"error\"")) {
|
|
3121
|
+
org.json.JSONObject smaliResult = new org.json.JSONObject(smaliJson);
|
|
3122
|
+
String smali = smaliResult.optString("smali", "");
|
|
3123
|
+
if (!smali.isEmpty()) {
|
|
3124
|
+
result.put("smali", smali);
|
|
3125
|
+
return result;
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3171
3129
|
}
|
|
3172
|
-
|
|
3173
|
-
// 使用 baksmali 库生成正确的 Smali 代码
|
|
3174
|
-
BaksmaliOptions options = new BaksmaliOptions();
|
|
3175
|
-
ClassDefinition classDefinition = new ClassDefinition(options, targetClass);
|
|
3176
|
-
|
|
3177
|
-
java.io.StringWriter stringWriter = new java.io.StringWriter();
|
|
3178
|
-
BaksmaliWriter writer = new BaksmaliWriter(stringWriter, null);
|
|
3179
|
-
classDefinition.writeTo(writer);
|
|
3180
|
-
writer.close();
|
|
3181
|
-
|
|
3182
|
-
result.put("smali", stringWriter.toString());
|
|
3183
|
-
|
|
3130
|
+
result.put("smali", "# 类未找到或 C++ 库不可用: " + className);
|
|
3184
3131
|
} catch (Exception e) {
|
|
3185
3132
|
result.put("smali", "# 加载失败: " + e.getMessage());
|
|
3186
3133
|
}
|