|
| 1 | +<div align='center'> |
| 2 | + <h1><code>what the fuzz: Linux mode</code></h1> |
| 3 | + <p> |
| 4 | + <strong>Experimental user-mode Linux mode by Jason Crowder and <a href="https://k0ss.net/">Kyle Ossinger</a> from Cisco ASIG</strong> |
| 5 | + </p> |
| 6 | + <p> |
| 7 | + <img src='https://github.com/0vercl0k/wtf/workflows/Builds/badge.svg'/> |
| 8 | + </p> |
| 9 | + <p> |
| 10 | + <img src='../pics/wtf-linux.gif'/> |
| 11 | + </p> |
| 12 | +</div> |
| 13 | + |
| 14 | +## Overview |
| 15 | + |
| 16 | +This provides experimental Linux ELF userland snapshotting support based on previous work by [Kasimir](https://github.com/0vercl0k/wtf/pull/102) and scripts from [Snapchange](https://github.com/awslabs/snapchange/tree/main/qemu_snapshot). |
| 17 | + |
| 18 | +<p align='center'> |
| 19 | +<img src='../pics/wtf-linux-snapshot.webp'> |
| 20 | +</p> |
| 21 | + |
| 22 | +## Setting up the environment |
| 23 | + |
| 24 | +Move into the `linux_mode/qemu_snapshot` directory and run `setup.sh`: |
| 25 | + |
| 26 | +```console |
| 27 | +user@pc:/wtf/linux_mode/qemu_snapshot$ ./setup.sh |
| 28 | +``` |
| 29 | + |
| 30 | +This script installs all pre-requisite tools, compiles qemu, and builds a target virtual machine consisting of a Linux kernel and disk image. |
| 31 | + |
| 32 | +## Taking a snapshot |
| 33 | + |
| 34 | +Create a subdirectory in `linux_mode` for your snapshot and create a `bkpt.py` file, like [linux_mode/crash_test/bkpt.py](crash_test/bkpt.py): |
| 35 | + |
| 36 | +```py |
| 37 | +# imports |
| 38 | +import sys, os |
| 39 | + |
| 40 | +# import fuzzing breakpoint |
| 41 | +from gdb_fuzzbkpt import * |
| 42 | + |
| 43 | +target_dir = 'linux_crash_test' |
| 44 | + |
| 45 | +# address to break on, found using gdb |
| 46 | +break_address = 'do_crash_test' |
| 47 | + |
| 48 | +# name of the file in which to break |
| 49 | +file_name = 'a.out' |
| 50 | + |
| 51 | +# create the breakpoint for the executable specified |
| 52 | +FuzzBkpt(target_dir, break_address, file_name, sym_path=file_name) |
| 53 | +``` |
| 54 | + |
| 55 | +* `target_dir` - subdirectory in `targets` to save the snapshot data |
| 56 | +* `break_address` - address to break on. This can be a hardcoded address or a symbol if `sym_path` is provided |
| 57 | +* `file_name` - name of the file in the target VM associated with the breakpoint |
| 58 | +* `sym_path` - optional argument if you'd like symbols to be loaded |
| 59 | + |
| 60 | +Start the virtual machine in one tab while in the snapshot subdirectory by running `../qemu_snapshot/gdb_server.sh`: |
| 61 | + |
| 62 | +```console |
| 63 | +user@pc:/wtf/linux_mode/crash_test$ ../qemu_snapshot/gdb_server.sh |
| 64 | +``` |
| 65 | + |
| 66 | +In a separate tab, scp the target file to the target VM. With `crash_test` this can be done by first compiling the target file: |
| 67 | + |
| 68 | +```console |
| 69 | +user@pc:/wtf/linux_mode/crash_test$ gcc test.c |
| 70 | +``` |
| 71 | + |
| 72 | +Then transfer the target file to the VM: |
| 73 | + |
| 74 | +```console |
| 75 | +user@pc:/wtf/linux_mode/crash_test$ pushd ../qemu_snapshot/target_vm |
| 76 | +user@pc:/wtf/linux_mode/qemu_snapshot/target_vm$ ./scp.sh ../../crash_test/a.out |
| 77 | +a.out 100% 16KB 1.2MB/s 00:00 |
| 78 | +``` |
| 79 | + |
| 80 | +Go back to the `crash_test` directory. |
| 81 | + |
| 82 | +```console |
| 83 | +user@pc:/wtf/linux_mode/qemu_snapshot/target_vm$ popd |
| 84 | +/wtf/linux_mode/crash_test |
| 85 | +user@pc:/wtf/linux_mode/crash_test$ |
| 86 | +``` |
| 87 | + |
| 88 | +Now, run `../qemu_snapshot/gdb_client.sh`: |
| 89 | + |
| 90 | +```console |
| 91 | +user@pc:/wtf/linux_mode/crash_test$ ../qemu_snapshot/gdb_client.sh |
| 92 | +``` |
| 93 | + |
| 94 | +In the first tab, log in to the Linux machine (user `root`) and run the target file: |
| 95 | + |
| 96 | +```console |
| 97 | +linux login: root |
| 98 | +Linux linux 6.7.0-rc3 #1 SMP PREEMPT_DYNAMIC Thu Nov 30 18:38:29 UTC 2023 x86_64 |
| 99 | + |
| 100 | +The programs included with the Debian GNU/Linux system are free software; |
| 101 | +the exact distribution terms for each program are described in the |
| 102 | +individual files in /usr/share/doc/*/copyright. |
| 103 | + |
| 104 | +Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent |
| 105 | +permitted by applicable law. |
| 106 | +A valid context for root could not be obtained. |
| 107 | +Last login: Fri Dec 1 21:21:22 UTC 2023 on ttyS0 |
| 108 | +root@linux:~# ./a.out |
| 109 | + |
| 110 | +Enter some input. |
| 111 | +d |
| 112 | +``` |
| 113 | + |
| 114 | +Once the breakpoint is hit, the second tab will start the snapshotting process: |
| 115 | + |
| 116 | +```console |
| 117 | +Continuing. |
| 118 | +In right process? True |
| 119 | +Calling mlockall |
| 120 | +Saving 67 bytes at 555555555146 |
| 121 | +In right process? True |
| 122 | +Restoring 67 bytes at 0x555555555146 |
| 123 | +Restored |
| 124 | +In the Qemu tab, press Ctrl+C, run the `cpu` command |
| 125 | +``` |
| 126 | + |
| 127 | +Once the second tab indicates to run the `cpu` command, press Ctrl+C and run the `cpu` command from the first tab: |
| 128 | + |
| 129 | +```console |
| 130 | +Thread 1 "qemu-system-x86" received signal SIGINT, Interrupt. |
| 131 | +0x00007ffff77a4ebe in __ppoll (fds=0x5555568337d0, nfds=8, timeout=<optimized out>, timeout@entry=0x7fffffffdea0, sigmask=si |
| 132 | +42 ../sysdeps/unix/sysv/linux/ppoll.c: No such file or directory. |
| 133 | +(gdb) cpu |
| 134 | +cpu_state: 0x55555681e240 |
| 135 | +done...continuing debuggee |
| 136 | +``` |
| 137 | + |
| 138 | +The second tab will detect once the first tab has finished executing the `cpu` command and continue creating a snapshot for the target VM. |
| 139 | + |
| 140 | +Once the second tab indicates that snapshotting is complete, the target VM can be terminated. |
| 141 | + |
| 142 | +```console |
| 143 | +In the Qemu tab, press Ctrl+C, run the `cpu` command |
| 144 | +Detected cpu registers dumped to regs.json |
| 145 | +Connecting to Qemu monitor at localhost:55555 |
| 146 | +Connected |
| 147 | +Instructing Qemu to dump physical memory to file raw |
| 148 | +Done |
| 149 | +Converting raw file raw to dump file /wtf/targets/linux_crash_test/state/mem.dmp |
| 150 | +Done |
| 151 | +mv regs.json /wtf/targets/linux_crash_test/state/regs.json |
| 152 | +mv symbol-store.json /wtf/targets/linux_crash_test/state/symbol-store.json |
| 153 | +Snapshotting complete |
| 154 | + |
| 155 | +Breakpoint 1, 0x0000555555555189 in do_crash_test () |
| 156 | +(gdb) |
| 157 | +``` |
| 158 | + |
| 159 | +## Harnessing and Fuzzing |
| 160 | + |
| 161 | +Writing harnesses is the same process as writing harnesses for Windows executables. Example harnesses for crash_test and page_fault_test are present in [src/wtf/fuzzer_linux_crash_test.cc](../src/wtf/fuzzer_linux_crash_test.cc) and [src/wtf/fuzzer_linux_page_fault_test.cc](../src/wtf/fuzzer_linux_page_fault_test.cc). |
| 162 | + |
| 163 | +Now that we have everything set up we can start our server and fuzzer: |
| 164 | + |
| 165 | +Provide a seed input: |
| 166 | + |
| 167 | +```console |
| 168 | +user@pc:/wtf/targets/linux_crash_test$ echo a>inputs/a |
| 169 | +``` |
| 170 | + |
| 171 | +Run the master: |
| 172 | + |
| 173 | +```console |
| 174 | +user@pc:/wtf/targets/linux_crash_test$ ../../src/build/wtf master --name linux_crash_test --max_len=10 |
| 175 | +``` |
| 176 | + |
| 177 | +Run the fuzzee and note that crashes are found quickly. |
| 178 | + |
| 179 | +```console |
| 180 | +user@pc:/wtf/targets/linux_crash_test$ ../../src/build/wtf fuzz --backend=bochscpu --name linux_crash_test |
| 181 | +Setting @fptw to 0xff'ff. |
| 182 | +The debugger instance is loaded with 16 items |
| 183 | +Setting debug register status to zero. |
| 184 | +Setting debug register status to zero. |
| 185 | +Setting mxcsr_mask to 0xffbf. |
| 186 | +Dialing to tcp://localhost:31337/.. |
| 187 | +#113174 cov: 47 exec/s: 11.3k lastcov: 2.0s crash: 1782 timeout: 0 cr3: 0 uptime: 10.0s |
| 188 | +``` |
| 189 | + |
| 190 | +To fuzz with KVM, create a coverage breakpoints file by loading the target file in IDA and running [scripts/gen_linux_coveragefile_ida.py](../scripts/gen_linux_coveragefile_ida.py). Transfer the coverage breakpoints file to the `coverage` subfolder in the target's directory. For example, for `linux_crash_test` transfer the coverage breakpoint file to `targets/linux_crash_test/coverage/a.cov`. Once transferred, KVM can be used for fuzzing: |
| 191 | + |
| 192 | +```console |
| 193 | +user@pc:/wtf/targets/linux_crash_test$ sudo ../../src/build/wtf fuzz --backend=kvm --name linux_crash_test |
| 194 | +Setting @fptw to 0xff'ff. |
| 195 | +The debugger instance is loaded with 16 items |
| 196 | +Parsing coverage/a.cov.. |
| 197 | +Applied 44 code coverage breakpoints |
| 198 | +Setting debug register status to zero. |
| 199 | +Setting debug register status to zero. |
| 200 | +Setting mxcsr_mask to 0xffbf. |
| 201 | +Resolved breakpoint 0xffffffff82001240 at GPA 0x2001240 aka HVA 0x564428d2afe0 |
| 202 | +Resolved breakpoint 0xffffffff82000ff0 at GPA 0x2000ff0 aka HVA 0x564428d2cda0 |
| 203 | +Resolved breakpoint 0xffffffff81099dc0 at GPA 0x1099dc0 aka HVA 0x564428d2db80 |
| 204 | +Resolved breakpoint 0xffffffff810708e0 at GPA 0x10708e0 aka HVA 0x564428d2e6b0 |
| 205 | +Resolved breakpoint 0x5555555551e7 at GPA 0x972c1e7 aka HVA 0x564428d32117 |
| 206 | +Dialing to tcp://localhost:31337/.. |
| 207 | +#24348 cov: 8 exec/s: 2.4k lastcov: 3.0s crash: 871 timeout: 0 cr3: 0 uptime: 10.0s |
| 208 | +``` |
| 209 | + |
| 210 | +## Symbolizing |
| 211 | + |
| 212 | +The only current way to symbolize and debug your testcases is to use the bochscpu backend and generate a Tenet traces as per [Generating Tenet traces](https://github.com/0vercl0k/wtf?tab=readme-ov-file#generating-tenet-traces). |
0 commit comments