about summary refs log tree commit diff
path: root/frida_mode/Scripting.md
diff options
context:
space:
mode:
Diffstat (limited to 'frida_mode/Scripting.md')
-rw-r--r--frida_mode/Scripting.md828
1 files changed, 719 insertions, 109 deletions
diff --git a/frida_mode/Scripting.md b/frida_mode/Scripting.md
index 8b961e18..4c6fe6b2 100644
--- a/frida_mode/Scripting.md
+++ b/frida_mode/Scripting.md
@@ -99,142 +99,752 @@ const address = module.base.add(0xdeadface);
 Afl.setPersistentAddress(address);
 ```
 
-# API
+# Persisent Hook
+A persistent hook can be implemented using a conventional shared object, sample
+source code for a hook suitable for the prototype of `LLVMFuzzerTestOneInput`
+can be found [here](hook/hook.c). This can be configured using code similar to
+the following.
+
 ```js
-/*
- * Print a message to the STDOUT. This should be preferred to
- * FRIDA's `console.log` since FRIDA will queue it's log messages.
- * If `console.log` is used in a callback in particular, then there
- * may no longer be a thread running to service this queue.
- */
-Afl.print(msg);
+const path = Afl.module.path;
+const dir = path.substring(0, path.lastIndexOf("/"));
+const mod = Module.load(`${dir}/frida_mode/build/hook.so`);
+const hook = mod.getExportByName('afl_persistent_hook');
+Afl.setPersistentHook(hook);
+```
 
-/*
- * This must always be called at the end of your script. This lets
- * FRIDA mode know that your configuration is finished and that
- * execution has reached the end of your script. Failure to call
- * this will result in a fatal error.
- */
+Alternatively, the hook can be provided by using FRIDAs built in support for `CModule`, powered by TinyCC.
+
+```js
+const cm = new CModule(`
+
+    #include <string.h>
+    #include <gum/gumdefs.h>
+
+    void afl_persistent_hook(GumCpuContext *regs, uint8_t *input_buf,
+      uint32_t input_buf_len) {
+
+      memcpy((void *)regs->rdi, input_buf, input_buf_len);
+      regs->rsi = input_buf_len;
+
+    }
+    `,
+    {
+        memcpy: Module.getExportByName(null, 'memcpy')
+    });
+Afl.setPersistentHook(cm.afl_persistent_hook);
+```
+
+# Advanced Persistence
+Consider the following target code...
+```c
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+void LLVMFuzzerTestOneInput(char *buf, int len) {
+
+  if (len < 1) return;
+  buf[len] = 0;
+
+  // we support three input cases
+  if (buf[0] == '0')
+    printf("Looks like a zero to me!\n");
+  else if (buf[0] == '1')
+    printf("Pretty sure that is a one!\n");
+  else
+    printf("Neither one or zero? How quaint!\n");
+
+}
+
+int run(char *file) {
+
+  int    fd = -1;
+  off_t  len;
+  char * buf = NULL;
+  size_t n_read;
+  int    result = -1;
+
+  do {
+
+    dprintf(STDERR_FILENO, "Running: %s\n", file);
+
+    fd = open(file, O_RDONLY);
+    if (fd < 0) {
+
+      perror("open");
+      break;
+
+    }
+
+    len = lseek(fd, 0, SEEK_END);
+    if (len < 0) {
+
+      perror("lseek (SEEK_END)");
+      break;
+
+    }
+
+    if (lseek(fd, 0, SEEK_SET) != 0) {
+
+      perror("lseek (SEEK_SET)");
+      break;
+
+    }
+
+    buf = malloc(len);
+    if (buf == NULL) {
+
+      perror("malloc");
+      break;
+
+    }
+
+    n_read = read(fd, buf, len);
+    if (n_read != len) {
+
+      perror("read");
+      break;
+
+    }
+
+    dprintf(STDERR_FILENO, "Running:    %s: (%zd bytes)\n", file, n_read);
+
+    LLVMFuzzerTestOneInput(buf, len);
+    dprintf(STDERR_FILENO, "Done:    %s: (%zd bytes)\n", file, n_read);
+
+    result = 0;
+
+  } while (false);
+
+  if (buf != NULL) { free(buf); }
+
+  if (fd != -1) { close(fd); }
+
+  return result;
+
+}
+
+void slow() {
+
+  usleep(100000);
+
+}
+
+int main(int argc, char **argv) {
+
+  if (argc != 2) { return 1; }
+  slow();
+  return run(argv[1]);
+
+}
+```
+
+FRIDA mode supports the replacement of any function, with an implementation
+generated by CModule. This allows for a bespoke harness to be written as
+follows:
+
+```
+const slow = DebugSymbol.fromName('slow').address;
+Afl.print(`slow: ${slow}`);
+
+const LLVMFuzzerTestOneInput = DebugSymbol.fromName('LLVMFuzzerTestOneInput').address;
+Afl.print(`LLVMFuzzerTestOneInput: ${LLVMFuzzerTestOneInput}`);
+
+const cm = new CModule(`
+
+    extern unsigned char * __afl_fuzz_ptr;
+    extern unsigned int * __afl_fuzz_len;
+    extern void LLVMFuzzerTestOneInput(char *buf, int len);
+
+    void slow(void) {
+
+      LLVMFuzzerTestOneInput(__afl_fuzz_ptr, *__afl_fuzz_len);
+    }
+    `,
+    {
+        LLVMFuzzerTestOneInput: LLVMFuzzerTestOneInput,
+        __afl_fuzz_ptr: Afl.getAflFuzzPtr(),
+        __afl_fuzz_len: Afl.getAflFuzzLen()
+    });
+
+Afl.setEntryPoint(cm.slow);
+Afl.setPersistentAddress(cm.slow);
+Afl.setInMemoryFuzzing();
+Interceptor.replace(slow, cm.slow);
+Afl.print("done");
 Afl.done();
