Functions
Prerequisites
- Frida Python bindings installed (
pip install frida-tools) - Target process running and its PID or name known
- Function address available (printed by target, or resolved via
Module.getGlobalExportByName()) - Platform: Linux/macOS (examples use x86-64; concepts apply cross-platform)
Experiment 1 — Integer Argument Instrumentation
Target program: hello.c
1#include <stdio.h>
2#include <unistd.h>
3
4void
5f (int n)
6{
7 printf ("Number: %d\n", n);
8}
9
10int
11main (int argc,
12 char * argv[])
13{
14 int i = 0;
15
16 printf ("f() is at %p\n", f);
17
18 while (1)
19 {
20 f (i++);
21 sleep (1);
22 }
23}
1gcc -Wall hello.c -o hello
2./hello
3# Output: f() is at 0x400544 (address varies)
The address printed by f() is at ... is passed as a hex argument to all instrumentation scripts below.
1a. Hook a function — inspect arguments
hook.py — attaches to the process and reads args[0] on each call:
1import frida
2import sys
3
4session = frida.attach("hello")
5script = session.create_script("""
6Interceptor.attach(ptr("%s"), {
7 onEnter(args) {
8 send(args[0].toInt32());
9 }
10});
11""" % int(sys.argv[1], 16))
12def on_message(message, data):
13 print(message)
14script.on('message', on_message)
15script.load()
16sys.stdin.read()
Usage:
1python hook.py 0x400544
Key APIs:
| API | Description |
|---|---|
frida.attach(name_or_pid) | Attach to a running process by name or PID |
session.create_script(js_src) | Compile a JavaScript instrumentation script |
script.on('message', cb) | Register Python callback for send() calls |
script.load() | Inject the script into the target process |
Interceptor.attach(ptr, callbacks) | Hook a function at the given native pointer |
args[N].toInt32() | Read argument N as a signed 32-bit integer |
send(value) | Transmit a value from the JS agent to the Python host |
onEnter / onLeave callbacks:
onEnter(args) — called before the function executes; args is NativePointer[]
onLeave(retval) — called after the function returns; retval is NativePointer
1b. Modify function arguments
modify.py — replaces args[0] with 1337 before every call:
1import frida
2import sys
3
4session = frida.attach("hello")
5script = session.create_script("""
6Interceptor.attach(ptr("%s"), {
7 onEnter(args) {
8 args[0] = ptr("1337");
9 }
10});
11""" % int(sys.argv[1], 16))
12script.load()
13sys.stdin.read()
Usage:
1python modify.py 0x400544
After injection the target prints Number: 1337 continuously instead of incrementing values. Pressing Ctrl-D ends the Frida session and normal behavior resumes.
Note: Assign to args[N] using ptr("integer_value") even when the parameter is an integer, not a pointer — Frida accepts this and reinterprets the bits.
1c. Call a native function directly
call.py — creates a NativeFunction wrapper and calls f(1911) three times:
1import frida
2import sys
3
4session = frida.attach("hello")
5script = session.create_script("""
6const f = new NativeFunction(ptr("%s"), 'void', ['int']);
7f(1911);
8f(1911);
9f(1911);
10""" % int(sys.argv[1], 16))
11script.load()
Usage:
1python call.py 0x400544
The target process prints Number: 1911 three extra times mixed into its regular output.
NativeFunction constructor:
1new NativeFunction(address, returnType, argTypes [, abi])
| Parameter | Type | Description |
|---|---|---|
address | NativePointer | Address of the function to call |
returnType | string | Return type: 'void', 'int', 'pointer', 'uint8', … |
argTypes | string[] | Ordered list of parameter types |
abi (optional) | string | Calling convention: 'default', 'sysv', 'win64', 'stdcall', 'thiscall', 'fastcall', … |
Supported type strings: 'void', 'pointer', 'int', 'uint', 'long', 'ulong', 'char', 'uchar', 'float', 'double', 'int8'–'int64', 'uint8'–'uint64', 'bool', 'size_t'
Experiment 2 — String Argument Instrumentation
Target program: hi.c
1#include <stdio.h>
2#include <unistd.h>
3
4int
5f (const char * s)
6{
7 printf ("String: %s\n", s);
8 return 0;
9}
10
11int
12main (int argc,
13 char * argv[])
14{
15 const char * s = "Testing!";
16
17 printf ("f() is at %p\n", f);
18 printf ("s is at %p\n", s);
19
20 while (1)
21 {
22 f (s);
23 sleep (1);
24 }
25}
1gcc -Wall hi.c -o hi
2./hi
3# Output: f() is at 0x400544
4# s is at 0x400600
2a. Inject a string and call a function
stringhook.py — allocates a string in target memory and calls f() with it:
1import frida
2import sys
3
4session = frida.attach("hi")
5script = session.create_script("""
6const st = Memory.allocUtf8String("TESTMEPLZ!");
7const f = new NativeFunction(ptr("%s"), 'int', ['pointer']);
8 // In NativeFunction param 2 is the return value type,
9 // and param 3 is an array of input types
10f(st);
11""" % int(sys.argv[1], 16))
12def on_message(message, data):
13 print(message)
14script.on('message', on_message)
15script.load()
Usage:
1python stringhook.py 0x400544
The target prints String: TESTMEPLZ! once, in addition to its normal String: Testing! output.
Memory allocation APIs:
| API | Returns | Description |
|---|---|---|
Memory.allocUtf8String(str) | NativePointer | Allocates and writes a null-terminated UTF-8 string; lifetime tied to returned pointer |
Memory.allocAnsiString(str) | NativePointer | ANSI/Windows variant |
Memory.alloc(size) | NativePointer | Allocates size bytes of zeroed memory aligned to page size |
Memory.protect(addr, size, prot) | bool | Change memory protection; prot is a string like 'rwx' |
Pointer type in NativeFunction: Use 'pointer' for any pointer parameter (char *, void *, struct *, etc.).
Experiment 3 — Struct Injection (sockaddr_in Hijack)
Target program: client.c
A TCP client that connects to a server at a user-supplied IP address on port 5000.
1#include <arpa/inet.h>
2#include <errno.h>
3#include <netdb.h>
4#include <netinet/in.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <sys/socket.h>
9#include <sys/types.h>
10#include <unistd.h>
11
12int
13main (int argc,
14 char * argv[])
15{
16 int sock_fd, i, n;
17 struct sockaddr_in serv_addr;
18 unsigned char * b;
19 const char * message;
20 char recv_buf[1024];
21
22 if (argc != 2)
23 {
24 fprintf (stderr, "Usage: %s <ip of server>\n", argv[0]);
25 return 1;
26 }
27
28 printf ("connect() is at: %p\n", connect);
29
30 if ((sock_fd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
31 {
32 perror ("Unable to create socket");
33 return 1;
34 }
35
36 bzero (&serv_addr, sizeof (serv_addr));
37
38 serv_addr.sin_family = AF_INET;
39 serv_addr.sin_port = htons (5000);
40
41 if (inet_pton (AF_INET, argv[1], &serv_addr.sin_addr) <= 0)
42 {
43 fprintf (stderr, "Unable to parse IP address\n");
44 return 1;
45 }
46 printf ("\nHere's the serv_addr buffer:\n");
47 b = (unsigned char *) &serv_addr;
48 for (i = 0; i != sizeof (serv_addr); i++)
49 printf ("%s%02x", (i != 0) ? " " : "", b[i]);
50
51 printf ("\n\nPress ENTER key to Continue\n");
52 while (getchar () == EOF && ferror (stdin) && errno == EINTR)
53 ;
54
55 if (connect (sock_fd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0)
56 {
57 perror ("Unable to connect");
58 return 1;
59 }
60
61 message = "Hello there!";
62 if (send (sock_fd, message, strlen (message), 0) < 0)
63 {
64 perror ("Unable to send");
65 return 1;
66 }
67
68 while (1)
69 {
70 n = recv (sock_fd, recv_buf, sizeof (recv_buf) - 1, 0);
71 if (n == -1 && errno == EINTR)
72 continue;
73 else if (n <= 0)
74 break;
75 recv_buf[n] = 0;
76
77 fputs (recv_buf, stdout);
78 }
79
80 if (n < 0)
81 {
82 perror ("Unable to read");
83 }
84
85 return 0;
86}
sockaddr_in byte layout (16 bytes, big-endian fields)
| Offset | Bytes | Field | Example value |
|---|---|---|---|
| 0 | 02 00 | sin_family (AF_INET = 2) | 0x0002 |
| 2 | 13 88 | sin_port (5000 decimal) | 0x1388 |
| 4–7 | 7F 00 00 01 | sin_addr (IPv4, network order) | 127.0.0.1 |
| 8–15 | 00 00 … 00 | padding | zeroes |
To redirect to port 5001 (0x1389) on 127.0.0.1:
02 00 13 89 7F 00 00 01 30 30 30 30 30 30 30 30
3a. Intercept connect() and inject a malicious struct
struct_mod.py:
1import frida
2import sys
3
4session = frida.attach("client")
5script = session.create_script("""
6// First, let's give ourselves a bit of memory to put our struct in:
7send('Allocating memory and writing bytes...');
8const st = Memory.alloc(16);
9// Now we need to fill it - this is a bit blunt, but works...
10st.writeByteArray([0x02, 0x00, 0x13, 0x89, 0x7F, 0x00, 0x00, 0x01, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]);
11// Module.getGlobalExportByName() can find functions without knowing the source
12// module, but it's slower, especially over large binaries! YMMV...
13Interceptor.attach(Module.getGlobalExportByName('connect'), {
14 onEnter(args) {
15 send('Injecting malicious byte array:');
16 args[1] = st;
17 }
18 //, onLeave(retval) {
19 // retval.replace(0); // Use this to manipulate the return value
20 //}
21});
22""")
23
24def on_message(message, data):
25 if message['type'] == 'error':
26 print("[!] " + message['stack'])
27 elif message['type'] == 'send':
28 print("[i] " + message['payload'])
29 else:
30 print(message)
31script.on('message', on_message)
32script.load()
33sys.stdin.read()
How it works:
Memory.alloc(16)— allocates 16 bytes for the fakesockaddr_instructst.writeByteArray([...])— writes the struct bytes directly (port0x1389= 5001, IP127.0.0.1)Module.getGlobalExportByName('connect')— resolvesconnect()without needing its address or module nameargs[1] = st— replaces thestruct sockaddr *argument with the injected struct pointerretval.replace(0)(commented out) — demonstrates how to override the return value inonLeave
To test:
1# Terminal 1: listen on port 5000 (original target)
2nc -l 5000
3
4# Terminal 2: listen on port 5001 (redirected target)
5nc -l 5001
6
7# Terminal 3: run the Frida script
8python struct_mod.py
9
10# Terminal 4: run the client (connects to 127.0.0.1)
11./client 127.0.0.1
12# Press ENTER when prompted
Connection lands on port 5001 instead of 5000.
API Quick Reference
Interceptor
| Method | Signature | Description |
|---|---|---|
attach | Interceptor.attach(target, callbacks [, data]) | Hook a function; callbacks: { onEnter(args), onLeave(retval) } |
detachAll | Interceptor.detachAll() | Remove all active hooks |
replace | Interceptor.replace(target, replacement) | Replace function entirely with a NativeCallback |
revert | Interceptor.revert(target) | Undo a replace() |
NativeFunction
1new NativeFunction(address, returnType, argTypes [, options])
options.abi— calling convention ('default'on most platforms)options.scheduling—'cooperative'(default) or'exclusive'(holds runtime lock)options.exceptions—'steal'(default) or'propagate'
NativeCallback
Used with Interceptor.replace() to implement a function in JavaScript:
1new NativeCallback(fn, returnType, argTypes [, abi])
1// Example: replace connect() with a no-op that always returns 0
2Interceptor.replace(
3 Module.getGlobalExportByName('connect'),
4 new NativeCallback(function (sockfd, addr, addrlen) {
5 return 0;
6 }, 'int', ['int', 'pointer', 'uint32'])
7);
Memory
| Method | Description |
|---|---|
Memory.alloc(size) | Allocate size bytes (zeroed, page-aligned lifetime) |
Memory.allocUtf8String(str) | Allocate null-terminated UTF-8 string |
Memory.allocAnsiString(str) | Allocate null-terminated ANSI string (Windows) |
Memory.copy(dst, src, n) | Copy n bytes between native pointers |
Memory.protect(addr, size, prot) | Set protection flags (e.g. 'rwx', 'r--') |
Module
| Method | Description |
|---|---|
Module.getGlobalExportByName(name) | Search all loaded modules for an export by name; slower than per-module lookup |
Module.getExportByName(module, name) | Faster lookup scoped to a specific module (pass null to search all) |
Module.findBaseAddress(name) | Base load address of a module |
Module.load(path) | Load a module into the process |
NativePointer read/write methods
| Method | Description |
|---|---|
ptr.readPointer() | Read a pointer-sized value |
ptr.readS8() / ptr.readU8() | Read signed/unsigned 8-bit int |
ptr.readS16() / ptr.readU16() | Read 16-bit int |
ptr.readS32() / ptr.readU32() | Read 32-bit int |
ptr.readS64() / ptr.readU64() | Read 64-bit int |
ptr.readFloat() / ptr.readDouble() | Read float/double |
ptr.readByteArray(n) | Read n bytes as ArrayBuffer |
ptr.readUtf8String([len]) | Read null-terminated (or length-bounded) UTF-8 string |
ptr.readAnsiString([len]) | Read ANSI string (Windows) |
ptr.writeByteArray(arr) | Write array of byte values |
ptr.writeUtf8String(str) | Write UTF-8 string (must have enough allocated space) |
ptr.toInt32() | Interpret pointer bits as signed 32-bit integer |
ptr.toUInt32() | Interpret pointer bits as unsigned 32-bit integer |
ptr.toString([radix]) | String representation (default hex) |
ptr.add(offset) | Return new pointer offset by offset bytes |
Common Patterns
Find a function by export name (no hardcoded address)
1const connectPtr = Module.getGlobalExportByName('connect');
2Interceptor.attach(connectPtr, { onEnter(args) { /* ... */ } });
Read and modify a struct field in onEnter
1onEnter(args) {
2 // args[1] points to struct sockaddr_in
3 const port = args[1].add(2).readU16(); // sin_port at offset 2
4 send('original port: ' + port);
5 args[1].add(2).writeU16(0x1389); // overwrite to port 5001
6}
Share state between onEnter and onLeave
1Interceptor.attach(ptr, {
2 onEnter(args) {
3 this.fd = args[0].toInt32(); // store on per-call `this`
4 },
5 onLeave(retval) {
6 send('fd=' + this.fd + ' retval=' + retval.toInt32());
7 }
8});
Override a return value
1onLeave(retval) {
2 retval.replace(0); // force the function to return 0
3}