about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--nyx_mode/README.md172
-rw-r--r--nyx_mode/custom_harness/example.c91
-rw-r--r--nyx_mode/custom_harness/fuzz.sh13
-rw-r--r--nyx_mode/custom_harness/fuzz_no_pt.sh13
4 files changed, 289 insertions, 0 deletions
diff --git a/nyx_mode/README.md b/nyx_mode/README.md
new file mode 100644
index 00000000..dd0d1aac
--- /dev/null
+++ b/nyx_mode/README.md
@@ -0,0 +1,172 @@
+### Fuzzing libxml2 with AFL++ in Nyx-mode
+
+This tutorial is based on the [Fuzzing libxml2 with AFL++](https://aflplus.plus/docs/tutorials/libxml2_tutorial/) tutorial.
+
+#### Initial Setup
+
+Setting up our fork of AFL++ and its Nyx backend is rather simple. But there are some requirements: first, Nyx expects either a recent linux kernel (`>= 5.11`) with KVM or KVM-Nyx to be installed. In both cases, access to the `/dev/kvm` device is required. So, make sure that the permissions are fine. 
+
+To get started, check out our repository and run our setup script to install `libnyx`, Nyx's packer utilities and QEMU-Nyx:
+
+```
+git clone https://github.com/nyx-fuzz/AFLplusplus-Nyx.git
+cd AFLplusplus-Nyx
+compile_nyx_mode.sh
+```
+
+### Preparing libxml2 
+
+This part is basically the same as described in the original tutorial. First, get the latest libxml2 source files by using `git`: 
+
+```
+git clone https://gitlab.gnome.org/GNOME/libxml2.git
+cd libxml2
+```
+
+From there on, you have to use -- at least for now -- our AFL-compiler instead of the AFL++ provided compiler. The reason for this is that we currently don't support all of AFL++'s features. Basically, we don't have full support for collision-free bitmaps yet. So, to continue run the following commands and adjust the path to our compiler (our compiler is located in the `packer` repository):
+
+``` 
+./autogen.sh
+./configure --enable-shared=no
+make CC=~/AFLplusplus-Nyx/packer/packer/compiler/afl-clang-fast CXX=~/AFLplusplus-Nyx/packer/packer/compiler/afl-clang-fast++ LD=~/AFLplusplus-Nyx/packer/packer/compiler/afl-clang-fast
+```
+
+#### Nyx share directories
+
+Nyx expects that the target is provided in a certain format. More specifically, the target is passed as a so-called „share directory“ to a Nyx-frontend implementation. The share directory contains the target as wells as a folder containing all dependencies and other files that are copied over to the guest. But more importantly, this share directory also contains a bootstrap script (`fuzz.sh`if you are using `KVM-Nyx`otherwise `fuzz_no_pt.sh`) that is also executed right after launching the fuzzer. Both bootstrap scripts use several tools to communicate with the "outer world":
+
+- `hcat` - this tool copies a given string to the host 
+- `hget` - this program requests a file from the host's share directory 
+- `hget_bulk` - an improved version of  `hget`. It is quite useful if you want to transfer huge files. But please keep in mind that this version of `hget` has a much larger startup overhead and won't improve your transfer rates on small files (typically files smaller than 100MB). 
+- `habort` - this tool basically sends an abort signal to the host (useful if something went wrong during bootstrap)
+- `hpush` - a tool to transfer a given file to the host (the transfered file will be put in the `dump/` folder of your Nyx workdir)
+
+Those tools are all using hypercalls which are defined in `packer/nyx.h`. We will give some more examples later on how to use these hypercalls directly to implement custom fuzzing harnesses.
+
+### Pack libxml2 into Nyx sharedir format
+
+To turn a given linux target into the Nyx  format, you can simply use `nyx_packer.py`. To do so, move to the following directory:
+
+```
+cd ~/AFLplusplus-Nyx/packer/packer
+```
+
+ And run the tool with the following options to  pack `libxml2`:
+
+```.
+./nyx_packer.py \
+	~/libxml2/xmllint \
+	/tmp/nyx_libxml2 \
+	afl \
+	instrumentation \
+	-args "/tmp/input" \
+	-file "/tmp/input" \
+	--fast_reload_mode \
+	--purge 
+```
+
+In this example, the packer will take `xmllint`, recursively get all dependencies and put both into the specified share directory (`/tmp/nyx_libxml2` in this case). Because we have selected the `afl` option, an `ld_preload`-based agent is also automatically built and put into the sharedir. Another option would be `spec`. Without going into too much detail here, the `spec`mode is only used by Nyx's [spec-fuzzer](https://github.com/nyx-fuzz/spec-fuzzer) implementation. Next, since our target is built with compile-time instrumentations, we must select the `instrumentation` option, othwise we could also use `processor-trace` option to enable Intel-PT fuzzing on targets without instrumentation. 
+
+To specify that the input generated by the fuzzer is passed as a seperate file to the target, we need to set the `-file` option. Otherwise, the input will be passed over to the target via `stdin`. To specify any required `argv` options you can use the `-args`parameter. 
+
+In case you want to fuzz the target only with fast snapshots enabled, you can also set the ` --fast_reload_mode`option to improve performance.
+
+Finally, we need to generate a Nyx configuration file. Simply run the following command and you're good to proceed:
+
+```
+./nyx_config_gen.py /tmp/nyx_libxml2/ Kernel
+```
+
+### Run AFL++Nyx
+
+From here on, we are almost done. Move to the AFL++Nyx folder and start the fuzzer with the following arguments:
+
+```
+mkdir /tmp/in/ 						# to create an input folder
+echo "AA" >> /tmp/in/A 		# create an input file to make the fuzzer happy for now
+ ./afl-fuzz -i /tmp/in/ -o /tmp/out -d -X /tmp/nyx_libxml2/
+```
+
+If everything has been successfully set up to this point, you will now be welcomed by the following AFL++ screen:
+
+```
+        american fuzzy lop ++3.15a {default} (/tmp/nyx_libxml2/) [fast] - NYX
+┌─ process timing ────────────────────────────────────┬─ overall results ────┐
+│        run time : 0 days, 0 hrs, 0 min, 14 sec      │  cycles done : 0     │
+│   last new find : 0 days, 0 hrs, 0 min, 0 sec       │ corpus count : 96    │
+│last saved crash : none seen yet                     │saved crashes : 0     │
+│ last saved hang : none seen yet                     │  saved hangs : 0     │
+├─ cycle progress ─────────────────────┬─ map coverage┴──────────────────────┤
+│  now processing : 28.0 (29.2%)       │    map density : 2.17% / 3.61%      │
+│  runs timed out : 0 (0.00%)          │ count coverage : 1.67 bits/tuple    │
+├─ stage progress ─────────────────────┼─ findings in depth ─────────────────┤
+│  now trying : havoc                  │ favored items : 27 (28.12%)         │
+│ stage execs : 22.3k/32.8k (68.19%)   │  new edges on : 58 (60.42%)         │
+│ total execs : 55.9k                  │ total crashes : 0 (0 saved)         │
+│  exec speed : 3810/sec               │  total tmouts : 0 (0 saved)         │
+├─ fuzzing strategy yields ────────────┴─────────────┬─ item geometry ───────┤
+│   bit flips : disabled (default, enable with -D)   │    levels : 3         │
+│  byte flips : disabled (default, enable with -D)   │   pending : 95        │
+│ arithmetics : disabled (default, enable with -D)   │  pend fav : 27        │
+│  known ints : disabled (default, enable with -D)   │ own finds : 95        │
+│  dictionary : n/a                                  │  imported : 0         │
+│havoc/splice : 57/32.8k, 0/0                        │ stability : 100.00%   │
+│py/custom/rq : unused, unused, unused, unused       ├───────────────────────┘
+│    trim/eff : n/a, disabled                        │          [cpu000: 25%]
+└────────────────────────────────────────────────────┘
+```
+
+If you want to run the fuzzer in distributed mode, which might be especially useful if you want to keep your memory footprint low, we got you covered. To start an initiating `parent` process, which will also create the snapshot which is later shared across all other `child`s, simply run AFL++Nyx with the following arguments:
+
+```
+./afl-fuzz -i /tmp/in/ -o /tmp/out -d -Y -M 0 /tmp/nyx_libxml2/
+```
+
+To attach other child processes adjust the `-S <id>` and run the following command:
+
+```
+./afl-fuzz -i /tmp/in/ -o /tmp/out -d -Y -S 1 /tmp/nyx_libxml2/
+```
+
+If you want to disable fast snapshots (except for crashes), you can simply set the `NYX_DISABLE_SNAPSHOT_MODE` environment variable. 
+
+### Run AFL++Nyx with a custom agent
+
+Most of the common use-cases for linux userland targets are already handled by our general purpose [agent](https://github.com/nyx-fuzz/packer/blob/main/packer/linux_x86_64-userspace/src/ld_preload_fuzz.c) implementation. But in case you want to build your own agent, or write a custom harness for a specific target or you just want implement all the hypercall and shared memory communication on your own, you can use our custom harness example as a starting point for that. You can find the code [here](custom_harness/)
+
+This custom harness can be statically compiled with by gcc or clang. There is no need to use an AFL compiler, becaues this agent implements its own very basic coverage tracking by simply setting specific bytes in the "coverage" bitmap after specific branches have been covered. 
+
+To prepare this target, we must first create a new folder that will later become the sharedir.
+
+````
+mkdir /tmp/nyx_custom_agent/
+````
+
+ To compile this example, run the following command (remove the `-DNO_PT_NYX` option if you are using KVM-Nyx ): 
+
+``` 
+gcc example.c -DNO_PT_NYX -static -I AFLplusplus/packer/ -o /tmp/nyx_custom_agent/target
+```
+
+Copy both bootstrap scripts into the sharedir: 
+
+```
+cp fuzz.sh /tmp/nyx_custom_agent
+cp fuzz_no_pt.sh /tmp/nyx_custom_agent
+```
+
+Copy all `htools` executable into the sharedir: 
+
+```
+cd ~/AFLplusplus-Nyx/packer/packer/linux_x86_64-userspace/
+sh compile_64.sh
+cp bin64/h* /tmp/nyx_custom_agent/
+```
+
+And finally, generate a Nyx configuration: 
+
+```
+cd ~/AFLplusplus-Nyx/packer/packer
+./nyx_config_gen.py /tmp/nyx_custom_agent/ Kernel
+```
+
diff --git a/nyx_mode/custom_harness/example.c b/nyx_mode/custom_harness/example.c
new file mode 100644
index 00000000..b217ed55
--- /dev/null
+++ b/nyx_mode/custom_harness/example.c
@@ -0,0 +1,91 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include "nyx.h"
+
+/* this is our "bitmap" that is later shared with the fuzzer (you can also pass the pointer of the bitmap used by compile-time instrumentations in your target) */ 
+uint8_t* trace_buffer[64*1024] = {0};
+
+int main(int argc, char** argv){
+	/* if you want to debug code running in Nyx, hprintf() is the way to go. 
+	*  Long story short -- it's just a guest-to-hypervisor printf. Hence the name "hprintf" 
+	*/
+	hprintf("Agent test\n");
+
+	/* Request information on available (host) capabilites (optional) */
+	host_config_t host_config;
+    kAFL_hypercall(HYPERCALL_KAFL_GET_HOST_CONFIG, (uintptr_t)&host_config);
+	hprintf("[capablities] host_config.bitmap_size: 0x%"PRIx64"\n", host_config.bitmap_size);
+    hprintf("[capablities] host_config.ijon_bitmap_size: 0x%"PRIx64"\n", host_config.ijon_bitmap_size);
+    hprintf("[capablities] host_config.payload_buffer_size: 0x%"PRIx64"x\n", host_config.payload_buffer_size);
+	
+	/* Submit agent configuration */
+	memset(trace_buffer, 0, 64*1024); // makes sure that the bitmap buffer is already mapped into the guest's memory (alternatively you can use mlock) */
+	agent_config_t agent_config = {0};
+	agent_config.agent_timeout_detection = 0; 								/* timeout detection is implemented by the agent (currently not used) */
+	agent_config.agent_tracing = 1;											/* set this flag to propagade that instrumentation-based fuzzing is availabe */
+	agent_config.agent_ijon_tracing = 0; 									/* set this flag to propagade that IJON extension is implmented agent-wise */
+	agent_config.trace_buffer_vaddr = (uintptr_t)trace_buffer;				/* trace "bitmap" pointer - required for instrumentation-only fuzzing */
+	agent_config.ijon_trace_buffer_vaddr = (uintptr_t)NULL;					/* "IJON" buffer pointer */
+    agent_config.agent_non_reload_mode = 1;									/* non-reload mode is supported (usually because the agent implements a fork-server; currently not used) */
+    kAFL_hypercall(HYPERCALL_KAFL_SET_AGENT_CONFIG, (uintptr_t)&agent_config);
+
+	/* Tell hypervisor the virtual address of the payload (input) buffer (call mlock to ensure that this buffer stays in the guest's memory)*/
+	kAFL_payload* payload_buffer = mmap((void*)0x4000000ULL, PAYLOAD_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
+	mlock(payload_buffer, (size_t)PAYLOAD_SIZE);
+	memset(payload_buffer, 0, PAYLOAD_SIZE);
+	kAFL_hypercall(HYPERCALL_KAFL_GET_PAYLOAD, (uintptr_t)payload_buffer);
+	hprintf("[init] payload buffer is mapped at %p\n", payload_buffer);
+
+	/* the main fuzzing loop */
+	while(1){
+
+		/* Creates a root snapshot on first execution. Also we requested the next input with this hypercall */
+		kAFL_hypercall(HYPERCALL_KAFL_USER_FAST_ACQUIRE, 0); // root snapshot <--
+
+#ifdef DEBUG
+		hprintf("Size: %ld Data: %x %x %x %x\n", payload_buffer->size,
+								payload_buffer->data[4],
+								payload_buffer->data[5],
+								payload_buffer->data[6],
+								payload_buffer->data[7]
+								);
+#endif
+
+		uint32_t len = payload_buffer->size;
+
+		/* set a byte to make AFL++ happy (otherwise the fuzzer might refuse to start fuzzing at all) */
+		((uint8_t*)trace_buffer)[0] = 0x1;
+
+		if (len >= 4){
+			/* set a byte in the bitmap to guide your fuzzer */
+			((uint8_t*)trace_buffer)[0] = 0x1;
+			if (payload_buffer->data[0] == '!'){
+				((uint8_t*)trace_buffer)[1] = 0x1;
+				if (payload_buffer->data[1] == 'N'){
+					((uint8_t*)trace_buffer)[2] = 0x1;
+					if (payload_buffer->data[2] == 'Y'){
+						((uint8_t*)trace_buffer)[3] = 0x1;
+						if (payload_buffer->data[3] == 'X'){
+							((uint8_t*)trace_buffer)[4] = 0x1;
+							/* Notifiy the hypervisor and the fuzzer that a "crash" has occured. Also a string is passed by this hypercall (this is currently not supported by AFL++-Nyx) */
+							kAFL_hypercall(HYPERCALL_KAFL_PANIC_EXTENDED, (uintptr_t)"Something went wrong\n");
+						}
+					}
+				}
+			}
+		}
+		/* this hypercall is used to notify the hypervisor and the fuzzer that a single fuzzing "execution" has finished.
+		 * If the reload-mode is enabled, we will jump back to our root snapshot. 
+		 * Otherwise, the hypervisor passes control back to the guest once the bitmap buffer has been "processed" by the fuzzer.
+		 */
+		kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
+
+		/* This shouldn't happen if you have enabled the reload mode */ 
+		hprintf("Das sollte niemals passieren :)\n");
+	}
+
+
+	return 0;
+}
diff --git a/nyx_mode/custom_harness/fuzz.sh b/nyx_mode/custom_harness/fuzz.sh
new file mode 100644
index 00000000..98138f70
--- /dev/null
+++ b/nyx_mode/custom_harness/fuzz.sh
@@ -0,0 +1,13 @@
+chmod +x hget
+cp hget /tmp/
+cd /tmp/
+echo 0 > /proc/sys/kernel/randomize_va_space
+echo 0 > /proc/sys/kernel/printk
+./hget hcat hcat
+./hget habort habort
+./hget target target
+chmod +x hcat
+chmod +x habort
+chmod +x target
+./target
+./habort "Target has terminated without initializing the fuzzing agent ..."
diff --git a/nyx_mode/custom_harness/fuzz_no_pt.sh b/nyx_mode/custom_harness/fuzz_no_pt.sh
new file mode 100644
index 00000000..b65a6493
--- /dev/null
+++ b/nyx_mode/custom_harness/fuzz_no_pt.sh
@@ -0,0 +1,13 @@
+chmod +x hget
+cp hget /tmp/
+cd /tmp/
+echo 0 > /proc/sys/kernel/randomize_va_space
+echo 0 > /proc/sys/kernel/printk
+./hget hcat_no_pt hcat
+./hget habort_no_pt habort
+./hget target target
+chmod +x hcat
+chmod +x habort
+chmod +x target
+./target
+./habort "Target has terminated without initializing the fuzzing agent ..."