251 lines
8.1 KiB
JavaScript
Executable File
251 lines
8.1 KiB
JavaScript
Executable File
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.LogLevel = exports.MessageType = exports.ScriptRuntime = exports.Script = void 0;
|
|
const cancellable_1 = require("./cancellable");
|
|
const signals_1 = require("./signals");
|
|
const util_1 = require("util");
|
|
class Script {
|
|
constructor(impl) {
|
|
this.impl = impl;
|
|
this.logHandlerImpl = log;
|
|
const services = new ScriptServices(this, impl.signals);
|
|
const rpcController = services;
|
|
this.exportsProxy = new ScriptExportsProxy(rpcController);
|
|
const signals = services;
|
|
this.destroyed = new signals_1.Signal(signals, "destroyed");
|
|
this.message = new signals_1.Signal(signals, "message");
|
|
}
|
|
get isDestroyed() {
|
|
return this.impl.isDestroyed;
|
|
}
|
|
get exports() {
|
|
return this.exportsProxy;
|
|
}
|
|
get logHandler() {
|
|
return this.logHandlerImpl;
|
|
}
|
|
set logHandler(handler) {
|
|
this.logHandlerImpl = handler;
|
|
}
|
|
get defaultLogHandler() {
|
|
return log;
|
|
}
|
|
load(cancellable) {
|
|
return this.impl.load(cancellable);
|
|
}
|
|
unload(cancellable) {
|
|
return this.impl.unload(cancellable);
|
|
}
|
|
eternalize(cancellable) {
|
|
return this.impl.eternalize(cancellable);
|
|
}
|
|
post(message, data = null) {
|
|
this.impl.post(message, data);
|
|
}
|
|
[util_1.inspect.custom](depth, options) {
|
|
return "Script {}";
|
|
}
|
|
}
|
|
exports.Script = Script;
|
|
var ScriptRuntime;
|
|
(function (ScriptRuntime) {
|
|
ScriptRuntime["Default"] = "default";
|
|
ScriptRuntime["QJS"] = "qjs";
|
|
ScriptRuntime["V8"] = "v8";
|
|
})(ScriptRuntime = exports.ScriptRuntime || (exports.ScriptRuntime = {}));
|
|
var MessageType;
|
|
(function (MessageType) {
|
|
MessageType["Send"] = "send";
|
|
MessageType["Error"] = "error";
|
|
})(MessageType = exports.MessageType || (exports.MessageType = {}));
|
|
var LogLevel;
|
|
(function (LogLevel) {
|
|
LogLevel["Info"] = "info";
|
|
LogLevel["Warning"] = "warning";
|
|
LogLevel["Error"] = "error";
|
|
})(LogLevel = exports.LogLevel || (exports.LogLevel = {}));
|
|
class ScriptServices extends signals_1.SignalAdapter {
|
|
constructor(script, signals) {
|
|
super(signals);
|
|
this.script = script;
|
|
this.pendingRequests = {};
|
|
this.nextRequestId = 1;
|
|
this.onDestroyed = () => {
|
|
this.signals.disconnect("destroyed", this.onDestroyed);
|
|
this.signals.disconnect("message", this.onMessage);
|
|
};
|
|
this.onMessage = (message, data) => {
|
|
if (message.type === MessageType.Send && isRpcSendMessage(message)) {
|
|
const [, id, operation, ...params] = message.payload;
|
|
this.onRpcMessage(id, operation, params, data);
|
|
}
|
|
else if (isLogMessage(message)) {
|
|
const opaqueMessage = message;
|
|
const logMessage = opaqueMessage;
|
|
this.script.logHandler(logMessage.level, logMessage.payload);
|
|
}
|
|
};
|
|
this.signals.connect("destroyed", this.onDestroyed);
|
|
this.signals.connect("message", this.onMessage);
|
|
}
|
|
getProxy(name, userHandler) {
|
|
if (name === "message") {
|
|
return (message, data) => {
|
|
if (!isInternalMessage(message)) {
|
|
userHandler(message, data);
|
|
}
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
request(operation, params, cancellable) {
|
|
return new Promise((resolve, reject) => {
|
|
const id = this.nextRequestId++;
|
|
const complete = (error, result) => {
|
|
if (cancellable !== undefined) {
|
|
cancellable.cancelled.disconnect(onOperationCancelled);
|
|
}
|
|
this.signals.disconnect("destroyed", onScriptDestroyed);
|
|
delete this.pendingRequests[id];
|
|
if (error === null) {
|
|
resolve(result);
|
|
}
|
|
else {
|
|
reject(error);
|
|
}
|
|
};
|
|
function onScriptDestroyed() {
|
|
complete(new Error("Script is destroyed"));
|
|
}
|
|
function onOperationCancelled() {
|
|
complete(new Error("Operation was cancelled"));
|
|
}
|
|
this.pendingRequests[id] = complete;
|
|
this.script.post(["frida:rpc", id, operation].concat(params));
|
|
this.signals.connect("destroyed", onScriptDestroyed);
|
|
if (cancellable !== undefined) {
|
|
cancellable.cancelled.connect(onOperationCancelled);
|
|
if (cancellable.isCancelled) {
|
|
onOperationCancelled();
|
|
return;
|
|
}
|
|
}
|
|
if (this.script.isDestroyed) {
|
|
onScriptDestroyed();
|
|
}
|
|
});
|
|
}
|
|
onRpcMessage(id, operation, params, data) {
|
|
if (operation === RpcOperation.Ok || operation === RpcOperation.Error) {
|
|
const callback = this.pendingRequests[id];
|
|
if (callback === undefined) {
|
|
return;
|
|
}
|
|
let value = null;
|
|
let error = null;
|
|
if (operation === RpcOperation.Ok) {
|
|
value = (data !== null) ? data : params[0];
|
|
}
|
|
else {
|
|
const [message, name, stack, rawErr] = params;
|
|
error = new Error(message);
|
|
error.name = name;
|
|
error.stack = stack;
|
|
Object.assign(error, rawErr);
|
|
}
|
|
callback(error, value);
|
|
}
|
|
}
|
|
}
|
|
function ScriptExportsProxy(rpcController) {
|
|
return new Proxy(this, {
|
|
has(target, property) {
|
|
return !isReservedMethodName(property);
|
|
;
|
|
},
|
|
get(target, property, receiver) {
|
|
if (property in target) {
|
|
return target[property];
|
|
}
|
|
if (property === util_1.inspect.custom) {
|
|
return inspectProxy;
|
|
}
|
|
if (isReservedMethodName(property)) {
|
|
return undefined;
|
|
}
|
|
return (...args) => {
|
|
let cancellable;
|
|
if (args[args.length - 1] instanceof cancellable_1.Cancellable) {
|
|
cancellable = args.pop();
|
|
}
|
|
return rpcController.request("call", [property, args], cancellable);
|
|
};
|
|
},
|
|
set(target, property, value, receiver) {
|
|
target[property] = value;
|
|
return true;
|
|
},
|
|
ownKeys(target) {
|
|
return Object.getOwnPropertyNames(target);
|
|
},
|
|
getOwnPropertyDescriptor(target, property) {
|
|
if (property in target) {
|
|
return Object.getOwnPropertyDescriptor(target, property);
|
|
}
|
|
if (isReservedMethodName(property)) {
|
|
return undefined;
|
|
}
|
|
return {
|
|
writable: true,
|
|
configurable: true,
|
|
enumerable: true
|
|
};
|
|
},
|
|
});
|
|
}
|
|
function inspectProxy() {
|
|
return "ScriptExportsProxy {}";
|
|
}
|
|
var RpcOperation;
|
|
(function (RpcOperation) {
|
|
RpcOperation["Ok"] = "ok";
|
|
RpcOperation["Error"] = "error";
|
|
})(RpcOperation || (RpcOperation = {}));
|
|
function isInternalMessage(message) {
|
|
return isRpcMessage(message) || isLogMessage(message);
|
|
}
|
|
function isRpcMessage(message) {
|
|
return message.type === MessageType.Send && isRpcSendMessage(message);
|
|
}
|
|
function isRpcSendMessage(message) {
|
|
const payload = message.payload;
|
|
if (!(payload instanceof Array)) {
|
|
return false;
|
|
}
|
|
return payload[0] === "frida:rpc";
|
|
}
|
|
function isLogMessage(message) {
|
|
return message.type === "log";
|
|
}
|
|
function log(level, text) {
|
|
switch (level) {
|
|
case LogLevel.Info:
|
|
console.log(text);
|
|
break;
|
|
case LogLevel.Warning:
|
|
console.warn(text);
|
|
break;
|
|
case LogLevel.Error:
|
|
console.error(text);
|
|
break;
|
|
}
|
|
}
|
|
const reservedMethodNames = new Set([
|
|
"then",
|
|
"catch",
|
|
"finally",
|
|
]);
|
|
function isReservedMethodName(name) {
|
|
return reservedMethodNames.has(name.toString());
|
|
}
|