├── typescript ├── r2papi ├── examples │ ├── project │ │ ├── index.d.ts │ │ ├── shell.d.ts │ │ ├── shell.ts │ │ ├── r2pipe.d.ts │ │ ├── r2pipe-types │ │ │ ├── index.d.ts │ │ │ └── package.json │ │ ├── Makefile │ │ ├── index.ts │ │ ├── lib │ │ │ └── trace.ts │ │ ├── tsconfig.json │ │ └── package.json │ ├── Makefile │ ├── asm │ │ ├── package.json │ │ ├── Makefile │ │ ├── index.js │ │ └── main.js │ ├── r2f-pkg │ │ ├── world.ts │ │ ├── package.json │ │ ├── Makefile │ │ └── index.ts │ ├── main.ts │ ├── test.ts │ └── draft-shell.qjs ├── .prettierrc ├── tools │ ├── tsi.sh │ ├── jsts.ts │ └── asyncify.py ├── async │ ├── index.ts │ ├── tsconfig.json │ ├── opt.ts │ ├── base64.ts │ ├── graph.ts │ ├── r2frida.ts │ ├── elang.ts │ ├── ai.ts │ ├── pdq.ts │ ├── shell.ts │ ├── r2pipe.ts │ └── esil.ts ├── minimal-skeleton.r2.js ├── .eslintrc.json ├── README.md ├── package.json ├── r2pipe.d.ts └── Makefile ├── rust ├── .gitignore ├── Makefile ├── README.md ├── Cargo.toml ├── examples │ ├── analysis_example.rs │ └── api_example.rs └── src │ ├── lib.rs │ ├── structs.rs │ └── api_trait.rs ├── nim ├── Makefile ├── r2papi.nim └── main.nim ├── python ├── test │ ├── test_bin │ ├── minimum.py │ ├── test.c │ ├── test_config.py │ ├── test_iomap.py │ ├── test_base.py │ ├── test_flags.py │ ├── test_file.py │ ├── test_write.py │ ├── test_print.py │ ├── test_debugger.py │ ├── test_esil.py │ ├── test_r2api.py │ └── test_search.py ├── .gitignore ├── r2papi │ ├── __init__.py │ ├── flags.py │ ├── iomap.py │ ├── config.py │ ├── file.py │ ├── write.py │ ├── base.py │ ├── debugger.py │ ├── esil.py │ ├── print.py │ ├── r2api.py │ └── search.py ├── docs │ ├── Makefile │ ├── make.bat │ ├── api.rst │ ├── index.rst │ ├── development.rst │ └── conf.py ├── README.md ├── Makefile └── pyproject.toml ├── playground └── asan │ ├── a.png │ ├── package.json │ ├── Makefile │ ├── tsconfig.json │ ├── README.md │ ├── asan.ts │ └── crash.txt ├── pascal ├── README.md ├── hello.pas └── Makefile ├── webdocs ├── public │ ├── favicon.ico │ ├── style.css │ └── index.html └── Makefile ├── .gitignore ├── .github └── workflows │ ├── rust.yml │ ├── python.yml │ ├── typescript.yml │ └── gh-pages.yml └── README.md /typescript/r2papi: -------------------------------------------------------------------------------- 1 | async -------------------------------------------------------------------------------- /typescript/examples/project/index.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /rust/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | **/*.rs.bk 4 | *.swp 5 | -------------------------------------------------------------------------------- /typescript/examples/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | r2 -i test.ts /bin/ls 3 | -------------------------------------------------------------------------------- /typescript/examples/asm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | 5 | -------------------------------------------------------------------------------- /typescript/examples/project/shell.d.ts: -------------------------------------------------------------------------------- 1 | export default function prompt(): void; 2 | -------------------------------------------------------------------------------- /nim/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | nim c -d:release --backend=js -o:test.qjs main.nim 3 | r2 -qi test.qjs - 4 | -------------------------------------------------------------------------------- /python/test/test_bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radareorg/radare2-r2papi/master/python/test/test_bin -------------------------------------------------------------------------------- /typescript/examples/r2f-pkg/world.ts: -------------------------------------------------------------------------------- 1 | export function world() : string { 2 | return "World!"; 3 | } 4 | -------------------------------------------------------------------------------- /playground/asan/a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radareorg/radare2-r2papi/master/playground/asan/a.png -------------------------------------------------------------------------------- /pascal/README.md: -------------------------------------------------------------------------------- 1 | Uses pas2js to compile 2 | 3 | https://downloads.freepascal.org/fpc/contrib/pas2js/2.2.0/ 4 | -------------------------------------------------------------------------------- /typescript/examples/project/shell.ts: -------------------------------------------------------------------------------- 1 | export default function prompt() { 2 | console.log("r2test> "); 3 | } 4 | -------------------------------------------------------------------------------- /typescript/examples/r2f-pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "r2papi": "^0.0.23" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /webdocs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radareorg/radare2-r2papi/master/webdocs/public/favicon.ico -------------------------------------------------------------------------------- /python/test/minimum.py: -------------------------------------------------------------------------------- 1 | import r2papi 2 | 3 | api = r2papi.R2Api("/bin/ls") 4 | print(api.info().arch) 5 | api.quit() 6 | -------------------------------------------------------------------------------- /playground/asan/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "r2papi": "^0.0.4", 4 | "r2pipe": "^2.8.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | .venv/ 3 | build/ 4 | _build/ 5 | dist/ 6 | *.zip 7 | __*cache__/ 8 | test/test_exe 9 | a.dot 10 | -------------------------------------------------------------------------------- /python/r2papi/__init__.py: -------------------------------------------------------------------------------- 1 | """R2Papi - High level API on top of r2pipe""" 2 | 3 | from .r2api import R2Api 4 | 5 | __all__ = ["R2Api"] 6 | -------------------------------------------------------------------------------- /playground/asan/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: node_modules 3 | r2 -qi asan.ts /bin/ls 4 | 5 | node_modules: 6 | mkdir -p node_modules 7 | npm i r2papi 8 | -------------------------------------------------------------------------------- /typescript/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "tabWidth": 4, 4 | "trailingComma": "none", 5 | "singleQuote": false, 6 | "printWidth": 80 7 | } 8 | -------------------------------------------------------------------------------- /typescript/examples/r2f-pkg/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | mkdir -p node_modules 3 | npm i 4 | r2pm -r r2frida-compile -S -o index.qjs index.ts 5 | r2 -qi index.qjs /bin/ls 6 | 7 | clean: 8 | rm -f index.qjs 9 | -------------------------------------------------------------------------------- /typescript/examples/project/r2pipe.d.ts: -------------------------------------------------------------------------------- 1 | declare module "r2pipe" { 2 | export class R2Pipe { 3 | cmd(string): string; 4 | cmdj(string): any; 5 | quit(); 6 | } 7 | export function open(string): R2Pipe; 8 | } 9 | -------------------------------------------------------------------------------- /rust/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cargo build --release 3 | 4 | ex example: 5 | cargo run --release 6 | 7 | test: 8 | cargo test --release 9 | 10 | fmt: 11 | cargo fmt --all -- --check 12 | 13 | clean: 14 | cargo clean 15 | -------------------------------------------------------------------------------- /typescript/examples/project/r2pipe-types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "r2pipe" { 2 | export class R2Pipe { 3 | cmd(string): string; 4 | cmdj(string): any; 5 | quit(); 6 | } 7 | export function open(string?): R2Pipe; 8 | } 9 | -------------------------------------------------------------------------------- /typescript/examples/asm/Makefile: -------------------------------------------------------------------------------- 1 | all: rvcodecjs 2 | node index.js 3 | r2frida-compile -Sc index.js > index.r2.js 4 | r2 -i index.r2.js - 5 | 6 | rvcodecjs: 7 | git clone https://gitlab.com/luplab/rvcodecjs 8 | cd rvcodecjs && npm i 9 | -------------------------------------------------------------------------------- /pascal/hello.pas: -------------------------------------------------------------------------------- 1 | program Hello; 2 | 3 | function r2cmd(C: string) : string; external name 'r2cmd'; 4 | 5 | var 6 | s: string; 7 | 8 | begin 9 | writeln ('Hello, world.'); 10 | s := r2cmd('?E Hello'); 11 | writeln (s); 12 | end. 13 | -------------------------------------------------------------------------------- /typescript/tools/tsi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # typescript interface from js 3 | CMD="$1" 4 | FILE=/bin/ls 5 | export RES=`r2 -qc "af;$CMD" $FILE` 6 | node -e 'console.log(require("json2ts").convert(process.env.RES));' 7 | # XXX the name of the interface must be defined somehow 8 | -------------------------------------------------------------------------------- /typescript/examples/project/r2pipe-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r2pipe", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "description": "dummy types package for r2pipe", 6 | "__dependencies": { 7 | "r2papi": "*" 8 | }, 9 | "devDependencies": { 10 | "typescript": "*" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /typescript/examples/project/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | -mv node_modules/_r2pipe node_modules/r2pipe 3 | npm run node 4 | -# 5 | mv node_modules/r2pipe node_modules/_r2pipe 6 | cp -rf r2pipe-types node_modules/r2pipe 7 | -npm run qjs 8 | rm -rf node_modules/r2pipe 9 | mv node_modules/_r2pipe node_modules/r2pipe 10 | -------------------------------------------------------------------------------- /typescript/examples/project/index.ts: -------------------------------------------------------------------------------- 1 | import prompt from "./shell.js"; 2 | import { functions, Function } from "./lib/trace.js"; 3 | 4 | console.log("Hello World"); 5 | const funcs = functions(); 6 | console.log(funcs.map((f)=>f.toString()).join("\n")); 7 | console.log(funcs.map((f)=>""+f).join("")); 8 | 9 | prompt(); 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | *.pyc 3 | venv/ 4 | 5 | .coverage 6 | *.egg-info 7 | .pytest_cache 8 | dist/ 9 | htmlcov/ 10 | python/docs/_build 11 | typescript/*/docs/ 12 | typescript/docs.zip 13 | typescript/sync/*.ts 14 | typescript/sync/*.json 15 | typescript/node_modules 16 | typescript/build 17 | typescript/docs 18 | webdocs/public/*/ 19 | -------------------------------------------------------------------------------- /python/test/test.c: -------------------------------------------------------------------------------- 1 | int func1() {return 1;} 2 | int func2() {return 2;} 3 | int func3() {return 3;} 4 | int func4() {return 4;} 5 | int func5() {return 5;} 6 | 7 | int main() { 8 | int a[5]; 9 | a[0] = func1(); 10 | a[1] = func2(); 11 | a[2] = func3(); 12 | a[3] = func4(); 13 | a[4] = func5(); 14 | return a[3]; 15 | } 16 | -------------------------------------------------------------------------------- /nim/r2papi.nim: -------------------------------------------------------------------------------- 1 | import std/[json, options, sugar] 2 | 3 | proc r2cmd(arg: cstring): cstring {.importc} 4 | 5 | type R2Papi = object 6 | 7 | proc cmdj(self: R2Papi, cmd: string): JsonNode = 8 | var res = $r2cmd(cmd) 9 | return parseJson(res) 10 | 11 | proc speak(self: R2Papi, msg: string) = 12 | echo r2cmd("?E " & msg) 13 | 14 | export r2cmd, cmdj, R2Papi, speak 15 | -------------------------------------------------------------------------------- /nim/main.nim: -------------------------------------------------------------------------------- 1 | import r2papi 2 | import std/strformat 3 | import std/sugar 4 | import std/json 5 | 6 | var r = r2papi.R2Papi() 7 | r.speak ("Hello World") 8 | 9 | var bigint = 0xeeff_ffff_ffff_fff0 10 | 11 | var o = r.cmdj("ij"); 12 | var fileName = o["core"]["file"].str 13 | echo(fileName) 14 | 15 | echo(fmt"{bigint:+x}") 16 | 17 | # echo("Hello World") 18 | # echo(r2cmd("x")) 19 | 20 | -------------------------------------------------------------------------------- /webdocs/public/style.css: -------------------------------------------------------------------------------- 1 | a, a:visited, a:link, a:active { 2 | text-decoration: none !important ; 3 | color:#ffb900!important; 4 | } 5 | a:hover { 6 | text-decoration: underline !important; 7 | text-shadow: 0px 0px 2px black; 8 | color: #f0e000!important; 9 | } 10 | body { 11 | background-color:#151515; 12 | font-family:montserrat; 13 | color:#f0f0f0; 14 | border:0px; 15 | padding-bottom:4em; 16 | } -------------------------------------------------------------------------------- /typescript/examples/asm/index.js: -------------------------------------------------------------------------------- 1 | //{{ifr2}} 2 | import { main } from "./main.js"; 3 | import * as Instruction from "./rvcodecjs/core/Instruction.js"; 4 | main(Instruction); 5 | //{{else}} 6 | /* 7 | import ("./main.js").then((main) => { 8 | import ("./rvcodecjs/core/Instruction.js").then((Instruction) => { 9 | console.log("jeje", Instruction); 10 | console.log("main", main.main); 11 | main.main(Instruction); 12 | }); 13 | }); 14 | */ 15 | //{{endif}} 16 | -------------------------------------------------------------------------------- /typescript/async/index.ts: -------------------------------------------------------------------------------- 1 | import { R2PipeAsync } from "./r2pipe.js"; 2 | export { R2PipeAsync } from "./r2pipe.js"; 3 | export { R2AI } from "./ai.js"; 4 | export { 5 | R, 6 | NativePointer, 7 | R2Papi, 8 | Reference, 9 | BasicBlock, 10 | ThreadClass 11 | } from "./r2papi.js"; 12 | export { EsilNode, EsilParser } from "./esil.js"; 13 | export { Base64, Base64Interface } from "./base64.js"; 14 | 15 | export declare const r2: R2PipeAsync; 16 | -------------------------------------------------------------------------------- /typescript/tools/jsts.ts: -------------------------------------------------------------------------------- 1 | function JsonToTypescript(name: string, a: any) : string { 2 | let str = `interface ${name} {\n`; 3 | if (a.length && a.length > 0) { 4 | a = a[0]; 5 | } 6 | for (let k of Object.keys(a)) { 7 | const typ = typeof (a[k]); 8 | const nam = k; 9 | str += ` ${nam}: ${typ};\n`; 10 | } 11 | return `${str}}\n`; 12 | } 13 | 14 | 15 | const res = JsonToTypescript("Test", { 16 | name: "hello world", 17 | bits: 32 18 | } 19 | ); 20 | 21 | console.log(res); 22 | -------------------------------------------------------------------------------- /playground/asan/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es2017", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /typescript/examples/main.ts: -------------------------------------------------------------------------------- 1 | // import {R2Pipe} from "r2papi"; 2 | declare type R2Pipe = any; 3 | declare let r2: any; 4 | 5 | declare var Gmain: Function | null; 6 | 7 | console.log("main"); 8 | try { 9 | Gmain 10 | } catch (e) { 11 | var Gmain : Function | null = null; 12 | } 13 | Gmain = function (r2: R2Pipe, args: any) { 14 | console.log(Object.keys(args)); 15 | const res = r2.cmd("x"); 16 | console.log("Hello World", res); 17 | } 18 | console.log("pre"); 19 | Gmain(r2, []); 20 | console.log("pos"); 21 | -------------------------------------------------------------------------------- /webdocs/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: web clean 2 | 3 | web: public/typescript public/python 4 | 5 | public/typescript: 6 | rm -Rf public/typescript 7 | mkdir -p public/typescript 8 | make -C ../typescript docs-html 9 | mv ../typescript/sync/docs public/typescript/sync 10 | mv ../typescript/async/docs public/typescript/async 11 | 12 | public/python: 13 | rm -Rf public/python 14 | make -C ../python/docs html 15 | mv ../python/docs/_build/html public/python 16 | 17 | clean: 18 | rm -Rf public/typescript public/python -------------------------------------------------------------------------------- /typescript/examples/r2f-pkg/index.ts: -------------------------------------------------------------------------------- 1 | import { world } from "./world.js"; 2 | import { r2, R2Papi } from "r2papi"; 3 | 4 | Main(); 5 | 6 | function Main() { 7 | var r = r2.cmd("?E Hello World"); 8 | console.log(r); 9 | var papi = new R2Papi(r2); 10 | const info = papi.binInfo(); 11 | console.log(info.machine); 12 | console.log("Hello " + world()); 13 | 14 | const shell = papi.getShell(); 15 | shell.chdir("/"); 16 | const files = shell.ls(); 17 | for (let file of files) { 18 | console.log(file); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /typescript/minimal-skeleton.r2.js: -------------------------------------------------------------------------------- 1 | /* 2 | export R2_PAPI_SCRIPT=$(pwd)/minimal-skeleton.r2.js 3 | r2 -j 'console.log(R.version)' - 4 | */ 5 | console.log("Minimalistic r2papi script"); 6 | 7 | class R2Papi { 8 | constructor () { 9 | } 10 | } 11 | 12 | class ProcessClass { 13 | constructor () { 14 | } 15 | } 16 | 17 | class ModuleClass { 18 | constructor () { 19 | } 20 | } 21 | 22 | class ThreadClass { 23 | constructor () { 24 | } 25 | } 26 | 27 | class NativePointer{ 28 | constructor () { 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pascal/Makefile: -------------------------------------------------------------------------------- 1 | UNAME=$(shell uname | tr A-Z a-z) 2 | PASVER=2.2.0 3 | 4 | ifeq ($(UNAME),darwin) 5 | OS=macos 6 | else 7 | OS=linux 8 | endif 9 | PAS2JS=pas2js-$(OS)-$(PASVER)/bin/x86_64-$(UNAME)/pas2js 10 | 11 | all: $(PAS2JS) 12 | $(PAS2JS) -O2 -B -Jc -Tnodejs hello.pas 13 | r2 -j hello.js 14 | 15 | json: 16 | $(PAS2JS) -O2 -B -Jc -Tnodejs json.pas 17 | r2 -j json.js 18 | 19 | $(PAS2JS): 20 | wget -c https://downloads.freepascal.org/fpc/contrib/pas2js/$(PASVER)/pas2js-$(OS)-$(PASVER).zip 21 | unzip pas2js-$(OS)-$(PASVER) 22 | -------------------------------------------------------------------------------- /typescript/examples/test.ts: -------------------------------------------------------------------------------- 1 | import { R, R2Papi, NativePointer } from "r2papi"; // ../index"; 2 | 3 | namespace Main { 4 | const searchResults = R.searchString("lib"); 5 | console.log(`Found ${searchResults.length} results`) 6 | for (let result of searchResults) { 7 | const nullptr = new NativePointer(0); 8 | console.log(nullptr.add(4).sub(4).isNull()); 9 | /* 10 | const text = new NativePointer(R, result.addr); 11 | console.log(result.addr, text); // .readCString()); 12 | */ 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /typescript/async/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "lib": ["es2020", "esnext"], 5 | "module": "commonjs", 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true, 8 | "declaration": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "outDir": "./dist" 12 | }, 13 | "include": [ 14 | "index.ts", 15 | "ai.ts", 16 | "base64.ts", 17 | "r2pipe.ts", 18 | "r2papi.ts", 19 | "esil.ts", 20 | "shell.ts", 21 | "r2frida.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /typescript/examples/project/lib/trace.ts: -------------------------------------------------------------------------------- 1 | import r2pipe from "r2pipe"; 2 | 3 | export class Function { 4 | addr: number; 5 | name: string; 6 | 7 | constructor (addr:number, name: string) { 8 | this.addr = addr; 9 | this.name = name; 10 | } 11 | toString() { 12 | return `Name: ${this.name} at ${this.addr}`; 13 | } 14 | } 15 | 16 | export function functions() : [Function] { 17 | const r2 = r2pipe.open(); 18 | console.log(r2.cmd("x 64")); 19 | // r2.close(); 20 | const funcs = new Array() as [Function]; 21 | funcs.push(new Function(0x32, "main")); 22 | return funcs; 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cd rust && cargo build --verbose 21 | - name: Run tests 22 | run: cd rust && cargo test --verbose 23 | - name: clippy 24 | run: cd rust && cargo clippy -- -D warnings 25 | - name: fmt 26 | run: cd rust && cargo fmt --all -- --check 27 | -------------------------------------------------------------------------------- /typescript/examples/project/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "lib": ["es2020"], 5 | "module": "commonjs", 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true, 8 | "declaration": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "outDir": "./dist", 12 | "types": ["node"], 13 | "typeRoots": [ 14 | "./@types", 15 | "node_modules/@types" 16 | ] 17 | }, 18 | "main": "./index.ts", 19 | "type": "module", 20 | "include": [ 21 | "index.ts", 22 | "lib/trace.ts", 23 | "shell.ts" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/python.yml: -------------------------------------------------------------------------------- 1 | name: Python 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Install radare2 6.0.4 17 | run: | 18 | wget https://github.com/radareorg/radare2/releases/download/6.0.4/radare2_6.0.4_amd64.deb 19 | sudo dpkg -i radare2_6.0.4_amd64.deb 20 | r2 -v 21 | - name: Test the r2api 22 | run: sudo make -C python test || true 23 | #run: rarun2 timeout=10 system="make -C python test" 24 | -------------------------------------------------------------------------------- /python/test/test_config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | 4 | import pytest 5 | import r2pipe 6 | from r2papi.config import Config 7 | 8 | 9 | @pytest.fixture 10 | def c(): 11 | r = r2pipe.open(f"{os.path.dirname(__file__)}/test_bin") 12 | c = Config(r) 13 | yield c 14 | c.r2.quit() 15 | 16 | 17 | def test_set_variable(c): 18 | assert c.asm.bits == 64 19 | c.asm.bits = 32 20 | assert c.asm.bits == 32 21 | 22 | 23 | def test_get_variable_str(c): 24 | assert c.asm.arch == "x86" 25 | 26 | 27 | def test_get_variable_bool(c): 28 | c._exec("e io.cache = true") 29 | assert c.io.cache 30 | -------------------------------------------------------------------------------- /typescript/examples/project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r2test", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "description": "hello world using r2papi", 6 | "dependencies": { 7 | "r2papi": "*" 8 | }, 9 | "devDependencies": { 10 | "eslint": "*", 11 | "r2pipe": "^2.8.4", 12 | "typescript": "*" 13 | }, 14 | "scripts": { 15 | "qjs": "r2frida-compile -o index.r2.js index.ts && r2 -qi index.r2.js -", 16 | "node": "tsc && r2 -qi dist/index.js -", 17 | "build": "tsc -m node16 --target es2020 --declaration index.ts", 18 | "test": "echo \"Error: no test specified\" && exit 1", 19 | "doc": "typedoc index.ts shell.ts esil.ts" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /python/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /typescript/examples/draft-shell.qjs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | const SH = new R2Shell (R); 7 | 8 | SH.system("git --help"); 9 | 10 | const output = S.run("git --help"); 11 | 12 | import {ls,cwd,system,fileExists,copy } from "./shell"; 13 | /// 14 | const files = SH.ls(); 15 | console.log(`CWD: ${SH.cwd()}:`); 16 | console.log(files.join('\n')); 17 | 18 | if (SH.fileExists('Makefile')) { 19 | if (SH.system("make") != 0) { 20 | console.log("FAILED"); 21 | } else { 22 | const src = `foo.${SH.LIBEXT}`; 23 | const dst = `${SH.R2_USER_PLUGINS}/${src}`; 24 | SH.copy (src, dst); 25 | } 26 | } else { 27 | console.error("Unknown build system"); 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /python/test/test_iomap.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | 4 | import pytest 5 | import r2pipe 6 | from r2papi.iomap import IOMap 7 | 8 | 9 | @pytest.fixture 10 | def m(): 11 | r = r2pipe.open(f"{os.path.dirname(__file__)}/test_bin") 12 | return IOMap(r, 1) 13 | 14 | 15 | def test_name(m): 16 | m.name = "foo" 17 | assert type(m.name) is str 18 | assert m.name == "foo" 19 | 20 | 21 | def test_flags(m): 22 | print(m.flags) 23 | m.flags = "rwx" 24 | assert m.flags == "rwx" 25 | 26 | 27 | def test_relocate(m): 28 | m.addr = 0x100 29 | assert m.addr == 0x100 30 | 31 | 32 | def test_remove(m): 33 | m.remove() 34 | assert m._mapObj() is None 35 | -------------------------------------------------------------------------------- /typescript/async/opt.ts: -------------------------------------------------------------------------------- 1 | // optimizations for esil ast 2 | import { EsilParser } from "./esil"; 3 | 4 | // Type 'R2Pipe' is missing the following properties from type 'R2Pipe': cmdj, callj, plugin, unload 5 | // import { R2Pipe } from "./r2pipe"; 6 | declare let r2: any; 7 | 8 | export function plusZero() {} 9 | 10 | function traverse(ep: EsilParser, child: any) { 11 | for (const child of ep.nodes) { 12 | if (child) { 13 | traverse(ep, child); 14 | } 15 | traverse(ep, child); 16 | } 17 | } 18 | 19 | const ep = new EsilParser(r2); 20 | ep.parseFunction(); 21 | ep.parse("0,eax,+,ebx,:="); 22 | traverse(ep, null); 23 | console.log("DO"); 24 | console.log(ep.toString()); 25 | console.log("NE"); 26 | -------------------------------------------------------------------------------- /typescript/async/base64.ts: -------------------------------------------------------------------------------- 1 | export class Base64 { 2 | /** 3 | * Encode the given input string using base64 4 | * 5 | * @param {string} input string to encode 6 | * @returns {string} base64 encoded string 7 | */ 8 | static encode(input: string): string { 9 | return b64(input); 10 | } 11 | /** 12 | * Decode the given base64 string into plain text 13 | * 14 | * @param {string} input string encoded in base64 format 15 | * @returns {string} base64 decoded string 16 | */ 17 | static decode(input: string): string { 18 | return b64(input, true); 19 | } 20 | } 21 | 22 | export interface Base64Interface { 23 | (message: string, decode?: boolean): string; 24 | } 25 | 26 | export declare const b64: Base64Interface; 27 | -------------------------------------------------------------------------------- /playground/asan/README.md: -------------------------------------------------------------------------------- 1 | # ASAN crashlog parser and visualization 2 | 3 | This typescript program is designed to run inside radare2 using the new QJS 4 | runtime and the high level r2papi API. 5 | 6 | --pancake 7 | 8 | ## How to use 9 | 10 | Run `make` to get the r2papi installed and genreate the .qjs file. 11 | 12 | The crash.txt file contains the output from ASAN, this file is loaded using 13 | the r2.cmd("cat") API call, calls the parsing function which walks the 14 | backtraces, merging the paths creating edges and registering how the 15 | offending variable is accessed (READ, WRITE, ALLOC or FREE). 16 | 17 | ```sh 18 | r2 -qi asan.ts - > a.dot 19 | dot -Tpng < a.dot > a.png 20 | ``` 21 | 22 | Uncoment the last line if you want the ascii art graph instead 23 | 24 | ## How it looks 25 | 26 | ![](a.png) 27 | -------------------------------------------------------------------------------- /typescript/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "parser": "@typescript-eslint/parser", 11 | "parserOptions": { 12 | "ecmaVersion": 8, 13 | "sourceType": "module" 14 | }, 15 | "plugins": [ 16 | "@typescript-eslint" 17 | ], 18 | "rules": { 19 | "no-empty": ["error", { "allowEmptyCatch": true }], 20 | "@typescript-eslint/no-this-alias": "off", 21 | "@typescript-eslint/no-explicit-any": "off", 22 | "@typescript-eslint/no-non-null-assertion": "off", 23 | "@typescript-eslint/no-unused-vars": "off" 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # R2Papi for Python 2 | 3 | This is PoC of the future autogenerated API bindings for r2pipe. 4 | 5 | ## Usage example 6 | 7 | We need to have a common API definition for all languages across r2pipe. 8 | 9 | ```python 10 | import r2pipe 11 | from r2papi import R2Api 12 | 13 | r = R2Api (r2pipe.open("/bin/ls")) 14 | if r.info().stripped: 15 | print "This binary is stripped" 16 | 17 | r.searchIn('io.sections.exec') 18 | r.analyzeCalls() 19 | print r.at('entry0').hexdump(16) 20 | print r.at('sym.imp.setenv').hexdump(16) 21 | 22 | print r.at('entry0').disasm(10) 23 | 24 | r.seek('entry0'); 25 | r.analyzeFunction() 26 | print r.disasmFunction() 27 | for fcn in r.functions(): 28 | print fcn.name 29 | 30 | r[0:10] 31 | # '\x7fELF\x02\x01\x01\x00\x00\x00' 32 | 33 | r[1] 34 | # 'E' 35 | 36 | r.quit() 37 | ``` 38 | -------------------------------------------------------------------------------- /rust/README.md: -------------------------------------------------------------------------------- 1 | r2papi.rs 2 | ========= 3 | 4 | The Rust Crate which acts a library containing structures and functions used to 5 | abstract over the underlying communication interface([r2pipe.rs](https://github.com/radare/r2pipe.rs)) and r2-specific commands for better 6 | scripting and usage. 7 | 8 | ## License 9 | 10 | Licensed under either of 11 | 12 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 13 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 14 | 15 | at your option. 16 | 17 | ### Contribution 18 | 19 | Unless you explicitly state otherwise, any contribution intentionally submitted 20 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 21 | -------------------------------------------------------------------------------- /python/Makefile: -------------------------------------------------------------------------------- 1 | all build: 2 | python -m pip install -e .[dev] 3 | python -m build 4 | 5 | test: clean 6 | python -m pip install -e .[dev] 7 | gcc -o test/test_exe test/test.c 8 | pytest --cov=r2papi --cov-report=html 9 | 10 | doc: 11 | rm -rf docs/_build 12 | python -m pip install -e .[docs] 13 | make -C docs html 14 | cd docs/_build \ 15 | && mv html r2papi-python-docs \ 16 | && zip -r ../../r2papi-python-docs.zip r2papi-python-docs 17 | 18 | clean: 19 | rm -rf build dist r2papi.egg-info ./htmlcov .coverage 20 | 21 | install: 22 | python -m pip install . 23 | 24 | pub publish: all 25 | # python3 -m twine upload -u __token__ --repository testpypi dist/* 26 | # python3 -m twine upload --repository https://pypi.python.org dist/* 27 | twine upload -u __token__ --repository-url https://upload.pypi.org/legacy/ --verbose dist/* 28 | 29 | twine: 30 | sudo pip install -U twine 31 | 32 | .PHONY: all test clean install twine 33 | 34 | -------------------------------------------------------------------------------- /python/docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /python/r2papi/flags.py: -------------------------------------------------------------------------------- 1 | from r2papi.base import R2Base, ResultArray 2 | 3 | 4 | class Flags(R2Base): 5 | def __init__(self, r2): 6 | super().__init__(r2) 7 | 8 | def all(self): 9 | return ResultArray(self._exec("fj", json=True)) 10 | 11 | def exists(self, name): 12 | self._exec("f?%s" % name) 13 | res = int(self._exec("??")) 14 | return res == 1 15 | 16 | def new(self, name, offset=None): 17 | if not offset: 18 | print(self._tmp_off) 19 | offset = self._tmp_off 20 | self._exec(f"f {name} {offset}") 21 | self._tmp_off = "" 22 | 23 | def delete(self, name="", offset=None): 24 | if offset is None: 25 | offset = self._tmp_off 26 | self._exec("f-%s%s" % (name, offset)) 27 | self._tmp_off = "" 28 | 29 | def rename(self, old, new=""): 30 | self._exec("fr %s %s %s" % (old, new, self._tmp_off)) 31 | self._tmp_off = "" 32 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "r2papi" 3 | version = "0.0.6" 4 | edition = "2021" 5 | authors = [ "pancake ", 6 | "sushant94 ", 7 | "chinmaydd "] 8 | 9 | description = "Library containing structures and functions used to abstract over the underlying communication interface(r2pipe.rs) and r2-specific commands for better scripting and usage." 10 | 11 | # Repository 12 | repository = "https://github.com/radare/radare2-r2papi" 13 | 14 | keywords = ["r2", "debugger", "hex" , "editor", "radare"] 15 | 16 | # Readme link 17 | readme = "README.md" 18 | 19 | # License information 20 | license = "MIT OR Apache-2.0" 21 | 22 | [features] 23 | default = [] 24 | 25 | [dependencies] 26 | libc = "*" 27 | clippy = { version = "*", optional = true } 28 | serde = "*" 29 | serde_json = "*" 30 | serde_derive = "*" 31 | 32 | [dependencies.r2pipe] 33 | git = "https://github.com/radareorg/r2pipe.rs" 34 | 35 | [[bin]] 36 | name = "api_example" 37 | path = "examples/api_example.rs" 38 | -------------------------------------------------------------------------------- /python/test/test_base.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | 4 | import pytest 5 | import r2pipe 6 | from r2papi.base import R2Base, Result 7 | 8 | 9 | @pytest.fixture 10 | def r(): 11 | r2 = r2pipe.open(f"{os.path.dirname(__file__)}/test_bin") 12 | return R2Base(r2) 13 | 14 | 15 | def test_exec(r): 16 | ret = r._exec("?e foo") 17 | assert ret == "foo" 18 | ret_json = r._exec("pxj 1", json=True) 19 | assert type(ret_json) is list 20 | assert len(ret_json) == 1 21 | 22 | 23 | def test_at(r): 24 | r.at(0x100) 25 | assert r._tmp_off == "@ 256" 26 | r.at("main") 27 | assert r._tmp_off == "@ main" 28 | 29 | 30 | def test_result(): 31 | o = {"bin": {"foo": "bar"}} 32 | r = Result(o) 33 | assert len(str(r).split("\n")) == 1 34 | o = {"foo": "bar"} 35 | r = Result(o) 36 | assert len(str(r).split("\n")) == 1 37 | 38 | 39 | def test_sym_to_addr(r): 40 | assert r.sym_to_addr("entry0") == 0x100000F20 41 | with pytest.raises(TypeError, match="Symbol type must be string"): 42 | r.sym_to_addr(0x100) 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | radare2-r2papi 2 | ============== 3 | 4 | Formerly known as r2pipe-api, but `papi` sounds better. 5 | 6 | [![Rust](https://github.com/radareorg/radare2-r2papi/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/radareorg/radare2-r2papi/actions/workflows/rust.yml) 7 | [![Python](https://github.com/radareorg/radare2-r2papi/actions/workflows/python.yml/badge.svg?branch=master)](https://github.com/radareorg/radare2-r2papi/actions/workflows/python.yml) 8 | [![Typescript](https://github.com/radareorg/radare2-r2papi/actions/workflows/typescript.yml/badge.svg?branch=master)](https://github.com/radareorg/radare2-r2papi/actions/workflows/typescript.yml) 9 | 10 | This repository contains a high-level API on top of r2pipe, abstracting 11 | the r2 commands with a human-friendly taste. 12 | 13 | As long as this API will require some discussion because it will serve 14 | as a way to redesign the R2 C Apis at some point, we must focus on 15 | practical use cases, flexibility, ortogonality and other ities. 16 | 17 | Feel free to open issues, send PRs with proposals. 18 | 19 | --pancake 20 | -------------------------------------------------------------------------------- /python/test/test_flags.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | 4 | import pytest 5 | import r2pipe 6 | from r2papi.flags import Flags 7 | 8 | 9 | @pytest.fixture 10 | def f(): 11 | r = r2pipe.open(f"{os.path.dirname(__file__)}/test_bin") 12 | flags = Flags(r) 13 | yield flags 14 | flags.r2.quit() 15 | 16 | 17 | def get_flag_from_offset(flags, offset): 18 | for f in flags: 19 | if f.addr == offset: 20 | return f 21 | raise ValueError("Flag not found") 22 | 23 | 24 | def test_new(f): 25 | f.at(0x100).delete() 26 | f.at(0x100).new("foo") 27 | nflag = get_flag_from_offset(f.all(), 0x100) 28 | assert nflag.name == "foo" 29 | assert nflag.addr == 0x100 30 | assert nflag.size == 1 31 | 32 | 33 | def test_delete(f): 34 | flag = f.all()[-1] 35 | f.delete(name=flag.name) 36 | assert not f.exists(flag.name) 37 | 38 | flag = f.all()[-1] 39 | f.at(flag.addr).delete() 40 | assert not f.exists(flag.name) 41 | 42 | 43 | def test_rename(f): 44 | flag = f.all()[-1] 45 | f.rename(flag.name, "foo") 46 | assert f.all()[-1].name == "foo" 47 | -------------------------------------------------------------------------------- /.github/workflows/typescript.yml: -------------------------------------------------------------------------------- 1 | name: TypeScript 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Install radare2 5.8.0 15 | run: | 16 | wget https://github.com/radareorg/radare2/releases/download/5.8.0/radare2_5.8.0_amd64.deb 17 | sudo dpkg -i radare2_5.8.0_amd64.deb 18 | #- name: Install radare2 from git 19 | # run: | 20 | # git clone --depth=1 https://github.com/radareorg/radare2 21 | # CFLAGS=-O1 radare2/sys/install.sh 22 | - name: Install TypeScript 23 | run: | 24 | npm i -g typescript 25 | tsc --version && make -C typescript node_modules 26 | - name: Build module 27 | run: | 28 | make -C typescript all 29 | cd typescript && npm run build 30 | - name: Run lint 31 | run: cd typescript && make lint 32 | - name: Run tests 33 | run: cd typescript && r2 -qi esil.ts -c 'af;pdq' /bin/ls 34 | - name: Make Documentation 35 | run: make -C typescript doc 36 | - uses: actions/upload-artifact@v4 37 | with: 38 | name: docs.zip 39 | path: typescript/docs.zip 40 | -------------------------------------------------------------------------------- /python/test/test_file.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | 4 | import pytest 5 | import r2pipe 6 | from r2papi.file import File 7 | from r2papi.iomap import IOMap 8 | 9 | 10 | @pytest.fixture 11 | def f(): 12 | r = r2pipe.open(f"{os.path.dirname(__file__)}/test_bin") 13 | file = File(r, 3) 14 | yield file 15 | file.r2.quit() 16 | 17 | 18 | def test_writable(f): 19 | assert f.writable is False 20 | f.writable = True 21 | assert f.writable is True 22 | 23 | 24 | def test_getFilename(f): 25 | assert f.getFilename() == f"{os.path.dirname(__file__)}/test_bin" 26 | assert f.filename == f"{os.path.dirname(__file__)}/test_bin" 27 | assert f.uri == f"{os.path.dirname(__file__)}/test_bin" 28 | 29 | 30 | def test_getSize(): 31 | # TODO 32 | pass 33 | 34 | 35 | def test_getFrom(f): 36 | assert f.getFrom() == 0 37 | assert f.offset == 0 38 | 39 | 40 | def test_iomaps(f): 41 | iomaps = f.iomaps 42 | firstmap = f._exec("omj", json=True)[0] 43 | assert type(iomaps) is list 44 | assert type(iomaps[0]) is IOMap 45 | assert iomaps[0].name == firstmap["name"] 46 | 47 | 48 | def test_close(f): 49 | f.close() 50 | assert f.uri is None 51 | assert f.fd is None 52 | -------------------------------------------------------------------------------- /python/docs/api.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | r2papi.R2Api 5 | ............ 6 | 7 | .. autoclass:: r2papi.R2Api 8 | :members: 9 | :show-inheritance: 10 | :inherited-members: 11 | 12 | r2papi.r2api.Function 13 | ..................... 14 | 15 | .. autoclass:: r2papi.r2api.Function 16 | :members: 17 | :show-inheritance: 18 | 19 | r2papi.file.File 20 | .................. 21 | 22 | .. autoclass:: r2papi.file.File 23 | :members: 24 | :show-inheritance: 25 | 26 | r2papi.print.Print 27 | .................. 28 | 29 | .. autoclass:: r2papi.print.Print 30 | :members: 31 | :show-inheritance: 32 | 33 | r2papi.search.Search 34 | .................... 35 | 36 | .. autoclass:: r2papi.search.Search 37 | :members: 38 | :show-inheritance: 39 | 40 | r2papi.debugger.Debugger 41 | ........................ 42 | 43 | .. autoclass:: r2papi.debugger.Debugger 44 | :members: 45 | :show-inheritance: 46 | 47 | r2papi.write.Write 48 | .................. 49 | 50 | .. autoclass:: r2papi.write.Write 51 | :members: 52 | :show-inheritance: 53 | 54 | r2papi.r2api.R2Base 55 | ................... 56 | 57 | Used for developers, if you're a user this class is not interesting for you. 58 | 59 | .. autoclass:: r2papi.r2api.R2Base 60 | :members: 61 | -------------------------------------------------------------------------------- /webdocs/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

