mirror of
https://github.com/yincongcyincong/wechat_chatter.git
synced 2026-07-15 10:26:52 +08:00
add script
This commit is contained in:
+401
-43
@@ -1,54 +1,412 @@
|
||||
const mod = Process.getModuleByName("WeChat");
|
||||
const realAddr = ptr("0x102BAA1E0").sub("0x100000000").add(mod.base);
|
||||
// pure_js_keyboard_injector.js
|
||||
console.log("Pure JS Keyboard Injector - Starting...");
|
||||
|
||||
console.log("[+] Real Function Address:", realAddr);
|
||||
// 常量定义
|
||||
const NSEventTypeKeyDown = 10;
|
||||
const NSEventTypeKeyUp = 11;
|
||||
const kVK_Return = 36; // 回车键
|
||||
|
||||
Interceptor.attach(realAddr, {
|
||||
onEnter(args) {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
try {
|
||||
if (args[i].isNull()) {
|
||||
continue;
|
||||
// 1. Hook QNSView的handleKeyEvent方法
|
||||
if (ObjC.available) {
|
||||
const QNSView = ObjC.classes.QNSView;
|
||||
|
||||
if (QNSView) {
|
||||
console.log("✓ Found QNSView class");
|
||||
|
||||
// Hook方法
|
||||
const handleKeyEventMethod = QNSView['- handleKeyEvent:eventType:'];
|
||||
if (handleKeyEventMethod) {
|
||||
Interceptor.attach(handleKeyEventMethod.implementation, {
|
||||
onEnter: function(args) {
|
||||
console.log("\n[QNSView Hook]");
|
||||
const event = new ObjC.Object(args[2]);
|
||||
const eventType = args[3];
|
||||
console.log(`Event Type (a4): ${eventType}`);
|
||||
console.log(`KeyCode: ${event.keyCode()}`);
|
||||
console.log(`Characters: ${event.characters()}`);
|
||||
}
|
||||
});
|
||||
console.log("✓ QNSView handleKeyEvent hooked");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n[+] arg${i} ${args[i]}`, args[i].compare(ptr("0x600000000000")) >= 0, args[i].compare(ptr("0x700000000000")) < 0);
|
||||
if (args[i].compare(ptr("0x600000000000")) >= 0 && args[i].compare(ptr("0x700000000000")) < 0) {
|
||||
console.log(`\n[+] enter arg${i} ${args[i]}`);
|
||||
const buf = args[i].readByteArray(128)
|
||||
if (!buf) {
|
||||
continue;
|
||||
}
|
||||
let s = "";
|
||||
const u8 = new Uint8Array(buf);
|
||||
for (let b of u8) {
|
||||
if (b >= 0x20 && b <= 0x7E) {
|
||||
s += String.fromCharCode(b);
|
||||
} else {
|
||||
s += ".";
|
||||
}
|
||||
}
|
||||
// 2. 发送回车键的函数
|
||||
function sendEnterKey() {
|
||||
try {
|
||||
console.log("\n=== 发送回车键 ===");
|
||||
|
||||
console.log("show s is", s);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Enter Error:", e);
|
||||
}
|
||||
const NSApplication = ObjC.classes.NSApplication;
|
||||
const NSEvent = ObjC.classes.NSEvent;
|
||||
const app = NSApplication.sharedApplication();
|
||||
const keyWindow = app.keyWindow();
|
||||
|
||||
if (!keyWindow) {
|
||||
console.log("❌ 没有找到活动窗口");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`窗口: ${keyWindow}`);
|
||||
|
||||
},
|
||||
// 查找QNSView
|
||||
function findQNSView(view) {
|
||||
if (view.$className === 'QNSView') {
|
||||
return view;
|
||||
}
|
||||
|
||||
onLeave(retval) {
|
||||
console.log("===== sub_105808800 LEAVE =====");
|
||||
console.log("Return value:", retval);
|
||||
// try {
|
||||
// console.log("Return hexdump:");
|
||||
// console.log(hexdump(retval, {
|
||||
// offset: 0,
|
||||
// length: 128
|
||||
// }));
|
||||
// } catch (_) {
|
||||
// }
|
||||
try {
|
||||
const subviews = view.subviews();
|
||||
const count = subviews.count();
|
||||
for (let i = 0; i < count; i++) {
|
||||
const subview = subviews.objectAtIndex_(i);
|
||||
const found = findQNSView(subview);
|
||||
if (found) return found;
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略错误
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const contentView = keyWindow.contentView();
|
||||
const qnsView = findQNSView(contentView);
|
||||
|
||||
if (!qnsView) {
|
||||
console.log("❌ 没有找到QNSView,使用备用方法");
|
||||
sendEnterKeyAlternative();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✓ 找到QNSView: ${qnsView}`);
|
||||
|
||||
// 创建回车键按下事件
|
||||
const keyDownEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
NSEventTypeKeyDown, // type = 10 (按下)
|
||||
{ x: 100, y: 100 }, // 位置
|
||||
0, // 修饰键
|
||||
Date.now() / 1000, // 时间戳(秒)
|
||||
keyWindow.windowNumber(), // 窗口编号
|
||||
NULL, // 上下文
|
||||
'\r', // 字符(回车)
|
||||
'\r', // 忽略修饰键的字符
|
||||
0, // 是否重复
|
||||
kVK_Return // 键码36=回车
|
||||
);
|
||||
|
||||
// 创建回车键释放事件
|
||||
const keyUpEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
NSEventTypeKeyUp, // type = 11 (释放)
|
||||
{ x: 100, y: 100 }, // 位置
|
||||
0, // 修饰键
|
||||
(Date.now() / 1000) + 0.05, // 稍后的时间
|
||||
keyWindow.windowNumber(), // 窗口编号
|
||||
NULL, // 上下文
|
||||
'\r', // 字符
|
||||
'\r', // 忽略修饰键的字符
|
||||
0, // 是否重复
|
||||
kVK_Return // 键码
|
||||
);
|
||||
|
||||
// 发送按键按下(根据逆向分析,a4=6可能是按键按下)
|
||||
console.log("发送回车键按下...");
|
||||
qnsView.handleKeyEvent_eventType_(keyDownEvent, 6);
|
||||
|
||||
// 延迟发送按键释放
|
||||
setTimeout(() => {
|
||||
console.log("发送回车键释放...");
|
||||
qnsView.handleKeyEvent_eventType_(keyUpEvent, 7); // 猜测7是按键释放
|
||||
}, 50);
|
||||
|
||||
console.log("✓ 回车键发送完成");
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 发送回车键失败: ${error}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 备用方法:使用CGEvent
|
||||
function sendEnterKeyAlternative() {
|
||||
try {
|
||||
console.log("尝试使用CGEvent发送回车键...");
|
||||
|
||||
const CGEventCreateKeyboardEvent = Module.findExportByName('CoreGraphics', 'CGEventCreateKeyboardEvent');
|
||||
const CGEventPost = Module.findExportByName('CoreGraphics', 'CGEventPost');
|
||||
|
||||
if (CGEventCreateKeyboardEvent && CGEventPost) {
|
||||
const kCGHIDEventTap = 0;
|
||||
|
||||
// 发送回车键按下
|
||||
const keyDown = new NativeFunction(CGEventCreateKeyboardEvent, 'pointer', ['pointer', 'uint64', 'bool'])(
|
||||
NULL,
|
||||
kVK_Return,
|
||||
true
|
||||
);
|
||||
new NativeFunction(CGEventPost, 'void', ['uint32', 'pointer'])(kCGHIDEventTap, keyDown);
|
||||
|
||||
// 延迟发送释放
|
||||
setTimeout(() => {
|
||||
const keyUp = new NativeFunction(CGEventCreateKeyboardEvent, 'pointer', ['pointer', 'uint64', 'bool'])(
|
||||
NULL,
|
||||
kVK_Return,
|
||||
false
|
||||
);
|
||||
new NativeFunction(CGEventPost, 'void', ['uint32', 'pointer'])(kCGHIDEventTap, keyUp);
|
||||
console.log("✓ CGEvent 回车键发送完成");
|
||||
}, 50);
|
||||
} else {
|
||||
console.log("❌ CGEvent API 不可用");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ CGEvent方法失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 发送文本"123"的函数
|
||||
function sendText123() {
|
||||
try {
|
||||
console.log("\n=== 发送文本 '123' ===");
|
||||
|
||||
const NSApplication = ObjC.classes.NSApplication;
|
||||
const NSEvent = ObjC.classes.NSEvent;
|
||||
const app = NSApplication.sharedApplication();
|
||||
const keyWindow = app.keyWindow();
|
||||
|
||||
if (!keyWindow) {
|
||||
console.log("❌ 没有找到活动窗口");
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找QNSView
|
||||
function findQNSView(view) {
|
||||
if (view.$className === 'QNSView') return view;
|
||||
try {
|
||||
const subviews = view.subviews();
|
||||
const count = subviews.count();
|
||||
for (let i = 0; i < count; i++) {
|
||||
const found = findQNSView(subviews.objectAtIndex_(i));
|
||||
if (found) return found;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
const qnsView = findQNSView(keyWindow.contentView());
|
||||
|
||||
if (!qnsView) {
|
||||
console.log("❌ 没有找到QNSView");
|
||||
return;
|
||||
}
|
||||
|
||||
// 要发送的字符和对应的键码
|
||||
const textToSend = [
|
||||
{ char: '1', keyCode: 18 },
|
||||
{ char: '2', keyCode: 19 },
|
||||
{ char: '3', keyCode: 20 }
|
||||
];
|
||||
|
||||
// 逐个发送字符
|
||||
textToSend.forEach((item, index) => {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
console.log(`发送字符: ${item.char}`);
|
||||
|
||||
// 创建按键按下事件
|
||||
const keyDownEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
NSEventTypeKeyDown,
|
||||
{ x: 100, y: 100 },
|
||||
0,
|
||||
Date.now() / 1000,
|
||||
keyWindow.windowNumber(),
|
||||
NULL,
|
||||
item.char,
|
||||
item.char,
|
||||
0,
|
||||
item.keyCode
|
||||
);
|
||||
|
||||
// 创建按键释放事件
|
||||
const keyUpEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
NSEventTypeKeyUp,
|
||||
{ x: 100, y: 100 },
|
||||
0,
|
||||
(Date.now() / 1000) + 0.03,
|
||||
keyWindow.windowNumber(),
|
||||
NULL,
|
||||
item.char,
|
||||
item.char,
|
||||
0,
|
||||
item.keyCode
|
||||
);
|
||||
|
||||
// 发送按键按下
|
||||
qnsView.handleKeyEvent_eventType_(keyDownEvent, 6);
|
||||
|
||||
// 延迟发送按键释放
|
||||
setTimeout(() => {
|
||||
qnsView.handleKeyEvent_eventType_(keyUpEvent, 7);
|
||||
}, 30);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`发送字符 ${item.char} 失败: ${error}`);
|
||||
}
|
||||
}, index * 100); // 每个字符间隔100ms
|
||||
});
|
||||
|
||||
console.log("✓ 文本'123'发送中...");
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 发送文本失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 组合函数:先发送123,然后回车
|
||||
function send123AndEnter() {
|
||||
console.log("\n=== 开始发送: 123 + 回车 ===");
|
||||
|
||||
// 先发送123
|
||||
sendText123();
|
||||
|
||||
// 延迟500ms后发送回车
|
||||
setTimeout(() => {
|
||||
console.log("\n=== 发送回车键 ===");
|
||||
sendEnterKey();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// 6. 通用按键注入函数
|
||||
function injectKey(keyCode, eventType, characters = '') {
|
||||
try {
|
||||
const NSApplication = ObjC.classes.NSApplication;
|
||||
const NSEvent = ObjC.classes.NSEvent;
|
||||
const app = NSApplication.sharedApplication();
|
||||
const keyWindow = app.keyWindow();
|
||||
|
||||
if (!keyWindow) {
|
||||
console.log("❌ 没有活动窗口");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找QNSView
|
||||
function findQNSView(view) {
|
||||
if (view.$className === 'QNSView') return view;
|
||||
try {
|
||||
const subviews = view.subviews();
|
||||
for (let i = 0; i < subviews.count(); i++) {
|
||||
const found = findQNSView(subviews.objectAtIndex_(i));
|
||||
if (found) return found;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
const qnsView = findQNSView(keyWindow.contentView());
|
||||
|
||||
if (qnsView) {
|
||||
const event = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
eventType, // 10=按下, 11=释放
|
||||
{ x: 100, y: 100 },
|
||||
0,
|
||||
Date.now() / 1000,
|
||||
keyWindow.windowNumber(),
|
||||
NULL,
|
||||
characters,
|
||||
characters,
|
||||
0,
|
||||
keyCode
|
||||
);
|
||||
|
||||
// 根据逆向分析,a4参数:6可能是按下,7可能是释放
|
||||
const a4Param = eventType === NSEventTypeKeyDown ? 6 : 7;
|
||||
qnsView.handleKeyEvent_eventType_(event, a4Param);
|
||||
|
||||
console.log(`✓ 发送按键: keyCode=${keyCode}, type=${eventType}, a4=${a4Param}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 注入按键失败: ${error}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 直接调用Qt事件发送(基于逆向分析)
|
||||
function sendQtKeyEvent(keyCode, text, modifiers = 0) {
|
||||
try {
|
||||
console.log(`\n=== 直接发送Qt键盘事件: ${text} ===`);
|
||||
|
||||
// 尝试找到QNSView并调用底层函数
|
||||
const NSApplication = ObjC.classes.NSApplication;
|
||||
const app = NSApplication.sharedApplication();
|
||||
const keyWindow = app.keyWindow();
|
||||
|
||||
if (!keyWindow) return;
|
||||
|
||||
// 查找QNSView
|
||||
function findQNSView(view) {
|
||||
if (view.$className === 'QNSView') return view;
|
||||
try {
|
||||
const subviews = view.subviews();
|
||||
for (let i = 0; i < subviews.count(); i++) {
|
||||
const found = findQNSView(subviews.objectAtIndex_(i));
|
||||
if (found) return found;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
const qnsView = findQNSView(keyWindow.contentView());
|
||||
|
||||
if (qnsView && qnsView.handleKeyEvent) {
|
||||
// 创建模拟的事件对象
|
||||
const fakeEvent = {
|
||||
keyCode: function() { return keyCode; },
|
||||
characters: function() { return text; },
|
||||
charactersIgnoringModifiers: function() { return text; },
|
||||
timestamp: function() { return Date.now() / 1000; },
|
||||
modifierFlags: function() { return modifiers; },
|
||||
isARepeat: function() { return 0; }
|
||||
};
|
||||
|
||||
// 包装成ObjC对象
|
||||
const eventWrapper = new ObjC.Object(fakeEvent);
|
||||
|
||||
// 发送事件
|
||||
qnsView.handleKeyEvent_eventType_(eventWrapper, 6);
|
||||
|
||||
console.log(`✓ Qt事件发送: ${text} (keyCode: ${keyCode})`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Qt事件发送失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 9. 创建交互式菜单
|
||||
function showMenu() {
|
||||
console.log("\n" + "=".repeat(50));
|
||||
console.log("🎹 键盘注入器 - 纯JS版本");
|
||||
console.log("=".repeat(50));
|
||||
console.log("可用命令:");
|
||||
console.log("1. sendEnterKey() - 发送回车键");
|
||||
console.log("2. sendText123() - 发送文本 '123'");
|
||||
console.log("3. send123AndEnter() - 发送 '123' 然后回车");
|
||||
console.log("4. injectKey(36, 10) - 发送回车键按下");
|
||||
console.log("5. injectKey(36, 11) - 发送回车键释放");
|
||||
console.log("6. sendQtKeyEvent(18, '1') - 直接发送Qt事件");
|
||||
console.log("=".repeat(50));
|
||||
console.log("示例: 发送 '123' 然后回车:");
|
||||
console.log(" send123AndEnter()");
|
||||
console.log("=".repeat(50));
|
||||
}
|
||||
|
||||
// 10. 自动执行(可选)
|
||||
// 取消下面行的注释可以自动发送
|
||||
// setTimeout(send123AndEnter, 1000);
|
||||
|
||||
// 显示菜单
|
||||
showMenu();
|
||||
|
||||
console.log("\n✅ 键盘注入器加载完成!");
|
||||
console.log("📝 输入命令开始注入键盘事件...");
|
||||
@@ -0,0 +1,419 @@
|
||||
// pure_js_keyboard_injector.js
|
||||
console.log("Pure JS Keyboard Injector - Starting...");
|
||||
|
||||
// 常量定义
|
||||
const NSEventTypeKeyDown = 10;
|
||||
const NSEventTypeKeyUp = 11;
|
||||
const kVK_Return = 36; // 回车键
|
||||
|
||||
// 1. Hook QNSView的handleKeyEvent方法
|
||||
if (ObjC.available) {
|
||||
const QNSView = ObjC.classes.QNSView;
|
||||
|
||||
if (QNSView) {
|
||||
console.log("✓ Found QNSView class");
|
||||
|
||||
// Hook方法
|
||||
const handleKeyEventMethod = QNSView['- handleKeyEvent:eventType:'];
|
||||
if (handleKeyEventMethod) {
|
||||
Interceptor.attach(handleKeyEventMethod.implementation, {
|
||||
onEnter: function(args) {
|
||||
console.log("\n[QNSView Hook]");
|
||||
const event = new ObjC.Object(args[2]);
|
||||
const eventType = args[3];
|
||||
console.log(`Event Type (a4): ${eventType}`);
|
||||
console.log(`KeyCode: ${event.keyCode()}`);
|
||||
console.log(`Characters: ${event.characters()}`);
|
||||
}
|
||||
});
|
||||
console.log("✓ QNSView handleKeyEvent hooked");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 发送回车键的函数
|
||||
function sendEnterKey() {
|
||||
try {
|
||||
console.log("\n=== 发送回车键 ===");
|
||||
|
||||
const NSApplication = ObjC.classes.NSApplication;
|
||||
const NSEvent = ObjC.classes.NSEvent;
|
||||
const app = NSApplication.sharedApplication();
|
||||
const keyWindow = app.keyWindow();
|
||||
|
||||
if (!keyWindow) {
|
||||
console.log("❌ 没有找到活动窗口");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`窗口: ${keyWindow}`);
|
||||
|
||||
// 查找QNSView
|
||||
function findQNSView(view) {
|
||||
if (view.$className === 'QNSView') {
|
||||
return view;
|
||||
}
|
||||
|
||||
try {
|
||||
const subviews = view.subviews();
|
||||
const count = subviews.count();
|
||||
for (let i = 0; i < count; i++) {
|
||||
const subview = subviews.objectAtIndex_(i);
|
||||
const found = findQNSView(subview);
|
||||
if (found) return found;
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略错误
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const contentView = keyWindow.contentView();
|
||||
const qnsView = findQNSView(contentView);
|
||||
|
||||
if (!qnsView) {
|
||||
console.log("❌ 没有找到QNSView,使用备用方法");
|
||||
sendEnterKeyAlternative();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✓ 找到QNSView: ${qnsView}`);
|
||||
|
||||
// 创建回车键按下事件
|
||||
const keyDownEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
NSEventTypeKeyDown, // type = 10 (按下)
|
||||
{ x: 100, y: 100 }, // 位置
|
||||
0, // 修饰键
|
||||
Date.now() / 1000, // 时间戳(秒)
|
||||
keyWindow.windowNumber(), // 窗口编号
|
||||
NULL, // 上下文
|
||||
'\r', // 字符(回车)
|
||||
'\r', // 忽略修饰键的字符
|
||||
0, // 是否重复
|
||||
kVK_Return // 键码36=回车
|
||||
);
|
||||
|
||||
// 创建回车键释放事件
|
||||
const keyUpEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
NSEventTypeKeyUp, // type = 11 (释放)
|
||||
{ x: 100, y: 100 }, // 位置
|
||||
0, // 修饰键
|
||||
(Date.now() / 1000) + 0.05, // 稍后的时间
|
||||
keyWindow.windowNumber(), // 窗口编号
|
||||
NULL, // 上下文
|
||||
'\r', // 字符
|
||||
'\r', // 忽略修饰键的字符
|
||||
0, // 是否重复
|
||||
kVK_Return // 键码
|
||||
);
|
||||
|
||||
// 发送按键按下(根据逆向分析,a4=6可能是按键按下)
|
||||
console.log("发送回车键按下...");
|
||||
qnsView.handleKeyEvent_eventType_(keyDownEvent, 6);
|
||||
|
||||
// 延迟发送按键释放
|
||||
setTimeout(() => {
|
||||
console.log("发送回车键释放...");
|
||||
qnsView.handleKeyEvent_eventType_(keyUpEvent, 7); // 猜测7是按键释放
|
||||
}, 50);
|
||||
|
||||
console.log("✓ 回车键发送完成");
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 发送回车键失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 备用方法:使用CGEvent
|
||||
function sendEnterKeyAlternative() {
|
||||
try {
|
||||
console.log("尝试使用CGEvent发送回车键...");
|
||||
|
||||
const CGEventCreateKeyboardEvent = Module.findExportByName('CoreGraphics', 'CGEventCreateKeyboardEvent');
|
||||
const CGEventPost = Module.findExportByName('CoreGraphics', 'CGEventPost');
|
||||
|
||||
if (CGEventCreateKeyboardEvent && CGEventPost) {
|
||||
const kCGHIDEventTap = 0;
|
||||
|
||||
// 发送回车键按下
|
||||
const keyDown = new NativeFunction(CGEventCreateKeyboardEvent, 'pointer', ['pointer', 'uint64', 'bool'])(
|
||||
NULL,
|
||||
kVK_Return,
|
||||
true
|
||||
);
|
||||
new NativeFunction(CGEventPost, 'void', ['uint32', 'pointer'])(kCGHIDEventTap, keyDown);
|
||||
|
||||
// 延迟发送释放
|
||||
setTimeout(() => {
|
||||
const keyUp = new NativeFunction(CGEventCreateKeyboardEvent, 'pointer', ['pointer', 'uint64', 'bool'])(
|
||||
NULL,
|
||||
kVK_Return,
|
||||
false
|
||||
);
|
||||
new NativeFunction(CGEventPost, 'void', ['uint32', 'pointer'])(kCGHIDEventTap, keyUp);
|
||||
console.log("✓ CGEvent 回车键发送完成");
|
||||
}, 50);
|
||||
} else {
|
||||
console.log("❌ CGEvent API 不可用");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ CGEvent方法失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 发送文本"123"的函数
|
||||
function sendText123() {
|
||||
try {
|
||||
console.log("\n=== 发送文本 '123' ===");
|
||||
|
||||
const NSApplication = ObjC.classes.NSApplication;
|
||||
const NSEvent = ObjC.classes.NSEvent;
|
||||
const app = NSApplication.sharedApplication();
|
||||
const keyWindow = app.keyWindow();
|
||||
|
||||
if (!keyWindow) {
|
||||
console.log("❌ 没有找到活动窗口");
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找QNSView
|
||||
function findQNSView(view) {
|
||||
if (view.$className === 'QNSView') return view;
|
||||
try {
|
||||
const subviews = view.subviews();
|
||||
const count = subviews.count();
|
||||
for (let i = 0; i < count; i++) {
|
||||
const found = findQNSView(subviews.objectAtIndex_(i));
|
||||
if (found) return found;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
const qnsView = findQNSView(keyWindow.contentView());
|
||||
|
||||
if (!qnsView) {
|
||||
console.log("❌ 没有找到QNSView");
|
||||
return;
|
||||
}
|
||||
|
||||
// 要发送的字符和对应的键码
|
||||
const textToSend = [
|
||||
{ char: '1', keyCode: 18 },
|
||||
{ char: '2', keyCode: 19 },
|
||||
{ char: '3', keyCode: 20 }
|
||||
];
|
||||
|
||||
// 逐个发送字符
|
||||
textToSend.forEach((item, index) => {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
console.log(`发送字符: ${item.char}`);
|
||||
|
||||
// 创建按键按下事件
|
||||
const keyDownEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
NSEventTypeKeyDown,
|
||||
{ x: 100, y: 100 },
|
||||
0,
|
||||
Date.now() / 1000,
|
||||
keyWindow.windowNumber(),
|
||||
NULL,
|
||||
item.char,
|
||||
item.char,
|
||||
0,
|
||||
item.keyCode
|
||||
);
|
||||
|
||||
// 创建按键释放事件
|
||||
const keyUpEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
NSEventTypeKeyUp,
|
||||
{ x: 100, y: 100 },
|
||||
0,
|
||||
(Date.now() / 1000) + 0.03,
|
||||
keyWindow.windowNumber(),
|
||||
NULL,
|
||||
item.char,
|
||||
item.char,
|
||||
0,
|
||||
item.keyCode
|
||||
);
|
||||
|
||||
// 发送按键按下
|
||||
qnsView.handleKeyEvent_eventType_(keyDownEvent, 6);
|
||||
|
||||
// 延迟发送按键释放
|
||||
setTimeout(() => {
|
||||
qnsView.handleKeyEvent_eventType_(keyUpEvent, 7);
|
||||
}, 30);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`发送字符 ${item.char} 失败: ${error}`);
|
||||
}
|
||||
}, index * 100); // 每个字符间隔100ms
|
||||
});
|
||||
|
||||
console.log("✓ 文本'123'发送中...");
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 发送文本失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 组合函数:先发送123,然后回车
|
||||
function send123AndEnter() {
|
||||
console.log("\n=== 开始发送: 123 + 回车 ===");
|
||||
|
||||
// 先发送123
|
||||
sendText123();
|
||||
|
||||
// 延迟500ms后发送回车
|
||||
setTimeout(() => {
|
||||
console.log("\n=== 发送回车键 ===");
|
||||
sendEnterKey();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// 6. 通用按键注入函数
|
||||
function injectKey(keyCode, eventType, characters = '') {
|
||||
try {
|
||||
const NSApplication = ObjC.classes.NSApplication;
|
||||
const NSEvent = ObjC.classes.NSEvent;
|
||||
const app = NSApplication.sharedApplication();
|
||||
const keyWindow = app.keyWindow();
|
||||
|
||||
if (!keyWindow) {
|
||||
console.log("❌ 没有活动窗口");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找QNSView
|
||||
function findQNSView(view) {
|
||||
if (view.$className === 'QNSView') return view;
|
||||
try {
|
||||
const subviews = view.subviews();
|
||||
for (let i = 0; i < subviews.count(); i++) {
|
||||
const found = findQNSView(subviews.objectAtIndex_(i));
|
||||
if (found) return found;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
const qnsView = findQNSView(keyWindow.contentView());
|
||||
|
||||
if (qnsView) {
|
||||
const event = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
eventType, // 10=按下, 11=释放
|
||||
{ x: 100, y: 100 },
|
||||
0,
|
||||
Date.now() / 1000,
|
||||
keyWindow.windowNumber(),
|
||||
NULL,
|
||||
characters,
|
||||
characters,
|
||||
0,
|
||||
keyCode
|
||||
);
|
||||
|
||||
// 根据逆向分析,a4参数:6可能是按下,7可能是释放
|
||||
const a4Param = eventType === NSEventTypeKeyDown ? 6 : 7;
|
||||
qnsView.handleKeyEvent_eventType_(event, a4Param);
|
||||
|
||||
console.log(`✓ 发送按键: keyCode=${keyCode}, type=${eventType}, a4=${a4Param}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 注入按键失败: ${error}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 直接调用Qt事件发送(基于逆向分析)
|
||||
function sendQtKeyEvent(keyCode, text, modifiers = 0) {
|
||||
try {
|
||||
console.log(`\n=== 直接发送Qt键盘事件: ${text} ===`);
|
||||
|
||||
// 尝试找到QNSView并调用底层函数
|
||||
const NSApplication = ObjC.classes.NSApplication;
|
||||
const app = NSApplication.sharedApplication();
|
||||
const keyWindow = app.keyWindow();
|
||||
|
||||
if (!keyWindow) return;
|
||||
|
||||
// 查找QNSView
|
||||
function findQNSView(view) {
|
||||
if (view.$className === 'QNSView') return view;
|
||||
try {
|
||||
const subviews = view.subviews();
|
||||
for (let i = 0; i < subviews.count(); i++) {
|
||||
const found = findQNSView(subviews.objectAtIndex_(i));
|
||||
if (found) return found;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
const qnsView = findQNSView(keyWindow.contentView());
|
||||
|
||||
if (qnsView && qnsView.handleKeyEvent) {
|
||||
// 创建模拟的事件对象
|
||||
const fakeEvent = {
|
||||
keyCode: function() { return keyCode; },
|
||||
characters: function() { return text; },
|
||||
charactersIgnoringModifiers: function() { return text; },
|
||||
timestamp: function() { return Date.now() / 1000; },
|
||||
modifierFlags: function() { return modifiers; },
|
||||
isARepeat: function() { return 0; }
|
||||
};
|
||||
|
||||
// 包装成ObjC对象
|
||||
const eventWrapper = new ObjC.Object(fakeEvent);
|
||||
|
||||
// 发送事件
|
||||
qnsView.handleKeyEvent_eventType_(eventWrapper, 6);
|
||||
|
||||
console.log(`✓ Qt事件发送: ${text} (keyCode: ${keyCode})`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Qt事件发送失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 8. 导出函数到全局
|
||||
global.sendEnterKey = sendEnterKey;
|
||||
global.sendText123 = sendText123;
|
||||
global.send123AndEnter = send123AndEnter;
|
||||
global.injectKey = injectKey;
|
||||
global.sendQtKeyEvent = sendQtKeyEvent;
|
||||
|
||||
// 9. 创建交互式菜单
|
||||
function showMenu() {
|
||||
console.log("\n" + "=".repeat(50));
|
||||
console.log("🎹 键盘注入器 - 纯JS版本");
|
||||
console.log("=".repeat(50));
|
||||
console.log("可用命令:");
|
||||
console.log("1. sendEnterKey() - 发送回车键");
|
||||
console.log("2. sendText123() - 发送文本 '123'");
|
||||
console.log("3. send123AndEnter() - 发送 '123' 然后回车");
|
||||
console.log("4. injectKey(36, 10) - 发送回车键按下");
|
||||
console.log("5. injectKey(36, 11) - 发送回车键释放");
|
||||
console.log("6. sendQtKeyEvent(18, '1') - 直接发送Qt事件");
|
||||
console.log("=".repeat(50));
|
||||
console.log("示例: 发送 '123' 然后回车:");
|
||||
console.log(" send123AndEnter()");
|
||||
console.log("=".repeat(50));
|
||||
}
|
||||
|
||||
// 10. 自动执行(可选)
|
||||
// 取消下面行的注释可以自动发送
|
||||
// setTimeout(send123AndEnter, 1000);
|
||||
|
||||
// 显示菜单
|
||||
showMenu();
|
||||
|
||||
console.log("\n✅ 键盘注入器加载完成!");
|
||||
console.log("📝 输入命令开始注入键盘事件...");
|
||||
+18
-13
@@ -1,12 +1,17 @@
|
||||
|
||||
|
||||
|
||||
装填数据,发到jobqueue是他们的目地,我感觉重点是在这里
|
||||
sub_1032003B0 -> sub_1024803E4 -> sub_1024C6354 可能是统一入口
|
||||
sub_10237997C image_handler.cc
|
||||
sub_1023E73E8 text_handler.cc
|
||||
sub_102363BB0 file_handler.cc
|
||||
sub_1023E73E8 text_handler.cc ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEaSERKS5_
|
||||
sub_102363BB0 file_handler.cc
|
||||
sub_100400950 装填消息
|
||||
每次都是这个175ED5D10指针装填数据
|
||||
|
||||
sub_10250D878 是整体的发消息入口有多个阶段来自这里
|
||||
StartSendMessageSerial sub_1024C4CB4
|
||||
CoSendMessageWithUploadInfo sub_1023E8108
|
||||
CoAddSendMessageToDb sub_1023C09D0
|
||||
CoPrepareShowSendMessage sub_1023BC4E0
|
||||
|
||||
|
||||
|
||||
键盘事件触发
|
||||
@@ -15,15 +20,15 @@ sub_100662CC4 处理消息的关键函数
|
||||
sub_100662CC4 -> sub_100668580 -> sub_10064DD2C
|
||||
sub_100662CC4 -> sub_10063F318 -> sub_1006DDDBC 处理发送消息结构体
|
||||
sub_1006DDDBC 消息体在这个函数 X1的第一个指针式utf16的发送值,X0不知道是啥,并且X1都是一个地址,追查一下地址 16FDFD688
|
||||
sub_105A25B30 获取值的函数
|
||||
|
||||
16FDFD6F0
|
||||
100179130 可以增加字段 和 sub_105A25B30 获取值的函数
|
||||
16FDFD6F0 指针
|
||||
|
||||
|
||||
真正的发送阶段
|
||||
sub_10250D878 是整体的发消息入口有多个阶段来自这里
|
||||
StartSendMessageSerial sub_1024C4CB4
|
||||
CoSendMessageWithUploadInfo sub_1023E8108
|
||||
CoAddSendMessageToDb sub_1023C09D0
|
||||
CoPrepareShowSendMessage sub_1023BC4E0
|
||||
sub_1024C7FB4 -> sub_102481CA0 -> sub_105268848
|
||||
sub_1024C7FB4 sendMessage 入口
|
||||
sub_1024C7FB4 sendMessage 入口
|
||||
|
||||
|
||||
sub_1023BB1F0->sub_1023990EC->realSendMsg_10239B4C4 发消息的真正函数
|
||||
又调用了sendMsgCoroutine_105261924 进行一步操作但是这个好像是只有发消息才会走的核心coroutine
|
||||
@@ -116,9 +116,7 @@ def print_register_struct(reg_name, struct_size=64, max_depth=3):
|
||||
:param max_depth: 最大递归深度
|
||||
"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"分析寄存器: {reg_name}")
|
||||
print(f"结构体大小: {struct_size} 字节")
|
||||
print(f"{'='*60}")
|
||||
print(f"分析寄存器: {reg_name} 结构体大小: {struct_size} 字节")
|
||||
|
||||
traceMap = {}
|
||||
try:
|
||||
@@ -195,4 +193,4 @@ analyze_all_args()
|
||||
example_usage()
|
||||
"""
|
||||
|
||||
print_register_struct("X1", 128, 3)
|
||||
print_register_struct("X0", 128, 4)
|
||||
@@ -0,0 +1,84 @@
|
||||
import ida_bytes
|
||||
import ida_dbg
|
||||
|
||||
|
||||
def is_printable_string(data):
|
||||
if not data:
|
||||
return False, ""
|
||||
|
||||
result = ""
|
||||
is_str = True
|
||||
for byte in data[:-1]:
|
||||
if 32 <= byte <= 126:
|
||||
result += chr(byte)
|
||||
else:
|
||||
result += "."
|
||||
is_str = False
|
||||
|
||||
return is_str, result
|
||||
|
||||
|
||||
def print_register_struct(reg_name, struct_size=64, max_depth=3):
|
||||
# 获取寄存器值
|
||||
try:
|
||||
reg_value = ida_dbg.get_reg_val(reg_name)
|
||||
except:
|
||||
print(f"无法读取寄存器 {reg_name}")
|
||||
return
|
||||
|
||||
# print(f"寄存器 {reg_name} = 0x{reg_value:X} 结构体大小: {struct_size} 字节 最大深度: {max_depth}")
|
||||
|
||||
if reg_value == 0:
|
||||
print("寄存器值为0 (NULL)")
|
||||
return
|
||||
|
||||
# 开始递归打印
|
||||
print_str(reg_value, struct_size, max_depth, 0)
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
|
||||
def print_str(addr, struct_size, max_depth, current_depth):
|
||||
"""递归打印字符串或指针内容"""
|
||||
if current_depth >= max_depth:
|
||||
print(f"达到最大递归深度 {max_depth}")
|
||||
return
|
||||
|
||||
indent = " " * current_depth
|
||||
|
||||
try:
|
||||
# 尝试读取最多256字节
|
||||
max_read = min(256, struct_size)
|
||||
data = bytearray()
|
||||
|
||||
for i in range(max_read):
|
||||
byte = ida_bytes.get_byte(addr + i)
|
||||
data.append(byte)
|
||||
if byte == 0: # 遇到null终止符
|
||||
break
|
||||
|
||||
# 检查是否为可打印字符串
|
||||
is_str, str_val = is_printable_string(data)
|
||||
|
||||
if is_str and len(str_val) > 0:
|
||||
# if str_val.find('http') != -1:
|
||||
print(f"0x{addr:X} {indent}字符串: \"{str_val}\"")
|
||||
else:
|
||||
# 不是字符串,尝试作为指针处理
|
||||
try:
|
||||
ptr_value = ida_bytes.get_qword(addr)
|
||||
# 检查指针是否有效(非空且对齐)
|
||||
if ptr_value != 0 and ptr_value % 8 == 0:
|
||||
# 递归检查下一层
|
||||
print_str(ptr_value, struct_size, max_depth, current_depth + 1)
|
||||
else:
|
||||
# 显示原始数据
|
||||
print(f"0x{addr:X} {indent}原始数据: {data.hex()}")
|
||||
except:
|
||||
# 无法读取指针,显示原始数据
|
||||
print(f"0x{addr:X} {indent}原始数据: {data.hex()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"{indent}读取地址 0x{addr:X} 失败: {e}")
|
||||
|
||||
|
||||
print_register_struct("X2", 64, 5)
|
||||
@@ -0,0 +1,76 @@
|
||||
import ida_dbg
|
||||
import ida_kernwin
|
||||
import ida_bytes
|
||||
import random
|
||||
|
||||
# --- 配置 ---
|
||||
TARGET_FUNC_EA = 0x1006DDE48 # 目标函数的虚拟地址 (EA)
|
||||
STRUCT_SIZE = 4 * 8 # 4 个 8 字节指针 = 32 字节
|
||||
|
||||
# 假设您已经知道这 4 个指针应该指向的目标地址
|
||||
# 这里的地址应该是目标程序内存中已有的有效地址
|
||||
PTR1_TARGET_VA = 0x108276190
|
||||
PTR2_TARGET_VA = 0x1082f6288
|
||||
PTR3_TARGET_VA = 0x0000600000614820 # 对应内存中的 P3
|
||||
PTR4_TARGET_VA = 0x0000000032AAAAA7 # 对应内存中的 P4 (如果它是地址的话)
|
||||
|
||||
def create_and_set_pointer_arg():
|
||||
"""在目标进程中创建结构体,并将其地址设置为 X0 参数"""
|
||||
|
||||
# 1. 在目标进程中分配内存 (例如 32 字节)
|
||||
# flag 0x01表示分配内存,返回分配内存的起始地址
|
||||
struct_addr = ida_dbg.alloc_dealloc_memory(STRUCT_SIZE, 0x01)
|
||||
if struct_addr == ida_idaapi.BADADDR:
|
||||
ida_kernwin.msg("错误:无法在目标进程中分配内存。")
|
||||
return
|
||||
|
||||
ida_kernwin.msg(f"已在 0x{struct_addr:X} 处分配 {STRUCT_SIZE} 字节内存。")
|
||||
|
||||
# 2. 写入 4 个指针的值 (QWORD - 8 字节)
|
||||
try:
|
||||
ida_bytes.patch_qword(struct_addr + 0, PTR1_TARGET_VA)
|
||||
ida_bytes.patch_qword(struct_addr + 8, PTR2_TARGET_VA)
|
||||
ida_bytes.patch_qword(struct_addr + 16, PTR3_TARGET_VA)
|
||||
ida_bytes.patch_qword(struct_addr + 24, PTR4_TARGET_VA)
|
||||
ida_kernwin.msg("已成功写入 4 个指针到新分配的内存。")
|
||||
except Exception as e:
|
||||
ida_kernwin.msg(f"写入内存失败: {e}")
|
||||
ida_dbg.alloc_dealloc_memory(struct_addr, 0) # 清理内存
|
||||
return
|
||||
|
||||
# 3. 设置 X0 寄存器为这个结构体的地址
|
||||
ida_dbg.set_reg("X0", struct_addr)
|
||||
ida_kernwin.msg(f"已设置 X0 = 结构体指针地址: 0x{struct_addr:X}")
|
||||
|
||||
# 返回地址,供后续调用函数使用
|
||||
return struct_addr
|
||||
|
||||
# --- 完整的调用函数 ---
|
||||
def remote_call_with_pointer():
|
||||
# ... (省略检查和上下文保存代码,与之前相同)
|
||||
|
||||
# 1. 创建并设置指针参数
|
||||
arg_x0_ptr = create_and_set_pointer_arg()
|
||||
if arg_x0_ptr is None:
|
||||
return
|
||||
|
||||
# 2. 设置其他参数 (如果需要)
|
||||
# ida_dbg.set_reg("X1", other_arg_value)
|
||||
|
||||
# 3. 执行函数调用 (X0 已经设置完毕)
|
||||
ida_dbg.call_user_func(TARGET_FUNC_EA, [], ida_dbg.CUF_WAIT)
|
||||
|
||||
# 4. 获取返回值 (X0)
|
||||
return_value = ida_dbg.get_reg("X0")
|
||||
|
||||
# 5. 恢复寄存器上下文 (与之前相同)
|
||||
# ...
|
||||
|
||||
# 6. 清理新分配的内存
|
||||
ida_dbg.alloc_dealloc_memory(arg_x0_ptr, 0) # flag 0x00表示释放内存
|
||||
ida_kernwin.msg(f"已释放分配的内存: 0x{arg_x0_ptr:X}")
|
||||
|
||||
ida_kernwin.msg(f"返回值 (X0): {return_value} (0x{return_value:X})")
|
||||
ida_dbg.run_requests()
|
||||
|
||||
# remote_call_with_pointer()
|
||||
Reference in New Issue
Block a user