+```
 
-/*
- * This function can be called within your script to cause FRIDA
- * mode to trigger a fatal error. This is useful if for example you
- * discover a problem you weren't expecting and want everything to
- * stop. The user will need to enable `AFL_DEBUG_CHILD=1` to view
- * this error message.
- */
-Afl.error();
+Here, we replace the function `slow` with our own code. This code is then
+selected as the entry point as well as the persistent loop address.
 
-/*
- * This has the same effect as setting `AFL_ENTRYPOINT`, but has the
- * convenience of allowing you to use FRIDAs APIs to determine the
- * address you would like to configure, rather than having to grep
- * the output of `readelf` or something similarly ugly. This
- * function should be called with a `NativePointer` as its
- * argument.
- */
-Afl.setEntryPoint(address);
+**WARNING** There are two key limitations in replacing a function in this way:
+- The function which is to be replaced must not be `main` this is because this
+is the point at which FRIDA mode is initialized and at the point the the JS has
+been run, the start of the `main` function has already been instrumented and
+cached.
+- The replacement function must not call itself. e.g. in this example we
+couldn't replace `LLVMFuzzerTestOneInput` and call itself.
 
-/*
- * This is equivalent to setting `AFL_FRIDA_PERSISTENT_ADDR`, again a
- * `NativePointer` should be provided as it's argument.
- */
-Afl.setPersistentAddress(address);
+# Patching
+Consider the [following](test/js/test2.c) test code...
 
+```c
 /*
- * This is equivalent to setting `AFL_FRIDA_PERSISTENT_RET`, again a
- * `NativePointer` should be provided as it's argument.
+   american fuzzy lop++ - a trivial program to test the build
+   --------------------------------------------------------
+   Originally written by Michal Zalewski
+   Copyright 2014 Google Inc. All rights reserved.
+   Copyright 2019-2020 AFLplusplus Project. All rights reserved.
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at:
+     http://www.apache.org/licenses/LICENSE-2.0
  */
