Functions

Prerequisites


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:

APIDescription
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])
ParameterTypeDescription
addressNativePointerAddress of the function to call
returnTypestringReturn type: 'void', 'int', 'pointer', 'uint8', …
argTypesstring[]Ordered list of parameter types
abi (optional)stringCalling 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:

APIReturnsDescription
Memory.allocUtf8String(str)NativePointerAllocates and writes a null-terminated UTF-8 string; lifetime tied to returned pointer
Memory.allocAnsiString(str)NativePointerANSI/Windows variant
Memory.alloc(size)NativePointerAllocates size bytes of zeroed memory aligned to page size
Memory.protect(addr, size, prot)boolChange 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)

OffsetBytesFieldExample value
002 00sin_family (AF_INET = 2)0x0002
213 88sin_port (5000 decimal)0x1388
4–77F 00 00 01sin_addr (IPv4, network order)127.0.0.1
8–1500 00 … 00paddingzeroes

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:

  1. Memory.alloc(16) — allocates 16 bytes for the fake sockaddr_in struct
  2. st.writeByteArray([...]) — writes the struct bytes directly (port 0x1389 = 5001, IP 127.0.0.1)
  3. Module.getGlobalExportByName('connect') — resolves connect() without needing its address or module name
  4. args[1] = st — replaces the struct sockaddr * argument with the injected struct pointer
  5. retval.replace(0) (commented out) — demonstrates how to override the return value in onLeave

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

MethodSignatureDescription
attachInterceptor.attach(target, callbacks [, data])Hook a function; callbacks: { onEnter(args), onLeave(retval) }
detachAllInterceptor.detachAll()Remove all active hooks
replaceInterceptor.replace(target, replacement)Replace function entirely with a NativeCallback
revertInterceptor.revert(target)Undo a replace()

NativeFunction

1new NativeFunction(address, returnType, argTypes [, options])

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

MethodDescription
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

MethodDescription
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

MethodDescription
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}