🪝 Frida 助手
Frida 助手 - eval
此模块允许你在目标进程中经由 Frida 注入并执行 JavaScript 代码。
可选
此模块并不随附在 XXTouchNG 中。
受限于你所在地区的法律法规,你可能无法取得、使用此模块。
引入
你需要先安装 Frida,然后在 Lua 脚本中显式引入此模块。
⏬ re.frida.server_16.0.2_iphoneos-arm.deb (17.8 MB)
eval = require("eval")
注入并同步执行 JavaScript 代码 (eval.js_sync)
声明 1
执行结果[, 附加数据], 失败原因 = eval.js_sync(JS代码, 进程号 或 标识符[, 超时时间])
声明 2
执行结果[, 附加数据], 失败原因 = eval.js_sync {
js = JS代码,
pid = 进程号,
bid = 标识符,
timeout = 超时时间
}
参数及返回值
- JS代码
- 文本型,JavaScript 表达式
- 进程号
- 整数型,可选,App 进程号
- 标识符
- 文本型,可选,App 标识符
- 超时时间
- 整数型,可选,单位为毫秒,默认为 不限制
- 失败原因 文本型
- 附加数据
- 字符串型,可选,JS附加数据,参见说明
- 执行结果 关联表
type文本型,始终为"send"payload任意类型,JS执行结果,参见说明
信息
进程号 和 标识符 二者至少指定一个。若同时指定,则优先使用 进程号。
说明
JS代码 的返回值需以 send() 函数回传,声明如下:
send(JS执行结果[, JS附加数据]) // JavaScript
- JS附加数据
ArrayBuffer,可选,若存在,则eval.js_sync的返回值数量会多出一个,即转换为 字符串型 的 附加数据
- JS执行结果
- 任意类型,若嵌套包含
ArrayBuffer类型的值,也会被转换为 字符串型 储存在 关联表 执行结果 的对应位置上
- 任意类型,若嵌套包含
示例:简单表达式
- Lua
- JavaScript
nLog(stringify(eval.js_sync("send(1 + 1);", app.front_pid())))
send(1 + 1);
示例:模拟 alert 对话框
- Lua
- JavaScript
local js = [==[
const alert = (message) => {
const UIAlertController = ObjC.classes.UIAlertController;
const UIAlertAction = ObjC.classes.UIAlertAction;
const UIApplication = ObjC.classes.UIApplication;
ObjC.schedule(ObjC.mainQueue, function () {
const alert = UIAlertController
.alertControllerWithTitle_message_preferredStyle_(
'Frida', message, 1);
const alertHandler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function () {
send(0);
}
});
var defaultAction = UIAlertAction
.actionWithTitle_style_handler_('OK', 0, alertHandler);
alert.addAction_(defaultAction);
UIApplication
.sharedApplication()
.keyWindow()
.rootViewController()
.presentViewController_animated_completion_(alert, true, NULL);
});
};
alert('Hello, World!');
]==]
--
nLog(stringify(eval.js_sync(js, "com.apple.mobileslideshow")))
const alert = (message) => {
const UIAlertController = ObjC.classes.UIAlertController;
const UIAlertAction = ObjC.classes.UIAlertAction;
const UIApplication = ObjC.classes.UIApplication;
ObjC.schedule(ObjC.mainQueue, function () {
const alert = UIAlertController
.alertControllerWithTitle_message_preferredStyle_(
'Frida', message, 1);
const alertHandler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function () {
send(0);
}
});
var defaultAction = UIAlertAction
.actionWithTitle_style_handler_('OK', 0, alertHandler);
alert.addAction_(defaultAction);
UIApplication
.sharedApplication()
.keyWindow()
.rootViewController()
.presentViewController_animated_completion_(alert, true, NULL);
});
};
alert('Hello, World!');
示例:模拟 confirm 对话框
- Lua
- JavaScript
local js = [==[
const confirm = (message) => {
const UIAlertController = ObjC.classes.UIAlertController;
const UIAlertAction = ObjC.classes.UIAlertAction;
const UIApplication = ObjC.classes.UIApplication;
ObjC.schedule(ObjC.mainQueue, function () {
const alert = UIAlertController
.alertControllerWithTitle_message_preferredStyle_(
'Frida', message, 1);
const defaultHandler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function () {
send(0);
}
});
const cancelHandler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function () {
send(1);
}
});
var defaultAction = UIAlertAction
.actionWithTitle_style_handler_('OK', 0, defaultHandler);
var cancelAction = UIAlertAction
.actionWithTitle_style_handler_('Cancel', 1, cancelHandler);
alert.addAction_(defaultAction);
alert.addAction_(cancelAction);
UIApplication
.sharedApplication()
.keyWindow()
.rootViewController()
.presentViewController_animated_completion_(alert, true, NULL);
});
};
confirm('Press a button!\nEither OK or Cancel.');
]==]
--
local res, err = eval.js_sync(js, "com.apple.mobileslideshow")
if err then
sys.alert(err)
else
if res.payload == 0 then sys.alert("OK pressed")
else sys.alert("Cancel pressed")
end
end
const confirm = (message) => {
const UIAlertController = ObjC.classes.UIAlertController;
const UIAlertAction = ObjC.classes.UIAlertAction;
const UIApplication = ObjC.classes.UIApplication;
ObjC.schedule(ObjC.mainQueue, function () {
const alert = UIAlertController
.alertControllerWithTitle_message_preferredStyle_(
'Frida', message, 1);
const defaultHandler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function () {
send(0);
}
});
const cancelHandler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function () {
send(1);
}
});
var defaultAction = UIAlertAction
.actionWithTitle_style_handler_('OK', 0, defaultHandler);
var cancelAction = UIAlertAction
.actionWithTitle_style_handler_('Cancel', 1, cancelHandler);
alert.addAction_(defaultAction);
alert.addAction_(cancelAction);
UIApplication
.sharedApplication()
.keyWindow()
.rootViewController()
.presentViewController_animated_completion_(alert, true, NULL);
});
};
confirm('Press a button!\nEither OK or Cancel.');
示例:模拟 prompt 对话框
- Lua
- JavaScript
local js = [==[
const prompt = (text, defaultText) => {
const UIAlertController = ObjC.classes.UIAlertController;
const UIAlertAction = ObjC.classes.UIAlertAction;
const UIApplication = ObjC.classes.UIApplication;
ObjC.schedule(ObjC.mainQueue, function () {
const alert = UIAlertController
.alertControllerWithTitle_message_preferredStyle_(
'Frida', text, 1);
const defaultHandler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function () {
send(alert.textFields().objectAtIndex_(0).text().valueOf());
}
});
const cancelHandler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function () {
send(1);
}
});
const inputHandler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function (textField) {
textField.setText_(defaultText);
textField.setPlaceholder_(defaultText);
}
});
var defaultAction = UIAlertAction
.actionWithTitle_style_handler_('OK', 0, defaultHandler);
var cancelAction = UIAlertAction
.actionWithTitle_style_handler_('Cancel', 1, cancelHandler);
alert.addAction_(defaultAction);
alert.addAction_(cancelAction);
alert.addTextFieldWithConfigurationHandler_(inputHandler);
UIApplication
.sharedApplication()
.keyWindow()
.rootViewController()
.presentViewController_animated_completion_(alert, true, NULL);
});
};
prompt('Please enter your name', 'Harry Potter');
]==]
--
local res, err = eval.js_sync(js, "com.apple.mobileslideshow")
if err then
sys.alert(err)
else
if type(res.payload) == "string" then
sys.alert("Hello, " .. res.payload .. "! How are you today?")
else
sys.alert("Cancel pressed")
end
end
const prompt = (text, defaultText) => {
const UIAlertController = ObjC.classes.UIAlertController;
const UIAlertAction = ObjC.classes.UIAlertAction;
const UIApplication = ObjC.classes.UIApplication;
ObjC.schedule(ObjC.mainQueue, function () {
const alert = UIAlertController
.alertControllerWithTitle_message_preferredStyle_(
'Frida', text, 1);
const defaultHandler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function () {
send(alert.textFields().objectAtIndex_(0).text().valueOf());
}
});
const cancelHandler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function () {
send(1);
}
});
const inputHandler = new ObjC.Block({
retType: 'void',
argTypes: ['object'],
implementation: function (textField) {
textField.setText_(defaultText);
textField.setPlaceholder_(defaultText);
}
});
var defaultAction = UIAlertAction
.actionWithTitle_style_handler_('OK', 0, defaultHandler);
var cancelAction = UIAlertAction
.actionWithTitle_style_handler_('Cancel', 1, cancelHandler);
alert.addAction_(defaultAction);
alert.addAction_(cancelAction);
alert.addTextFieldWithConfigurationHandler_(inputHandler);
UIApplication
.sharedApplication()
.keyWindow()
.rootViewController()
.presentViewController_animated_completion_(alert, true, NULL);
});
};
prompt('Please enter your name', 'Harry Potter');
示例:App 内部截图
- Lua
- JavaScript
local js = [==[
const screenshot = (callback) => {
ObjC.schedule(ObjC.mainQueue, function() {
var getNativeFunction = function (ex, retVal, args) {
return new NativeFunction(Module.findExportByName('UIKit', ex), retVal, args);
};
var api = {
UIWindow: ObjC.classes.UIWindow,
UIGraphicsBeginImageContextWithOptions: getNativeFunction('UIGraphicsBeginImageContextWithOptions', 'void', [['double', 'double'], 'bool', 'double']),
UIGraphicsBeginImageContextWithOptions: getNativeFunction('UIGraphicsBeginImageContextWithOptions', 'void', [['double', 'double'], 'bool', 'double']),
UIGraphicsEndImageContext: getNativeFunction('UIGraphicsEndImageContext', 'void', []),
UIGraphicsGetImageFromCurrentImageContext: getNativeFunction('UIGraphicsGetImageFromCurrentImageContext', 'pointer', []),
UIImagePNGRepresentation: getNativeFunction('UIImagePNGRepresentation', 'pointer', ['pointer'])
};
var view = api.UIWindow.keyWindow();
var bounds = view.bounds();
var size = bounds[1];
api.UIGraphicsBeginImageContextWithOptions(size, 0, 0);
view.drawViewHierarchyInRect_afterScreenUpdates_(bounds, true);
var image = api.UIGraphicsGetImageFromCurrentImageContext();
api.UIGraphicsEndImageContext();
var png = new ObjC.Object(api.UIImagePNGRepresentation(image));
callback(Memory.readByteArray(png.bytes(), png.length()));
});
};
screenshot((buffer) => { send('screenshot', buffer); });
]==]
--
local res, data, err = eval.js_sync { js = js, pid = app.front_pid() }
if err then
sys.alert(err)
else
if res.payload == "screenshot" then
local img = image.load_data(data)
img:save_to_album()
sys.alert("Screenshot saved to album")
else
sys.alert("Invalid response")
end
end
const screenshot = (callback) => {
ObjC.schedule(ObjC.mainQueue, function() {
var getNativeFunction = function (ex, retVal, args) {
return new NativeFunction(Module.findExportByName('UIKit', ex), retVal, args);
};
var api = {
UIWindow: ObjC.classes.UIWindow,
UIGraphicsBeginImageContextWithOptions: getNativeFunction('UIGraphicsBeginImageContextWithOptions', 'void', [['double', 'double'], 'bool', 'double']),
UIGraphicsBeginImageContextWithOptions: getNativeFunction('UIGraphicsBeginImageContextWithOptions', 'void', [['double', 'double'], 'bool', 'double']),
UIGraphicsEndImageContext: getNativeFunction('UIGraphicsEndImageContext', 'void', []),
UIGraphicsGetImageFromCurrentImageContext: getNativeFunction('UIGraphicsGetImageFromCurrentImageContext', 'pointer', []),
UIImagePNGRepresentation: getNativeFunction('UIImagePNGRepresentation', 'pointer', ['pointer'])
};
var view = api.UIWindow.keyWindow();
var bounds = view.bounds();
var size = bounds[1];
api.UIGraphicsBeginImageContextWithOptions(size, 0, 0);
view.drawViewHierarchyInRect_afterScreenUpdates_(bounds, true);
var image = api.UIGraphicsGetImageFromCurrentImageContext();
api.UIGraphicsEndImageContext();
var png = new ObjC.Object(api.UIImagePNGRepresentation(image));
callback(Memory.readByteArray(png.bytes(), png.length()));
});
};
screenshot((buffer) => { send('screenshot', buffer); });
注入并异步执行 JavaScript 代码 (eval.js_async)
声明 1
操作成败, 失败原因 = eval.js_async(JS代码, 进程号 或 标识符)
声明 2
操作成败, 失败原因 = eval.js_async {
js = JS代码,
pid = 进程号,
bid = 标识符,
}
参数及返回值
信息
进程号 和 标识符 二者至少指定一个。若同时指定,则优先使用 进程号。
示例:异步消息回传
- Lua
- JavaScript
local js = [==[
const ng_send = (payload, data, queue) => {
const queueName = queue || "xxtouch.app.eval";
const procQueue = ObjC.classes.ProcQueue.sharedInstance();
const payloadString = ObjC.Object(JSON.stringify(payload));
if (data) {
const dataBase64EncodedString = ObjC.Object(data).base64EncodedStringWithOptions_(0);
procQueue.unsafeProcQueuePushTailObject_forKey_(dataBase64EncodedString, queueName);
}
procQueue.unsafeProcQueuePushTailObject_forKey_(payloadString, queueName);
};
ng_send('Hello, world!', new Uint8Array([1, 2, 3, 4, 5]).buffer);
]==]
--
require("thread")(function ()
thread.register_event("xxtouch.app.eval", function (val)
nLog(val)
end)
local res, err = eval.js_async { js = js, pid = app.front_pid() }
if err then
sys.alert(err)
else
nLog("detached from target process")
end
end)
const ng_send = (payload, data, queue) => {
const queueName = queue || "xxtouch.app.eval";
const procQueue = ObjC.classes.ProcQueue.sharedInstance();
const payloadString = ObjC.Object(JSON.stringify(payload));
if (data) {
const dataBase64EncodedString = ObjC.Object(data).base64EncodedStringWithOptions_(0);
procQueue.unsafeProcQueuePushTailObject_forKey_(dataBase64EncodedString, queueName);
}
procQueue.unsafeProcQueuePushTailObject_forKey_(payloadString, queueName);
};
ng_send('Hello, world!', new Uint8Array([1, 2, 3, 4, 5]).buffer);
注入并异步执行 Lua 代码 (eval.lua)
声明 1
操作成败, 失败原因 = eval.lua(LUA代码[, 虚拟机名称])
声明 2
操作成败, 失败原因 = eval.lua {
lua = LUA代码,
vm = 虚拟机名称,
}
参数及返回值
- LUA代码
- 文本型,Lua 表达式
- 虚拟机名称
- 文本型,可选,默认为 共享虚拟机
- 失败原因 文本型
- 操作成败 布尔型
信息
Lua 运行时环境为 LuaJIT 2.1.0-beta3,不支持 Lua 5.3 语法。
说明
LUA代码 运行在独立的 Lua 虚拟机中,与脚本进程隔离,亦无法使用 XXTouchNG 的各项模块。