r2papi
Version:
r2api on top of r2pipe for typescript and js
501 lines (500 loc) • 17.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EsilParser = exports.EsilNode = exports.EsilToken = void 0;
// ("this is just a comment"), -- comments are also part of the runtime
/*
=("//", {
=(obj, {}())
=([obj, comment], 32)
if(eq([obj,comment], 32),
ret()
)
ret(obj)
})
*/
class EsilToken {
constructor(text = "", position = 0) {
this.label = "";
this.comment = "";
this.text = "";
this.addr = "0"; // for ut64 we use strings for numbers :<
this.position = 0;
this.text = text;
this.position = position;
}
toString() {
return this.text;
}
}
exports.EsilToken = EsilToken;
class EsilNode {
constructor(token = new EsilToken(), type = "none") {
this.type = "none";
this.token = token;
this.children = [];
}
setSides(lhs, rhs) {
this.lhs = lhs;
this.rhs = rhs;
}
addChildren(ths, fhs) {
if (ths !== undefined) {
this.children.push(ths);
}
if (fhs !== undefined) {
this.children.push(fhs);
}
}
toEsil() {
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() {
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();
}
}
exports.EsilNode = EsilNode;
class EsilParser {
constructor(r2) {
this.cur = 0;
this.r2 = r2;
this.cur = 0;
this.stack = [];
this.nodes = [];
this.tokens = [];
this.root = new EsilNode(new EsilToken("function", 0), "block");
}
toJSON() {
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() {
return this.nodes.map((x) => x.toEsil()).join(",");
}
optimizeFlags(node) {
if (node.rhs !== undefined) {
this.optimizeFlags(node.rhs);
}
if (node.lhs !== undefined) {
this.optimizeFlags(node.lhs);
}
for (let i = 0; i < node.children.length; i++) {
this.optimizeFlags(node.children[i]);
}
const addr = node.toString();
if (+addr > 4096) {
const cname = r2.cmd(`fd.@ ${addr}`);
const fname = cname.trim().split("\n")[0].trim();
if (fname != "" && fname.indexOf("+") === -1) {
node.token.text = fname;
}
}
}
optimize(options) {
if (options.indexOf("flag") != -1) {
this.optimizeFlags(this.root);
}
}
toString() {
return this.root.children.map((x) => x.toString()).join(";\n");
}
reset() {
this.nodes = [];
this.stack = [];
this.tokens = [];
this.cur = 0;
this.root = new EsilNode(new EsilToken("function", 0), "block");
}
parseRange(from, to) {
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");
}
parseFunction(addr) {
const ep = this;
function parseAmount(n) {
// console.log("PDQ "+n);
const output = 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 ();
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 = (r2.cmd("?v $$")).trim();
// const func = r2.cmdj("pdrj"); // XXX this command changes the current seek
if (addr === undefined) {
addr = oaddr;
}
const bbs = r2.cmdj(`afbj@${addr}`); // XXX this command changes the current seek
for (const bb of bbs) {
// console.log("bb_" + bb.addr + ":");
r2.cmd(`s ${bb.addr}`);
parseAmount(bb.ninstr);
}
r2.cmd(`s ${oaddr}`);
}
parse(expr, addr) {
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) {
return this.tokens[a];
}
pushToken(tok) {
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");
}
isNumber(expr) {
if (expr.toString().startsWith("0")) {
return true;
}
return +expr > 0;
}
isInternal(expr) {
const text = expr.toString();
return text.startsWith("$") && text.length > 1;
}
parseUntil(start) {
const from = start + 1;
let pos = from;
const origStack = [];
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];
}
getNodeFor(index) {
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;
}
findNodeFor(index) {
for (const node of this.nodes) {
if (node.token.position === index) {
return node;
}
}
return null;
}
isOperation(expr) {
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 = 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;
}
}
exports.EsilParser = EsilParser;