├── go.mod ├── Makefile ├── r2pipe_api_test.go ├── README.md ├── r2pipe_native_test.go ├── .github └── workflows │ ├── go.yml │ └── codeql.yml ├── errside_test.go ├── r2pipe_test.go ├── LICENSE ├── api.go ├── native.go └── r2pipe.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/radareorg/r2pipe-go 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | go test 3 | 4 | ex: 5 | cd example ; go run example.go 6 | 7 | sync: 8 | git clone --depth=1 https://github.com/radareorg/radare2-r2pipe 9 | cp -rf radare2-r2pipe/go/*.go . 10 | rm -rf radare2-r2pipe 11 | 12 | .PHONY: all sync 13 | -------------------------------------------------------------------------------- /r2pipe_api_test.go: -------------------------------------------------------------------------------- 1 | // radare - LGPL - Copyright 2017 - pancake 2 | 3 | package r2pipe 4 | 5 | import "testing" 6 | 7 | func TestApiCmd(t *testing.T) { 8 | r2p, err := NewApiPipe("/bin/ls") 9 | // r2p, err := NewPipe("/bin/ls") 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | defer r2p.Close() 14 | version, err := r2p.Cmd("pd 10 @ entry0") 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | print(version + "\n") 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | r2pipe.go 2 | ========= 3 | 4 | Go module to interact with radare2 5 | 6 | Source 7 | ------ 8 | 9 | This repository is in sync with [radareorg/r2pipe](https://github.com/radareorg/radare2-r2pipe). 10 | 11 | Run `make sync` to update it after changing things in the source repository. 12 | 13 | Documentation 14 | ------------- 15 | 16 | [![GoDoc](https://pkg.go.dev/badge/github.com/radareorg/r2pipe-go)](https://pkg.go.dev/github.com/radareorg/r2pipe-go#readme-documentation) 17 | 18 | -------------------------------------------------------------------------------- /r2pipe_native_test.go: -------------------------------------------------------------------------------- 1 | // radare - LGPL - Copyright 2017-2021 - pancake 2 | 3 | package r2pipe 4 | 5 | import "testing" 6 | import "fmt" 7 | 8 | func TestNativeCmd(t *testing.T) { 9 | fmt.Println("[*] Testing r2 native api pipe") 10 | r2p, err := NewNativePipe("/bin/ls") 11 | // r2p, err := NewPipe("/bin/ls") 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | defer r2p.Close() 16 | version, err := r2p.Cmd("pd 10 @ entry0") 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | print(version + "\n") 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.17 20 | 21 | - name: Set up radare2 22 | run: git clone --depth=1 https://github.com/radareorg/radare2 && radare2/sys/install.sh > /dev/null 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /errside_test.go: -------------------------------------------------------------------------------- 1 | package r2pipe 2 | 3 | import "testing" 4 | import "fmt" 5 | import "time" 6 | 7 | func TestErrSide(t *testing.T) { 8 | r2p, err := NewPipe("malloc://256") 9 | if err != nil { 10 | t.Fatal(err) 11 | } 12 | r2p.Close() 13 | var res = false 14 | r2p.On("errmsg", res, func(p *Pipe, typ string, user interface{}, dat string) bool { 15 | fmt.Println("errmsg received") 16 | res = true 17 | return false 18 | // return true 19 | }) 20 | 21 | r2p.Cmd("aaa") 22 | time.Sleep(1) 23 | if res { 24 | fmt.Println("It works!") 25 | } 26 | defer r2p.Close() 27 | fmt.Println("[*] Testing r2pipe-side stderr message") 28 | } 29 | -------------------------------------------------------------------------------- /r2pipe_test.go: -------------------------------------------------------------------------------- 1 | // radare - LGPL - Copyright 2015 - nibble 2 | 3 | package r2pipe 4 | 5 | import ( 6 | "fmt" 7 | "testing" 8 | ) 9 | 10 | type Offset struct { 11 | Offset uint 12 | Current bool 13 | } 14 | 15 | func TestCmd(t *testing.T) { 16 | fmt.Println("[*] Testing r2 spawn pipe") 17 | r2p, err := NewPipe("malloc://256") 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | defer r2p.Close() 22 | 23 | check := "Hello World" 24 | 25 | _, err = r2p.Cmd("w " + check) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | buf, err := r2p.Cmd("ps") 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | if buf != check { 34 | t.Errorf("buf=%v; want=%v", buf, check) 35 | } 36 | 37 | offset := Offset{} 38 | r2p.CmdjStruct("sj ~{0}", &offset) 39 | 40 | if !offset.Current { 41 | t.Errorf("CurrentOffset=%v; want=%v", offset.Current, true) 42 | } 43 | 44 | r2p.CmdjfStruct("sj ~{%d}", &offset, 0) 45 | if !offset.Current { 46 | t.Errorf("CurrentOffset=%v; want=%v", offset.Current, true) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | schedule: 9 | - cron: "22 1 * * 4" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ go ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v2 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v2 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v2 40 | with: 41 | category: "/language:${{ matrix.language }}" 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 radare 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | 24 | -------------------------------------------------------------------------------- /api.go: -------------------------------------------------------------------------------- 1 | // radare - LGPL - Copyright 2021 - pancake 2 | 3 | package r2pipe 4 | 5 | // #cgo CFLAGS: -I/usr/local/include/libr 6 | // #cgo CFLAGS: -I/usr/local/include/libr/sdb 7 | // #cgo LDFLAGS: -L/usr/local/lib -lr_core 8 | // #cgo pkg-config: r_core 9 | // #include 10 | // #include 11 | // extern void r_core_free(void *); 12 | // extern void *r_core_new(void); 13 | // extern char *r_core_cmd_str(void*, const char *); 14 | // 15 | import "C" 16 | 17 | import ( 18 | "unsafe" 19 | ) 20 | 21 | func (r2p *Pipe) ApiCmd(cmd string) (string, error) { 22 | res := C.r_core_cmd_str(r2p.Core, C.CString(cmd)) 23 | return C.GoString(res), nil 24 | } 25 | 26 | func (r2p *Pipe) ApiClose() error { 27 | C.r_core_free(unsafe.Pointer(r2p.Core)) 28 | r2p.Core = nil 29 | return nil 30 | } 31 | 32 | func NewApiPipe(file string) (*Pipe, error) { 33 | r2 := C.r_core_new() 34 | r2p := &Pipe{ 35 | File: file, 36 | Core: r2, 37 | cmd: func(r2p *Pipe, cmd string) (string, error) { 38 | return r2p.ApiCmd(cmd) 39 | }, 40 | close: func(r2p *Pipe) error { 41 | return r2p.ApiClose() 42 | }, 43 | } 44 | if file != "" { 45 | r2p.ApiCmd("o " + file) 46 | } 47 | return r2p, nil 48 | } 49 | -------------------------------------------------------------------------------- /native.go: -------------------------------------------------------------------------------- 1 | // radare - LGPL - Copyright 2017 - pancake 2 | 3 | package r2pipe 4 | 5 | //#cgo linux LDFLAGS: -ldl 6 | //#include 7 | //#include 8 | // #include 9 | // void *gor_core_new(void *f) { 10 | // void *(*rcn)(); 11 | // rcn = (void *(*)())f; 12 | // return rcn(); 13 | // } 14 | // 15 | // void gor_core_free(void *f, void *arg) { 16 | // void (*fr)(void *); 17 | // fr = (void (*)(void *))f; 18 | // fr(arg); 19 | // } 20 | // 21 | // char *gor_core_cmd_str(void *f, void *arg, char *arg2) { 22 | // char *(*cmdstr)(void *, char *); 23 | // cmdstr = (char *(*)(void *, char *))f; 24 | // return cmdstr(arg, arg2); 25 | // } 26 | import "C" 27 | 28 | import ( 29 | "errors" 30 | "fmt" 31 | "runtime" 32 | "unsafe" 33 | ) 34 | 35 | type Ptr = unsafe.Pointer 36 | 37 | // *struct{} 38 | 39 | var ( 40 | lib Ptr = nil 41 | r_core_new func() Ptr 42 | r_core_free func(Ptr) 43 | r_core_cmd_str func(Ptr, string) string 44 | ) 45 | 46 | type DL struct { 47 | handle unsafe.Pointer 48 | name string 49 | } 50 | 51 | func dlOpen(path string) (*DL, error) { 52 | var ret DL 53 | switch runtime.GOOS { 54 | case "darwin": 55 | path = path + ".dylib" 56 | case "windows": 57 | path = path + ".dll" 58 | default: 59 | path = path + ".so" //linux/bsds 60 | } 61 | cpath := C.CString(path) 62 | if cpath == nil { 63 | return nil, errors.New("Failed to get cpath") 64 | } 65 | //r2pioe only uses flag 0 66 | ret.handle = C.dlopen(cpath, 0) 67 | ret.name = path 68 | C.free(unsafe.Pointer(cpath)) 69 | if ret.handle == nil { 70 | return nil, errors.New(fmt.Sprintf("Failed to open %s", path)) 71 | } 72 | return &ret, nil 73 | } 74 | 75 | func dlSym(dl *DL, name string) (unsafe.Pointer, error) { 76 | err := fmt.Errorf("Failed to load '%s' from '%s'", name, dl.name) 77 | cname := C.CString(name) 78 | if cname == nil { 79 | return nil, err 80 | } 81 | handle := C.dlsym(dl.handle, cname) 82 | C.free(unsafe.Pointer(cname)) 83 | if handle == nil { 84 | return nil, err 85 | } 86 | return handle, nil 87 | } 88 | 89 | func NativeLoad() error { 90 | if lib != nil { 91 | return nil 92 | } 93 | lib, err := dlOpen("libr_core") 94 | _ = lib 95 | if err != nil { 96 | return err 97 | } 98 | handle1, _ := dlSym(lib, "r_core_new") 99 | r_core_new = func() Ptr { 100 | a := (Ptr)(C.gor_core_new(handle1)) 101 | return a 102 | } 103 | handle2, _ := dlSym(lib, "r_core_free") 104 | r_core_free = func(p Ptr) { 105 | C.gor_core_free(handle2, unsafe.Pointer(p)) 106 | } 107 | handle3, _ := dlSym(lib, "r_core_cmd_str") 108 | r_core_cmd_str = func(p Ptr, s string) string { 109 | a := C.CString(s) 110 | b := C.gor_core_cmd_str(handle3, unsafe.Pointer(p), a) 111 | C.free(unsafe.Pointer(a)) 112 | return C.GoString(b) 113 | } 114 | return nil 115 | } 116 | 117 | func (r2p *Pipe) NativeCmd(cmd string) (string, error) { 118 | res := r_core_cmd_str(r2p.Core, cmd) 119 | return res, nil 120 | } 121 | 122 | func (r2p *Pipe) NativeClose() error { 123 | r_core_free(r2p.Core) 124 | r2p.Core = nil 125 | return nil 126 | } 127 | 128 | func NewNativePipe(file string) (*Pipe, error) { 129 | if err := NativeLoad(); err != nil { 130 | return nil, err 131 | } 132 | r2 := r_core_new() 133 | r2p := &Pipe{ 134 | File: file, 135 | Core: r2, 136 | cmd: func(r2p *Pipe, cmd string) (string, error) { 137 | return r2p.NativeCmd(cmd) 138 | }, 139 | close: func(r2p *Pipe) error { 140 | return r2p.NativeClose() 141 | }, 142 | } 143 | if file != "" { 144 | r2p.NativeCmd("o " + file) 145 | } 146 | return r2p, nil 147 | } 148 | -------------------------------------------------------------------------------- /r2pipe.go: -------------------------------------------------------------------------------- 1 | // radare - LGPL - Copyright 2015 - nibble 2 | 3 | /* 4 | Package r2pipe allows to call r2 commands from Go. A simple hello world would 5 | look like the following snippet: 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/radare/r2pipe-go" 13 | ) 14 | 15 | func main() { 16 | r2p, err := r2pipe.NewPipe("malloc://256") 17 | if err != nil { 18 | panic(err) 19 | } 20 | defer r2p.Close() 21 | 22 | _, err = r2p.Cmd("w Hello World") 23 | if err != nil { 24 | panic(err) 25 | } 26 | buf, err := r2p.Cmd("ps") 27 | if err != nil { 28 | panic(err) 29 | } 30 | fmt.Println(buf) 31 | } 32 | */ 33 | package r2pipe 34 | 35 | import ( 36 | "bufio" 37 | "bytes" 38 | "encoding/json" 39 | "fmt" 40 | "io" 41 | "os" 42 | "os/exec" 43 | "strconv" 44 | "strings" 45 | "unsafe" 46 | ) 47 | 48 | // A Pipe represents a communication interface with r2 that will be used to 49 | // execute commands and obtain their results. 50 | type Pipe struct { 51 | File string 52 | r2cmd *exec.Cmd 53 | stdin io.WriteCloser 54 | stdout io.ReadCloser 55 | stderr io.ReadCloser 56 | Core unsafe.Pointer 57 | cmd CmdDelegate 58 | close CloseDelegate 59 | } 60 | 61 | type ( 62 | CmdDelegate func(*Pipe, string) (string, error) 63 | CloseDelegate func(*Pipe) error 64 | EventDelegate func(*Pipe, string, interface{}, string) bool 65 | ) 66 | 67 | // NewPipe returns a new r2 pipe and initializes an r2 core that will try to 68 | // load the provided file or URI. If file is an empty string, the env vars 69 | // R2PIPE_{IN,OUT} will be used as file descriptors for input and output, this 70 | // is the case when r2pipe is called within r2. 71 | func NewPipe(file string) (*Pipe, error) { 72 | if file == "" { 73 | return newPipeFd() 74 | } 75 | 76 | return newPipeCmd(file) 77 | } 78 | 79 | func newPipeFd() (*Pipe, error) { 80 | r2pipeIn := os.Getenv("R2PIPE_IN") 81 | r2pipeOut := os.Getenv("R2PIPE_OUT") 82 | 83 | if r2pipeIn == "" || r2pipeOut == "" { 84 | return nil, fmt.Errorf("missing R2PIPE_{IN,OUT} vars") 85 | } 86 | 87 | r2pipeInFd, err := strconv.Atoi(r2pipeIn) 88 | if err != nil { 89 | return nil, fmt.Errorf("failed to convert IN into file descriptor") 90 | } 91 | 92 | r2pipeOutFd, err := strconv.Atoi(r2pipeOut) 93 | if err != nil { 94 | return nil, fmt.Errorf("failed to convert OUT into file descriptor") 95 | } 96 | 97 | stdout := os.NewFile(uintptr(r2pipeInFd), "R2PIPE_IN") 98 | stdin := os.NewFile(uintptr(r2pipeOutFd), "R2PIPE_OUT") 99 | 100 | r2p := &Pipe{ 101 | File: "", 102 | r2cmd: nil, 103 | stdin: stdin, 104 | stdout: stdout, 105 | Core: nil, 106 | } 107 | 108 | return r2p, nil 109 | } 110 | 111 | func newPipeCmd(file string) (*Pipe, error) { 112 | 113 | r2p := &Pipe{File: file, r2cmd: exec.Command("radare2", "-q0", file)} 114 | var err error 115 | r2p.stdin, err = r2p.r2cmd.StdinPipe() 116 | if err == nil { 117 | r2p.stdout, err = r2p.r2cmd.StdoutPipe() 118 | if err == nil { 119 | r2p.stderr, err = r2p.r2cmd.StdoutPipe() 120 | } 121 | if err = r2p.r2cmd.Start(); err == nil { 122 | //Read the initial data 123 | _, err = bufio.NewReader(r2p.stdout).ReadString('\x00') 124 | } 125 | } 126 | return r2p, err 127 | } 128 | 129 | // Write implements the standard Write interface: it writes data to the r2 130 | // pipe, blocking until r2 have consumed all the data. 131 | func (r2p *Pipe) Write(p []byte) (n int, err error) { 132 | return r2p.stdin.Write(p) 133 | } 134 | 135 | // Read implements the standard Read interface: it reads data from the r2 136 | // pipe's stdin, blocking until the previously issued commands have finished. 137 | func (r2p *Pipe) Read(p []byte) (n int, err error) { 138 | return r2p.stdout.Read(p) 139 | } 140 | 141 | func (r2p *Pipe) ReadErr(p []byte) (n int, err error) { 142 | return r2p.stderr.Read(p) 143 | } 144 | 145 | func (r2p *Pipe) On(evname string, p interface{}, cb EventDelegate) error { 146 | path, err := r2p.Cmd("===stderr") 147 | if err != nil { 148 | return err 149 | } 150 | f, err := os.OpenFile(path, os.O_RDONLY, 0600) 151 | 152 | if err != nil { 153 | return err 154 | } 155 | go func() { 156 | var buf bytes.Buffer 157 | for { 158 | io.Copy(&buf, f) 159 | if buf.Len() > 0 { 160 | if !cb(r2p, evname, p, buf.String()) { 161 | break 162 | } 163 | } 164 | } 165 | f.Close() 166 | }() 167 | return nil 168 | } 169 | 170 | // Cmd is a helper that allows to run r2 commands and receive their output. 171 | func (r2p *Pipe) Cmd(cmd string) (string, error) { 172 | if r2p.Core != nil { 173 | if r2p.cmd != nil { 174 | return r2p.cmd(r2p, cmd) 175 | } 176 | 177 | return "", nil 178 | } 179 | 180 | if _, err := fmt.Fprintln(r2p, cmd); err != nil { 181 | return "", err 182 | } 183 | 184 | buf, err := bufio.NewReader(r2p).ReadString('\x00') 185 | if err != nil { 186 | return "", err 187 | } 188 | return strings.TrimRight(buf, "\n\x00"), err 189 | } 190 | 191 | //like cmd but formats the command 192 | func (r2p *Pipe) Cmdf(f string, args ...interface{}) (string, error) { 193 | return r2p.Cmd(fmt.Sprintf(f, args...)) 194 | } 195 | 196 | // Cmdj acts like Cmd but interprets the output of the command as json. It 197 | // returns the parsed json keys and values. 198 | func (r2p *Pipe) Cmdj(cmd string) (out interface{}, err error) { 199 | rstr, err := r2p.Cmd(cmd) 200 | if err == nil { 201 | err = json.Unmarshal([]byte(rstr), out) 202 | } 203 | return out, err 204 | } 205 | 206 | // CmdjStruct acts like Cmdjs but it will fill the interface/struct with the wanted values. It 207 | // returns the command execution error. 208 | func (r2p *Pipe) CmdjStruct(cmd string, out interface{}) (err error) { 209 | rstr, err := r2p.Cmd(cmd) 210 | 211 | if err == nil { 212 | err = json.Unmarshal([]byte(rstr), out) 213 | } 214 | return err 215 | } 216 | 217 | //like cmdj but formats the command 218 | func (r2p *Pipe) Cmdjf(f string, args ...interface{}) (interface{}, error) { 219 | return r2p.Cmdj(fmt.Sprintf(f, args...)) 220 | } 221 | 222 | // like Cmdj, but besides format the command it will already fill the interface sent 223 | func (r2p *Pipe) CmdjfStruct(f string, out interface{}, args ...interface{}) error { 224 | return r2p.CmdjStruct(fmt.Sprintf(f, args...), out) 225 | } 226 | 227 | // Close shuts down r2, closing the created pipe. 228 | func (r2p *Pipe) Close() error { 229 | if r2p.close != nil { 230 | return r2p.close(r2p) 231 | } 232 | 233 | if r2p.File == "" { 234 | return nil 235 | } 236 | 237 | if _, err := r2p.Cmd("q"); err != nil { 238 | return err 239 | } 240 | 241 | return r2p.r2cmd.Wait() 242 | } 243 | 244 | // Forcing shutdown of r2, closing the created pipe. 245 | func (r2p *Pipe) ForceClose() error { 246 | if r2p.close != nil { 247 | return r2p.close(r2p) 248 | } 249 | 250 | if r2p.File == "" { 251 | return nil 252 | } 253 | 254 | if _, err := r2p.Cmd("q!"); err != nil { 255 | return err 256 | } 257 | 258 | return r2p.r2cmd.Wait() 259 | } 260 | --------------------------------------------------------------------------------