-Afl.setPersistentReturn(address);
 
-/*
- * This is equivalent to setting `AFL_FRIDA_PERSISTENT_CNT`, a
- * `number` should be provided as it's argument.
- */
-Afl.setPersistentCount(count);
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+const uint32_t crc32_tab[] = {
+	0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+
+  ...
+
+	0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+uint32_t
+crc32(const void *buf, size_t size)
+{
+	const uint8_t *p = buf;
+	uint32_t crc;
+ 	crc = ~0U;
+	while (size--)
+		crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
+	return crc ^ ~0U;
+}
 
 /*
- * See `AFL_FRIDA_PERSISTENT_DEBUG`.
+ * Don't you hate those contrived examples which CRC their data. We can use
+ * FRIDA to patch this function out and always return success. Otherwise, we
+ * could change it to actually correct the checksum.
  */
-Afl.setPersistentDebug();
+int crc32_check (char * buf, int len) {
+  if (len < sizeof(uint32_t)) { return 0; }
+  uint32_t expected = *(uint32_t *)&buf[len - sizeof(uint32_t)];
+  uint32_t calculated = crc32(buf, len - sizeof(uint32_t));
+  return expected == calculated;
+}
 
 /*
- * See `AFL_FRIDA_DEBUG_MAPS`.
+ * So you've found a really boring bug in an earlier campaign which results in
+ * a NULL dereference or something like that. That bug can get in the way,
+ * causing the persistent loop to exit whenever it is triggered, and can also
+ * cloud your output unnecessarily. Again, we can use FRIDA to patch it out.
  */
-Afl.setDebugMaps();
+void some_boring_bug(char c) {
+  switch (c) {
+    case 'A'...'Z':
+    case 'a'...'z':
+      __builtin_trap();
+      break;
+  }
+}
 
-/*
- * This is equivalent to setting a value in `AFL_FRIDA_INST_RANGES`,
- * it takes as arguments a `NativePointer` and a `number`. It can be
- * called multiple times to include several ranges.
- */
-Afl.addIncludedRange(address, size);
+void LLVMFuzzerTestOneInput(char *buf, int len) {
+
+  if (!crc32_check(buf, len)) return;
+
+  some_boring_bug(buf[0]);
+
+  if (buf[0] == '0') {
+    printf("Looks like a zero to me!\n");
+  }
+  else if (buf[0] == '1') {
+    printf("Pretty sure that is a one!\n");
+  }
+  else if (buf[0] == '2') {
+    if (buf[1] == '3') {
+      if (buf[2] == '4') {
+        printf("Oh we, weren't expecting that!");
+        __builtin_trap();
+      }
+    }
+  }
+  else
+    printf("Neither one or zero? How quaint!\n");
 
-/*
- * This is equivalent to setting a value in `AFL_FRIDA_EXCLUDE_RANGES`,
- * it takes as arguments a `NativePointer` and a `number`. It can be
- * called multiple times to exclude several ranges.
- */
-Afl.addExcludedRange(address, size);
+}
 
-/*
- * See `AFL_INST_LIBS`.
- */
-Afl.setInstrumentLibraries();
+int main(int argc, char **argv) {
 
-/*
- * See `AFL_FRIDA_INST_DEBUG_FILE`. This function takes a single `string` as
- * an argument.
- */
-Afl.setInstrumentDebugFile(file);
+  int    fd = -1;
+  off_t  len;
+  char * buf = NULL;
+  size_t n_read;
+  int    result = -1;
 
-/*
- * See `AFL_FRIDA_INST_NO_PREFETCH`.
- */
-Afl.setPrefetchDisable();
+  if (argc != 2) { return 1; }
 
-/*
- * See `AFL_FRIDA_INST_NO_OPTIMIZE`
- */
-Afl.setInstrumentNoOptimize();
+  printf("Running: %s\n", argv[1]);
 
-/*
- * See `AFL_FRIDA_INST_TRACE`.
- */
-Afl.setInstrumentEnableTracing();
+  fd = open(argv[1], O_RDONLY);
+  if (fd < 0) { return 1; }
 
-/*
- * See `AFL_FRIDA_INST_TRACE_UNIQUE`.
- */
-Afl.setInstrumentTracingUnique()
+  len = lseek(fd, 0, SEEK_END);
+  if (len < 0) { return 1; }
 
-/*
- * See `AFL_FRIDA_OUTPUT_STDOUT`. This function takes a single `string` as
- * an argument.
- */
-Afl.setStdOut(file);
+  if (lseek(fd, 0, SEEK_SET) != 0) { return 1; }
 
-/*
- * See `AFL_FRIDA_OUTPUT_STDERR`. This function takes a single `string` as
- * an argument.
- */
-Afl.setStdErr(file);
+  buf = malloc(len);
+  if (buf == NULL) { return 1; }
 
-/*
- * See `AFL_FRIDA_STATS_FILE`. This function takes a single `string` as
- * an argument.
- */
-Afl.setStatsFile(file);
+  n_read = read(fd, buf, len);
+  if (n_read != len) { return 1; }
 
-/*
- * See `AFL_FRIDA_STATS_INTERVAL`. This function takes a `number` as an
- * argument
- */
-Afl.setStatsInterval(interval);
+  printf("Running:    %s: (%zd bytes)\n", argv[1], n_read);
+
+  LLVMFuzzerTestOneInput(buf, len);
+  printf("Done:    %s: (%zd bytes)\n", argv[1], n_read);
+
+  return 0;
+}
+```
+
+There are a couple of obstacles with our target application. Unlike when fuzzing
+source code, though, we can't simply edit it and recompile it. The following
+script shows how we can use the normal functionality of FRIDA to modify any
+troublesome behaviour.
+
+```js
+Afl.print('******************');
+Afl.print('* AFL FRIDA MODE *');
+Afl.print('******************');
+Afl.print('');
+
+const main = DebugSymbol.fromName('main').address;
+Afl.print(`main: ${main}`);
+Afl.setEntryPoint(main);
+Afl.setPersistentAddress(main);
+Afl.setPersistentCount(10000000);
+
+const crc32_check = DebugSymbol.fromName('crc32_check').address;
+const crc32_replacement = new NativeCallback(
+    (buf, len) => {
+        Afl.print(`len: ${len}`);
+        if (len < 4) {
+            return 0;
+        }
+
+        return 1;
+    },
+    'int',
+    ['pointer', 'int']);
+Interceptor.replace(crc32_check, crc32_replacement);
+
+const some_boring_bug = DebugSymbol.fromName('some_boring_bug').address
+const boring_replacement = new NativeCallback(
+    (c) => { },
+    'void',
+    ['char']);
+Interceptor.replace(some_boring_bug, boring_replacement);
+
+Afl.done();
+Afl.print("done");
+```
+
+# Advanced Patching
+Consider the following code fragment...
+```c
+extern void some_boring_bug2(char c);
+
+__asm__ (
+      ".text                                 \n"
+      "some_boring_bug2:                     \n"
+      ".global some_boring_bug2              \n"
+      ".type some_boring_bug2, @function     \n"
+      "mov %edi, %eax                        \n"
+      "cmp $0xb4, %al                        \n"
+      "jne ok                                \n"
+      "ud2                                   \n"
+      "ok:                                   \n"
+      "ret                                   \n");
+
+void LLVMFuzzerTestOneInput(char *buf, int len) {
+
+  ...
+
+  some_boring_bug2(buf[0]);
+
+  ...
+
+}
+```
+
+Rather than using FRIDAs `Interceptor.replace` or `Interceptor.attach` APIs, it
+is possible to apply much more fine grained modification to the target
+application by means of using the Stalker APIs.
+
+The following code locates the function of interest and patches out the UD2
+instruction signifying a crash.
+
+```js
+/* Modify the instructions */
+const some_boring_bug2 = DebugSymbol.fromName('some_boring_bug2').address
+const pid = Memory.alloc(4);
+pid.writeInt(Process.id);
+
+const cm = new CModule(`
+    #include <stdio.h>
+    #include <gum/gumstalker.h>
+
+    typedef int pid_t;
+
+    #define STDERR_FILENO 2
+    #define BORING2_LEN 10
+
+    extern int dprintf(int fd, const char *format, ...);
+    extern void some_boring_bug2(char c);
+    extern pid_t getpid(void);
+    extern pid_t pid;
+
+    gboolean js_stalker_callback(const cs_insn *insn, gboolean begin,
+        gboolean excluded, GumStalkerOutput *output)
+    {
+        pid_t my_pid = getpid();
+        GumX86Writer *cw = output->writer.x86;
+
+        if (GUM_ADDRESS(insn->address) < GUM_ADDRESS(some_boring_bug2)) {
+
+            return TRUE;
+
+        }
+
+        if (GUM_ADDRESS(insn->address) >=
+            GUM_ADDRESS(some_boring_bug2) + BORING2_LEN) {
+
+            return TRUE;
+
+        }
+
+        if (my_pid == pid) {
+
+            if (begin) {
+
+                dprintf(STDERR_FILENO, "\n> 0x%016lX: %s %s\n", insn->address,
+                        insn->mnemonic, insn->op_str);
+
+            } else {
+
+                dprintf(STDERR_FILENO, "  0x%016lX: %s %s\n", insn->address,
+                        insn->mnemonic, insn->op_str);
+
+            }
+
+        }
+
+        if (insn->id == X86_INS_UD2) {
+
+            gum_x86_writer_put_nop(cw);
+            return FALSE;
+
+        } else {
+
+            return TRUE;
+
+        }
+    }
+    `,
+    {
+        dprintf: Module.getExportByName(null, 'dprintf'),
+        getpid: Module.getExportByName(null, 'getpid'),
+        some_boring_bug2: some_boring_bug2,
+        pid: pid
+    });
+Afl.setStalkerCallback(cm.js_stalker_callback)
+Afl.setStdErr("/tmp/stderr.txt");
+```
+
+Note that you will more likely want to find the
+patch address by using:
+
+```js
+const module = Process.getModuleByName('target.exe');
+/* Hardcoded offset within the target image */
+const address = module.base.add(0xdeadface);
+```
+OR
+```
+const address = DebugSymbol.fromName("my_function").address.add(0xdeadface);
+```
+OR
+```
+const address = Module.getExportByName(null, "my_function").add(0xdeadface);
+```
+
+The function `js_stalker_callback` should return `TRUE` if the original
+instruction should be emitted in the instrumented code, or `FALSE` otherwise.
+In the example above, we can see it is replaced with a `NOP`.
+
+Lastly, note that the same callback will be called when compiling instrumented
+code both in the child of the forkserver (as it is executed) and also in the
+parent of the forserver (when prefetching is enabled) so that it can be
+inherited by the next forked child. It is **VERY** important that the same
+instructions be generated in both the parent and the child, or if prefetching is
+disabled that the same instructions are generated every time the block is
+compiled. Failure to do so will likely lead to bugs which are incredibly
+difficult to diagnose. The code above only prints the instructions when running
+in the parent process (the one provided by `Process.id` when the JS script is
+executed).
+
+# API
+```js
+class Afl {
+
+  /**
+   * Field containing the `Module` object for `afl-frida-trace.so` (the FRIDA mode
+   * implementation).
+   */
+  public static module: Module = Process.getModuleByName("afl-frida-trace.so");
+
+  /**
+   * This is equivalent to setting a value in `AFL_FRIDA_EXCLUDE_RANGES`,
+   * it takes as arguments a `NativePointer` and a `number`. It can be
+   * called multiple times to exclude several ranges.
+   */
+  public static addExcludedRange(addressess: NativePointer, size: number): void {
+    Afl.jsApiAddExcludeRange(addressess, size);
+  }
+
+  /**
+   * This is equivalent to setting a value in `AFL_FRIDA_INST_RANGES`,
+   * it takes as arguments a `NativePointer` and a `number`. It can be
+   * called multiple times to include several ranges.
+   */
+  public static addIncludedRange(addressess: NativePointer, size: number): void {
+    Afl.jsApiAddIncludeRange(addressess, size);
+  }
+
+  /**
+   * This must always be called at the end of your script. This lets
+   * FRIDA mode know that your configuration is finished and that
+   * execution has reached the end of your script. Failure to call
+   * this will result in a fatal error.
+   */
+  public static done(): void {
+    Afl.jsApiDone();
+  }
+
+  /**
+   * This function can be called within your script to cause FRIDA
+   * mode to trigger a fatal error. This is useful if for example you
+   * discover a problem you weren't expecting and want everything to
+   * stop. The user will need to enable `AFL_DEBUG_CHILD=1` to view
+   * this error message.
+   */
+  public static error(msg: string): void {
+    const buf = Memory.allocUtf8String(msg);
+    Afl.jsApiError(buf);
+  }
+
+  /**
+   * Function used to provide access to `__afl_fuzz_ptr`, which contains the length of
+   * fuzzing data when using in-memory test case fuzzing.
+   */
+  public static getAflFuzzLen(): NativePointer {
+
+    return Afl.jsApiGetSymbol("__afl_fuzz_len");
+  }
+
+  /**
+   * Function used to provide access to `__afl_fuzz_ptr`, which contains the fuzzing
+   * data when using in-memory test case fuzzing.
+   */
+  public static getAflFuzzPtr(): NativePointer {
+
+    return Afl.jsApiGetSymbol("__afl_fuzz_ptr");
+  }
+
+  /**
+   * Print a message to the STDOUT. This should be preferred to
+   * FRIDA's `console.log` since FRIDA will queue it's log messages.
+   * If `console.log` is used in a callback in particular, then there
+   * may no longer be a thread running to service this queue.
+   */
+  public static print(msg: string): void {
+    const STDOUT_FILENO = 2;
+    const log = `${msg}\n`;
+    const buf = Memory.allocUtf8String(log);
+    Afl.jsApiWrite(STDOUT_FILENO, buf, log.length);
+  }
+
+  /**
+   * See `AFL_FRIDA_DEBUG_MAPS`.
+   */
+  public static setDebugMaps(): void {
+    Afl.jsApiSetDebugMaps();
+  }
+
+  /**
+   * This has the same effect as setting `AFL_ENTRYPOINT`, but has the
+   * convenience of allowing you to use FRIDAs APIs to determine the
+   * address you would like to configure, rather than having to grep
+   * the output of `readelf` or something similarly ugly. This
+   * function should be called with a `NativePointer` as its
+   * argument.
+   */
+  public static setEntryPoint(address: NativePointer): void {
+    Afl.jsApiSetEntryPoint(address);
+  }
+
+  /**
+   * Function used to enable in-memory test cases for fuzzing.
+   */
+  public static setInMemoryFuzzing(): void {
+    Afl.jsApiAflSharedMemFuzzing.writeInt(1);
+  }
+
+  /**
+   * See `AFL_FRIDA_INST_DEBUG_FILE`. This function takes a single `string` as
+   * an argument.
+   */
+  public static setInstrumentDebugFile(file: string): void {
+    const buf = Memory.allocUtf8String(file);
+    Afl.jsApiSetInstrumentDebugFile(buf);
+  }
+
+  /**
+   * See `AFL_FRIDA_INST_TRACE`.
+   */
+  public static setInstrumentEnableTracing(): void {
+    Afl.jsApiSetInstrumentTrace();
+  }
+
+  /**
+   * See `AFL_INST_LIBS`.
+   */
+  public static setInstrumentLibraries(): void {
+    Afl.jsApiSetInstrumentLibraries();
+  }
+
+  /**
+   * See `AFL_FRIDA_INST_NO_OPTIMIZE`
+   */
+  public static setInstrumentNoOptimize(): void {
+    Afl.jsApiSetInstrumentNoOptimize();
+  }
+
+  /**
+   * See `AFL_FRIDA_INST_TRACE_UNIQUE`.
+   */
+  public static setInstrumentTracingUnique(): void {
+    Afl.jsApiSetInstrumentTraceUnique();
+  }
+
+  /**
+   * This is equivalent to setting `AFL_FRIDA_PERSISTENT_ADDR`, again a
+   * `NativePointer` should be provided as it's argument.
+   */
+  public static setPersistentAddress(address: NativePointer): void {
+    Afl.jsApiSetPersistentAddress(address);
+  }
+
+  /**
+   * This is equivalent to setting `AFL_FRIDA_PERSISTENT_CNT`, a
+   * `number` should be provided as it's argument.
+   */
+  public static setPersistentCount(count: number): void {
+    Afl.jsApiSetPersistentCount(count);
+  }
+
+  /**
+   * See `AFL_FRIDA_PERSISTENT_DEBUG`.
+   */
+  public static setPersistentDebug(): void {
+    Afl.jsApiSetPersistentDebug();
+  }
+
+  /**
+   * See `AFL_FRIDA_PERSISTENT_ADDR`. This function takes a NativePointer as an
+   * argument. See above for examples of use.
+   */
+  public static setPersistentHook(address: NativePointer): void {
+    Afl.jsApiSetPersistentHook(address);
+  }
+
+  /**
+   * This is equivalent to setting `AFL_FRIDA_PERSISTENT_RET`, again a
+   * `NativePointer` should be provided as it's argument.
+   */
+  public static setPersistentReturn(address: NativePointer): void {
+    Afl.jsApiSetPersistentReturn(address);
+  }
+
+  /**
+   * See `AFL_FRIDA_INST_NO_PREFETCH`.
+   */
+  public static setPrefetchDisable(): void {
+    Afl.jsApiSetPrefetchDisable();
+  }
+
+  /*
+   * Set a function to be called for each instruction which is instrumented
+   * by AFL FRIDA mode.
+   */
+  public static setStalkerCallback(callback: NativePointer): void {
+    Afl.jsApiSetStalkerCallback(callback);
+  }
+
+  /**
+   * See `AFL_FRIDA_STATS_FILE`. This function takes a single `string` as
+   * an argument.
+   */
+  public static setStatsFile(file: string): void {
+    const buf = Memory.allocUtf8String(file);
+    Afl.jsApiSetStatsFile(buf);
+  }
+
+  /**
+   * See `AFL_FRIDA_STATS_INTERVAL`. This function takes a `number` as an
+   * argument
+   */
+  public static setStatsInterval(interval: number): void {
+    Afl.jsApiSetStatsInterval(interval);
+  }
+
+  /**
+   * See `AFL_FRIDA_STATS_TRANSITIONS`
+   */
+  public static setStatsTransitions(): void {
+    Afl.jsApiSetStatsTransitions();
+  }
+
+  /**
+   * See `AFL_FRIDA_OUTPUT_STDERR`. This function takes a single `string` as
+   * an argument.
+   */
+  public static setStdErr(file: string): void {
+    const buf = Memory.allocUtf8String(file);
+    Afl.jsApiSetStdErr(buf);
+  }
+
+  /**
+   * See `AFL_FRIDA_OUTPUT_STDOUT`. This function takes a single `string` as
+   * an argument.
+   */
+  public static setStdOut(file: string): void {
+    const buf = Memory.allocUtf8String(file);
+    Afl.jsApiSetStdOut(buf);
+  }
+
+}
 
-/*
- * See `AFL_FRIDA_STATS_TRANSITIONS`
- */
-Afl.setStatsTransitions()
 ```