├── .github ├── FUNDING.yml ├── actions │ ├── install-frida-devkit │ │ └── action.yml │ └── install-frida-go-examples │ │ └── action.yml └── workflows │ ├── examples.yml │ └── linting.yml ├── EXAMPLES.md ├── LICENSE ├── README.md ├── frida ├── android-selinux.c ├── android-selinux.h ├── application.go ├── authentication-service.c ├── authentication-service.h ├── bus.go ├── certificate.go ├── child.go ├── cleanups.go ├── closure.go ├── comp.go ├── crash.go ├── device.go ├── endpoint_parameters.go ├── errors.go ├── example_test.go ├── file_monitor.go ├── frida.go ├── go_to_variants.go ├── iostream.go ├── manager.go ├── misc.go ├── peer_options.go ├── portal_membership.go ├── portal_options.go ├── portal_service.go ├── process.go ├── relay.go ├── remote_device_options.go ├── script.go ├── script_options.go ├── service.go ├── session.go ├── session_options.go ├── snapshot_options.go ├── spawn.go ├── spawn_options.go ├── types.go ├── types_converter.go ├── unmarshaller.go └── variants_to_go.go ├── go.mod └── go.sum /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: frida 2 | -------------------------------------------------------------------------------- /.github/actions/install-frida-devkit/action.yml: -------------------------------------------------------------------------------- 1 | name: Install Frida Devkit 2 | description: Install Frida Devkit 3 | inputs: 4 | arch: 5 | required: true 6 | path: the architecture of the devkit 7 | os: 8 | required: true 9 | path: the target operating system of the devkit 10 | version: 11 | required: true 12 | path: the version of the devkit 13 | runs: 14 | using: composite 15 | steps: 16 | - run: | 17 | mkdir /tmp/frida-core-devkit && cd /tmp/frida-core-devkit 18 | wget https://github.com/frida/frida/releases/download/${{ inputs.version }}/frida-core-devkit-${{ inputs.version }}-${{ inputs.os }}-${{ inputs.arch }}.tar.xz -O - | tar --extract --xz 19 | sudo mkdir -p /usr/local/include 20 | sudo mkdir -p /usr/local/lib 21 | sudo cp frida-core.h /usr/local/include/frida-core.h 22 | sudo cp libfrida-core.a /usr/local/lib/libfrida-core.a 23 | shell: bash 24 | -------------------------------------------------------------------------------- /.github/actions/install-frida-go-examples/action.yml: -------------------------------------------------------------------------------- 1 | name: Install frida-go examples 2 | description: Install frida-go examples 3 | runs: 4 | using: composite 5 | steps: 6 | - run: | 7 | git clone https://github.com/nsecho/frida-go-examples.git ./frida-go-examples 8 | rm ./frida-go-examples/README.md 9 | shell: bash 10 | -------------------------------------------------------------------------------- /.github/workflows/examples.yml: -------------------------------------------------------------------------------- 1 | name: examples 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - "main" 8 | 9 | jobs: 10 | examples: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | include: 15 | - runs_on: ubuntu-latest 16 | arch: x86_64 17 | os: linux 18 | frida_version: "17.0.5" 19 | - runs_on: macos-latest 20 | arch: arm64 21 | os: macos 22 | frida_version: "17.0.5" 23 | runs-on: ${{ matrix.runs_on }} 24 | env: 25 | GOEXPERIMENT: cgocheck2 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: actions/setup-go@v5 29 | with: 30 | go-version: '^1.22.x' 31 | - uses: ./.github/actions/install-frida-go-examples 32 | - uses: ./.github/actions/install-frida-devkit 33 | with: 34 | arch: ${{ matrix.arch }} 35 | os: ${{ matrix.os }} 36 | version: ${{ matrix.frida_version }} 37 | - run: | 38 | gcc -v; for example in ./frida-go-examples/*; do 39 | go build "$example" 40 | done 41 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | name: linting 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - "main" 8 | 9 | jobs: 10 | staticcheck: 11 | strategy: 12 | matrix: 13 | arch: [x86_64] 14 | os: [linux] 15 | frida_version: ["16.3.3"] 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-go@v5 20 | - uses: ./.github/actions/install-frida-devkit 21 | with: 22 | arch: ${{ matrix.arch }} 23 | os: ${{ matrix.os }} 24 | version: ${{ matrix.frida_version }} 25 | - run: go install honnef.co/go/tools/cmd/staticcheck@2024.1.1 26 | - run: ~/go/bin/staticcheck -checks all ./frida 27 | -------------------------------------------------------------------------------- /EXAMPLES.md: -------------------------------------------------------------------------------- 1 | ## Channels 2 | 3 | ```golang 4 | package main 5 | 6 | import ( 7 | "bufio" 8 | "fmt" 9 | "os" 10 | 11 | "github.com/frida/frida-go/frida" 12 | ) 13 | 14 | func main() { 15 | r := bufio.NewReader(os.Stdin) 16 | dev := frida.USBDevice() 17 | channel, err := dev.OpenChannel("tcp:8080") 18 | if err != nil { 19 | panic(err) 20 | } 21 | defer channel.Close() 22 | 23 | dt := make([]byte, 512) 24 | read, err := channel.Read(&dt) 25 | if err != nil { 26 | panic(err) 27 | } 28 | fmt.Printf("Got %s; %d bytes\n", string(dt), read) 29 | 30 | n, err := channel.Write([]byte("what's up")) 31 | if err != nil { 32 | panic(err) 33 | } 34 | fmt.Printf("Written %d bytes\n", n) 35 | 36 | r.ReadLine() 37 | } 38 | ``` 39 | 40 | ## Child gating 41 | 42 | ```golang 43 | package main 44 | 45 | import ( 46 | "bufio" 47 | "fmt" 48 | "os" 49 | 50 | "github.com/frida/frida-go/frida" 51 | ) 52 | 53 | var sc = ` 54 | Interceptor.attach(Module.getExportByName(null, 'open'), { 55 | onEnter: function (args) { 56 | send({ 57 | type: 'open', 58 | path: Memory.readUtf8String(args[0]) 59 | }); 60 | } 61 | }); 62 | ` 63 | 64 | func main() { 65 | d := frida.LocalDevice() 66 | 67 | instrument := func(pid int) { 68 | fmt.Printf("✔ attach(pid={%d})\n", pid) 69 | sess, err := d.Attach(pid, nil) 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | sess.On("detached", func(reason frida.SessionDetachReason) { 75 | fmt.Printf("⚡ detached: pid={%d}, reason='{%s}'\n", pid, frida.SessionDetachReason(reason)) 76 | }) 77 | 78 | fmt.Printf("✔ enable_child_gating()\n") 79 | if err := sess.EnableChildGating(); err != nil { 80 | panic(err) 81 | } 82 | 83 | fmt.Printf("✔ create_script()\n") 84 | script, err := sess.CreateScript(sc) 85 | if err != nil { 86 | panic(err) 87 | } 88 | 89 | script.On("message", func(message string) { 90 | fmt.Printf("⚡ message: pid={%d}, payload={message['%s']}\n", pid, message) 91 | }) 92 | 93 | fmt.Printf("✔ load()\n") 94 | script.Load() 95 | 96 | fmt.Printf("✔ resume(pid={%d})\n", pid) 97 | d.Resume(pid) 98 | } 99 | 100 | d.On("child-added", func(child *frida.Child) { 101 | fmt.Printf("⚡ child_added: {%d}, parent_pid: {%d}\n", 102 | child.PID(), 103 | child.PPID()) 104 | instrument(int(child.PID())) 105 | }) 106 | 107 | d.On("child-removed", func(child *frida.Child) { 108 | fmt.Printf("⚡ child_removed: {%v}\n", child.PID()) 109 | }) 110 | 111 | d.On("output", func(pid int, fd int, data []byte) { 112 | fmt.Printf("⚡ output: pid={%d}, fd={%d}, data={%s}\n", 113 | pid, 114 | fd, 115 | string(data)) 116 | }) 117 | 118 | fopts := frida.NewSpawnOptions() 119 | fopts.SetArgv([]string{ 120 | "/bin/sh", 121 | "-c", 122 | "cat /etc/hosts", 123 | }) 124 | fopts.SetStdio(frida.StdioPipe) 125 | 126 | fmt.Printf("✔ spawn(argv={%v})\n", fopts.Argv()) 127 | pid, err := d.Spawn("/bin/sh", fopts) 128 | if err != nil { 129 | panic(err) 130 | } 131 | 132 | instrument(pid) 133 | 134 | r := bufio.NewReader(os.Stdin) 135 | r.ReadLine() 136 | } 137 | ```` 138 | 139 | ## Compiler build 140 | 141 | __agent.ts:__ 142 | ```typescript 143 | import { log } from "./log.js"; 144 | 145 | log("Hello from Frida:", Frida.version); 146 | ``` 147 | 148 | __log.ts:__ 149 | ```typescript 150 | export function log(...args: any[]) { 151 | console.log(...args); 152 | } 153 | ``` 154 | 155 | __main.go:__ 156 | ```golang 157 | package main 158 | 159 | import ( 160 | "fmt" 161 | "github.com/frida/frida-go/frida" 162 | "os" 163 | ) 164 | 165 | func main() { 166 | c := frida.NewCompiler() 167 | c.On("starting", func() { 168 | fmt.Println("[*] Starting compiler") 169 | }) 170 | c.On("finished", func() { 171 | fmt.Println("[*] Compiler finished") 172 | }) 173 | c.On("bundle", func(bundle string) { 174 | fmt.Printf("[*] Compiler bundle: %s\n", 175 | bundle) 176 | }) 177 | c.On("diagnostics", func(diag string) { 178 | fmt.Printf("[*] Compiler diagnostics: %s\n", diag) 179 | }) 180 | 181 | bundle, err := c.Build("agent.ts") 182 | if err != nil { 183 | panic(err) 184 | } 185 | os.WriteFile("_agent.js", []byte(bundle), os.ModePerm) 186 | } 187 | ``` 188 | 189 | ## Compiler watch 190 | 191 | __agent.ts:__ 192 | ```typescript 193 | import { log } from "./log.js"; 194 | 195 | log("Hello from Frida:", Frida.version); 196 | ``` 197 | 198 | __log.ts:__ 199 | ```typescript 200 | export function log(...args: any[]) { 201 | console.log(...args); 202 | } 203 | ``` 204 | 205 | __main.go:__ 206 | ```golang 207 | package main 208 | 209 | import ( 210 | "bufio" 211 | "encoding/json" 212 | "fmt" 213 | "os" 214 | 215 | "github.com/frida/frida-go/frida" 216 | ) 217 | 218 | func main() { 219 | sess, err := frida.Attach(0) 220 | if err != nil { 221 | panic(err) 222 | } 223 | 224 | var script *frida.Script = nil 225 | 226 | onMessage := func(msg string) { 227 | msgMap := make(map[string]string) 228 | json.Unmarshal([]byte(msg), &msgMap) 229 | fmt.Printf("on_message: %s\n", msgMap["payload"]) 230 | } 231 | 232 | compiler := frida.NewCompiler() 233 | compiler.On("output", func(bundle string) { 234 | if script != nil { 235 | fmt.Println("Unloading old bundle...") 236 | script.Unload() 237 | script = nil 238 | } 239 | fmt.Println("Loading bundle...") 240 | script, _ = sess.CreateScript(bundle) 241 | script.On("message", onMessage) 242 | script.Load() 243 | }) 244 | 245 | compiler.Watch("agent.ts") 246 | 247 | r := bufio.NewReader(os.Stdin) 248 | r.ReadLine() 249 | } 250 | ``` 251 | 252 | ## File Monitor 253 | 254 | ```golang 255 | package main 256 | 257 | import ( 258 | "bufio" 259 | "fmt" 260 | "os" 261 | "time" 262 | 263 | "github.com/frida/frida-go/frida" 264 | ) 265 | 266 | func main() { 267 | mon := frida.NewFileMonitor("/tmp/test.txt") 268 | if err := mon.Enable(); err != nil { 269 | panic(err) 270 | } 271 | 272 | mon.On("change", func(changedFile, otherFile, changeType string) { 273 | fmt.Printf("[*] File %s has changed (%s)\n", changedFile, changeType) 274 | }) 275 | 276 | fmt.Printf("[*] Monitoring path: %s\n", mon.Path()) 277 | 278 | t := time.NewTimer(20 * time.Second) 279 | <-t.C 280 | 281 | if err := mon.Disable(); err != nil { 282 | panic(err) 283 | } 284 | 285 | r := bufio.NewReader(os.Stdin) 286 | r.ReadLine() 287 | } 288 | ``` 289 | 290 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2022 frida-go contributors 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # frida-go 2 | Go bindings for frida. 3 | 4 | For the documentation, visit [https://pkg.go.dev/github.com/frida/frida-go/frida](https://pkg.go.dev/github.com/frida/frida-go/frida). 5 | 6 | # Installation 7 | * `GO111MODULE` needs to be set to `on` or `auto`. 8 | * Download the _frida-core-devkit_ from the Frida releases [page](https://github.com/frida/frida/releases/) for you operating system and architecture. 9 | * Extract the downloaded archive 10 | * Copy _frida-core.h_ inside your systems include directory(inside /usr/local/include/) and _libfrida-core.a_ inside your lib directory (usually /usr/local/lib). 11 | 12 | To use in your project, just execute: 13 | ```bash 14 | $ go get github.com/frida/frida-go/frida@latest 15 | ``` 16 | 17 | Supported OS: 18 | - [x] MacOS 19 | - [x] Linux 20 | - [x] Android 21 | - [ ] Windows 22 | 23 | # Small example 24 | ```golang 25 | package main 26 | 27 | import ( 28 | "bufio" 29 | "fmt" 30 | "github.com/frida/frida-go/frida" 31 | "os" 32 | ) 33 | 34 | var script = ` 35 | Interceptor.attach(Module.getExportByName(null, 'open'), { 36 | onEnter(args) { 37 | const what = args[0].readUtf8String(); 38 | console.log("[*] open(" + what + ")"); 39 | } 40 | }); 41 | Interceptor.attach(Module.getExportByName(null, 'close'), { 42 | onEnter(args) { 43 | console.log("close called"); 44 | } 45 | }); 46 | ` 47 | 48 | func main() { 49 | mgr := frida.NewDeviceManager() 50 | 51 | devices, err := mgr.EnumerateDevices() 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | for _, d := range devices { 57 | fmt.Println("[*] Found device with id:", d.ID()) 58 | } 59 | 60 | localDev, err := mgr.LocalDevice() 61 | if err != nil { 62 | fmt.Println("Could not get local device: ", err) 63 | // Let's exit here because there is no point to do anything with nonexistent device 64 | os.Exit(1) 65 | } 66 | 67 | fmt.Println("[*] Chosen device: ", localDev.Name()) 68 | 69 | fmt.Println("[*] Attaching to Telegram") 70 | session, err := localDev.Attach("Telegram", nil) 71 | if err != nil { 72 | fmt.Println("Error occurred attaching:", err) 73 | os.Exit(1) 74 | } 75 | 76 | script, err := session.CreateScript(script) 77 | if err != nil { 78 | fmt.Println("Error occurred creating script:", err) 79 | os.Exit(1) 80 | } 81 | 82 | script.On("message", func(msg string) { 83 | fmt.Println("[*] Received", msg) 84 | }) 85 | 86 | if err := script.Load(); err != nil { 87 | fmt.Println("Error loading script:", err) 88 | os.Exit(1) 89 | } 90 | 91 | r := bufio.NewReader(os.Stdin) 92 | r.ReadLine() 93 | } 94 | 95 | ``` 96 | 97 | Build and run it, output will look something like this: 98 | ```bash 99 | $ go build example.go && ./example 100 | [*] Found device with id: local 101 | [*] Found device with id: socket 102 | [*] Chosen device: Local System 103 | [*] Attaching to Telegram 104 | [*] Received {"type":"log","level":"info","payload":"[*] open(/Users/daemon1/Library/Application Support/Telegram Desktop/tdata/user_data/cache/0/25/0FDE3ED70BCA)"} 105 | [*] Received {"type":"log","level":"info","payload":"[*] open(/Users/daemon1/Library/Application Support/Telegram Desktop/tdata/user_data/cache/0/8E/FD728183E115)"} 106 | ``` 107 | -------------------------------------------------------------------------------- /frida/android-selinux.c: -------------------------------------------------------------------------------- 1 | #include "android-selinux.h" 2 | 3 | void android_patch_selinux(void) { 4 | #ifdef __ANDROID__ 5 | frida_selinux_patch_policy(); 6 | #endif 7 | } -------------------------------------------------------------------------------- /frida/android-selinux.h: -------------------------------------------------------------------------------- 1 | #ifndef __ANDROID_SELINUX__ 2 | #define __ANDROID_SELINUX__ 3 | 4 | #include 5 | 6 | void android_patch_selinux(void); 7 | 8 | #endif -------------------------------------------------------------------------------- /frida/application.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | 6 | import ( 7 | "fmt" 8 | "unsafe" 9 | ) 10 | 11 | // Application represents the main application installed on the device 12 | type Application struct { 13 | application *C.FridaApplication 14 | } 15 | 16 | // Identifier returns application bundle identifier 17 | func (a *Application) Identifier() string { 18 | return C.GoString(C.frida_application_get_identifier(a.application)) 19 | } 20 | 21 | // Name returns application name 22 | func (a *Application) Name() string { 23 | return C.GoString(C.frida_application_get_name(a.application)) 24 | } 25 | 26 | // PID returns application PID or "-" if it could not be obtained when application is not running 27 | func (a *Application) PID() int { 28 | return int(C.frida_application_get_pid(a.application)) 29 | } 30 | 31 | // String returns the string representation of Application printing identifier, name and pid 32 | func (a *Application) String() string { 33 | return fmt.Sprintf("Identifier: %s Name: %s PID: %d", a.Identifier(), a.Name(), a.PID()) 34 | } 35 | 36 | // Params return the application parameters, like version, path etc 37 | func (a *Application) Params() map[string]any { 38 | ht := C.frida_application_get_parameters(a.application) 39 | params := gHashTableToMap(ht) 40 | return params 41 | } 42 | 43 | // Clean will clean resources held by the application. 44 | func (a *Application) Clean() { 45 | clean(unsafe.Pointer(a.application), unrefFrida) 46 | } 47 | -------------------------------------------------------------------------------- /frida/authentication-service.c: -------------------------------------------------------------------------------- 1 | #include "authentication-service.h" 2 | 3 | void init_frida (void) __attribute__ ((constructor)); 4 | 5 | void init_frida(void) { 6 | frida_init (); 7 | } 8 | 9 | extern void * authenticate(void*,char*); 10 | 11 | struct _GoAuthenticationService { 12 | GObject parent; 13 | void * callback; 14 | GThreadPool * pool; 15 | }; 16 | 17 | GoAuthenticationService * frida_go_authentication_service_new (void * callback); 18 | static void frida_go_authentication_service_iface_init (gpointer g_iface, gpointer iface_data); 19 | static void frida_go_authentication_service_dispose (GObject * object); 20 | static void frida_go_authentication_service_authenticate (GoAuthenticationService * service, const gchar * token, 21 | GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data); 22 | static gchar * frida_go_authentication_service_authenticate_finish (GoAuthenticationService * service, GAsyncResult * result, 23 | GError ** error); 24 | static void frida_go_authentication_service_do_authenticate (GTask * task, GoAuthenticationService * self); 25 | 26 | G_DEFINE_TYPE_EXTENDED (GoAuthenticationService, frida_go_authentication_service, G_TYPE_OBJECT, 0, 27 | G_IMPLEMENT_INTERFACE (FRIDA_TYPE_AUTHENTICATION_SERVICE, frida_go_authentication_service_iface_init)) 28 | 29 | 30 | GoAuthenticationService * frida_go_authentication_service_new (void * callback) { 31 | GoAuthenticationService * service = NULL; 32 | 33 | service = g_object_new (FRIDA_TYPE_GO_AUTHENTICATION_SERVICE, NULL); 34 | service->callback = callback; 35 | 36 | return service; 37 | } 38 | 39 | static void frida_go_authentication_service_iface_init (gpointer g_iface, gpointer iface_data){ 40 | FridaAuthenticationServiceIface * iface = g_iface; 41 | 42 | iface->authenticate = frida_go_authentication_service_authenticate; 43 | iface->authenticate_finish = frida_go_authentication_service_authenticate_finish; 44 | } 45 | 46 | static void 47 | frida_go_authentication_service_class_init (GoAuthenticationServiceClass * klass) 48 | { 49 | GObjectClass * object_class = G_OBJECT_CLASS (klass); 50 | 51 | object_class->dispose = frida_go_authentication_service_dispose; 52 | } 53 | 54 | static void frida_go_authentication_service_dispose (GObject * object) { 55 | GoAuthenticationService * self = FRIDA_GO_AUTHENTICATION_SERVICE(object); 56 | 57 | if (self->pool != NULL) { 58 | g_thread_pool_free (self->pool, FALSE, FALSE); 59 | self->pool = NULL; 60 | } 61 | 62 | if (self->callback != NULL) { 63 | self->callback = NULL; 64 | } 65 | 66 | G_OBJECT_CLASS (frida_go_authentication_service_parent_class)->dispose (object); 67 | } 68 | 69 | static void 70 | frida_go_authentication_service_init (GoAuthenticationService * self) 71 | { 72 | self->pool = g_thread_pool_new ((GFunc) frida_go_authentication_service_do_authenticate, self, 1, FALSE, NULL); 73 | } 74 | 75 | static void frida_go_authentication_service_authenticate (GoAuthenticationService * service, const gchar * token, 76 | GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data) 77 | { 78 | GoAuthenticationService * self; 79 | GTask * task; 80 | 81 | self = FRIDA_GO_AUTHENTICATION_SERVICE (service); 82 | 83 | task = g_task_new (self, cancellable, callback, user_data); 84 | g_task_set_task_data (task, g_strdup (token), g_free); 85 | 86 | g_thread_pool_push (self->pool, task, NULL); 87 | } 88 | 89 | static gchar * 90 | frida_go_authentication_service_authenticate_finish (GoAuthenticationService * service, GAsyncResult * result, GError ** error) 91 | { 92 | return g_task_propagate_pointer (G_TASK (result), error); 93 | } 94 | 95 | static void 96 | frida_go_authentication_service_do_authenticate (GTask * task, GoAuthenticationService * self) 97 | { 98 | const gchar * token; 99 | const gchar * session_info = NULL; 100 | gchar * message; 101 | void * result = NULL; 102 | 103 | token = g_task_get_task_data (task); 104 | 105 | result = authenticate(self->callback, (char*)token); 106 | 107 | if (result == NULL) { 108 | message = g_strdup ("Internal error"); 109 | } 110 | 111 | session_info = (char*)result; 112 | 113 | if (session_info != NULL) { 114 | g_task_return_pointer (task, session_info, g_free); 115 | } else { 116 | g_task_return_new_error (task, FRIDA_ERROR, FRIDA_ERROR_INVALID_ARGUMENT, "%s", message); 117 | } 118 | 119 | g_free (message); 120 | g_object_unref (task); 121 | } -------------------------------------------------------------------------------- /frida/authentication-service.h: -------------------------------------------------------------------------------- 1 | #ifndef __AUTHENTICATION_SERVICE_H__ 2 | #define __AUTHENTICATION_SERVICE_H__ 3 | 4 | #include 5 | 6 | #define FRIDA_TYPE_GO_AUTHENTICATION_SERVICE (frida_go_authentication_service_get_type ()) 7 | G_DECLARE_FINAL_TYPE (GoAuthenticationService, frida_go_authentication_service, FRIDA, GO_AUTHENTICATION_SERVICE, GObject) 8 | 9 | GoAuthenticationService * frida_go_authentication_service_new (void * callback); 10 | 11 | #endif -------------------------------------------------------------------------------- /frida/bus.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | //#include 5 | import "C" 6 | import ( 7 | "runtime" 8 | "unsafe" 9 | ) 10 | 11 | // Bus represent bus used to communicate with the devices. 12 | type Bus struct { 13 | bus *C.FridaBus 14 | } 15 | 16 | // IsDetached returns whether the bus is detached from the device or not. 17 | func (b *Bus) IsDetached() bool { 18 | dt := C.int(C.frida_bus_is_detached(b.bus)) 19 | return dt == 1 20 | } 21 | 22 | // Attach attaches on the device bus. 23 | func (b *Bus) Attach() error { 24 | var err *C.GError 25 | C.frida_bus_attach_sync(b.bus, nil, &err) 26 | return handleGError(err) 27 | } 28 | 29 | // Post send(post) msg to the device. 30 | func (b *Bus) Post(msg string, data []byte) { 31 | msgC := C.CString(msg) 32 | defer C.free(unsafe.Pointer(msgC)) 33 | 34 | gBytesData := goBytesToGBytes(data) 35 | runtime.SetFinalizer(gBytesData, func(g *C.GBytes) { 36 | clean(unsafe.Pointer(g), unrefGObject) 37 | }) 38 | C.frida_bus_post(b.bus, msgC, gBytesData) 39 | runtime.KeepAlive(gBytesData) 40 | } 41 | 42 | // Clean will clean resources held by the bus. 43 | func (b *Bus) Clean() { 44 | clean(unsafe.Pointer(b.bus), unrefFrida) 45 | } 46 | 47 | // On connects bus to specific signals. Once sigName is triggered, 48 | // fn callback will be called with parameters populated. 49 | // 50 | // Signals available are: 51 | // - "detached" with callback as func() {} 52 | // - "message" with callback as func(message string, data []byte) {} 53 | func (b *Bus) On(sigName string, fn any) { 54 | connectClosure(unsafe.Pointer(b.bus), sigName, fn) 55 | } 56 | -------------------------------------------------------------------------------- /frida/certificate.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "time" 7 | "unsafe" 8 | ) 9 | 10 | const ( 11 | cTimeFormat = "%Y-%m-%d %H:%M:%S" 12 | timeFormat = "2006-01-02 15:04:05" 13 | ) 14 | 15 | // Certificate represents the GTlsCertificate. 16 | type Certificate struct { 17 | cert *C.GTlsCertificate 18 | } 19 | 20 | // IssuerName returns the issuer name for the certificate. 21 | func (c *Certificate) IssuerName() string { 22 | iss := C.g_tls_certificate_get_issuer_name(c.cert) 23 | defer C.free(unsafe.Pointer(iss)) 24 | return C.GoString(iss) 25 | } 26 | 27 | // SubjectName returns the subject name for the certificate. 28 | func (c *Certificate) SubjectName() string { 29 | sub := C.g_tls_certificate_get_subject_name(c.cert) 30 | defer C.free(unsafe.Pointer(sub)) 31 | return C.GoString(sub) 32 | } 33 | 34 | // NotValidBefore returns the time before which certificate is not valid. 35 | func (c *Certificate) NotValidBefore() (time.Time, error) { 36 | vld := C.g_tls_certificate_get_not_valid_before(c.cert) 37 | frmt := C.CString(cTimeFormat) 38 | defer C.free(unsafe.Pointer(frmt)) 39 | 40 | cc := C.g_date_time_format(vld, frmt) 41 | defer C.free(unsafe.Pointer(cc)) 42 | 43 | return time.Parse(timeFormat, C.GoString(cc)) 44 | } 45 | 46 | // NotValidAfter returns the time after which certificate is not valid. 47 | func (c *Certificate) NotValidAfter() (time.Time, error) { 48 | vld := C.g_tls_certificate_get_not_valid_after(c.cert) 49 | frmt := C.CString(cTimeFormat) 50 | defer C.free(unsafe.Pointer(frmt)) 51 | 52 | cc := C.g_date_time_format(vld, frmt) 53 | defer C.free(unsafe.Pointer(cc)) 54 | 55 | return time.Parse(timeFormat, C.GoString(cc)) 56 | } 57 | -------------------------------------------------------------------------------- /frida/child.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import "unsafe" 6 | 7 | // Child type represents child when child gating is enabled. 8 | type Child struct { 9 | child *C.FridaChild 10 | } 11 | 12 | // PID returns the process id of the child. 13 | func (f *Child) PID() uint { 14 | return uint(C.frida_child_get_pid(f.child)) 15 | } 16 | 17 | // PPID returns the parent process id of the child. 18 | func (f *Child) PPID() uint { 19 | return uint(C.frida_child_get_parent_pid(f.child)) 20 | } 21 | 22 | // Origin returns the origin of the child. 23 | func (f *Child) Origin() ChildOrigin { 24 | return ChildOrigin(C.frida_child_get_origin(f.child)) 25 | } 26 | 27 | // Identifier returns string identifier of the child. 28 | func (f *Child) Identifier() string { 29 | return C.GoString(C.frida_child_get_identifier(f.child)) 30 | } 31 | 32 | // Path returns the path of the child. 33 | func (f *Child) Path() string { 34 | return C.GoString(C.frida_child_get_path(f.child)) 35 | } 36 | 37 | // Argv returns argv passed to the child. 38 | func (f *Child) Argv() []string { 39 | var length C.gint 40 | arr := C.frida_child_get_argv(f.child, &length) 41 | 42 | return cArrayToStringSlice(arr, C.int(length)) 43 | } 44 | 45 | // Envp returns envp passed to the child. 46 | func (f *Child) Envp() []string { 47 | var length C.gint 48 | arr := C.frida_child_get_envp(f.child, &length) 49 | 50 | return cArrayToStringSlice(arr, C.int(length)) 51 | } 52 | 53 | // Clean will clean resources held by the child. 54 | func (f *Child) Clean() { 55 | clean(unsafe.Pointer(f.child), unrefFrida) 56 | } 57 | -------------------------------------------------------------------------------- /frida/cleanups.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | type cleanupType string 10 | 11 | const ( 12 | unrefGError cleanupType = "*GError" 13 | unrefFrida cleanupType = "frida types" 14 | unrefGObject cleanupType = "GObject*" 15 | ) 16 | 17 | type cleanupFn func(unsafe.Pointer) 18 | 19 | var cleanups = map[cleanupType]cleanupFn{ 20 | unrefGError: gErrorFree, 21 | unrefFrida: unrefGObj, 22 | unrefGObject: unrefGObj, 23 | } 24 | 25 | func gErrorFree(err unsafe.Pointer) { 26 | C.g_error_free((*C.GError)(err)) 27 | } 28 | 29 | func unrefGObj(obj unsafe.Pointer) { 30 | C.g_object_unref((C.gpointer)(obj)) 31 | } 32 | 33 | func clean(obj unsafe.Pointer, cType cleanupType) { 34 | if obj != nil { 35 | fn := cleanups[cType] 36 | if fn != nil { 37 | fn(obj) 38 | } 39 | } 40 | } 41 | 42 | func freeCharArray(arr **C.char, size C.int) { 43 | for i := 0; i < int(size); i++ { 44 | elem := getCharArrayElement(arr, i) 45 | C.free(unsafe.Pointer(elem)) 46 | } 47 | C.free(unsafe.Pointer(arr)) 48 | } 49 | -------------------------------------------------------------------------------- /frida/closure.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | /* 4 | #include 5 | 6 | extern void deleteClosure(gpointer, GClosure*); 7 | extern void goMarshalCls(GClosure*, GValue*, guint, GValue*, gpointer, GValue*); 8 | 9 | static GClosure * newClosure() { 10 | GClosure * closure = g_closure_new_simple(sizeof(GClosure), NULL); 11 | g_closure_set_marshal(closure, (GClosureMarshal)(goMarshalCls)); 12 | g_closure_add_finalize_notifier(closure, NULL, (GClosureNotify)(deleteClosure)); 13 | 14 | return closure; 15 | } 16 | 17 | static GType getVType(GValue * val) { 18 | return (G_VALUE_TYPE(val)); 19 | } 20 | 21 | static guint lookup_signal(void * obj, char * sigName) { 22 | return g_signal_lookup(sigName, G_OBJECT_TYPE(obj)); 23 | } 24 | */ 25 | import "C" 26 | import ( 27 | "fmt" 28 | "reflect" 29 | "runtime" 30 | "sync" 31 | "unsafe" 32 | ) 33 | 34 | var closures = &sync.Map{} 35 | 36 | //export deleteClosure 37 | func deleteClosure(ptr C.gpointer, closure *C.GClosure) { 38 | closures.Delete(unsafe.Pointer(closure)) 39 | } 40 | 41 | //export goMarshalCls 42 | func goMarshalCls(gclosure *C.GClosure, returnValue *C.GValue, nParams C.guint, 43 | params *C.GValue, 44 | invocationHint C.gpointer, 45 | marshalData *C.GValue) { 46 | 47 | var closure funcstack 48 | cV, ok := closures.Load(unsafe.Pointer(gclosure)) 49 | if !ok { 50 | closure = funcstack{} 51 | } else { 52 | closure = cV.(funcstack) 53 | } 54 | 55 | countOfParams := int(nParams) 56 | 57 | fnType := closure.Func.Type() 58 | fnCountArgs := fnType.NumIn() 59 | 60 | if fnCountArgs > countOfParams { 61 | msg := fmt.Sprintf("too many args: have %d, max %d\n", fnCountArgs, countOfParams) 62 | panic(msg) 63 | } 64 | 65 | gvalues := func(params *C.GValue, count int) []C.GValue { 66 | var slc []C.GValue 67 | hdr := (*reflect.SliceHeader)(unsafe.Pointer(&slc)) 68 | hdr.Cap = count 69 | hdr.Len = count 70 | hdr.Data = uintptr(unsafe.Pointer(params)) 71 | 72 | return slc 73 | }(params, countOfParams) 74 | 75 | fnArgs := make([]reflect.Value, fnCountArgs) 76 | 77 | for i := 0; i < fnCountArgs; i++ { 78 | goV := GValueToGo(&gvalues[i+1]) 79 | fnArgs[i] = reflect.ValueOf(goV).Convert(fnType.In(i)) 80 | } 81 | 82 | closure.Func.Call(fnArgs) 83 | } 84 | 85 | type funcstack struct { 86 | Func reflect.Value 87 | Frames []uintptr 88 | } 89 | 90 | func connectClosure(obj unsafe.Pointer, sigName string, fn any) { 91 | v := reflect.ValueOf(fn) 92 | 93 | if v.Type().Kind() != reflect.Func { 94 | panic("got no function") 95 | } 96 | 97 | frames := make([]uintptr, 3) 98 | frames = frames[:runtime.Callers(2+2, frames)] 99 | 100 | fs := funcstack{ 101 | Func: v, 102 | Frames: frames, 103 | } 104 | 105 | sigC := C.CString(sigName) 106 | defer C.free(unsafe.Pointer(sigC)) 107 | 108 | gclosure := newClosureFunc(fs) 109 | sigID := C.lookup_signal(obj, sigC) 110 | 111 | // Do nothing if signal is 0 meaning not found 112 | if int(sigID) != 0 { 113 | C.g_signal_connect_closure_by_id((C.gpointer)(obj), sigID, 0, gclosure, C.gboolean(1)) 114 | } 115 | } 116 | 117 | func newClosureFunc(fnStack funcstack) *C.GClosure { 118 | cls := C.newClosure() 119 | closures.Store(unsafe.Pointer(cls), fnStack) 120 | return cls 121 | } 122 | -------------------------------------------------------------------------------- /frida/comp.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | /*#include 4 | */ 5 | import "C" 6 | import ( 7 | "reflect" 8 | "unsafe" 9 | ) 10 | 11 | // CompilerOptions represent options passed to compiler to build/watch. 12 | type CompilerOptions struct { 13 | c *C.FridaCompilerOptions 14 | } 15 | 16 | // NewCompilerOptions creates new compiler options. 17 | func NewCompilerOptions() *CompilerOptions { 18 | c := C.frida_compiler_options_new() 19 | return &CompilerOptions{c: c} 20 | } 21 | 22 | // SetProjectRoot sets the project root, you would use this if your entrypoint 23 | // script is in another directory besides the current one. 24 | func (c *CompilerOptions) SetProjectRoot(projectRoot string) { 25 | pRoot := C.CString(projectRoot) 26 | defer C.free(unsafe.Pointer(pRoot)) 27 | 28 | C.frida_compiler_options_set_project_root(c.c, pRoot) 29 | } 30 | 31 | // SetJSCompression allows you to choose compression for generated file. 32 | func (c *CompilerOptions) SetJSCompression(compress JSCompressionType) { 33 | C.frida_compiler_options_set_compression(c.c, (C.FridaJsCompression)(compress)) 34 | } 35 | 36 | // SetSourceMaps allows you to choose whether you want source maps included or omitted. 37 | func (c *CompilerOptions) SetSourceMaps(sourceMaps SourceMaps) { 38 | C.frida_compiler_options_set_source_maps(c.c, (C.FridaSourceMaps)(sourceMaps)) 39 | } 40 | 41 | // Compiler type is used to compile scripts. 42 | type Compiler struct { 43 | cc *C.FridaCompiler 44 | fn reflect.Value 45 | } 46 | 47 | // NewCompiler creates new compiler. 48 | func NewCompiler() *Compiler { 49 | mgr := getDeviceManager() 50 | cc := C.frida_compiler_new(mgr.getManager()) 51 | 52 | return &Compiler{ 53 | cc: cc, 54 | } 55 | } 56 | 57 | // Build builds the script from the entrypoint. 58 | func (c *Compiler) Build(entrypoint string, opts *CompilerOptions) (string, error) { 59 | entrypointC := C.CString(entrypoint) 60 | defer C.free(unsafe.Pointer(entrypointC)) 61 | 62 | var o *C.FridaBuildOptions = nil 63 | if opts != nil { 64 | o = (*C.FridaBuildOptions)(opts.c) 65 | } 66 | 67 | var err *C.GError 68 | ret := C.frida_compiler_build_sync(c.cc, entrypointC, o, nil, &err) 69 | return C.GoString(ret), handleGError(err) 70 | } 71 | 72 | // Watch watches for changes at the entrypoint and sends the "output" signal. 73 | func (c *Compiler) Watch(entrypoint string, opts *CompilerOptions) error { 74 | entrypointC := C.CString(entrypoint) 75 | defer C.free(unsafe.Pointer(entrypointC)) 76 | 77 | var o *C.FridaWatchOptions = nil 78 | if opts != nil { 79 | o = (*C.FridaWatchOptions)(opts.c) 80 | } 81 | 82 | var err *C.GError 83 | C.frida_compiler_watch_sync(c.cc, entrypointC, o, nil, &err) 84 | return handleGError(err) 85 | } 86 | 87 | // Clean will clean resources held by the compiler. 88 | func (c *Compiler) Clean() { 89 | clean(unsafe.Pointer(c.cc), unrefFrida) 90 | } 91 | 92 | // On connects compiler to specific signals. Once sigName is triggered, 93 | // fn callback will be called with parameters populated. 94 | // 95 | // Signals available are: 96 | // - "starting" with callback as func() {} 97 | // - "finished" with callback as func() {} 98 | // - "output" with callback as func(bundle string) {} 99 | // - "diagnostics" with callback as func(diag string) {} 100 | // - "file_changed" with callback as func() {} 101 | func (c *Compiler) On(sigName string, fn any) { 102 | // hijack diagnostics and pass only text 103 | if sigName == "diagnostics" { 104 | c.fn = reflect.ValueOf(fn) 105 | connectClosure(unsafe.Pointer(c.cc), sigName, c.hijackFn) 106 | } else { 107 | connectClosure(unsafe.Pointer(c.cc), sigName, fn) 108 | } 109 | } 110 | 111 | func (c *Compiler) hijackFn(diag map[string]any) { 112 | text := diag["text"].(string) 113 | args := []reflect.Value{reflect.ValueOf(text)} 114 | c.fn.Call(args) 115 | } 116 | -------------------------------------------------------------------------------- /frida/crash.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "fmt" 7 | "unsafe" 8 | ) 9 | 10 | // Crash represents crash of frida. 11 | type Crash struct { 12 | crash *C.FridaCrash 13 | } 14 | 15 | // PID returns the process identifier oc.crashed application 16 | func (c *Crash) PID() int { 17 | if c.crash != nil { 18 | return int(C.frida_crash_get_pid(c.crash)) 19 | } 20 | return -1 21 | } 22 | 23 | // ProcessName returns the name of the process that crashed 24 | func (c *Crash) ProcessName() string { 25 | if c.crash != nil { 26 | return C.GoString(C.frida_crash_get_process_name(c.crash)) 27 | } 28 | return "" 29 | } 30 | 31 | // Summary returns the summary of the crash 32 | func (c *Crash) Summary() string { 33 | if c.crash != nil { 34 | return C.GoString(C.frida_crash_get_summary(c.crash)) 35 | } 36 | return "" 37 | } 38 | 39 | // Report returns the report of the crash 40 | func (c *Crash) Report() string { 41 | if c.crash != nil { 42 | return C.GoString(C.frida_crash_get_report(c.crash)) 43 | } 44 | return "" 45 | } 46 | 47 | // Params returns the parameters of the crash. 48 | func (c *Crash) Params() map[string]any { 49 | if c.crash != nil { 50 | ht := C.frida_crash_get_parameters(c.crash) 51 | params := gHashTableToMap(ht) 52 | return params 53 | } 54 | return nil 55 | } 56 | 57 | // String returns string interpretation of the crash 58 | func (c *Crash) String() string { 59 | if c.crash != nil { 60 | return fmt.Sprintf(": <%p>", c.crash) 61 | } 62 | return "" 63 | } 64 | 65 | // Clean will clean resources held by the crash. 66 | func (c *Crash) Clean() { 67 | if c.crash != nil { 68 | clean(unsafe.Pointer(c.crash), unrefFrida) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /frida/device.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "context" 7 | "errors" 8 | "reflect" 9 | "runtime" 10 | "sort" 11 | "unsafe" 12 | ) 13 | 14 | type DeviceInt interface { 15 | ID() string 16 | Name() string 17 | DeviceIcon() any 18 | DeviceType() DeviceType 19 | Bus() *Bus 20 | IsLost() bool 21 | Params(opts ...OptFunc) (map[string]any, error) 22 | ParamsWithContext(ctx context.Context) (map[string]any, error) 23 | FrontmostApplication(scope Scope) (*Application, error) 24 | EnumerateApplications(identifier string, scope Scope, opts ...OptFunc) ([]*Application, error) 25 | ProcessByPID(pid int, scope Scope) (*Process, error) 26 | ProcessByName(name string, scope Scope) (*Process, error) 27 | FindProcessByPID(pid int, scope Scope) (*Process, error) 28 | FindProcessByName(name string, scope Scope) (*Process, error) 29 | EnumerateProcesses(scope Scope) ([]*Process, error) 30 | EnableSpawnGating() error 31 | DisableSpawnGating() error 32 | EnumeratePendingSpawn() ([]*Spawn, error) 33 | EnumeratePendingChildren() ([]*Child, error) 34 | Spawn(name string, opts *SpawnOptions) (int, error) 35 | Input(pid int, data []byte) error 36 | Resume(pid int) error 37 | Kill(pid int) error 38 | Attach(val any, sessionOpts *SessionOptions, opts ...OptFunc) (*Session, error) 39 | AttachWithContext(ctx context.Context, val any, opts *SessionOptions) (*Session, error) 40 | InjectLibraryFile(target any, path, entrypoint, data string) (uint, error) 41 | InjectLibraryBlob(target any, byteData []byte, entrypoint, data string) (uint, error) 42 | OpenChannel(address string) (*IOStream, error) 43 | OpenService(address string) (*Service, error) 44 | Clean() 45 | On(sigName string, fn any) 46 | } 47 | 48 | // Device represents Device struct from frida-core 49 | type Device struct { 50 | device *C.FridaDevice 51 | } 52 | 53 | // ID will return the ID of the device. 54 | func (d *Device) ID() string { 55 | if d.device != nil { 56 | return C.GoString(C.frida_device_get_id(d.device)) 57 | } 58 | return "" 59 | } 60 | 61 | // Name will return the name of the device. 62 | func (d *Device) Name() string { 63 | if d.device != nil { 64 | return C.GoString(C.frida_device_get_name(d.device)) 65 | } 66 | return "" 67 | } 68 | 69 | // DeviceIcon will return the device icon. 70 | func (d *Device) DeviceIcon() any { 71 | if d.device != nil { 72 | icon := C.frida_device_get_icon(d.device) 73 | dt := gPointerToGo((C.gpointer)(icon)) 74 | return dt 75 | } 76 | return nil 77 | } 78 | 79 | // DeviceType returns type of the device. 80 | func (d *Device) DeviceType() DeviceType { 81 | if d.device != nil { 82 | fdt := C.frida_device_get_dtype(d.device) 83 | return DeviceType(fdt) 84 | } 85 | return -1 86 | } 87 | 88 | // Bus returns device bus. 89 | func (d *Device) Bus() *Bus { 90 | if d.device != nil { 91 | bus := C.frida_device_get_bus(d.device) 92 | return &Bus{ 93 | bus: bus, 94 | } 95 | } 96 | return nil 97 | } 98 | 99 | // IsLost returns boolean whether device is lost or not. 100 | func (d *Device) IsLost() bool { 101 | if d.device != nil { 102 | lost := C.frida_device_is_lost(d.device) 103 | return int(lost) == 1 104 | } 105 | return false 106 | } 107 | 108 | // ParamsWithContext runs Params but with context. 109 | // This function will properly handle cancelling the frida operation. 110 | // It is advised to use this rather than handling Cancellable yourself. 111 | func (d *Device) ParamsWithContext(ctx context.Context) (map[string]any, error) { 112 | rawParams, err := handleWithContext(ctx, func(c *Cancellable, doneC chan any, errC chan error) { 113 | params, err := d.Params(WithCancel(c)) 114 | if err != nil { 115 | errC <- err 116 | return 117 | } 118 | doneC <- params 119 | }) 120 | params, _ := rawParams.(map[string]any) 121 | return params, err 122 | } 123 | 124 | // Params returns system parameters of the device 125 | // You can add an option with the variadic opts argument. 126 | // 127 | // Example: 128 | // 129 | // params, err := device.Params() 130 | // 131 | // 132 | // // or WithCancel 133 | // 134 | // cancel := frida.NewCancellable() 135 | // params, err := device.Params(frida.WithCancel(c)) 136 | // 137 | // // ... 138 | // 139 | // cancel.Cancel() 140 | func (d *Device) Params(opts ...OptFunc) (map[string]any, error) { 141 | o := setupOptions(opts) 142 | return d.params(o) 143 | } 144 | 145 | func (d *Device) params(opts options) (map[string]any, error) { 146 | if d.device == nil { 147 | return nil, errors.New("could not obtain params for nil device") 148 | } 149 | 150 | var err *C.GError 151 | ht := C.frida_device_query_system_parameters_sync(d.device, opts.cancellable, &err) 152 | if err != nil { 153 | return nil, handleGError(err) 154 | } 155 | return gHashTableToMap(ht), nil 156 | } 157 | 158 | // FrontmostApplication will return the frontmost application or the application in focus 159 | // on the device. 160 | func (d *Device) FrontmostApplication(scope Scope) (*Application, error) { 161 | if d.device != nil { 162 | var err *C.GError 163 | app := &Application{} 164 | 165 | sc := C.FridaScope(scope) 166 | queryOpts := C.frida_frontmost_query_options_new() 167 | C.frida_frontmost_query_options_set_scope(queryOpts, sc) 168 | app.application = C.frida_device_get_frontmost_application_sync(d.device, 169 | queryOpts, 170 | nil, 171 | &err) 172 | if err != nil { 173 | return nil, handleGError(err) 174 | } 175 | 176 | if app.application == nil { 177 | return nil, errors.New("could not obtain frontmost application! Is any application started?") 178 | } 179 | 180 | return app, nil 181 | } 182 | return nil, errors.New("could not obtain frontmost app for nil device") 183 | } 184 | 185 | // EnumerateApplications will return slice of applications on the device 186 | // You can add an option with the variadic opts argument 187 | // 188 | // Example: 189 | // 190 | // apps, err := device.EnumerateApplications("", frida.ScopeFull) 191 | // 192 | // // or providing the option to cancel 193 | // 194 | // cancel := frida.NewCancellable() 195 | // apps, err := device.EnumerateApplications("", frida.ScopeFull, frida.WithCancel(c)) 196 | // 197 | // // ... 198 | // 199 | // cancel.Cancel() 200 | func (d *Device) EnumerateApplications(identifier string, scope Scope, opts ...OptFunc) ([]*Application, error) { 201 | o := setupOptions(opts) 202 | return d.enumerateApplications(identifier, scope, o) 203 | } 204 | 205 | func (d *Device) enumerateApplications(identifier string, scope Scope, opts options) ([]*Application, error) { 206 | if d.device == nil { 207 | return nil, errors.New("could not enumerate applications for nil device") 208 | } 209 | 210 | queryOpts := C.frida_application_query_options_new() 211 | C.frida_application_query_options_set_scope(queryOpts, C.FridaScope(scope)) 212 | 213 | if identifier != "" { 214 | identifierC := C.CString(identifier) 215 | defer C.free(unsafe.Pointer(identifierC)) 216 | C.frida_application_query_options_select_identifier(queryOpts, identifierC) 217 | } 218 | 219 | var err *C.GError 220 | appList := C.frida_device_enumerate_applications_sync(d.device, queryOpts, opts.cancellable, &err) 221 | if err != nil { 222 | return nil, handleGError(err) 223 | } 224 | 225 | appListSize := int(C.frida_application_list_size(appList)) 226 | apps := make([]*Application, appListSize) 227 | 228 | for i := 0; i < appListSize; i++ { 229 | app := C.frida_application_list_get(appList, C.gint(i)) 230 | apps[i] = &Application{app} 231 | } 232 | 233 | sort.Slice(apps, func(i, j int) bool { 234 | return apps[i].PID() > apps[j].PID() 235 | }) 236 | 237 | clean(unsafe.Pointer(queryOpts), unrefFrida) 238 | clean(unsafe.Pointer(appList), unrefFrida) 239 | 240 | return apps, nil 241 | } 242 | 243 | // ProcessByPID returns the process by passed pid. 244 | func (d *Device) ProcessByPID(pid int, scope Scope) (*Process, error) { 245 | if d.device == nil { 246 | return nil, errors.New("could not obtain process for nil device") 247 | } 248 | 249 | opts := C.frida_process_match_options_new() 250 | C.frida_process_match_options_set_timeout(opts, C.gint(defaultProcessTimeout)) 251 | C.frida_process_match_options_set_scope(opts, C.FridaScope(scope)) 252 | defer clean(unsafe.Pointer(opts), unrefFrida) 253 | 254 | var err *C.GError 255 | proc := C.frida_device_get_process_by_pid_sync(d.device, C.guint(pid), opts, nil, &err) 256 | return &Process{proc}, handleGError(err) 257 | } 258 | 259 | // ProcessByName returns the process by passed name. 260 | func (d *Device) ProcessByName(name string, scope Scope) (*Process, error) { 261 | if d.device == nil { 262 | return nil, errors.New("could not obtain process for nil device") 263 | } 264 | nameC := C.CString(name) 265 | defer C.free(unsafe.Pointer(nameC)) 266 | 267 | opts := C.frida_process_match_options_new() 268 | C.frida_process_match_options_set_timeout(opts, C.gint(defaultProcessTimeout)) 269 | C.frida_process_match_options_set_scope(opts, C.FridaScope(scope)) 270 | defer clean(unsafe.Pointer(opts), unrefFrida) 271 | 272 | var err *C.GError 273 | proc := C.frida_device_get_process_by_name_sync(d.device, nameC, opts, nil, &err) 274 | return &Process{proc}, handleGError(err) 275 | } 276 | 277 | // FindProcessByPID will try to find the process with given pid. 278 | func (d *Device) FindProcessByPID(pid int, scope Scope) (*Process, error) { 279 | if d.device == nil { 280 | return nil, errors.New("could not find process for nil device") 281 | } 282 | 283 | opts := C.frida_process_match_options_new() 284 | C.frida_process_match_options_set_timeout(opts, C.gint(defaultProcessTimeout)) 285 | C.frida_process_match_options_set_scope(opts, C.FridaScope(scope)) 286 | defer clean(unsafe.Pointer(opts), unrefFrida) 287 | 288 | var err *C.GError 289 | proc := C.frida_device_find_process_by_pid_sync(d.device, C.guint(pid), opts, nil, &err) 290 | return &Process{proc}, handleGError(err) 291 | } 292 | 293 | // FindProcessByName will try to find the process with name specified. 294 | func (d *Device) FindProcessByName(name string, scope Scope) (*Process, error) { 295 | if d.device == nil { 296 | return nil, errors.New("could not find process for nil device") 297 | } 298 | 299 | nameC := C.CString(name) 300 | defer C.free(unsafe.Pointer(nameC)) 301 | 302 | opts := C.frida_process_match_options_new() 303 | C.frida_process_match_options_set_timeout(opts, C.gint(defaultProcessTimeout)) 304 | C.frida_process_match_options_set_scope(opts, C.FridaScope(scope)) 305 | defer clean(unsafe.Pointer(opts), unrefFrida) 306 | 307 | var err *C.GError 308 | proc := C.frida_device_find_process_by_name_sync(d.device, nameC, opts, nil, &err) 309 | return &Process{proc}, handleGError(err) 310 | } 311 | 312 | // EnumerateProcesses will slice of processes running with scope provided 313 | func (d *Device) EnumerateProcesses(scope Scope) ([]*Process, error) { 314 | if d.device != nil { 315 | opts := C.frida_process_query_options_new() 316 | C.frida_process_query_options_set_scope(opts, C.FridaScope(scope)) 317 | defer clean(unsafe.Pointer(opts), unrefFrida) 318 | 319 | var err *C.GError 320 | procList := C.frida_device_enumerate_processes_sync(d.device, opts, nil, &err) 321 | if err != nil { 322 | return nil, handleGError(err) 323 | } 324 | 325 | procListSize := int(C.frida_process_list_size(procList)) 326 | procs := make([]*Process, procListSize) 327 | 328 | for i := 0; i < procListSize; i++ { 329 | proc := C.frida_process_list_get(procList, C.gint(i)) 330 | procs[i] = &Process{proc} 331 | } 332 | 333 | clean(unsafe.Pointer(procList), unrefFrida) 334 | return procs, nil 335 | } 336 | return nil, errors.New("could not enumerate processes for nil device") 337 | } 338 | 339 | // EnableSpawnGating will enable spawn gating on the device. 340 | func (d *Device) EnableSpawnGating() error { 341 | if d.device == nil { 342 | return errors.New("could not enable spawn gating for nil device") 343 | } 344 | 345 | var err *C.GError 346 | C.frida_device_enable_spawn_gating_sync(d.device, nil, &err) 347 | return handleGError(err) 348 | } 349 | 350 | // DisableSpawnGating will disable spawn gating on the device. 351 | func (d *Device) DisableSpawnGating() error { 352 | if d.device == nil { 353 | return errors.New("could not disable spawn gating for nil device") 354 | } 355 | 356 | var err *C.GError 357 | C.frida_device_disable_spawn_gating_sync(d.device, nil, &err) 358 | return handleGError(err) 359 | } 360 | 361 | // EnumeratePendingSpawn will return the slice of pending spawns. 362 | func (d *Device) EnumeratePendingSpawn() ([]*Spawn, error) { 363 | if d.device != nil { 364 | var err *C.GError 365 | spawnList := C.frida_device_enumerate_pending_spawn_sync(d.device, nil, &err) 366 | if err != nil { 367 | return nil, handleGError(err) 368 | } 369 | 370 | spawnListSize := int(C.frida_spawn_list_size(spawnList)) 371 | spawns := make([]*Spawn, spawnListSize) 372 | 373 | for i := 0; i < spawnListSize; i++ { 374 | spawn := C.frida_spawn_list_get(spawnList, C.gint(i)) 375 | spawns[i] = &Spawn{spawn} 376 | } 377 | 378 | clean(unsafe.Pointer(spawnList), unrefFrida) 379 | return spawns, nil 380 | } 381 | return nil, errors.New("could not enumerate pending spawn for nil device") 382 | } 383 | 384 | // EnumeratePendingChildren will return the slice of pending children. 385 | func (d *Device) EnumeratePendingChildren() ([]*Child, error) { 386 | if d.device != nil { 387 | var err *C.GError 388 | childList := C.frida_device_enumerate_pending_children_sync(d.device, nil, &err) 389 | if err != nil { 390 | return nil, handleGError(err) 391 | } 392 | 393 | childListSize := int(C.frida_child_list_size(childList)) 394 | children := make([]*Child, childListSize) 395 | 396 | for i := 0; i < childListSize; i++ { 397 | child := C.frida_child_list_get(childList, C.gint(i)) 398 | children[i] = &Child{child} 399 | } 400 | 401 | clean(unsafe.Pointer(childList), unrefFrida) 402 | return children, nil 403 | } 404 | return nil, errors.New("could not enumerate pending children for nil device") 405 | } 406 | 407 | // Spawn will spawn an application or binary. 408 | func (d *Device) Spawn(name string, opts *SpawnOptions) (int, error) { 409 | if d.device == nil { 410 | return -1, errors.New("could not spawn for nil device") 411 | } 412 | 413 | var opt *C.FridaSpawnOptions = nil 414 | if opts != nil { 415 | opt = opts.opts 416 | } 417 | defer clean(unsafe.Pointer(opt), unrefFrida) 418 | 419 | nameC := C.CString(name) 420 | defer C.free(unsafe.Pointer(nameC)) 421 | 422 | var err *C.GError 423 | pid := C.frida_device_spawn_sync(d.device, nameC, opt, nil, &err) 424 | 425 | return int(pid), handleGError(err) 426 | } 427 | 428 | // Input inputs []bytes into the process with pid specified. 429 | func (d *Device) Input(pid int, data []byte) error { 430 | if d.device == nil { 431 | return errors.New("could not input bytes into nil device") 432 | 433 | } 434 | gBytesData := goBytesToGBytes(data) 435 | runtime.SetFinalizer(gBytesData, func(g *C.GBytes) { 436 | clean(unsafe.Pointer(g), unrefGObject) 437 | }) 438 | 439 | var err *C.GError 440 | C.frida_device_input_sync(d.device, C.guint(pid), gBytesData, nil, &err) 441 | runtime.KeepAlive(gBytesData) 442 | return handleGError(err) 443 | } 444 | 445 | // Resume will resume the process with pid. 446 | func (d *Device) Resume(pid int) error { 447 | if d.device == nil { 448 | return errors.New("could not resume for nil device") 449 | } 450 | var err *C.GError 451 | C.frida_device_resume_sync(d.device, C.guint(pid), nil, &err) 452 | return handleGError(err) 453 | } 454 | 455 | // Kill kills process with pid specified. 456 | func (d *Device) Kill(pid int) error { 457 | if d.device == nil { 458 | return errors.New("could not kill for nil device") 459 | } 460 | var err *C.GError 461 | C.frida_device_kill_sync(d.device, C.guint(pid), nil, &err) 462 | return handleGError(err) 463 | } 464 | 465 | // AttachWithContext runs Attach but with context. 466 | // This function will properly handle cancelling the frida operation. 467 | // It is advised to use this rather than handling Cancellable yourself. 468 | func (d *Device) AttachWithContext(ctx context.Context, val any, sessionOpts *SessionOptions) (*Session, error) { 469 | rawSession, err := handleWithContext(ctx, func(c *Cancellable, doneC chan any, errC chan error) { 470 | session, err := d.Attach(val, sessionOpts, WithCancel(c)) 471 | if err != nil { 472 | errC <- err 473 | return 474 | } 475 | doneC <- session 476 | }) 477 | session, _ := rawSession.(*Session) 478 | return session, err 479 | } 480 | 481 | // Attach will attach on specified process name or PID. 482 | // You can pass the nil as SessionOptions or you can create it if you want 483 | // the session to persist for specific timeout. 484 | func (d *Device) Attach(val any, sessionOpts *SessionOptions, opts ...OptFunc) (*Session, error) { 485 | o := setupOptions(opts) 486 | return d.attach(val, sessionOpts, o) 487 | } 488 | 489 | func (d *Device) attach(val any, sessionOpts *SessionOptions, opts options) (*Session, error) { 490 | if d.device == nil { 491 | return nil, errors.New("could not attach for nil device") 492 | } 493 | var pid int 494 | switch v := reflect.ValueOf(val); v.Kind() { 495 | case reflect.String: 496 | proc, err := d.ProcessByName(val.(string), ScopeMinimal) 497 | if err != nil { 498 | return nil, err 499 | } 500 | pid = proc.PID() 501 | case reflect.Int: 502 | pid = val.(int) 503 | default: 504 | return nil, errors.New("expected name of app/process or PID") 505 | } 506 | 507 | var opt *C.FridaSessionOptions = nil 508 | if sessionOpts != nil { 509 | opt = sessionOpts.opts 510 | defer clean(unsafe.Pointer(opt), unrefFrida) 511 | } 512 | 513 | var err *C.GError 514 | s := C.frida_device_attach_sync(d.device, C.guint(pid), opt, opts.cancellable, &err) 515 | return &Session{s}, handleGError(err) 516 | } 517 | 518 | // InjectLibraryFile will inject the library in the target with path to library specified. 519 | // Entrypoint is the entrypoint to the library and the data is any data you need to pass 520 | // to the library. 521 | func (d *Device) InjectLibraryFile(target any, path, entrypoint, data string) (uint, error) { 522 | if d.device == nil { 523 | return 0, errors.New("could not inject library for nil device") 524 | } 525 | var pid int 526 | switch v := reflect.ValueOf(target); v.Kind() { 527 | case reflect.String: 528 | proc, err := d.ProcessByName(target.(string), ScopeMinimal) 529 | if err != nil { 530 | return 0, err 531 | } 532 | pid = proc.PID() 533 | case reflect.Int: 534 | pid = target.(int) 535 | default: 536 | return 0, errors.New("expected name of app/process or PID") 537 | } 538 | 539 | if path == "" { 540 | return 0, errors.New("you need to provide path to library") 541 | } 542 | 543 | var pathC *C.char 544 | var entrypointC *C.char = nil 545 | var dataC *C.char = nil 546 | 547 | pathC = C.CString(path) 548 | defer C.free(unsafe.Pointer(pathC)) 549 | 550 | if entrypoint != "" { 551 | entrypointC = C.CString(entrypoint) 552 | defer C.free(unsafe.Pointer(entrypointC)) 553 | } 554 | 555 | if data != "" { 556 | dataC = C.CString(data) 557 | defer C.free(unsafe.Pointer(dataC)) 558 | } 559 | 560 | var err *C.GError 561 | id := C.frida_device_inject_library_file_sync(d.device, 562 | C.guint(pid), 563 | pathC, 564 | entrypointC, 565 | dataC, 566 | nil, 567 | &err) 568 | 569 | return uint(id), handleGError(err) 570 | } 571 | 572 | // InjectLibraryBlob will inject the library in the target with byteData path. 573 | // Entrypoint is the entrypoint to the library and the data is any data you need to pass 574 | // to the library. 575 | func (d *Device) InjectLibraryBlob(target any, byteData []byte, entrypoint, data string) (uint, error) { 576 | if d.device == nil { 577 | return 0, errors.New("could not inject library blob for nil device") 578 | } 579 | var pid int 580 | switch v := reflect.ValueOf(target); v.Kind() { 581 | case reflect.String: 582 | proc, err := d.ProcessByName(target.(string), ScopeMinimal) 583 | if err != nil { 584 | return 0, err 585 | } 586 | pid = proc.PID() 587 | case reflect.Int: 588 | pid = target.(int) 589 | default: 590 | return 0, errors.New("expected name of app/process or PID") 591 | } 592 | 593 | if len(byteData) == 0 { 594 | return 0, errors.New("you need to provide byteData") 595 | } 596 | 597 | var entrypointC *C.char = nil 598 | var dataC *C.char = nil 599 | 600 | if entrypoint != "" { 601 | entrypointC = C.CString(entrypoint) 602 | defer C.free(unsafe.Pointer(entrypointC)) 603 | } 604 | 605 | if data != "" { 606 | dataC = C.CString(data) 607 | defer C.free(unsafe.Pointer(dataC)) 608 | } 609 | 610 | gBytesData := goBytesToGBytes(byteData) 611 | runtime.SetFinalizer(gBytesData, func(g *C.GBytes) { 612 | defer clean(unsafe.Pointer(g), unrefGObject) 613 | }) 614 | 615 | var err *C.GError 616 | id := C.frida_device_inject_library_blob_sync(d.device, 617 | C.guint(pid), 618 | gBytesData, 619 | entrypointC, 620 | dataC, 621 | nil, 622 | &err) 623 | runtime.KeepAlive(gBytesData) 624 | 625 | return uint(id), handleGError(err) 626 | } 627 | 628 | // OpenChannel open channel with the address and returns the IOStream 629 | func (d *Device) OpenChannel(address string) (*IOStream, error) { 630 | if d.device == nil { 631 | return nil, errors.New("could not open channel for nil device") 632 | } 633 | addressC := C.CString(address) 634 | defer C.free(unsafe.Pointer(addressC)) 635 | 636 | var err *C.GError 637 | stream := C.frida_device_open_channel_sync(d.device, addressC, nil, &err) 638 | return NewIOStream(stream), handleGError(err) 639 | } 640 | 641 | func (d *Device) OpenService(address string) (*Service, error) { 642 | if d.device == nil { 643 | return nil, errors.New("could not open service") 644 | } 645 | addrC := C.CString(address) 646 | defer C.free(unsafe.Pointer(addrC)) 647 | 648 | var err *C.GError 649 | svc := C.frida_device_open_service_sync(d.device, addrC, nil, &err) 650 | return &Service{svc}, handleGError(err) 651 | } 652 | 653 | // Clean will clean the resources held by the device. 654 | func (d *Device) Clean() { 655 | if d.device != nil { 656 | clean(unsafe.Pointer(d.device), unrefFrida) 657 | } 658 | } 659 | 660 | // On connects device to specific signals. Once sigName is triggered, 661 | // fn callback will be called with parameters populated. 662 | // 663 | // Signals available are: 664 | // - "spawn_added" with callback as func(spawn *frida.Spawn) {} 665 | // - "spawn_removed" with callback as func(spawn *frida.Spawn) {} 666 | // - "child_added" with callback as func(child *frida.Child) {} 667 | // - "child_removed" with callback as func(child *frida.Child) {} 668 | // - "process_crashed" with callback as func(crash *frida.Crash) {} 669 | // - "output" with callback as func(pid, fd int, data []byte) {} 670 | // - "uninjected" with callback as func(id int) {} 671 | // - "lost" with callback as func() {} 672 | func (d *Device) On(sigName string, fn any) { 673 | if d.device != nil { 674 | connectClosure(unsafe.Pointer(d.device), sigName, fn) 675 | } 676 | } 677 | -------------------------------------------------------------------------------- /frida/endpoint_parameters.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include "authentication-service.h" 4 | import "C" 5 | import ( 6 | "errors" 7 | "unsafe" 8 | ) 9 | 10 | // AuthenticationFn is a callback function passed to the endpoint params. 11 | // Function does authentication and in the case the user is authenticated, 12 | // non-empty string is returned. 13 | // If the user is not authenticated, empty string should be returned. 14 | type AuthenticationFn func(string) string 15 | 16 | // EParams represent config needed to setup endpoint parameters that are used to setup Portal. 17 | // Types of authentication includes: 18 | // - no authentication (not providing Token nor AuthenticationCallback) 19 | // - static authentication (providing token) 20 | // - authentication using callback (providing AuthenticationCallback) 21 | // 22 | // If the Token and AuthenticationCallback are passed, static authentication will be used (token based) 23 | type EParams struct { 24 | Address string 25 | Port uint16 26 | Certificate string 27 | Origin string 28 | Token string 29 | AuthenticationCallback AuthenticationFn 30 | AssetRoot string 31 | } 32 | 33 | // EndpointParameters represent internal FridaEndpointParameters 34 | type EndpointParameters struct { 35 | params *C.FridaEndpointParameters 36 | } 37 | 38 | //export authenticate 39 | func authenticate(cb unsafe.Pointer, token *C.char) *C.char { 40 | fn := (*func(string) string)(cb) 41 | ret := (*fn)(C.GoString(token)) 42 | if ret == "" { 43 | return nil 44 | } 45 | return C.CString(ret) 46 | } 47 | 48 | // NewEndpointParameters returns *EndpointParameters needed to setup Portal by using 49 | // provided EParams object. 50 | func NewEndpointParameters(params *EParams) (*EndpointParameters, error) { 51 | if params.Address == "" { 52 | return nil, errors.New("you need to provide address") 53 | } 54 | 55 | addrC := C.CString(params.Address) 56 | defer C.free(unsafe.Pointer(addrC)) 57 | 58 | var tknC *C.char = nil 59 | var originC *C.char = nil 60 | var authService *C.FridaAuthenticationService = nil 61 | var assetPath *C.GFile = nil 62 | 63 | if params.Token != "" { 64 | tknC = C.CString(params.Token) 65 | defer C.free(unsafe.Pointer(tknC)) 66 | 67 | authService = (*C.FridaAuthenticationService)(C.frida_static_authentication_service_new(tknC)) 68 | } else if params.AuthenticationCallback != nil { 69 | authService = (*C.FridaAuthenticationService)(C.frida_go_authentication_service_new(unsafe.Pointer(¶ms.AuthenticationCallback))) 70 | } 71 | 72 | if params.Origin != "" { 73 | originC = C.CString(params.Origin) 74 | defer C.free(unsafe.Pointer(originC)) 75 | } 76 | 77 | var cert *C.GTlsCertificate = nil 78 | if params.Certificate != "" { 79 | crt, err := gTLSCertificateFromFile(params.Certificate) 80 | if err != nil { 81 | return nil, err 82 | } 83 | cert = crt.cert 84 | } 85 | 86 | if params.AssetRoot != "" { 87 | assetPath = gFileFromPath(params.AssetRoot) 88 | } 89 | 90 | ret := C.frida_endpoint_parameters_new( 91 | addrC, 92 | C.guint16(params.Port), 93 | cert, 94 | originC, 95 | authService, 96 | assetPath, 97 | ) 98 | 99 | return &EndpointParameters{ret}, nil 100 | } 101 | 102 | // Address returns the address of the endpoint parameters. 103 | func (e *EndpointParameters) Address() string { 104 | return C.GoString(C.frida_endpoint_parameters_get_address(e.params)) 105 | } 106 | 107 | // Port returns the port of the endpoint parameters. 108 | func (e *EndpointParameters) Port() uint16 { 109 | return uint16(C.frida_endpoint_parameters_get_port(e.params)) 110 | } 111 | 112 | // Certificate returns the certificate of the endpoint parameters. 113 | func (e *EndpointParameters) Certificate() *Certificate { 114 | cert := C.frida_endpoint_parameters_get_certificate(e.params) 115 | return &Certificate{cert} 116 | } 117 | 118 | // Origin returns the origin of the endpoint parameters. 119 | func (e *EndpointParameters) Origin() string { 120 | return C.GoString(C.frida_endpoint_parameters_get_origin(e.params)) 121 | } 122 | 123 | // AssetRoot returns the asset root directory. 124 | func (e *EndpointParameters) AssetRoot() string { 125 | assetRoot := C.frida_endpoint_parameters_get_asset_root(e.params) 126 | pathC := C.g_file_get_path(assetRoot) 127 | return C.GoString(pathC) 128 | } 129 | 130 | // SetAssetRoot sets asset root directory for the portal. 131 | func (e *EndpointParameters) SetAssetRoot(assetPath string) { 132 | assetRoot := gFileFromPath(assetPath) 133 | C.frida_endpoint_parameters_set_asset_root(e.params, assetRoot) 134 | } 135 | 136 | // Clean will clean the resources held by the endpoint parameters. 137 | func (e *EndpointParameters) Clean() { 138 | clean(unsafe.Pointer(e.params), unrefFrida) 139 | } 140 | -------------------------------------------------------------------------------- /frida/errors.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrContextCancelled = errors.New("context cancelled") 7 | ) 8 | -------------------------------------------------------------------------------- /frida/example_test.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func Example() { 8 | manager := NewDeviceManager() 9 | devices, err := manager.EnumerateDevices() 10 | if err != nil { 11 | panic(err) 12 | } 13 | 14 | fmt.Printf("[*] Frida version: %s\n", Version()) 15 | fmt.Println("[*] Devices: ") 16 | for _, device := range devices { 17 | fmt.Printf("[*] %s => %s\n", device.Name(), device.ID()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frida/file_monitor.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | 6 | import "unsafe" 7 | 8 | // FileMonitor type is the type responsible for monitoring file changes 9 | type FileMonitor struct { 10 | fm *C.FridaFileMonitor 11 | } 12 | 13 | // NewFileMonitor creates new FileMonito with the file path provided. 14 | func NewFileMonitor(path string) *FileMonitor { 15 | pathC := C.CString(path) 16 | defer C.free(unsafe.Pointer(pathC)) 17 | 18 | m := C.frida_file_monitor_new(pathC) 19 | 20 | return &FileMonitor{ 21 | fm: m, 22 | } 23 | } 24 | 25 | // Path returns the path of the monitored file. 26 | func (mon *FileMonitor) Path() string { 27 | return C.GoString(C.frida_file_monitor_get_path(mon.fm)) 28 | } 29 | 30 | // Enable enables file monitoring. 31 | func (mon *FileMonitor) Enable() error { 32 | var err *C.GError 33 | C.frida_file_monitor_enable_sync(mon.fm, nil, &err) 34 | return handleGError(err) 35 | } 36 | 37 | // Disable disables file monitoring. 38 | func (mon *FileMonitor) Disable() error { 39 | var err *C.GError 40 | C.frida_file_monitor_disable_sync(mon.fm, nil, &err) 41 | return handleGError(err) 42 | } 43 | 44 | // Clean will clean the resources held by the file monitor. 45 | func (mon *FileMonitor) Clean() { 46 | clean(unsafe.Pointer(mon.fm), unrefFrida) 47 | } 48 | 49 | // On connects file monitor to specific signals. Once sigName is triggered, 50 | // fn callback will be called with parameters populated. 51 | // 52 | // Signals available are: 53 | // - "change" with callback as func(changedFile, otherFile, changeType string) {} 54 | func (mon *FileMonitor) On(sigName string, fn any) { 55 | connectClosure(unsafe.Pointer(mon.fm), sigName, fn) 56 | } 57 | -------------------------------------------------------------------------------- /frida/frida.go: -------------------------------------------------------------------------------- 1 | // Package frida provides Go bindings for frida. 2 | // Some of the provided functionality includes: 3 | // 4 | // * Listing devices/applications/processes 5 | // * Attaching to applications/processes 6 | // * Fetching information about devices/applications/processes 7 | package frida 8 | 9 | /* 10 | #cgo LDFLAGS: -lfrida-core -lm -ldl 11 | #cgo CFLAGS: -I/usr/local/include/ -w 12 | #cgo darwin LDFLAGS: -lbsm -framework IOKit -framework Foundation -framework AppKit -framework Security -lpthread 13 | #cgo darwin CFLAGS: -Wno-error=incompatible-function-pointer-types 14 | #cgo android LDFLAGS: -llog 15 | #cgo android CFLAGS: -DANDROID -Wno-error=incompatible-function-pointer-types 16 | #cgo linux,!android LDFLAGS: -lrt -lresolv -lpthread 17 | #cgo linux CFLAGS: -pthread 18 | #include 19 | #include "android-selinux.h" 20 | */ 21 | import "C" 22 | import ( 23 | "sync" 24 | ) 25 | 26 | var data = &sync.Map{} 27 | 28 | // PatchAndroidSELinux tries to patch selinux; root access is required. 29 | func PatchAndroidSELinux() { 30 | C.android_patch_selinux() 31 | } 32 | 33 | // Version returns currently used frida version 34 | func Version() string { 35 | return C.GoString(C.frida_version_string()) 36 | } 37 | 38 | func getDeviceManager() *DeviceManager { 39 | v, ok := data.Load("mgr") 40 | if !ok { 41 | mgr := NewDeviceManager() 42 | data.Store("mgr", mgr) 43 | return mgr 44 | } 45 | return v.(*DeviceManager) 46 | } 47 | 48 | // LocalDevice is a wrapper around DeviceByType(DeviceTypeLocal). 49 | func LocalDevice() *Device { 50 | mgr := getDeviceManager() 51 | v, ok := data.Load("localDevice") 52 | if !ok { 53 | dev, _ := mgr.DeviceByType(DeviceTypeLocal) 54 | data.Store("localDevice", dev) 55 | return dev.(*Device) 56 | } 57 | return v.(*Device) 58 | } 59 | 60 | // USBDevice is a wrapper around DeviceByType(DeviceTypeUsb). 61 | func USBDevice() *Device { 62 | mgr := getDeviceManager() 63 | v, ok := data.Load("usbDevice") 64 | if !ok { 65 | _, ok := data.Load("enumeratedDevices") 66 | if !ok { 67 | mgr.EnumerateDevices() 68 | data.Store("enumeratedDevices", true) 69 | } 70 | dev, err := mgr.DeviceByType(DeviceTypeUsb) 71 | if err != nil { 72 | return nil 73 | } 74 | data.Store("usbDevice", dev) 75 | return dev.(*Device) 76 | } 77 | return v.(*Device) 78 | } 79 | 80 | // DeviceByID tries to get the device by id on the default manager 81 | func DeviceByID(id string) (*Device, error) { 82 | mgr := getDeviceManager() 83 | v, ok := data.Load(id) 84 | if !ok { 85 | _, ok := data.Load("enumeratedDevices") 86 | if !ok { 87 | mgr.EnumerateDevices() 88 | data.Store("enumeratedDevices", true) 89 | } 90 | dev, err := mgr.DeviceByID(id) 91 | if err != nil { 92 | return nil, err 93 | } 94 | data.Store(id, dev) 95 | return v.(*Device), nil 96 | } 97 | return v.(*Device), nil 98 | } 99 | 100 | // Attach attaches at val(string or int pid) using local device. 101 | func Attach(val any) (*Session, error) { 102 | dev := LocalDevice() 103 | return dev.Attach(val, nil) 104 | } 105 | -------------------------------------------------------------------------------- /frida/go_to_variants.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | /* 4 | #include 5 | 6 | static void add_key_val_to_builder(GVariantBuilder *builder, char *key, GVariant *variant) 7 | { 8 | g_variant_builder_add(builder, "{sv}", key, variant); 9 | } 10 | 11 | static void add_val_to_builder(GVariantBuilder *builder, GVariant *variant) 12 | { 13 | g_variant_builder_add(builder, "v", variant); 14 | } 15 | */ 16 | import "C" 17 | import ( 18 | "fmt" 19 | "reflect" 20 | ) 21 | 22 | // goToGVariant converts go types to GVariant representation 23 | func goToGVariant(data any) *C.GVariant { 24 | return parse(reflect.ValueOf(data)) 25 | } 26 | 27 | func parse(v reflect.Value) *C.GVariant { 28 | for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { 29 | v = v.Elem() 30 | } 31 | switch v.Kind() { 32 | case reflect.Array, reflect.Slice: 33 | var builder C.GVariantBuilder 34 | C.g_variant_builder_init(&builder, C.G_VARIANT_TYPE_ARRAY) 35 | for i := 0; i < v.Len(); i++ { 36 | val := parse(v.Index(i)) 37 | C.add_val_to_builder(&builder, val) 38 | } 39 | variant := C.g_variant_builder_end(&builder) 40 | return variant 41 | case reflect.Map: 42 | var builder C.GVariantBuilder 43 | C.g_variant_builder_init(&builder, C.G_VARIANT_TYPE_VARDICT) 44 | for _, k := range v.MapKeys() { 45 | val := parse(v.MapIndex(k)) 46 | C.add_key_val_to_builder(&builder, C.CString(k.String()), val) 47 | } 48 | variant := C.g_variant_builder_end(&builder) 49 | return variant 50 | case reflect.Bool: 51 | if v.Interface().(bool) { 52 | return C.g_variant_new_boolean(C.gboolean(1)) 53 | } else { 54 | return C.g_variant_new_boolean(C.gboolean(0)) 55 | } 56 | case reflect.String: 57 | return C.g_variant_new_string(C.CString(v.String())) 58 | case reflect.Int16: 59 | return C.g_variant_new_int16(C.gint16(v.Interface().(int16))) 60 | case reflect.Uint16: 61 | return C.g_variant_new_uint16(C.guint16(v.Interface().(uint16))) 62 | case reflect.Int: 63 | return C.g_variant_new_int32(C.gint32(v.Interface().(int))) 64 | case reflect.Int32: 65 | return C.g_variant_new_int32(C.gint32(v.Interface().(int32))) 66 | case reflect.Uint32: 67 | return C.g_variant_new_uint32(C.guint32(v.Interface().(uint32))) 68 | case reflect.Int64: 69 | return C.g_variant_new_int64(C.gint64(v.Interface().(int64))) 70 | case reflect.Uint64: 71 | return C.g_variant_new_int64(C.gint64(v.Interface().(int64))) 72 | default: 73 | msg := fmt.Sprintf("type \"%s\" not implemented, please file an issue on github", v.Kind().String()) 74 | panic(msg) 75 | } 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /frida/iostream.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "io" 7 | "unsafe" 8 | ) 9 | 10 | // IOStream type represents struct used to interact with the device using channels. 11 | type IOStream struct { 12 | stream *C.GIOStream 13 | input *C.GInputStream 14 | output *C.GOutputStream 15 | } 16 | 17 | // NewIOStream creates new IOStream. 18 | func NewIOStream(stream *C.GIOStream) *IOStream { 19 | input := C.g_io_stream_get_input_stream(stream) 20 | output := C.g_io_stream_get_output_stream(stream) 21 | return &IOStream{ 22 | stream: stream, 23 | input: input, 24 | output: output, 25 | } 26 | } 27 | 28 | // IsClosed returns whether the stream is closed or not. 29 | func (ios *IOStream) IsClosed() bool { 30 | closed := C.g_io_stream_is_closed(ios.stream) 31 | return int(closed) == 1 32 | } 33 | 34 | // Close closes the stream. 35 | func (ios *IOStream) Close() error { 36 | var err *C.GError 37 | C.g_io_stream_close(ios.stream, nil, &err) 38 | return handleGError(err) 39 | } 40 | 41 | // Read tries to read len(data) bytes into the data from the stream. 42 | func (ios *IOStream) Read(data *[]byte) (int, error) { 43 | if len(*data) == 0 { 44 | return 0, nil 45 | } 46 | 47 | buf := C.CBytes(*data) 48 | defer C.free(unsafe.Pointer(buf)) 49 | 50 | count := C.gsize(len(*data)) 51 | var err *C.GError 52 | read := C.g_input_stream_read(ios.input, 53 | unsafe.Pointer(buf), 54 | count, 55 | nil, 56 | &err) 57 | if err != nil { 58 | return -1, handleGError(err) 59 | } 60 | 61 | if int(read) == 0 { 62 | return 0, io.EOF 63 | } 64 | 65 | dataRead := C.GoBytes(unsafe.Pointer(buf), C.int(read)) 66 | copy(*data, dataRead) 67 | 68 | return int(read), nil 69 | } 70 | 71 | // ReadAll tries to read all the bytes provided with the count from the stream. 72 | func (ios *IOStream) ReadAll(count int) ([]byte, error) { 73 | bt := make([]byte, count) 74 | buf := C.CBytes(bt) 75 | defer C.free(unsafe.Pointer(buf)) 76 | 77 | countC := C.gsize(count) 78 | var bytesRead C.gsize 79 | var err *C.GError 80 | C.g_input_stream_read_all( 81 | ios.input, 82 | buf, 83 | countC, 84 | &bytesRead, 85 | nil, 86 | &err) 87 | return C.GoBytes(unsafe.Pointer(buf), C.int(bytesRead)), handleGError(err) 88 | } 89 | 90 | // Write tries to write len(data) bytes to the stream. 91 | func (ios *IOStream) Write(data []byte) (int, error) { 92 | if len(data) == 0 { 93 | return 0, nil 94 | } 95 | buf := C.CBytes(data) 96 | count := C.gsize(len(data)) 97 | var err *C.GError 98 | written := C.g_output_stream_write(ios.output, 99 | buf, 100 | count, 101 | nil, 102 | &err) 103 | return int(written), handleGError(err) 104 | } 105 | 106 | // WriteAll tries to write all the data provided. 107 | func (ios *IOStream) WriteAll(data []byte) error { 108 | if len(data) == 0 { 109 | return nil 110 | } 111 | buf := C.CBytes(data) 112 | count := C.gsize(len(data)) 113 | var err *C.GError 114 | C.g_output_stream_write(ios.output, 115 | buf, 116 | count, 117 | nil, 118 | &err) 119 | return handleGError(err) 120 | } 121 | 122 | // Clean will clean resources held by the iostream. 123 | func (ios *IOStream) Clean() { 124 | clean(unsafe.Pointer(ios.stream), unrefGObject) 125 | clean(unsafe.Pointer(ios.input), unrefGObject) 126 | clean(unsafe.Pointer(ios.output), unrefGObject) 127 | } 128 | -------------------------------------------------------------------------------- /frida/manager.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | 6 | import "unsafe" 7 | 8 | // DeviceManagerInt is the device DeviceManagerInt interface 9 | type DeviceManagerInt interface { 10 | Close() error 11 | EnumerateDevices() ([]DeviceInt, error) 12 | LocalDevice() (DeviceInt, error) 13 | USBDevice() (DeviceInt, error) 14 | RemoteDevice() (DeviceInt, error) 15 | DeviceByID(id string) (DeviceInt, error) 16 | DeviceByType(devType DeviceType) (DeviceInt, error) 17 | FindDeviceByID(id string) (DeviceInt, error) 18 | FindDeviceByType(devType DeviceType) (DeviceInt, error) 19 | AddRemoteDevice(address string, remoteOpts *RemoteDeviceOptions) (DeviceInt, error) 20 | RemoveRemoteDevice(address string) error 21 | Clean() 22 | On(sigName string, fn any) 23 | 24 | getManager() *C.FridaDeviceManager 25 | } 26 | 27 | // DeviceManager is the main structure which holds on devices available to Frida 28 | // Single instance of the DeviceManager is created when you call frida.Attach() or frida.LocalDevice(). 29 | type DeviceManager struct { 30 | manager *C.FridaDeviceManager 31 | } 32 | 33 | // NewDeviceManager returns new frida device manager. 34 | func NewDeviceManager() *DeviceManager { 35 | manager := C.frida_device_manager_new() 36 | return &DeviceManager{manager} 37 | } 38 | 39 | // Close method will close current manager. 40 | func (d *DeviceManager) Close() error { 41 | var err *C.GError 42 | C.frida_device_manager_close_sync(d.manager, nil, &err) 43 | return handleGError(err) 44 | } 45 | 46 | // EnumerateDevices will return all connected devices. 47 | func (d *DeviceManager) EnumerateDevices() ([]DeviceInt, error) { 48 | var err *C.GError 49 | deviceList := C.frida_device_manager_enumerate_devices_sync(d.manager, nil, &err) 50 | if err != nil { 51 | return nil, handleGError(err) 52 | } 53 | 54 | numDevices := int(C.frida_device_list_size(deviceList)) 55 | devices := make([]DeviceInt, numDevices) 56 | 57 | for i := 0; i < numDevices; i++ { 58 | device := C.frida_device_list_get(deviceList, C.gint(i)) 59 | devices[i] = &Device{device} 60 | } 61 | 62 | clean(unsafe.Pointer(deviceList), unrefFrida) 63 | return devices, nil 64 | } 65 | 66 | // LocalDevice returns the device with type DeviceTypeLocal. 67 | func (d *DeviceManager) LocalDevice() (DeviceInt, error) { 68 | return d.DeviceByType(DeviceTypeLocal) 69 | } 70 | 71 | // USBDevice returns the device with type DeviceTypeUsb. 72 | func (d *DeviceManager) USBDevice() (DeviceInt, error) { 73 | return d.DeviceByType(DeviceTypeUsb) 74 | } 75 | 76 | // RemoteDevice returns the device with type DeviceTypeRemote. 77 | func (d *DeviceManager) RemoteDevice() (DeviceInt, error) { 78 | return d.DeviceByType(DeviceTypeRemote) 79 | } 80 | 81 | // DeviceByID will return device with id passed or an error if it can't find any. 82 | // Note: the caller must call EnumerateDevices() to get devices that are of type usb 83 | func (d *DeviceManager) DeviceByID(id string) (DeviceInt, error) { 84 | idC := C.CString(id) 85 | defer C.free(unsafe.Pointer(idC)) 86 | 87 | timeout := C.gint(defaultDeviceTimeout) 88 | 89 | var err *C.GError 90 | device := C.frida_device_manager_get_device_by_id_sync(d.manager, idC, timeout, nil, &err) 91 | return &Device{device: device}, handleGError(err) 92 | } 93 | 94 | // DeviceByType will return device or an error by device type specified. 95 | // Note: the caller must call EnumerateDevices() to get devices that are of type usb 96 | func (d *DeviceManager) DeviceByType(devType DeviceType) (DeviceInt, error) { 97 | var err *C.GError 98 | device := C.frida_device_manager_get_device_by_type_sync(d.manager, 99 | C.FridaDeviceType(devType), 100 | 1, 101 | nil, 102 | &err) 103 | return &Device{device: device}, handleGError(err) 104 | } 105 | 106 | // FindDeviceByID will try to find the device by id specified 107 | // Note: the caller must call EnumerateDevices() to get devices that are of type usb 108 | func (d *DeviceManager) FindDeviceByID(id string) (DeviceInt, error) { 109 | devID := C.CString(id) 110 | defer C.free(unsafe.Pointer(devID)) 111 | 112 | timeout := C.gint(defaultDeviceTimeout) 113 | 114 | var err *C.GError 115 | device := C.frida_device_manager_find_device_by_id_sync(d.manager, 116 | devID, 117 | timeout, 118 | nil, 119 | &err) 120 | 121 | return &Device{device: device}, handleGError(err) 122 | } 123 | 124 | // FindDeviceByType will try to find the device by device type specified 125 | // Note: the caller must call EnumerateDevices() to get devices that are of type usb 126 | func (d *DeviceManager) FindDeviceByType(devType DeviceType) (DeviceInt, error) { 127 | timeout := C.gint(defaultDeviceTimeout) 128 | 129 | var err *C.GError 130 | device := C.frida_device_manager_find_device_by_type_sync(d.manager, 131 | C.FridaDeviceType(devType), 132 | C.gint(timeout), 133 | nil, 134 | &err) 135 | 136 | return &Device{device: device}, handleGError(err) 137 | } 138 | 139 | // AddRemoteDevice add a remote device from the provided address with remoteOpts populated 140 | func (d *DeviceManager) AddRemoteDevice(address string, remoteOpts *RemoteDeviceOptions) (DeviceInt, error) { 141 | addressC := C.CString(address) 142 | defer C.free(unsafe.Pointer(addressC)) 143 | 144 | var err *C.GError 145 | device := C.frida_device_manager_add_remote_device_sync(d.manager, addressC, remoteOpts.opts, nil, &err) 146 | 147 | return &Device{device: device}, handleGError(err) 148 | } 149 | 150 | // RemoveRemoteDevice removes remote device available at address 151 | func (d *DeviceManager) RemoveRemoteDevice(address string) error { 152 | addressC := C.CString(address) 153 | defer C.free(unsafe.Pointer(addressC)) 154 | 155 | var err *C.GError 156 | C.frida_device_manager_remove_remote_device_sync(d.manager, 157 | addressC, 158 | nil, 159 | &err) 160 | return handleGError(err) 161 | } 162 | 163 | // Clean will clean the resources held by the manager. 164 | func (d *DeviceManager) Clean() { 165 | clean(unsafe.Pointer(d.manager), unrefFrida) 166 | } 167 | 168 | // On connects manager to specific signals. Once sigName is triggered, 169 | // fn callback will be called with parameters populated. 170 | // 171 | // Signals available are: 172 | // - "added" with callback as func(device *frida.Device) {} 173 | // - "removed" with callback as func(device *frida.Device) {} 174 | // - "changed" with callback as func() {} 175 | func (d *DeviceManager) On(sigName string, fn any) { 176 | connectClosure(unsafe.Pointer(d.manager), sigName, fn) 177 | } 178 | 179 | func (d *DeviceManager) getManager() *C.FridaDeviceManager { 180 | return d.manager 181 | } 182 | -------------------------------------------------------------------------------- /frida/misc.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "unsafe" 11 | ) 12 | 13 | type Cancellable struct { 14 | cancellable *C.GCancellable 15 | } 16 | 17 | // NewCancellable wraps GCancellable 18 | // used to provide ability to cancel frida funcs. 19 | // Reminder that the caller must either `Cancellable.Cancel()` or 20 | // `Cancellable.Unref()` to unref the underlying C data. 21 | func NewCancellable() *Cancellable { 22 | return &Cancellable{ 23 | cancellable: C.g_cancellable_new(), 24 | } 25 | } 26 | 27 | // Cancel sends the cancel signal to GCancellable 28 | // as well unrefs 29 | func (c *Cancellable) Cancel() { 30 | C.g_cancellable_cancel(c.cancellable) 31 | } 32 | 33 | // Unref unrefs the wrapped GCancellable 34 | func (c *Cancellable) Unref() { 35 | C.g_object_unref((C.gpointer)(c.cancellable)) 36 | } 37 | 38 | type options struct { 39 | cancellable *C.GCancellable 40 | } 41 | 42 | func setupOptions(opts []OptFunc) options { 43 | o := &options{} 44 | for _, opt := range opts { 45 | opt(o) 46 | } 47 | return *o 48 | } 49 | 50 | type OptFunc func(o *options) 51 | 52 | // WithCancel is inteded to be used a varadic option. 53 | // Provides the ability to to pass GCancellable to 54 | // frida functions. 55 | // 56 | // Note: it is advisable to use the `FuncCtx` 57 | // version of functions rather than handling this yourself. 58 | func WithCancel(cancel *Cancellable) OptFunc { 59 | return func(o *options) { 60 | o.cancellable = cancel.cancellable 61 | } 62 | } 63 | 64 | func handleGError(gErr *C.GError) error { 65 | if gErr == nil { 66 | return nil 67 | } 68 | defer clean(unsafe.Pointer(gErr), unrefGError) 69 | return fmt.Errorf("FError: %s", C.GoString(gErr.message)) 70 | } 71 | 72 | // MessageType represents all possible message types populated 73 | // in the first argument of the on_message callback. 74 | type MessageType string 75 | 76 | const ( 77 | MessageTypeLog MessageType = "log" 78 | MessageTypeError MessageType = "error" 79 | MessageTypeSend MessageType = "send" 80 | ) 81 | 82 | // LevelType represents possible levels when Message.Type == MessageTypeLog 83 | type LevelType string 84 | 85 | const ( 86 | LevelTypeLog LevelType = "info" 87 | LevelTypeWarn LevelType = "warning" 88 | LevelTypeError LevelType = "error" 89 | ) 90 | 91 | // Message represents the data returned inside the message parameter in on_message script callback 92 | type Message struct { 93 | Type MessageType `json:"type"` 94 | Level LevelType `json:"level,omitempty"` // populated when type==MessageTypeLog 95 | Description string `json:"description,omitempty"` // populated when type==MessageTypeError 96 | Stack string `json:"stack,omitempty"` // populated when type==MessageTypeError 97 | Filename string `json:"fileName,omitempty"` // populated when type==MessageTypeError 98 | LineNumber int `json:"lineNumber,omitempty"` // populated when type==MessageTypeError 99 | ColumnNumber int `json:"columnNumber,omitempty"` // populated when type==MessageTypeError 100 | Payload any `json:"payload,omitempty"` 101 | IsPayloadMap bool 102 | } 103 | 104 | // ScriptMessageToMessage returns the parsed Message from the message string received in 105 | // script.On("message", func(msg string, data []byte) {}) callback. 106 | func ScriptMessageToMessage(message string) (*Message, error) { 107 | var m Message 108 | if err := json.Unmarshal([]byte(message), &m); err != nil { 109 | return nil, err 110 | } 111 | if m.Type != MessageTypeError { 112 | var payload map[string]any 113 | if err := json.Unmarshal([]byte(m.Payload.(string)), &payload); err == nil { 114 | m.Payload = payload 115 | m.IsPayloadMap = true 116 | } 117 | } 118 | return &m, nil 119 | } 120 | 121 | func handleWithContext(ctx context.Context, f func(c *Cancellable, done chan any, errC chan error)) (any, error) { 122 | doneC := make(chan any, 1) 123 | errC := make(chan error, 1) 124 | 125 | c := NewCancellable() 126 | go f(c, doneC, errC) 127 | 128 | for { 129 | select { 130 | case <-ctx.Done(): 131 | c.Cancel() 132 | return nil, ErrContextCancelled 133 | case done := <-doneC: 134 | c.Unref() 135 | return done, nil 136 | case err := <-errC: 137 | c.Unref() 138 | return nil, err 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /frida/peer_options.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // PeerOptions type represents struct used to setup p2p connection. 10 | type PeerOptions struct { 11 | opts *C.FridaPeerOptions 12 | } 13 | 14 | // NewPeerOptions creates new empty peer options. 15 | func NewPeerOptions() *PeerOptions { 16 | opts := C.frida_peer_options_new() 17 | return &PeerOptions{opts} 18 | } 19 | 20 | // StunServer returns the stun server for peer options. 21 | func (p *PeerOptions) StunServer() string { 22 | return C.GoString(C.frida_peer_options_get_stun_server(p.opts)) 23 | } 24 | 25 | // ClearRelays removes previously added relays. 26 | func (p *PeerOptions) ClearRelays() { 27 | C.frida_peer_options_clear_relays(p.opts) 28 | } 29 | 30 | // AddRelay adds new relay to use for peer options. 31 | func (p *PeerOptions) AddRelay(relay *Relay) { 32 | C.frida_peer_options_add_relay(p.opts, relay.r) 33 | } 34 | 35 | // SetStunServer sets the stun server for peer options. 36 | func (p *PeerOptions) SetStunServer(stunServer string) { 37 | stunC := C.CString(stunServer) 38 | defer C.free(unsafe.Pointer(stunC)) 39 | C.frida_peer_options_set_stun_server(p.opts, stunC) 40 | } 41 | 42 | // Clean will clean the resources held by the peer options. 43 | func (p *PeerOptions) Clean() { 44 | clean(unsafe.Pointer(p.opts), unrefFrida) 45 | } 46 | -------------------------------------------------------------------------------- /frida/portal_membership.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import "unsafe" 6 | 7 | // PortalMembership type is used to join portal with session. 8 | type PortalMembership struct { 9 | mem *C.FridaPortalMembership 10 | } 11 | 12 | // Terminate terminates the session membership 13 | func (p *PortalMembership) Terminate() error { 14 | var err *C.GError 15 | C.frida_portal_membership_terminate_sync(p.mem, nil, &err) 16 | return handleGError(err) 17 | } 18 | 19 | // Clean will clean the resources held by the portal membership. 20 | func (p *PortalMembership) Clean() { 21 | clean(unsafe.Pointer(p.mem), unrefFrida) 22 | } 23 | -------------------------------------------------------------------------------- /frida/portal_options.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import "unsafe" 6 | 7 | // PortalOptions type represents struct used to connect to the portal. 8 | type PortalOptions struct { 9 | opts *C.FridaPortalOptions 10 | } 11 | 12 | // NewPortalOptions creates new portal options. 13 | func NewPortalOptions() *PortalOptions { 14 | opts := C.frida_portal_options_new() 15 | return &PortalOptions{ 16 | opts: opts, 17 | } 18 | } 19 | 20 | // Certificate returns the tls certificate for portal options. 21 | func (p *PortalOptions) Certificate() *Certificate { 22 | cert := C.frida_portal_options_get_certificate(p.opts) 23 | return &Certificate{cert} 24 | } 25 | 26 | // Token returns the token for the portal. 27 | func (p *PortalOptions) Token() string { 28 | return C.GoString(C.frida_portal_options_get_token(p.opts)) 29 | } 30 | 31 | // ACL returns the acls for the portal. 32 | func (p *PortalOptions) ACL() []string { 33 | var sz C.gint 34 | arr := C.frida_portal_options_get_acl(p.opts, &sz) 35 | return cArrayToStringSlice(arr, C.int(sz)) 36 | } 37 | 38 | // SetCertificate sets the certificate for the portal. 39 | func (p *PortalOptions) SetCertificate(certPath string) error { 40 | cert, err := gTLSCertificateFromFile(certPath) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | C.frida_portal_options_set_certificate(p.opts, cert.cert) 46 | return nil 47 | } 48 | 49 | // SetToken sets the token for the authentication. 50 | func (p *PortalOptions) SetToken(token string) { 51 | tokenC := C.CString(token) 52 | defer C.free(unsafe.Pointer(tokenC)) 53 | 54 | C.frida_portal_options_set_token(p.opts, tokenC) 55 | } 56 | 57 | // SetACL sets the acls from the string slice provided. 58 | func (p *PortalOptions) SetACL(acls []string) { 59 | arr, sz := stringSliceToCharArr(acls) 60 | C.frida_portal_options_set_acl(p.opts, arr, C.gint(sz)) 61 | freeCharArray(arr, C.int(sz)) 62 | } 63 | 64 | // Clean will clean the resources held by the portal options. 65 | func (p *PortalOptions) Clean() { 66 | clean(unsafe.Pointer(p.opts), unrefFrida) 67 | } 68 | -------------------------------------------------------------------------------- /frida/portal_service.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "runtime" 7 | "unsafe" 8 | ) 9 | 10 | // Portal represents portal to collect exposed gadgets and sessions. 11 | type Portal struct { 12 | portal *C.FridaPortalService 13 | } 14 | 15 | // NewPortal creates new Portal from the EndpointParameters provided. 16 | func NewPortal(clusterParams, controlParams *EndpointParameters) *Portal { 17 | p := C.frida_portal_service_new(clusterParams.params, controlParams.params) 18 | 19 | return &Portal{ 20 | portal: p, 21 | } 22 | } 23 | 24 | // Device returns portal device. 25 | func (p *Portal) Device() DeviceInt { 26 | dev := C.frida_portal_service_get_device(p.portal) 27 | return &Device{dev} 28 | } 29 | 30 | // ClusterParams returns the cluster parameters for the portal. 31 | func (p *Portal) ClusterParams() *EndpointParameters { 32 | params := C.frida_portal_service_get_cluster_params(p.portal) 33 | return &EndpointParameters{params} 34 | } 35 | 36 | // ControlParams returns the control parameters for the portal. 37 | func (p *Portal) ControlParams() *EndpointParameters { 38 | params := C.frida_portal_service_get_control_params(p.portal) 39 | return &EndpointParameters{params} 40 | } 41 | 42 | // Start stars the portal. 43 | func (p *Portal) Start() error { 44 | var err *C.GError 45 | C.frida_portal_service_start_sync(p.portal, nil, &err) 46 | return handleGError(err) 47 | } 48 | 49 | // Stop stops the portal. 50 | func (p *Portal) Stop() error { 51 | var err *C.GError 52 | C.frida_portal_service_stop_sync(p.portal, nil, &err) 53 | return handleGError(err) 54 | } 55 | 56 | // Kick kicks the connection with connectionID provided. 57 | func (p *Portal) Kick(connectionID uint) { 58 | C.frida_portal_service_kick(p.portal, C.guint(connectionID)) 59 | } 60 | 61 | // Post posts the message to the connectionID with json string or bytes. 62 | func (p *Portal) Post(connectionID uint, json string, data []byte) { 63 | jsonC := C.CString(json) 64 | defer C.free(unsafe.Pointer(jsonC)) 65 | 66 | gBytesData := goBytesToGBytes(data) 67 | runtime.SetFinalizer(gBytesData, func(g *C.GBytes) { 68 | clean(unsafe.Pointer(g), unrefGObject) 69 | }) 70 | 71 | C.frida_portal_service_post(p.portal, C.guint(connectionID), jsonC, gBytesData) 72 | runtime.KeepAlive(gBytesData) 73 | } 74 | 75 | // Narrowcast sends the message to all controllers tagged with the tag. 76 | func (p *Portal) Narrowcast(tag, json string, data []byte) { 77 | tagC := C.CString(tag) 78 | defer C.free(unsafe.Pointer(tagC)) 79 | 80 | jsonC := C.CString(json) 81 | defer C.free(unsafe.Pointer(jsonC)) 82 | 83 | gBytesData := goBytesToGBytes(data) 84 | runtime.SetFinalizer(gBytesData, func(g *C.GBytes) { 85 | clean(unsafe.Pointer(g), unrefGObject) 86 | }) 87 | C.frida_portal_service_narrowcast(p.portal, tagC, jsonC, gBytesData) 88 | runtime.KeepAlive(gBytesData) 89 | } 90 | 91 | // Broadcast sends the message to all controllers. 92 | func (p *Portal) Broadcast(json string, data []byte) { 93 | jsonC := C.CString(json) 94 | defer C.free(unsafe.Pointer(jsonC)) 95 | 96 | gBytesData := goBytesToGBytes(data) 97 | runtime.SetFinalizer(gBytesData, func(g *C.GBytes) { 98 | clean(unsafe.Pointer(g), unrefGObject) 99 | }) 100 | C.frida_portal_service_broadcast(p.portal, jsonC, gBytesData) 101 | runtime.KeepAlive(gBytesData) 102 | } 103 | 104 | // EnumerateTags returns all the tags that connection with connectionID is tagged 105 | // with. 106 | func (p *Portal) EnumerateTags(connectionID uint) []string { 107 | var length C.gint 108 | tagsC := C.frida_portal_service_enumerate_tags( 109 | p.portal, 110 | C.guint(connectionID), 111 | &length) 112 | 113 | return cArrayToStringSlice(tagsC, C.int(length)) 114 | } 115 | 116 | // TagConnection tags the connection with connectionID with the tag provided. 117 | func (p *Portal) TagConnection(connectionID uint, tag string) { 118 | tagC := C.CString(tag) 119 | defer C.free(unsafe.Pointer(tagC)) 120 | 121 | C.frida_portal_service_tag(p.portal, C.guint(connectionID), tagC) 122 | } 123 | 124 | // UntagConnection untags the connection with connectionID with the tag provided. 125 | func (p *Portal) UntagConnection(connectionID uint, tag string) { 126 | tagC := C.CString(tag) 127 | defer C.free(unsafe.Pointer(tagC)) 128 | 129 | C.frida_portal_service_untag(p.portal, C.guint(connectionID), tagC) 130 | } 131 | 132 | // Clean will clean the resources held by the frida. 133 | func (p *Portal) Clean() { 134 | clean(unsafe.Pointer(p.portal), unrefFrida) 135 | } 136 | 137 | // On connects portal to specific signals. Once sigName is triggered, 138 | // fn callback will be called with parameters populated. 139 | // 140 | // Signals available are: 141 | // - "node_connected" with callback as func(connId uint, addr *frida.Address) {} 142 | // - "node_joined" with callback as func(connId uint, app *frida.Application) {} 143 | // - "node_left" with callback as func(connId uint, app *frida.Application) {} 144 | // - "node_disconnected" with callback as func(connId uint, addr *frida.Address) {} 145 | // - "controller_connected" with callback as func(connId uint, addr *frida.Address) {} 146 | // - "controller_disconnected" with callback as func(connId uint, addr *frida.Address) {} 147 | // - "authenticated" with callback as func(connId uint, sessionInfo string) {} 148 | // - "subscribe" with callback as func(connId uint) {} 149 | // - "message" with callback as func(connId uint, jsonData string, data []byte) {} 150 | func (p *Portal) On(sigName string, fn any) { 151 | connectClosure(unsafe.Pointer(p.portal), sigName, fn) 152 | } 153 | -------------------------------------------------------------------------------- /frida/process.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import "unsafe" 6 | 7 | // Process represents process on the device. 8 | type Process struct { 9 | proc *C.FridaProcess 10 | } 11 | 12 | // PID returns the PID of the process. 13 | func (p *Process) PID() int { 14 | if p.proc != nil { 15 | return int(C.frida_process_get_pid(p.proc)) 16 | } 17 | return -1 18 | } 19 | 20 | // Name returns the name of the process. 21 | func (p *Process) Name() string { 22 | if p.proc != nil { 23 | return C.GoString(C.frida_process_get_name(p.proc)) 24 | } 25 | return "" 26 | } 27 | 28 | // Params returns the parameters of the process. 29 | func (p *Process) Params() map[string]any { 30 | if p.proc != nil { 31 | ht := C.frida_process_get_parameters(p.proc) 32 | params := gHashTableToMap(ht) 33 | return params 34 | } 35 | return nil 36 | } 37 | 38 | // Clean will clean the resources held by the process. 39 | func (p *Process) Clean() { 40 | if p.proc != nil { 41 | clean(unsafe.Pointer(p.proc), unrefFrida) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /frida/relay.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import "unsafe" 6 | 7 | // Relay type represents relay for setting up p2p. 8 | type Relay struct { 9 | r *C.FridaRelay 10 | } 11 | 12 | // NewRelay creates the new relay with the credentials provided. 13 | func NewRelay(address, username, password string, kind RelayKind) *Relay { 14 | var addressC *C.char = nil 15 | var usernameC *C.char = nil 16 | var passwordC *C.char = nil 17 | 18 | if address != "" { 19 | addressC = C.CString(address) 20 | defer C.free(unsafe.Pointer(addressC)) 21 | } 22 | 23 | if username != "" { 24 | usernameC = C.CString(username) 25 | defer C.free(unsafe.Pointer(usernameC)) 26 | } 27 | 28 | if password != "" { 29 | passwordC = C.CString(password) 30 | defer C.free(unsafe.Pointer(passwordC)) 31 | } 32 | 33 | knd := C.FridaRelayKind(kind) 34 | 35 | rly := C.frida_relay_new( 36 | addressC, 37 | usernameC, 38 | passwordC, 39 | knd) 40 | 41 | return &Relay{rly} 42 | } 43 | 44 | // Address returns the address of the relay. 45 | func (relay *Relay) Address() string { 46 | return C.GoString(C.frida_relay_get_address(relay.r)) 47 | } 48 | 49 | // Username returns the username for the relay. 50 | func (relay *Relay) Username() string { 51 | return C.GoString(C.frida_relay_get_username(relay.r)) 52 | } 53 | 54 | // Password returns the password for the relay. 55 | func (relay *Relay) Password() string { 56 | return C.GoString(C.frida_relay_get_password(relay.r)) 57 | } 58 | 59 | // RelayKind returns the kind of relay. 60 | func (relay *Relay) RelayKind() RelayKind { 61 | return RelayKind(C.frida_relay_get_kind(relay.r)) 62 | } 63 | 64 | // Clean will clean the resources held by the relay. 65 | func (relay *Relay) Clean() { 66 | clean(unsafe.Pointer(relay.r), unrefFrida) 67 | } 68 | -------------------------------------------------------------------------------- /frida/remote_device_options.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | /*#include 4 | #include 5 | */ 6 | import "C" 7 | import "unsafe" 8 | 9 | // RemoteDeviceOptions type is used to configure the remote device. 10 | type RemoteDeviceOptions struct { 11 | opts *C.FridaRemoteDeviceOptions 12 | } 13 | 14 | // NewRemoteDeviceOptions returns the new remote device options. 15 | func NewRemoteDeviceOptions() *RemoteDeviceOptions { 16 | opts := C.frida_remote_device_options_new() 17 | 18 | return &RemoteDeviceOptions{ 19 | opts: opts, 20 | } 21 | } 22 | 23 | // Certificate returns the certificate for the remote device options. 24 | func (r *RemoteDeviceOptions) Certificate() *Certificate { 25 | cert := C.frida_remote_device_options_get_certificate(r.opts) 26 | return &Certificate{cert} 27 | } 28 | 29 | // Origin returns the origin for the remote device options. 30 | func (r *RemoteDeviceOptions) Origin() string { 31 | return C.GoString(C.frida_remote_device_options_get_origin(r.opts)) 32 | } 33 | 34 | // Token returns the token for the remote device options. 35 | func (r *RemoteDeviceOptions) Token() string { 36 | return C.GoString(C.frida_remote_device_options_get_token(r.opts)) 37 | } 38 | 39 | // KeepAliveInterval returns the keepalive interval for the remote device options. 40 | func (r *RemoteDeviceOptions) KeepAliveInterval() int { 41 | return int(C.frida_remote_device_options_get_keepalive_interval(r.opts)) 42 | } 43 | 44 | // SetCertificate sets the certificate for the remote device. 45 | func (r *RemoteDeviceOptions) SetCertificate(certPath string) error { 46 | cert, err := gTLSCertificateFromFile(certPath) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | C.frida_remote_device_options_set_certificate(r.opts, cert.cert) 52 | return nil 53 | } 54 | 55 | // SetOrigin sets the origin for the remote device options. 56 | func (r *RemoteDeviceOptions) SetOrigin(origin string) { 57 | originC := C.CString(origin) 58 | defer C.free(unsafe.Pointer(originC)) 59 | 60 | C.frida_remote_device_options_set_origin(r.opts, originC) 61 | } 62 | 63 | // SetToken sets the token for the remote device options. 64 | func (r *RemoteDeviceOptions) SetToken(token string) { 65 | tokenC := C.CString(token) 66 | defer C.free(unsafe.Pointer(tokenC)) 67 | 68 | C.frida_remote_device_options_set_token(r.opts, tokenC) 69 | } 70 | 71 | // SetKeepAlive sets keepalive interval for the remote device options. 72 | func (r *RemoteDeviceOptions) SetKeepAlive(interval int) { 73 | C.frida_remote_device_options_set_keepalive_interval(r.opts, C.gint(interval)) 74 | } 75 | 76 | // Clean will clean the resources held by the remote device options. 77 | func (r *RemoteDeviceOptions) Clean() { 78 | clean(unsafe.Pointer(r.opts), unrefFrida) 79 | } 80 | 81 | func gTLSCertificateFromFile(pempath string) (*Certificate, error) { 82 | cert := C.CString(pempath) 83 | defer C.free(unsafe.Pointer(cert)) 84 | 85 | var err *C.GError 86 | gTLSCert := C.g_tls_certificate_new_from_file(cert, &err) 87 | 88 | return &Certificate{gTLSCert}, handleGError(err) 89 | } 90 | 91 | func gFileFromPath(assetPath string) *C.GFile { 92 | pth := C.CString(assetPath) 93 | defer C.free(unsafe.Pointer(pth)) 94 | 95 | return C.g_file_new_for_path(pth) 96 | } 97 | -------------------------------------------------------------------------------- /frida/script.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "context" 7 | "encoding/json" 8 | "reflect" 9 | "runtime" 10 | "strings" 11 | "sync" 12 | "unsafe" 13 | 14 | "github.com/google/uuid" 15 | ) 16 | 17 | var rpcCalls = sync.Map{} 18 | 19 | // Script represents loaded string in the memory. 20 | type Script struct { 21 | hasHandler bool 22 | sc *C.FridaScript 23 | fn reflect.Value 24 | } 25 | 26 | // IsDestroyed function returns whether the script previously loaded is destroyed (could be caused by unload) 27 | func (s *Script) IsDestroyed() bool { 28 | destroyed := C.frida_script_is_destroyed(s.sc) 29 | return int(destroyed) == 1 30 | } 31 | 32 | // Load function loads the script into the process. 33 | func (s *Script) Load() error { 34 | if !s.hasHandler { 35 | s.On("message", func() {}) 36 | } 37 | var err *C.GError 38 | C.frida_script_load_sync(s.sc, nil, &err) 39 | return handleGError(err) 40 | } 41 | 42 | // Unload function unload previously loaded script 43 | func (s *Script) Unload() error { 44 | var err *C.GError 45 | C.frida_script_unload_sync(s.sc, nil, &err) 46 | return handleGError(err) 47 | } 48 | 49 | // Eternalize function will keep the script loaded even after deataching from the process 50 | func (s *Script) Eternalize() error { 51 | var err *C.GError 52 | C.frida_script_eternalize_sync(s.sc, nil, &err) 53 | return handleGError(err) 54 | } 55 | 56 | // Post sends post to the script. 57 | func (s *Script) Post(jsonString string, data []byte) { 58 | jsonStringC := C.CString(jsonString) 59 | defer C.free(unsafe.Pointer(jsonStringC)) 60 | 61 | if len(data) > 0 { 62 | gBytesData := goBytesToGBytes(data) 63 | 64 | runtime.SetFinalizer(gBytesData, func(g *C.GBytes) { 65 | clean(unsafe.Pointer(g), unrefGObject) 66 | }) 67 | C.frida_script_post(s.sc, jsonStringC, gBytesData) 68 | runtime.KeepAlive(gBytesData) 69 | } else { 70 | C.frida_script_post(s.sc, jsonStringC, nil) 71 | } 72 | } 73 | 74 | // EnableDebugger function enables debugging on the port specified 75 | func (s *Script) EnableDebugger(port uint16) error { 76 | var err *C.GError 77 | C.frida_script_enable_debugger_sync(s.sc, C.guint16(port), nil, &err) 78 | 79 | return handleGError(err) 80 | } 81 | 82 | // DisableDebugger function disables debugging 83 | func (s *Script) DisableDebugger() error { 84 | var err *C.GError 85 | C.frida_script_disable_debugger_sync(s.sc, nil, &err) 86 | return handleGError(err) 87 | } 88 | 89 | // ExportsCall will try to call fn from the rpc.exports with args provided 90 | func (s *Script) ExportsCall(fn string, args ...any) any { 91 | ch := s.makeExportsCall(fn, args...) 92 | defer releaseChannel(ch) 93 | ret := <-ch 94 | return ret 95 | } 96 | 97 | // ExportsCallWithContext will try to call fn from the rpc.exports with args provided using context provided. 98 | func (s *Script) ExportsCallWithContext(ctx context.Context, fn string, args ...any) any { 99 | ch := s.makeExportsCall(fn, args...) 100 | defer releaseChannel(ch) 101 | select { 102 | case <-ctx.Done(): 103 | return ErrContextCancelled 104 | case ret := <-ch: 105 | return ret 106 | } 107 | } 108 | 109 | // Clean will clean the resources held by the script. 110 | func (s *Script) Clean() { 111 | clean(unsafe.Pointer(s.sc), unrefFrida) 112 | } 113 | 114 | // On connects script to specific signals. Once sigName is triggered, 115 | // fn callback will be called with parameters populated. 116 | // 117 | // Signals available are: 118 | // - "destroyed" with callback as func() {} 119 | // - "message" with callback as func(message string, data []byte) {} 120 | func (s *Script) On(sigName string, fn any) { 121 | s.hasHandler = true 122 | // hijack message to handle rpc calls 123 | if sigName == "message" { 124 | s.fn = reflect.ValueOf(fn) 125 | connectClosure(unsafe.Pointer(s.sc), sigName, s.hijackFn) 126 | } else { 127 | connectClosure(unsafe.Pointer(s.sc), sigName, fn) 128 | } 129 | } 130 | 131 | func getRPCIDFromMessage(message string) (string, any, error) { 132 | unmarshalled := make(map[string]any) 133 | if err := json.Unmarshal([]byte(message), &unmarshalled); err != nil { 134 | return "", nil, err 135 | } 136 | 137 | var rpcID string 138 | var ret any 139 | 140 | loopMap := func(mp map[string]any) { 141 | for _, v := range mp { 142 | if reflect.ValueOf(v).Kind() == reflect.Slice { 143 | slc := v.([]any) 144 | rpcID = slc[1].(string) 145 | ret = slc[3] 146 | 147 | } 148 | } 149 | } 150 | loopMap(unmarshalled) 151 | 152 | return rpcID, ret, nil 153 | } 154 | 155 | func (s *Script) hijackFn(message string, data []byte) { 156 | if strings.Contains(message, "frida:rpc") { 157 | rpcID, ret, err := getRPCIDFromMessage(message) 158 | if err != nil { 159 | panic(err) 160 | } 161 | callerCh, ok := rpcCalls.Load(rpcID) 162 | if !ok { 163 | panic("rpc-id not found") 164 | } 165 | ch := callerCh.(chan any) 166 | ch <- ret 167 | rpcCalls.Delete(rpcID) 168 | } else { 169 | var args []reflect.Value 170 | switch s.fn.Type().NumIn() { 171 | case 1: 172 | args = append(args, reflect.ValueOf(message)) 173 | case 2: 174 | args = append(args, reflect.ValueOf(message)) 175 | args = append(args, reflect.ValueOf(data)) 176 | } 177 | s.fn.Call(args) 178 | } 179 | } 180 | 181 | func newRPCCall(fnName string) []any { 182 | id := uuid.New() 183 | dt := []any{ 184 | "frida:rpc", 185 | id.String()[:16], 186 | "call", 187 | fnName, 188 | } 189 | 190 | return dt 191 | } 192 | 193 | func (s *Script) makeExportsCall(fn string, args ...any) chan any { 194 | rpcData := newRPCCall(fn) 195 | 196 | aIface := make([]any, len(args)) 197 | copy(aIface, args) 198 | 199 | ct := 0 200 | rpc := make([]any, len(rpcData)+len(aIface)) 201 | 202 | for i := 0; i < len(rpcData); i++ { 203 | rpc[ct] = rpcData[i] 204 | ct++ 205 | } 206 | 207 | if len(aIface) > 0 { 208 | rpc[ct] = aIface 209 | } 210 | 211 | ch := getChannel() 212 | rpcCalls.Store(rpcData[1], ch) 213 | 214 | bt, _ := json.Marshal(rpc) 215 | s.Post(string(bt), nil) 216 | 217 | return ch 218 | } 219 | 220 | var channelPool = sync.Pool{ 221 | New: func() interface{} { 222 | return make(chan any, 1) 223 | }, 224 | } 225 | 226 | func getChannel() chan any { 227 | ch := channelPool.Get().(chan any) 228 | select { 229 | case <-ch: 230 | default: 231 | } 232 | 233 | return ch 234 | } 235 | 236 | func releaseChannel(ch chan any) { 237 | select { 238 | case <-ch: 239 | default: 240 | } 241 | channelPool.Put(ch) 242 | } 243 | -------------------------------------------------------------------------------- /frida/script_options.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "runtime" 7 | "unsafe" 8 | ) 9 | 10 | // ScriptOptions type represents options passed to the session to create script. 11 | type ScriptOptions struct { 12 | opts *C.FridaScriptOptions 13 | } 14 | 15 | // NewScriptOptions creates new script options with the script name provided. 16 | func NewScriptOptions(name string) *ScriptOptions { 17 | opts := C.frida_script_options_new() 18 | 19 | nameC := C.CString(name) 20 | defer C.free(unsafe.Pointer(nameC)) 21 | 22 | C.frida_script_options_set_name(opts, nameC) 23 | 24 | return &ScriptOptions{ 25 | opts: opts, 26 | } 27 | } 28 | 29 | // SetName sets the name of the script. 30 | func (s *ScriptOptions) SetName(name string) { 31 | nameC := C.CString(name) 32 | defer C.free(unsafe.Pointer(nameC)) 33 | 34 | C.frida_script_options_set_name(s.opts, nameC) 35 | } 36 | 37 | // SetSnapshot sets the snapshot for the script. 38 | func (s *ScriptOptions) SetSnapshot(value []byte) { 39 | bts := goBytesToGBytes(value) 40 | runtime.SetFinalizer(bts, func(g *C.GBytes) { 41 | clean(unsafe.Pointer(g), unrefGObject) 42 | }) 43 | C.frida_script_options_set_snapshot(s.opts, bts) 44 | runtime.KeepAlive(bts) 45 | } 46 | 47 | // SetSnapshotTransport sets the transport for the snapshot 48 | func (s *ScriptOptions) SetSnapshotTransport(tr SnapshotTransport) { 49 | C.frida_script_options_set_snapshot_transport(s.opts, 50 | C.FridaSnapshotTransport(tr)) 51 | } 52 | 53 | // SetRuntime sets the runtime for the script. 54 | func (s *ScriptOptions) SetRuntime(rt ScriptRuntime) { 55 | C.frida_script_options_set_runtime(s.opts, C.FridaScriptRuntime(rt)) 56 | } 57 | 58 | // Name returns the name for the script. 59 | func (s *ScriptOptions) Name() string { 60 | return C.GoString(C.frida_script_options_get_name(s.opts)) 61 | } 62 | 63 | // Snapshot returns the snapshot for the script. 64 | func (s *ScriptOptions) Snapshot() []byte { 65 | snap := C.frida_script_options_get_snapshot(s.opts) 66 | bts := getGBytes(snap) 67 | clean(unsafe.Pointer(snap), unrefGObject) 68 | return bts 69 | } 70 | 71 | // SnapshotTransport returns the transport for the script. 72 | func (s *ScriptOptions) SnapshotTransport() SnapshotTransport { 73 | tr := C.frida_script_options_get_snapshot_transport(s.opts) 74 | return SnapshotTransport(tr) 75 | } 76 | 77 | // Clean will clean the resources held by the script options. 78 | func (s *ScriptOptions) Clean() { 79 | clean(unsafe.Pointer(s.opts), unrefFrida) 80 | } 81 | -------------------------------------------------------------------------------- /frida/service.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | /* 4 | #include 5 | #include 6 | 7 | static GVariant * new_variant_from_c_string(const char * val, FridaService * svc) 8 | { 9 | GVariantBuilder builder; 10 | 11 | g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); 12 | g_variant_builder_add(&builder, "{sv}", "method", g_variant_new_string("takeScreenshot")); 13 | GVariant * variant = g_variant_builder_end(&builder); 14 | 15 | return variant; 16 | } 17 | 18 | */ 19 | import "C" 20 | 21 | type ServiceInt interface { 22 | } 23 | 24 | // Service represents Service from frida-core 25 | type Service struct { 26 | service *C.FridaService 27 | } 28 | 29 | func (s *Service) Request(req any) (any, error) { 30 | variant := goToGVariant(req) 31 | 32 | var err *C.GError 33 | resp := C.frida_service_request_sync(s.service, variant, nil, &err) 34 | if err != nil { 35 | return nil, handleGError(err) 36 | } 37 | return gVariantToGo(resp), nil 38 | } 39 | 40 | func (s *Service) Activate() error { 41 | var err *C.GError 42 | C.frida_service_activate_sync(s.service, nil, &err) 43 | return handleGError(err) 44 | } 45 | 46 | func (s *Service) Cancel() error { 47 | var err *C.GError 48 | C.frida_service_cancel_sync(s.service, nil, &err) 49 | return handleGError(err) 50 | } 51 | 52 | func (s *Service) IsClosed() bool { 53 | val := C.frida_service_is_closed(s.service) 54 | return int(val) != 0 55 | } 56 | -------------------------------------------------------------------------------- /frida/session.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "context" 7 | "runtime" 8 | "unsafe" 9 | ) 10 | 11 | // Session type represents the session with the device. 12 | type Session struct { 13 | s *C.FridaSession 14 | } 15 | 16 | // IsDetached returns bool whether session is detached or not. 17 | func (s *Session) IsDetached() bool { 18 | detached := C.frida_session_is_detached(s.s) 19 | return int(detached) == 1 20 | } 21 | 22 | // DetachWithContext runs Detach but with context. 23 | // This function will properly handle cancelling the frida operation. 24 | // It is advised to use this rather than handling Cancellable yourself. 25 | func (s *Session) DetachWithContext(ctx context.Context) error { 26 | _, err := handleWithContext(ctx, func(c *Cancellable, done chan any, errC chan error) { 27 | errC <- s.Detach(WithCancel(c)) 28 | }) 29 | return err 30 | } 31 | 32 | // Detach detaches the current session. 33 | func (s *Session) Detach(opts ...OptFunc) error { 34 | o := setupOptions(opts) 35 | return s.detach(o) 36 | } 37 | 38 | // detach 39 | func (s *Session) detach(opts options) error { 40 | var err *C.GError 41 | C.frida_session_detach_sync(s.s, opts.cancellable, &err) 42 | return handleGError(err) 43 | } 44 | 45 | // Resume resumes the current session. 46 | func (s *Session) Resume() error { 47 | var err *C.GError 48 | C.frida_session_resume_sync(s.s, nil, &err) 49 | return handleGError(err) 50 | } 51 | 52 | // EnableChildGating enables child gating on the session. 53 | func (s *Session) EnableChildGating() error { 54 | var err *C.GError 55 | C.frida_session_enable_child_gating_sync(s.s, nil, &err) 56 | 57 | return handleGError(err) 58 | } 59 | 60 | // DisableChildGating disables child gating on the session. 61 | func (s *Session) DisableChildGating() error { 62 | var err *C.GError 63 | C.frida_session_disable_child_gating_sync(s.s, nil, &err) 64 | 65 | return handleGError(err) 66 | } 67 | 68 | // CreateScript creates new string from the string provided. 69 | func (s *Session) CreateScript(script string) (*Script, error) { 70 | return s.CreateScriptWithOptions(script, nil) 71 | } 72 | 73 | // CreateScriptBytes is a wrapper around CreateScript(script string) 74 | func (s *Session) CreateScriptBytes(script []byte, opts *ScriptOptions) (*Script, error) { 75 | bts := goBytesToGBytes(script) 76 | runtime.SetFinalizer(bts, func(g *C.GBytes) { 77 | clean(unsafe.Pointer(g), unrefGObject) 78 | }) 79 | 80 | if opts == nil { 81 | opts = NewScriptOptions("frida-go") 82 | } 83 | defer clean(unsafe.Pointer(opts.opts), unrefFrida) 84 | 85 | var err *C.GError 86 | sc := C.frida_session_create_script_from_bytes_sync(s.s, 87 | bts, 88 | opts.opts, 89 | nil, 90 | &err) 91 | runtime.KeepAlive(bts) 92 | 93 | return &Script{ 94 | sc: sc, 95 | }, handleGError(err) 96 | } 97 | 98 | func (s *Session) CreateScriptWithSnapshot(script string, snapshot []byte) (*Script, error) { 99 | opts := NewScriptOptions("frida-go") 100 | opts.SetSnapshot(snapshot) 101 | return s.CreateScriptWithOptions(script, opts) 102 | } 103 | 104 | // CreateScriptWithOptions creates the script with the script options provided. 105 | // Useful in cases where you previously created the snapshot. 106 | func (s *Session) CreateScriptWithOptions(script string, opts *ScriptOptions) (*Script, error) { 107 | sc := C.CString(script) 108 | defer C.free(unsafe.Pointer(sc)) 109 | 110 | if opts == nil { 111 | opts = NewScriptOptions("frida-go") 112 | } 113 | defer clean(unsafe.Pointer(opts.opts), unrefFrida) 114 | 115 | if opts.Name() == "" { 116 | opts.SetName("frida-go") 117 | } 118 | 119 | var err *C.GError 120 | cScript := C.frida_session_create_script_sync(s.s, sc, opts.opts, nil, &err) 121 | return &Script{ 122 | sc: cScript, 123 | }, handleGError(err) 124 | } 125 | 126 | // CompileScript compiles the script from the script as string provided. 127 | func (s *Session) CompileScript(script string, opts *ScriptOptions) ([]byte, error) { 128 | scriptC := C.CString(script) 129 | defer C.free(unsafe.Pointer(scriptC)) 130 | 131 | if opts == nil { 132 | opts = NewScriptOptions("frida-go") 133 | } 134 | defer clean(unsafe.Pointer(opts.opts), unrefFrida) 135 | 136 | var err *C.GError 137 | bts := C.frida_session_compile_script_sync(s.s, 138 | scriptC, 139 | opts.opts, 140 | nil, 141 | &err, 142 | ) 143 | if err != nil { 144 | return nil, handleGError(err) 145 | } 146 | return getGBytes(bts), nil 147 | } 148 | 149 | // SnapshotScript creates snapshot from the script. 150 | func (s *Session) SnapshotScript(embedScript string, snapshotOpts *SnapshotOptions) ([]byte, error) { 151 | embedScriptC := C.CString(embedScript) 152 | defer C.free(unsafe.Pointer(embedScriptC)) 153 | 154 | var err *C.GError 155 | ret := C.frida_session_snapshot_script_sync( 156 | s.s, 157 | embedScriptC, 158 | snapshotOpts.opts, 159 | nil, 160 | &err, 161 | ) 162 | if err != nil { 163 | return nil, handleGError(err) 164 | } 165 | return getGBytes(ret), nil 166 | } 167 | 168 | // SetupPeerConnection sets up peer (p2p) connection with peer options provided. 169 | func (s *Session) SetupPeerConnection(opts *PeerOptions) error { 170 | var err *C.GError 171 | C.frida_session_setup_peer_connection_sync(s.s, opts.opts, nil, &err) 172 | return handleGError(err) 173 | } 174 | 175 | // JoinPortal joins portal at the address with portal options provided. 176 | func (s *Session) JoinPortal(address string, opts *PortalOptions) (*PortalMembership, error) { 177 | addrC := C.CString(address) 178 | defer C.free(unsafe.Pointer(addrC)) 179 | 180 | var err *C.GError 181 | mem := C.frida_session_join_portal_sync(s.s, addrC, opts.opts, nil, &err) 182 | 183 | return &PortalMembership{mem}, handleGError(err) 184 | } 185 | 186 | // Clean will clean the resources held by the session. 187 | func (s *Session) Clean() { 188 | clean(unsafe.Pointer(s.s), unrefFrida) 189 | } 190 | 191 | // On connects session to specific signals. Once sigName is triggered, 192 | // fn callback will be called with parameters populated. 193 | // 194 | // Signals available are: 195 | // - "detached" with callback as func(reason frida.SessionDetachReason, crash *frida.Crash) {} 196 | func (s *Session) On(sigName string, fn any) { 197 | connectClosure(unsafe.Pointer(s.s), sigName, fn) 198 | } 199 | -------------------------------------------------------------------------------- /frida/session_options.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import "unsafe" 6 | 7 | // SessionOptions type is used to configure session 8 | type SessionOptions struct { 9 | opts *C.FridaSessionOptions 10 | } 11 | 12 | // NewSessionOptions create new SessionOptions with the realm and 13 | // timeout to persist provided 14 | func NewSessionOptions(realm Realm, persistTimeout uint) *SessionOptions { 15 | opts := C.frida_session_options_new() 16 | C.frida_session_options_set_realm(opts, C.FridaRealm(realm)) 17 | C.frida_session_options_set_persist_timeout(opts, C.guint(persistTimeout)) 18 | 19 | return &SessionOptions{opts} 20 | } 21 | 22 | // Realm returns the realm of the options 23 | func (s *SessionOptions) Realm() Realm { 24 | rlm := C.frida_session_options_get_realm(s.opts) 25 | return Realm(rlm) 26 | } 27 | 28 | // PersistTimeout returns the persist timeout of the script.s 29 | func (s *SessionOptions) PersistTimeout() int { 30 | return int(C.frida_session_options_get_persist_timeout(s.opts)) 31 | } 32 | 33 | // Clean will clean the resources held by the session options. 34 | func (s *SessionOptions) Clean() { 35 | clean(unsafe.Pointer(s.opts), unrefFrida) 36 | } 37 | -------------------------------------------------------------------------------- /frida/snapshot_options.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import "unsafe" 6 | 7 | type SnapshotOptions struct { 8 | opts *C.FridaSnapshotOptions 9 | } 10 | 11 | // NewSnapshotOptions creates new snapshot options with warmup 12 | // script and script runtime provided. 13 | func NewSnapshotOptions(warmupScript string, rt ScriptRuntime) *SnapshotOptions { 14 | opts := C.frida_snapshot_options_new() 15 | warmupScriptC := C.CString(warmupScript) 16 | defer C.free(unsafe.Pointer(warmupScriptC)) 17 | 18 | C.frida_snapshot_options_set_warmup_script(opts, warmupScriptC) 19 | C.frida_snapshot_options_set_runtime(opts, C.FridaScriptRuntime(rt)) 20 | 21 | return &SnapshotOptions{ 22 | opts: opts, 23 | } 24 | } 25 | 26 | // WarmupScript returns the warmup script used to create the script options. 27 | func (s *SnapshotOptions) WarmupScript() string { 28 | return C.GoString(C.frida_snapshot_options_get_warmup_script(s.opts)) 29 | } 30 | 31 | // Runtime returns the runtime used to create the script options. 32 | func (s *SnapshotOptions) Runtime() ScriptRuntime { 33 | return ScriptRuntime(int(C.frida_snapshot_options_get_runtime(s.opts))) 34 | } 35 | 36 | // Clean will clean the resources held by the snapshot options. 37 | func (s *SnapshotOptions) Clean() { 38 | clean(unsafe.Pointer(s.opts), unrefFrida) 39 | } 40 | -------------------------------------------------------------------------------- /frida/spawn.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import "unsafe" 6 | 7 | // Spawn represents spawn of the device. 8 | type Spawn struct { 9 | spawn *C.FridaSpawn 10 | } 11 | 12 | // PID returns process id of the spawn. 13 | func (s *Spawn) PID() int { 14 | return int(C.frida_spawn_get_pid(s.spawn)) 15 | } 16 | 17 | // Identifier returns identifier of the spawn. 18 | func (s *Spawn) Identifier() string { 19 | return C.GoString(C.frida_spawn_get_identifier(s.spawn)) 20 | } 21 | 22 | // Clean will clean the resources held by the spawn. 23 | func (s *Spawn) Clean() { 24 | clean(unsafe.Pointer(s), unrefFrida) 25 | } 26 | -------------------------------------------------------------------------------- /frida/spawn_options.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // SpawnOptions struct is responsible for setting/getting argv, envp etc 10 | type SpawnOptions struct { 11 | opts *C.FridaSpawnOptions 12 | } 13 | 14 | // NewSpawnOptions create new instance of SpawnOptions. 15 | func NewSpawnOptions() *SpawnOptions { 16 | opts := C.frida_spawn_options_new() 17 | return &SpawnOptions{ 18 | opts: opts, 19 | } 20 | } 21 | 22 | // SetArgv set spawns argv with the argv provided. 23 | func (s *SpawnOptions) SetArgv(argv []string) { 24 | arr, sz := stringSliceToCharArr(argv) 25 | 26 | C.frida_spawn_options_set_argv(s.opts, arr, sz) 27 | } 28 | 29 | // Argv returns argv of the spawn. 30 | func (s *SpawnOptions) Argv() []string { 31 | var count C.gint 32 | argvC := C.frida_spawn_options_get_argv(s.opts, &count) 33 | 34 | argv := cArrayToStringSlice(argvC, C.int(count)) 35 | 36 | return argv 37 | } 38 | 39 | // SetEnvp set spawns envp with the envp provided. 40 | func (s *SpawnOptions) SetEnvp(envp map[string]string) { 41 | i := 0 42 | sl := make([]string, len(envp)) 43 | 44 | for k, v := range envp { 45 | sl[i] = k + "=" + v 46 | i++ 47 | } 48 | 49 | arr, sz := stringSliceToCharArr(sl) 50 | C.frida_spawn_options_set_envp(s.opts, arr, sz) 51 | } 52 | 53 | // Envp returns envp of the spawn. 54 | func (s *SpawnOptions) Envp() []string { 55 | var count C.gint 56 | envpC := C.frida_spawn_options_get_argv(s.opts, &count) 57 | 58 | envp := cArrayToStringSlice(envpC, C.int(count)) 59 | 60 | return envp 61 | } 62 | 63 | // SetEnv set spawns env with the env provided. 64 | func (s *SpawnOptions) SetEnv(env map[string]string) { 65 | i := 0 66 | sl := make([]string, len(env)) 67 | 68 | for k, v := range env { 69 | sl[i] = k + "=" + v 70 | i++ 71 | } 72 | 73 | arr, sz := stringSliceToCharArr(sl) 74 | C.frida_spawn_options_set_env(s.opts, arr, sz) 75 | } 76 | 77 | // Env returns env of the spawn. 78 | func (s *SpawnOptions) Env() []string { 79 | var count C.gint 80 | envpC := C.frida_spawn_options_get_env(s.opts, &count) 81 | 82 | env := cArrayToStringSlice(envpC, C.int(count)) 83 | 84 | return env 85 | } 86 | 87 | // SetCwd sets current working directory (CWD) for the spawn. 88 | func (s *SpawnOptions) SetCwd(cwd string) { 89 | cwdC := C.CString(cwd) 90 | defer C.free(unsafe.Pointer(cwdC)) 91 | 92 | C.frida_spawn_options_set_cwd(s.opts, cwdC) 93 | } 94 | 95 | // Cwd returns current working directory (CWD) of the spawn. 96 | func (s *SpawnOptions) Cwd() string { 97 | return C.GoString(C.frida_spawn_options_get_cwd(s.opts)) 98 | } 99 | 100 | // SetStdio sets standard input/output of the spawn with the stdio provided. 101 | func (s *SpawnOptions) SetStdio(stdio Stdio) { 102 | C.frida_spawn_options_set_stdio(s.opts, C.FridaStdio(stdio)) 103 | } 104 | 105 | // Stdio returns spawns stdio. 106 | func (s *SpawnOptions) Stdio() Stdio { 107 | return Stdio(int(C.frida_spawn_options_get_stdio(s.opts))) 108 | } 109 | 110 | // Aux returns aux of the spawn. 111 | func (s *SpawnOptions) Aux() map[string]any { 112 | ht := C.frida_spawn_options_get_aux(s.opts) 113 | aux := gHashTableToMap(ht) 114 | return aux 115 | } 116 | 117 | // Clean will clean the resources held by the spawn options. 118 | func (s *SpawnOptions) Clean() { 119 | clean(unsafe.Pointer(s.opts), unrefFrida) 120 | } 121 | -------------------------------------------------------------------------------- /frida/types.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | //#include 4 | import "C" 5 | import "fmt" 6 | 7 | const ( 8 | defaultDeviceTimeout = 10 9 | defaultProcessTimeout = 10 10 | ) 11 | 12 | type DeviceType int 13 | 14 | const ( 15 | DeviceTypeLocal DeviceType = iota 16 | DeviceTypeRemote 17 | DeviceTypeUsb 18 | ) 19 | 20 | func (d DeviceType) String() string { 21 | return [...]string{"local", 22 | "remote", 23 | "usb"}[d] 24 | } 25 | 26 | type Realm int 27 | 28 | const ( 29 | RealmNative Realm = iota 30 | RealmEmulated 31 | ) 32 | 33 | func (r Realm) String() string { 34 | return [...]string{"native", 35 | "emulated"}[r] 36 | } 37 | 38 | type JSCompressionType int 39 | 40 | const ( 41 | JSCompressionNone JSCompressionType = iota 42 | JSCompressionTerser 43 | ) 44 | 45 | type SourceMaps int 46 | 47 | const ( 48 | SourceMapsIncluded SourceMaps = iota 49 | SourceMapsOmitted 50 | ) 51 | 52 | type ScriptRuntime int 53 | 54 | const ( 55 | ScriptRuntimeDefault ScriptRuntime = iota 56 | ScriptRuntimeQJS 57 | ScriptRuntimeV8 58 | ) 59 | 60 | func (s ScriptRuntime) String() string { 61 | return [...]string{"default", 62 | "qjs", 63 | "v8"}[s] 64 | } 65 | 66 | type Scope int 67 | 68 | const ( 69 | ScopeMinimal Scope = iota 70 | ScopeMetadata 71 | ScopeFull 72 | ) 73 | 74 | func (s Scope) String() string { 75 | return [...]string{"minimal", 76 | "metadata", 77 | "full"}[s] 78 | } 79 | 80 | type Stdio int 81 | 82 | const ( 83 | StdioInherit Stdio = iota 84 | StdioPipe 85 | ) 86 | 87 | func (s Stdio) String() string { 88 | return [...]string{"inherit", 89 | "pipe"}[s] 90 | } 91 | 92 | type Runtime int 93 | 94 | const ( 95 | RuntimeDefault Runtime = iota 96 | RuntimeQJS 97 | RuntimeV8 98 | ) 99 | 100 | func (r Runtime) String() string { 101 | return [...]string{"default", 102 | "qjs", 103 | "v8"}[r] 104 | } 105 | 106 | type ChildOrigin int 107 | 108 | const ( 109 | ChildOriginFork ChildOrigin = iota 110 | ChildOriginExec 111 | ChildOriginSpawn 112 | ) 113 | 114 | func (origin ChildOrigin) String() string { 115 | return [...]string{"fork", 116 | "exec", 117 | "spawn"}[origin] 118 | } 119 | 120 | type RelayKind int 121 | 122 | const ( 123 | RelayKindTurnUDP RelayKind = iota 124 | RelayKindTurnTCP 125 | RelayKindTurnTLS 126 | ) 127 | 128 | func (kind RelayKind) String() string { 129 | return [...]string{"turn-udp", 130 | "turn-tcp", 131 | "turn-tls"}[kind] 132 | } 133 | 134 | type SessionDetachReason int 135 | 136 | const ( 137 | SessionDetachReasonApplicationRequested SessionDetachReason = iota + 1 138 | SessionDetachReasonProcessReplaced 139 | SessionDetachReasonProcessTerminated 140 | SessionDetachReasonServerTerminated 141 | SessionDetachReasonDeviceLost 142 | ) 143 | 144 | func (reason SessionDetachReason) String() string { 145 | return [...]string{"", 146 | "application-requested", 147 | "process-replaced", 148 | "process-terminated", 149 | "server-terminated", 150 | "device-list"}[reason] 151 | } 152 | 153 | type SnapshotTransport int 154 | 155 | const ( 156 | SnapshotTransportInline SnapshotTransport = iota 157 | SnapshotTransportSharedMemory 158 | ) 159 | 160 | // Address represents structure returned by some specific signals. 161 | type Address struct { 162 | Addr string 163 | Port uint16 164 | } 165 | 166 | // String representation of Address in format ADDR:PORT 167 | func (a *Address) String() string { 168 | return fmt.Sprintf("%s:%d", a.Addr, a.Port) 169 | } 170 | -------------------------------------------------------------------------------- /frida/types_converter.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | /* 4 | #include 5 | #include 6 | 7 | static char ** new_char_array(int size) 8 | { 9 | return malloc(sizeof(char*) * size); 10 | } 11 | 12 | static void add_to_arr(char **arr, char *s, int n) 13 | { 14 | arr[n] = s; 15 | } 16 | 17 | static char * get_char_elem(char **arr, int n) 18 | { 19 | return arr[n]; 20 | } 21 | 22 | static guint8 * new_guint8_array(int size) 23 | { 24 | return malloc(sizeof(guint8) * size); 25 | } 26 | 27 | static void att_to_guint8_array(guint8 * arr, guint8 elem, int n) 28 | { 29 | arr[n] = elem; 30 | } 31 | */ 32 | import "C" 33 | 34 | func getCharArrayElement(arr **C.char, n int) *C.char { 35 | elem := C.get_char_elem(arr, C.int(n)) 36 | return elem 37 | } 38 | 39 | func stringSliceToCharArr(ss []string) (**C.char, C.int) { 40 | arr := C.new_char_array(C.int(len(ss))) 41 | 42 | for i, s := range ss { 43 | C.add_to_arr(arr, C.CString(s), C.int(i)) 44 | } 45 | 46 | return arr, C.int(len(ss)) 47 | } 48 | 49 | func goBytesToGBytes(bts []byte) *C.GBytes { 50 | arr := C.new_guint8_array(C.int(len(bts))) 51 | 52 | for i, bt := range bts { 53 | C.att_to_guint8_array(arr, C.guint8(bt), C.int(i)) 54 | } 55 | 56 | gBytes := C.g_bytes_new((C.gconstpointer)(arr), C.gsize(len(bts))) 57 | return gBytes 58 | } 59 | 60 | func cArrayToStringSlice(arr **C.char, length C.int) []string { 61 | s := make([]string, int(length)) 62 | 63 | for i := 0; i < int(length); i++ { 64 | elem := C.get_char_elem(arr, C.int(i)) 65 | s[i] = C.GoString(elem) 66 | } 67 | 68 | return s 69 | } 70 | -------------------------------------------------------------------------------- /frida/unmarshaller.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static char * get_gvalue_gtype(GValue * val) { 10 | return (char*)(G_VALUE_TYPE_NAME(val)); 11 | } 12 | 13 | static int get_in_port(struct sockaddr *sa) 14 | { 15 | in_port_t port; 16 | if (sa->sa_family == AF_INET) 17 | port = (((struct sockaddr_in*)sa)->sin_port); 18 | 19 | port = (((struct sockaddr_in6*)sa)->sin6_port); 20 | 21 | return (int)port; 22 | } 23 | 24 | static char * get_ip_str(const struct sockaddr *sa, size_t maxlen) 25 | { 26 | char * dest = malloc(sizeof(char) * maxlen); 27 | switch(sa->sa_family) { 28 | case AF_INET: 29 | inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), 30 | dest, maxlen); 31 | break; 32 | 33 | case AF_INET6: 34 | inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), 35 | dest, maxlen); 36 | break; 37 | 38 | default: 39 | strncpy(dest, "Unknown AF", maxlen); 40 | return NULL; 41 | } 42 | 43 | return dest; 44 | } 45 | 46 | static struct sockaddr * new_sockaddr() { 47 | struct sockaddr * addr = malloc(sizeof(struct sockaddr)); 48 | return addr; 49 | } 50 | */ 51 | import "C" 52 | import ( 53 | "fmt" 54 | "unsafe" 55 | ) 56 | 57 | type gTypeName string 58 | 59 | const ( 60 | gchararray gTypeName = "gchararray" 61 | gBytes gTypeName = "GBytes" 62 | fridaCrash gTypeName = "FridaCrash" 63 | fridaSessionDetachReason gTypeName = "FridaSessionDetachReason" 64 | fridaChild gTypeName = "FridaChild" 65 | fridaDevice gTypeName = "FridaDevice" 66 | fridaApplication gTypeName = "FridaApplication" 67 | guint gTypeName = "guint" 68 | gint gTypeName = "gint" 69 | gFileMonitorEvent gTypeName = "GFileMonitorEvent" 70 | gSocketAddress gTypeName = "GSocketAddress" 71 | gVariant gTypeName = "GVariant" 72 | ) 73 | 74 | type unmarshallerFunc func(val *C.GValue) any 75 | 76 | var gTypeString = map[gTypeName]unmarshallerFunc{ 77 | gchararray: getString, 78 | gBytes: getGBytesV, 79 | fridaCrash: getFridaCrash, 80 | fridaSessionDetachReason: getFridaSessionDetachReason, 81 | fridaChild: getFridaChild, 82 | fridaDevice: getFridaDevice, 83 | fridaApplication: getFridaApplication, 84 | guint: getInt, 85 | gint: getInt, 86 | gFileMonitorEvent: getFm, 87 | gSocketAddress: getGSocketAddress, 88 | gVariant: getGVariant, 89 | } 90 | 91 | // GValueToGo is the function that is called upon unmarshalling glib values 92 | // into go corresponding ones. 93 | func GValueToGo(val *C.GValue) any { 94 | gt := C.get_gvalue_gtype(val) 95 | cgt := C.GoString(gt) 96 | 97 | f, ok := gTypeString[gTypeName(cgt)] 98 | if !ok { 99 | return fmt.Sprintf("%s type is not implemented, please file an issue", cgt) 100 | } 101 | 102 | return f(val) 103 | } 104 | 105 | func getString(val *C.GValue) any { 106 | cc := C.g_value_get_string(val) 107 | return C.GoString(cc) 108 | } 109 | 110 | func getGBytesV(val *C.GValue) any { 111 | if val != nil { 112 | obj := (*C.GBytes)(C.g_value_get_object(val)) 113 | return getGBytes(obj) 114 | } 115 | return []byte{} 116 | } 117 | 118 | func getGBytes(obj *C.GBytes) []byte { 119 | if obj != nil { 120 | bytesC := (*C.guint8)(C.g_bytes_get_data(obj, nil)) 121 | sz := C.g_bytes_get_size(obj) 122 | 123 | return C.GoBytes(unsafe.Pointer(bytesC), C.int(sz)) 124 | } 125 | return []byte{} 126 | } 127 | 128 | func getFridaCrash(val *C.GValue) any { 129 | crash := (*C.FridaCrash)(C.g_value_get_object(val)) 130 | 131 | return &Crash{ 132 | crash: crash, 133 | } 134 | } 135 | 136 | func getFridaSessionDetachReason(val *C.GValue) any { 137 | reason := C.g_value_get_int(val) 138 | return SessionDetachReason(int(reason)) 139 | } 140 | 141 | func getFridaChild(val *C.GValue) any { 142 | child := (*C.FridaChild)(C.g_value_get_object(val)) 143 | 144 | return &Child{ 145 | child: child, 146 | } 147 | } 148 | 149 | func getFridaDevice(val *C.GValue) any { 150 | dev := (*C.FridaDevice)(C.g_value_get_object(val)) 151 | 152 | return &Device{ 153 | device: dev, 154 | } 155 | } 156 | 157 | func getFridaApplication(val *C.GValue) any { 158 | app := (*C.FridaApplication)(C.g_value_get_object(val)) 159 | 160 | return &Application{ 161 | application: app, 162 | } 163 | } 164 | 165 | func getInt(val *C.GValue) any { 166 | v := C.g_value_get_int(val) 167 | return int(v) 168 | } 169 | 170 | func getFm(val *C.GValue) any { 171 | v := C.int(C.g_value_get_int(val)) 172 | 173 | vals := map[int]string{ 174 | 0: "changed", 175 | 1: "changes-done-hint", 176 | 2: "deleted", 177 | 3: "created", 178 | 4: "attribute-changed", 179 | 5: "pre-mount", 180 | 6: "unmounted", 181 | 7: "moved", 182 | } 183 | 184 | return vals[int(v)] 185 | } 186 | 187 | func getGSocketAddress(val *C.GValue) any { 188 | obj := (*C.GSocketAddress)(C.g_value_get_object(val)) 189 | sz := C.g_socket_address_get_native_size(obj) 190 | dest := C.new_sockaddr() 191 | var err *C.GError 192 | C.g_socket_address_to_native(obj, (C.gpointer)(dest), C.gsize(sz), &err) 193 | if err != nil { 194 | panic(err) 195 | } 196 | 197 | s := C.get_ip_str(dest, C.size_t(sz)) 198 | port := C.get_in_port(dest) 199 | 200 | return &Address{ 201 | Addr: C.GoString(s), 202 | Port: uint16(port), 203 | } 204 | } 205 | 206 | func getGVariant(val *C.GValue) any { 207 | v := C.g_value_get_variant(val) 208 | return gVariantToGo(v) 209 | } 210 | -------------------------------------------------------------------------------- /frida/variants_to_go.go: -------------------------------------------------------------------------------- 1 | package frida 2 | 3 | /* 4 | #include 5 | 6 | extern void getSVArray(gchar*,GVariant*,char*); 7 | extern void getASVArray(gchar*,GVariant*,char*); 8 | extern void populateSliceLoop(GVariant*,char*); 9 | 10 | static void loop_simple_array(GVariant ** variant, char * fmt, char * slc) 11 | { 12 | GVariantIter iter; 13 | GVariant * v; 14 | 15 | g_variant_iter_init(&iter, *variant); 16 | while(g_variant_iter_loop(&iter, fmt, &v)) 17 | populateSliceLoop(v, slc); 18 | } 19 | 20 | static void iter_array_of_dicts(GVariant *var, char *data) 21 | { 22 | GVariantIter iter; 23 | GVariant *value; 24 | gchar *key; 25 | 26 | g_variant_iter_init (&iter, var); 27 | while (g_variant_iter_loop (&iter, "{sv}", &key, &value)) 28 | { 29 | getSVArray(key, value, data); 30 | } 31 | } 32 | 33 | static void iter_double_array_of_dicts(GVariant *var, char *data) 34 | { 35 | GVariantIter iter1; 36 | GVariantIter *iter2; 37 | 38 | g_variant_iter_init(&iter1, var); 39 | while (g_variant_iter_loop (&iter1, "a{sv}", &iter2)) { 40 | GVariant *val; 41 | gchar *key; 42 | 43 | while (g_variant_iter_loop(iter2, "{sv}", &key, &val)) { 44 | gchar * tp; 45 | tp = (char*)g_variant_get_type_string(val); 46 | getASVArray(key, val, data); 47 | } 48 | } 49 | } 50 | 51 | static char* read_byte_array(GVariant *variant, int * n_elements) 52 | { 53 | guint8 * array = NULL; 54 | 55 | array = g_variant_get_fixed_array (variant, 56 | (gsize *)n_elements, 57 | sizeof(guint8)); 58 | return (char*)array; 59 | } 60 | */ 61 | import "C" 62 | import ( 63 | "fmt" 64 | "unsafe" 65 | ) 66 | 67 | type variantSlice struct { 68 | s []*C.GVariant 69 | } 70 | 71 | type genericMap struct { 72 | m map[string]any 73 | } 74 | 75 | //export getSVArray 76 | func getSVArray(key *C.gchar, variant *C.GVariant, aData *C.char) { 77 | k := C.GoString((*C.char)(key)) 78 | v := gVariantToGo(variant) 79 | mp := (*genericMap)(unsafe.Pointer(aData)) 80 | mp.m[k] = v 81 | } 82 | 83 | //export getASVArray 84 | func getASVArray(key *C.gchar, variant *C.GVariant, mData *C.char) { 85 | keyGo := C.GoString(key) 86 | mp := (*genericMap)(unsafe.Pointer(mData)) 87 | mp.m[keyGo] = gVariantToGo(variant) 88 | } 89 | 90 | //export populateSliceLoop 91 | func populateSliceLoop(variant *C.GVariant, slc *C.char) { 92 | s := (*variantSlice)(unsafe.Pointer(slc)) 93 | s.s = append(s.s, variant) 94 | } 95 | 96 | // gVariantToGo converts GVariant to corresponding go type 97 | func gVariantToGo(variant *C.GVariant) any { 98 | variantType := getVariantStringFormat(variant) 99 | 100 | switch variantType { 101 | case "s": 102 | return stringFromVariant(variant) 103 | case "b": 104 | return boolFromVariant(variant) 105 | case "x": 106 | return int64FromVariant(variant) 107 | case "v": 108 | v := C.g_variant_get_variant(variant) 109 | return gVariantToGo(v) 110 | case "a{sv}": 111 | mp := make(map[string]any) 112 | gm := genericMap{ 113 | m: mp, 114 | } 115 | C.iter_array_of_dicts(variant, (*C.char)(unsafe.Pointer(&gm))) 116 | return gm.m 117 | case "av": 118 | s := variantSlice{} 119 | C.loop_simple_array(&variant, C.CString("v"), (*C.char)(unsafe.Pointer(&s))) 120 | arr := make([]any, len(s.s)) 121 | for i, elem := range s.s { 122 | arr[i] = gVariantToGo(elem) 123 | } 124 | return arr 125 | case "aa{sv}": 126 | mp := make(map[string]any) 127 | gm := genericMap{ 128 | m: mp, 129 | } 130 | C.iter_double_array_of_dicts(variant, (*C.char)(unsafe.Pointer(&gm))) 131 | return gm.m 132 | case "ay": // array of bytes 133 | var nElements C.int 134 | cBytes := C.read_byte_array(variant, &nElements) 135 | return C.GoBytes(unsafe.Pointer(cBytes), nElements) 136 | default: 137 | return fmt.Sprintf("type \"%s\" not implemented", variantType) 138 | } 139 | } 140 | 141 | // getVariantStringFormat returns underlying variant type 142 | func getVariantStringFormat(variant *C.GVariant) string { 143 | variantString := "" 144 | if variant != nil { 145 | variantType := C.g_variant_get_type_string(variant) 146 | variantString = C.GoString((*C.char)(variantType)) 147 | } 148 | return variantString 149 | } 150 | 151 | // stringFromVariant extracts string from GVariant 152 | func stringFromVariant(variant *C.GVariant) string { 153 | var sz C.gsize 154 | return C.GoString(C.g_variant_get_string(variant, &sz)) 155 | } 156 | 157 | // stringFromVariant extracts string from GVariant 158 | func boolFromVariant(variant *C.GVariant) bool { 159 | val := C.g_variant_get_boolean(variant) 160 | return int(val) != 0 161 | } 162 | 163 | // int64FromVariant extracts int64 from GVariant 164 | func int64FromVariant(variant *C.GVariant) int64 { 165 | val := C.g_variant_get_int64(variant) 166 | return int64(val) 167 | } 168 | 169 | // gPointerToGo converts gpointer to corresponding go type 170 | func gPointerToGo(ptr C.gpointer) any { 171 | return gVariantToGo((*C.GVariant)(ptr)) 172 | } 173 | 174 | // gHashTableToMap converts GHashTable to go map 175 | func gHashTableToMap(ht *C.GHashTable) map[string]any { 176 | iter := C.GHashTableIter{} 177 | var key C.gpointer 178 | var val C.gpointer 179 | 180 | C.g_hash_table_iter_init(&iter, ht) 181 | 182 | hSize := int(C.g_hash_table_size(ht)) 183 | var data map[string]any 184 | 185 | if hSize >= 1 { 186 | data = make(map[string]any, hSize) 187 | nx := 1 188 | for nx == 1 { 189 | nx = int(C.g_hash_table_iter_next(&iter, &key, &val)) 190 | 191 | keyGo := C.GoString((*C.char)(unsafe.Pointer(key))) 192 | valGo := gPointerToGo(val) 193 | 194 | data[keyGo] = valGo 195 | } 196 | } 197 | 198 | return data 199 | } 200 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/frida/frida-go 2 | 3 | go 1.19 4 | 5 | require github.com/google/uuid v1.6.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 2 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 3 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 4 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 5 | --------------------------------------------------------------------------------