跳到主要内容
版本:3.0.1

🪝 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 表达式
  • 进程号
  • 标识符
  • 超时时间
    • 整数型可选,单位为毫秒,默认为 不限制
  • 失败原因 文本型
  • 附加数据
    • 字符串型可选JS附加数据,参见说明
  • 执行结果 关联表
    • type 文本型,始终为 "send"
    • payload 任意类型JS执行结果,参见说明
信息

进程号标识符 二者至少指定一个。若同时指定,则优先使用 进程号

说明

JS代码 的返回值需以 send() 函数回传,声明如下:

send(JS执行结果[, JS附加数据])  // JavaScript
  • JS附加数据
    • ArrayBuffer可选,若存在,则 eval.js_sync 的返回值数量会多出一个,即转换为 字符串型附加数据
  • JS执行结果
    • 任意类型,若嵌套包含 ArrayBuffer 类型的值,也会被转换为 字符串型 储存在 关联表 执行结果 的对应位置上

示例:简单表达式

nLog(stringify(eval.js_sync("send(1 + 1);", app.front_pid())))

示例:模拟 alert 对话框

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")))

示例:模拟 confirm 对话框

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

示例:模拟 prompt 对话框

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

示例:App 内部截图

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

注入并异步执行 JavaScript 代码 (eval.js_async)

声明 1

操作成败, 失败原因 = eval.js_async(JS代码, 进程号 或 标识符)

声明 2

操作成败, 失败原因 = eval.js_async {
js = JS代码,
pid = 进程号,
bid = 标识符,
}

参数及返回值

  • JS代码
    • 文本型,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)

注入并异步执行 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 的各项模块。