343 lines
13 KiB
JavaScript
343 lines
13 KiB
JavaScript
/**
|
||
* system_control.js - Frida系统控制扩展
|
||
* 功能:截图、UI导航、页面检测、输入模拟
|
||
* 通过Frida直接调用Android内部API,不依赖ADB
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
rpc.exports = {
|
||
|
||
// ==================== 截图 ====================
|
||
takeScreenshot: function (params) {
|
||
/**
|
||
* 通过Android Shell命令截图并返回base64
|
||
* 使用Runtime.exec执行screencap(需要Root或同进程权限)
|
||
*/
|
||
try {
|
||
return Java.performNow(function () {
|
||
var savePath = (params && params.path) || '/sdcard/frida_screenshot.png';
|
||
var Runtime = Java.use('java.lang.Runtime');
|
||
var runtime = Runtime.getRuntime();
|
||
|
||
// 方案1: 直接用screencap命令
|
||
var process = runtime.exec(['sh', '-c', 'screencap -p ' + savePath]);
|
||
process.waitFor();
|
||
|
||
// 读取文件并转base64
|
||
var File = Java.use('java.io.File');
|
||
var file = File.$new(savePath);
|
||
if (!file.exists()) {
|
||
return { success: false, error: '截图文件不存在' };
|
||
}
|
||
|
||
var fileSize = file.length();
|
||
var FileInputStream = Java.use('java.io.FileInputStream');
|
||
var fis = FileInputStream.$new(file);
|
||
var bytes = Java.array('byte', new Array(parseInt(fileSize)).fill(0));
|
||
fis.read(bytes);
|
||
fis.close();
|
||
|
||
var Base64 = Java.use('android.util.Base64');
|
||
var b64 = Base64.encodeToString(bytes, 2); // NO_WRAP
|
||
|
||
return {
|
||
success: true,
|
||
path: savePath,
|
||
size: parseInt(fileSize),
|
||
base64_length: b64.length,
|
||
base64: b64.substring(0, 200) + '...(truncated)',
|
||
full_base64: b64,
|
||
};
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
takeScreenshotToFile: function (params) {
|
||
/**
|
||
* 截图保存到指定路径(不返回base64,避免传输大数据)
|
||
*/
|
||
try {
|
||
return Java.performNow(function () {
|
||
var savePath = (params && params.path) || '/sdcard/frida_sc_' + Date.now() + '.png';
|
||
var Runtime = Java.use('java.lang.Runtime');
|
||
var runtime = Runtime.getRuntime();
|
||
var process = runtime.exec(['sh', '-c', 'screencap -p ' + savePath]);
|
||
process.waitFor();
|
||
|
||
var File = Java.use('java.io.File');
|
||
var file = File.$new(savePath);
|
||
if (file.exists()) {
|
||
return { success: true, path: savePath, size: parseInt(file.length()) };
|
||
}
|
||
return { success: false, error: '截图失败' };
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
// ==================== 页面检测 ====================
|
||
getCurrentActivity: function () {
|
||
/**
|
||
* 获取当前前台Activity名称
|
||
*/
|
||
try {
|
||
return Java.performNow(function () {
|
||
var ActivityThread = Java.use('android.app.ActivityThread');
|
||
var currentApp = ActivityThread.currentApplication();
|
||
var activities = [];
|
||
|
||
// 遍历所有Activity
|
||
Java.choose('android.app.Activity', {
|
||
onMatch: function (activity) {
|
||
if (!activity.isFinishing() && !activity.isDestroyed()) {
|
||
activities.push({
|
||
class: activity.getClass().getName(),
|
||
title: activity.getTitle() ? activity.getTitle().toString() : '',
|
||
visible: activity.hasWindowFocus(),
|
||
});
|
||
}
|
||
},
|
||
onComplete: function () {},
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
activities: activities,
|
||
foreground: activities.length > 0 ? activities[activities.length - 1].class : 'unknown',
|
||
};
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
// ==================== UI导航 ====================
|
||
navigateToChat: function (params) {
|
||
/**
|
||
* 导航到指定聊天窗口
|
||
* 通过微信内部Intent跳转
|
||
*/
|
||
try {
|
||
return Java.performNow(function () {
|
||
var wxid = (params && params.wxid) || 'filehelper';
|
||
var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
|
||
var Intent = Java.use('android.content.Intent');
|
||
var Uri = Java.use('android.net.Uri');
|
||
|
||
// 微信内部跳转到聊天页面
|
||
var intent = Intent.$new();
|
||
intent.setClassName('com.tencent.mm', 'com.tencent.mm.ui.chatting.ChattingUI');
|
||
intent.putExtra('Chat_User', wxid);
|
||
intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK
|
||
|
||
ctx.startActivity(intent);
|
||
return { success: true, navigated_to: 'ChattingUI', wxid: wxid };
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
navigateToContacts: function () {
|
||
/**
|
||
* 导航到通讯录页面
|
||
*/
|
||
try {
|
||
return Java.performNow(function () {
|
||
var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
|
||
var Intent = Java.use('android.content.Intent');
|
||
|
||
var intent = Intent.$new();
|
||
intent.setClassName('com.tencent.mm', 'com.tencent.mm.ui.LauncherUI');
|
||
intent.putExtra('LauncherUI.From.Shortcut.Msg', true);
|
||
intent.addFlags(0x10000000);
|
||
ctx.startActivity(intent);
|
||
|
||
return { success: true, navigated_to: 'Contacts' };
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
navigateToMe: function () {
|
||
/**
|
||
* 导航到"我"页面
|
||
*/
|
||
try {
|
||
return Java.performNow(function () {
|
||
var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
|
||
var Intent = Java.use('android.content.Intent');
|
||
|
||
var intent = Intent.$new();
|
||
intent.setClassName('com.tencent.mm', 'com.tencent.mm.ui.LauncherUI');
|
||
intent.addFlags(0x10000000);
|
||
ctx.startActivity(intent);
|
||
|
||
return { success: true, navigated_to: 'Me' };
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
navigateToMoments: function () {
|
||
/**
|
||
* 导航到朋友圈
|
||
*/
|
||
try {
|
||
return Java.performNow(function () {
|
||
var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
|
||
var Intent = Java.use('android.content.Intent');
|
||
|
||
var intent = Intent.$new();
|
||
intent.setClassName('com.tencent.mm', 'com.tencent.mm.plugin.sns.ui.SnsTimeLineUI');
|
||
intent.addFlags(0x10000000);
|
||
ctx.startActivity(intent);
|
||
|
||
return { success: true, navigated_to: 'Moments' };
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
// ==================== 输入模拟 ====================
|
||
simulateTap: function (params) {
|
||
/**
|
||
* 模拟点击(通过shell input tap)
|
||
*/
|
||
try {
|
||
return Java.performNow(function () {
|
||
var x = (params && params.x) || 0;
|
||
var y = (params && params.y) || 0;
|
||
var Runtime = Java.use('java.lang.Runtime');
|
||
var runtime = Runtime.getRuntime();
|
||
var process = runtime.exec(['sh', '-c', 'input tap ' + x + ' ' + y]);
|
||
process.waitFor();
|
||
return { success: true, tapped: { x: x, y: y } };
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
simulateInput: function (params) {
|
||
/**
|
||
* 模拟文本输入
|
||
*/
|
||
try {
|
||
return Java.performNow(function () {
|
||
var text = (params && params.text) || '';
|
||
var Runtime = Java.use('java.lang.Runtime');
|
||
var runtime = Runtime.getRuntime();
|
||
// 需要对特殊字符转义
|
||
var escaped = text.replace(/ /g, '%s').replace(/'/g, "\\'");
|
||
var process = runtime.exec(['sh', '-c', "input text '" + escaped + "'"]);
|
||
process.waitFor();
|
||
return { success: true, input: text };
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
simulateKeyEvent: function (params) {
|
||
/**
|
||
* 模拟按键事件(返回键=4, Home=3, 回车=66)
|
||
*/
|
||
try {
|
||
return Java.performNow(function () {
|
||
var keycode = (params && params.keycode) || 4;
|
||
var Runtime = Java.use('java.lang.Runtime');
|
||
var runtime = Runtime.getRuntime();
|
||
var process = runtime.exec(['sh', '-c', 'input keyevent ' + keycode]);
|
||
process.waitFor();
|
||
return { success: true, keycode: keycode };
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
// ==================== 文件操作 ====================
|
||
readFileBase64: function (params) {
|
||
/**
|
||
* 读取手机上的文件并返回base64
|
||
*/
|
||
try {
|
||
return Java.performNow(function () {
|
||
var filePath = (params && params.path) || '';
|
||
if (!filePath) return { success: false, error: '缺少path参数' };
|
||
|
||
var File = Java.use('java.io.File');
|
||
var file = File.$new(filePath);
|
||
if (!file.exists()) return { success: false, error: '文件不存在: ' + filePath };
|
||
|
||
var fileSize = file.length();
|
||
if (fileSize > 5 * 1024 * 1024) {
|
||
return { success: false, error: '文件太大(>5MB): ' + fileSize };
|
||
}
|
||
|
||
var FileInputStream = Java.use('java.io.FileInputStream');
|
||
var fis = FileInputStream.$new(file);
|
||
var bytes = Java.array('byte', new Array(parseInt(fileSize)).fill(0));
|
||
fis.read(bytes);
|
||
fis.close();
|
||
|
||
var Base64 = Java.use('android.util.Base64');
|
||
var b64 = Base64.encodeToString(bytes, 2);
|
||
|
||
return { success: true, path: filePath, size: parseInt(fileSize), base64: b64 };
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
listFiles: function (params) {
|
||
/**
|
||
* 列出目录下的文件
|
||
*/
|
||
try {
|
||
return Java.performNow(function () {
|
||
var dirPath = (params && params.path) || '/sdcard/';
|
||
var File = Java.use('java.io.File');
|
||
var dir = File.$new(dirPath);
|
||
if (!dir.exists() || !dir.isDirectory()) {
|
||
return { success: false, error: '目录不存在: ' + dirPath };
|
||
}
|
||
var files = dir.listFiles();
|
||
var result = [];
|
||
var len = files ? files.length : 0;
|
||
for (var i = 0; i < Math.min(len, 50); i++) {
|
||
result.push({
|
||
name: files[i].getName(),
|
||
is_dir: files[i].isDirectory(),
|
||
size: files[i].isFile() ? parseInt(files[i].length()) : 0,
|
||
});
|
||
}
|
||
return { success: true, path: dirPath, count: len, files: result };
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
// ==================== 连接状态 ====================
|
||
getConnectionStatus: function () {
|
||
return {
|
||
success: true,
|
||
mode: 'frida_wireless',
|
||
transport: 'tcp',
|
||
pid: Process.id,
|
||
arch: Process.arch,
|
||
timestamp: Date.now(),
|
||
};
|
||
},
|
||
};
|