debug_wechat_send.js
14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
/**
* Frida debug script for WeChat 8.0.72 send message API discovery
* Usage: frida -U -f com.tencent.mm --no-pause -l debug_wechat_send.js
*
* Commands in Frida REPL:
* sendTestMsg("wxid_xxx", "hello") // try to send a test message
* searchSendMsg() // list all send-msg related classes
* hookWCDB() // monitor message table inserts
* hookFactories() // hook u01/v01/w01 factory patterns
*/
Java.perform(function () {
console.log("[HermesDebug] Script loaded, waiting for classes...");
// ── 1. Hook WCDB insert to verify if outgoing messages reach DB ──
function hookWCDB() {
try {
var SQLiteDatabase = Java.use("com.tencent.wcdb.database.SQLiteDatabase");
SQLiteDatabase.insert.overload('java.lang.String', 'java.lang.String', 'android.content.ContentValues').implementation = function(table, nullColumnHack, values) {
if (table && table.indexOf("message") !== -1) {
var talker = values.getAsString("talker");
var content = values.getAsString("content");
var type = values.getAsInteger("type");
var isSend = values.getAsInteger("isSend");
console.log("[WCDB] INSERT message talker=" + talker + " type=" + type + " isSend=" + isSend + " content=" + (content ? content.substring(0, 60) : "null"));
}
return this.insert(table, nullColumnHack, values);
};
SQLiteDatabase.insertWithOnConflict.overload('java.lang.String', 'java.lang.String', 'android.content.ContentValues', 'int').implementation = function(table, nullColumnHack, values, conflictAlgorithm) {
if (table && table.indexOf("message") !== -1) {
var talker = values.getAsString("talker");
var content = values.getAsString("content");
var type = values.getAsInteger("type");
var isSend = values.getAsInteger("isSend");
console.log("[WCDB] INSERT2 message talker=" + talker + " type=" + type + " isSend=" + isSend + " content=" + (content ? content.substring(0, 60) : "null"));
}
return this.insertWithOnConflict(table, nullColumnHack, values, conflictAlgorithm);
};
console.log("[HermesDebug] WCDB hooks installed");
} catch (e) {
console.log("[HermesDebug] WCDB hook failed: " + e);
}
}
// ── 2. Search for send-msg related classes dynamically ──
function searchSendMsg() {
console.log("[HermesDebug] Searching for send-msg classes...");
var count = 0;
Java.enumerateLoadedClasses({
onMatch: function(className) {
var lower = className.toLowerCase();
if (lower.indexOf("sendmsg") !== -1 ||
lower.indexOf("sendmessage") !== -1 ||
lower.indexOf("scenesendmsg") !== -1 ||
lower.indexOf("netscenesendmsg") !== -1 ||
className.indexOf("u01.") === 0 ||
className.indexOf("v01.") === 0 ||
className.indexOf("w01.") === 0) {
console.log(" [CLASS] " + className);
count++;
}
},
onComplete: function() {
console.log("[HermesDebug] Found " + count + " candidate classes");
}
});
}
// ── 3. Hook factory methods in u01/v01/w01 packages ──
function hookFactories() {
console.log("[HermesDebug] Hooking factory methods...");
var hookCount = 0;
Java.enumerateLoadedClasses({
onMatch: function(className) {
if (className.indexOf("u01.") !== 0 &&
className.indexOf("v01.") !== 0 &&
className.indexOf("w01.") !== 0) {
return;
}
try {
var cls = Java.use(className);
var methods = cls.class.getDeclaredMethods();
for (var i = 0; i < methods.length; i++) {
var m = methods[i];
var name = m.getName();
var paramTypes = m.getParameterTypes();
var retType = m.getReturnType().getName();
// Hook a(String) that returns non-void (factory pattern)
if (name === "a" && paramTypes.length === 1 &&
paramTypes[0].getName() === "java.lang.String" &&
retType !== "void") {
try {
cls[name].overload('java.lang.String').implementation = function(arg) {
var result = this[name](arg);
console.log("[FACTORY] " + className + ".a(\"" + arg + "\") => " + retType + " instance=" + result);
return result;
};
hookCount++;
} catch (e) {
// overload mismatch, skip
}
}
// Also hook b(String) content setter
if (name === "b" && paramTypes.length === 1 &&
paramTypes[0].getName() === "java.lang.String") {
try {
cls[name].overload('java.lang.String').implementation = function(arg) {
console.log("[SETTER] " + className + ".b(\"" + arg.substring(0, 30) + "\")");
return this[name](arg);
};
} catch (e) {}
}
// Hook h(int)
if (name === "h" && paramTypes.length === 1 &&
paramTypes[0].getName() === "int") {
try {
cls[name].overload('int').implementation = function(arg) {
console.log("[SETTER] " + className + ".h(" + arg + ")");
return this[name](arg);
};
} catch (e) {}
}
// Hook parameter-less a() that returns something (build/submit)
if (name === "a" && paramTypes.length === 0 && retType !== "void") {
try {
cls[name].overload().implementation = function() {
var result = this[name]();
console.log("[BUILD] " + className + ".a() => " + retType + " result=" + result);
return result;
};
} catch (e) {}
}
}
} catch (e) {
// class not found or other error
}
},
onComplete: function() {
console.log("[HermesDebug] Hooked " + hookCount + " factory methods");
}
});
}
// ── 4. Try to send a test message via reflection ──
function sendTestMsg(toUser, content) {
console.log("[HermesDebug] Attempting to send test msg to=" + toUser);
try {
// Try u01.r1 first
var factoryClass = Java.use("u01.r1");
console.log("[HermesDebug] Found u01.r1");
// List all methods
var methods = factoryClass.class.getDeclaredMethods();
console.log("[HermesDebug] u01.r1 methods:");
for (var i = 0; i < methods.length; i++) {
var m = methods[i];
if (m.getModifiers() & 0x0001) { // public
var params = "";
var pts = m.getParameterTypes();
for (var j = 0; j < pts.length; j++) {
params += pts[j].getName() + " ";
}
console.log(" " + m.getName() + "(" + params.trim() + ") => " + m.getReturnType().getName());
}
}
// Try static a(String)
var builder = factoryClass.a("java.lang.String").call(factoryClass, toUser);
console.log("[HermesDebug] builder class=" + builder.getClass().getName());
var builderClass = Java.use(builder.getClass().getName());
// Try common setter methods
var setterMap = {
"content": ["b", "c", "d", "e", "f", "g", "setContent", "setText"],
"type": ["h", "i", "j", "k", "setType", "setMsgType"],
"flag": ["l", "m", "n", "setFlag"]
};
// Try content setter
for (var si = 0; si < setterMap.content.length; si++) {
var sn = setterMap.content[si];
try {
builderClass[sn].overload('java.lang.String').call(builder, content);
console.log("[HermesDebug] Set content via " + sn + "(String)");
break;
} catch (e) {}
}
// Try type setter (0 = text)
for (var si = 0; si < setterMap.type.length; si++) {
var sn = setterMap.type[si];
try {
builderClass[sn].overload('int').call(builder, 0);
console.log("[HermesDebug] Set type=0 via " + sn + "(int)");
break;
} catch (e) {}
}
// Try build
var result = builderClass.a().call(builder);
console.log("[HermesDebug] Build result class=" + (result ? result.getClass().getName() : "null"));
// Try submit
if (result) {
var resultClass = Java.use(result.getClass().getName());
try {
var submitResult = resultClass.a().call(result);
console.log("[HermesDebug] Submit result=" + submitResult);
} catch (e) {
console.log("[HermesDebug] Submit failed: " + e);
}
}
} catch (e) {
console.log("[HermesDebug] u01.r1 failed: " + e);
// Fallback: search for any class with a(String) -> factory pattern
console.log("[HermesDebug] Searching for alternative factory classes...");
Java.enumerateLoadedClasses({
onMatch: function(className) {
if (className.indexOf("u01.") !== 0 && className.indexOf("v01.") !== 0) return;
try {
var cls = Java.use(className);
var methods = cls.class.getDeclaredMethods();
for (var i = 0; i < methods.length; i++) {
var m = methods[i];
if (m.getName() === "a" && m.getParameterTypes().length === 1 &&
m.getParameterTypes()[0].getName() === "java.lang.String" &&
m.getModifiers() & 0x0008) { // static
console.log("[ALT] " + className + ".a(String) is static, ret=" + m.getReturnType().getName());
}
}
} catch (e) {}
},
onComplete: function() {}
});
}
}
// ── 5. Hook ContentProvider to see if LSPosed module's provider calls arrive ──
function hookContentProvider() {
try {
var ContentResolver = Java.use("android.content.ContentResolver");
ContentResolver.query.overload('android.net.Uri', '[Ljava.lang.String;', 'java.lang.String', '[Ljava.lang.String;', 'java.lang.String').implementation = function(uri, projection, selection, selectionArgs, sortOrder) {
if (uri.toString().indexOf("com.tencent.mm.plugin.ext") !== -1) {
console.log("[CP] query uri=" + uri + " args=" + (selectionArgs ? selectionArgs.join(",") : "null"));
}
return this.query(uri, projection, selection, selectionArgs, sortOrder);
};
ContentResolver.insert.overload('android.net.Uri', 'android.content.ContentValues').implementation = function(uri, values) {
if (uri.toString().indexOf("com.tencent.mm.plugin.ext") !== -1) {
console.log("[CP] insert uri=" + uri + " values=" + values);
}
return this.insert(uri, values);
};
console.log("[HermesDebug] ContentProvider hooks installed");
} catch (e) {
console.log("[HermesDebug] ContentProvider hook failed: " + e);
}
}
// ── 6. Hook broadcast receiver registration ──
function hookBroadcast() {
try {
var Context = Java.use("android.content.Context");
Context.registerReceiver.overload('android.content.BroadcastReceiver', 'android.content.IntentFilter', 'int').implementation = function(receiver, filter, flags) {
var actions = [];
var count = filter.countActions();
for (var i = 0; i < count; i++) {
actions.push(filter.getAction(i));
}
if (actions.join(",").indexOf("hermes") !== -1) {
console.log("[BROADCAST] registerReceiver actions=" + actions.join(",") + " flags=" + flags);
}
return this.registerReceiver(receiver, filter, flags);
};
console.log("[HermesDebug] Broadcast hook installed");
} catch (e) {
console.log("[HermesDebug] Broadcast hook failed: " + e);
}
}
// ── Install all hooks ──
hookWCDB();
hookContentProvider();
hookBroadcast();
// Wait a bit for WeChat to load more classes, then hook factories
setTimeout(function() {
searchSendMsg();
hookFactories();
}, 5000);
// ── Expose functions to Frida REPL ──
rpc.exports = {
sendTestMsg: sendTestMsg,
searchSendMsg: searchSendMsg,
hookFactories: hookFactories,
hookWCDB: hookWCDB
};
console.log("[HermesDebug] Setup complete. Available rpc exports: sendTestMsg, searchSendMsg, hookFactories, hookWCDB");
});