frida-js
Version:
Pure-JS bindings to control Frida from node.js & browsers.
285 lines • 10.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FridaScript = exports.FridaAgentSession = exports.FridaSession = exports.MessageType = exports.downloadFridaServer = exports.calculateFridaSRI = exports.getFridaReleaseDetails = void 0;
exports.connect = connect;
const WebSocket = require("isomorphic-ws");
const createWebSocketStream = require("@httptoolkit/websocket-stream");
const dbus = require("@httptoolkit/dbus-native");
const dbus_value_1 = require("./dbus-value");
var download_frida_1 = require("./download-frida");
Object.defineProperty(exports, "getFridaReleaseDetails", { enumerable: true, get: function () { return download_frida_1.getFridaReleaseDetails; } });
Object.defineProperty(exports, "calculateFridaSRI", { enumerable: true, get: function () { return download_frida_1.calculateFridaSRI; } });
Object.defineProperty(exports, "downloadFridaServer", { enumerable: true, get: function () { return download_frida_1.downloadFridaServer; } });
const DEFAULT_FRIDA_PORT = 27042;
const connectFridaWebSocket = async (fridaHost, options) => {
const socket = new WebSocket(`ws://${fridaHost}/ws`, options);
socket.binaryType = 'arraybuffer';
await new Promise((resolve, reject) => {
socket.addEventListener('open', resolve);
socket.addEventListener('error', reject);
});
return socket;
};
async function connect(options = {}) {
const fridaHost = options.host || `127.0.0.1:${DEFAULT_FRIDA_PORT}`;
const webSocket = options.stream
? await connectFridaWebSocket(fridaHost, {
createConnection: () => options.stream
})
: await connectFridaWebSocket(fridaHost);
const bus = dbus.createClient({
stream: createWebSocketStream(webSocket),
direct: true,
authMethods: []
});
return new FridaSession(bus);
}
/**
* A message sent from a script to the agent.
* https://github.com/frida/frida-node/blob/main/lib/script.ts#L103-L115
*/
var MessageType;
(function (MessageType) {
MessageType["Send"] = "send";
MessageType["Error"] = "error";
MessageType["Log"] = "log";
})(MessageType || (exports.MessageType = MessageType = {}));
var AgentMessageKind;
(function (AgentMessageKind) {
AgentMessageKind[AgentMessageKind["Script"] = 1] = "Script";
AgentMessageKind[AgentMessageKind["Debugger"] = 2] = "Debugger";
})(AgentMessageKind || (AgentMessageKind = {}));
const SUPPORTED_API_VERSIONS = ['17', '16'];
class FridaSession {
bus;
constructor(bus) {
this.bus = bus;
}
/**
* Disconnect from Frida. Returns a promise that resolves once the connection has been closed.
*/
async disconnect() {
return this.bus.disconnect();
}
async getHostSession() {
for (let version of SUPPORTED_API_VERSIONS) {
const hostSession = await this.bus
.getService(`re.frida.HostSession${version}`)
.getInterface('/re/frida/HostSession', `re.frida.HostSession${version}`);
if (hostSession) {
return hostSession;
}
}
throw new Error('Could not create Frida host session (Unsupported API version?)');
}
async getAgentSession(sessionId, pid, hostSession) {
for (let version of SUPPORTED_API_VERSIONS) {
const agentSession = await this.bus
.getService(`re.frida.AgentSession${version}`)
.getInterface('/re/frida/AgentSession/' + sessionId, `re.frida.AgentSession${version}`);
if (agentSession) {
return new FridaAgentSession(this.bus, version, hostSession, pid, sessionId, agentSession);
}
}
throw new Error('Could not create Frida agent session (Unsupported API version?)');
}
/**
* Query the system parameters of the target Frida server. Returns metadata
* as a nested dictionary of strings.
*/
async queryMetadata() {
const hostSession = await this.getHostSession();
const rawMetadata = await hostSession.QuerySystemParameters();
return (0, dbus_value_1.parseDBusVariantDict)(rawMetadata);
}
/**
* List all running processes accessible to the target Frida server. Returns an array
* of { pid, name } objects.
*/
async enumerateProcesses() {
const hostSession = await this.getHostSession();
return (await hostSession.EnumerateProcesses({})).map((proc) => ({
pid: proc[0],
name: proc[1]
}));
}
/**
* List all installed applications accessible on the target Frida server. Returns an array of
* { pid, id, name } objects, where pid is null if the application is not currently running.
*
* This is only applicable to mobile devices, and will return an empty array everywhere else.
*/
async enumerateApplications() {
const hostSession = await this.getHostSession();
return (await hostSession.EnumerateApplications({})).map((proc) => ({
pid: proc[2] || null, // Not running = 0. We map it to null.
id: proc[0],
name: proc[1]
}));
}
async attachToProcess(pid) {
const hostSession = await this.getHostSession();
const [sessionId] = await hostSession.Attach(pid, {});
const agentSession = await this.getAgentSession(sessionId, pid, hostSession);
return {
session: agentSession
};
}
/**
* Attach to a given pid and inject a Frida script to manipulate the target program.
*
* Whether you can attach to the process may depend on system configuration. For
* Linux specifically, if the process is not a child of your own process, you may
* need to run `sudo sysctl kernel.yama.ptrace_scope=0` first.
*/
async injectIntoProcess(pid, fridaScript) {
const { session } = await this.attachToProcess(pid);
const script = await session.createScript(fridaScript);
setTimeout(async () => {
try {
await script.loadScript();
}
catch (e) {
console.warn(e);
}
}, 0);
return {
session,
script
};
}
/**
* Run arbitrary Node.js code directly within a target Node process. The given
* code string will be wrapped with a Frida hook that injects it directly into
* the event loop, so it will run immediately.
*/
async injectIntoNodeJSProcess(pid, nodeScript) {
const fridaScript = require('../scripts/node-js-inject.js')
.buildNodeJsInjectionScript(nodeScript);
return this.injectIntoProcess(pid, fridaScript);
}
async spawnPaused(command, args) {
const hostSession = await this.getHostSession();
const argOptions = args
? [true, [command, ...args]]
: [false, []];
const pid = await hostSession.Spawn(command, [
...argOptions,
false, [],
false, [],
"",
0,
[]
]);
const [sessionId] = await hostSession.Attach(pid, {});
const agentSession = await this.getAgentSession(sessionId, pid, hostSession);
return {
pid,
session: agentSession
};
}
async spawnWithScript(command, args, fridaScript) {
const { session, pid } = await this.spawnPaused(command, args);
const script = await session.createScript(fridaScript);
setTimeout(async () => {
try {
await script.loadScript();
await session.resume();
}
catch (e) {
console.warn(e);
}
}, 0);
return {
pid,
session,
script
};
}
}
exports.FridaSession = FridaSession;
class FridaAgentSession {
bus;
hostVersion;
hostSession;
pid;
sessionId;
agentSession;
constructor(bus, hostVersion, hostSession, pid, sessionId, agentSession) {
this.bus = bus;
this.hostVersion = hostVersion;
this.hostSession = hostSession;
this.pid = pid;
this.sessionId = sessionId;
this.agentSession = agentSession;
}
/**
* This method sets up a message handler for messages sent from the agent.
* @param cb Callback to be called when a message is received from the agent.
*/
onMessage(cb) {
this.bus.setMethodCallHandler(`/re/frida/AgentMessageSink/${this.sessionId}`, `re.frida.AgentMessageSink${this.hostVersion}`, "PostMessages", [(messages) => {
for (const message of messages) {
const msg = JSON.parse(message[2]);
switch (message[0]) { // message[0] is the message kind
case AgentMessageKind.Script:
cb(msg);
break;
}
}
}, null]);
}
/**
* Create a new Frida script within this agent session.
* @param script The Frida script in plain text to create.
* @param options Options to pass to the script.
*/
async createScript(script, options = {}) {
const [scriptId] = await this.agentSession.CreateScript(script, options);
return new FridaScript(this.bus, this.agentSession, [scriptId]);
}
resume() {
return this.hostSession.Resume(this.pid);
}
kill() {
return this.hostSession.Kill(this.pid);
}
}
exports.FridaAgentSession = FridaAgentSession;
const ZERO_LENGTH_BUFFER = Buffer.alloc(0);
class FridaScript {
bus;
agentSession;
scriptId;
constructor(bus, agentSession, scriptId) {
this.bus = bus;
this.agentSession = agentSession;
this.scriptId = scriptId;
}
/**
* Load the script into the target process.
* @returns Promise that resolves when the script is loaded.
*/
async loadScript() {
return this.agentSession.LoadScript(this.scriptId);
}
/**
* Send a message to the script.
* @param message - The message object to send, will be JSON stringified.
* @param data - Optional binary data to send along with the message.
* @returns Promise that resolves when the message is posted.
*/
async post(message, data) {
return this.agentSession.PostMessages([
[
AgentMessageKind.Script,
this.scriptId,
JSON.stringify(message),
data != null,
data ?? ZERO_LENGTH_BUFFER,
]
], 0);
}
}
exports.FridaScript = FridaScript;
//# sourceMappingURL=index.js.map