API documentations

11 |

r2pipe

12 | 16 |

r2papi

17 | 24 |

r2api

25 | 29 |

Other

30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "r2papi" 7 | version = "0.1.5" 8 | description = "High level API on top of r2pipe" 9 | readme = "README.md" 10 | requires-python = ">=3.9" 11 | dependencies = ["r2pipe"] 12 | classifiers = [ 13 | "Development Status :: 5 - Production/Stable", 14 | "Natural Language :: English", 15 | "Intended Audience :: Developers", 16 | "Programming Language :: Python :: 3", 17 | "Programming Language :: Python :: 3.9", 18 | "Programming Language :: Python :: 3.10", 19 | "Programming Language :: Python :: 3.11", 20 | "Programming Language :: Python :: 3.12", 21 | "Programming Language :: Python :: 3.13", 22 | "Topic :: Software Development :: Libraries :: Python Modules", 23 | "Topic :: Text Processing :: General", 24 | "Topic :: Utilities", 25 | "Operating System :: OS Independent", 26 | ] 27 | 28 | [project.urls] 29 | Homepage = "https://www.radare.org" 30 | Source = "https://github.com/radareorg/radare2-r2papi/tree/master/python" 31 | 32 | [tool.setuptools] 33 | packages = ["r2papi"] 34 | 35 | [project.optional-dependencies] 36 | dev = ["r2pipe", "pytest-cov", "build>=1.2.1"] 37 | docs = ["sphinx", "sphinx-rtd-theme"] 38 | 39 | [tool.pytest.ini_options] 40 | minversion = "7.4.3" 41 | testpaths = ["test"] 42 | -------------------------------------------------------------------------------- /typescript/async/graph.ts: -------------------------------------------------------------------------------- 1 | declare let r2: any; 2 | 3 | class Graph { 4 | constructor() {} 5 | async reset() { 6 | await r2.cmd("ag-"); 7 | } 8 | 9 | /** 10 | * Add a node into the graph 11 | * 12 | * @param {string} title label of the node, this label must be unique to the graph 13 | * @param {string} body contents of the node 14 | */ 15 | async addNode(title: string, body: string) { 16 | await r2.cmd(`agn ${title} ${body}`); 17 | } 18 | 19 | /** 20 | * Add an edge linking two nodes referenced by the title 21 | * 22 | * @param {string} a source title node 23 | * @param {string} b destination title node 24 | */ 25 | async addEdge(a: string, b: string) { 26 | await r2.cmd(`age ${a} ${b}`); 27 | } 28 | 29 | /** 30 | * Get an ascii art representation of the graph as a string 31 | * 32 | * @returns {string} the computed graph 33 | */ 34 | async toString(): Promise { 35 | return r2.cmd("agg"); 36 | } 37 | } 38 | 39 | export async function main() { 40 | const g = new Graph(); 41 | 42 | await g.addNode("hello", "World"); 43 | await g.addNode("world", "Hello"); 44 | await g.addNode("patata", "Hello"); 45 | await g.addEdge("hello", "world"); 46 | await g.addEdge("hello", "patata"); 47 | await g.addEdge("world", "world"); 48 | 49 | console.log(g); 50 | } 51 | await main(); 52 | -------------------------------------------------------------------------------- /typescript/README.md: -------------------------------------------------------------------------------- 1 | # Typescript APIs for radare2 2 | 3 | The r2papi module implements a set of idiomatic and high-level APIs that are based on top of the minimalistic `r2pipe` API. 4 | 5 | The constructor of the `R2Papi` class takes an actual r2pipe instance, which only requires the `.cmd()` method to interface with radare2. 6 | 7 | The whole r2papi module is documented and integrates well with any editor with LSP support (like Visual Studio Code or NeoVIM) 8 | 9 | R2Papi relies on commands and functionalities that were implemented around r2-5.8.x, so take this into account and update your software! 10 | 11 | Note that TypeScript usually transpiles to Javascript, and as long as r2 ships it's own javascript runtime (based on QuickJS, which is ES6) it is possible to run the resulting r2papi scripts without any extra dependency in your system. 12 | 13 | But that does not mean that you need r2 to use r2papi, you can also use this module from NodeJS, Bun, Frida and even r2frida-compile! 14 | 15 | 16 | Install the Radare2 Skeleton tool and check the project templates in the [r2skel](https://github.com/radareorg/radare2-skel) repository to start writing your scripts and plugins on top of this API. 17 | 18 | ```bash 19 | $ r2pm -ci r2skel 20 | ``` 21 | 22 | ## Development 23 | 24 | The source code is contained in the `async/` directory. The code is then converted into sync using a sed oneliner. 25 | 26 | * Publishes `r2papi` (for sync api) and `r2papi-async` for the asynchronous 27 | * The whole sync api is autogenerated 28 | 29 | --pancake 30 | -------------------------------------------------------------------------------- /python/r2papi/iomap.py: -------------------------------------------------------------------------------- 1 | from r2papi.base import R2Base 2 | 3 | 4 | class IOMap(R2Base): 5 | def __init__(self, r2, mapNum): 6 | super().__init__(r2) 7 | self.num = mapNum 8 | 9 | def _mapObj(self): 10 | maps = self._exec("omj", json=True) 11 | for m in maps: 12 | if m["map"] == self.num: 13 | return m 14 | return None 15 | 16 | def setName(self, name): 17 | return self._exec("omni %s %s" % (self.num, name)) 18 | 19 | def setFlags(self, flags): 20 | return self._exec("omp %s %s" % (self.num, flags)) 21 | 22 | def relocateTo(self, addr): 23 | return self._exec("omv %s %s" % (self.num, addr)) 24 | 25 | def remove(self): 26 | return self._exec("om-%s" % self.num) 27 | 28 | def __getattr__(self, attr): 29 | obj = self._mapObj() 30 | # Using IOMap.form will cause a syntax error, so we use IOMap.offset 31 | attr = "from" if attr == "addr" else attr 32 | # Flags are now called "perm" 33 | attr = "perm" if attr == "flags" else attr 34 | 35 | if attr in obj.keys(): 36 | return obj[attr] 37 | return None 38 | 39 | def __setattr__(self, attr, value): 40 | if attr == "name": 41 | self.setName(value) 42 | elif attr == "flags": 43 | if type(value) == str and len(value) <= 3: 44 | self.setFlags(value) 45 | elif attr == "addr": 46 | self.relocateTo(value) 47 | else: 48 | self.__dict__[attr] = value 49 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Publish webdocs to GitHub Pages 2 | 3 | on: 4 | # Runs on pushes targeting the master branch 5 | push: 6 | branches: 7 | - master 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Build job 26 | build: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Install dependencies 31 | run: | 32 | sudo apt-get --assume-yes update 33 | sudo apt-get --assume-yes install sphinx 34 | sudo npm i -g typedoc 35 | - name: Setup Pages 36 | id: pages 37 | uses: actions/configure-pages@v5 38 | - name: Build webdocs 39 | run: make -C webdocs 40 | - name: Upload artifact 41 | uses: actions/upload-pages-artifact@v3 42 | with: 43 | path: ./webdocs/public 44 | 45 | # Deployment job 46 | deploy: 47 | environment: 48 | name: github-pages 49 | url: ${{ steps.deployment.outputs.page_url }} 50 | runs-on: ubuntu-latest 51 | needs: build 52 | steps: 53 | - name: Deploy to GitHub Pages 54 | id: deployment 55 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /python/test/test_write.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | 4 | import os 5 | 6 | import pytest 7 | import r2pipe 8 | from r2papi.write import Write 9 | 10 | 11 | @pytest.fixture 12 | def w(): 13 | r = r2pipe.open(f"{os.path.dirname(__file__)}/test_bin") 14 | r.cmd("e io.cache=true") 15 | write = Write(r) 16 | yield write 17 | write.r2.quit() 18 | 19 | 20 | def test_hex(w): 21 | w.at("entry0").hex("aa") 22 | assert w._exec("p8 1 @ entry0") == "aa" 23 | 24 | 25 | def test_string(w): 26 | w.at("entry0").string("gtfo") 27 | assert w._exec("ps 4 @ entry0") == "gtfo" 28 | w.at("0x100").string("AAA", final_nullbyte=True) 29 | assert w._exec("p8 4 @ 0x100") == "41414100" 30 | 31 | 32 | def test_bytes(w): 33 | w.at("entry0").bytes(b"test") 34 | assert w._exec("p8 4 @ entry0") == "74657374" 35 | w.at("entry0").bytes("test") 36 | assert w._exec("p8 4 @ entry0") == "74657374" 37 | with pytest.raises(TypeError): 38 | w.at("entry0").bytes(123) 39 | 40 | 41 | def test_random(w): 42 | w.at("entry0").random(4) 43 | assert len(w._exec("p8 4 @ entry0")) == 8 44 | 45 | 46 | def test_base64(w): 47 | w.at("entry0").base64("dGVzdA==", encode=False) 48 | assert w._exec("ps 4 @ entry0") == "test" 49 | w.at("entry0").base64("test", encode=True) 50 | assert w._exec("ps 8 @ entry0") == "dGVzdA==" 51 | 52 | 53 | def test_assemble(w): 54 | w.at("entry0").assembly("nop") 55 | assert w._exec("p8 1 @ entry0") == "90" 56 | w.at("entry0").assembly("nop; nop; nop") 57 | assert w._exec("p8 3 @ entry0") == "909090" 58 | 59 | 60 | def test_nop(w): 61 | w.at("entry0").nop() 62 | assert w._exec("p8 1 @ entry0") == "90" 63 | -------------------------------------------------------------------------------- /typescript/async/r2frida.ts: -------------------------------------------------------------------------------- 1 | import { R2Pipe, R2PipeAsync, newAsyncR2PipeFromSync } from "./r2pipe.js"; 2 | 3 | declare global { 4 | // eslint-disable-next-line no-var 5 | var r2: R2Pipe; 6 | } 7 | 8 | export class R2Frida { 9 | isAvailable: boolean; 10 | r2: R2PipeAsync; 11 | constructor(r2: R2PipeAsync) { 12 | this.r2 = r2; 13 | this.isAvailable = false; 14 | } 15 | async checkAvailability() { 16 | if (!this.isAvailable) { 17 | const output = await r2.cmd("o~frida://"); 18 | if (output.trim() === "") { 19 | throw new Error("There's no frida session available"); 20 | } 21 | } 22 | this.isAvailable = true; 23 | } 24 | 25 | async eval(expr: string): Promise { 26 | return r2.cmd(`"": ${expr}`); 27 | } 28 | async targetDetails(): Promise { 29 | return r2.cmdj(":ij") as TargetDetails; 30 | } 31 | } 32 | 33 | /* 34 | export async function main() { 35 | console.log("Hello from r2papi-r2frida"); 36 | const r2async = newAsyncR2PipeFromSync(r2); 37 | const r2f = new R2Frida(r2async); 38 | r2f.eval("console.log(123);"); 39 | const { pid, arch, cwd } = await r2f.targetDetails(); 40 | console.log(pid, arch, cwd); 41 | } 42 | main(); 43 | */ 44 | 45 | export interface TargetDetails { 46 | arch: string; 47 | bits: number; 48 | os: string; 49 | pid: number; 50 | uid: number; 51 | runtime: string; 52 | objc: boolean; 53 | swift: boolean; 54 | mainLoop: boolean; 55 | pageSize: number; 56 | pointerSize: number; 57 | codeSigningPolicy: string; 58 | isDebuggerAttached: boolean; 59 | cwd: string; 60 | bundle: string; 61 | exename: string; 62 | appname: string; 63 | appversion: string; 64 | appnumversion: string; 65 | minOS: string; 66 | modulename: string; 67 | modulebase: string; 68 | homedir: string; 69 | tmpdir: string; 70 | bundledir?: any; 71 | } 72 | -------------------------------------------------------------------------------- /python/r2papi/config.py: -------------------------------------------------------------------------------- 1 | from r2papi.base import R2Base 2 | 3 | 4 | class ConfigType(R2Base): 5 | def __init__(self, r2, var_type): 6 | super(ConfigType, self).__init__(r2) 7 | 8 | valid_vars = [] 9 | for v in self._exec("e??j %s" % var_type, json=True): 10 | valid_vars.append(v["name"].split(".")[1]) 11 | 12 | # Update directly from __dict__ to avoid __setattr__ and 13 | # __getattr__ 14 | self.__dict__["valid_vars"] = valid_vars 15 | self.__dict__["var_type"] = var_type 16 | 17 | def __getattr__(self, attr): 18 | if attr in self.valid_vars: 19 | ret = self._exec("e %s.%s" % (self.var_type, attr)) 20 | if ret.isdigit(): 21 | ret = int(ret) 22 | elif ret == "true": 23 | ret = True 24 | elif ret == "false": 25 | ret = False 26 | return ret 27 | else: 28 | raise AttributeError() 29 | 30 | def __setattr__(self, attr, val): 31 | # Dirty way to avoid infinite recursion 32 | if attr == "r2" or attr == "_tmp_off": 33 | self.__dict__[attr] = val 34 | elif attr in self.valid_vars: 35 | if attr == True: 36 | attr = "true" 37 | if attr == False: 38 | attr = "false" 39 | self._exec("e %s.%s = %s" % (self.var_type, attr, val)) 40 | else: 41 | raise AttributeError() 42 | 43 | 44 | class Config(R2Base): 45 | def __init__(self, r2): 46 | super(Config, self).__init__(r2) 47 | 48 | # anal, scr, asm, io... 49 | self.vars_types = [] 50 | v = self._exec("e??j", json=True) 51 | for var in v: 52 | var_type = var["name"].split(".")[0] 53 | if var_type not in self.vars_types: 54 | self.vars_types.append(var_type) 55 | 56 | def __getattr__(self, attr): 57 | if attr in self.vars_types: 58 | return ConfigType(self.r2, attr) 59 | -------------------------------------------------------------------------------- /playground/asan/asan.ts: -------------------------------------------------------------------------------- 1 | import { r2, Base64, R2Pipe, R2Papi } from "r2papi"; 2 | 3 | interface Entry { 4 | address: string; 5 | fileLine: string; 6 | fcnName: string; 7 | action: string; 8 | }; 9 | 10 | function parseAsan(text: string) { 11 | const lines = text.split('\n'); 12 | let btid = ""; 13 | let r2s = ""; 14 | var papi = new R2Papi(r2); 15 | 16 | let nodes = new Map(); 17 | let edges = ""; 18 | let prev = undefined; 19 | for (let line of lines) { 20 | line.trim(); 21 | line = line.replace(/^ */, ''); 22 | if (line.startsWith("READ of")) { 23 | btid = "READ"; 24 | } else if (line.startsWith("freed by thread")) { 25 | btid = "FREE"; 26 | } else if (line.startsWith("previously allocated")) { 27 | btid = "ALLOC"; 28 | } else if (line[0] === '#') { 29 | const words = line.split(' '); 30 | const entry: Entry = { 31 | address: words[1] || '', 32 | fcnName: words[3] || '', 33 | fileLine: words[4] || '', 34 | action: btid, 35 | }; 36 | if (entry.fcnName.indexOf('<') === -1) { 37 | const line = entry.action + ' ' + entry.fileLine; 38 | const uses = nodes.get(entry.fcnName) ?? []; 39 | if (uses.indexOf(line) === -1) { 40 | uses.push(line); 41 | } 42 | nodes.set(entry.fcnName, uses); 43 | if (prev !== undefined) { 44 | edges += "age " + entry.fcnName + " " + prev.fcnName + "\n"; 45 | } 46 | } 47 | prev = entry; 48 | } 49 | } 50 | for (let entry of nodes.entries ()) { 51 | const body = Base64.encode(entry[1].join("\n")); 52 | console.log(body); 53 | r2s += "agn " + entry[0] + " base64:" + body + "\n"; 54 | } 55 | r2s += edges; 56 | return r2s; 57 | } 58 | 59 | function main(r2:R2Pipe) { 60 | const crash_txt = r2.cmd("cat crash.txt"); 61 | const r2script = parseAsan(crash_txt); 62 | // console.log(r2script); 63 | for (let line of r2script.split(/\n/g)) { 64 | r2.cmd(line); 65 | } 66 | // graphviz dot 67 | console.log(r2.cmd("aggd")); 68 | // ascii art graph 69 | console.log(r2.cmd("agg")); 70 | } 71 | 72 | main(r2); 73 | -------------------------------------------------------------------------------- /typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r2papi", 3 | "version": "0.4.9", 4 | "description": "r2api on top of r2pipe for typescript and js", 5 | "author": "pancake@nopcode.org", 6 | "homepage": "http://www.radare.org", 7 | "bugs": { 8 | "url": "https://github.com/radareorg/radare2-r2pipe/issues" 9 | }, 10 | "devDependencies": { 11 | "@typescript-eslint/eslint-plugin": "^6.19.1", 12 | "@typescript-eslint/parser": "^6.19.1", 13 | "eslint": "^8.34.0", 14 | "r2pipe": "^2.8.6", 15 | "json2ts": "^0.0.7", 16 | "prettier": "^3.2.4", 17 | "typedoc": "^0.25.7", 18 | "typescript": "^5.3.3" 19 | }, 20 | "scripts": { 21 | "build": "cd sync && tsc -m node16 --target es2020 --declaration r2pipe.ts base64.ts ai.ts r2papi.ts esil.ts shell.ts", 22 | "abuild": "cd async && tsc -m node16 --target es2020 --declaration r2pipe.ts base64.ts ai.ts r2papi.ts esil.ts shell.ts", 23 | "test": "echo \"Error: no test specified\" && exit 1", 24 | "adoc": "cd async && typedoc r2pipe.ts ai.ts r2papi.ts base64.ts shell.ts esil.ts", 25 | "sdoc": "cd sync && typedoc r2pipe.ts ai.ts r2papi.ts base64.ts shell.ts esil.ts" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/radareorg/radare2-r2papi.git" 30 | }, 31 | "keywords": [ 32 | "radare", 33 | "radare2", 34 | "r2", 35 | "reversing", 36 | "disassembler", 37 | "hexadecimal", 38 | "editor", 39 | "exploit", 40 | "exploiting" 41 | ], 42 | "files": [ 43 | "README.md", 44 | "ai.js", 45 | "ai.ts", 46 | "ai.d.ts", 47 | "index.js", 48 | "index.ts", 49 | "index.d.js", 50 | "r2frida.js", 51 | "r2frida.ts", 52 | "r2frida.d.js", 53 | "base64.js", 54 | "base64.ts", 55 | "base64.d.js", 56 | "r2pipe.js", 57 | "r2pipe.ts", 58 | "r2pipe.d.ts", 59 | "esil.js", 60 | "esil.ts", 61 | "esil.d.ts", 62 | "shell.js", 63 | "shell.ts", 64 | "shell.d.ts", 65 | "r2papi.js", 66 | "r2papi.ts", 67 | "r2papi.d.ts" 68 | ], 69 | "license": "MIT" 70 | } 71 | -------------------------------------------------------------------------------- /python/r2papi/file.py: -------------------------------------------------------------------------------- 1 | from r2papi.base import R2Base 2 | from r2papi.iomap import IOMap 3 | 4 | 5 | class File(R2Base): 6 | def __init__(self, r2, fd): 7 | super(File, self).__init__(r2) 8 | 9 | self.fd = fd 10 | 11 | if not self.fd: 12 | raise IOError("File not found") 13 | 14 | def _getCurrObject(self): 15 | files = self._exec("oj", json=True) 16 | for f in files: 17 | if f["fd"] == self.fd: 18 | return f 19 | 20 | return None 21 | 22 | @property 23 | def writable(self): 24 | obj = self._getCurrObject() 25 | return obj["writable"] if obj else None 26 | 27 | @writable.setter 28 | def writable(self, value): 29 | if type(value) == bool: 30 | if value: 31 | # TODO: this reopens in rw-, so if the file was loaded with 32 | # r-x we lose the x 33 | self._exec("oo+ %s" % self.fd) 34 | else: 35 | # TODO: How to set -w ? 36 | pass 37 | 38 | def getFilename(self): 39 | obj = self._getCurrObject() 40 | return obj["uri"] if obj else None 41 | 42 | def getSize(self): 43 | obj = self._getCurrObject() 44 | return obj["size"] if obj else None 45 | 46 | def getIOMaps(self): 47 | maps = self._exec("omj", json=True) 48 | ret_maps = [] 49 | curr_fd = self.fd 50 | for m in maps: 51 | if m["fd"] == curr_fd: 52 | ret_maps.append(IOMap(self.r2, m["map"])) 53 | 54 | return ret_maps 55 | 56 | def getFrom(self): 57 | obj = self._getCurrObject() 58 | return obj["from"] if obj else None 59 | 60 | def __getattr__(self, attr): 61 | if attr == "uri" or attr == "filename": 62 | return self.getFilename() 63 | elif attr == "size": 64 | return self.getSize() 65 | # Offset instead of from because from is a reserved word 66 | elif attr == "offset": 67 | return self.getFrom() 68 | elif attr == "iomaps" or attr == "IOmaps": 69 | return self.getIOMaps() 70 | 71 | def close(self): 72 | self._exec("o-%s" % self.fd) 73 | self.fd = None 74 | -------------------------------------------------------------------------------- /rust/examples/analysis_example.rs: -------------------------------------------------------------------------------- 1 | //! Example to showcase use of different, fine-grained analysis in radare. 2 | 3 | extern crate r2papi; 4 | extern crate r2pipe; 5 | extern crate serde_json; 6 | 7 | use r2papi::api_trait::R2PApi; 8 | use r2pipe::r2::R2; 9 | 10 | fn main() { 11 | { 12 | let path = "/bin/ls"; 13 | let mut r2 = R2::new(Some(path)).expect("Failed to spawn r2"); 14 | r2.analyze_all().unwrap(); 15 | println!("{:#?}", r2.fn_list()); 16 | } 17 | { 18 | let path = "/bin/ls"; 19 | let mut r2 = R2::new(Some(path)).expect("Failed to spawn r2"); 20 | r2.analyze_and_autoname().unwrap(); 21 | println!("{:#?}", r2.fn_list()); 22 | } 23 | { 24 | let path = "/bin/ls"; 25 | let mut r2 = R2::new(Some(path)).expect("Failed to spawn r2"); 26 | r2.analyze_function_calls().unwrap(); 27 | println!("{:#?}", r2.fn_list()); 28 | } 29 | { 30 | let path = "/bin/ls"; 31 | let mut r2 = R2::new(Some(path)).expect("Failed to spawn r2"); 32 | r2.analyze_data_references().unwrap(); 33 | println!("{:#?}", r2.fn_list()); 34 | } 35 | { 36 | let path = "/bin/ls"; 37 | let mut r2 = R2::new(Some(path)).expect("Failed to spawn r2"); 38 | r2.analyze_references_esil().unwrap(); 39 | println!("{:#?}", r2.fn_list()); 40 | } 41 | { 42 | let path = "/bin/ls"; 43 | let mut r2 = R2::new(Some(path)).expect("Failed to spawn r2"); 44 | r2.analyze_function_preludes().unwrap(); 45 | println!("{:#?}", r2.fn_list()); 46 | } 47 | { 48 | let path = "/bin/ls"; 49 | let mut r2 = R2::new(Some(path)).expect("Failed to spawn r2"); 50 | r2.analyze_function_references().unwrap(); 51 | println!("{:#?}", r2.fn_list()); 52 | } 53 | { 54 | let path = "/bin/ls"; 55 | let mut r2 = R2::new(Some(path)).expect("Failed to spawn r2"); 56 | r2.analyze_symbols().unwrap(); 57 | println!("{:#?}", r2.fn_list()); 58 | } 59 | { 60 | let path = "/bin/ls"; 61 | let mut r2 = R2::new(Some(path)).expect("Failed to spawn r2"); 62 | r2.analyze_consecutive_functions().unwrap(); 63 | println!("{:#?}", r2.fn_list()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /typescript/async/elang.ts: -------------------------------------------------------------------------------- 1 | import { R2PipeAsync } from "./index"; 2 | 3 | // esil high level language - functional programming 4 | // 5 | 6 | export class EsilLang { 7 | [vars: string]: any; 8 | constructor() { 9 | this.vars = {}; 10 | } 11 | set(obj: any, val: any) { 12 | this.vars[obj] = val; 13 | } 14 | fun(name: string, code: any[]) { 15 | this.vars[name] = code; 16 | } 17 | get(varname: string): any { 18 | if (varname in Object.keys(this.vars)) { 19 | return this.vars[varname]; 20 | } 21 | this.vars[varname] = 0; 22 | return 0; 23 | } 24 | println(...args: any) { 25 | console.log(...args); 26 | } 27 | eval(code: string) {} 28 | } 29 | 30 | // basic elements: array, string, 31 | // console.log("Hello Lang"); 32 | const el = new EsilLang(); 33 | const code = `[ 34 | ] 35 | `; 36 | 37 | el.eval(code); 38 | console.log("Hello `test()` World"); 39 | 40 | /* 41 | 96,sp,-=,x28,sp,=[8],x27,sp,8,+,=[8] 42 | (96,sp,-=),(x28,sp,=[8]),(x27,(sp,8,+),=[8]) 43 | 44 | (-=,sp,96),(=[8],sp,x28),(=[8],(+,8,sp),x27) 45 | (sp,-=, 96),(sp, =[8], x28),((+,8,sp), =[8], x27) 46 | 47 | (96, sp, -=), 48 | (x28, sp, =[8]), 49 | (x27, (sp, 8, +), =[8]) 50 | 51 | set(poke, { 52 | args("nan", addr, value, size) 53 | r2(str('wx', size, ' ', value, ' @ ', addr)) 54 | for (=(i,0), <= (i, 10), { 55 | println("Hello World") 56 | = (i, +(i, 1)) 57 | }) 58 | }) 59 | 60 | set("main", "println(123)") 61 | main() 62 | 63 | set(sp, reg("sp")) 64 | label("bb4000") 65 | set(sp, -(sp, 96)) 66 | poke(sp, x28, 8) // addr value size 67 | poke(+(sp, 8), x27, 8) 68 | if (eq (sp, bp), [ 69 | goto("bb4000") 70 | ] 71 | set(pc, lr) 72 | 73 | 74 | el.fun("main", [ 75 | set("test", """ 76 | [ 77 | set("n", 0), 78 | label("repeat"), 79 | if (eq (arg("n"), 0), 80 | [ret(1)] 81 | ), 82 | println("Hello World"), 83 | goto("repeat") 84 | ret(0) 85 | ), 86 | set("res", r2("x 32")), 87 | // $res = r2("x 32") 88 | if (eq (res, 0), [ 89 | // if body when res == 0 90 | ] 91 | """) 92 | test(), 93 | "test"(), 94 | 123(), 95 | call("test"), // test(), 96 | 'set("name", "John")', // '0x80480,x0,:=' 97 | 'println("Hello", get("name"))' // 98 | ]); 99 | 100 | */ 101 | -------------------------------------------------------------------------------- /typescript/tools/asyncify.py: -------------------------------------------------------------------------------- 1 | import re 2 | from os import listdir 3 | 4 | 5 | def slurp(f): 6 | fd = open(f, errors="ignore") 7 | data = fd.read() 8 | fd.close() 9 | return str(data) 10 | 11 | 12 | def dump(f, x): 13 | fd = open(f, "w") 14 | fd.write(x) 15 | fd.close() 16 | 17 | 18 | def asyncify_function(lines): 19 | def asyncify_line(line): 20 | hascall = False 21 | if "this.r2.cmd" in line or "this.r2.call" in line: 22 | hascall = True 23 | if "return" not in line: 24 | line = line.replace("this.r2.c", "await this.r2.c") 25 | elif "r2.cmd" in line or "r2.call" in line: 26 | hascall = True 27 | if "return" not in line: 28 | line = line.replace("r2.c", "await r2.c") 29 | if hascall and ".trim" in line: 30 | line += " // XXX" 31 | return line 32 | 33 | def is_async_function(lines): 34 | allcode = "\n".join(lines) 35 | return "r2.cmd" in allcode or "r2.call" in allcode or "api.call" in allcode 36 | 37 | if is_async_function(lines): 38 | code = [] 39 | code.append(lines[0].replace("\t", "\tasync ")) 40 | for line in lines[1:]: 41 | code.append(asyncify_line(line)) 42 | return "\n".join(code) 43 | return "\n".join(lines) 44 | 45 | 46 | def asyncify(s): 47 | f = f"async/{s}" 48 | data = slurp(f) 49 | async_method = False 50 | lines = [] 51 | 52 | def is_function_start(line): 53 | if re.match(r"^\t[a-z]", line) and ("constructor" not in line): 54 | return line.endswith("{") or "{ // " in line 55 | return False 56 | 57 | def is_function_end(line): 58 | return line == "\t}" 59 | 60 | function = [] 61 | in_function = False 62 | for line in data.split("\n"): 63 | if in_function: 64 | if is_function_end(line): 65 | function.append(line) 66 | afunc = asyncify_function(function) 67 | lines.append(afunc) 68 | in_function = False 69 | function = [] 70 | else: 71 | function.append(line) 72 | elif is_function_start(line): 73 | in_function = True 74 | function.append(line) 75 | else: 76 | lines.append(line) 77 | dump(f, "\n".join(lines)) 78 | 79 | 80 | for file in listdir("async"): 81 | asyncify(file) 82 | -------------------------------------------------------------------------------- /rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Add example usage here. 2 | 3 | #![cfg_attr(feature = "clippy", feature(plugin))] 4 | #![cfg_attr(feature = "clippy", plugin(clippy))] 5 | 6 | extern crate libc; 7 | extern crate r2pipe; 8 | extern crate serde; 9 | extern crate serde_json; 10 | #[macro_use] 11 | extern crate serde_derive; 12 | 13 | #[macro_use] 14 | pub mod api; 15 | pub mod api_trait; 16 | pub mod structs; 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | //use super::*; 21 | //use api_trait::R2PApi; 22 | //use r2pipe::R2; 23 | 24 | #[test] 25 | fn lib_tests() { 26 | /* 27 | let mut r2:R2 = api::R2::new(Some("-")).unwrap(); 28 | r2.init().unwrap(); 29 | r2.malloc(1024).unwrap(); 30 | r2.set_arch("arm").unwrap(); 31 | r2.set_bits(64).unwrap(); 32 | r2.write_bytes(Some(0), &[0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48]) 33 | .unwrap(); 34 | assert!(r2.read_u8(Some(0)).unwrap() == 0x41); 35 | assert!(r2.read_u16_le(Some(0)).unwrap() == 0x4241); 36 | assert!(r2.read_u16_be(Some(0)).unwrap() == 0x4142); 37 | assert!(r2.read_u32_le(Some(0)).unwrap() == 0x44434241); 38 | assert!(r2.read_u32_be(Some(0)).unwrap() == 0x41424344); 39 | assert!(r2.read_u64_le(Some(0)).unwrap() == 0x48474645_44434241); 40 | r2.seek(Some(0)).unwrap(); 41 | assert!(r2.read_u64_be(None).unwrap() == 0x41424344_45464748); 42 | 43 | r2.write_u8(None, 0x30).unwrap(); 44 | r2.write_u16_le(None, 0x3130).unwrap(); 45 | r2.write_u16_be(None, 0x3031).unwrap(); 46 | r2.write_u32_le(None, 0x33323130).unwrap(); 47 | r2.write_u32_be(None, 0x30313233).unwrap(); 48 | r2.write_u64_le(None, 0x37363534_33323130).unwrap(); 49 | r2.write_u64_be(None, 0x30313233_34353637).unwrap(); 50 | 51 | let bytes = r2.read_bytes(8, Some(0)).unwrap(); 52 | assert!(bytes[0] == 0x30 && bytes[7] == 0x37); 53 | assert!(r2.seek(None).unwrap() == 0); 54 | assert!(r2.size().unwrap() == 1024); 55 | */ 56 | 57 | /* 58 | r2.analyze().unwrap(); 59 | assert!(r2.arch().unwrap().bins[0].bits == Some(64)); 60 | assert!(r2.imports().unwrap().len() > 50); 61 | assert!(r2.exports().unwrap().len() > 10); 62 | let afl = r2.fn_list().unwrap(); 63 | assert!(afl.len() > 200); 64 | assert!(afl[0].name == Some("entry0".to_string())); 65 | assert!(1 == 1);*/ 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /python/docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to r2papi's documentation! 2 | ====================================== 3 | 4 | High level API on top of r2pipe. An intuitive and easy way to use and 5 | script radare2. 6 | 7 | +---------+----------------------------------------------------+ 8 | | Author | Quim | 9 | +---------+----------------------------------------------------+ 10 | | License | ??? | 11 | +---------+----------------------------------------------------+ 12 | | Version | ??? | 13 | +---------+----------------------------------------------------+ 14 | 15 | .. todo:: 16 | 17 | What license to use? 18 | 19 | .. todo:: 20 | 21 | How versions will be managed 22 | 23 | .. todo:: 24 | 25 | I'm a terrible writer, feel free to edit wathever you want 26 | 27 | Exposes `radare2`_ functionality through a high level python API. It's a good 28 | way for new radare users to see the capabilities that it have, without having 29 | to deal with commands and other CLI stuff that sometimes scares people. 30 | 31 | The code is hosted in `github`_. 32 | 33 | .. _radare2: https://github.com/radare/radare2 34 | .. _github: https://github.com/radareorg/radare2-r2papi 35 | 36 | Install 37 | ======= 38 | 39 | The installation process is really simple, it's not in PyPI yet, but it'll be at 40 | some point. 41 | 42 | Create a new virtual environment **(optional)**: 43 | 44 | .. code-block:: shell 45 | 46 | $ python3 -m venv venv 47 | $ source venv/bin/activate 48 | (venv) $ pip install --upgrade pip 49 | 50 | Install ``radare2`` from git: 51 | 52 | .. code-block:: shell 53 | 54 | $ git clone https://github.com/radare/radare2 55 | $ cd radare2 56 | $ ./sys/install.sh 57 | 58 | 3. Clone the git repository and install the library with ``pip``: 59 | 60 | .. code-block:: shell 61 | 62 | $ git clone https://github.com/radareorg/radare2-r2papi 63 | $ cd radare2-r2papi/python 64 | $ pip install . 65 | 66 | To test if it was intalled, the following code must be executed without errors: 67 | 68 | .. code-block:: python 69 | 70 | >>> from r2api import R2Api 71 | >>> R2Api('-') 72 | 73 | 74 | .. toctree:: 75 | :maxdepth: 3 76 | :caption: Contents: 77 | 78 | development 79 | api 80 | 81 | 82 | 83 | Indices and tables 84 | ================== 85 | 86 | * :ref:`genindex` 87 | * :ref:`modindex` 88 | * :ref:`search` 89 | -------------------------------------------------------------------------------- /python/r2papi/write.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import codecs 3 | 4 | from r2papi.base import R2Base 5 | 6 | 7 | class Write(R2Base): 8 | def __init__(self, r2): 9 | super().__init__(r2) 10 | 11 | def reopen(self, mode = ""): 12 | """ 13 | Reopen the file in write/cache mode. 14 | If mode is "ow", it will overwrite the file i.e, reopen it in 'rw' mode. 15 | Default is 'cache' mode. 16 | """ 17 | cmd = "e io.cache=1" 18 | if mode == "ow": # overwrite 19 | cmd = "oo+" 20 | super()._exec(cmd) 21 | 22 | def bytes(self, buf): 23 | if type(buf) == str: 24 | # Just use this if you want to write utf-8 data, if not, write 25 | # bytes object. 26 | res = self._exec( 27 | "wx %s%s|" 28 | % ( 29 | binascii.hexlify(buf.encode("utf-8")).decode("ascii"), 30 | self._tmp_off, 31 | ) 32 | ) 33 | elif type(buf) == bytes: 34 | res = self._exec( 35 | "wx %s%s|" 36 | % (codecs.encode(buf, "hex_codec").decode("ascii"), self._tmp_off) 37 | ) 38 | else: 39 | raise TypeError("You must write an string or bytes") 40 | 41 | self._tmp_off = "" 42 | return res 43 | 44 | def hex(self, hex_string): 45 | ret = self._exec("wx %s%s" % (hex_string, self._tmp_off)) 46 | self._tmp_off = "" 47 | return ret 48 | 49 | def string(self, string, final_nullbyte=False): 50 | if final_nullbyte: 51 | string = string + "\\x00" 52 | ret = self._exec('"w %s" %s' % (string, self._tmp_off)) 53 | self._tmp_off = "" 54 | return ret 55 | 56 | def base64(self, string, encode=True): 57 | if encode: 58 | ret = self._exec("w6e %s %s" % (string, self._tmp_off)) 59 | else: 60 | ret = self._exec("w6d %s %s" % (string, self._tmp_off)) 61 | self._tmp_off = "" 62 | return ret 63 | 64 | def assembly(self, asm_str): 65 | ret = self._exec('"wa %s" %s' % (asm_str, self._tmp_off)) 66 | self._tmp_off = "" 67 | return ret 68 | 69 | def random(self, size=0): 70 | ret = self._exec("wr %s%s" % (size, self._tmp_off)) 71 | self._tmp_off = "" 72 | return ret 73 | 74 | def nop(self): 75 | self._exec("wao nop %s" % (self._tmp_off)) 76 | self._tmp_off = "" 77 | -------------------------------------------------------------------------------- /python/test/test_print.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | 4 | import os 5 | import sys 6 | 7 | import pytest 8 | import r2pipe 9 | from r2papi.print import Print 10 | 11 | PYTHON_VERSION = sys.version_info[0] 12 | 13 | 14 | @pytest.fixture 15 | def p(): 16 | r = r2pipe.open(f"{os.path.dirname(__file__)}/test_bin") 17 | p = Print(r) 18 | yield p 19 | p.r2.quit() 20 | 21 | 22 | def test_byte(p): 23 | assert p.at("entry0").byte() == 0x55 24 | 25 | 26 | def test_bytes(p): 27 | assert p.at("entry0").bytes(5, asList=True) == [85, 72, 137, 229, 72] 28 | if PYTHON_VERSION == 3: 29 | assert p.at("entry0").bytes(5) == b"UH\x89\xe5H" 30 | else: 31 | assert p.at("entry0").bytes(5) == "UH\x89\xe5H" 32 | 33 | 34 | def test_string(p): 35 | p._exec("e io.cache=1") 36 | p._exec("wx 4141410000 @ 0x100") 37 | assert p.at(0x100).string() == "AAA" 38 | 39 | 40 | def test_bits(p): 41 | p._exec("e io.cache=1") 42 | p._exec("wx 4141410000 @ 0x100") 43 | assert p.at(0x100).bits(8) == "01000001" 44 | 45 | 46 | def test_disassemble(p): 47 | assert len(p.at("entry0").disassemble(5)) == 5 48 | assert p.at("entry0").disassemble(5)[0].esil == "rbp,8,rsp,-,=[8],8,rsp,-=" 49 | 50 | 51 | def test_disasmBytes(p): 52 | assert len(p.at("entry0").disasmBytes(2)) == 2 53 | assert p.at("entry0").disasmBytes(1)[0].type == "rpush" 54 | assert p.at("entry0").disasmBytes(2)[1].disasm == "invalid" 55 | 56 | 57 | def test_hexdump(p): 58 | assert p.at("entry0").hexdump(2) == "5548" 59 | 60 | 61 | def test_debruijn(p): 62 | assert ( 63 | p.debruijn() 64 | == "4141414241414341414441414541414641414741414841414941414a41414b41414c41414d41414e41414f41415041415141415241415341415441415541415641415741415841415941415a41416141416241416341416441416541416641416741416841416941416a41416b41416c41416d41416e41416f41417041417141417241417341417441417541417641417741417841417941417a41413141413241413341413441413541413641413741413841413941413041424241424341424441424541424641424741424841424941424a41424b41424c41424d41424e41424f414250414251414252414253414254414255414256414257414258414259" 65 | ) 66 | assert p.debruijn(16) == "41414142414143414144414145414146" 67 | 68 | # Make sure it clear temporary offset 69 | p.at("foo").debruijn() 70 | assert p._tmp_off == "" 71 | 72 | 73 | def test_hash(p): 74 | assert p.at("entry0").hash("md5", size=1) == "4c614360da93c0a041b22e537de151eb" 75 | with pytest.raises(ValueError): 76 | p.at("entry0").hash("foo") 77 | -------------------------------------------------------------------------------- /python/test/test_debugger.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | 4 | import r2pipe 5 | import pytest 6 | 7 | from r2papi.debugger import Debugger 8 | 9 | 10 | @pytest.fixture 11 | def d(): 12 | r = r2pipe.open(f"{os.path.dirname(__file__)}/test_exe") 13 | dbg = Debugger(r) 14 | yield dbg 15 | dbg.r2.quit() 16 | 17 | 18 | def test_start_and_cont(d): 19 | assert d.start() is None 20 | d.untilRet().cont() 21 | assert d._tmp_off == "" 22 | 23 | 24 | def test_until_call_and_ret(d): 25 | d.untilCall() 26 | d.cont() 27 | assert not d._untilCall 28 | d.untilRet() 29 | d.cont() 30 | assert not d._untilRet 31 | d.untilUnknownCall() 32 | d.cont() 33 | assert not d._untilUnknownCall 34 | 35 | 36 | def test_breakpoint_set_and_delete(d): 37 | d.setBreakpoint(addr=0x1000) 38 | bps = d.listBreakpoints() 39 | assert any(bp.addr == 0x1000 for bp in bps) 40 | d.deleteBreakpoint(addr=0x1000) 41 | bps = d.listBreakpoints() 42 | assert not any(bp.addr == 0x1000 for bp in bps) 43 | 44 | 45 | def test_breakpoint_using_tmp_off(d): 46 | d.at("main").setBreakpoint() 47 | bps = d.listBreakpoints() 48 | assert len(bps) == 1 49 | assert bps[0].enabled is True 50 | d.deleteBreakpoint() 51 | 52 | 53 | def test_read_register(d): 54 | d.start() 55 | reg_value = d.cpu.readRegister("rsp") 56 | assert reg_value is not None 57 | reg_value = d.cpu.readRegister("invalid_reg") 58 | assert reg_value is None 59 | 60 | 61 | def test_read_register_using_getattr(d): 62 | d.start() 63 | reg_value = d.cpu.rsp 64 | assert reg_value is not None 65 | reg_value = d.cpu.invalid_reg 66 | assert reg_value is None 67 | 68 | 69 | def test_write_register(d): 70 | d.start() 71 | d.cpu.writeRegister("rsp", 0x12345678) 72 | reg_value = d.cpu.readRegister("rsp") 73 | assert reg_value == 0x12345678 74 | 75 | 76 | def test_write_register_using_setattr(d): 77 | d.start() 78 | d.cpu.rsp = 0x12345678 79 | reg_value = d.cpu.readRegister("rsp") 80 | assert reg_value == 0x12345678 81 | 82 | 83 | def test_cpu_str(d): 84 | d.start() 85 | reg_str = str(d.cpu) 86 | assert isinstance(reg_str, str) 87 | assert len(reg_str) > 0 88 | assert any(reg in reg_str for reg in d.cpu.registers().keys()) 89 | 90 | 91 | def test_cpu_str_format(d): 92 | d.start() 93 | reg_str = str(d.cpu) 94 | lines = reg_str.split("\n") 95 | for line in lines: 96 | if line.strip(): 97 | parts = line.split() 98 | assert len(parts) == 2, f"Line '{line}' doesn't have exactly two parts" 99 | reg_name = parts[0] 100 | reg_value = parts[1] 101 | assert ( 102 | reg_name in d.cpu.registers().keys() 103 | ), f"Unknown register '{reg_name}'" 104 | assert reg_value.startswith( 105 | "0x" 106 | ), f"Value '{reg_value}' doesn't start with '0x'" 107 | -------------------------------------------------------------------------------- /python/r2papi/base.py: -------------------------------------------------------------------------------- 1 | def ResultArray(o): 2 | """ 3 | Convert an iterable of raw JSON objects into a list of ``Result`` instances. 4 | """ 5 | results: list[Result] = [] 6 | if o: 7 | for a in o: 8 | results.append(Result(a)) 9 | return results 10 | 11 | 12 | class Result: 13 | """Encapsulate a JSON response from radare2. 14 | 15 | The object's attributes mirror the keys of the provided dict, and a 16 | private ``_dict`` attribute holds the raw mapping for easy introspection. 17 | """ 18 | 19 | def __init__(self, o: dict): 20 | self._dict: dict = {} 21 | # Prefer the ``bin`` sub‑dictionary if present 22 | try: 23 | bin_dict = o["bin"] 24 | except KeyError: 25 | bin_dict = o 26 | 27 | for key, value in bin_dict.items(): 28 | setattr(self, key, value) 29 | self._dict[key] = value 30 | 31 | def __getitem__(self, key): 32 | return self._dict[key] 33 | 34 | def __contains__(self, key): 35 | return key in self._dict 36 | 37 | def pprint(self) -> str: 38 | """Pretty‑print the stored dictionary in a column‑aligned format.""" 39 | lines = [f"{k:<10}{v}" for k, v in self._dict.items()] 40 | # Join without trailing newline 41 | return "\n".join(lines) 42 | 43 | def __str__(self) -> str: 44 | return self.pprint() 45 | 46 | 47 | class R2Base: 48 | """Base class providing common radare2‑pipe utilities.""" 49 | 50 | def __init__(self, r2): 51 | """ 52 | Args: 53 | r2 (r2pipe.OpenBase): An opened r2pipe instance. 54 | """ 55 | self.r2 = r2 56 | self._tmp_off = "" 57 | 58 | def _exec(self, cmd: str, json: bool = False, rstrip: bool = True): 59 | """Execute a radare2 command. 60 | 61 | Args: 62 | cmd: Command string. 63 | json: If ``True`` parse output as JSON. 64 | rstrip: Strip trailing whitespace from non‑JSON output. 65 | 66 | Returns: 67 | Either a Python object (when ``json=True``) or a stripped string. 68 | """ 69 | if json: 70 | return self.r2.cmdj(cmd) 71 | res = self.r2.cmd(cmd) 72 | return res.rstrip() if rstrip else res 73 | 74 | def curr_seek_addr(self) -> int: 75 | """Return the current address after a temporary seek.""" 76 | try: 77 | return int(self._exec(f"?vi $$ {self._tmp_off}")) 78 | except ValueError as exc: 79 | raise ValueError(f"Invalid address {self._tmp_off}") from exc 80 | finally: 81 | self._tmp_off = "" 82 | 83 | def sym_to_addr(self, sym: str) -> int: 84 | """Resolve a symbol name to its address.""" 85 | if not isinstance(sym, str): 86 | raise TypeError("Symbol type must be string") 87 | return self.at(sym).curr_seek_addr() 88 | 89 | def at(self, seek: str): 90 | """Temporarily seek to ``seek`` for the next command, then restore.""" 91 | self._tmp_off = f"@ {seek}" 92 | return self 93 | -------------------------------------------------------------------------------- /python/test/test_esil.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | 4 | import pytest 5 | import r2pipe 6 | from r2papi.base import Result 7 | from r2papi.esil import Esil 8 | 9 | 10 | @pytest.fixture() 11 | def e(): 12 | r2 = r2pipe.open(f"{os.path.dirname(__file__)}/test_bin") 13 | esil = Esil(r2) 14 | yield esil 15 | esil.r2.quit() 16 | 17 | 18 | def test_eval(e): 19 | assert e.eval("2,3,+") == 5 20 | 21 | 22 | def test_vm_init(e): 23 | e._exec("s 0x100") 24 | e.vm.init() 25 | assert e.vm.cpu.rip == 0x100 26 | assert e.vm.cpu.rsp == 0x178000 27 | assert e.vm.cpu.rbp == 0x178000 28 | assert e.vm.stack_from == 0x100000 29 | assert e.vm.stack_size == 0xF0000 30 | assert e.vm.stack_name == "" 31 | assert len(e._exec("oj", json=True)) == 3 32 | 33 | 34 | def test_vm_utilAddr(e): 35 | e._exec("s sym._func1") 36 | e.vm.init() 37 | e.vm.untilAddr(0x100000EDA).cont() 38 | assert e.vm.cpu.rax == 1 39 | 40 | 41 | def test_vm_utilExpr(e): 42 | e._exec("s sym._func1") 43 | e.vm.init() 44 | e.vm.untilExpr("1,rax,=").cont() 45 | assert e.vm.cpu.rax == 1 46 | 47 | 48 | def test_vm_untilSyscall(e): 49 | e._exec("s sym._func1") 50 | e.vm.init() 51 | e.vm.untilSyscall(1).cont() 52 | assert e.vm.cpu.rax == 1 53 | 54 | 55 | def test_vm_step(e): 56 | e._exec("s sym._func1") 57 | e.vm.init() 58 | e.vm.step() 59 | e.vm.step() 60 | e.vm.step() 61 | assert e.vm.cpu.rax == 1 62 | 63 | 64 | def test_vm_instr(e): 65 | e._exec("s sym._func1") 66 | e.vm.init() 67 | e.vm.at("sym._func1").emulateInstr(3) 68 | assert e.vm.cpu.rax == 1 69 | e.vm.at("0x100000ed0").emulateInstr(3) 70 | assert e.vm.cpu.rax == 1 71 | e.vm.emulateInstr(3, "0x100000ed0") 72 | assert e.vm.cpu.rax == 1 73 | 74 | 75 | def test_cpu_get(e): 76 | e._exec("s sym._func1") 77 | e.vm.init() 78 | assert e.vm.cpu.rax == 0 79 | assert e.vm.cpu.rip == 0x100000ED0 80 | 81 | 82 | def test_cpu_set(e): 83 | e._exec("s sym._func1") 84 | e.vm.init() 85 | e.vm.cpu.rip = 0x100 86 | assert e.vm.cpu.rip == 0x100 87 | 88 | 89 | def test_vm_stepOver(e): 90 | e._exec("s sym._func1") 91 | e.vm.init() 92 | e.vm.stepOver() 93 | assert e.vm.cpu.rip == 0x100000ED1 94 | 95 | 96 | def test_regs_used(e): 97 | e._exec("s sym._func1") 98 | e.vm.init() 99 | e.vm.step() 100 | regs = e.regsUsed() 101 | assert isinstance(regs, Result) 102 | used = getattr(regs, "_dict", {}) 103 | assert isinstance(used, dict) 104 | assert len(used) > 0 105 | 106 | 107 | def test_change_pc(e): 108 | e._exec("s sym._func1") 109 | e.vm.init() 110 | e.vm.cpu.changePC(0x200) 111 | assert e.vm.cpu.rip == 0x200 112 | 113 | 114 | def test_cpu_str(e): 115 | e._exec("s sym._func1") 116 | e.vm.init() 117 | cpu_str = str(e.vm.cpu) 118 | assert isinstance(cpu_str, str) 119 | assert "rip" in cpu_str.lower() 120 | assert "rsp" in cpu_str.lower() 121 | assert "rax" in cpu_str.lower() 122 | -------------------------------------------------------------------------------- /python/test/test_r2api.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | import sys 4 | 5 | import pytest 6 | from r2papi.file import File 7 | from r2papi.r2api import Function, R2Api 8 | 9 | 10 | @pytest.fixture 11 | def r(): 12 | r = R2Api(f"{os.path.dirname(__file__)}/test_bin") 13 | yield r 14 | r.quit() 15 | 16 | 17 | def test_quit(r): 18 | r.quit() 19 | with pytest.raises(AttributeError): 20 | r._exec("px 1") 21 | 22 | 23 | def test_context(): 24 | with R2Api(f"{os.path.dirname(__file__)}/test_bin") as r: 25 | pass 26 | with pytest.raises(AttributeError): 27 | r._exec("px 1") 28 | 29 | 30 | def test_info(r): 31 | info = r.info() 32 | # This may change (?) 33 | assert info.stripped is False 34 | assert info.endian == "little" 35 | 36 | 37 | def test_files(r): 38 | files = r.files 39 | print(files) 40 | assert len(files) == 2 41 | assert type(files[0]) is File 42 | assert files[0].fd == 3 43 | 44 | 45 | def test_functions(r): 46 | r.analyzeAll() 47 | functions = r.functions() 48 | assert type(functions[0]) is Function 49 | assert len(functions) == 7 50 | 51 | 52 | def test_functionByName(r): 53 | r.analyzeAll() 54 | f = r.functionByName("sym._func1") 55 | assert type(f) is Function 56 | assert f.name == "sym._func1" 57 | 58 | 59 | def test_functionRename(r): 60 | r.analyzeAll() 61 | f = r.functionByName("sym._func1") 62 | assert type(f) is Function 63 | assert f.name == "sym._func1" 64 | f.name = "foo" 65 | assert f.name == "foo" 66 | 67 | 68 | def test_function(r): 69 | r.analyzeAll() 70 | r._exec("s sym._func1") 71 | r._exec("s +1") 72 | f = r.function() 73 | assert type(f) is Function 74 | assert f.name == "sym._func1" 75 | 76 | f = r.at(f.offset + 1).function() 77 | assert type(f) is Function 78 | assert f.name == "sym._func1" 79 | f.analyze() 80 | 81 | 82 | def test_functionGraphImg(r): 83 | r.analyzeAll() 84 | f = r.functionByName("sym._func1") 85 | 86 | try: 87 | f.graphImg() 88 | # Make sure the image is created 89 | expected = "sym._func1-graph.gif" 90 | with open(expected, "r") as _: 91 | pass 92 | os.remove(expected) 93 | 94 | custom = "custom-img-path.gif" 95 | f.graphImg(custom) 96 | with open(custom, "r") as _: 97 | pass 98 | os.remove(custom) 99 | except Exception: 100 | # Graphviz may not be installed 101 | print("Skipping graph image tests") 102 | 103 | 104 | def test_read(r): 105 | r.analyzeAll() 106 | offset = r.functionByName("main").offset 107 | # Assume x86 108 | assert r[offset] == b"\x55" 109 | assert r.at(offset).read(1) == b"\x55" 110 | 111 | 112 | def test_writeBytes(r): 113 | r._exec("e io.cache = true") 114 | r.analyzeAll() 115 | offset = r.functionByName("main").offset 116 | r[offset] = b"\xff" 117 | assert r[offset] == b"\xff" 118 | if sys.version_info[0] == 3: 119 | # UTF-8 write 120 | r[offset] = "ñ" 121 | assert r[offset : offset + 2] == b"\xc3\xb1" 122 | -------------------------------------------------------------------------------- /python/r2papi/debugger.py: -------------------------------------------------------------------------------- 1 | from r2papi.base import R2Base, ResultArray 2 | 3 | 4 | class CPU(R2Base): 5 | def __init__(self, r2): 6 | super().__init__(r2) 7 | 8 | def readRegister(self, reg_name): 9 | res = self._exec("drj", json=True) 10 | try: 11 | return res[reg_name] 12 | except: 13 | return None 14 | 15 | def writeRegister(self, reg_name, value): 16 | res = self._exec("dr %s=%s" % (reg_name, value)) 17 | if res == "": 18 | raise ValueError("Ivalid register %s" % reg_name) 19 | 20 | def registers(self): 21 | return self._exec("drj", json=True) 22 | 23 | def __str__(self): 24 | regs = self.registers() 25 | items = regs.items() 26 | 27 | ret_str = "" 28 | for r, v in items: 29 | ret_str += "{:<10}{:#016x}\n".format(r, v) 30 | return ret_str 31 | 32 | def __getattr__(self, attr): 33 | if attr in self.registers().keys(): 34 | return self.readRegister(attr) 35 | 36 | def __setattr__(self, attr, value): 37 | if attr == "r2": 38 | # Hack to avoid infite recursion, maybe there's a better solution 39 | self.__dict__[attr] = value 40 | else: 41 | if attr in self.registers().keys(): 42 | self.writeRegister(attr, value) 43 | else: 44 | self.__dict__[attr] = value 45 | 46 | 47 | class Debugger(R2Base): 48 | def __init__(self, r2): 49 | super().__init__(r2) 50 | 51 | self.cpu = CPU(r2) 52 | 53 | self._untilCall = False 54 | self._untilUnknownCall = False 55 | self._untilRet = False 56 | 57 | self.listBreakpoints = lambda: ResultArray(self._exec("dbj", json=True)) 58 | self.step = lambda: self._exec("ds") 59 | self.memoryMaps = lambda: ResultArray(self._exec("dmj", json=True)) 60 | self.backtrace = lambda: self._exec("dbtj", json=True) 61 | 62 | def start(self): 63 | self._exec("doo") 64 | 65 | def cont(self): 66 | if self._untilCall: 67 | self._exec("dcc") 68 | elif self._untilUnknownCall: 69 | self._exec("dccu") 70 | elif self._untilRet: 71 | self._exec("dcr") 72 | else: 73 | self._exec("dc") 74 | self._untilCall = False 75 | self._untilRet = False 76 | self._untilUnknownCall = False 77 | 78 | def untilCall(self): 79 | self._untilCall = True 80 | return self 81 | 82 | def untilRet(self): 83 | self._untilRet = True 84 | return self 85 | 86 | def untilUnknownCall(self): 87 | self._untilUnknownCall = True 88 | return self 89 | 90 | def setBreakpoint(self, addr=0): 91 | if self._tmp_off != "": 92 | # '@ foo' -> 'foo' 93 | addr = self._tmp_off[2:] 94 | self._exec("db %s" % addr) 95 | self._tmp_off = "" 96 | 97 | def deleteBreakpoint(self, addr=0): 98 | if self._tmp_off != "": 99 | # '@ foo' -> 'foo' 100 | addr = self._tmp_off[2:] 101 | self._exec("db- %s" % addr) 102 | self._tmp_off = "" 103 | -------------------------------------------------------------------------------- /rust/examples/api_example.rs: -------------------------------------------------------------------------------- 1 | extern crate r2papi; 2 | extern crate r2pipe; 3 | extern crate serde_json; 4 | 5 | use r2papi::api_trait::R2PApi; 6 | use r2pipe::r2::R2; 7 | 8 | fn main() { 9 | let path = "/bin/ls"; 10 | let mut r2 = R2::new(Some(path)).expect("Failed to spawn r2"); 11 | r2.init().unwrap(); 12 | r2.analyze().unwrap(); 13 | 14 | //println!("malloc {:#?}", r2.malloc(1024)); 15 | println!("arch {:#?}", r2.arch()); 16 | println!("reg_info {:#?}", r2.reg_info()); 17 | println!("bin_info {:#?}", r2.bin_info()); 18 | println!("flag_info {:#?}", r2.flag_info()); 19 | println!("fn_list {:#?}", r2.fn_list()); 20 | println!("symbols {:#?}", r2.symbols()); 21 | println!("entry {:#?}", r2.entry()); 22 | println!("import {:#?}", r2.imports()); 23 | println!("exports {:#?}", r2.exports()); 24 | println!("relogs {:#?}", r2.relocs()); 25 | println!("libraries {:#?}", r2.libraries()); 26 | println!("seek1 {:#?}", r2.seek(None)); 27 | println!("seek2 {:#?}", r2.seek(Some(0x123))); 28 | println!("hashes {:#?}", r2.hashes()); 29 | println!("segments {:#?}", r2.segments()); 30 | println!("size {:#?}", r2.size()); 31 | println!("read_bytes {:#?}", r2.read_bytes(4, None)); 32 | //println!("read_bits {:#?}", r2.read_bits(4, None)); 33 | //r2.write_bytes(None, &[0x41,0x41,0x41,0x41]).unwrap(); 34 | //r2.write_bytes(Some(0x11223344), &[0x41,0x41,0x41,0x41]).unwrap(); 35 | println!("read u8 {:#?}", r2.read_u8(None)); 36 | println!("read u16 le {:#?}", r2.read_u16_le(None)); 37 | println!("read u32 le {:#?}", r2.read_u32_le(None)); 38 | println!("read u64 le {:#?}", r2.read_u64_le(None)); 39 | println!("read u16 be {:#?}", r2.read_u16_be(None)); 40 | println!("read u32 be {:#?}", r2.read_u32_be(None)); 41 | println!("read u64 be {:#?}", r2.read_u64_be(None)); 42 | /* 43 | println!("write u8 {:#?}", r2.write_u8(None, 0x41)); 44 | println!("write u16 le {:#?}", r2.write_u16_le(None, 0x4141)); 45 | println!("write u32 le {:#?}", r2.write_u32_le(None, 0x41414141)); 46 | println!("write u64 le {:#?}", r2.write_u64_le(None, 0x41414141_41414141)); 47 | println!("write u16 be {:#?}", r2.write_u16_be(None, 0x4141)); 48 | println!("write u32 be {:#?}", r2.write_u32_be(None, 0x41414141)); 49 | println!("write u64 be {:#?}", r2.write_u64_be(None, 0x41414141_41414141)); 50 | */ 51 | 52 | r2.esil_init().expect("cannot initialize esil"); 53 | let esil_regs = r2.esil_regs().unwrap(); 54 | println!("esil regs: {:#?}", esil_regs); 55 | let mut pc = r2.esil_get_reg("pc").unwrap(); 56 | println!("esil pc: 0x{:x}", pc); 57 | r2.esil_set_reg("pc", pc).unwrap(); 58 | r2.esil_step().unwrap(); 59 | r2.esil_step_over().unwrap(); 60 | r2.esil_step_back().unwrap(); 61 | r2.esil_step_until_addr(pc + 20).unwrap(); 62 | r2.esil_cont_until_int().unwrap(); 63 | r2.esil_cont_until_call().unwrap(); 64 | r2.esil_cont_until_exception().unwrap(); 65 | pc = r2.esil_get_reg("pc").unwrap(); 66 | r2.esil_cont_until_addr(pc + 20).unwrap(); 67 | r2.set_arch("arm").unwrap(); 68 | r2.set_bits(64).unwrap(); 69 | 70 | r2.malloc(1024).unwrap(); 71 | println!("buffers: {:#?}", r2.buffers()); 72 | r2.free(5).unwrap(); 73 | r2.set("dbg.bpsize", "4").unwrap(); 74 | println!("dbg.bpsize: {:#?}", r2.get("dbg.bpsize")); 75 | } 76 | -------------------------------------------------------------------------------- /typescript/r2pipe.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generic interface to interact with radare2, abstracts the access to the associated 3 | * instance of the tool, which could be native via rlang or remote via pipes or tcp/http. 4 | * 5 | * @typedef R2Pipe 6 | */ 7 | export interface R2Pipe { 8 | /** 9 | * Run a command in the associated instance of radare2 10 | * 11 | * @param {string} command to be executed inside radare2. 12 | * @returns {string} The output of the command execution 13 | */ 14 | cmd(cmd: string): string; 15 | /** 16 | * Run a radare2 command in a different address. Same as `.cmd(x + '@ ' + a)` 17 | * 18 | * @param {string} cmd to be executed inside radare2. 19 | * @returns {string} The output of the command execution 20 | */ 21 | cmdAt(cmd: string): string; 22 | /** 23 | * Run a radare2 command expecting the output to be JSON 24 | * 25 | * @param {string} cmd to be executed inside radare2. 26 | * @returns {object} the JSON decoded object from the output of the command 27 | */ 28 | cmdj(cmd: string): any; 29 | /** 30 | * Call a radare2 command. This is similar to `R2Pipe.cmd`, but skips all the command parsing rules, 31 | * which is safer and faster but you cannot use any special modifier like `@`, `~`, ... 32 | * 33 | * See R2Pipe.callAt() to call a command on a different address 34 | * 35 | * @param {string} cmd to be executed inside radare2. The given command should end with `j` 36 | * @returns {object} the JSON decoded object from the output of the command 37 | */ 38 | call(cmd: string): string; 39 | /** 40 | * Call a radare2 command in a different address 41 | * 42 | * @param {string} cmd to be executed inside radare2. 43 | * @param {NativePointer|string|number} where to seek to execute this command (previous offset is restored after executing it) 44 | * @returns {string} the string containing the output of the command 45 | */ 46 | callAt(cmd: string, at: string | number | any): string; 47 | /** 48 | * Same as cmdj but using .call which avoids command injection problems 49 | * 50 | * @param {string} cmd to be executed inside radare2. 51 | * @returns {string} the string containing the output of the command 52 | */ 53 | callj(cmd: string): any; 54 | /** 55 | * Same as cmdj but using .call which avoids command injection problems 56 | * 57 | * @param {string} cmd to be executed inside radare2. 58 | * @returns {object} the JSON decoded object from the output of the command 59 | */ 60 | log(msg: string): string; 61 | /** 62 | * Instantiate a new radare2 plugin with the given type and constructor method. 63 | * 64 | * @param {string} type of plugin ("core", "io", "arch", ...) 65 | * @param {string} function that returns the plugin definition 66 | * @returns {boolean} true if successful 67 | */ 68 | plugin(type: string, maker: any): boolean; 69 | /** 70 | * Unload the plugin associated with a `type` and a `name`. 71 | * 72 | * @param {string} type of plugin ("core", "io", "arch", ...) 73 | * @param {string} name of the plugin 74 | * @returns {boolean} true if successful 75 | */ 76 | unload(type: string, name: string): boolean; 77 | } 78 | /** 79 | * A global instance of R2Pipe associated with the current instance of radare2 80 | * 81 | * @type {R2Pipe} 82 | */ 83 | export declare var r2: R2Pipe; 84 | -------------------------------------------------------------------------------- /python/docs/development.rst: -------------------------------------------------------------------------------- 1 | Development guide 2 | ================= 3 | 4 | Introduction 5 | ------------ 6 | 7 | The ``r2papi`` tries to abstract radare functionalities, there's a class 8 | (or there will be at some point) representing each major feature that it have. 9 | 10 | `r2pipe`_ is used to communicate to an underlying ``radare2`` process. Working 11 | with ``r2pipe`` is really simple, it just have three methods ``open``, ``cmd`` 12 | and ``cmdj``. 13 | 14 | * ``open``: Opens a binary, memory dump, malloc:// or anything that r2 15 | supports. Returns an ``OpenBase`` object. 16 | * ``cmd``: Method of ``OpenBase``, it executes a r2 command, and returns its 17 | output as a string. 18 | * ``cmdj``: The same as ``cmd``, but interpret the output as json, and return a 19 | python native object. 20 | 21 | An example: 22 | 23 | .. code-block:: python 24 | 25 | >>> import r2pipe 26 | >>> r = r2pipe.open('/bin/ls') 27 | >>> r.cmd('i~arch') 28 | 'arch x86\nmachine AMD x86-64 architecture' 29 | >>> r.cmdj('ij')['bin']['arch'] 30 | 'x86' 31 | 32 | 33 | :class:`r2papi.R2Api` contains instances of all the classes implementing radare 34 | functionalities (:class:`r2papi.print.Print`, :class:`r2papi.debugger.Debugger`, :class:`r2papi.esil.Esil`...). And they are 35 | available under ``R2Api.print``, ``R2Api.debugger``... 36 | This objects also can contain subclasses, to be more intuitive, one example is 37 | the debugger: ``R2Api.debugger.cpu``. 38 | 39 | Development Environment 40 | ----------------------- 41 | 42 | It is recommended to use a virtual environment while developing (or using) 43 | r2papi, this will help you maintain your system clean and avoid problems 44 | with dependencies. 45 | 46 | Use the following code to create a new virtual environment, and to start using 47 | it. 48 | 49 | .. code-block:: bash 50 | 51 | $ python3 -m venv r2api-venv 52 | $ source r2api-venv/bin/activate 53 | (r2api-venv) $ # Now you are in the virtual environment, make sure pip is updated 54 | (r2api-venv) $ pip install --upgrade pip 55 | 56 | Now that a clean virtual environment have been created, install r2papi. 57 | 58 | .. code-block:: bash 59 | 60 | (r2api-venv) $ git clone https://github.com/radareorg/radare2-r2papi/ 61 | (r2api-venv) $ cd radare2-r2papi/python 62 | (r2api-venv) $ pip install -e . 63 | 64 | Everything is ready to start contributing to r2papi! 65 | 66 | Testing 67 | ------- 68 | 69 | Ideally, there should be a test for each functionallity. 70 | 71 | There are two dependencies for testing, `pytest` and `pytest-cov`. They can be 72 | installed with `pip`. 73 | 74 | .. code-block:: bash 75 | 76 | (r2api-venv) $ pip install pytest pytest-cov 77 | 78 | Tests are organized in different files, one for each radare2 functionality. They 79 | are found under `radare2-r2papi/python/test` path. There is a Makefile used 80 | to execute the tests and get the coverage metrics, just execute `make`. 81 | 82 | Once all the tests have finished, open `htmlcov/index.html` to see the code 83 | coverage that is achieved. 84 | 85 | Documentation 86 | ------------- 87 | 88 | The API must be documented, and Sphinx is used for this. When documenting a 89 | method, class, or module, use `rst` syntax so Sphinx can autogenerate the docs. 90 | 91 | Remember to install sphinx first: 92 | 93 | .. code-block:: bash 94 | 95 | (r2api-venv) $ pip install sphinx 96 | 97 | Documentation can be built executing `make html` in 98 | `radare2-r2papi/python/docs`, then open `_build/html/index.html`. 99 | 100 | Base Class 101 | ---------- 102 | 103 | There's a base class :class:`r2papi.base.R2Base` that implements the basic stuff needed: 104 | 105 | * Temporal seek (like r2 command ``@``) 106 | * Command execution 107 | 108 | Almost all the classes inherits from it. 109 | 110 | .. _r2pipe: https://github.com/radare/radare2-r2pipe 111 | -------------------------------------------------------------------------------- /python/r2papi/esil.py: -------------------------------------------------------------------------------- 1 | from r2papi.base import R2Base, Result 2 | 3 | 4 | class EsilCPU(R2Base): 5 | def __init__(self, r2): 6 | super().__init__(r2) 7 | 8 | def registers(self): 9 | return self._exec("aerj", json=True) 10 | 11 | def readRegister(self, register): 12 | return int(self._exec("aer %s" % register), 16) 13 | 14 | def writeRegister(self, register, value): 15 | self._exec("aer %s=%s" % (register, value)) 16 | 17 | def changePC(self, new_pc): 18 | self._exec("aepc %s" % new_pc) 19 | 20 | def __str__(self): 21 | regs = self.registers() 22 | items = regs.items() 23 | 24 | ret_str = "" 25 | for r, v in items: 26 | ret_str += "{:<10}{:#016x}\n".format(r, v) 27 | return ret_str 28 | 29 | def __getattr__(self, attr): 30 | if attr in self.registers().keys(): 31 | return self.readRegister(attr) 32 | 33 | def __setattr__(self, attr, val): 34 | if attr == "r2": 35 | # Hack to avoid infite recursion, maybe there's a better solution 36 | self.__dict__[attr] = val 37 | elif attr in self.registers().keys(): 38 | self.writeRegister(attr, val) 39 | 40 | 41 | class EsilVM(R2Base): 42 | def __init__(self, r2): 43 | super().__init__(r2) 44 | self.cpu = EsilCPU(r2) 45 | 46 | self.contUntilAddr = None 47 | self.contUntilExpr = None 48 | self.contUntilSyscall = None 49 | 50 | self.stack_from = None 51 | self.stack_size = None 52 | self.stack_name = None 53 | 54 | def init(self, stack_form=0x100000, stack_size=0xF0000, name=""): 55 | self._exec("aei") 56 | self._exec("aeip") 57 | self._exec(f"aeim {stack_form} {stack_size} {name}") 58 | self.stack_from = stack_form 59 | self.stack_size = stack_size 60 | self.stack_name = name 61 | 62 | def untilAddr(self, addr): 63 | self.contUntilAddr = addr 64 | return self 65 | 66 | def untilExpr(self, esil_expr): 67 | self.contUntilExpr = esil_expr 68 | return self 69 | 70 | def untilSyscall(self, syscall_num): 71 | self.contUntilSyscall = syscall_num 72 | return self 73 | 74 | def cont(self, untilAddr=None): 75 | if untilAddr: 76 | self._exec("aecu %s" % (untilAddr)) 77 | elif self.contUntilAddr: 78 | self._exec("aecu %s" % (self.contUntilAddr)) 79 | self.contUntilAddr = None 80 | elif self.contUntilExpr: 81 | self._exec("aecue %s" % self.contUntilExpr) 82 | self.contUntilExpr = None 83 | elif self.contUntilSyscall: 84 | self._exec("aecs %s" % self.contUntilSyscall) 85 | self.contUntilSyscall = None 86 | 87 | def step(self, num=1): 88 | self._exec("%daes" % num) 89 | 90 | def stepOver(self): 91 | self._exec("aeso") 92 | 93 | def stepBack(self): 94 | # XXX: Not working ? 95 | self._exec("aesb") 96 | 97 | def emulateInstr(self, num=1, offset=None): 98 | if offset is None: 99 | if self._tmp_off != "": 100 | offset = self._tmp_off 101 | if not offset.startswith("@ 0x"): 102 | offset = self.curr_seek_addr() 103 | elif offset.startswith("@ "): 104 | offset = self._tmp_off[2:] 105 | else: 106 | # XXX: Check if this is correct 107 | offset = "$$" 108 | self._exec(f"aesp {offset} {num}") 109 | 110 | 111 | class Esil(R2Base): 112 | def __init__(self, r2): 113 | super().__init__(r2) 114 | self.vm = EsilVM(r2) 115 | 116 | def eval(self, esil_str): 117 | return int(self._exec('"ae %s"' % esil_str), 16) 118 | 119 | def regsUsed(self, num_instructions=1): 120 | res = self._exec("aeaj %d %s" % (num_instructions, self._tmp_off), json=True) 121 | self._tmp_off = "" 122 | return Result(res) 123 | -------------------------------------------------------------------------------- /typescript/async/ai.ts: -------------------------------------------------------------------------------- 1 | import { R, Module, Process, Thread } from "./r2papi.js"; 2 | import { r2, R2PipeAsync } from "./r2pipe.js"; 3 | 4 | /** 5 | * Class that interacts with the `r2ai` plugin (requires `rlang-python` and `r2i` r2pm packages to be installed). 6 | * Provides a way to script the interactions with different language models using javascript from inside radare2. 7 | * 8 | * @typedef R2AI 9 | */ 10 | export class R2AI { 11 | /** 12 | * 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. 13 | * 14 | * @type {boolean} 15 | */ 16 | available: boolean = false; 17 | /** 18 | * Name of the model instantiated to be used for the subsequent calls. 19 | * 20 | * @type {string} 21 | */ 22 | model: string = ""; 23 | r2: R2PipeAsync; 24 | 25 | constructor(r2: R2PipeAsync, num?: number, model?: string) { 26 | this.r2 = r2; 27 | this.available = false; 28 | } 29 | 30 | async checkAvailability(): Promise { 31 | if (this.available) { 32 | return true; 33 | } 34 | this.available = r2.cmd("r2ai -h").trim() !== ""; 35 | /* 36 | if (this.available) { 37 | if (num) { 38 | r2.call(`r2ai -n ${num}`) 39 | } 40 | // r2.call('r2ai -e DEBUG=1') 41 | if (model) { 42 | this.model = model; 43 | } 44 | } 45 | */ 46 | return this.available; 47 | } 48 | /** 49 | * Reset conversation messages 50 | */ 51 | async reset() { 52 | await this.checkAvailability(); 53 | if (this.available) { 54 | await r2.call("r2ai -R"); 55 | } 56 | } 57 | /** 58 | * Set the role (system prompt) message for the language model to obey. 59 | * 60 | * @param {string} text containing the system prompt 61 | * @returns {boolean} true if successful 62 | */ 63 | async setRole(msg: string): Promise { 64 | if (this.available) { 65 | await r2.call(`r2ai -r ${msg}`); 66 | return true; 67 | } 68 | return false; 69 | } 70 | 71 | /** 72 | * Set the Model name or path to the GGUF file to use. 73 | * 74 | * @param {string} model name or path to GGUF file 75 | * @returns {boolean} true if successful 76 | */ 77 | async setModel(modelName: string): Promise { 78 | if (this.available) { 79 | await r2.call(`r2ai -m ${this.model}`); 80 | return true; 81 | } 82 | return false; 83 | } 84 | /** 85 | * Get the current selected model name. 86 | * 87 | * @returns {boolean} model name 88 | */ 89 | async getModel(): Promise { 90 | if (this.available) { 91 | this.model = await r2.call("r2ai -m").trim(); 92 | } 93 | return this.model; 94 | } 95 | /** 96 | * Get a list of suggestions for model names to use. 97 | * 98 | * @returns {string[]} array of strings containing the model names known to work 99 | */ 100 | async listModels(): Promise { 101 | if (this.available) { 102 | const models = await r2.call("r2ai -M"); 103 | return models 104 | .replace(/-m /, "") 105 | .trim() 106 | .split(/\n/g) 107 | .filter((x: string) => x.indexOf(":") !== -1); 108 | } 109 | return []; 110 | } 111 | /** 112 | * Send message to the language model to be appended to the current conversation (see `.reset()`) 113 | * 114 | * @param {string} text sent from the user to the language model 115 | * @returns {string} response from the language model 116 | */ 117 | async query(msg: string): Promise { 118 | if (!this.available || msg == "") { 119 | return ""; 120 | } 121 | const fmsg = msg.trim().replace(/\n/g, "."); 122 | const response = r2.call(`r2ai ${fmsg}`); 123 | return response.trim(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /typescript/async/pdq.ts: -------------------------------------------------------------------------------- 1 | import { R2PipeAsync } from "./index"; 2 | import { EsilParser } from "./esil"; 3 | 4 | declare let r2: R2PipeAsync; 5 | declare let r2plugin: any; 6 | /// ========== main =========== /// 7 | 8 | const ep = new EsilParser(r2); 9 | 10 | /* 11 | */ 12 | 13 | function testLoop() { 14 | ep.parse("rax,rbx,:=,1,GOTO"); 15 | } 16 | 17 | function testBasic() { 18 | ep.parse("1,rax,="); 19 | ep.parse("1,3,+,rax,:="); 20 | ep.parse("1,3,+,!,rax,:="); 21 | } 22 | function testComplex() { 23 | ep.parse("1,rax,=,1,3,+,rax,:=,1,3,+,!,rax,:="); 24 | ep.parse("cf,!,zf,|,?{,x16,}{,xzr,},x16,="); 25 | } 26 | function testConditional() { 27 | ep.parse("1,?{,x0,}{,x1,},:="); 28 | ep.parse("zf,!,nf,vf,^,!,&,?{,4294982284,pc,:=,}"); 29 | } 30 | 31 | async function pdq(arg: string) { 32 | r2.cmd("e cfg.json.num=string"); 33 | switch (arg[0]) { 34 | case " ": 35 | await parseAmount(+arg); 36 | break; 37 | case "i": 38 | await parseAmount(1); 39 | break; 40 | case "f": 41 | case undefined: 42 | case "": 43 | { 44 | const oaddr = (await r2.cmd("?v $$")).trim(); 45 | // const func = r2.cmdj("pdrj"); // XXX this command changes the current seek 46 | const bbs = await r2.cmdj("afbj"); // XXX this command changes the current seek 47 | for (const bb of bbs) { 48 | console.log("bb_" + bb.addr + ":"); 49 | r2.cmd(`s ${bb.addr}`); 50 | await parseAmount(bb.ninstr); 51 | } 52 | r2.cmd(`s ${oaddr}`); 53 | } 54 | break; 55 | case "e": 56 | ep.reset(); 57 | ep.parse(arg.slice(1).trim(), await r2.cmd("?v $$")); 58 | console.log(ep.toString()); 59 | break; 60 | case "t": 61 | testComplex(); 62 | testBasic(); 63 | testConditional(); 64 | testLoop(); 65 | console.log(ep.toString()); 66 | console.log("---"); 67 | console.log(ep.toEsil()); 68 | break; 69 | case "?": 70 | console.log("Usage: pdq[ef?] [ninstr] - quick decompiler plugin"); 71 | console.log("pdq - decompile current function"); 72 | console.log("pdq 100 - decompile 100 instructions"); 73 | console.log("pdqe 1,rax,:= - decompile given esil expressoin"); 74 | console.log("pdqi - decompile one instruction"); 75 | console.log("pdqt - run tests"); 76 | break; 77 | } 78 | } 79 | 80 | async function parseAmount(n: number): Promise { 81 | // console.log("PDQ "+n); 82 | const pies = await r2.cmd("pie " + n + " @e:scr.color=0"); 83 | const lines = pies.trim().split("\n"); 84 | for (const line of lines) { 85 | if (line.length === 0) { 86 | console.log("Empty"); 87 | continue; 88 | } 89 | // console.log("parse", r2.cmd("?v:$$")); 90 | const kv = line.split(" "); 91 | if (kv.length > 1) { 92 | // line != "") { 93 | // console.log("// @ " + kv[0]); 94 | ep.reset(); 95 | r2.cmd(`s ${kv[0]}`); 96 | ep.parse(kv[1], kv[0]); 97 | ep.optimize("flags,labels"); 98 | console.log(ep.toString()); 99 | } 100 | } 101 | // console.log(ep.toString()); 102 | } 103 | 104 | r2.unload("core", "pdq"); 105 | r2.plugin("core", function () { 106 | function coreCall(cmd: string) { 107 | if (cmd.startsWith("pdq")) { 108 | try { 109 | pdq(cmd.slice(3)); 110 | } catch (e) { 111 | console.error(e); 112 | e.printStackTrace(); 113 | } 114 | return true; 115 | } 116 | return false; 117 | } 118 | return { 119 | name: "pdq", 120 | desc: "quick decompiler", 121 | call: coreCall 122 | }; 123 | }); 124 | -------------------------------------------------------------------------------- /python/test/test_search.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from __future__ import print_function 3 | 4 | import os 5 | import sys 6 | 7 | import pytest 8 | import r2pipe 9 | from r2papi.search import Search 10 | from r2papi.write import Write 11 | 12 | PYTHON_VERSION = sys.version_info[0] 13 | 14 | 15 | @pytest.fixture 16 | def s(): 17 | r = r2pipe.open(f"{os.path.dirname(__file__)}/test_bin") 18 | search = Search(r) 19 | yield search 20 | search.r2.quit() 21 | 22 | 23 | def _writer(search_obj): 24 | return Write(search_obj.r2) 25 | 26 | 27 | def test_string_and_json(s): 28 | w = _writer(s) 29 | w.reopen() 30 | w.hex("666f6f00") 31 | assert "foo" in s.at(0x200).string("foo") 32 | json_res = s.at(0x200).string_json("foo") 33 | assert isinstance(json_res, list) 34 | assert "foo" in json_res[0].data 35 | 36 | 37 | def test_inverse_searches(s): 38 | w = _writer(s) 39 | w.reopen() 40 | w.hex("000000ff") 41 | res = s.at(0x300).inverse_hex("00") 42 | assert isinstance(res, list) 43 | assert "11" in res[0].data 44 | 45 | 46 | def test_base_and_deltified(s): 47 | assert isinstance(s.base_address(), str) 48 | assert s.base_address() == "0x08000000" 49 | w = _writer(search_obj=s) 50 | w.reopen() 51 | w.hex("10111213") 52 | res = s.at(0x400).deltified("010101") 53 | assert isinstance(res, list) 54 | assert "10111213" in res[0].data 55 | 56 | 57 | def test_file_search(s, tmp_path): 58 | w = _writer(s) 59 | w.reopen() 60 | 61 | file_content = b"deadbeef" 62 | tmp_file = tmp_path / "search.bin" 63 | tmp_file.write_bytes(file_content) 64 | 65 | jes = s.file(str(tmp_file), int(0x00000000), 2) 66 | assert isinstance(jes, list) 67 | assert jes[0].data == "6465" 68 | assert jes[1].data == "6465" 69 | assert jes[2].data == "6465" 70 | 71 | 72 | def test_case_insensitive_and_rabin_karp(s): 73 | w = _writer(s) 74 | w.reopen() 75 | w.hex("466F4F00") 76 | res = s.at(0x700).case_insensitive("foo") 77 | assert isinstance(res, list) 78 | assert "FoO" in res[0].data 79 | res = s.at(0x700).rabin_karp("FoO") 80 | assert isinstance(res, list) 81 | assert "FoO" in res[0].data 82 | 83 | 84 | def test_entropy(s): 85 | result_no_thr = s.entropy() 86 | assert isinstance(result_no_thr, list), "entropy() must return ResultArray" 87 | for entry in result_no_thr: 88 | assert "start" in entry, "missing start address" 89 | assert "end" in entry, "missing end address" 90 | assert "entropy" in entry, "missing entropy value" 91 | assert isinstance(entry["start"], int), "start should be int" 92 | assert isinstance(entry["end"], int), "end should be int" 93 | assert isinstance(entry["entropy"], float), "entropy should be float" 94 | 95 | result_thr = s.entropy(5) 96 | assert isinstance(result_thr, list), "entropy(5) must return ResultArray" 97 | for entry in result_thr: 98 | assert "start" in entry 99 | assert "end" in entry 100 | assert "entropy" in entry 101 | 102 | 103 | def test_wide_string_plain(s): 104 | w = _writer(s) 105 | w.reopen() 106 | w.hex("620061007200") 107 | assert s.at(0x800).wide_string("bar")[0].data == "620061007200" 108 | assert s.at(0x800).wide_string_ci("BaR")[0].data == "620061007200" 109 | 110 | 111 | def test_wide_string_json(s): 112 | w = _writer(s) 113 | w.reopen() 114 | w.hex("620061007200") 115 | res = s.at(0x800).wide_string_json("bar") 116 | assert len(res) == 1 117 | assert res[0]["type"] == "hexpair" 118 | assert res[0]["data"] == "620061007200" 119 | 120 | 121 | def test_wide_string_ci_json(s): 122 | w = _writer(s) 123 | w.reopen() 124 | w.hex("620061007200") 125 | res = s.at(0x800).wide_string_ci_json("BAR") 126 | assert len(res) == 1 127 | assert res[0]["type"] == "hexpair" 128 | assert res[0]["data"] == "620061007200" 129 | 130 | 131 | def test_size_range(s): 132 | res = s.size_range(5, 5) 133 | assert isinstance(res, list) 134 | assert len(res) == 7 135 | assert "guard" in res[0].data 136 | -------------------------------------------------------------------------------- /typescript/examples/asm/main.js: -------------------------------------------------------------------------------- 1 | let GInstruction = null; 2 | 3 | export const main = function main (Instruction) { 4 | GInstruction = Instruction; 5 | 6 | if (typeof r2 !== 'undefined') { 7 | // entrypoint 8 | r2.cmd("-a x86"); 9 | r2.cmd("-b 64"); 10 | 11 | const a = new Assembler(); 12 | a.asm("nop"); 13 | a.asm("nop"); 14 | a.asm("nop"); 15 | console.log("hex: " + a.toString()); 16 | console.log(r2.cmd('""pad ' + a.toString())); 17 | 18 | r2.cmd("-e cfg.bigendian=false"); 19 | r2.cmd("-a riscv"); 20 | r2.cmd("-b 32"); 21 | r2.cmd("-e cfg.bigendian=true"); 22 | } 23 | 24 | const p = new RiscvProgram(); 25 | // p.setEndian(true); // big endian 26 | // p.setProgramCounter(0); 27 | // p.asm('lui x1, 0'); 28 | p.lui(1, 0); 29 | p.lui(2, 10); 30 | p.lui(3, 1); 31 | p.label('repeat'); 32 | // p.addi(1, 1, 3); 33 | p.lui(0, 1000); 34 | p.nop(); 35 | p.nop(); 36 | p.nop(); 37 | p.nop(); 38 | p.nop(); 39 | p.bne(1, 2, 'repeat'); 40 | if (typeof r2 !== 'undefined') { 41 | r2.cmd("wx " + p.toString()); 42 | console.log(r2.cmd("pd 20")); 43 | } else { 44 | console.log(p.toString()); 45 | } 46 | } 47 | 48 | function rvasm(s) { 49 | if (GInstruction === null) { 50 | return rasm2(s); 51 | } 52 | try { 53 | // 'lui x17, 116088'); 54 | var inst = new GInstruction.Instruction(s); 55 | return inst.hex; 56 | } catch (e) { 57 | console.error(e); 58 | return "____"; 59 | } 60 | } 61 | 62 | function rasm2(s) { 63 | if (typeof r2 === "undefined") { 64 | console.error("Requires r2"); 65 | return "____"; 66 | } 67 | var hex = r2.cmd('""pa ' + s).trim(); 68 | if (hex.length > 0) { 69 | return hex; 70 | } 71 | console.error("Invalid instruction: " + s); 72 | return "____"; 73 | } 74 | 75 | class RiscvProgram { 76 | #program = ""; 77 | #labels = {}; 78 | #endian = false; 79 | #pc = 0; 80 | constructor() { 81 | this.#program = ''; 82 | this.#labels = {}; 83 | } 84 | setProgramCounter(pc) { 85 | this.#pc = pc; 86 | } 87 | setEndian(big) { 88 | this.#endian = big; 89 | } 90 | toString() { 91 | return this.#program; 92 | } 93 | append(x) { 94 | // reverse endian!! 95 | if (!this.#endian) { 96 | x = x.match(/.{2}/g).reverse().join(''); 97 | } 98 | this.#pc += x.length / 2; 99 | this.#program += x; 100 | } 101 | 102 | // api 103 | label(s) { 104 | const pos = this.#pc; // this.#program.length / 4; 105 | this.#labels[s] = this.#pc; 106 | return pos; 107 | } 108 | asm(s) { 109 | const hex = rvasm(s); 110 | append(hex); 111 | } 112 | lui(rd, imm) { 113 | const islab = this.#labels[imm]; 114 | const arg = islab? islab: imm; 115 | const hex = rvasm(`lui x${rd}, ${arg}`); 116 | this.append(hex); 117 | } 118 | addi(rd, rs1, rs2) { 119 | const hex = rvasm(`addi x${rd}, x${rs1}, x${rs2}`); 120 | this.append(hex); 121 | } 122 | beq(rs1, rs2, imm) { 123 | const arg = this.relative(imm); 124 | const hex = rvasm(`beq x${rs1}, x${rs2}, ${arg}`); 125 | this.append(hex); 126 | } 127 | bne(rs1, rs2, imm) { 128 | const arg = this.relative(imm); 129 | const hex = rvasm(`bne x${rs1}, x${rs2}, ${arg}`); 130 | this.append(hex); 131 | } 132 | nop() { 133 | this.addi(0,0,0); 134 | } 135 | relative(imm) { 136 | const islab = this.#labels[imm]; 137 | const arg = islab? islab: imm; 138 | const delta = arg - this.#pc; 139 | return delta; 140 | } 141 | } 142 | 143 | class Assembler { 144 | program = ""; 145 | labels = {}; 146 | endian = false; 147 | pc = 0; 148 | constructor() { 149 | this.program = ''; 150 | this.labels = {}; 151 | } 152 | setProgramCounter(pc) { 153 | this.pc = pc; 154 | } 155 | setEndian(big) { 156 | this.endian = big; 157 | } 158 | toString() { 159 | return this.program; 160 | } 161 | append(x) { 162 | // reverse endian!! 163 | if (!this.endian) { 164 | x = x.match(/.{2}/g).reverse().join(''); 165 | } 166 | this.pc += x.length / 2; 167 | this.program += x; 168 | } 169 | // api 170 | label(s) { 171 | const pos = this.pc; // this.#program.length / 4; 172 | this.labels[s] = this.pc; 173 | return pos; 174 | } 175 | asm(s) { 176 | const hex = rasm2(s); 177 | this.append(hex); 178 | } 179 | } 180 | 181 | -------------------------------------------------------------------------------- /typescript/Makefile: -------------------------------------------------------------------------------- 1 | TSC=$(shell pwd)/node_modules/.bin/tsc 2 | PLUGDIR=$(shell r2 -H R2_USER_PLUGINS) 3 | ESLINT=$(shell pwd)/node_modules/.bin/eslint 4 | TSI=sh tools/tsi.sh 5 | SRCDIR=async 6 | 7 | .PHONY: doc docs docs-html webdoc sync 8 | # build 9 | 10 | all: node_modules 11 | $(MAKE) one 12 | # $(MAKE) build 13 | 14 | one: build 15 | cd build ; cat shell.r2.js esil.r2.js base64.r2.js r2pipe.r2.js ai.r2.js r2papi.r2.js | grep -v require > ../r2papi.r2.js 16 | echo 'var R2Papi=R2PapiSync;' >> r2papi.r2.js 17 | 18 | # cat $(addsuffix .r2.js,$(addprefix build/,$(FILES))) | grep -v require > one.r2.js 19 | # echo "var R2Papi=R2PapiSync;" >> one.r2.js 20 | #shell.r2.js esil.r2.js base64.r2.js r2pipe.r2.js ai.r2.js r2papi.r2.js | grep -v require > one.r2.js 21 | # R2_DEBUG_NOPAPI=1 r2 -i one.r2.js /bin/ls 22 | 23 | onetest: 24 | R2_PAPI_SCRIPT=$(shell pwd)/r2papi.r2.js \ 25 | r2 -c "'js r2.cmd((ptr().api)?'q!! 0':'q!! 1');" /bin/ls 26 | 27 | vs: 28 | open -a "Visual Studio Code" $(SRCDIR) || vscode . 29 | 30 | SYNCREGEX= 31 | SYNCREGEX+=-e 's,await ,,g' 32 | SYNCREGEX+=-e 's,async ,,g' 33 | SYNCREGEX+=-e 's,R2PipeAsync,R2PipeSync,g' 34 | SYNCREGEX+=-e 's,R2PapiAsync,R2PapiSync,g' 35 | SYNCREGEX+=-e 's/Promise<\(.*\)>/\1/' 36 | 37 | lint: 38 | $(ESLINT) $(SRCDIR)/*.ts 39 | 40 | # FILES=r2pipe r2papi opt esil shell base64 ai r2frida index 41 | FILES=shell esil base64 r2pipe ai r2papi 42 | 43 | # Create a 'sync' version of the original 'async' 44 | sync: 45 | rm -rf sync 46 | mkdir -p sync 47 | cp -f $(addprefix $(SRCDIR)/,$(addsuffix .ts,$(FILES))) sync 48 | for a in sync/*.ts ; do sed $(SYNCREGEX) -i=.bak $$a ; done 49 | rm -f sync/*.bak 50 | cp -f $(SRCDIR)/tsconfig.json sync/tsconfig.json 51 | 52 | build: sync 53 | rm -rf build 54 | mkdir build 55 | cp -f sync/*.ts build 56 | cp -rf $(SRCDIR)/tsconfig.json build/tsconfig.json 57 | $(TSC) -p build -d 58 | for a in $(FILES); do \ 59 | echo "tsc $$a.ts -o $$a.r2.js .." ; $(TSC) -m node16 --target es2020 sync/$$a.ts && mv sync/$$a.js build/$$a.r2.js ; \ 60 | done 61 | 62 | # TODO: dupe of build only for pub. this should be deleted to avoid confusion 63 | abuild: sync 64 | rm -rf build 65 | mkdir build 66 | cp -f $(SRCDIR)/*.ts build 67 | cp -rf $(SRCDIR)/tsconfig.json build 68 | $(TSC) -p build -d 69 | @for a in $(FILES); do \ 70 | echo "tsc $$a.ts -o $$a.r2.js .." ; \ 71 | $(TSC) -m node16 --target es2020 $(SRCDIR)/$$a.ts && \ 72 | mv $(SRCDIR)/$$a.js build/$$a.r2.js ; \ 73 | done 74 | rm -f build/*.ts 75 | 76 | pdq: node_modules 77 | # TODO: use pdq.r2.ts 78 | $(TSC) -m node16 --target es2020 pdq.ts 79 | cat pdq.js | sed -e 's,require.*$$,global;,' > pdq.r2.js 80 | 81 | install user-install: 82 | mkdir -p $(PLUGDIR) 83 | cp -f pdq.r2.js $(PLUGDIR) 84 | 85 | uninstall user-uninstall: 86 | rm -f $(PLUGDIR)/pdq.qjs 87 | 88 | node_modules: 89 | mkdir -p node_modules 90 | npm i 91 | 92 | node_modules/jsfmt: node_modules 93 | npm install jsfmt 94 | 95 | MODVER=$(shell node -e 'console.log(JSON.parse(require("fs").readFileSync("package.json"))["version"])') 96 | 97 | docs-html: sync 98 | npm run adoc 99 | npm run sdoc 100 | 101 | doc docs: docs-html 102 | rm -f docs.zip 103 | zip -r docs.zip sync/docs $(SRCDIR)/docs 104 | r2 -qc "open docs/index.html" -- 105 | 106 | webdoc: 107 | npm run doc 108 | rm -rf .tmp 109 | mkdir .tmp 110 | mv docs .tmp/r2papi 111 | cd .tmp && tar czvf r2papi.tgz r2papi 112 | scp .tmp/r2papi.tgz radare.org:doc 113 | ssh radare.org 'cd doc && tar xzvf r2papi.tgz' 114 | rm -rf .tmp 115 | 116 | npm publish pub: 117 | $(MAKE) abuild 118 | # Publishing the sync api 119 | $(MAKE) build 120 | cp -f README.md package.json package-lock.json build 121 | cd build && npm publish 122 | # Publishing the async api 123 | $(MAKE) abuild 124 | cp -f README.md build 125 | sed -e 's,r2papi",r2papi-async",' < package.json > build/package.json 126 | sed -e 's,r2papi",r2papi-async",' < package-lock.json > build/package-lock.json 127 | cd build && npm publish 128 | 129 | unpub unpublish: 130 | npm unpublish r2papi@${MODVER} 131 | 132 | # Create a Typescript declaration file taking json output from r2 commands 133 | r2wip.d.ts: 134 | $(TSI) ij >> r2wip.d.ts 135 | $(TSI) pdj >> r2wip.d.ts 136 | $(TSI) afij >> r2wip.d.ts 137 | $(TSI) abj >> r2wip.d.ts 138 | 139 | clean: 140 | rm -rf *.qjs *.r2.js one.* dist __dist *.zip *.d.ts *.js build sync r2papi.r2.js r2wip.d.ts 141 | -------------------------------------------------------------------------------- /python/r2papi/print.py: -------------------------------------------------------------------------------- 1 | from r2papi.base import R2Base, ResultArray 2 | 3 | 4 | class Print(R2Base): 5 | """ 6 | Class that represents the ``p`` command in radare2. It's used to read 7 | information. 8 | """ 9 | 10 | def __init__(self, r2): 11 | super().__init__(r2) 12 | self.hash_types = self._exec("ph").split() 13 | 14 | def byte(self): 15 | """ 16 | Returns: 17 | int: One byte at current (or temporal) offset. 18 | """ 19 | return self.bytes(1, asList=True)[0] 20 | 21 | def bytes(self, size=0, asList=False): 22 | """ 23 | Args: 24 | size (int, optional): 25 | Number of bytes to return. 26 | asList (bool, optional): 27 | If True, a list is returned containing a byte on each element. 28 | If False, a bytes (python3) or str (python2) object is returned. 29 | 30 | Returns: 31 | bytes | str | list: Bytes object in python3, `str` in python2. If 32 | asList is set to true, a list of integers is returned. 33 | """ 34 | size = "" if size == 0 else size 35 | if asList: 36 | ret = self._exec("p8j %s%s" % (size, self._tmp_off), json=True) 37 | else: 38 | ret = self._exec("p8 %s%s" % (size, self._tmp_off)) 39 | ret = bytes.fromhex(ret) 40 | self._tmp_off = "" 41 | return ret 42 | 43 | def string(self): 44 | """ 45 | Returns: 46 | str: Zero terminated string at current seek. Seek can be temporary 47 | changed with the :meth:`r2api.r2api.R2Api.at` method. 48 | """ 49 | # [:-1] to remove newline, probably r2pipe should be changed 50 | res = self._exec("psz %s" % self._tmp_off, rstrip=False)[:-1] 51 | self._tmp_off = "" 52 | return res 53 | 54 | def bits(self, size=0): 55 | """ 56 | Args: 57 | size (int, optional): 58 | Number of bits to be returned. If it's 0, the default block size 59 | will be returned. 60 | Returns: 61 | str: Specified number of bits from current (or temporary) offset. 62 | """ 63 | size = "" if size == 0 else size 64 | ret = self._exec("pb %s%s" % (size, self._tmp_off)) 65 | self._tmp_off = "" 66 | return ret 67 | 68 | def disassemble(self, size=0): 69 | """ 70 | Args: 71 | size (int, optional): 72 | Number of instructions to be returned. If it's 0, the default 73 | block size will be returned. 74 | Returns: 75 | list: List of :class:`r2api.base.Result` with the specified number 76 | of instructions from current (or temporary) offset. 77 | """ 78 | size = "" if size == 0 else size 79 | ret = self._exec("pdj %s%s" % (size, self._tmp_off), json=True) 80 | self._tmp_off = "" 81 | return ResultArray(ret) 82 | 83 | def disasmBytes(self, size=0): 84 | """ 85 | Args: 86 | size (int, optional): 87 | Number of bytes to be decoded into instructions. If it's 0, the 88 | default block size will be used. 89 | Returns: 90 | list: List of :class:`r2api.base.Result` containing the 91 | instructions. 92 | """ 93 | size = "" if size == 0 else size 94 | ret = self._exec("pDj %s%s" % (size, self._tmp_off), json=True) 95 | self._tmp_off = "" 96 | return ResultArray(ret) 97 | 98 | def hexdump(self, size=0): 99 | """ 100 | Args: 101 | size (int, optional): 102 | Number of bytes to be decoded as hexdump. 103 | Returns: 104 | str: Hexdump of ``size`` bytes as string. 105 | """ 106 | size = "" if size == 0 else size 107 | ret = self._exec("p8 %s%s" % (size, self._tmp_off)) 108 | self._tmp_off = "" 109 | return ret 110 | 111 | def hash(self, h_type, size=0): 112 | """ 113 | .. todo:: 114 | 115 | Docs 116 | """ 117 | if h_type not in self.hash_types: 118 | raise ValueError("Hash function not supported") 119 | size = "" if size == 0 else size 120 | ret = self._exec("ph %s %s%s" % (h_type, size, self._tmp_off)) 121 | self._tmp_off = "" 122 | return ret 123 | 124 | def debruijn(self, size=0): 125 | """ 126 | Args: 127 | size (int, optional): 128 | Number of bytes from de Bruijn sequence to return. 129 | Returns: 130 | str: de Bruijn sequence as hexdump. 131 | """ 132 | size = "" if size == 0 else size 133 | ret = self._exec("ppd %s" % size) 134 | self._tmp_off = "" 135 | return ret 136 | 137 | @property 138 | def pwd(self): 139 | """ 140 | Returns: 141 | str: Path working directory 142 | """ 143 | return self._exec("pwd") 144 | -------------------------------------------------------------------------------- /playground/asan/crash.txt: -------------------------------------------------------------------------------- 1 | ================================================================= 2 | ==66232==ERROR: AddressSanitizer: heap-use-after-free on address 0x000108a90fd8 at pc 0x00010201cb44 bp 0x00016f78d990 sp 0x00016f78d988 3 | READ of size 8 at 0x000108a90fd8 thread T0 4 | #0 0x10201cb40 in r_rbnode_next new_rbtree.c:417 5 | #1 0x100e91f48 in r_io_bank_read_at io_bank.c:796 6 | #2 0x100e50c74 in r_io_vread_at io.c:213 7 | #3 0x100e51620 in internal_r_io_read_at io.c:234 8 | #4 0x100e514b4 in r_io_read_at io.c:269 9 | #5 0x1046b0cf0 in cmd_w cmd_write.c:1438 10 | #6 0x10441c18c in cmd_write cmd_write.c:2255 11 | #7 0x104935838 in r_cmd_call cmd_api.c:519 12 | #8 0x104466808 in r_core_cmd_subst_i cmd.c:4797 13 | #9 0x10444d098 in r_core_cmd_subst cmd.c:3631 14 | #10 0x1042ea404 in run_cmd_depth cmd.c:5696 15 | #11 0x1042caf28 in r_core_cmd cmd.c:5780 16 | #12 0x104239268 in r_core_prompt_exec core.c:3573 17 | #13 0x104238748 in r_core_prompt_loop core.c:3391 18 | #14 0x101a5f2c0 in r_main_radare2 radare2.c:1682 19 | #15 0x10066734c in main radare2.c:104 20 | #16 0x186907e4c () 21 | 22 | 0x000108a90fd8 is located 8 bytes inside of 40-byte region [0x000108a90fd0,0x000108a90ff8) 23 | freed by thread T0 here: 24 | #0 0x1059e2de4 in wrap_free+0x98 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x3ede4) 25 | #1 0x1020175c4 in r_crbtree_take new_rbtree.c:362 26 | #2 0x10201bea8 in r_crbtree_delete new_rbtree.c:382 27 | #3 0x100e8acd0 in _delete_submaps_from_bank_tree io_bank.c:407 28 | #4 0x100e8cb0c in r_io_bank_update_map_boundaries io_bank.c:572 29 | #5 0x100e6a3e8 in r_io_map_resize io_map.c:476 30 | #6 0x10461c590 in cmd_open_map cmd_open.c:959 31 | #7 0x1043ae3dc in cmd_open cmd_open.c:2199 32 | #8 0x104935838 in r_cmd_call cmd_api.c:519 33 | #9 0x104466808 in r_core_cmd_subst_i cmd.c:4797 34 | #10 0x10444d098 in r_core_cmd_subst cmd.c:3631 35 | #11 0x1042ea404 in run_cmd_depth cmd.c:5696 36 | #12 0x1042caf28 in r_core_cmd cmd.c:5780 37 | #13 0x10425f560 in r_core_cmdf cmd.c:5930 38 | #14 0x100e314f8 in __read io_socket.c:51 39 | #15 0x100e5c6d0 in r_io_plugin_read io_plugin.c:154 40 | #16 0x100e6ec4c in r_io_desc_read io_desc.c:204 41 | #17 0x100e726cc in r_io_desc_read_at io_desc.c:334 42 | #18 0x100ea7278 in r_io_fd_read_at io_fd.c:67 43 | #19 0x100e91edc in r_io_bank_read_at io_bank.c:793 44 | #20 0x100e50c74 in r_io_vread_at io.c:213 45 | #21 0x100e51620 in internal_r_io_read_at io.c:234 46 | #22 0x100e514b4 in r_io_read_at io.c:269 47 | #23 0x1046b0cf0 in cmd_w cmd_write.c:1438 48 | #24 0x10441c18c in cmd_write cmd_write.c:2255 49 | #25 0x104935838 in r_cmd_call cmd_api.c:519 50 | #26 0x104466808 in r_core_cmd_subst_i cmd.c:4797 51 | #27 0x10444d098 in r_core_cmd_subst cmd.c:3631 52 | #28 0x1042ea404 in run_cmd_depth cmd.c:5696 53 | #29 0x1042caf28 in r_core_cmd cmd.c:5780 54 | 55 | previously allocated by thread T0 here: 56 | #0 0x1059e3074 in wrap_calloc+0x9c (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x3f074) 57 | #1 0x102013488 in _node_new new_rbtree.c:104 58 | #2 0x102011604 in r_crbtree_insert new_rbtree.c:142 59 | #3 0x100e847a8 in r_io_bank_map_add_top io_bank.c:181 60 | #4 0x100e609dc in r_io_map_add io_map.c:172 61 | #5 0x1046ebf34 in r_core_bin_load cfile.c:679 62 | #6 0x101a65b18 in binload radare2.c:467 63 | #7 0x101a58de8 in r_main_radare2 radare2.c:1371 64 | #8 0x10066734c in main radare2.c:104 65 | #9 0x186907e4c () 66 | 67 | SUMMARY: AddressSanitizer: heap-use-after-free new_rbtree.c:417 in r_rbnode_next 68 | Shadow bytes around the buggy address: 69 | 0x0070211721a0: fa fa fd fd fd fd fd fa fa fa 00 00 00 00 01 fa 70 | 0x0070211721b0: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd 71 | 0x0070211721c0: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd 72 | 0x0070211721d0: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd 73 | 0x0070211721e0: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd 74 | =>0x0070211721f0: fa fa fd fd fd fd fd fa fa fa fd[fd]fd fd fd fa 75 | 0x007021172200: fa fa fd fd fd fd fd fd fa fa 00 00 00 00 00 fa 76 | 0x007021172210: fa fa 00 00 00 00 00 fa fa fa 00 00 00 00 00 fa 77 | 0x007021172220: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd 78 | 0x007021172230: fa fa fd fd fd fd fd fd fa fa 00 00 00 00 00 fa 79 | 0x007021172240: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd 80 | Shadow byte legend (one shadow byte represents 8 application bytes): 81 | Addressable: 00 82 | Partially addressable: 01 02 03 04 05 06 07 83 | Heap left redzone: fa 84 | Freed heap region: fd 85 | Stack left redzone: f1 86 | Stack mid redzone: f2 87 | Stack right redzone: f3 88 | Stack after return: f5 89 | Stack use after scope: f8 90 | Global redzone: f9 91 | Global init order: f6 92 | Poisoned by user: f7 93 | Container overflow: fc 94 | Array cookie: ac 95 | Intra object redzone: bb 96 | ASan internal: fe 97 | Left alloca redzone: ca 98 | Right alloca redzone: cb 99 | ==66232==ABORTING 100 | -------------------------------------------------------------------------------- /python/docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | 18 | sys.path.insert(0, os.path.abspath("../r2papi")) 19 | 20 | 21 | # -- Project information ----------------------------------------------------- 22 | 23 | project = "r2papi" 24 | copyright = "2018, radare" 25 | author = "radare" 26 | 27 | # The short X.Y version 28 | version = "" 29 | # The full version, including alpha/beta/rc tags 30 | release = "" 31 | 32 | 33 | # -- General configuration --------------------------------------------------- 34 | 35 | # If your documentation needs a minimal Sphinx version, state it here. 36 | # 37 | # needs_sphinx = '1.0' 38 | 39 | # Add any Sphinx extension module names here, as strings. They can be 40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 41 | # ones. 42 | extensions = [ 43 | "sphinx.ext.autodoc", 44 | "sphinx.ext.todo", 45 | "sphinx.ext.viewcode", 46 | "sphinx.ext.napoleon", 47 | ] 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ["_templates"] 51 | 52 | # The suffix(es) of source filenames. 53 | # You can specify multiple suffix as a list of string: 54 | # 55 | # source_suffix = ['.rst', '.md'] 56 | source_suffix = ".rst" 57 | 58 | # The master toctree document. 59 | master_doc = "index" 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # 64 | # This is also used if you do content translation via gettext catalogs. 65 | # Usually you set "language" from the command line for these cases. 66 | # language = None 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | # This pattern also affects html_static_path and html_extra_path. 71 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 72 | 73 | # The name of the Pygments (syntax highlighting) style to use. 74 | pygments_style = None 75 | 76 | 77 | # -- Options for HTML output ------------------------------------------------- 78 | 79 | # The theme to use for HTML and HTML Help pages. See the documentation for 80 | # a list of builtin themes. 81 | # 82 | html_theme = "alabaster" 83 | 84 | # Theme options are theme-specific and customize the look and feel of a theme 85 | # further. For a list of options available for each theme, see the 86 | # documentation. 87 | # 88 | # html_theme_options = {} 89 | 90 | # Add any paths that contain custom static files (such as style sheets) here, 91 | # relative to this directory. They are copied after the builtin static files, 92 | # so a file named "default.css" will overwrite the builtin "default.css". 93 | # html_static_path = ["_static"] 94 | 95 | # Custom sidebar templates, must be a dictionary that maps document names 96 | # to template names. 97 | # 98 | # The default sidebars (for documents that don't match any pattern) are 99 | # defined by theme itself. Builtin themes are using these templates by 100 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 101 | # 'searchbox.html']``. 102 | # 103 | # html_sidebars = {} 104 | 105 | 106 | # -- Options for HTMLHelp output --------------------------------------------- 107 | 108 | # Output file base name for HTML help builder. 109 | htmlhelp_basename = "r2papidoc" 110 | 111 | 112 | # -- Options for LaTeX output ------------------------------------------------ 113 | 114 | latex_elements = { 115 | # The paper size ('letterpaper' or 'a4paper'). 116 | # 117 | # 'papersize': 'letterpaper', 118 | # The font size ('10pt', '11pt' or '12pt'). 119 | # 120 | # 'pointsize': '10pt', 121 | # Additional stuff for the LaTeX preamble. 122 | # 123 | # 'preamble': '', 124 | # Latex figure (float) alignment 125 | # 126 | # 'figure_align': 'htbp', 127 | } 128 | 129 | # Grouping the document tree into LaTeX files. List of tuples 130 | # (source start file, target name, title, 131 | # author, documentclass [howto, manual, or own class]). 132 | latex_documents = [ 133 | (master_doc, "r2papi.tex", "r2papi Documentation", "radare", "manual"), 134 | ] 135 | 136 | 137 | # -- Options for manual page output ------------------------------------------ 138 | 139 | # One entry per manual page. List of tuples 140 | # (source start file, name, description, authors, manual section). 141 | man_pages = [(master_doc, "r2papi", "r2papi Documentation", [author], 1)] 142 | 143 | 144 | # -- Options for Texinfo output ---------------------------------------------- 145 | 146 | # Grouping the document tree into Texinfo files. List of tuples 147 | # (source start file, target name, title, author, 148 | # dir menu entry, description, category) 149 | texinfo_documents = [ 150 | ( 151 | master_doc, 152 | "r2papi", 153 | "r2papi Documentation", 154 | author, 155 | "r2papi", 156 | "One line description of project.", 157 | "Miscellaneous", 158 | ), 159 | ] 160 | 161 | 162 | # -- Options for Epub output ------------------------------------------------- 163 | 164 | # Bibliographic Dublin Core info. 165 | epub_title = project 166 | 167 | # The unique identifier of the text. This can be a ISBN number 168 | # or the project homepage. 169 | # 170 | # epub_identifier = '' 171 | 172 | # A unique identification for the text. 173 | # 174 | # epub_uid = '' 175 | 176 | # A list of files that should not be packed into the epub file. 177 | epub_exclude_files = ["search.html"] 178 | 179 | 180 | # -- Extension configuration ------------------------------------------------- 181 | 182 | # -- Options for todo extension ---------------------------------------------- 183 | 184 | # If true, `todo` and `todoList` produce output, else they produce nothing. 185 | todo_include_todos = True 186 | -------------------------------------------------------------------------------- /typescript/async/shell.ts: -------------------------------------------------------------------------------- 1 | // shell utilities on top of r2pipe 2 | 3 | import { R2Papi } from "./r2papi.js"; 4 | 5 | /** 6 | * Interface to hold the name and description for every filesystem type parser implementation. 7 | * 8 | * @typedef FileSystemType 9 | */ 10 | export interface FileSystemType { 11 | /** 12 | * name of the filesystem format, to be used when mounting it. 13 | * 14 | * @type {string} 15 | */ 16 | name: string; 17 | /** 18 | * short string that describes the plugin 19 | * 20 | * @type {string} 21 | */ 22 | description: string; 23 | } 24 | 25 | /** 26 | * Global static class providing information about the actual radare2 in use. 27 | * This class mimics the `Frida` global object in frida, which can be useful to 28 | * determine the executing environment of the script 29 | * 30 | * @typedef Radare2 31 | */ 32 | export interface Radare2 { 33 | /** 34 | * string representing the radare2 version (3 numbers separated by dots) 35 | * 36 | * @type {string} 37 | */ 38 | version: string; 39 | } 40 | 41 | /** 42 | * Class that interacts with the `r2ai` plugin (requires `rlang-python` and `r2i` r2pm packages to be installed). 43 | * Provides a way to script the interactions with different language models using javascript from inside radare2. 44 | * 45 | * @typedef R2Shell 46 | */ 47 | export class R2Shell { 48 | /** 49 | * Keep a reference to the associated r2papi instance 50 | * 51 | * @type {R2Papi} 52 | */ 53 | public rp: R2Papi; 54 | 55 | /** 56 | * Create a new instance of the R2Shell 57 | * 58 | * @param {R2Papi} take the R2Papi intance to used as backend to run the commands 59 | * @returns {R2Shell} instance of the shell api 60 | */ 61 | constructor(papi: R2Papi) { 62 | this.rp = papi; 63 | } 64 | 65 | /** 66 | * Create a new directory in the host system, if the opational recursive argument is set to 67 | * true it will create all the necessary subdirectories instead of just the specified one. 68 | * 69 | * @param {string} text path to the new directory to be created 70 | * @param {boolean?} disabled by default, but if it's true, it will create subdirectories recursively if necessary 71 | * @returns {boolean} true if successful 72 | */ 73 | mkdir(file: string, recursive?: boolean): boolean { 74 | if (recursive === true) { 75 | this.rp.call(`mkdir -p ${file}`); 76 | } else { 77 | this.rp.call(`mkdir ${file}`); 78 | } 79 | return true; 80 | } 81 | 82 | /** 83 | * Deletes a file 84 | * 85 | * @param {string} path to the file to remove 86 | * @returns {boolean} true if successful 87 | */ 88 | unlink(file: string): boolean { 89 | this.rp.call(`rm ${file}`); 90 | return true; 91 | } 92 | 93 | /** 94 | * Change current directory 95 | * 96 | * @param {string} path to the directory 97 | * @returns {boolean} true if successful 98 | */ 99 | async chdir(path: string): Promise { 100 | await this.rp.call(`cd ${path}`); 101 | return true; 102 | } 103 | 104 | /** 105 | * List files in the current directory 106 | * 107 | * @returns {string[]} array of file names 108 | */ 109 | async ls(): Promise { 110 | const files = await this.rp.call(`ls -q`); 111 | return files.trim().split("\n"); 112 | } 113 | 114 | /** 115 | * TODO: Checks if a file exists (not implemented) 116 | * 117 | * @returns {boolean} true if the file exists, false if it does not 118 | */ 119 | fileExists(path: string): boolean { 120 | // TODO 121 | return false; 122 | } 123 | 124 | /** 125 | * Opens an URL or application 126 | * Execute `xdg-open` on linux, `start` on windows, `open` on Mac 127 | * 128 | * @param {string} URI or file to open by the system 129 | */ 130 | async open(arg: string): Promise { 131 | await this.rp.call(`open ${arg}`); 132 | } 133 | 134 | /** 135 | * Run a system command and get the return code 136 | * 137 | * @param {string} system command to be executed 138 | * @returns {number} return code (0 is success) 139 | */ 140 | system(cmd: string): number { 141 | this.rp.call(`!${cmd}`); 142 | return 0; 143 | } 144 | 145 | /** 146 | * Mount the given offset on the specified path using the filesytem. 147 | * This is not a system-level mountpoint, it's using the internal filesystem abstraction of radare2. 148 | * 149 | * @param {string} filesystem type name (see . 150 | * @param {string} system command to be executed 151 | * @param {string|number} 152 | * @returns {number} return code (0 is success) 153 | */ 154 | mount(fstype: string, path: string, addr: string | number): boolean { 155 | if (!addr) { 156 | addr = 0; 157 | } 158 | this.rp.call(`m ${fstype} ${path} ${addr}`); 159 | return true; 160 | } 161 | /** 162 | * Unmount the mountpoint associated with the given path. 163 | * 164 | * @param {string} path to the mounted filesystem 165 | * @returns {void} TODO: should return boolean 166 | */ 167 | async umount(path: string): Promise { 168 | await this.rp.call(`m-${path}`); 169 | } 170 | /** 171 | * Change current directory on the internal radare2 filesystem 172 | * 173 | * @param {string} path name to change to 174 | * @returns {void} TODO: should return boolean 175 | */ 176 | async chdir2(path: string): Promise { 177 | await this.rp.call(`mdq ${path}`); 178 | } 179 | /** 180 | * List the files contained in the given path within the virtual radare2 filesystem. 181 | * 182 | * @param {string} path name to change to 183 | * @returns {void} TODO: should return boolean 184 | */ 185 | async ls2(path: string): Promise { 186 | const files = await this.rp.call(`mdq ${path}`); 187 | return files.trim().split("\n"); 188 | } 189 | /** 190 | * Enumerate all the mountpoints set in the internal virtual filesystem of radare2 191 | * @returns {any[]} array of mount 192 | */ 193 | async enumerateFilesystemTypes(): Promise { 194 | return this.rp.cmdj("mLj"); 195 | } 196 | /** 197 | * Enumerate all the mountpoints set in the internal virtual filesystem of radare2 198 | * @returns {any[]} array of mount 199 | */ 200 | async enumerateMountpoints(): Promise { 201 | const output = await this.rp.cmdj("mj"); 202 | return output["mountpoints"]; 203 | } 204 | /** 205 | * TODO: not implemented 206 | */ 207 | isSymlink(file: string): boolean { 208 | return false; 209 | } 210 | /** 211 | * TODO: not implemented 212 | */ 213 | isDirectory(file: string): boolean { 214 | return false; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /rust/src/structs.rs: -------------------------------------------------------------------------------- 1 | //! Provides structures for JSON encoding and decoding 2 | 3 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 4 | pub struct LOpInfo { 5 | pub esil: Option, 6 | pub offset: Option, 7 | pub opcode: Option, 8 | #[serde(rename = "type")] 9 | pub optype: Option, 10 | pub size: Option, 11 | pub bytes: Option, 12 | } 13 | 14 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 15 | pub struct LFunctionInfo { 16 | pub addr: Option, 17 | pub name: Option, 18 | pub ops: Option>, 19 | pub size: Option, 20 | } 21 | 22 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 23 | pub struct LRegInfo { 24 | pub alias_info: Vec, 25 | pub reg_info: Vec, 26 | } 27 | 28 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 29 | pub struct LAliasInfo { 30 | pub reg: String, 31 | pub role: u64, 32 | pub role_str: String, 33 | } 34 | 35 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 36 | pub struct LRegProfile { 37 | pub name: String, 38 | pub offset: usize, 39 | pub size: usize, 40 | pub type_str: String, 41 | #[serde(rename = "type")] 42 | pub regtype: usize, 43 | } 44 | 45 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 46 | pub struct LFlagInfo { 47 | pub offset: u64, 48 | pub name: String, 49 | pub size: u64, 50 | } 51 | 52 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 53 | pub struct LBinInfo { 54 | pub core: Option, 55 | pub bin: Option, 56 | } 57 | 58 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 59 | pub struct LCoreInfo { 60 | pub file: Option, 61 | pub size: Option, 62 | } 63 | 64 | #[derive(Debug, Copy, Clone, Serialize, Deserialize)] 65 | #[serde(rename = "endian")] 66 | pub enum Endian { 67 | #[serde(rename = "big")] 68 | Big, 69 | #[serde(rename = "little")] 70 | Little, 71 | } 72 | 73 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 74 | pub struct LBin { 75 | pub arch: Option, 76 | pub bits: Option, 77 | pub endian: Option, 78 | } 79 | 80 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 81 | pub struct FunctionInfo { 82 | pub callrefs: Option>, 83 | pub calltype: Option, 84 | pub codexrefs: Option>, 85 | pub datarefs: Option>, 86 | pub dataxrefs: Option>, 87 | pub name: Option, 88 | pub offset: Option, 89 | pub realsz: Option, 90 | pub size: Option, 91 | #[serde(rename = "type")] 92 | pub ftype: Option, 93 | #[serde(skip)] 94 | pub locals: Option>, 95 | } 96 | 97 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 98 | pub struct LCallInfo { 99 | #[serde(rename = "addr")] 100 | pub target: Option, 101 | #[serde(rename = "type")] 102 | pub call_type: Option, 103 | #[serde(rename = "at")] 104 | pub source: Option, 105 | } 106 | 107 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 108 | pub struct LSectionInfo { 109 | pub flags: Option, 110 | pub name: Option, 111 | pub paddr: Option, 112 | pub size: Option, 113 | pub vaddr: Option, 114 | pub vsize: Option, 115 | } 116 | 117 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 118 | pub struct LStringInfo { 119 | pub length: Option, 120 | pub ordinal: Option, 121 | pub paddr: Option, 122 | pub section: Option, 123 | pub size: Option, 124 | pub string: Option, 125 | pub vaddr: Option, 126 | #[serde(rename = "type")] 127 | pub stype: Option, 128 | } 129 | 130 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 131 | pub struct LVarInfo { 132 | pub name: Option, 133 | pub kind: Option, 134 | #[serde(rename = "type")] 135 | pub vtype: Option, 136 | #[serde(rename = "ref")] 137 | pub reference: Option, 138 | } 139 | 140 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 141 | pub struct LVarRef { 142 | pub base: Option, 143 | pub offset: Option, 144 | } 145 | 146 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 147 | pub struct LCCInfo { 148 | pub ret: Option, 149 | pub args: Option>, 150 | #[serde(rename = "float_args")] 151 | pub fargs: Option>, 152 | } 153 | 154 | #[derive(Debug, Clone, Serialize, Deserialize)] 155 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 156 | // Taken from ELF Spec 157 | pub enum LSymbolType { 158 | Notype, 159 | Obj, 160 | Func, 161 | Section, 162 | File, 163 | Common, 164 | Loos, 165 | Hios, 166 | Loproc, 167 | SparcRegister, 168 | HiProc, 169 | } 170 | 171 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 172 | pub struct LSymbolInfo { 173 | pub demname: Option, 174 | pub flagname: Option, 175 | pub name: Option, 176 | pub paddr: Option, 177 | pub size: Option, 178 | #[serde(rename = "type")] 179 | pub stype: Option, 180 | pub vaddr: Option, 181 | } 182 | 183 | #[derive(Debug, Clone, Serialize, Deserialize)] 184 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 185 | // Taken from ELF Spec 186 | pub enum LBindType { 187 | Local, 188 | Global, 189 | GLOBAL, 190 | Weak, 191 | Loos, 192 | Hios, 193 | Loproc, 194 | Hiproc, 195 | None, 196 | NONE, 197 | } 198 | 199 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 200 | pub struct LImportInfo { 201 | pub bind: Option, 202 | pub name: Option, 203 | pub ordinal: Option, 204 | pub plt: Option, 205 | #[serde(rename = "type")] 206 | pub itype: Option, 207 | } 208 | 209 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 210 | pub struct LEntry { 211 | pub vaddr: Option, 212 | pub paddr: Option, 213 | pub baddr: Option, 214 | pub laddr: Option, 215 | pub hvaddr: Option, 216 | pub haddr: Option, 217 | #[serde(rename = "type")] 218 | pub etype: Option, 219 | } 220 | 221 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 222 | pub struct LExportInfo { 223 | pub name: Option, 224 | pub flagname: Option, 225 | pub realname: Option, 226 | pub odinal: Option, 227 | pub bind: Option, 228 | pub size: Option, 229 | #[serde(rename = "type")] 230 | pub etype: Option, 231 | pub vaddr: Option, 232 | pub paddr: Option, 233 | pub is_imported: Option, 234 | } 235 | 236 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 237 | pub struct LRelocInfo { 238 | pub is_ifunc: Option, 239 | pub name: Option, 240 | pub paddr: Option, 241 | #[serde(rename = "type")] 242 | // TODO 243 | pub rtype: Option, 244 | pub vaddr: Option, 245 | } 246 | 247 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 248 | pub struct LArch { 249 | pub bins: Vec, 250 | } 251 | 252 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 253 | pub struct Arch { 254 | pub arch: Option, 255 | pub bits: Option, 256 | pub offset: Option, 257 | pub size: Option, 258 | pub machine: Option, 259 | } 260 | 261 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 262 | pub struct Seek { 263 | pub offset: Option, 264 | pub name: Option, 265 | pub current: Option, 266 | } 267 | 268 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 269 | pub struct Hashes { 270 | pub md5: Option, 271 | pub sha1: Option, 272 | } 273 | 274 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 275 | pub struct LEsilRegs { 276 | pub regs: Vec, 277 | } 278 | 279 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 280 | pub struct Reg { 281 | pub name: Option, 282 | pub value: Option, 283 | } 284 | 285 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 286 | pub struct Segment { 287 | pub name: Option, 288 | pub size: Option, 289 | pub vsize: Option, 290 | pub perm: Option, 291 | pub paddr: Option, 292 | pub vaddr: Option, 293 | } 294 | 295 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 296 | pub struct Buffer { 297 | pub raised: Option, 298 | pub fd: Option, 299 | pub uri: Option, 300 | pub from: Option, 301 | pub writable: Option, 302 | pub size: Option, 303 | } 304 | -------------------------------------------------------------------------------- /typescript/async/r2pipe.ts: -------------------------------------------------------------------------------- 1 | export class R2PipeAsyncFromSync { 2 | r2p: R2Pipe; 3 | constructor(r2p: R2Pipe) { 4 | this.r2p = r2p; 5 | } 6 | /** 7 | * Run a command in the associated instance of radare2 and return the output as a string 8 | * 9 | * @param {string} command to be executed inside radare2. 10 | * @returns {string} The output of the command execution 11 | */ 12 | async cmd(command: string): Promise { 13 | return this.r2p.cmd(command); 14 | } 15 | async cmdAt( 16 | command: string, 17 | address: number | string | any 18 | ): Promise { 19 | return this.r2p.cmdAt(command, address); 20 | } 21 | async cmdj(cmd: string): Promise { 22 | return this.r2p.cmdj(cmd); 23 | } 24 | async call(command: string): Promise { 25 | return this.r2p.call(command); 26 | } 27 | async callj(cmd: string): Promise { 28 | return this.r2p.cmdj(cmd); 29 | } 30 | async callAt( 31 | command: string, 32 | address: number | string | any 33 | ): Promise { 34 | return this.r2p.cmdAt(command, address); 35 | } 36 | async log(msg: string) { 37 | return this.r2p.log(msg); 38 | } 39 | async plugin(type: string, maker: any): Promise { 40 | return this.r2p.plugin(type, maker); 41 | } 42 | async unload(type: string, name: string): Promise { 43 | return this.r2p.unload(type, name); 44 | } 45 | } 46 | 47 | export function newAsyncR2PipeFromSync(r2p: R2Pipe): R2PipeAsync { 48 | const asyncR2Pipe = new R2PipeAsyncFromSync(r2p); 49 | return asyncR2Pipe as R2PipeAsync; 50 | } 51 | 52 | /** 53 | * Generic interface to interact with radare2, abstracts the access to the associated 54 | * instance of the tool, which could be native via rlang or remote via pipes or tcp/http. 55 | * 56 | * @typedef R2Pipe 57 | */ 58 | export interface R2Pipe { 59 | /** 60 | * Run a command in the associated instance of radare2 and return the output as a string 61 | * 62 | * @param {string} command to be executed inside radare2. 63 | * @returns {string} The output of the command execution 64 | */ 65 | cmd(cmd: string): string; 66 | 67 | /** 68 | * Run a radare2 command in a different address. Same as `.cmd(x + '@ ' + a)` 69 | * 70 | * @param {string} command to be executed inside radare2. 71 | * @param {number|string|NativePointer} command to be executed inside radare2. 72 | * @returns {string} The output of the command execution 73 | */ 74 | cmdAt(cmd: string, address: number | string | any): string; 75 | 76 | /** 77 | * Run a radare2 command expecting the output to be JSON 78 | * 79 | * @param {string} command to be executed inside radare2. The given command should end with `j` 80 | * @returns {object} the JSON decoded object from the output of the command 81 | */ 82 | cmdj(cmd: string): any; 83 | 84 | /** 85 | * Call a radare2 command. This is similar to `R2Pipe.cmd`, but skips all the command parsing rules, 86 | * which is safer and faster but you cannot use any special modifier like `@`, `~`, ... 87 | * 88 | * See R2Pipe.callAt() to call a command on a different address 89 | * 90 | * @param {string} command to be executed inside radare2. The given command should end with `j` 91 | * @returns {object} the JSON decoded object from the output of the command 92 | */ 93 | call(cmd: string): string; 94 | 95 | /** 96 | * Call a radare2 command in a different address 97 | * 98 | * @param {string} command to be executed inside radare2. The given command should end with `j` 99 | * @param {NativePointer|string|number} where to seek to execute this command (previous offset is restored after executing it) 100 | * @returns {string} the string containing the output of the command 101 | */ 102 | callAt(cmd: string, address: string | number | any): string; 103 | 104 | /** 105 | * Same as cmdj but using .call which avoids command injection problems 106 | * 107 | * @param {string} command to be executed inside radare2. The given command should end with `j` 108 | * @returns {object} the JSON decoded object from the command output 109 | */ 110 | callj(cmd: string): any; 111 | 112 | /** 113 | * Log a string to the associated console. This is used internally by `console.log` in some implementations. 114 | * 115 | * @param {string} text to be displayed 116 | */ 117 | log(msg: string): void; 118 | 119 | /** 120 | * Instantiate a new radare2 plugin with the given type and constructor method. 121 | * 122 | * @param {string} type of plugin ("core", "io", "arch", ...) 123 | * @param {string} function that returns the plugin definition 124 | * @returns {boolean} true if successful 125 | */ 126 | plugin(type: string, maker: any): boolean; 127 | 128 | /** 129 | * Unload the plugin associated with a `type` and a `name`. 130 | * 131 | * @param {string} type of plugin ("core", "io", "arch", ...) 132 | * @param {string} name of the plugin 133 | * @returns {boolean} true if successful 134 | */ 135 | unload(type: string, name: string): boolean; 136 | } 137 | 138 | /** 139 | * Generic interface to interact with radare2, abstracts the access to the associated 140 | * instance of the tool, which could be native via rlang or remote via pipes or tcp/http. 141 | * 142 | * @typedef R2PipeAsync 143 | */ 144 | export interface R2PipeAsync { 145 | /** 146 | * Run a command in the associated instance of radare2 147 | * 148 | * @param {string} command to be executed inside radare2. 149 | * @returns {string} The output of the command execution 150 | */ 151 | cmd(cmd: string): Promise; 152 | 153 | /** 154 | * Run a radare2 command in a different address. Same as `.cmd(x + '@ ' + a)` 155 | * 156 | * @param {string} command to be executed inside radare2. 157 | * @param {number|string|NativePointer} command to be executed inside radare2. 158 | * @returns {string} The output of the command execution 159 | */ 160 | cmdAt(cmd: string, address: number | string | any): Promise; 161 | 162 | /** 163 | * Run a radare2 command expecting the output to be JSON 164 | * 165 | * @param {string} command to be executed inside radare2. The given command should end with `j` 166 | * @returns {object} the JSON decoded object from the output of the command 167 | */ 168 | cmdj(cmd: string): Promise; 169 | 170 | /** 171 | * Call a radare2 command. This is similar to `R2Pipe.cmd`, but skips all the command parsing rules, 172 | * which is safer and faster but you cannot use any special modifier like `@`, `~`, ... 173 | * 174 | * See R2Pipe.callAt() to call a command on a different address 175 | * 176 | * @param {string} command to be executed inside radare2. The given command should end with `j` 177 | * @returns {object} the JSON decoded object from the output of the command 178 | */ 179 | call(cmd: string): Promise; 180 | 181 | /** 182 | * Call a radare2 command in a different address 183 | * 184 | * @param {string} command to be executed inside radare2. The given command should end with `j` 185 | * @param {NativePointer|string|number} where to seek to execute this command (previous offset is restored after executing it) 186 | * @returns {string} the string containing the output of the command 187 | */ 188 | callAt(cmd: string, address: string | number | any): Promise; 189 | 190 | /** 191 | * Same as cmdj but using .call which avoids command injection problems 192 | * 193 | * @param {string} command to be executed inside radare2. The given command should end with `j` 194 | * @returns {object} the JSON decoded object from the command output 195 | */ 196 | callj(cmd: string): Promise; 197 | 198 | /** 199 | * Log a string to the associated console. This is used internally by `console.log` in some implementations. 200 | * 201 | * @param {string} text to be displayed 202 | */ 203 | log(msg: string): void; 204 | 205 | /** 206 | * Instantiate a new radare2 plugin with the given type and constructor method. 207 | * 208 | * @param {string} type of plugin ("core", "io", "arch", ...) 209 | * @param {string} function that returns the plugin definition 210 | * @returns {boolean} true if successful 211 | */ 212 | plugin(type: string, maker: any): Promise; 213 | 214 | /** 215 | * Unload the plugin associated with a `type` and a `name`. 216 | * 217 | * @param {string} type of plugin ("core", "io", "arch", ...) 218 | * @param {string} name of the plugin 219 | * @returns {boolean} true if successful 220 | */ 221 | unload(type: string, name: string): Promise; 222 | } 223 | 224 | /** 225 | * A global instance of R2Pipe associated with the current instance of radare2 226 | * 227 | * @type {R2PipeSync} 228 | */ 229 | export declare const r2: R2Pipe; 230 | -------------------------------------------------------------------------------- /python/r2papi/r2api.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import r2pipe 4 | 5 | from r2papi.base import R2Base, Result, ResultArray 6 | from r2papi.config import Config 7 | from r2papi.debugger import Debugger 8 | from r2papi.esil import Esil 9 | from r2papi.file import File 10 | from r2papi.flags import Flags 11 | from r2papi.print import Print 12 | from r2papi.write import Write 13 | 14 | 15 | class Function(R2Base): 16 | """Class representing a function in radare2.""" 17 | 18 | def __init__(self, r2, addr): 19 | """ 20 | Args: 21 | addr (str): Beginning of the function, it can be an offset, 22 | function name... 23 | """ 24 | super().__init__(r2) 25 | 26 | self.offset = addr 27 | 28 | def analyze(self): 29 | """Analyze the function. It uses the radare2 ``af`` command.""" 30 | self._exec("af %s" % self.offset) 31 | 32 | def info(self): 33 | """Get the function information, using the radare2 ``afi`` command. 34 | 35 | Returns: 36 | :class:`r2api.base.Result`: Function information 37 | """ 38 | # XXX: Is this [0] always correct? 39 | res = self._exec("afij @ %s" % self.offset, json=True)[0] 40 | return Result(res) 41 | 42 | def rename(self, name): 43 | """Uses the radare2 ``afn`` command""" 44 | self._exec("afn %s %s" % (name, self.offset)) 45 | 46 | def graphImg(self, path=""): 47 | """ 48 | .. todo:: 49 | 50 | Return Graph object (does not exist yet), that should have a image 51 | method as there are more stuff that can be done with graphs? 52 | 53 | Save the function graph as a GIF image. By default, it's saved in 54 | ``{functionname}-graph.gif`` 55 | 56 | Args: 57 | path (str, optional): 58 | Path to store the image (including filename). 59 | """ 60 | path = "%s-graph.gif" % self.name if path == "" else path 61 | self._exec("e asm.comments=0") 62 | self._exec("e asm.var=0") 63 | self._exec("e asm.flags=0") 64 | self._exec("agfw %s @ %s" % (path, self.offset)) 65 | 66 | @property 67 | def name(self): 68 | """ 69 | Property where the getter returns the name of the function, and the 70 | setter changes it (using :meth:`r2api.r2api.Function.rename`). 71 | """ 72 | return self.info().name 73 | 74 | @name.setter 75 | def name(self, value): 76 | self.rename(value) 77 | 78 | 79 | class R2Api(R2Base): 80 | """Main class in ``r2papi``, it contains all the methods and objects 81 | used. 82 | 83 | A ``with`` statement can be used to make sure that the radare2 process is 84 | closed. 85 | 86 | .. code-block:: python 87 | 88 | with R2Api('/bin/ls') as r: 89 | r.print.hexdump() 90 | 91 | Attributes: 92 | print (:class:`r2pipe.print.Print`): Used only in Python3. 93 | All kind of things related with the print command in ``radare2``, 94 | this includes from getting an hexdump to get the dissasembly of a 95 | function. 96 | print2 (:class:`r2pipe.print.Print`): Used only in Python2 because 97 | ``print`` is a reserved keyword. see the previous attribute for 98 | further description. 99 | write (:class:`r2pipe.write.Write`): Write related operations, write 100 | binary data, strings, assembly... 101 | config (:class:`r2pipe.config.Config`): Configure radare2, like the 102 | ``e`` command in the radare console. Control all kind of stuff like 103 | architecture, analysis options... 104 | flags (:class:`r2pipe.flags.Flags`): Create and manage flags, those are 105 | conceptually similar to bookmarks. They associate a name with an 106 | offset. 107 | esil (:class:`r2pipe.flags.Esil`): Esil is the IL of radare2, it's 108 | string based and can be used, among others, to emulate code. 109 | """ 110 | 111 | def __init__(self, filename=None, r2=None): 112 | """ 113 | Args: 114 | filename (str): Filename to open, it accepts everything that radare 115 | accepts, so ``'-'`` opens ``malloc://512``. 116 | r2 (r2pipe.OpenBase): r2pipe object, only used if filename is None. 117 | """ 118 | if filename: 119 | if r2pipe.in_r2(): 120 | r2 = r2pipe.open() 121 | else: 122 | r2 = r2pipe.open(filename) 123 | super().__init__(r2) 124 | 125 | self.debugger = Debugger(r2) 126 | 127 | self.print = Print(r2) 128 | 129 | self.write = Write(r2) 130 | self.config = Config(r2) 131 | self.flags = Flags(r2) 132 | self.esil = Esil(r2) 133 | 134 | self.info = lambda: Result(self._exec("ij", json=True)) 135 | self.searchIn = lambda x: self._exec("e search.in=%s" % (x)) 136 | self.analyzeAll = lambda: self._exec("aaa") 137 | self.analyzeCalls = lambda: self._exec("aac") 138 | self.basicBlocks = lambda: ResultArray(self._exec("afbj", json=True)) 139 | self.xrefsAt = lambda: ResultArray( 140 | self._exec("axtj %s" % self._tmp_off, json=True) 141 | ) 142 | self.refsTo = lambda: ResultArray(self._exec("axfj", json=True)) 143 | self.opInfo = lambda: ResultArray( 144 | self._exec("aoj %s" % self._tmp_off, json=True) 145 | )[0] 146 | self.seek = lambda x: self._exec("s %s" % (x)) 147 | 148 | def __enter__(self): 149 | return self 150 | 151 | def __exit__(self, e_type, e_val, tb): 152 | self.quit() 153 | 154 | def open(self, filename, at="", perms=""): 155 | # See o? 156 | self._exec("o %s %s %s" % (filename, at, perms)) 157 | 158 | @property 159 | def files(self): 160 | """ 161 | list: returns a list of :class:`r2api.file.File` objects. 162 | """ 163 | files = self._exec("oj", json=True) 164 | return [File(self.r2, f["fd"]) for f in files] 165 | 166 | def function(self): 167 | """Get the function at the current or temporary seek, if it exists. 168 | 169 | Example: 170 | 171 | .. code-block:: python 172 | 173 | with R2Api('bin') as r: 174 | r.analyzeAll() 175 | func = r.at(0x100).function() 176 | print(func.name) 177 | func = r.at('flag').function() 178 | print(func.offset) 179 | 180 | .. todo:: 181 | 182 | Raise exception instead of returning None when the function does not 183 | exist? 184 | 185 | Returns: 186 | :class:`r2api.r2api.Function`: Function found or None. 187 | """ 188 | function_name = self._exec("afn. %s" % self._tmp_off) 189 | self._tmp_off = "" 190 | return self.functionByName(function_name) 191 | 192 | def functions(self): 193 | """ 194 | .. note:: 195 | 196 | If no function is returned, remember to anaylze de binary first. 197 | 198 | Returns: 199 | list: List of :class:`r2api.r2api.Function` objects, representing 200 | all the functions in the binary. 201 | """ 202 | res = self._exec("aflj", json=True) 203 | if not res: 204 | res = self._exec("isj", json=True) 205 | return [Function(self.r2, f["addr"]) for f in res] if res else [] 206 | 207 | def functionByName(self, name): 208 | """ 209 | Args: 210 | name (str): Name of the target function. 211 | Returns: 212 | :class:`r2api.r2api.Function`: Function or None. 213 | """ 214 | res = [func for func in self.functions() if func.name == name] 215 | if not res: 216 | return None 217 | if len(res) == 1: 218 | return res[0] 219 | # TODO: Is this possible? 220 | raise ValueError("One name returned more than one function") 221 | 222 | def read(self, n): 223 | """Get ``n`` bytes as a binary string from the current offset. 224 | 225 | Args: 226 | n (int): Number of bytes to read. 227 | Returns: 228 | bytes: Binary string containing the data in python2, ``bytes`` 229 | object in python3. 230 | """ 231 | res = self._exec("p8 %s%s|" % (n, self._tmp_off)) 232 | self._tmp_off = "" 233 | return bytes.fromhex(res) 234 | 235 | def __getitem__(self, k): 236 | if type(k) == slice: 237 | _from = k.start 238 | if type(k.start) == str: 239 | _from = self.sym_to_addr(k.start) 240 | 241 | _to = k.stop 242 | if type(k.stop) == str: 243 | _to = self.sym_to_addr(k.stop) 244 | 245 | read_len = _to - _from 246 | at_addr = _from 247 | else: 248 | read_len = 1 249 | at_addr = k 250 | return self.print.at(str(at_addr)).bytes(read_len) 251 | 252 | def __setitem__(self, k, v): 253 | return self.write.at(k).bytes(v) 254 | 255 | def quit(self): 256 | """Closes the radare2 process.""" 257 | if self.r2: 258 | self.r2.quit() 259 | self.r2 = None 260 | -------------------------------------------------------------------------------- /python/r2papi/search.py: -------------------------------------------------------------------------------- 1 | from r2papi.base import R2Base, ResultArray 2 | 3 | 4 | class Search(R2Base): 5 | """ 6 | Wrapper for radare2 ``/`` (search) commands. 7 | """ 8 | 9 | def _clean_output(self, raw: str) -> list[str]: 10 | """ 11 | Return a list of non‑warning lines from ``raw``. 12 | Lines that start with ``WARN:`` or ``INFO:`` (case‑insensitive) are 13 | discarded because they do not contain the usual ``offset hit data`` 14 | format expected by the parsers. 15 | """ 16 | return [ 17 | ln 18 | for ln in raw.strip().splitlines() 19 | if ln and not ln.lower().startswith(("warn:", "info:")) 20 | ] 21 | 22 | def string(self, pattern: str): 23 | """ 24 | Search for a null‑terminated string ``pattern``. 25 | """ 26 | return self._exec(f"/ {pattern}") 27 | 28 | def string_json(self, pattern: str): 29 | """ 30 | JSON version of :meth:`string`. 31 | """ 32 | ret = self._exec(f"/j {pattern}", json=True) 33 | return ResultArray(ret) 34 | 35 | def inverse_hex(self, hexbytes: str): 36 | """ 37 | Inverse hex‑search (find first byte *different* from ``hexbytes``). 38 | Returns a ``ResultArray`` where each ``Result`` has ``offset``, ``hit`` and ``data`` fields. 39 | """ 40 | raw = self._exec(f"/!x {hexbytes}") 41 | 42 | # Split output into separate lines – the command may return many hits. 43 | lines = self._clean_output(raw) 44 | 45 | results = [] 46 | for line in lines: 47 | try: 48 | offset_str, hit, data = line.split(maxsplit=2) 49 | except ValueError: 50 | results.append({"error": "unexpected line format", "raw": line}) 51 | continue 52 | 53 | try: 54 | offset = int(offset_str, 16) 55 | except ValueError: 56 | offset = offset_str 57 | 58 | results.append({"offset": offset, "hit": hit, "data": data}) 59 | 60 | return ResultArray(results) 61 | 62 | def base_address(self): 63 | """Search for a possible base address – ``/B``.""" 64 | return self._exec("/B") 65 | 66 | def deltified(self, hexseq: str): 67 | """ 68 | Search for a *deltified* sequence of bytes. 69 | ``/d ``. 70 | """ 71 | raw = self._exec(f"/d {hexseq}") 72 | 73 | lines = self._clean_output(raw) 74 | 75 | results = [] 76 | for line in lines: 77 | try: 78 | offset_str, hit, data = line.split(maxsplit=2) 79 | except ValueError: 80 | results.append({"error": "unexpected line format", "raw": line}) 81 | continue 82 | 83 | try: 84 | offset = int(offset_str, 16) 85 | except ValueError: 86 | offset = offset_str 87 | 88 | results.append({"offset": offset, "hit": hit, "data": data}) 89 | 90 | return ResultArray(results) 91 | 92 | def file(self, filename: str, offset: int = None, size: int = None): 93 | """ 94 | Search the contents of a file with offset and size. 95 | """ 96 | cmd = f"/F {filename}" 97 | if offset is not None: 98 | cmd += f" {offset}" 99 | if size is not None: 100 | cmd += f" {size}" 101 | raw = self._exec(cmd) 102 | 103 | lines = self._clean_output(raw) 104 | 105 | results = [] 106 | for line in lines: 107 | try: 108 | offset_str, hit, data = line.split(maxsplit=2) 109 | except ValueError: 110 | results.append({"error": "unexpected line format", "raw": line}) 111 | continue 112 | 113 | try: 114 | offset_val = int(offset_str, 16) 115 | except ValueError: 116 | offset_val = offset_str 117 | 118 | results.append({"offset": offset_val, "hit": hit, "data": data}) 119 | 120 | return ResultArray(results) 121 | 122 | def case_insensitive(self, pattern: str): 123 | """Case‑insensitive string search – ``/i ``.""" 124 | raw = self._exec(f"/i {pattern}") 125 | 126 | lines = self._clean_output(raw) 127 | 128 | results = [] 129 | for line in lines: 130 | try: 131 | offset_str, hit, data = line.split(maxsplit=2) 132 | except ValueError: 133 | results.append({"error": "unexpected line format", "raw": line}) 134 | continue 135 | 136 | try: 137 | offset = int(offset_str, 16) 138 | except ValueError: 139 | offset = offset_str 140 | 141 | results.append({"offset": offset, "hit": hit, "data": data}) 142 | 143 | return ResultArray(results) 144 | 145 | def rabin_karp(self, pattern: str): 146 | """Search using the Rabin‑Karp algorithm – ``/k ``.""" 147 | raw = self._exec(f"/k {pattern}") 148 | 149 | lines = self._clean_output(raw) 150 | 151 | results = [] 152 | for line in lines: 153 | try: 154 | offset_str, hit, data = line.split(maxsplit=2) 155 | except ValueError: 156 | results.append({"error": "unexpected line format", "raw": line}) 157 | continue 158 | 159 | try: 160 | offset = int(offset_str, 16) 161 | except ValueError: 162 | offset = offset_str 163 | 164 | results.append({"offset": offset, "hit": hit, "data": data}) 165 | 166 | return ResultArray(results) 167 | 168 | def entropy(self, threshold: int = None): 169 | """ 170 | Find sections by grouping blocks with similar entropy. 171 | ``/s[*] [threshold]`` – ``*`` can be omitted; *threshold* is optional. 172 | Returns a ``ResultArray`` where each entry has ``start``, ``end`` and 173 | ``entropy`` fields. 174 | """ 175 | cmd = "/s" 176 | if threshold is not None: 177 | cmd += f" {threshold}" 178 | raw = self._exec(cmd) 179 | 180 | # 0x100002000 - 0x100002100 ~ 4.535647 181 | lines = self._clean_output(raw) 182 | 183 | results = [] 184 | for line in lines: 185 | try: 186 | parts = line.split() 187 | start_str, _, end_str, _, entropy_str = parts[:5] 188 | 189 | start = int(start_str, 16) 190 | end = int(end_str, 16) 191 | entropy = float(entropy_str) 192 | results.append( 193 | {"start": start, "end": end, "entropy": entropy, "raw": line} 194 | ) 195 | except Exception: 196 | results.append({"error": "unexpected line format", "raw": line}) 197 | 198 | return ResultArray(results) 199 | 200 | def wide_string(self, pattern: str): 201 | """Wide string search – ``/w ``.""" 202 | raw = self._exec(f"/w {pattern}") 203 | 204 | lines = self._clean_output(raw) 205 | 206 | results = [] 207 | for line in lines: 208 | try: 209 | offset_str, hit, data = line.split(maxsplit=2) 210 | except ValueError: 211 | results.append({"error": "unexpected line format", "raw": line}) 212 | continue 213 | 214 | try: 215 | offset = int(offset_str, 16) 216 | except ValueError: 217 | offset = offset_str 218 | 219 | results.append({"offset": offset, "hit": hit, "data": data}) 220 | 221 | return ResultArray(results) 222 | 223 | def wide_string_json(self, pattern: str): 224 | """Wide string search – JSON output – ``/wj ``.""" 225 | ret = self._exec(f"/wj {pattern}", json=True) 226 | return ResultArray(ret) 227 | 228 | def wide_string_ci(self, pattern: str): 229 | """Case‑insensitive wide‑string search – ``/wi ``.""" 230 | raw = self._exec(f"/wi {pattern}") 231 | 232 | lines = self._clean_output(raw) 233 | 234 | results = [] 235 | for line in lines: 236 | try: 237 | offset_str, hit, data = line.split(maxsplit=2) 238 | except ValueError: 239 | results.append({"error": "unexpected line format", "raw": line}) 240 | continue 241 | 242 | try: 243 | offset = int(offset_str, 16) 244 | except ValueError: 245 | offset = offset_str 246 | 247 | results.append({"offset": offset, "hit": hit, "data": data}) 248 | 249 | return ResultArray(results) 250 | 251 | def wide_string_ci_json(self, pattern: str): 252 | """Case‑insensitive wide‑string search – JSON output – ``/wij ``.""" 253 | ret = self._exec(f"/wij {pattern}", json=True) 254 | return ResultArray(ret) 255 | 256 | def size_range(self, min_len: int, max_len: int): 257 | """ 258 | Search for strings whose length is between *min_len* and *max_len*. 259 | ``/z ``. 260 | """ 261 | raw = self._exec(f"/z {min_len} {max_len}") 262 | 263 | lines = self._clean_output(raw) 264 | 265 | results = [] 266 | for line in lines: 267 | try: 268 | offset_str, hit, data = line.split(maxsplit=2) 269 | except ValueError: 270 | results.append({"error": "unexpected line format", "raw": line}) 271 | continue 272 | 273 | try: 274 | offset = int(offset_str, 16) 275 | except ValueError: 276 | offset = offset_str 277 | 278 | results.append({"offset": offset, "hit": hit, "data": data}) 279 | 280 | return ResultArray(results) 281 | -------------------------------------------------------------------------------- /rust/src/api_trait.rs: -------------------------------------------------------------------------------- 1 | use crate::structs::*; 2 | use r2pipe::Error; 3 | 4 | // Maybe have r2papi-rs' own error type? 5 | 6 | pub trait R2PApi { 7 | /// Initialize r2 instance with some basic configurations 8 | fn init(&mut self) -> Result<(), Error>; 9 | /// Recv raw output 10 | fn raw(&mut self, cmd: String) -> Result; 11 | /// Run r2-based analysis on the file to extract information 12 | fn analyze(&mut self) -> Result<(), Error>; 13 | /// Open file in write mode 14 | fn write_mode(&mut self) -> Result<(), Error>; 15 | 16 | ////////////////////////////////////////////// 17 | //// Architecture/OS Information 18 | ///////////////////////////////////////////// 19 | /// Get register information 20 | fn reg_info(&mut self) -> Result; 21 | /// Get binary information 22 | fn bin_info(&mut self) -> Result; 23 | /// Get architecture information 24 | fn arch(&mut self) -> Result; 25 | /// Get file hashes 26 | fn hashes(&mut self) -> Result; 27 | /// Guess binary size 28 | fn size(&mut self) -> Result; 29 | /// Change the architecture bits 30 | fn set_bits(&mut self, bits: u8) -> Result<(), Error>; 31 | /// Change the architecture type 32 | fn set_arch(&mut self, arch: &str) -> Result<(), Error>; 33 | 34 | ////////////////////////////////////////////// 35 | //// Binary/Loader Initialized Information 36 | ////////////////////////////////////////////// 37 | /// Get a list of all symbols defined in the binary 38 | fn symbols(&mut self) -> Result, Error>; 39 | /// Get a list of all entry points 40 | fn entry(&mut self) -> Result, Error>; 41 | /// Get a list of all imports for this binary 42 | fn imports(&mut self) -> Result, Error>; 43 | /// Get a list of all exports by this binary 44 | fn exports(&mut self) -> Result, Error>; 45 | /// Get list of sections 46 | fn sections(&mut self) -> Result, Error>; 47 | /// Get relocations 48 | fn relocs(&mut self) -> Result, Error>; 49 | /// Get shared libraries 50 | fn libraries(&mut self) -> Result, Error>; 51 | /// Get segments 52 | fn segments(&mut self) -> Result, Error>; 53 | 54 | /////////////////////////////////////////////////////////////////// 55 | //// Analysis functions to initialize/perform specific analysis 56 | ////////////////////////////////////////////////////////////////// 57 | // TODO: Have options to set timeouts, also make it non-blocking if possible to hide latency of 58 | // these analysis. Then, these can be called early on the chain, perform other non-related 59 | // operations while analysis happens and finally, wait on the results. 60 | /// All Analysis 61 | fn analyze_all(&mut self) -> Result<(), Error>; 62 | /// Analyze and auto-name functions 63 | fn analyze_and_autoname(&mut self) -> Result<(), Error>; 64 | /// Analyze function calls 65 | fn analyze_function_calls(&mut self) -> Result<(), Error>; 66 | /// Analyze data references 67 | fn analyze_data_references(&mut self) -> Result<(), Error>; 68 | /// Analyze references esil 69 | fn analyze_references_esil(&mut self) -> Result<(), Error>; 70 | /// Find and analyze function preludes 71 | fn analyze_function_preludes(&mut self) -> Result<(), Error>; 72 | /// Analyze instruction references 73 | fn analyze_function_references(&mut self) -> Result<(), Error>; 74 | /// Analyze symbols 75 | fn analyze_symbols(&mut self) -> Result<(), Error>; 76 | /// Analyze consecutive functions in section 77 | fn analyze_consecutive_functions(&mut self) -> Result<(), Error>; 78 | 79 | /////////////////////////////////////////////// 80 | //// Analysis Information 81 | /////////////////////////////////////////////// 82 | /// Get flag information 83 | fn flag_info(&mut self) -> Result, Error>; 84 | /// Get list of functions 85 | fn fn_list(&mut self) -> Result, Error>; 86 | /// Get list of strings 87 | fn strings(&mut self, data_only: bool) -> Result, Error>; 88 | /// Get list of local variables in function defined at a particular address 89 | fn locals_of(&mut self, location: u64) -> Result, Error>; 90 | /// Get calling convention information for a function defined at a particular address 91 | fn cc_info_of(&mut self, location: u64) -> Result; 92 | /// Detect a function at a particular address in the binary 93 | fn function>(&mut self, func: T) -> Result; 94 | 95 | ///////////////////////////////////////////////// 96 | //// Disassembly Information 97 | ///////////////////////////////////////////////// 98 | /// Get a Vec of a certain number of instruction objects at an offset in the binary 99 | fn insts>( 100 | &mut self, 101 | n: Option, 102 | offset: Option, 103 | ) -> Result, Error>; 104 | fn disassemble_n_bytes(&mut self, n: u64, offset: Option) -> Result, Error>; 105 | fn disassemble_n_insts(&mut self, n: u64, offset: Option) -> Result, Error>; 106 | fn seek(&mut self, addr: Option) -> Result; 107 | 108 | ///////////////////////////////////////////////// 109 | //// Read and Write Data 110 | ///////////////////////////////////////////////// 111 | /// Read n amout of bytes from a specified offset, or None for current position. 112 | fn read_bytes(&mut self, n: u64, offset: Option) -> Result, Error>; 113 | /// Read n amount of bits from a specified offset, or None for current position. 114 | //fn read_bits(&mut self, n: u64, offset: Option) -> Result; 115 | /// Write bytes to a specified offset, or None for current position 116 | fn write_bytes(&mut self, offset: Option, bytes: &[u8]) -> Result<(), Error>; 117 | /// Read u8 from a specified offset, or None for current position 118 | fn read_u8(&mut self, offset: Option) -> Result; 119 | /// Read u16 little endian from a specified offset, or None for current position 120 | fn read_u16_le(&mut self, offset: Option) -> Result; 121 | /// Read u18 little endian from a specified offset, or None for current position 122 | fn read_u32_le(&mut self, offset: Option) -> Result; 123 | /// Read u64 little endian from a specified offset, or None for current position 124 | fn read_u64_le(&mut self, offset: Option) -> Result; 125 | /// Read u16 big endian from a specified offset, or None for current position 126 | fn read_u16_be(&mut self, offset: Option) -> Result; 127 | /// Read u18 big endian from a specified offset, or None for current position 128 | fn read_u32_be(&mut self, offset: Option) -> Result; 129 | /// Read u64 big endian from a specified offset, or None for current position 130 | fn read_u64_be(&mut self, offset: Option) -> Result; 131 | /// Write u8 from a specified offset, or None for current position 132 | fn write_u8(&mut self, offset: Option, value: u8) -> Result<(), Error>; 133 | /// Write u16 little endian from a specified offset, or None for current position 134 | fn write_u16_le(&mut self, offset: Option, value: u16) -> Result<(), Error>; 135 | /// Write u18 little endian from a specified offset, or None for current position 136 | fn write_u32_le(&mut self, offset: Option, value: u32) -> Result<(), Error>; 137 | /// Write u64 little endian from a specified offset, or None for current position 138 | fn write_u64_le(&mut self, offset: Option, value: u64) -> Result<(), Error>; 139 | /// Write u16 big endian from a specified offset, or None for current position 140 | fn write_u16_be(&mut self, offset: Option, value: u16) -> Result<(), Error>; 141 | /// Write u18 big endian from a specified offset, or None for current position 142 | fn write_u32_be(&mut self, offset: Option, value: u32) -> Result<(), Error>; 143 | /// Write u64 big endian from a specified offset, or None for current position 144 | fn write_u64_be(&mut self, offset: Option, value: u64) -> Result<(), Error>; 145 | 146 | ///////////////////////////////////////////////// 147 | //// Esil emulation 148 | ///////////////////////////////////////////////// 149 | /// Initialize esil memory. 150 | fn esil_init(&mut self) -> Result<(), Error>; 151 | /// Get all the registers information. 152 | fn esil_regs(&mut self) -> Result; 153 | /// Set specific register value. 154 | fn esil_set_reg(&mut self, reg: &str, value: u64) -> Result<(), Error>; 155 | /// Get specific register value. 156 | fn esil_get_reg(&mut self, reg: &str) -> Result; 157 | /// Emulate single step. 158 | fn esil_step(&mut self) -> Result<(), Error>; 159 | /// Emulate step over. 160 | fn esil_step_over(&mut self) -> Result<(), Error>; 161 | /// Emulate back step. 162 | fn esil_step_back(&mut self) -> Result<(), Error>; 163 | /// Emulate until address. 164 | fn esil_step_until_addr(&mut self, addr: u64) -> Result<(), Error>; 165 | /// Continue until exception. 166 | fn esil_cont_until_exception(&mut self) -> Result<(), Error>; 167 | /// Continue until interrupt. 168 | fn esil_cont_until_int(&mut self) -> Result<(), Error>; 169 | /// Continue until call. 170 | fn esil_cont_until_call(&mut self) -> Result<(), Error>; 171 | /// Continue until address. 172 | fn esil_cont_until_addr(&mut self, addr: u64) -> Result<(), Error>; 173 | 174 | ///////////////////////////////////////////////// 175 | //// Buffers 176 | ///////////////////////////////////////////////// 177 | /// Allocate a buffer of size sz 178 | fn malloc(&mut self, sz: usize) -> Result<(), Error>; 179 | /// Get buffers 180 | fn buffers(&mut self) -> Result, Error>; 181 | /// Free buffer 182 | fn free(&mut self, n: u64) -> Result<(), Error>; 183 | 184 | ///////////////////////////////////////////////// 185 | //// Eval 186 | ///////////////////////////////////////////////// 187 | /// Set a setting (eval) set("dbg.clone", "true") 188 | fn set(&mut self, key: &str, value: &str) -> Result<(), Error>; 189 | /// Get setting let clone = get("dbg.clone") 190 | fn get(&mut self, key: &str) -> Result; 191 | } 192 | -------------------------------------------------------------------------------- /typescript/async/esil.ts: -------------------------------------------------------------------------------- 1 | import { R2PipeAsync } from "./r2pipe.js"; 2 | 3 | declare let console: any; 4 | declare let r2: R2PipeAsync; 5 | 6 | // ("this is just a comment"), -- comments are also part of the runtime 7 | 8 | /* 9 | =("//", { 10 | =(obj, {}()) 11 | =([obj, comment], 32) 12 | if(eq([obj,comment], 32), 13 | ret() 14 | ) 15 | ret(obj) 16 | }) 17 | */ 18 | 19 | export class EsilToken { 20 | label: string = ""; 21 | comment: string = ""; 22 | text: string = ""; 23 | addr: string = "0"; // for ut64 we use strings for numbers :< 24 | position: number = 0; 25 | constructor(text: string = "", position: number = 0) { 26 | this.text = text; 27 | this.position = position; 28 | } 29 | toString(): string { 30 | return this.text; 31 | } 32 | } 33 | 34 | export type EsilNodeType = 35 | | "number" 36 | | "flag" 37 | | "register" 38 | | "operation" 39 | | "none" 40 | | "block" 41 | | "goto" 42 | | "label"; 43 | 44 | export class EsilNode { 45 | lhs: EsilNode | undefined; 46 | rhs: EsilNode | undefined; 47 | children: EsilNode[]; 48 | token: EsilToken; 49 | type: EsilNodeType = "none"; 50 | constructor( 51 | token: EsilToken = new EsilToken(), 52 | type: EsilNodeType = "none" 53 | ) { 54 | this.token = token; 55 | this.children = []; 56 | } 57 | setSides(lhs: EsilNode, rhs: EsilNode): void { 58 | this.lhs = lhs; 59 | this.rhs = rhs; 60 | } 61 | addChildren(ths: EsilNode, fhs: EsilNode): void { 62 | if (ths !== undefined) { 63 | this.children.push(ths); 64 | } 65 | if (fhs !== undefined) { 66 | this.children.push(fhs); 67 | } 68 | } 69 | toEsil(): string { 70 | if (this.lhs !== undefined && this.rhs !== undefined) { 71 | // XXX handle ?{ }{ } 72 | let left = this.lhs.toEsil(); 73 | if (left !== "") { 74 | left += ","; 75 | } 76 | const right = this.rhs.toEsil(); 77 | return `${right},${left}${this.token}`; 78 | } 79 | return ""; // this.token.text; 80 | } 81 | toString(): string { 82 | let str = ""; 83 | if (this.token.label !== "") { 84 | str += this.token.label + ":\n"; 85 | } 86 | if (this.token.addr !== "0") { 87 | // str += "// @ " + this.token.addr + "\n"; 88 | } 89 | if (this.token.comment !== "") { 90 | str += "/*" + this.token.comment + "*/\n"; 91 | } 92 | if (this.token.toString() === "GOTO") { 93 | if (this.children.length > 0) { 94 | const children = this.children[0]; 95 | str += "goto label_" + children.token.position + ";\n"; 96 | } else { 97 | // console.log(JSON.stringify(this,null, 2)); 98 | const pos = 0; 99 | str += `goto label_${pos};\n`; 100 | } 101 | } 102 | if (this.children.length > 0) { 103 | str += ` (if (${this.rhs})\n`; 104 | for (const children of this.children) { 105 | if (children !== null) { 106 | const x = children.toString(); 107 | if (x != "") { 108 | str += ` ${x}\n`; 109 | } 110 | } 111 | } 112 | str += " )\n"; 113 | } 114 | if (this.lhs !== undefined && this.rhs !== undefined) { 115 | return str + ` ( ${this.lhs} ${this.token} ${this.rhs} )`; 116 | // return str + `${this.lhs} ${this.token} ${this.rhs}`; 117 | } 118 | return str + this.token.toString(); 119 | } 120 | } 121 | 122 | export class EsilParser { 123 | r2: R2PipeAsync; 124 | stack: EsilNode[]; // must be a stack or a list.. to parse sub expressions we must reset 125 | nodes: EsilNode[]; 126 | root: EsilNode; 127 | tokens: EsilToken[]; 128 | cur: number = 0; 129 | 130 | constructor(r2: R2PipeAsync) { 131 | this.r2 = r2; 132 | this.cur = 0; 133 | this.stack = []; 134 | this.nodes = []; 135 | this.tokens = []; 136 | this.root = new EsilNode(new EsilToken("function", 0), "block"); 137 | } 138 | toJSON(): string { 139 | if (this.stack.length > 0) { 140 | // return JSON.stringify (this.stack, null, 2); 141 | throw new Error("The ESIL stack is not empty"); 142 | } 143 | return JSON.stringify(this.root, null, 2); 144 | } 145 | toEsil(): string { 146 | return this.nodes.map((x) => x.toEsil()).join(","); 147 | } 148 | private async optimizeFlags(node: EsilNode) { 149 | if (node.rhs !== undefined) { 150 | await this.optimizeFlags(node.rhs); 151 | } 152 | if (node.lhs !== undefined) { 153 | await this.optimizeFlags(node.lhs); 154 | } 155 | for (let i = 0; i < node.children.length; i++) { 156 | await this.optimizeFlags(node.children[i]); 157 | } 158 | const addr: string = node.toString(); 159 | if (+addr > 4096) { 160 | const cname = await r2.cmd(`fd.@ ${addr}`); 161 | const fname = cname.trim().split("\n")[0].trim(); 162 | if (fname != "" && fname.indexOf("+") === -1) { 163 | node.token.text = fname; 164 | } 165 | } 166 | } 167 | async optimize(options: string): Promise { 168 | if (options.indexOf("flag") != -1) { 169 | await this.optimizeFlags(this.root); 170 | } 171 | } 172 | toString(): string { 173 | return this.root.children.map((x) => x.toString()).join(";\n"); 174 | } 175 | reset(): void { 176 | this.nodes = []; 177 | this.stack = []; 178 | this.tokens = []; 179 | this.cur = 0; 180 | this.root = new EsilNode(new EsilToken("function", 0), "block"); 181 | } 182 | parseRange(from: number, to: number) { 183 | let pos = from; 184 | while (pos < this.tokens.length && pos < to) { 185 | const token = this.peek(pos); 186 | if (!token) { 187 | // console.log("BREAK"); 188 | break; 189 | } 190 | // console.log(pos, token); 191 | this.cur = pos; 192 | this.pushToken(token); 193 | pos = this.cur; 194 | pos++; 195 | } 196 | // console.log("done"); 197 | } 198 | async parseFunction(addr?: string): Promise { 199 | const ep = this; 200 | async function parseAmount(n: number): Promise { 201 | // console.log("PDQ "+n); 202 | const output = await r2.cmd("pie " + n + " @e:scr.color=0"); 203 | const lines = output.trim().split("\n"); 204 | for (const line of lines) { 205 | if (line.length === 0) { 206 | console.log("Empty"); 207 | continue; 208 | } 209 | // console.log("parse", r2.cmd("?v:$$")); 210 | const kv = line.split(" "); 211 | if (kv.length > 1) { 212 | // line != "") { 213 | // console.log("// @ " + kv[0]); 214 | //ep.reset (); 215 | await r2.cmd(`s ${kv[0]}`); 216 | ep.parse(kv[1], kv[0]); 217 | ep.optimize("flags,labels"); 218 | //console.log(ep.toString()); 219 | } 220 | } 221 | // console.log(ep.toString()); 222 | } 223 | const oaddr = (await r2.cmd("?v $$")).trim(); 224 | // const func = r2.cmdj("pdrj"); // XXX this command changes the current seek 225 | if (addr === undefined) { 226 | addr = oaddr; 227 | } 228 | const bbs = await r2.cmdj(`afbj@${addr}`); // XXX this command changes the current seek 229 | for (const bb of bbs) { 230 | // console.log("bb_" + bb.addr + ":"); 231 | await r2.cmd(`s ${bb.addr}`); 232 | await parseAmount(bb.ninstr); 233 | } 234 | r2.cmd(`s ${oaddr}`); 235 | } 236 | parse(expr: string, addr?: string): void | never { 237 | const tokens = expr 238 | .trim() 239 | .split(",") 240 | .map((x) => x.trim()); 241 | const from = this.tokens.length; 242 | for (const tok of tokens) { 243 | const token = new EsilToken(tok, this.tokens.length); 244 | if (addr !== undefined) { 245 | token.addr = addr; 246 | } 247 | this.tokens.push(token); 248 | } 249 | const to = this.tokens.length; 250 | this.parseRange(from, to); 251 | } 252 | 253 | peek(a: number): EsilToken | undefined { 254 | return this.tokens[a]; 255 | } 256 | 257 | pushToken(tok: EsilToken): void | never { 258 | if (this.isNumber(tok)) { 259 | const node = new EsilNode(tok, "number"); 260 | this.stack.push(node); 261 | this.nodes.push(node); 262 | } else if (this.isInternal(tok)) { 263 | const node = new EsilNode(tok, "flag"); 264 | this.stack.push(node); 265 | this.nodes.push(node); 266 | } else if (this.isOperation(tok)) { 267 | // run the operation login 268 | } else { 269 | // assume it's a register, so just push the string 270 | const node = new EsilNode(tok, "register"); 271 | this.stack.push(node); 272 | this.nodes.push(node); 273 | } 274 | // we need a list of register names to do this check properly 275 | // throw new Error ("Unknown token"); 276 | } 277 | private isNumber(expr: EsilToken): boolean { 278 | if (expr.toString().startsWith("0")) { 279 | return true; 280 | } 281 | return +expr > 0; 282 | } 283 | private isInternal(expr: EsilToken): boolean { 284 | const text = expr.toString(); 285 | return text.startsWith("$") && text.length > 1; 286 | } 287 | private parseUntil(start: number): EsilNode | null { 288 | const from = start + 1; 289 | let pos = from; 290 | const origStack: any[] = []; 291 | const this_nodes_length = this.nodes.length; 292 | this.stack.forEach((x) => origStack.push(x)); 293 | while (pos < this.tokens.length) { 294 | const token = this.peek(pos); 295 | if (!token) { 296 | break; 297 | } 298 | if (token.toString() === "}") { 299 | break; 300 | } 301 | if (token.toString() === "}{") { 302 | // return token; 303 | break; 304 | } 305 | // console.log("peek ", this.tokens[pos]); 306 | pos++; 307 | } 308 | this.stack = origStack; 309 | const to = pos; 310 | this.parseRange(from, to); 311 | const same = this.nodes.length == this_nodes_length; 312 | // console.log("BLOCK ("+ ep.toString()); 313 | if (same) { 314 | return null; 315 | } 316 | return this.nodes[this.nodes.length - 1]; // this.tokens.length - 1]; 317 | } 318 | private getNodeFor(index: number): null | EsilNode { 319 | const tok = this.peek(index); 320 | if (tok === undefined) { 321 | return null; 322 | } 323 | for (const node of this.nodes) { 324 | if (node.token.position === index) { 325 | return node; 326 | } 327 | } 328 | this.nodes.push(new EsilNode(new EsilToken("label", index), "label")); 329 | return null; 330 | } 331 | private findNodeFor(index: number): null | EsilNode { 332 | for (const node of this.nodes) { 333 | if (node.token.position === index) { 334 | return node; 335 | } 336 | } 337 | return null; 338 | } 339 | private isOperation(expr: EsilToken): never | boolean { 340 | switch (expr.toString()) { 341 | // 1pop1push 342 | case "[1]": 343 | case "[2]": 344 | case "[4]": 345 | case "[8]": 346 | if (this.stack.length >= 1) { 347 | const i1 = this.stack.pop()!; 348 | // TODO: MemoryReferenceNode(i1)); 349 | const mn = new EsilNode(i1.token, "operation"); // expr.toString()); 350 | this.stack.push(i1); // mn); 351 | } else { 352 | throw new Error("Stack needs more items"); 353 | } 354 | return true; 355 | // 1pop1push 356 | case "!": 357 | if (this.stack.length >= 1) { 358 | const i0 = new EsilNode( 359 | new EsilToken("", expr.position), 360 | "none" 361 | ); 362 | const i1 = this.stack.pop()!; 363 | const nn = new EsilNode(expr, "operation"); 364 | nn.setSides(i0, i1); 365 | this.stack.push(nn); 366 | } else { 367 | throw new Error("Stack needs more items"); 368 | } 369 | return true; 370 | case "": 371 | case "}": 372 | case "}{": 373 | // no pops or nothing, just does nothing 374 | return true; 375 | case "DUP": 376 | if (this.stack.length < 1) { 377 | throw new Error("goto cant pop"); 378 | } else { 379 | const destNode = this.stack.pop()!; 380 | this.stack.push(destNode); 381 | this.stack.push(destNode); 382 | } 383 | return true; 384 | case "GOTO": 385 | // take previous statement which should be const and add a label 386 | { 387 | const prev = this.peek(expr.position - 1); 388 | if (prev !== null) { 389 | // TODO: check stack 390 | if (this.stack.length < 1) { 391 | throw new Error("goto cant pop"); 392 | } 393 | const destNode = this.stack.pop()!; 394 | if (destNode !== null) { 395 | const value: number = 0 | +destNode.toString(); 396 | if (value > 0) { 397 | const destToken = this.peek(value); 398 | if (destToken !== undefined) { 399 | destToken.label = "label_" + value; 400 | destToken.comment = "hehe"; 401 | const nn = new EsilNode(expr, "goto"); 402 | const gn = this.getNodeFor( 403 | destToken.position 404 | ); 405 | if (gn != null) { 406 | nn.children.push(gn); 407 | } 408 | this.root.children.push(nn); 409 | } else { 410 | console.error("Cannot find goto node"); 411 | } 412 | } else { 413 | console.error("Cannot find dest node for goto"); 414 | } 415 | } 416 | } 417 | } 418 | return true; 419 | // controlflow 420 | case "?{": // ESIL_TOKEN_IF 421 | if (this.stack.length >= 1) { 422 | const i0 = new EsilNode( 423 | new EsilToken("if", expr.position), 424 | "none" 425 | ); 426 | const i1 = this.stack.pop()!; 427 | const nn = new EsilNode(expr, "operation"); 428 | nn.setSides(i0, i1); // left side can be ignored for now.. but we can express this somehow 429 | const trueBlock = this.parseUntil(expr.position); 430 | let falseBlock = null; 431 | // nn.addChildren(trueBlock, falseBlock); 432 | if (trueBlock !== null) { 433 | nn.children.push(trueBlock); 434 | this.nodes.push(trueBlock); 435 | falseBlock = this.parseUntil( 436 | trueBlock.token.position + 1 437 | ); 438 | if (falseBlock !== null) { 439 | nn.children.push(falseBlock); 440 | this.nodes.push(falseBlock); 441 | } 442 | } 443 | // console.log("true", trueBlock); 444 | // console.log("false", falseBlock); 445 | // this.stack.push(nn); 446 | this.nodes.push(nn); 447 | this.root.children.push(nn); 448 | if (falseBlock !== null) { 449 | this.cur = falseBlock.token.position; 450 | } 451 | } else { 452 | throw new Error("Stack needs more items"); 453 | } 454 | return true; 455 | case "-": 456 | if (this.stack.length >= 2) { 457 | const i0 = this.stack.pop()!; 458 | const i1 = this.stack.pop()!; 459 | const nn = new EsilNode(expr, "operation"); 460 | nn.setSides(i0, i1); 461 | if (this.stack.length === 0) { 462 | // this.root.children.push(nn); 463 | } 464 | this.stack.push(nn); 465 | this.nodes.push(nn); 466 | } else { 467 | throw new Error("Stack needs more items"); 468 | } 469 | return true; 470 | // 2pop1push 471 | case "<": 472 | case ">": 473 | case "^": 474 | case "&": 475 | case "|": 476 | case "+": 477 | case "*": 478 | case "/": 479 | case ">>=": 480 | case "<<=": 481 | case ">>>=": 482 | case "<<<=": 483 | case ">>>>=": 484 | case "<<<<=": 485 | if (this.stack.length >= 2) { 486 | const i0 = this.stack.pop()!; 487 | const i1 = this.stack.pop()!; 488 | const nn = new EsilNode(expr, "operation"); 489 | nn.setSides(i0, i1); 490 | if (this.stack.length === 0) { 491 | // this.root.children.push(nn); 492 | } 493 | this.stack.push(nn); 494 | this.nodes.push(nn); 495 | } else { 496 | throw new Error("Stack needs more items"); 497 | } 498 | return true; 499 | // 2pop0push 500 | case "=": 501 | case ":=": 502 | case "-=": 503 | case "+=": 504 | case "==": 505 | case "=[1]": 506 | case "=[2]": 507 | case "=[4]": 508 | case "=[8]": 509 | if (this.stack.length >= 2) { 510 | const i0 = this.stack.pop()!; 511 | const i1 = this.stack.pop()!; 512 | const nn = new EsilNode(expr, "operation"); 513 | nn.setSides(i0, i1); 514 | if (this.stack.length === 0) { 515 | this.root.children.push(nn); 516 | } 517 | this.nodes.push(nn); 518 | } else { 519 | throw new Error("Stack needs more items"); 520 | } 521 | return true; 522 | } 523 | return false; 524 | } 525 | } 526 | --------------------------------------------------------------------------------