From 419295ce5a3d0c3bbd443890cb03300742d94567 Mon Sep 17 00:00:00 2001 From: pancake Date: Tue, 23 Jan 2024 19:00:49 +0100 Subject: [PATCH] Use prettier to indent the Typescript code --- typescript/.eslintrc.json | 27 + typescript/.prettierrc | 7 + typescript/Makefile | 2 +- typescript/async/ai.ts | 202 ++-- typescript/async/base64.ts | 38 +- typescript/async/elang.ts | 45 +- typescript/async/esil.ts | 969 ++++++++-------- typescript/async/graph.ts | 63 +- typescript/async/index.ts | 9 +- typescript/async/opt.ts | 17 +- typescript/async/pdq.ts | 191 ++-- typescript/async/r2frida.ts | 104 +- typescript/async/r2papi.ts | 2094 +++++++++++++++++----------------- typescript/async/r2pipe.ts | 48 +- typescript/async/shell.ts | 349 +++--- typescript/package-lock.json | 22 + typescript/package.json | 9 +- 17 files changed, 2169 insertions(+), 2027 deletions(-) create mode 100644 typescript/.eslintrc.json create mode 100644 typescript/.prettierrc diff --git a/typescript/.eslintrc.json b/typescript/.eslintrc.json new file mode 100644 index 0000000..3a5c59d --- /dev/null +++ b/typescript/.eslintrc.json @@ -0,0 +1,27 @@ +{ + "env": { + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 8, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "no-empty": ["error", { "allowEmptyCatch": true }], + "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unused-vars": "off" + } +} + + diff --git a/typescript/.prettierrc b/typescript/.prettierrc new file mode 100644 index 0000000..a8b39cb --- /dev/null +++ b/typescript/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "tabWidth": 4, + "trailingComma": "none", + "singleQuote": false, + "printWidth": 80 +} diff --git a/typescript/Makefile b/typescript/Makefile index eacd7e7..01b0918 100644 --- a/typescript/Makefile +++ b/typescript/Makefile @@ -5,7 +5,7 @@ all: node_modules $(MAKE) build vs: - open -a "Visual Studio Code" . || vscode . + open -a "Visual Studio Code" async || vscode . SYNCREGEX= diff --git a/typescript/async/ai.ts b/typescript/async/ai.ts index 2bcee9a..9de0bbd 100644 --- a/typescript/async/ai.ts +++ b/typescript/async/ai.ts @@ -1,38 +1,38 @@ -import { R, Module, Process, Thread } from "./r2papi.js" +import { R, Module, Process, Thread } from "./r2papi.js"; import { r2, R2PipeAsync } from "./r2pipe.js"; /** * Class that interacts with the `r2ai` plugin (requires `rlang-python` and `r2i` r2pm packages to be installed). * Provides a way to script the interactions with different language models using javascript from inside radare2. - * + * * @typedef R2AI */ export class R2AI { - /** - * Instance variable that informs if the `r2ai` plugin is loaded, must be true in order to use the rest of the methods of this class. - * - * @type {boolean} - */ - available: boolean = false; - /** - * Name of the model instantiated to be used for the subsequent calls. - * - * @type {string} - */ - model: string = ""; - r2: R2PipeAsync; + /** + * Instance variable that informs if the `r2ai` plugin is loaded, must be true in order to use the rest of the methods of this class. + * + * @type {boolean} + */ + available: boolean = false; + /** + * Name of the model instantiated to be used for the subsequent calls. + * + * @type {string} + */ + model: string = ""; + r2: R2PipeAsync; - constructor(r2: R2PipeAsync, num?: number, model?: string) { - this.r2 = r2; - this.available = false; - } + constructor(r2: R2PipeAsync, num?: number, model?: string) { + this.r2 = r2; + this.available = false; + } - async checkAvailability() : Promise { - if (this.available) { - return true; - } - this.available = r2.cmd('r2ai -h').trim() !== ""; - /* + async checkAvailability(): Promise { + if (this.available) { + return true; + } + this.available = r2.cmd("r2ai -h").trim() !== ""; + /* if (this.available) { if (num) { r2.call(`r2ai -n ${num}`) @@ -43,78 +43,82 @@ export class R2AI { } } */ - return this.available; - } - /** - * Reset conversation messages - */ - async reset() { - await this.checkAvailability(); - if (this.available) { - await r2.call('r2ai -R') - } - } - /** - * Set the role (system prompt) message for the language model to obey. - * - * @param {string} text containing the system prompt - * @returns {boolean} true if successful - */ - async setRole(msg: string): Promise { - if (this.available) { - await r2.call(`r2ai -r ${msg}`); - return true; - } - return false; - } - /** - * Set the Model name or path to the GGUF file to use. - * - * @param {string} model name or path to GGUF file - * @returns {boolean} true if successful - */ - async setModel(modelName: string): Promise { - if (this.available) { - await r2.call(`r2ai -m ${this.model}`) - return true; - } - return false; - } - /** - * Get the current selected model name. - * - * @returns {boolean} model name - */ - async getModel(): Promise { - if (this.available) { - this.model = await r2.call("r2ai -m").trim(); - } - return this.model; - } - /** - * Get a list of suggestions for model names to use. - * - * @returns {string[]} array of strings containing the model names known to work - */ - async listModels(): Promise { - if (this.available) { - const models = await r2.call("r2ai -M"); - return models.replace(/-m /, "").trim().split(/\n/g).filter((x: string) => x.indexOf(":") !== -1); - } - return []; - } - /** - * Send message to the language model to be appended to the current conversation (see `.reset()`) - * - * @param {string} text sent from the user to the language model - * @returns {string} response from the language model - */ - async query(msg: string): Promise { - if (!this.available || msg == '') { - return ''; - } - const fmsg = msg.trim().replace(/\n/g, '.'); - const response = r2.call(`r2ai ${fmsg}`); - return response.trim(); - } + return this.available; + } + /** + * Reset conversation messages + */ + async reset() { + await this.checkAvailability(); + if (this.available) { + await r2.call("r2ai -R"); + } + } + /** + * Set the role (system prompt) message for the language model to obey. + * + * @param {string} text containing the system prompt + * @returns {boolean} true if successful + */ + async setRole(msg: string): Promise { + if (this.available) { + await r2.call(`r2ai -r ${msg}`); + return true; + } + return false; + } + /** + * Set the Model name or path to the GGUF file to use. + * + * @param {string} model name or path to GGUF file + * @returns {boolean} true if successful + */ + async setModel(modelName: string): Promise { + if (this.available) { + await r2.call(`r2ai -m ${this.model}`); + return true; + } + return false; + } + /** + * Get the current selected model name. + * + * @returns {boolean} model name + */ + async getModel(): Promise { + if (this.available) { + this.model = await r2.call("r2ai -m").trim(); + } + return this.model; + } + /** + * Get a list of suggestions for model names to use. + * + * @returns {string[]} array of strings containing the model names known to work + */ + async listModels(): Promise { + if (this.available) { + const models = await r2.call("r2ai -M"); + return models + .replace(/-m /, "") + .trim() + .split(/\n/g) + .filter((x: string) => x.indexOf(":") !== -1); + } + return []; + } + /** + * Send message to the language model to be appended to the current conversation (see `.reset()`) + * + * @param {string} text sent from the user to the language model + * @returns {string} response from the language model + */ + async query(msg: string): Promise { + if (!this.available || msg == "") { + return ""; + } + const fmsg = msg.trim().replace(/\n/g, "."); + const response = r2.call(`r2ai ${fmsg}`); + return response.trim(); + } } diff --git a/typescript/async/base64.ts b/typescript/async/base64.ts index 143c8a6..cca5949 100644 --- a/typescript/async/base64.ts +++ b/typescript/async/base64.ts @@ -1,26 +1,26 @@ export class Base64 { - /** - * Encode the given input string using base64 - * - * @param {string} input string to encode - * @returns {string} base64 encoded string - */ - static encode(input: string): string { - return b64(input); - } - /** - * Decode the given base64 string into plain text - * - * @param {string} input string encoded in base64 format - * @returns {string} base64 decoded string - */ - static decode(input: string): string { - return b64(input, true); - } + /** + * Encode the given input string using base64 + * + * @param {string} input string to encode + * @returns {string} base64 encoded string + */ + static encode(input: string): string { + return b64(input); + } + /** + * Decode the given base64 string into plain text + * + * @param {string} input string encoded in base64 format + * @returns {string} base64 decoded string + */ + static decode(input: string): string { + return b64(input, true); + } } export interface Base64Interface { - (message: string, decode?: boolean): string; + (message: string, decode?: boolean): string; } export declare const b64: Base64Interface; diff --git a/typescript/async/elang.ts b/typescript/async/elang.ts index d73d90a..dd91732 100644 --- a/typescript/async/elang.ts +++ b/typescript/async/elang.ts @@ -4,31 +4,30 @@ import { R2PipeAsync } from "./index"; // export class EsilLang { - [vars : string]: any; - constructor() { - this.vars = {}; - } - set(obj: any, val: any) { - this.vars[obj] = val; - } - fun(name: string, code: any[]) { - this.vars[name] = code; - } - get(varname: string) : any { - if (varname in Object.keys(this.vars)) { - return this.vars[varname]; - } - this.vars[varname] = 0; - return 0; - } - println(...args: any) { - console.log(...args); - } - eval(code: string) { - } + [vars: string]: any; + constructor() { + this.vars = {}; + } + set(obj: any, val: any) { + this.vars[obj] = val; + } + fun(name: string, code: any[]) { + this.vars[name] = code; + } + get(varname: string): any { + if (varname in Object.keys(this.vars)) { + return this.vars[varname]; + } + this.vars[varname] = 0; + return 0; + } + println(...args: any) { + console.log(...args); + } + eval(code: string) {} } -// basic elements: array, string, +// basic elements: array, string, // console.log("Hello Lang"); const el = new EsilLang(); const code = `[ diff --git a/typescript/async/esil.ts b/typescript/async/esil.ts index 91c6e83..e967376 100644 --- a/typescript/async/esil.ts +++ b/typescript/async/esil.ts @@ -17,488 +17,509 @@ declare let r2: R2PipeAsync; */ export class EsilToken { - label: string = ""; - comment: string = ""; - text: string = ""; - addr: string = "0"; // for ut64 we use strings for numbers :< - position: number = 0; - constructor(text: string = "", position: number = 0) { - this.text = text; - this.position = position; - } - toString() : string { - return this.text; - } + label: string = ""; + comment: string = ""; + text: string = ""; + addr: string = "0"; // for ut64 we use strings for numbers :< + position: number = 0; + constructor(text: string = "", position: number = 0) { + this.text = text; + this.position = position; + } + toString(): string { + return this.text; + } } -export type EsilNodeType = "number" | "flag" | "register" | "operation" | "none" | "block" | "goto" | "label"; +export type EsilNodeType = + | "number" + | "flag" + | "register" + | "operation" + | "none" + | "block" + | "goto" + | "label"; export class EsilNode { - lhs: EsilNode | undefined; - rhs: EsilNode | undefined; - children: EsilNode[]; - token: EsilToken; - type: EsilNodeType = "none"; - constructor (token: EsilToken = new EsilToken(), type: EsilNodeType = "none") { - this.token = token; - this.children = []; - } - setSides(lhs: EsilNode, rhs: EsilNode): void { - this.lhs = lhs; - this.rhs = rhs; - } - addChildren(ths: EsilNode, fhs: EsilNode): void { - if (ths !== undefined) { - this.children.push(ths); - } - if (fhs !== undefined) { - this.children.push(fhs); - } - } - toEsil() : string { - if (this.lhs !== undefined && this.rhs !== undefined) { - // XXX handle ?{ }{ } - let left = this.lhs.toEsil(); - if (left !== "") { - left += ","; - } - const right = this.rhs.toEsil(); - return `${right},${left}${this.token}`; - } - return ''; // this.token.text; - } - toString() : string { - let str = ""; - if (this.token.label !== "") { - str += this.token.label + ":\n"; - } - if (this.token.addr !== "0") { - // str += "// @ " + this.token.addr + "\n"; - } - if (this.token.comment !== "") { - str += "/*" + this.token.comment + "*/\n"; - } - if (this.token.toString() === "GOTO") { - if (this.children.length > 0) { - const children = this.children[0]; - str += "goto label_" + children.token.position + ";\n"; - } else { - // console.log(JSON.stringify(this,null, 2)); - const pos = 0; - str += `goto label_${pos};\n`; - } - } - if (this.children.length > 0) { - str += ` (if (${this.rhs})\n`; - for (const children of this.children) { - if (children !== null) { - const x = children.toString(); - if (x != "") { - str += ` ${x}\n`; - } - } - } - str += " )\n"; - } - if (this.lhs !== undefined && this.rhs !== undefined) { - return str + ` ( ${this.lhs} ${this.token} ${this.rhs} )`; - // return str + `${this.lhs} ${this.token} ${this.rhs}`; - } - return str + this.token.toString(); - } + lhs: EsilNode | undefined; + rhs: EsilNode | undefined; + children: EsilNode[]; + token: EsilToken; + type: EsilNodeType = "none"; + constructor( + token: EsilToken = new EsilToken(), + type: EsilNodeType = "none" + ) { + this.token = token; + this.children = []; + } + setSides(lhs: EsilNode, rhs: EsilNode): void { + this.lhs = lhs; + this.rhs = rhs; + } + addChildren(ths: EsilNode, fhs: EsilNode): void { + if (ths !== undefined) { + this.children.push(ths); + } + if (fhs !== undefined) { + this.children.push(fhs); + } + } + toEsil(): string { + if (this.lhs !== undefined && this.rhs !== undefined) { + // XXX handle ?{ }{ } + let left = this.lhs.toEsil(); + if (left !== "") { + left += ","; + } + const right = this.rhs.toEsil(); + return `${right},${left}${this.token}`; + } + return ""; // this.token.text; + } + toString(): string { + let str = ""; + if (this.token.label !== "") { + str += this.token.label + ":\n"; + } + if (this.token.addr !== "0") { + // str += "// @ " + this.token.addr + "\n"; + } + if (this.token.comment !== "") { + str += "/*" + this.token.comment + "*/\n"; + } + if (this.token.toString() === "GOTO") { + if (this.children.length > 0) { + const children = this.children[0]; + str += "goto label_" + children.token.position + ";\n"; + } else { + // console.log(JSON.stringify(this,null, 2)); + const pos = 0; + str += `goto label_${pos};\n`; + } + } + if (this.children.length > 0) { + str += ` (if (${this.rhs})\n`; + for (const children of this.children) { + if (children !== null) { + const x = children.toString(); + if (x != "") { + str += ` ${x}\n`; + } + } + } + str += " )\n"; + } + if (this.lhs !== undefined && this.rhs !== undefined) { + return str + ` ( ${this.lhs} ${this.token} ${this.rhs} )`; + // return str + `${this.lhs} ${this.token} ${this.rhs}`; + } + return str + this.token.toString(); + } } export class EsilParser { - r2: R2PipeAsync; - stack: EsilNode[]; // must be a stack or a list.. to parse sub expressions we must reset - nodes: EsilNode[]; - root: EsilNode; - tokens: EsilToken[]; - cur: number = 0; + r2: R2PipeAsync; + stack: EsilNode[]; // must be a stack or a list.. to parse sub expressions we must reset + nodes: EsilNode[]; + root: EsilNode; + tokens: EsilToken[]; + cur: number = 0; - constructor (r2: R2PipeAsync) { - this.r2 = r2; - this.cur = 0; - this.stack = []; - this.nodes = []; - this.tokens = []; - this.root = new EsilNode (new EsilToken("function", 0), "block"); - } - toJSON() : string { - if (this.stack.length > 0) { - // return JSON.stringify (this.stack, null, 2); - throw new Error("The ESIL stack is not empty"); - } - return JSON.stringify (this.root, null, 2); - } - toEsil() : string { - return this.nodes - .map( (x) => x.toEsil()) - .join(','); - } - private async optimizeFlags(node: EsilNode) { - if (node.rhs !== undefined) { - await this.optimizeFlags(node.rhs); - } - if (node.lhs !== undefined) { - await this.optimizeFlags(node.lhs); - } - for (let i = 0; i < node.children.length;i++) { - await this.optimizeFlags(node.children[i]); - } - const addr : string = node.toString(); - if (+addr > 4096) { - const cname = await r2.cmd(`fd.@ ${addr}`); - const fname = cname.trim().split("\n")[0].trim(); - if (fname != "" && fname.indexOf("+") === -1) { - node.token.text = fname; - } - } - } - async optimize(options: string) : Promise { - if (options.indexOf("flag") != -1) { - await this.optimizeFlags(this.root); - } - } - toString() : string { - return this.root.children - .map( (x) => x.toString()) - .join(';\n'); - } - reset() : void { - this.nodes = []; - this.stack = []; - this.tokens = []; - this.cur = 0; - this.root = new EsilNode (new EsilToken("function", 0), "block"); - } - parseRange(from: number, to: number) { - let pos = from; - while (pos < this.tokens.length && pos < to) { - const token = this.peek(pos); - if (!token) { - // console.log("BREAK"); - break; - } - // console.log(pos, token); - this.cur = pos; - this.pushToken(token); - pos = this.cur; - pos++; - } - // console.log("done"); - } - async parseFunction(addr?: string) : Promise { - const ep = this; - async function parseAmount(n:number) : Promise { - // console.log("PDQ "+n); - const output = await r2.cmd("pie " + n + " @e:scr.color=0"); - const lines = output.trim().split("\n"); - for (const line of lines) { - if (line.length === 0) { - console.log("Empty"); - continue; - } - // console.log("parse", r2.cmd("?v:$$")); - const kv = line.split(' '); - if (kv.length > 1) { // line != "") { - // console.log("// @ " + kv[0]); - //ep.reset (); - await r2.cmd(`s ${kv[0]}`); - ep.parse(kv[1], kv[0]); - ep.optimize("flags,labels"); - //console.log(ep.toString()); - } - } - // console.log(ep.toString()); - } - const oaddr = (await r2.cmd("?v $$")).trim(); - // const func = r2.cmdj("pdrj"); // XXX this command changes the current seek - if (addr === undefined) { - addr = oaddr; - } - const bbs = await r2.cmdj(`afbj@${addr}`); // XXX this command changes the current seek - for (const bb of bbs) { - // console.log("bb_" + bb.addr + ":"); - await r2.cmd(`s ${bb.addr}`); - await parseAmount (bb.ninstr); - } - r2.cmd(`s ${oaddr}`); - } - parse(expr: string, addr?: string) : void | never { - const tokens = expr.trim().split(',').map( (x) => x.trim() ); - const from = this.tokens.length; - for (const tok of tokens) { - const token = new EsilToken(tok, this.tokens.length); - if (addr !== undefined) { - token.addr = addr; - } - this.tokens.push(token); - } - const to = this.tokens.length; - this.parseRange (from, to); - } + constructor(r2: R2PipeAsync) { + this.r2 = r2; + this.cur = 0; + this.stack = []; + this.nodes = []; + this.tokens = []; + this.root = new EsilNode(new EsilToken("function", 0), "block"); + } + toJSON(): string { + if (this.stack.length > 0) { + // return JSON.stringify (this.stack, null, 2); + throw new Error("The ESIL stack is not empty"); + } + return JSON.stringify(this.root, null, 2); + } + toEsil(): string { + return this.nodes.map((x) => x.toEsil()).join(","); + } + private async optimizeFlags(node: EsilNode) { + if (node.rhs !== undefined) { + await this.optimizeFlags(node.rhs); + } + if (node.lhs !== undefined) { + await this.optimizeFlags(node.lhs); + } + for (let i = 0; i < node.children.length; i++) { + await this.optimizeFlags(node.children[i]); + } + const addr: string = node.toString(); + if (+addr > 4096) { + const cname = await r2.cmd(`fd.@ ${addr}`); + const fname = cname.trim().split("\n")[0].trim(); + if (fname != "" && fname.indexOf("+") === -1) { + node.token.text = fname; + } + } + } + async optimize(options: string): Promise { + if (options.indexOf("flag") != -1) { + await this.optimizeFlags(this.root); + } + } + toString(): string { + return this.root.children.map((x) => x.toString()).join(";\n"); + } + reset(): void { + this.nodes = []; + this.stack = []; + this.tokens = []; + this.cur = 0; + this.root = new EsilNode(new EsilToken("function", 0), "block"); + } + parseRange(from: number, to: number) { + let pos = from; + while (pos < this.tokens.length && pos < to) { + const token = this.peek(pos); + if (!token) { + // console.log("BREAK"); + break; + } + // console.log(pos, token); + this.cur = pos; + this.pushToken(token); + pos = this.cur; + pos++; + } + // console.log("done"); + } + async parseFunction(addr?: string): Promise { + const ep = this; + async function parseAmount(n: number): Promise { + // console.log("PDQ "+n); + const output = await r2.cmd("pie " + n + " @e:scr.color=0"); + const lines = output.trim().split("\n"); + for (const line of lines) { + if (line.length === 0) { + console.log("Empty"); + continue; + } + // console.log("parse", r2.cmd("?v:$$")); + const kv = line.split(" "); + if (kv.length > 1) { + // line != "") { + // console.log("// @ " + kv[0]); + //ep.reset (); + await r2.cmd(`s ${kv[0]}`); + ep.parse(kv[1], kv[0]); + ep.optimize("flags,labels"); + //console.log(ep.toString()); + } + } + // console.log(ep.toString()); + } + const oaddr = (await r2.cmd("?v $$")).trim(); + // const func = r2.cmdj("pdrj"); // XXX this command changes the current seek + if (addr === undefined) { + addr = oaddr; + } + const bbs = await r2.cmdj(`afbj@${addr}`); // XXX this command changes the current seek + for (const bb of bbs) { + // console.log("bb_" + bb.addr + ":"); + await r2.cmd(`s ${bb.addr}`); + await parseAmount(bb.ninstr); + } + r2.cmd(`s ${oaddr}`); + } + parse(expr: string, addr?: string): void | never { + const tokens = expr + .trim() + .split(",") + .map((x) => x.trim()); + const from = this.tokens.length; + for (const tok of tokens) { + const token = new EsilToken(tok, this.tokens.length); + if (addr !== undefined) { + token.addr = addr; + } + this.tokens.push(token); + } + const to = this.tokens.length; + this.parseRange(from, to); + } - peek (a:number): EsilToken | undefined { - return this.tokens[a]; - } + peek(a: number): EsilToken | undefined { + return this.tokens[a]; + } - pushToken(tok: EsilToken) : void | never { - if (this.isNumber (tok)) { - const node = new EsilNode(tok, "number"); - this.stack.push(node); - this.nodes.push(node); - } else if (this.isInternal (tok)) { - const node = new EsilNode(tok, "flag"); - this.stack.push(node); - this.nodes.push(node); - } else if (this.isOperation (tok)) { - // run the operation login - } else { - // assume it's a register, so just push the string - const node = new EsilNode(tok, "register"); - this.stack.push(node); - this.nodes.push(node); - } - // we need a list of register names to do this check properly - // throw new Error ("Unknown token"); - } - private isNumber(expr: EsilToken) : boolean { - if (expr.toString().startsWith("0")) { - return true; - } - return +expr > 0; - } - private isInternal(expr: EsilToken) : boolean { - const text = expr.toString(); - return text.startsWith("$") && text.length > 1; - } - private parseUntil(start: number) : EsilNode | null { - const from = start + 1; - let pos = from; - const origStack : any[] = []; - const this_nodes_length = this.nodes.length; - this.stack.forEach((x) => origStack.push(x)); - while (pos < this.tokens.length) { - const token = this.peek(pos); - if (!token) { - break; - } - if (token.toString() === '}') { - break; - } - if (token.toString() === '}{') { - // return token; - break; - } - // console.log("peek ", this.tokens[pos]); - pos++; - } - this.stack = origStack; - const to = pos; - this.parseRange(from, to); - const same = this.nodes.length == this_nodes_length; - // console.log("BLOCK ("+ ep.toString()); - if (same) { - return null; - } - return this.nodes[this.nodes.length - 1]; // this.tokens.length - 1]; - } - private getNodeFor(index:number): null | EsilNode { - const tok = this.peek(index); - if (tok === undefined) { - return null; - } - for (const node of this.nodes) { - if (node.token.position === index) { - return node; - } - } - this.nodes.push(new EsilNode(new EsilToken("label", index), "label")); - return null; - } - private findNodeFor(index:number): null | EsilNode { - for (const node of this.nodes) { - if (node.token.position === index) { - return node; - } - } - return null; - } - private isOperation(expr: EsilToken) : never | boolean { - switch(expr.toString()) { - // 1pop1push - case "[1]": - case "[2]": - case "[4]": - case "[8]": - if (this.stack.length >= 1) { - const i1 = this.stack.pop()!; - // TODO: MemoryReferenceNode(i1)); - const mn = new EsilNode(i1.token, "operation"); // expr.toString()); - this.stack.push(i1); // mn); - } else { - throw new Error("Stack needs more items"); - } - return true; - // 1pop1push - case "!": - if (this.stack.length >= 1) { - const i0 = new EsilNode(new EsilToken("", expr.position), "none"); - const i1 = this.stack.pop()!; - const nn = new EsilNode(expr, "operation"); - nn.setSides(i0, i1); - this.stack.push(nn); - } else { - throw new Error("Stack needs more items"); - } - return true; - case "": - case "}": - case "}{": - // no pops or nothing, just does nothing - return true; - case "DUP": - if (this.stack.length < 1) { - throw new Error("goto cant pop"); - } else { - const destNode = this.stack.pop()!; - this.stack.push(destNode); - this.stack.push(destNode); - } - return true; - case "GOTO": - // take previous statement which should be const and add a label - { - const prev = this.peek(expr.position - 1); - if (prev !== null) { - // TODO: check stack - if (this.stack.length < 1) { - throw new Error("goto cant pop"); - } - const destNode = this.stack.pop()!; - if (destNode !== null) { - const value : number = 0 | +destNode.toString(); - if (value > 0) { - const destToken = this.peek(value); - if (destToken !== undefined) { - destToken.label = "label_" + value; - destToken.comment = "hehe"; - const nn = new EsilNode(expr, "goto"); - const gn = this.getNodeFor(destToken.position); - if (gn != null) { - nn.children.push(gn); - } - this.root.children.push(nn); - } else { - console.error("Cannot find goto node"); - } - } else { - console.error("Cannot find dest node for goto"); - } - } - } - } - return true; - // controlflow - case "?{": // ESIL_TOKEN_IF - if (this.stack.length >= 1) { - const i0 = new EsilNode(new EsilToken("if", expr.position), "none"); - const i1 = this.stack.pop()!; - const nn = new EsilNode(expr, "operation"); - nn.setSides(i0, i1); // left side can be ignored for now.. but we can express this somehow - const trueBlock = this.parseUntil(expr.position); - let falseBlock = null; - // nn.addChildren(trueBlock, falseBlock); - if (trueBlock !== null) { - nn.children.push(trueBlock); - this.nodes.push(trueBlock); - falseBlock = this.parseUntil(trueBlock.token.position + 1); - if (falseBlock !== null) { - nn.children.push(falseBlock); - this.nodes.push(falseBlock); - } - } - // console.log("true", trueBlock); - // console.log("false", falseBlock); - // this.stack.push(nn); - this.nodes.push(nn); - this.root.children.push(nn); - if (falseBlock !== null) { - this.cur = falseBlock.token.position; - } - } else { - throw new Error("Stack needs more items"); - } - return true; - case "-": - if (this.stack.length >= 2) { - const i0 = this.stack.pop()!; - const i1 = this.stack.pop()!; - const nn = new EsilNode(expr, "operation"); - nn.setSides(i0, i1); - if (this.stack.length === 0) { - // this.root.children.push(nn); - } - this.stack.push(nn); - this.nodes.push(nn); - } else { - throw new Error("Stack needs more items"); - } - return true; - // 2pop1push - case "<": - case ">": - case "^": - case "&": - case "|": - case "+": - case "*": - case "/": - case ">>=": - case "<<=": - case ">>>=": - case "<<<=": - case ">>>>=": - case "<<<<=": - if (this.stack.length >= 2) { - const i0 = this.stack.pop()!; - const i1 = this.stack.pop()!; - const nn = new EsilNode(expr, "operation"); - nn.setSides(i0, i1); - if (this.stack.length === 0) { - // this.root.children.push(nn); - } - this.stack.push(nn); - this.nodes.push(nn); - } else { - throw new Error("Stack needs more items"); - } - return true; - // 2pop0push - case "=": - case ":=": - case "-=": - case "+=": - case "==": - case "=[1]": - case "=[2]": - case "=[4]": - case "=[8]": - if (this.stack.length >= 2) { - const i0 = this.stack.pop()!; - const i1 = this.stack.pop()!; - const nn = new EsilNode(expr, "operation"); - nn.setSides(i0, i1); - if (this.stack.length === 0) { - this.root.children.push(nn); - } - this.nodes.push(nn); - } else { - throw new Error("Stack needs more items"); - } - return true; - } - return false; - } + pushToken(tok: EsilToken): void | never { + if (this.isNumber(tok)) { + const node = new EsilNode(tok, "number"); + this.stack.push(node); + this.nodes.push(node); + } else if (this.isInternal(tok)) { + const node = new EsilNode(tok, "flag"); + this.stack.push(node); + this.nodes.push(node); + } else if (this.isOperation(tok)) { + // run the operation login + } else { + // assume it's a register, so just push the string + const node = new EsilNode(tok, "register"); + this.stack.push(node); + this.nodes.push(node); + } + // we need a list of register names to do this check properly + // throw new Error ("Unknown token"); + } + private isNumber(expr: EsilToken): boolean { + if (expr.toString().startsWith("0")) { + return true; + } + return +expr > 0; + } + private isInternal(expr: EsilToken): boolean { + const text = expr.toString(); + return text.startsWith("$") && text.length > 1; + } + private parseUntil(start: number): EsilNode | null { + const from = start + 1; + let pos = from; + const origStack: any[] = []; + const this_nodes_length = this.nodes.length; + this.stack.forEach((x) => origStack.push(x)); + while (pos < this.tokens.length) { + const token = this.peek(pos); + if (!token) { + break; + } + if (token.toString() === "}") { + break; + } + if (token.toString() === "}{") { + // return token; + break; + } + // console.log("peek ", this.tokens[pos]); + pos++; + } + this.stack = origStack; + const to = pos; + this.parseRange(from, to); + const same = this.nodes.length == this_nodes_length; + // console.log("BLOCK ("+ ep.toString()); + if (same) { + return null; + } + return this.nodes[this.nodes.length - 1]; // this.tokens.length - 1]; + } + private getNodeFor(index: number): null | EsilNode { + const tok = this.peek(index); + if (tok === undefined) { + return null; + } + for (const node of this.nodes) { + if (node.token.position === index) { + return node; + } + } + this.nodes.push(new EsilNode(new EsilToken("label", index), "label")); + return null; + } + private findNodeFor(index: number): null | EsilNode { + for (const node of this.nodes) { + if (node.token.position === index) { + return node; + } + } + return null; + } + private isOperation(expr: EsilToken): never | boolean { + switch (expr.toString()) { + // 1pop1push + case "[1]": + case "[2]": + case "[4]": + case "[8]": + if (this.stack.length >= 1) { + const i1 = this.stack.pop()!; + // TODO: MemoryReferenceNode(i1)); + const mn = new EsilNode(i1.token, "operation"); // expr.toString()); + this.stack.push(i1); // mn); + } else { + throw new Error("Stack needs more items"); + } + return true; + // 1pop1push + case "!": + if (this.stack.length >= 1) { + const i0 = new EsilNode( + new EsilToken("", expr.position), + "none" + ); + const i1 = this.stack.pop()!; + const nn = new EsilNode(expr, "operation"); + nn.setSides(i0, i1); + this.stack.push(nn); + } else { + throw new Error("Stack needs more items"); + } + return true; + case "": + case "}": + case "}{": + // no pops or nothing, just does nothing + return true; + case "DUP": + if (this.stack.length < 1) { + throw new Error("goto cant pop"); + } else { + const destNode = this.stack.pop()!; + this.stack.push(destNode); + this.stack.push(destNode); + } + return true; + case "GOTO": + // take previous statement which should be const and add a label + { + const prev = this.peek(expr.position - 1); + if (prev !== null) { + // TODO: check stack + if (this.stack.length < 1) { + throw new Error("goto cant pop"); + } + const destNode = this.stack.pop()!; + if (destNode !== null) { + const value: number = 0 | +destNode.toString(); + if (value > 0) { + const destToken = this.peek(value); + if (destToken !== undefined) { + destToken.label = "label_" + value; + destToken.comment = "hehe"; + const nn = new EsilNode(expr, "goto"); + const gn = this.getNodeFor( + destToken.position + ); + if (gn != null) { + nn.children.push(gn); + } + this.root.children.push(nn); + } else { + console.error("Cannot find goto node"); + } + } else { + console.error("Cannot find dest node for goto"); + } + } + } + } + return true; + // controlflow + case "?{": // ESIL_TOKEN_IF + if (this.stack.length >= 1) { + const i0 = new EsilNode( + new EsilToken("if", expr.position), + "none" + ); + const i1 = this.stack.pop()!; + const nn = new EsilNode(expr, "operation"); + nn.setSides(i0, i1); // left side can be ignored for now.. but we can express this somehow + const trueBlock = this.parseUntil(expr.position); + let falseBlock = null; + // nn.addChildren(trueBlock, falseBlock); + if (trueBlock !== null) { + nn.children.push(trueBlock); + this.nodes.push(trueBlock); + falseBlock = this.parseUntil( + trueBlock.token.position + 1 + ); + if (falseBlock !== null) { + nn.children.push(falseBlock); + this.nodes.push(falseBlock); + } + } + // console.log("true", trueBlock); + // console.log("false", falseBlock); + // this.stack.push(nn); + this.nodes.push(nn); + this.root.children.push(nn); + if (falseBlock !== null) { + this.cur = falseBlock.token.position; + } + } else { + throw new Error("Stack needs more items"); + } + return true; + case "-": + if (this.stack.length >= 2) { + const i0 = this.stack.pop()!; + const i1 = this.stack.pop()!; + const nn = new EsilNode(expr, "operation"); + nn.setSides(i0, i1); + if (this.stack.length === 0) { + // this.root.children.push(nn); + } + this.stack.push(nn); + this.nodes.push(nn); + } else { + throw new Error("Stack needs more items"); + } + return true; + // 2pop1push + case "<": + case ">": + case "^": + case "&": + case "|": + case "+": + case "*": + case "/": + case ">>=": + case "<<=": + case ">>>=": + case "<<<=": + case ">>>>=": + case "<<<<=": + if (this.stack.length >= 2) { + const i0 = this.stack.pop()!; + const i1 = this.stack.pop()!; + const nn = new EsilNode(expr, "operation"); + nn.setSides(i0, i1); + if (this.stack.length === 0) { + // this.root.children.push(nn); + } + this.stack.push(nn); + this.nodes.push(nn); + } else { + throw new Error("Stack needs more items"); + } + return true; + // 2pop0push + case "=": + case ":=": + case "-=": + case "+=": + case "==": + case "=[1]": + case "=[2]": + case "=[4]": + case "=[8]": + if (this.stack.length >= 2) { + const i0 = this.stack.pop()!; + const i1 = this.stack.pop()!; + const nn = new EsilNode(expr, "operation"); + nn.setSides(i0, i1); + if (this.stack.length === 0) { + this.root.children.push(nn); + } + this.nodes.push(nn); + } else { + throw new Error("Stack needs more items"); + } + return true; + } + return false; + } } diff --git a/typescript/async/graph.ts b/typescript/async/graph.ts index 7244111..5ffc803 100644 --- a/typescript/async/graph.ts +++ b/typescript/async/graph.ts @@ -1,52 +1,51 @@ declare let r2: any; class Graph { - constructor () { - } - async reset() { - await r2.cmd("ag-"); - } + constructor() {} + async reset() { + await r2.cmd("ag-"); + } - /** + /** * Add a node into the graph - * - * @param {string} title label of the node, this label must be unique to the graph - * @param {string} body contents of the node + * + * @param {string} title label of the node, this label must be unique to the graph + * @param {string} body contents of the node */ - async addNode(title: string, body: string) { - await r2.cmd(`agn ${title} ${body}`); - } + async addNode(title: string, body: string) { + await r2.cmd(`agn ${title} ${body}`); + } - /** + /** * Add an edge linking two nodes referenced by the title - * - * @param {string} a source title node - * @param {string} b destination title node + * + * @param {string} a source title node + * @param {string} b destination title node */ - async addEdge(a: string, b: string) { - await r2.cmd(`age ${a} ${b}`); - } + async addEdge(a: string, b: string) { + await r2.cmd(`age ${a} ${b}`); + } - /** + /** * Get an ascii art representation of the graph as a string - * + * * @returns {string} the computed graph */ - async toString() : Promise { - return r2.cmd("agg"); - } + async toString(): Promise { + return r2.cmd("agg"); + } } export async function main() { - const g = new Graph(); + const g = new Graph(); - await g.addNode("hello", "World"); - await g.addNode("world", "Hello"); - await g.addNode("patata", "Hello"); - await g.addEdge("hello", "world"); - await g.addEdge("hello", "patata"); - await g.addEdge("world", "world"); + await g.addNode("hello", "World"); + await g.addNode("world", "Hello"); + await g.addNode("patata", "Hello"); + await g.addEdge("hello", "world"); + await g.addEdge("hello", "patata"); + await g.addEdge("world", "world"); - console.log(g); + console.log(g); } await main(); diff --git a/typescript/async/index.ts b/typescript/async/index.ts index 4590783..eac7599 100644 --- a/typescript/async/index.ts +++ b/typescript/async/index.ts @@ -1,7 +1,14 @@ import { R2PipeAsync } from "./r2pipe.js"; export { R2PipeAsync } from "./r2pipe.js"; export { R2AI } from "./ai.js"; -export { R, NativePointer, R2Papi, Reference, BasicBlock, ThreadClass } from "./r2papi.js"; +export { + R, + NativePointer, + R2Papi, + Reference, + BasicBlock, + ThreadClass +} from "./r2papi.js"; export { EsilNode, EsilParser } from "./esil.js"; export { Base64, Base64Interface } from "./base64.js"; diff --git a/typescript/async/opt.ts b/typescript/async/opt.ts index 32c8678..5c61d8c 100644 --- a/typescript/async/opt.ts +++ b/typescript/async/opt.ts @@ -3,18 +3,17 @@ import { EsilParser } from "./esil"; // Type 'R2Pipe' is missing the following properties from type 'R2Pipe': cmdj, callj, plugin, unload // import { R2Pipe } from "./r2pipe"; -declare let r2 : any; +declare let r2: any; -export function plusZero() { -} +export function plusZero() {} function traverse(ep: EsilParser, child: any) { - for (const child of ep.nodes) { - if (child) { - traverse(ep, child); - } - traverse(ep, child); - } + for (const child of ep.nodes) { + if (child) { + traverse(ep, child); + } + traverse(ep, child); + } } const ep = new EsilParser(r2); diff --git a/typescript/async/pdq.ts b/typescript/async/pdq.ts index ec44495..c2b380d 100644 --- a/typescript/async/pdq.ts +++ b/typescript/async/pdq.ts @@ -8,115 +8,116 @@ declare let r2plugin: any; const ep = new EsilParser(r2); /* -*/ + */ function testLoop() { - ep.parse("rax,rbx,:=,1,GOTO"); + ep.parse("rax,rbx,:=,1,GOTO"); } function testBasic() { - ep.parse("1,rax,="); - ep.parse("1,3,+,rax,:="); - ep.parse("1,3,+,!,rax,:="); + ep.parse("1,rax,="); + ep.parse("1,3,+,rax,:="); + ep.parse("1,3,+,!,rax,:="); } function testComplex() { - ep.parse("1,rax,=,1,3,+,rax,:=,1,3,+,!,rax,:="); - ep.parse("cf,!,zf,|,?{,x16,}{,xzr,},x16,="); + ep.parse("1,rax,=,1,3,+,rax,:=,1,3,+,!,rax,:="); + ep.parse("cf,!,zf,|,?{,x16,}{,xzr,},x16,="); } function testConditional() { - ep.parse("1,?{,x0,}{,x1,},:="); - ep.parse("zf,!,nf,vf,^,!,&,?{,4294982284,pc,:=,}"); + ep.parse("1,?{,x0,}{,x1,},:="); + ep.parse("zf,!,nf,vf,^,!,&,?{,4294982284,pc,:=,}"); } -async function pdq(arg:string) { - r2.cmd("e cfg.json.num=string"); - switch (arg[0]) { - case " ": - await parseAmount (+arg); - break; - case "i": - await parseAmount (1); - break; - case "f": - case undefined: - case "": - { - const oaddr = (await r2.cmd("?v $$")).trim(); - // const func = r2.cmdj("pdrj"); // XXX this command changes the current seek - const bbs = await r2.cmdj("afbj"); // XXX this command changes the current seek - for (const bb of bbs) { - console.log("bb_" + bb.addr + ":"); - r2.cmd(`s ${bb.addr}`); - await parseAmount (bb.ninstr); - } - r2.cmd(`s ${oaddr}`); - } - break; - case "e": - ep.reset (); - ep.parse(arg.slice(1).trim(), await r2.cmd("?v $$")); - console.log(ep.toString()); - break; - case "t": - testComplex(); - testBasic(); - testConditional(); - testLoop(); - console.log(ep.toString()); - console.log("---"); - console.log(ep.toEsil()); - break; - case "?": - console.log("Usage: pdq[ef?] [ninstr] - quick decompiler plugin"); - console.log("pdq - decompile current function"); - console.log("pdq 100 - decompile 100 instructions"); - console.log("pdqe 1,rax,:= - decompile given esil expressoin"); - console.log("pdqi - decompile one instruction"); - console.log("pdqt - run tests"); - break; - } +async function pdq(arg: string) { + r2.cmd("e cfg.json.num=string"); + switch (arg[0]) { + case " ": + await parseAmount(+arg); + break; + case "i": + await parseAmount(1); + break; + case "f": + case undefined: + case "": + { + const oaddr = (await r2.cmd("?v $$")).trim(); + // const func = r2.cmdj("pdrj"); // XXX this command changes the current seek + const bbs = await r2.cmdj("afbj"); // XXX this command changes the current seek + for (const bb of bbs) { + console.log("bb_" + bb.addr + ":"); + r2.cmd(`s ${bb.addr}`); + await parseAmount(bb.ninstr); + } + r2.cmd(`s ${oaddr}`); + } + break; + case "e": + ep.reset(); + ep.parse(arg.slice(1).trim(), await r2.cmd("?v $$")); + console.log(ep.toString()); + break; + case "t": + testComplex(); + testBasic(); + testConditional(); + testLoop(); + console.log(ep.toString()); + console.log("---"); + console.log(ep.toEsil()); + break; + case "?": + console.log("Usage: pdq[ef?] [ninstr] - quick decompiler plugin"); + console.log("pdq - decompile current function"); + console.log("pdq 100 - decompile 100 instructions"); + console.log("pdqe 1,rax,:= - decompile given esil expressoin"); + console.log("pdqi - decompile one instruction"); + console.log("pdqt - run tests"); + break; + } } -async function parseAmount(n:number) : Promise { - // console.log("PDQ "+n); - const pies = await r2.cmd("pie " + n + " @e:scr.color=0"); - const lines = pies.trim().split("\n"); - for (const line of lines) { - if (line.length === 0) { - console.log("Empty"); - continue; - } - // console.log("parse", r2.cmd("?v:$$")); - const kv = line.split(' '); - if (kv.length > 1) { // line != "") { - // console.log("// @ " + kv[0]); - ep.reset (); - r2.cmd(`s ${kv[0]}`); - ep.parse(kv[1], kv[0]); - ep.optimize("flags,labels"); - console.log(ep.toString()); - } - } - // console.log(ep.toString()); +async function parseAmount(n: number): Promise { + // console.log("PDQ "+n); + const pies = await r2.cmd("pie " + n + " @e:scr.color=0"); + const lines = pies.trim().split("\n"); + for (const line of lines) { + if (line.length === 0) { + console.log("Empty"); + continue; + } + // console.log("parse", r2.cmd("?v:$$")); + const kv = line.split(" "); + if (kv.length > 1) { + // line != "") { + // console.log("// @ " + kv[0]); + ep.reset(); + r2.cmd(`s ${kv[0]}`); + ep.parse(kv[1], kv[0]); + ep.optimize("flags,labels"); + console.log(ep.toString()); + } + } + // console.log(ep.toString()); } r2.unload("core", "pdq"); -r2.plugin("core", function() { - function coreCall(cmd: string) { - if (cmd.startsWith("pdq")) { - try { - pdq(cmd.slice(3)); - } catch (e) { - console.error(e); - e.printStackTrace(); - } - return true; - } - return false; - } - return { - "name": "pdq", - "desc": "quick decompiler", - "call": coreCall, - } +r2.plugin("core", function () { + function coreCall(cmd: string) { + if (cmd.startsWith("pdq")) { + try { + pdq(cmd.slice(3)); + } catch (e) { + console.error(e); + e.printStackTrace(); + } + return true; + } + return false; + } + return { + name: "pdq", + desc: "quick decompiler", + call: coreCall + }; }); diff --git a/typescript/async/r2frida.ts b/typescript/async/r2frida.ts index 77ba696..8c04aa6 100644 --- a/typescript/async/r2frida.ts +++ b/typescript/async/r2frida.ts @@ -2,64 +2,64 @@ import { r2 } from "r2papi"; import { R2PipeAsync, newAsyncR2PipeFromSync } from "./r2pipe.js"; export class R2Frida { - isAvailable: boolean; - r2: R2PipeAsync; - constructor(r2: R2PipeAsync) { - this.r2 = r2; - this.isAvailable = false; - } - async checkAvailability() { - if (!this.isAvailable) { - const output = await r2.cmd("o~frida://"); - if (output.trim() === "") { - throw new Error("There's no frida session available"); - } - } - this.isAvailable = true; - } + isAvailable: boolean; + r2: R2PipeAsync; + constructor(r2: R2PipeAsync) { + this.r2 = r2; + this.isAvailable = false; + } + async checkAvailability() { + if (!this.isAvailable) { + const output = await r2.cmd("o~frida://"); + if (output.trim() === "") { + throw new Error("There's no frida session available"); + } + } + this.isAvailable = true; + } - async eval(expr : string) : Promise { - return r2.cmd(`"": ${expr}`); - } - async targetDetails() : Promise { - return r2.cmdj(":ij") as TargetDetails; - } + async eval(expr: string): Promise { + return r2.cmd(`"": ${expr}`); + } + async targetDetails(): Promise { + return r2.cmdj(":ij") as TargetDetails; + } } export async function main() { - console.log("Hello from r2papi-r2frida"); - const r2async = newAsyncR2PipeFromSync(r2); - const r2f = new R2Frida(r2async); - r2f.eval("console.log(123);"); - const { pid, arch, cwd } = await r2f.targetDetails(); - console.log(pid, arch, cwd); + console.log("Hello from r2papi-r2frida"); + const r2async = newAsyncR2PipeFromSync(r2); + const r2f = new R2Frida(r2async); + r2f.eval("console.log(123);"); + const { pid, arch, cwd } = await r2f.targetDetails(); + console.log(pid, arch, cwd); } main(); export interface TargetDetails { - arch: string; - bits: number; - os:string; - pid: number; - uid: number; - runtime: string; - objc:boolean; - swift:boolean; - mainLoop:boolean; - pageSize: number; - pointerSize: number; - codeSigningPolicy: string; - isDebuggerAttached: boolean; - cwd: string; - bundle: string; - exename: string; - appname: string; - appversion: string; - appnumversion: string; - minOS: string; - modulename: string; - modulebase: string; - homedir: string; - tmpdir: string; - bundledir?:any; + arch: string; + bits: number; + os: string; + pid: number; + uid: number; + runtime: string; + objc: boolean; + swift: boolean; + mainLoop: boolean; + pageSize: number; + pointerSize: number; + codeSigningPolicy: string; + isDebuggerAttached: boolean; + cwd: string; + bundle: string; + exename: string; + appname: string; + appversion: string; + appnumversion: string; + minOS: string; + modulename: string; + modulebase: string; + homedir: string; + tmpdir: string; + bundledir?: any; } diff --git a/typescript/async/r2papi.ts b/typescript/async/r2papi.ts index c6a8fa8..77a6373 100644 --- a/typescript/async/r2papi.ts +++ b/typescript/async/r2papi.ts @@ -3,794 +3,824 @@ import { R2Shell } from "./shell.js"; import { newAsyncR2PipeFromSync, r2, R2PipeAsync } from "./r2pipe.js"; -export type InstructionType = "mov" | "jmp" | "cmp" | "nop" | "call" | "add" | "sub"; +export type InstructionType = + | "mov" + | "jmp" + | "cmp" + | "nop" + | "call" + | "add" + | "sub"; export type InstructionFamily = "cpu" | "fpu" | "priv"; export type GraphFormat = "dot" | "json" | "mermaid" | "ascii"; export type Permission = "---" | "r--" | "rw-" | "rwx" | "r-x" | "-wx" | "--x"; export type R2Papi = R2PapiAsync; export interface SearchResult { - offset: number; // TODO: rename to addr - type: string; - data: string; + offset: number; // TODO: rename to addr + type: string; + data: string; } export interface DebugModule { - base: string; - name: string; - path: string; - size: number; + base: string; + name: string; + path: string; + size: number; } export interface Flag { - name: string; - size: number; - offset: number; + name: string; + size: number; + offset: number; } -export type PluginFamily = "core" | "io" | "arch" | "esil" | "lang" | "bin" | "debug" | "anal" | "crypto"; +export type PluginFamily = + | "core" + | "io" + | "arch" + | "esil" + | "lang" + | "bin" + | "debug" + | "anal" + | "crypto"; // XXX not working? export type ThreadState = "waiting" | "running" | "dead" ; export interface ThreadContext { - context: any; - id: number; - state: string; - selected: boolean; + context: any; + id: number; + state: string; + selected: boolean; } export interface CallRef { - addr: number; - type: string; - at: number; + addr: number; + type: string; + at: number; } export interface FunctionDetails { - offset: number; - name: string; - size: number; - noreturn: boolean; - stackframe: number; - ebbs: number; - signature: string; - nbbs: number; - callrefs: CallRef[]; - codexrefs: CallRef[]; + offset: number; + name: string; + size: number; + noreturn: boolean; + stackframe: number; + ebbs: number; + signature: string; + nbbs: number; + callrefs: CallRef[]; + codexrefs: CallRef[]; } export interface BinFile { - arch: string; - static: boolean; - va: boolean; - stripped: boolean; - pic: boolean; - relocs: boolean; - sanitize: boolean; - baddr: number; - binsz: number; - bintype: string; - bits: number; - canary: boolean; - class: string; - compiler: string; - endian: string; - machine: string; - nx: boolean; - os: string; - laddr: number; - linenum: boolean; - havecode: boolean; - intrp: string; + arch: string; + static: boolean; + va: boolean; + stripped: boolean; + pic: boolean; + relocs: boolean; + sanitize: boolean; + baddr: number; + binsz: number; + bintype: string; + bits: number; + canary: boolean; + class: string; + compiler: string; + endian: string; + machine: string; + nx: boolean; + os: string; + laddr: number; + linenum: boolean; + havecode: boolean; + intrp: string; } export interface Reference { - from: number; - type: string; - perm: string; - opcode: string; - fcn_addr: number; - fcn_name: string; - realname: string; - refname: string; + from: number; + type: string; + perm: string; + opcode: string; + fcn_addr: number; + fcn_name: string; + realname: string; + refname: string; } export interface BasicBlock { - addr: number, - size: number, - jump: number, - fail: number, - opaddr: number, - inputs: number, - outputs: number, - ninstr: number, - instrs: number[], - traced: boolean + addr: number; + size: number; + jump: number; + fail: number; + opaddr: number; + inputs: number; + outputs: number; + ninstr: number; + instrs: number[]; + traced: boolean; } export class ThreadClass { - api: any = null; - constructor(r2: any) { - this.api = r2; - } - backtrace() { - return r2.call("dbtj"); - } - sleep(seconds: number) { - return r2.call("sleep " + seconds); - } + api: any = null; + constructor(r2: any) { + this.api = r2; + } + backtrace() { + return r2.call("dbtj"); + } + sleep(seconds: number) { + return r2.call("sleep " + seconds); + } } export interface Instruction { - type: InstructionType; - addr: number; - opcode: string; - pseudo: string; - mnemonic: string; - sign: boolean; - family: InstructionFamily; - description: string; - esil: string; - opex: any; - size: number; - ptr: number; - bytes: string; - id: number; - refptr: number; - direction: "read" | "write"; - stackptr: number; - stack: string; // "inc"|"dec"|"get"|"set"|"nop"|"null"; + type: InstructionType; + addr: number; + opcode: string; + pseudo: string; + mnemonic: string; + sign: boolean; + family: InstructionFamily; + description: string; + esil: string; + opex: any; + size: number; + ptr: number; + bytes: string; + id: number; + refptr: number; + direction: "read" | "write"; + stackptr: number; + stack: string; // "inc"|"dec"|"get"|"set"|"nop"|"null"; } export class ModuleClass { - api: any = null; - constructor(r2: R2PipeAsync) { - this.api = r2; - } - fileName(): string { - return this.api.call("dpe").trim() - } - name(): string { - return "Module"; - } - findBaseAddress() { - return "TODO"; - } - findExportByName(name: string): any { - // TODO - return "TODO"; - } - getBaseAddress(name: string) { - return "TODO"; - } - getExportByName(name: string) { - return r2.call("iE,name/eq/" + name + ",vaddr/cols,:quiet").trim(); - } - enumerateExports() { - // TODO: adjust to be the same output as Frida - return r2.callj("iEj"); - } - enumerateImports() { - // TODO: adjust to be the same output as Frida - return r2.callj("iij"); - } - enumerateRanges() { - // TODO: adjust to be the same output as Frida - return r2.callj("isj"); - } - enumerateSymbols() { - // TODO: adjust to be the same output as Frida - return r2.callj("isj"); - } + api: any = null; + constructor(r2: R2PipeAsync) { + this.api = r2; + } + fileName(): string { + return this.api.call("dpe").trim(); + } + name(): string { + return "Module"; + } + findBaseAddress() { + return "TODO"; + } + findExportByName(name: string): any { + // TODO + return "TODO"; + } + getBaseAddress(name: string) { + return "TODO"; + } + getExportByName(name: string) { + return r2.call("iE,name/eq/" + name + ",vaddr/cols,:quiet").trim(); + } + enumerateExports() { + // TODO: adjust to be the same output as Frida + return r2.callj("iEj"); + } + enumerateImports() { + // TODO: adjust to be the same output as Frida + return r2.callj("iij"); + } + enumerateRanges() { + // TODO: adjust to be the same output as Frida + return r2.callj("isj"); + } + enumerateSymbols() { + // TODO: adjust to be the same output as Frida + return r2.callj("isj"); + } } export class ProcessClass { - r2: any = null; - constructor(r2: R2PipeAsync) { - this.r2 = r2; - } - enumerateMallocRanges() { - } - enumerateSystemRanges() { - } - enumerateRanges() { - } - enumerateThreads() { - return r2.callj("dptj"); - } - enumerateModules(): any { - r2.call("cfg.json.num=string"); // to handle 64bit values properly - if (r2.callj("e cfg.debug")) { - const modules = r2.callj("dmmj"); - const res = []; - for (const mod of modules) { - const entry = { - base: new NativePointer(mod.addr), - size: new NativePointer(mod.addr_end).sub(mod.addr), - path: mod.file, - name: mod.name, - }; - res.push(entry); - } - return res; - } else { - const fname = (x: string) => { - const y = x.split("/"); - return y[y.length - 1]; - } - const bobjs = r2.callj("obj"); - const res = []; - for (const obj of bobjs) { - const entry = { - base: new NativePointer(obj.addr), - size: obj.size, - path: obj.file, - name: fname(obj.file) - }; - res.push(entry); - } - const libs = r2.callj("ilj"); - for (const lib of libs) { - const entry = { - base: 0, - size: 0, - path: lib, - name: fname(lib) - }; - res.push(entry); - } - return res; - } - } - getModuleByAddress(addr: NativePointer | number | string): any { - } - getModuleByName(moduleName: string): any { - } - codeSigningPolicy(): string { - return "optional"; - } - getTmpDir() { - return this.r2.call("e dir.tmp").trim(); - } - getHomeDir() { - return this.r2.call("e dir.home").trim(); - } - platform() { - return this.r2.call("e asm.os").trim(); - } - getCurrentDir() { - return this.r2.call("pwd").trim(); - } - getCurrentThreadId(): number { - return +this.r2.call("dpq"); - } - pageSize(): number { - if (this.r2.callj("e asm.bits") === 64 && this.r2.call("e asm.arch").startsWith("arm")) { - return 16384; - } - return 4096; - } - isDebuggerAttached(): boolean { - return this.r2.callj("e cfg.debug"); - } - setExceptionHandler() { - // do nothing - } - id() { - // - return this.r2.callj("dpq").trim(); - } - pointerSize() { - return r2.callj("e asm.bits") / 8; - } + r2: any = null; + constructor(r2: R2PipeAsync) { + this.r2 = r2; + } + enumerateMallocRanges() {} + enumerateSystemRanges() {} + enumerateRanges() {} + enumerateThreads() { + return r2.callj("dptj"); + } + enumerateModules(): any { + r2.call("cfg.json.num=string"); // to handle 64bit values properly + if (r2.callj("e cfg.debug")) { + const modules = r2.callj("dmmj"); + const res = []; + for (const mod of modules) { + const entry = { + base: new NativePointer(mod.addr), + size: new NativePointer(mod.addr_end).sub(mod.addr), + path: mod.file, + name: mod.name + }; + res.push(entry); + } + return res; + } else { + const fname = (x: string) => { + const y = x.split("/"); + return y[y.length - 1]; + }; + const bobjs = r2.callj("obj"); + const res = []; + for (const obj of bobjs) { + const entry = { + base: new NativePointer(obj.addr), + size: obj.size, + path: obj.file, + name: fname(obj.file) + }; + res.push(entry); + } + const libs = r2.callj("ilj"); + for (const lib of libs) { + const entry = { + base: 0, + size: 0, + path: lib, + name: fname(lib) + }; + res.push(entry); + } + return res; + } + } + getModuleByAddress(addr: NativePointer | number | string): any {} + getModuleByName(moduleName: string): any {} + codeSigningPolicy(): string { + return "optional"; + } + getTmpDir() { + return this.r2.call("e dir.tmp").trim(); + } + getHomeDir() { + return this.r2.call("e dir.home").trim(); + } + platform() { + return this.r2.call("e asm.os").trim(); + } + getCurrentDir() { + return this.r2.call("pwd").trim(); + } + getCurrentThreadId(): number { + return +this.r2.call("dpq"); + } + pageSize(): number { + if ( + this.r2.callj("e asm.bits") === 64 && + this.r2.call("e asm.arch").startsWith("arm") + ) { + return 16384; + } + return 4096; + } + isDebuggerAttached(): boolean { + return this.r2.callj("e cfg.debug"); + } + setExceptionHandler() { + // do nothing + } + id() { + // + return this.r2.callj("dpq").trim(); + } + pointerSize() { + return r2.callj("e asm.bits") / 8; + } } /** * Assembler and disassembler facilities to decode and encode instructions - * + * * @typedef Assembler */ export class Assembler { - program: string = ""; - labels: any = {}; - endian: boolean = false; - pc: NativePointer = ptr(0); - r2: R2PipeAsync; - constructor(myr2?: R2PipeAsync) { - if (myr2 === undefined) { - this.r2 = newAsyncR2PipeFromSync(r2); - } else { - this.r2 = myr2; - } - this.program = ''; - this.labels = {}; - } - /** - * Change the address of the program counter, some instructions need to know where - * are they located before being encoded or decoded. - * - * @param {NativePointerValue} - */ - setProgramCounter(pc: NativePointer) { - this.pc = pc; - } - setEndian(big: boolean) { - this.endian = big; - } - toString() { - return this.program; - } - async append(x: string) { - // append text - this.pc = await this.pc.add(x.length / 2); - this.program += x; - } - // api - label(s: string): NativePointer { - const pos = this.pc; // this.#program.length / 4; - this.labels[s] = this.pc; - return pos; - } + program: string = ""; + labels: any = {}; + endian: boolean = false; + pc: NativePointer = ptr(0); + r2: R2PipeAsync; + constructor(myr2?: R2PipeAsync) { + if (myr2 === undefined) { + this.r2 = newAsyncR2PipeFromSync(r2); + } else { + this.r2 = myr2; + } + this.program = ""; + this.labels = {}; + } + /** + * Change the address of the program counter, some instructions need to know where + * are they located before being encoded or decoded. + * + * @param {NativePointerValue} + */ + setProgramCounter(pc: NativePointer) { + this.pc = pc; + } + setEndian(big: boolean) { + this.endian = big; + } + toString() { + return this.program; + } + async append(x: string) { + // append text + this.pc = await this.pc.add(x.length / 2); + this.program += x; + } + // api + label(s: string): NativePointer { + const pos = this.pc; // this.#program.length / 4; + this.labels[s] = this.pc; + return pos; + } - /** - * Encode (assemble) an instruction by taking the string representation. - * - * @param {string} the string representation of the instruction to assemble - * @returns {string} the hexpairs that represent the assembled instruciton - */ - async encode(s: string): Promise { - const output = await this.r2.call(`pa ${s}`); - return output.trim(); - } + /** + * Encode (assemble) an instruction by taking the string representation. + * + * @param {string} the string representation of the instruction to assemble + * @returns {string} the hexpairs that represent the assembled instruciton + */ + async encode(s: string): Promise { + const output = await this.r2.call(`pa ${s}`); + return output.trim(); + } - /** - * Decode (disassemble) an instruction by taking the hexpairs string as input. - * TODO: should take an array of bytes too - * - * @param {string} the hexadecimal pairs of bytes to decode as an instruction - * @returns {string} the mnemonic and operands of the resulting decoding - */ - async decode(s: string): Promise { - const output = await this.r2.call(`pad ${s}`); - return output.trim(); - } + /** + * Decode (disassemble) an instruction by taking the hexpairs string as input. + * TODO: should take an array of bytes too + * + * @param {string} the hexadecimal pairs of bytes to decode as an instruction + * @returns {string} the mnemonic and operands of the resulting decoding + */ + async decode(s: string): Promise { + const output = await this.r2.call(`pad ${s}`); + return output.trim(); + } } /** * High level abstraction on top of the r2 command interface provided by r2pipe. - * + * * @typedef R2Papi */ export class R2PapiAsync { - /** - * Keep a reference r2pipe instance - * - * @type {R2PipeAsync} - */ - public r2: R2PipeAsync; + /** + * Keep a reference r2pipe instance + * + * @type {R2PipeAsync} + */ + public r2: R2PipeAsync; - /** - * Create a new instance of the R2Papi class, taking an r2pipe interface as reference. - * - * @param {R2PipeAsync} the r2pipe instance to use as backend. - * @returns {R2Papi} instance - */ - constructor(r2: R2PipeAsync) { - this.r2 = r2; - } - toString() { - return "[object R2Papi]"; - } - toJSON() { - return this.toString(); - } - /** - * Get the base address used by the current loaded binary - * - * @returns {NativePointer} address of the base of the binary - */ - async getBaseAddress(): Promise { - return new NativePointer(await this.cmd("e bin.baddr")); - } - jsonToTypescript(name: string, a: any): string { - let str = `interface ${name} {\n`; - if (a.length && a.length > 0) { - a = a[0]; - } - for (const k of Object.keys(a)) { - const typ = typeof (a[k]); - const nam = k; - str += ` ${nam}: ${typ};\n`; - } - return `${str}}\n`; - } - /** - * Get the general purpose register size of the targize architecture in bits - * - * @returns {number} the regsize - */ - getBits(): number { - return +this.cmd('-b'); - } - /** - * Get the name of the arch plugin selected, which tends to be the same target architecture. - * Note that on some situations, this info will be stored protected bby the AirForce. - * When using the r2ghidra arch plugin the underlying arch is in `asm.cpu`: - * - * @returns {string} the name of the target architecture. - */ - async getArch(): Promise { - return await this.cmd('-a'); - } - /** - * Get the name of the selected CPU for the current selected architecture. - * - * @returns {string} the value of asm.cpu - */ - async getCpu(): Promise { - // return this.cmd('-c'); - return this.cmd('-e asm.cpu'); // use arch.cpu - } - // TODO: setEndian, setCpu, ... - async setArch(arch: string, bits: number | undefined) { - await this.cmd("-a " + arch); - if (bits !== undefined) { - this.cmd("-b " + bits); - } - } - async setFlagSpace(name: string) { - await this.cmd('fs ' + name); - } - async setLogLevel(level: number) { - await this.cmd('e log.level=' + level); - } - /** - * should return the id for the new map using the given file descriptor - */ - // rename to createMap or mapFile? - newMap(fd: number, vaddr: NativePointer, size: number, paddr: NativePointer, perm: Permission, name: string = ""): void { - this.cmd(`om ${fd} ${vaddr} ${size} ${paddr} ${perm} ${name}`); - } + /** + * Create a new instance of the R2Papi class, taking an r2pipe interface as reference. + * + * @param {R2PipeAsync} the r2pipe instance to use as backend. + * @returns {R2Papi} instance + */ + constructor(r2: R2PipeAsync) { + this.r2 = r2; + } + toString() { + return "[object R2Papi]"; + } + toJSON() { + return this.toString(); + } + /** + * Get the base address used by the current loaded binary + * + * @returns {NativePointer} address of the base of the binary + */ + async getBaseAddress(): Promise { + return new NativePointer(await this.cmd("e bin.baddr")); + } + jsonToTypescript(name: string, a: any): string { + let str = `interface ${name} {\n`; + if (a.length && a.length > 0) { + a = a[0]; + } + for (const k of Object.keys(a)) { + const typ = typeof a[k]; + const nam = k; + str += ` ${nam}: ${typ};\n`; + } + return `${str}}\n`; + } + /** + * Get the general purpose register size of the targize architecture in bits + * + * @returns {number} the regsize + */ + getBits(): number { + return +this.cmd("-b"); + } + /** + * Get the name of the arch plugin selected, which tends to be the same target architecture. + * Note that on some situations, this info will be stored protected bby the AirForce. + * When using the r2ghidra arch plugin the underlying arch is in `asm.cpu`: + * + * @returns {string} the name of the target architecture. + */ + async getArch(): Promise { + return await this.cmd("-a"); + } + /** + * Get the name of the selected CPU for the current selected architecture. + * + * @returns {string} the value of asm.cpu + */ + async getCpu(): Promise { + // return this.cmd('-c'); + return this.cmd("-e asm.cpu"); // use arch.cpu + } + // TODO: setEndian, setCpu, ... + async setArch(arch: string, bits: number | undefined) { + await this.cmd("-a " + arch); + if (bits !== undefined) { + this.cmd("-b " + bits); + } + } + async setFlagSpace(name: string) { + await this.cmd("fs " + name); + } + async setLogLevel(level: number) { + await this.cmd("e log.level=" + level); + } + /** + * should return the id for the new map using the given file descriptor + */ + // rename to createMap or mapFile? + newMap( + fd: number, + vaddr: NativePointer, + size: number, + paddr: NativePointer, + perm: Permission, + name: string = "" + ): void { + this.cmd(`om ${fd} ${vaddr} ${size} ${paddr} ${perm} ${name}`); + } - at(a: string): NativePointer { - return new NativePointer(a); - } - getShell(): R2Shell { - return new R2Shell(this); - } - // Radare/Frida - async version(): Promise { - const v = await this.r2.cmd("?Vq"); - return v.trim(); - } - // Process - async platform(): Promise { - const output = await this.r2.cmd("uname"); - return output.trim(); - } - async arch(): Promise { - const output = await this.r2.cmd("uname -a"); - return output.trim(); - } - async bits(): Promise { - const output = await this.r2.cmd("uname -b"); - return output.trim(); - } - id(): number { - // getpid(); - return +this.r2.cmd("?vi:$p"); - } - // Other stuff - printAt(msg: string, x: number, y: number): void { - // see pg, but pg is obrken :D - } - clearScreen(): R2Papi { - this.r2.cmd("!clear"); - return this; - } - async getConfig(key: string): Promise { - if (key === '') { - return new Error('Empty key'); - } - const exist = await this.r2.cmd(`e~^${key} =`); - if (exist.trim() === "") { - return new Error('Config key does not exist'); - } - const value = await this.r2.call("e " + key); - return value.trim(); - } - async setConfig(key: string, val: string): Promise { - await this.r2.call("e " + key + "=" + val); - return this; - } - async getRegisterStateForEsil(): Promise { - const dre = await this.cmdj("dre"); - return this.cmdj("dre"); - } - async getRegisters(): Promise { - // this.r2.log("winrar" + JSON.stringify(JSON.parse(this.r2.cmd("drj")),null, 2) ); - return this.cmdj("drj"); - } - resizeFile(newSize: number): R2Papi { - this.cmd(`r ${newSize}`); - return this; - } - insertNullBytes(newSize: number, at?: NativePointer | number | string): R2Papi { - if (at === undefined) { - at = "$$"; - } - this.cmd(`r+${newSize}@${at}`); - return this; - } - removeBytes(newSize: number, at?: NativePointer | number | string): R2Papi { - if (at === undefined) { - at = "$$"; - } - this.cmd(`r-${newSize}@${at}`); - return this; - } - seek(addr: number): R2Papi { - this.cmd(`s ${addr}`); - return this; - } - currentSeek(): NativePointer { - return new NativePointer('$$'); - } - seekToRelativeOpcode(nth: number): NativePointer { - this.cmd(`so ${nth}`); - return this.currentSeek(); - } - getBlockSize(): number { - return +this.cmd("b"); - } - setBlockSize(a: number): R2Papi { - this.cmd(`b ${a}`); - return this; - } - countFlags(): number { - return Number(this.cmd("f~?")); - } - countFunctions(): number { - return Number(this.cmd("aflc")); - } - analyzeFunctionsWithEsil(depth?: number) { - this.cmd("aaef"); - } - analyzeProgramWithEsil(depth?: number) { - this.cmd("aae"); - } - analyzeProgram(depth?: number) { - if (depth === undefined) { - depth = 0; - } - switch (depth) { - case 0: this.cmd("aa"); break; - case 1: this.cmd("aaa"); break; - case 2: this.cmd("aaaa"); break; - case 3: this.cmd("aaaaa"); break; - } - return this; - } - enumerateThreads(): ThreadContext[] { - // TODO: use apt/dpt to list threads at iterate over them to get the registers - const regs0 = this.cmdj("drj"); - const thread0 = { - context: regs0, - id: 0, - state: "waiting", - selected: true, - }; - return [thread0]; - } - currentThreadId(): number { - if (+this.cmd("e cfg.debug")) { - return +this.cmd("dpt."); - } - return this.id(); - } - setRegisters(obj: any) { - for (const r of Object.keys(obj)) { - const v = obj[r]; - this.r2.cmd("dr " + r + "=" + v); - } - } - async hex(s: number | string): Promise { - const output = await this.r2.cmd("?v " + s); - return output.trim(); - } - async step(): Promise { - await this.r2.cmd("ds"); - return this; - } - stepOver(): R2Papi { - this.r2.cmd("dso"); - return this; - } - math(expr: number | string): number { - return +this.r2.cmd("?v " + expr); - } - stepUntil(dst: NativePointer | string | number): void { - this.cmd(`dsu ${dst}`); - } - async enumerateXrefsTo(s: string): Promise { - const output = await this.call("axtq " + s); - return output.trim().split(/\n/); - } - // TODO: rename to searchXrefsTo ? - findXrefsTo(s: string, use_esil: boolean) { - if (use_esil) { - this.call("/r " + s); - } else { - this.call("/re " + s); - } - } - analyzeFunctionsFromCalls(): R2Papi { - this.call("aac") - return this; - } - autonameAllFunctions(): R2Papi { - this.call("aan") - return this; - } - analyzeFunctionsWithPreludes(): R2Papi { - this.call("aap") - return this; - } - analyzeObjCReferences(): R2Papi { - this.cmd("aao"); - return this; - } - async analyzeImports(): Promise { - await this.cmd("af @ sym.imp.*"); - return this; - } - async searchDisasm(s: string): Promise { - const res: SearchResult[] = await this.callj("/ad " + s); - return res; - } - async searchString(s: string): Promise { - const res: SearchResult[] = await this.cmdj("/j " + s); - return res; - } - async searchBytes(data: number[]): Promise { - function num2hex(data: number): string { - return (data & 0xff).toString(16); - } - const s = data.map(num2hex).join(''); - const res: SearchResult[] = await this.cmdj("/xj " + s); - return res; - } - async binInfo(): Promise { - try { - return this.cmdj("ij~{bin}"); - } catch (e: any) { - return {} as BinFile; - } - } - // TODO: take a BinFile as argument instead of number - selectBinary(id: number): void { - this.call(`ob ${id}`); - } - async openFile(name: string): Promise { - const ofd = await this.call('oqq'); - await this.call(`o ${name}`); - const nfd = await this.call('oqq'); - if (ofd.trim() === nfd.trim()) { - return new Error('Cannot open file'); - } - return parseInt(nfd); - } - async openFileNomap(name: string): Promise { - const ofd = await this.call('oqq'); - this.call(`of ${name}`); - const nfd = await this.call('oqq'); - if (ofd.trim() === nfd.trim()) { - return new Error('Cannot open file'); - } - return parseInt(nfd); - } - async currentFile(name: string): Promise { - return (await this.call("o.")).trim(); - } - enumeratePlugins(type: PluginFamily): any { - switch (type) { - case "bin": - return this.callj("Lij"); - case "io": - return this.callj("Loj"); - case "core": - return this.callj("Lcj"); - case "arch": - return this.callj("LAj"); - case "anal": - return this.callj("Laj"); - case "lang": - return this.callj("Llj"); - } - return [] - } - async enumerateModules(): Promise { - return this.callj("dmmj"); - } - async enumerateFiles(): Promise { - return this.callj("oj"); - } - async enumerateBinaries(): Promise { - return this.callj("obj"); - } - async enumerateMaps(): Promise { - return this.callj("omj"); - } - async enumerateClasses(): Promise { - return this.callj("icj"); - } - async enumerateSymbols(): Promise { - return this.callj("isj"); - } - async enumerateExports(): Promise { - return this.callj("iEj"); - } - async enumerateImports(): Promise { - return this.callj("iij"); - } - async enumerateLibraries(): Promise { - return this.callj("ilj"); - } - async enumerateSections(): Promise { - return this.callj("iSj"); - } - async enumerateSegments(): Promise { - return this.callj("iSSj"); - } - async enumerateEntrypoints(): Promise { - return this.callj("iej"); - } - async enumerateRelocations(): Promise { - return this.callj("irj"); - } - async enumerateFunctions(): Promise { - return this.cmdj("aflj"); - } - async enumerateFlags(): Promise { - return this.cmdj("fj"); - } - skip() { - this.r2.cmd("dss"); - } - ptr(s: string | number): NativePointer { - return new NativePointer(s, this); - } - async call(s: string): Promise { - return this.r2.call(s); - } - async callj(s: string): Promise { - return JSON.parse(await this.call(s)); - } - async cmd(s: string): Promise { - return this.r2.cmd(s); - } - async cmdj(s: string): Promise { - return JSON.parse(await this.cmd(s)); - } - async log(s: string) { - return this.r2.log(s); - } - async clippy(msg: string): Promise { - this.r2.log(await this.r2.cmd("?E " + msg)); - } - async ascii(msg: string): Promise { - this.r2.log(await this.r2.cmd("?ea " + msg)); - } + at(a: string): NativePointer { + return new NativePointer(a); + } + getShell(): R2Shell { + return new R2Shell(this); + } + // Radare/Frida + async version(): Promise { + const v = await this.r2.cmd("?Vq"); + return v.trim(); + } + // Process + async platform(): Promise { + const output = await this.r2.cmd("uname"); + return output.trim(); + } + async arch(): Promise { + const output = await this.r2.cmd("uname -a"); + return output.trim(); + } + async bits(): Promise { + const output = await this.r2.cmd("uname -b"); + return output.trim(); + } + id(): number { + // getpid(); + return +this.r2.cmd("?vi:$p"); + } + // Other stuff + printAt(msg: string, x: number, y: number): void { + // see pg, but pg is obrken :D + } + clearScreen(): R2Papi { + this.r2.cmd("!clear"); + return this; + } + async getConfig(key: string): Promise { + if (key === "") { + return new Error("Empty key"); + } + const exist = await this.r2.cmd(`e~^${key} =`); + if (exist.trim() === "") { + return new Error("Config key does not exist"); + } + const value = await this.r2.call("e " + key); + return value.trim(); + } + async setConfig(key: string, val: string): Promise { + await this.r2.call("e " + key + "=" + val); + return this; + } + async getRegisterStateForEsil(): Promise { + const dre = await this.cmdj("dre"); + return this.cmdj("dre"); + } + async getRegisters(): Promise { + // this.r2.log("winrar" + JSON.stringify(JSON.parse(this.r2.cmd("drj")),null, 2) ); + return this.cmdj("drj"); + } + resizeFile(newSize: number): R2Papi { + this.cmd(`r ${newSize}`); + return this; + } + insertNullBytes( + newSize: number, + at?: NativePointer | number | string + ): R2Papi { + if (at === undefined) { + at = "$$"; + } + this.cmd(`r+${newSize}@${at}`); + return this; + } + removeBytes(newSize: number, at?: NativePointer | number | string): R2Papi { + if (at === undefined) { + at = "$$"; + } + this.cmd(`r-${newSize}@${at}`); + return this; + } + seek(addr: number): R2Papi { + this.cmd(`s ${addr}`); + return this; + } + currentSeek(): NativePointer { + return new NativePointer("$$"); + } + seekToRelativeOpcode(nth: number): NativePointer { + this.cmd(`so ${nth}`); + return this.currentSeek(); + } + getBlockSize(): number { + return +this.cmd("b"); + } + setBlockSize(a: number): R2Papi { + this.cmd(`b ${a}`); + return this; + } + countFlags(): number { + return Number(this.cmd("f~?")); + } + countFunctions(): number { + return Number(this.cmd("aflc")); + } + analyzeFunctionsWithEsil(depth?: number) { + this.cmd("aaef"); + } + analyzeProgramWithEsil(depth?: number) { + this.cmd("aae"); + } + analyzeProgram(depth?: number) { + if (depth === undefined) { + depth = 0; + } + switch (depth) { + case 0: + this.cmd("aa"); + break; + case 1: + this.cmd("aaa"); + break; + case 2: + this.cmd("aaaa"); + break; + case 3: + this.cmd("aaaaa"); + break; + } + return this; + } + enumerateThreads(): ThreadContext[] { + // TODO: use apt/dpt to list threads at iterate over them to get the registers + const regs0 = this.cmdj("drj"); + const thread0 = { + context: regs0, + id: 0, + state: "waiting", + selected: true + }; + return [thread0]; + } + currentThreadId(): number { + if (+this.cmd("e cfg.debug")) { + return +this.cmd("dpt."); + } + return this.id(); + } + setRegisters(obj: any) { + for (const r of Object.keys(obj)) { + const v = obj[r]; + this.r2.cmd("dr " + r + "=" + v); + } + } + async hex(s: number | string): Promise { + const output = await this.r2.cmd("?v " + s); + return output.trim(); + } + async step(): Promise { + await this.r2.cmd("ds"); + return this; + } + stepOver(): R2Papi { + this.r2.cmd("dso"); + return this; + } + math(expr: number | string): number { + return +this.r2.cmd("?v " + expr); + } + stepUntil(dst: NativePointer | string | number): void { + this.cmd(`dsu ${dst}`); + } + async enumerateXrefsTo(s: string): Promise { + const output = await this.call("axtq " + s); + return output.trim().split(/\n/); + } + // TODO: rename to searchXrefsTo ? + findXrefsTo(s: string, use_esil: boolean) { + if (use_esil) { + this.call("/r " + s); + } else { + this.call("/re " + s); + } + } + analyzeFunctionsFromCalls(): R2Papi { + this.call("aac"); + return this; + } + autonameAllFunctions(): R2Papi { + this.call("aan"); + return this; + } + analyzeFunctionsWithPreludes(): R2Papi { + this.call("aap"); + return this; + } + analyzeObjCReferences(): R2Papi { + this.cmd("aao"); + return this; + } + async analyzeImports(): Promise { + await this.cmd("af @ sym.imp.*"); + return this; + } + async searchDisasm(s: string): Promise { + const res: SearchResult[] = await this.callj("/ad " + s); + return res; + } + async searchString(s: string): Promise { + const res: SearchResult[] = await this.cmdj("/j " + s); + return res; + } + async searchBytes(data: number[]): Promise { + function num2hex(data: number): string { + return (data & 0xff).toString(16); + } + const s = data.map(num2hex).join(""); + const res: SearchResult[] = await this.cmdj("/xj " + s); + return res; + } + async binInfo(): Promise { + try { + return this.cmdj("ij~{bin}"); + } catch (e: any) { + return {} as BinFile; + } + } + // TODO: take a BinFile as argument instead of number + selectBinary(id: number): void { + this.call(`ob ${id}`); + } + async openFile(name: string): Promise { + const ofd = await this.call("oqq"); + await this.call(`o ${name}`); + const nfd = await this.call("oqq"); + if (ofd.trim() === nfd.trim()) { + return new Error("Cannot open file"); + } + return parseInt(nfd); + } + async openFileNomap(name: string): Promise { + const ofd = await this.call("oqq"); + this.call(`of ${name}`); + const nfd = await this.call("oqq"); + if (ofd.trim() === nfd.trim()) { + return new Error("Cannot open file"); + } + return parseInt(nfd); + } + async currentFile(name: string): Promise { + return (await this.call("o.")).trim(); + } + enumeratePlugins(type: PluginFamily): any { + switch (type) { + case "bin": + return this.callj("Lij"); + case "io": + return this.callj("Loj"); + case "core": + return this.callj("Lcj"); + case "arch": + return this.callj("LAj"); + case "anal": + return this.callj("Laj"); + case "lang": + return this.callj("Llj"); + } + return []; + } + async enumerateModules(): Promise { + return this.callj("dmmj"); + } + async enumerateFiles(): Promise { + return this.callj("oj"); + } + async enumerateBinaries(): Promise { + return this.callj("obj"); + } + async enumerateMaps(): Promise { + return this.callj("omj"); + } + async enumerateClasses(): Promise { + return this.callj("icj"); + } + async enumerateSymbols(): Promise { + return this.callj("isj"); + } + async enumerateExports(): Promise { + return this.callj("iEj"); + } + async enumerateImports(): Promise { + return this.callj("iij"); + } + async enumerateLibraries(): Promise { + return this.callj("ilj"); + } + async enumerateSections(): Promise { + return this.callj("iSj"); + } + async enumerateSegments(): Promise { + return this.callj("iSSj"); + } + async enumerateEntrypoints(): Promise { + return this.callj("iej"); + } + async enumerateRelocations(): Promise { + return this.callj("irj"); + } + async enumerateFunctions(): Promise { + return this.cmdj("aflj"); + } + async enumerateFlags(): Promise { + return this.cmdj("fj"); + } + skip() { + this.r2.cmd("dss"); + } + ptr(s: string | number): NativePointer { + return new NativePointer(s, this); + } + async call(s: string): Promise { + return this.r2.call(s); + } + async callj(s: string): Promise { + return JSON.parse(await this.call(s)); + } + async cmd(s: string): Promise { + return this.r2.cmd(s); + } + async cmdj(s: string): Promise { + return JSON.parse(await this.cmd(s)); + } + async log(s: string) { + return this.r2.log(s); + } + async clippy(msg: string): Promise { + this.r2.log(await this.r2.cmd("?E " + msg)); + } + async ascii(msg: string): Promise { + this.r2.log(await this.r2.cmd("?ea " + msg)); + } } // useful to call functions via dxc and to define and describe function signatures export class NativeFunction { - constructor() { - } + constructor() {} } // uhm not sure how to map this into r2 yet export class NativeCallback { - constructor() { - } + constructor() {} } // export const NULL = ptr("0");yet @@ -798,7 +828,7 @@ export class NativeCallback { /** * Global function that returns a new instance of a NativePointer. * Saves some typing: `ptr(0)` is the same as `new NativePointer(0)` - * + * * @type function */ export declare function ptr(v: NativePointerValue): NativePointer; @@ -806,7 +836,7 @@ export declare function ptr(v: NativePointerValue): NativePointer; /** * A NativePointer can be described using a string that contains a number in any base (hexadecimal and decimal are the most common formats used) * But it actually supports anything else that could be handled by radare2. You can use symbol names, math operations or special symbols like `$$`. - * + * * @type NativePointerValue */ export type NativePointerValue = string | number | NativePointer; @@ -814,309 +844,329 @@ export type NativePointerValue = string | number | NativePointer; /** * Class providing a way to work with 64bit pointers from Javascript, this API mimics the same * well-known promitive available in Frida, but it's baked by the current session of r2. - * + * * It is also possible to use this class via the global `ptr` function. - * + * * @typedef NativePointer */ export class NativePointer { - addr: string; + addr: string; - api: R2Papi; - constructor(s: NativePointerValue, api?: R2Papi) { - if (api === undefined) { - this.api = R; - } else { - this.api = api; - } - // this.api.r2.log("NP " + s); - this.addr = ("" + s).trim(); - } - /** - * Set a flag (name) at the offset pointed - * - * @param {string} name name of the flag to set - * @returns {string} base64 decoded string - */ - setFlag(name: string) { - this.api.call(`f ${name}=${this.addr}`); - } - /** - * Remove the flag in the current offset - * - */ - unsetFlag() { - this.api.call(`f-${this.addr}`); - } - /** - * Render an hexadecimal dump of the bytes contained in the range starting - * in the current pointer and given length. - * - * @param {number} length optional amount of bytes to dump, using blocksize - * @returns {string} string containing the hexadecimal dump of memory - */ - async hexdump(length?: number): Promise { - const len = (length === undefined) ? "" : "" + length; - return this.api.cmd(`x${len}@${this.addr}`); - } - async functionGraph(format?: GraphFormat): Promise { - if (format === "dot") { - return this.api.cmd(`agfd@ ${this.addr}`); - } - if (format === "json") { - return this.api.cmd(`agfj@${this.addr}`); - } - if (format === "mermaid") { - return this.api.cmd(`agfm@${this.addr}`); - } - return this.api.cmd(`agf@${this.addr}`); - } - async readByteArray(len: number): Promise { - return JSON.parse(await this.api.cmd(`p8j ${len}@${this.addr}`)); - } - async readHexString(len: number): Promise { - return (await this.api.cmd(`p8 ${len}@${this.addr}`)).trim(); - } - async and(a: number): Promise { - const addr = await this.api.call(`?v ${this.addr} & ${a}`); - return new NativePointer(addr.trim()); - } - async or(a: number): Promise { - const addr = await this.api.call(`?v ${this.addr} | ${a}`); - return new NativePointer(addr.trim()); - } - async add(a: number): Promise { - const addr = await this.api.call(`?v ${this.addr}+${a}`); - return new NativePointer(addr); - } - async sub(a: number): Promise { - const addr = await this.api.call(`?v ${this.addr}-${a}`); - return new NativePointer(addr); - } - async writeByteArray(data: number[]): Promise { - await this.api.cmd("wx " + data.join("")) - return this; - } - writeAssembly(instruction: string): NativePointer { - this.api.cmd(`wa ${instruction} @ ${this.addr}`); - return this; - } - writeCString(s: string): NativePointer { - this.api.call("w " + s); - return this; - } - writeWideString(s: string): NativePointer { - this.api.call("ww " + s); - return this; - } - /** - * Check if it's a pointer to the address zero. Also known as null pointer. - * - * @returns {boolean} true if null - */ - async isNull(): Promise { - return (await this.toNumber()) == 0 - } - /** - * Compare current pointer with the passed one, and return -1, 0 or 1. - * - * * if (this < arg) return -1; - * * if (this > arg) return 1; - * * if (this == arg) return 0; - * - * @returns {number} returns -1, 0 or 1 depending on the comparison of the pointers - */ - compare(a: NativePointerValue): number { - const bv: NativePointer = (typeof a === "string" || typeof a === "number") - ? new NativePointer(a) : a; - const dist = r2.call(`?vi ${this.addr} - ${bv.addr}`); - if (dist[0] === '-') { - return -1; - } - if (dist[0] === '0') { - return 0; - } - return 1; - } - /** - * Check if it's a pointer to the address zero. Also known as null pointer. - * - * @returns {boolean} true if null - */ - async pointsToNull(): Promise { - const value = await this.readPointer(); - return await value.compare(0) == 0; - } - async toJSON(): Promise { - const output = await this.api.cmd('?vi ' + this.addr.trim()); - return output.trim(); - } - async toString(): Promise { - return (await this.api.cmd('?v ' + this.addr.trim())).trim(); - } - async toNumber(): Promise { - return parseInt(await this.toString()); - } - writePointer(p: NativePointer): void { - this.api.cmd(`wvp ${p}@${this}`); // requires 5.8.2 - } - async readRelativePointer(): Promise { - return this.add(await this.readS32()); - } - async readPointer(): Promise { - const address = await this.api.call("pvp@" + this.addr); - return new NativePointer(address); - } - async readS8(): Promise { // requires 5.8.9 - return parseInt(await this.api.cmd(`pv1d@${this.addr}`)); - } - async readU8(): Promise { - return parseInt(await this.api.cmd(`pv1u@${this.addr}`)); - } - async readU16(): Promise { - return parseInt(await this.api.cmd(`pv2d@${this.addr}`)); - } - async readU16le(): Promise { - return parseInt(await this.api.cmd(`pv2d@${this.addr}@e:cfg.bigendian=false`)); // requires 5.8.9 - } - async readU16be(): Promise { - return parseInt(await this.api.cmd(`pv2d@${this.addr}@e:cfg.bigendian=true`)); // requires 5.8.9 - } - async readS16(): Promise { - return parseInt(await this.api.cmd(`pv2d@${this.addr}`)); // requires 5.8.9 - } - async readS16le(): Promise { - return parseInt(await this.api.cmd(`pv2d@${this.addr}@e:cfg.bigendian=false`)); // requires 5.8.9 - } - async readS16be(): Promise { - return parseInt(await this.api.cmd(`pv2d@${this.addr}@e:cfg.bigendian=true`)); // requires 5.8.9 - } - async readS32(): Promise { // same as readInt32() - return parseInt(await this.api.cmd(`pv4d@${this.addr}`)); // requires 5.8.9 - } - async readU32(): Promise { - return parseInt(await this.api.cmd(`pv4u@${this.addr}`)); // requires 5.8.9 - } - async readU32le(): Promise { - return parseInt(await this.api.cmd(`pv4u@${this.addr}@e:cfg.bigendian=false`)); // requires 5.8.9 - } - async readU32be(): Promise { - return parseInt(await this.api.cmd(`pv4u@${this.addr}@e:cfg.bigendian=true`)); // requires 5.8.9 - } - async readU64(): Promise { - // XXX: use bignum or string here - return parseInt(await this.api.cmd(`pv8u@${this.addr}`)); - } - async readU64le(): Promise { - return parseInt(await this.api.cmd(`pv8u@${this.addr}@e:cfg.bigendian=false`)); // requires 5.8.9 - } - async readU64be(): Promise { - return parseInt(await this.api.cmd(`pv8u@${this.addr}@e:cfg.bigendian=true`)); // requires 5.8.9 - } - async writeInt(n: number): Promise { - return this.writeU32(n); - } - /** - * Write a byte in the current offset, the value must be between 0 and 255 - * - * @param {string} n number to write in the pointed byte in the current address - * @returns {boolean} false if the operation failed - */ - async writeU8(n: number): Promise { - this.api.cmd(`wv1 ${n}@${this.addr}`); - return true; - } - async writeU16(n: number): Promise { - this.api.cmd(`wv2 ${n}@${this.addr}`); - return true; - } - async writeU16be(n: number): Promise { - this.api.cmd(`wv2 ${n}@${this.addr}@e:cfg.bigendian=true`); - return true; - } - async writeU16le(n: number): Promise { - this.api.cmd(`wv2 ${n}@${this.addr}@e:cfg.bigendian=false`); - return true; - } - async writeU32(n: number): Promise { - await this.api.cmd(`wv4 ${n}@${this.addr}`); - return true; - } - async writeU32be(n: number): Promise { - await this.api.cmd(`wv4 ${n}@${this.addr}@e:cfg.bigendian=true`); - return true; - } - async writeU32le(n: number): Promise { - await this.api.cmd(`wv4 ${n}@${this.addr}@e:cfg.bigendian=false`); - return true; - } - async writeU64(n: number): Promise { - await this.api.cmd(`wv8 ${n}@${this.addr}`); - return true; - } - async writeU64be(n: number): Promise { - await this.api.cmd(`wv8 ${n}@${this.addr}@e:cfg.bigendian=true`); - return true; - } - async writeU64le(n: number): Promise { - await this.api.cmd(`wv8 ${n}@${this.addr}@e:cfg.bigendian=false`); - return true; - } - async readInt32(): Promise { - return this.readU32(); - } - async readCString(): Promise { - const output = await this.api.cmd(`pszj@${this.addr}`); - return JSON.parse(output).string; - } - async readWideString(): Promise { - const output = await this.api.cmd(`pswj@${this.addr}`); - return JSON.parse(output).string; - } - async readPascalString(): Promise { - const output = await this.api.cmd(`pspj@${this.addr}`); - return JSON.parse(output).string; - } - async instruction(): Promise { - const output = await this.api.cmdj(`aoj@${this.addr}`); - return output[0] as Instruction; - } - async disassemble(length?: number): Promise { - const len = (length === undefined) ? "" : "" + length; - return this.api.cmd(`pd ${len}@${this.addr}`); - } - async analyzeFunction(): Promise { - await this.api.cmd("af@" + this.addr); - return this; - } - async analyzeFunctionRecursively(): Promise { - await this.api.cmd("afr@" + this.addr); - return this; - } - async name(): Promise { - return (await this.api.cmd("fd " + this.addr)).trim(); - } - async methodName(): Promise { - // TODO: @ should be optional here, as addr should be passable as argument imho - return (await this.api.cmd("ic.@" + this.addr)).trim(); - } - async symbolName(): Promise { - // TODO: @ should be optional here, as addr should be passable as argument imho - const name = await this.api.cmd("isj.@" + this.addr); - return name.trim(); - } - async getFunction(): Promise { - return this.api.cmdj("afij@" + this.addr); - } - async basicBlock(): Promise { - return this.api.cmdj("abj@" + this.addr); - } - async functionBasicBlocks(): Promise { - return this.api.cmdj("afbj@" + this.addr); - } - async xrefs(): Promise { - return this.api.cmdj("axtj@" + this.addr); - } + api: R2Papi; + constructor(s: NativePointerValue, api?: R2Papi) { + if (api === undefined) { + this.api = R; + } else { + this.api = api; + } + // this.api.r2.log("NP " + s); + this.addr = ("" + s).trim(); + } + /** + * Set a flag (name) at the offset pointed + * + * @param {string} name name of the flag to set + * @returns {string} base64 decoded string + */ + setFlag(name: string) { + this.api.call(`f ${name}=${this.addr}`); + } + /** + * Remove the flag in the current offset + * + */ + unsetFlag() { + this.api.call(`f-${this.addr}`); + } + /** + * Render an hexadecimal dump of the bytes contained in the range starting + * in the current pointer and given length. + * + * @param {number} length optional amount of bytes to dump, using blocksize + * @returns {string} string containing the hexadecimal dump of memory + */ + async hexdump(length?: number): Promise { + const len = length === undefined ? "" : "" + length; + return this.api.cmd(`x${len}@${this.addr}`); + } + async functionGraph(format?: GraphFormat): Promise { + if (format === "dot") { + return this.api.cmd(`agfd@ ${this.addr}`); + } + if (format === "json") { + return this.api.cmd(`agfj@${this.addr}`); + } + if (format === "mermaid") { + return this.api.cmd(`agfm@${this.addr}`); + } + return this.api.cmd(`agf@${this.addr}`); + } + async readByteArray(len: number): Promise { + return JSON.parse(await this.api.cmd(`p8j ${len}@${this.addr}`)); + } + async readHexString(len: number): Promise { + return (await this.api.cmd(`p8 ${len}@${this.addr}`)).trim(); + } + async and(a: number): Promise { + const addr = await this.api.call(`?v ${this.addr} & ${a}`); + return new NativePointer(addr.trim()); + } + async or(a: number): Promise { + const addr = await this.api.call(`?v ${this.addr} | ${a}`); + return new NativePointer(addr.trim()); + } + async add(a: number): Promise { + const addr = await this.api.call(`?v ${this.addr}+${a}`); + return new NativePointer(addr); + } + async sub(a: number): Promise { + const addr = await this.api.call(`?v ${this.addr}-${a}`); + return new NativePointer(addr); + } + async writeByteArray(data: number[]): Promise { + await this.api.cmd("wx " + data.join("")); + return this; + } + writeAssembly(instruction: string): NativePointer { + this.api.cmd(`wa ${instruction} @ ${this.addr}`); + return this; + } + writeCString(s: string): NativePointer { + this.api.call("w " + s); + return this; + } + writeWideString(s: string): NativePointer { + this.api.call("ww " + s); + return this; + } + /** + * Check if it's a pointer to the address zero. Also known as null pointer. + * + * @returns {boolean} true if null + */ + async isNull(): Promise { + return (await this.toNumber()) == 0; + } + /** + * Compare current pointer with the passed one, and return -1, 0 or 1. + * + * * if (this < arg) return -1; + * * if (this > arg) return 1; + * * if (this == arg) return 0; + * + * @returns {number} returns -1, 0 or 1 depending on the comparison of the pointers + */ + compare(a: NativePointerValue): number { + const bv: NativePointer = + typeof a === "string" || typeof a === "number" + ? new NativePointer(a) + : a; + const dist = r2.call(`?vi ${this.addr} - ${bv.addr}`); + if (dist[0] === "-") { + return -1; + } + if (dist[0] === "0") { + return 0; + } + return 1; + } + /** + * Check if it's a pointer to the address zero. Also known as null pointer. + * + * @returns {boolean} true if null + */ + async pointsToNull(): Promise { + const value = await this.readPointer(); + return (await value.compare(0)) == 0; + } + async toJSON(): Promise { + const output = await this.api.cmd("?vi " + this.addr.trim()); + return output.trim(); + } + async toString(): Promise { + return (await this.api.cmd("?v " + this.addr.trim())).trim(); + } + async toNumber(): Promise { + return parseInt(await this.toString()); + } + writePointer(p: NativePointer): void { + this.api.cmd(`wvp ${p}@${this}`); // requires 5.8.2 + } + async readRelativePointer(): Promise { + return this.add(await this.readS32()); + } + async readPointer(): Promise { + const address = await this.api.call("pvp@" + this.addr); + return new NativePointer(address); + } + async readS8(): Promise { + // requires 5.8.9 + return parseInt(await this.api.cmd(`pv1d@${this.addr}`)); + } + async readU8(): Promise { + return parseInt(await this.api.cmd(`pv1u@${this.addr}`)); + } + async readU16(): Promise { + return parseInt(await this.api.cmd(`pv2d@${this.addr}`)); + } + async readU16le(): Promise { + return parseInt( + await this.api.cmd(`pv2d@${this.addr}@e:cfg.bigendian=false`) + ); // requires 5.8.9 + } + async readU16be(): Promise { + return parseInt( + await this.api.cmd(`pv2d@${this.addr}@e:cfg.bigendian=true`) + ); // requires 5.8.9 + } + async readS16(): Promise { + return parseInt(await this.api.cmd(`pv2d@${this.addr}`)); // requires 5.8.9 + } + async readS16le(): Promise { + return parseInt( + await this.api.cmd(`pv2d@${this.addr}@e:cfg.bigendian=false`) + ); // requires 5.8.9 + } + async readS16be(): Promise { + return parseInt( + await this.api.cmd(`pv2d@${this.addr}@e:cfg.bigendian=true`) + ); // requires 5.8.9 + } + async readS32(): Promise { + // same as readInt32() + return parseInt(await this.api.cmd(`pv4d@${this.addr}`)); // requires 5.8.9 + } + async readU32(): Promise { + return parseInt(await this.api.cmd(`pv4u@${this.addr}`)); // requires 5.8.9 + } + async readU32le(): Promise { + return parseInt( + await this.api.cmd(`pv4u@${this.addr}@e:cfg.bigendian=false`) + ); // requires 5.8.9 + } + async readU32be(): Promise { + return parseInt( + await this.api.cmd(`pv4u@${this.addr}@e:cfg.bigendian=true`) + ); // requires 5.8.9 + } + async readU64(): Promise { + // XXX: use bignum or string here + return parseInt(await this.api.cmd(`pv8u@${this.addr}`)); + } + async readU64le(): Promise { + return parseInt( + await this.api.cmd(`pv8u@${this.addr}@e:cfg.bigendian=false`) + ); // requires 5.8.9 + } + async readU64be(): Promise { + return parseInt( + await this.api.cmd(`pv8u@${this.addr}@e:cfg.bigendian=true`) + ); // requires 5.8.9 + } + async writeInt(n: number): Promise { + return this.writeU32(n); + } + /** + * Write a byte in the current offset, the value must be between 0 and 255 + * + * @param {string} n number to write in the pointed byte in the current address + * @returns {boolean} false if the operation failed + */ + async writeU8(n: number): Promise { + this.api.cmd(`wv1 ${n}@${this.addr}`); + return true; + } + async writeU16(n: number): Promise { + this.api.cmd(`wv2 ${n}@${this.addr}`); + return true; + } + async writeU16be(n: number): Promise { + this.api.cmd(`wv2 ${n}@${this.addr}@e:cfg.bigendian=true`); + return true; + } + async writeU16le(n: number): Promise { + this.api.cmd(`wv2 ${n}@${this.addr}@e:cfg.bigendian=false`); + return true; + } + async writeU32(n: number): Promise { + await this.api.cmd(`wv4 ${n}@${this.addr}`); + return true; + } + async writeU32be(n: number): Promise { + await this.api.cmd(`wv4 ${n}@${this.addr}@e:cfg.bigendian=true`); + return true; + } + async writeU32le(n: number): Promise { + await this.api.cmd(`wv4 ${n}@${this.addr}@e:cfg.bigendian=false`); + return true; + } + async writeU64(n: number): Promise { + await this.api.cmd(`wv8 ${n}@${this.addr}`); + return true; + } + async writeU64be(n: number): Promise { + await this.api.cmd(`wv8 ${n}@${this.addr}@e:cfg.bigendian=true`); + return true; + } + async writeU64le(n: number): Promise { + await this.api.cmd(`wv8 ${n}@${this.addr}@e:cfg.bigendian=false`); + return true; + } + async readInt32(): Promise { + return this.readU32(); + } + async readCString(): Promise { + const output = await this.api.cmd(`pszj@${this.addr}`); + return JSON.parse(output).string; + } + async readWideString(): Promise { + const output = await this.api.cmd(`pswj@${this.addr}`); + return JSON.parse(output).string; + } + async readPascalString(): Promise { + const output = await this.api.cmd(`pspj@${this.addr}`); + return JSON.parse(output).string; + } + async instruction(): Promise { + const output = await this.api.cmdj(`aoj@${this.addr}`); + return output[0] as Instruction; + } + async disassemble(length?: number): Promise { + const len = length === undefined ? "" : "" + length; + return this.api.cmd(`pd ${len}@${this.addr}`); + } + async analyzeFunction(): Promise { + await this.api.cmd("af@" + this.addr); + return this; + } + async analyzeFunctionRecursively(): Promise { + await this.api.cmd("afr@" + this.addr); + return this; + } + async name(): Promise { + return (await this.api.cmd("fd " + this.addr)).trim(); + } + async methodName(): Promise { + // TODO: @ should be optional here, as addr should be passable as argument imho + return (await this.api.cmd("ic.@" + this.addr)).trim(); + } + async symbolName(): Promise { + // TODO: @ should be optional here, as addr should be passable as argument imho + const name = await this.api.cmd("isj.@" + this.addr); + return name.trim(); + } + async getFunction(): Promise { + return this.api.cmdj("afij@" + this.addr); + } + async basicBlock(): Promise { + return this.api.cmdj("abj@" + this.addr); + } + async functionBasicBlocks(): Promise { + return this.api.cmdj("afbj@" + this.addr); + } + async xrefs(): Promise { + return this.api.cmdj("axtj@" + this.addr); + } } /* @@ -1129,7 +1179,7 @@ function ptr(x: string|number) { /** * Global instance of R2Papi based on the current session of radare2. * Note that `r2` is the global instance of `r2pipe` used by `R`. - * + * * @type R2Papi */ export declare const R: R2Papi; @@ -1137,7 +1187,7 @@ export declare const R: R2Papi; /** * Global instance of the Module class based on the current radare2 session. * This variable mimics the same APIs shipped by Frida. - * + * * @type ModuleClass */ export declare const Module: ModuleClass; @@ -1145,7 +1195,7 @@ export declare const Module: ModuleClass; /** * Global instance of the Process class based on the current radare2 session. * This variable mimics the same APIs shipped by Frida. - * + * * @type ProcessClass */ export declare const Process: ProcessClass; @@ -1153,7 +1203,7 @@ export declare const Process: ProcessClass; /** * Global instance of the Thread class based on the current radare2 session. * This variable mimics the same APIs shipped by Frida. - * + * * @type ThreadClass */ export declare const Thread: ThreadClass; diff --git a/typescript/async/r2pipe.ts b/typescript/async/r2pipe.ts index 21c79cf..390b965 100644 --- a/typescript/async/r2pipe.ts +++ b/typescript/async/r2pipe.ts @@ -1,26 +1,32 @@ export class R2PipeAsyncFromSync { - r2p: R2Pipe; - constructor(r2p: R2Pipe) { - this.r2p = r2p; - } - async cmd(command: string): Promise { - return this.r2p.cmd(command); - } - async cmdAt(command : string, address: number | string | any): Promise { - return this.r2p.cmdAt(command, address); - } + r2p: R2Pipe; + constructor(r2p: R2Pipe) { + this.r2p = r2p; + } + async cmd(command: string): Promise { + return this.r2p.cmd(command); + } + async cmdAt( + command: string, + address: number | string | any + ): Promise { + return this.r2p.cmdAt(command, address); + } async cmdj(cmd: string): Promise { - return this.r2p.cmdj(cmd); + return this.r2p.cmdj(cmd); + } + async call(command: string): Promise { + return this.r2p.call(command); } - async call(command: string): Promise { - return this.r2p.call(command); - } async callj(cmd: string): Promise { - return this.r2p.cmdj(cmd); + return this.r2p.cmdj(cmd); + } + async callAt( + command: string, + address: number | string | any + ): Promise { + return this.r2p.cmdAt(command, address); } - async callAt(command : string, address: number | string | any): Promise { - return this.r2p.cmdAt(command, address); - } async log(msg: string) { return this.r2p.log(msg); } @@ -34,13 +40,13 @@ export class R2PipeAsyncFromSync { export function newAsyncR2PipeFromSync(r2p: R2Pipe): R2PipeAsync { const asyncR2Pipe = new R2PipeAsyncFromSync(r2p); - return asyncR2Pipe as R2PipeAsync + return asyncR2Pipe as R2PipeAsync; } /** * Generic interface to interact with radare2, abstracts the access to the associated * instance of the tool, which could be native via rlang or remote via pipes or tcp/http. - * + * * @typedef R2Pipe */ export interface R2Pipe { @@ -126,7 +132,7 @@ export interface R2Pipe { /** * Generic interface to interact with radare2, abstracts the access to the associated * instance of the tool, which could be native via rlang or remote via pipes or tcp/http. - * + * * @typedef R2PipeAsync */ export interface R2PipeAsync { diff --git a/typescript/async/shell.ts b/typescript/async/shell.ts index 9889c9d..c7f1e11 100644 --- a/typescript/async/shell.ts +++ b/typescript/async/shell.ts @@ -8,18 +8,18 @@ import { R2Papi } from "./r2papi.js"; * @typedef FileSystemType */ export interface FileSystemType { - /** - * name of the filesystem format, to be used when mounting it. - * - * @type {string} - */ - name: string; - /** - * short string that describes the plugin - * - * @type {string} - */ - description: string; + /** + * name of the filesystem format, to be used when mounting it. + * + * @type {string} + */ + name: string; + /** + * short string that describes the plugin + * + * @type {string} + */ + description: string; } /** @@ -30,15 +30,14 @@ export interface FileSystemType { * @typedef Radare2 */ export interface Radare2 { - /** - * string representing the radare2 version (3 numbers separated by dots) - * - * @type {string} - */ - version: string; + /** + * string representing the radare2 version (3 numbers separated by dots) + * + * @type {string} + */ + version: string; } - /** * Class that interacts with the `r2ai` plugin (requires `rlang-python` and `r2i` r2pm packages to be installed). * Provides a way to script the interactions with different language models using javascript from inside radare2. @@ -46,172 +45,172 @@ export interface Radare2 { * @typedef R2Shell */ export class R2Shell { - /** - * Keep a reference to the associated r2papi instance - * - * @type {R2Papi} - */ - public rp: R2Papi; + /** + * Keep a reference to the associated r2papi instance + * + * @type {R2Papi} + */ + public rp: R2Papi; - /** - * Create a new instance of the R2Shell - * - * @param {R2Papi} take the R2Papi intance to used as backend to run the commands - * @returns {R2Shell} instance of the shell api - */ + /** + * Create a new instance of the R2Shell + * + * @param {R2Papi} take the R2Papi intance to used as backend to run the commands + * @returns {R2Shell} instance of the shell api + */ constructor(papi: R2Papi) { this.rp = papi; } - /** - * Create a new directory in the host system, if the opational recursive argument is set to - * true it will create all the necessary subdirectories instead of just the specified one. - * - * @param {string} text path to the new directory to be created - * @param {boolean?} disabled by default, but if it's true, it will create subdirectories recursively if necessary - * @returns {boolean} true if successful - */ - mkdir(file: string, recursive?:boolean): boolean { - if (recursive === true) { - this.rp.call (`mkdir -p ${file}`); - } else { - this.rp.call (`mkdir ${file}`); - } - return true; - } + /** + * Create a new directory in the host system, if the opational recursive argument is set to + * true it will create all the necessary subdirectories instead of just the specified one. + * + * @param {string} text path to the new directory to be created + * @param {boolean?} disabled by default, but if it's true, it will create subdirectories recursively if necessary + * @returns {boolean} true if successful + */ + mkdir(file: string, recursive?: boolean): boolean { + if (recursive === true) { + this.rp.call(`mkdir -p ${file}`); + } else { + this.rp.call(`mkdir ${file}`); + } + return true; + } - /** - * Deletes a file - * - * @param {string} path to the file to remove - * @returns {boolean} true if successful - */ - unlink(file: string): boolean { - this.rp.call (`rm ${file}`); - return true; - } + /** + * Deletes a file + * + * @param {string} path to the file to remove + * @returns {boolean} true if successful + */ + unlink(file: string): boolean { + this.rp.call(`rm ${file}`); + return true; + } - /** - * Change current directory - * - * @param {string} path to the directory - * @returns {boolean} true if successful - */ - async chdir(path:string) : Promise { - await this.rp.call (`cd ${path}`); - return true; - } + /** + * Change current directory + * + * @param {string} path to the directory + * @returns {boolean} true if successful + */ + async chdir(path: string): Promise { + await this.rp.call(`cd ${path}`); + return true; + } - /** - * List files in the current directory - * - * @returns {string[]} array of file names - */ - async ls(): Promise { - const files = await this.rp.call(`ls -q`); - return files.trim().split('\n'); - } + /** + * List files in the current directory + * + * @returns {string[]} array of file names + */ + async ls(): Promise { + const files = await this.rp.call(`ls -q`); + return files.trim().split("\n"); + } - /** - * TODO: Checks if a file exists (not implemented) - * - * @returns {boolean} true if the file exists, false if it does not - */ - fileExists(path: string) : boolean { - // TODO - return false; - } + /** + * TODO: Checks if a file exists (not implemented) + * + * @returns {boolean} true if the file exists, false if it does not + */ + fileExists(path: string): boolean { + // TODO + return false; + } - /** - * Opens an URL or application - * Execute `xdg-open` on linux, `start` on windows, `open` on Mac - * - * @param {string} URI or file to open by the system - */ - async open(arg: string): Promise { - await this.rp.call (`open ${arg}`); - } + /** + * Opens an URL or application + * Execute `xdg-open` on linux, `start` on windows, `open` on Mac + * + * @param {string} URI or file to open by the system + */ + async open(arg: string): Promise { + await this.rp.call(`open ${arg}`); + } - /** - * Run a system command and get the return code - * - * @param {string} system command to be executed - * @returns {number} return code (0 is success) - */ - system(cmd: string): number { - this.rp.call (`!${cmd}`); - return 0; - } + /** + * Run a system command and get the return code + * + * @param {string} system command to be executed + * @returns {number} return code (0 is success) + */ + system(cmd: string): number { + this.rp.call(`!${cmd}`); + return 0; + } - /** - * Mount the given offset on the specified path using the filesytem. - * This is not a system-level mountpoint, it's using the internal filesystem abstraction of radare2. - * - * @param {string} filesystem type name (see . - * @param {string} system command to be executed - * @param {string|number} - * @returns {number} return code (0 is success) - */ - mount(fstype: string, path: string, offset: string|number) : boolean { - if (!offset) { - offset = 0; - } - this.rp.call (`m ${fstype} ${path} ${offset}`); - return true; - } - /** - * Unmount the mountpoint associated with the given path. - * - * @param {string} path to the mounted filesystem - * @returns {void} TODO: should return boolean - */ - async umount(path: string) : Promise { - await this.rp.call (`m-${path}`); - } - /** - * Change current directory on the internal radare2 filesystem - * - * @param {string} path name to change to - * @returns {void} TODO: should return boolean - */ - async chdir2(path: string) : Promise { - await this.rp.call (`mdq ${path}`); - } - /** - * List the files contained in the given path within the virtual radare2 filesystem. - * - * @param {string} path name to change to - * @returns {void} TODO: should return boolean - */ - async ls2(path: string) : Promise { - const files = await this.rp.call (`mdq ${path}`) - return files.trim().split('\n'); - } - /** - * Enumerate all the mountpoints set in the internal virtual filesystem of radare2 - * @returns {any[]} array of mount - */ - async enumerateFilesystemTypes(): Promise { - return this.rp.cmdj ("mLj"); - } - /** - * Enumerate all the mountpoints set in the internal virtual filesystem of radare2 - * @returns {any[]} array of mount - */ - async enumerateMountpoints(): Promise { - const output = await this.rp.cmdj ("mj"); - return output['mountpoints']; - } - /** - * TODO: not implemented - */ - isSymlink(file:string) : boolean { - return false; - } - /** - * TODO: not implemented - */ - isDirectory(file:string) : boolean { - return false; - } + /** + * Mount the given offset on the specified path using the filesytem. + * This is not a system-level mountpoint, it's using the internal filesystem abstraction of radare2. + * + * @param {string} filesystem type name (see . + * @param {string} system command to be executed + * @param {string|number} + * @returns {number} return code (0 is success) + */ + mount(fstype: string, path: string, offset: string | number): boolean { + if (!offset) { + offset = 0; + } + this.rp.call(`m ${fstype} ${path} ${offset}`); + return true; + } + /** + * Unmount the mountpoint associated with the given path. + * + * @param {string} path to the mounted filesystem + * @returns {void} TODO: should return boolean + */ + async umount(path: string): Promise { + await this.rp.call(`m-${path}`); + } + /** + * Change current directory on the internal radare2 filesystem + * + * @param {string} path name to change to + * @returns {void} TODO: should return boolean + */ + async chdir2(path: string): Promise { + await this.rp.call(`mdq ${path}`); + } + /** + * List the files contained in the given path within the virtual radare2 filesystem. + * + * @param {string} path name to change to + * @returns {void} TODO: should return boolean + */ + async ls2(path: string): Promise { + const files = await this.rp.call(`mdq ${path}`); + return files.trim().split("\n"); + } + /** + * Enumerate all the mountpoints set in the internal virtual filesystem of radare2 + * @returns {any[]} array of mount + */ + async enumerateFilesystemTypes(): Promise { + return this.rp.cmdj("mLj"); + } + /** + * Enumerate all the mountpoints set in the internal virtual filesystem of radare2 + * @returns {any[]} array of mount + */ + async enumerateMountpoints(): Promise { + const output = await this.rp.cmdj("mj"); + return output["mountpoints"]; + } + /** + * TODO: not implemented + */ + isSymlink(file: string): boolean { + return false; + } + /** + * TODO: not implemented + */ + isDirectory(file: string): boolean { + return false; + } } diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 367ea64..acad208 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -8,6 +8,9 @@ "name": "r2papi", "version": "0.3.3", "license": "MIT", + "dependencies": { + "prettier": "^3.2.4" + }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^6.19.1", "@typescript-eslint/parser": "^6.19.1", @@ -1449,6 +1452,20 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -2954,6 +2971,11 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", diff --git a/typescript/package.json b/typescript/package.json index 9d5484c..ed55b36 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -8,12 +8,13 @@ "url": "https://github.com/radareorg/radare2-r2pipe/issues" }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^6.19.1", + "@typescript-eslint/parser": "^6.19.1", + "eslint": "^8.34.0", "r2pipe": "^2.8.6", + "prettier": "^3.2.4", "typedoc": "^0.25.7", - "typescript": "^5.3.3", - "eslint": "^8.34.0", - "@typescript-eslint/parser": "^6.19.1", - "@typescript-eslint/eslint-plugin": "^6.19.1" + "typescript": "^5.3.3" }, "scripts": { "build": "tsc -m node16 --target es2020 --declaration r2pipe.ts base64.ts ai.ts r2papi.ts esil.ts shell.ts",