diff --git a/.github/workflows/wtf.yml b/.github/workflows/wtf.yml
index 6ffc756..33c7627 100644
--- a/.github/workflows/wtf.yml
+++ b/.github/workflows/wtf.yml
@@ -2,11 +2,6 @@ name: Builds
on: [push, pull_request]
-permissions:
- actions: read
- contents: read
- security-events: write
-
jobs:
Windows:
runs-on: windows-2019
@@ -71,11 +66,15 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
+ # Revert to the below when https://github.com/llvm/llvm-project/issues/84271 is fixed:
+ # sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
- name: Installing dependencies
run: |
sudo apt-get -y update
sudo apt install -y g++-10 ninja-build
- sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
+ curl -O https://apt.llvm.org/llvm.sh
+ chmod u+x ./llvm.sh
+ sudo ./llvm.sh 17
- name: Build with gcc
if: matrix.compiler == 'gcc'
@@ -87,11 +86,13 @@ jobs:
chmod u+x ./build-release.sh
./build-release.sh
+ # Revert to the below when https://github.com/llvm/llvm-project/issues/84271 is fixed:
+ # clang-18 / clang++-18
- name: Build with clang
if: matrix.compiler == 'clang'
env:
- CC: clang-18
- CXX: clang++-18
+ CC: clang-17
+ CXX: clang++-17
run: |
cd src/build
chmod u+x ./build-release.sh
diff --git a/README.md b/README.md
index d7fcc8f..561b80f 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
what the fuzz
- A distributed, code-coverage guided, cross-platform snapshot-based fuzzer designed for attacking user and or kernel-mode targets running on Microsoft Windows.
+ A distributed, code-coverage guided, cross-platform snapshot-based fuzzer designed for attacking user and or kernel-mode targets running on Microsoft Windows and Linux user-mode (experimental!).
@@ -13,7 +13,7 @@
## Overview
-**what the fuzz** or **wtf** is a distributed, code-coverage guided, customizable, cross-platform snapshot-based fuzzer designed for attacking user and or kernel-mode targets running on Microsoft Windows. Execution of the target can be done inside an emulator with [bochscpu](https://github.com/yrp604/bochscpu) (slowest, most precise), inside a Windows VM with the [Windows Hypervisor Platform APIs](https://docs.microsoft.com/en-us/virtualization/api/hypervisor-platform/hypervisor-platform) or inside a Linux VM with the [KVM APIs](https://www.kernel.org/doc/html/latest/virt/kvm/api.html) (fastest).
+**what the fuzz** or **wtf** is a distributed, code-coverage guided, customizable, cross-platform snapshot-based fuzzer designed for attacking user and or kernel-mode targets running on Microsoft Windows or Linux (**experimental**, see [linux_mode](linux_mode/)). Execution of the target can be done inside an emulator with [bochscpu](https://github.com/yrp604/bochscpu) (slowest, most precise), inside a Windows VM with the [Windows Hypervisor Platform APIs](https://docs.microsoft.com/en-us/virtualization/api/hypervisor-platform/hypervisor-platform) or inside a Linux VM with the [KVM APIs](https://www.kernel.org/doc/html/latest/virt/kvm/api.html) (fastest).
It uncovered memory corruption vulnerabilities in a wide range of softwares: [IDA Pro](https://github.com/0vercl0k/fuzzing-ida75), a popular [AAA game](https://blog.ret2.io/2021/07/21/wtf-snapshot-fuzzing/), the [Windows kernel](https://microsoft.fandom.com/wiki/Architecture_of_Windows_NT), the [Microsoft RDP client](https://www.hexacon.fr/slides/Hexacon2022-Fuzzing_RDPEGFX_with_wtf.pdf), [NVIDIA GPU Display driver](https://nvidia.custhelp.com/app/answers/detail/a_id/5383), etc.
@@ -25,8 +25,6 @@ If you would like to read more about its history or how to use it on a real targ
- [Fuzzing RDPEGFX with "what the fuzz"](https://thalium.github.io/blog/posts/rdpegfx/) by [Colas Le Guernic](https://github.com/clslgrnc), Jérémy Rubert, and Anonymous
- [A Journey to Network Protocol Fuzzing – Dissecting Microsoft IMAP Client Protocol](https://www.fortinet.com/blog/threat-research/analyzing-microsoft-imap-client-protocol) by [Wayne Chin Yick Low](https://www.fortinet.com/blog/search?author=Wayne+Chin+Yick+Low)
-Special thanks to [@yrp604](https://github.com/yrp604) for providing valuable inputs throughout the project and [@masthoon](https://github.com/masthoon) for suggesting to write a demo targeting [HEVD](https://github.com/hacksysteam/HackSysExtremeVulnerableDriver) secure mode.
-
## Usage
The best way to try the features out is to work with the [fuzzer_hevd](src/wtf/fuzzer_hevd.cc) / [fuzzer_tlv_server](src/wtf/fuzzer_tlv_server.cc) modules. You can grab the [target-hevd.7z](https://github.com/0vercl0k/wtf/releases) / [target-tlv_server.7z](https://github.com/0vercl0k/wtf/releases) archives and extract them into the `targets/` directory. The archives contain the directory trees that are expected for every targets:
@@ -316,3 +314,17 @@ To build it yourself you need to start a *Visual Studio Developper Command Promp
## Authors
* Axel '[0vercl0k](https://twitter.com/0vercl0k)' Souchet
+
+## Contributors
+
+Special thanks to:
+- [@yrp604](https://github.com/yrp604) for providing valuable inputs throughout the project,
+- [@masthoon](https://github.com/masthoon) for suggesting to write a demo targeting [HEVD](https://github.com/hacksysteam/HackSysExtremeVulnerableDriver) secure mode,
+- [Markus Gaasedelen](https://github.com/0vercl0k/wtf/pull/12/) for adding Tenet support,
+- [@y0ny0ns0n](https://github.com/y0ny0ns0n) for contributing the [multi-input fuzzing example](https://github.com/0vercl0k/wtf/pull/67),
+- [Colas Le Guernic](https://github.com/clslgrnc) / Jérémy Rubert / Anonymous for implementing [edge coverage for bochscpu](https://github.com/0vercl0k/wtf/pull/137),
+- [@1ndahous3](https://github.com/1ndahous3) for contributing the [generic ioctl fuzzer module](https://github.com/0vercl0k/wtf/pull/155),
+- Jason Crowder / [Kyle Ossinger](https://k0ss.net/) from Cisco ASIG for [the Linux mode](https://github.com/0vercl0k/wtf/pull/192),
+- and all the other contributors 🙏
+
+[ ![contributors-img](https://contrib.rocks/image?repo=0vercl0k/wtf) ](https://github.com/0vercl0k/wtf/graphs/contributors)
diff --git a/linux_mode/.gitignore b/linux_mode/.gitignore
new file mode 100644
index 0000000..9015cdb
--- /dev/null
+++ b/linux_mode/.gitignore
@@ -0,0 +1,11 @@
+gdb.txt
+
+__pycache__/
+
+# Snapshot files
+mem.dmp
+raw
+symbol-store.json
+vm.log
+vm.pid
+regs.json
\ No newline at end of file
diff --git a/linux_mode/README.md b/linux_mode/README.md
new file mode 100644
index 0000000..7b59d55
--- /dev/null
+++ b/linux_mode/README.md
@@ -0,0 +1,212 @@
+
+
what the fuzz: Linux mode
+
+ Experimental user-mode Linux mode by Jason Crowder and Kyle Ossinger from Cisco ASIG
+
+
+
+
+
+
+
+
+
+## Overview
+
+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).
+
+
+
+
+
+## Setting up the environment
+
+Move into the `linux_mode/qemu_snapshot` directory and run `setup.sh`:
+
+```console
+user@pc:/wtf/linux_mode/qemu_snapshot$ ./setup.sh
+```
+
+This script installs all pre-requisite tools, compiles qemu, and builds a target virtual machine consisting of a Linux kernel and disk image.
+
+## Taking a snapshot
+
+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):
+
+```py
+# imports
+import sys, os
+
+# import fuzzing breakpoint
+from gdb_fuzzbkpt import *
+
+target_dir = 'linux_crash_test'
+
+# address to break on, found using gdb
+break_address = 'do_crash_test'
+
+# name of the file in which to break
+file_name = 'a.out'
+
+# create the breakpoint for the executable specified
+FuzzBkpt(target_dir, break_address, file_name, sym_path=file_name)
+```
+
+* `target_dir` - subdirectory in `targets` to save the snapshot data
+* `break_address` - address to break on. This can be a hardcoded address or a symbol if `sym_path` is provided
+* `file_name` - name of the file in the target VM associated with the breakpoint
+* `sym_path` - optional argument if you'd like symbols to be loaded
+
+Start the virtual machine in one tab while in the snapshot subdirectory by running `../qemu_snapshot/gdb_server.sh`:
+
+```console
+user@pc:/wtf/linux_mode/crash_test$ ../qemu_snapshot/gdb_server.sh
+```
+
+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:
+
+```console
+user@pc:/wtf/linux_mode/crash_test$ gcc test.c
+```
+
+Then transfer the target file to the VM:
+
+```console
+user@pc:/wtf/linux_mode/crash_test$ pushd ../qemu_snapshot/target_vm
+user@pc:/wtf/linux_mode/qemu_snapshot/target_vm$ ./scp.sh ../../crash_test/a.out
+a.out 100% 16KB 1.2MB/s 00:00
+```
+
+Go back to the `crash_test` directory.
+
+```console
+user@pc:/wtf/linux_mode/qemu_snapshot/target_vm$ popd
+/wtf/linux_mode/crash_test
+user@pc:/wtf/linux_mode/crash_test$
+```
+
+Now, run `../qemu_snapshot/gdb_client.sh`:
+
+```console
+user@pc:/wtf/linux_mode/crash_test$ ../qemu_snapshot/gdb_client.sh
+```
+
+In the first tab, log in to the Linux machine (user `root`) and run the target file:
+
+```console
+linux login: root
+Linux linux 6.7.0-rc3 #1 SMP PREEMPT_DYNAMIC Thu Nov 30 18:38:29 UTC 2023 x86_64
+
+The programs included with the Debian GNU/Linux system are free software;
+the exact distribution terms for each program are described in the
+individual files in /usr/share/doc/*/copyright.
+
+Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
+permitted by applicable law.
+A valid context for root could not be obtained.
+Last login: Fri Dec 1 21:21:22 UTC 2023 on ttyS0
+root@linux:~# ./a.out
+
+Enter some input.
+d
+```
+
+Once the breakpoint is hit, the second tab will start the snapshotting process:
+
+```console
+Continuing.
+In right process? True
+Calling mlockall
+Saving 67 bytes at 555555555146
+In right process? True
+Restoring 67 bytes at 0x555555555146
+Restored
+In the Qemu tab, press Ctrl+C, run the `cpu` command
+```
+
+Once the second tab indicates to run the `cpu` command, press Ctrl+C and run the `cpu` command from the first tab:
+
+```console
+Thread 1 "qemu-system-x86" received signal SIGINT, Interrupt.
+0x00007ffff77a4ebe in __ppoll (fds=0x5555568337d0, nfds=8, timeout=
, timeout@entry=0x7fffffffdea0, sigmask=si
+42 ../sysdeps/unix/sysv/linux/ppoll.c: No such file or directory.
+(gdb) cpu
+cpu_state: 0x55555681e240
+done...continuing debuggee
+```
+
+The second tab will detect once the first tab has finished executing the `cpu` command and continue creating a snapshot for the target VM.
+
+Once the second tab indicates that snapshotting is complete, the target VM can be terminated.
+
+```console
+In the Qemu tab, press Ctrl+C, run the `cpu` command
+Detected cpu registers dumped to regs.json
+Connecting to Qemu monitor at localhost:55555
+Connected
+Instructing Qemu to dump physical memory to file raw
+Done
+Converting raw file raw to dump file /wtf/targets/linux_crash_test/state/mem.dmp
+Done
+mv regs.json /wtf/targets/linux_crash_test/state/regs.json
+mv symbol-store.json /wtf/targets/linux_crash_test/state/symbol-store.json
+Snapshotting complete
+
+Breakpoint 1, 0x0000555555555189 in do_crash_test ()
+(gdb)
+```
+
+## Harnessing and Fuzzing
+
+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).
+
+Now that we have everything set up we can start our server and fuzzer:
+
+Provide a seed input:
+
+```console
+user@pc:/wtf/targets/linux_crash_test$ echo a>inputs/a
+```
+
+Run the master:
+
+```console
+user@pc:/wtf/targets/linux_crash_test$ ../../src/build/wtf master --name linux_crash_test --max_len=10
+```
+
+Run the fuzzee and note that crashes are found quickly.
+
+```console
+user@pc:/wtf/targets/linux_crash_test$ ../../src/build/wtf fuzz --backend=bochscpu --name linux_crash_test
+Setting @fptw to 0xff'ff.
+The debugger instance is loaded with 16 items
+Setting debug register status to zero.
+Setting debug register status to zero.
+Setting mxcsr_mask to 0xffbf.
+Dialing to tcp://localhost:31337/..
+#113174 cov: 47 exec/s: 11.3k lastcov: 2.0s crash: 1782 timeout: 0 cr3: 0 uptime: 10.0s
+```
+
+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:
+
+```console
+user@pc:/wtf/targets/linux_crash_test$ sudo ../../src/build/wtf fuzz --backend=kvm --name linux_crash_test
+Setting @fptw to 0xff'ff.
+The debugger instance is loaded with 16 items
+Parsing coverage/a.cov..
+Applied 44 code coverage breakpoints
+Setting debug register status to zero.
+Setting debug register status to zero.
+Setting mxcsr_mask to 0xffbf.
+Resolved breakpoint 0xffffffff82001240 at GPA 0x2001240 aka HVA 0x564428d2afe0
+Resolved breakpoint 0xffffffff82000ff0 at GPA 0x2000ff0 aka HVA 0x564428d2cda0
+Resolved breakpoint 0xffffffff81099dc0 at GPA 0x1099dc0 aka HVA 0x564428d2db80
+Resolved breakpoint 0xffffffff810708e0 at GPA 0x10708e0 aka HVA 0x564428d2e6b0
+Resolved breakpoint 0x5555555551e7 at GPA 0x972c1e7 aka HVA 0x564428d32117
+Dialing to tcp://localhost:31337/..
+#24348 cov: 8 exec/s: 2.4k lastcov: 3.0s crash: 871 timeout: 0 cr3: 0 uptime: 10.0s
+```
+
+## Symbolizing
+
+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).
diff --git a/linux_mode/crash_test/README.md b/linux_mode/crash_test/README.md
new file mode 100644
index 0000000..d6f3473
--- /dev/null
+++ b/linux_mode/crash_test/README.md
@@ -0,0 +1,2 @@
+Test program and breakpoint script to verify Linux snapshotting and fuzzing work
+correctly.
\ No newline at end of file
diff --git a/linux_mode/crash_test/bkpt.py b/linux_mode/crash_test/bkpt.py
new file mode 100644
index 0000000..3bd1fc8
--- /dev/null
+++ b/linux_mode/crash_test/bkpt.py
@@ -0,0 +1,17 @@
+# Jason Crowder - February 2024
+# imports
+import sys, os
+
+# import fuzzing breakpoint
+from gdb_fuzzbkpt import *
+
+target_dir = "linux_crash_test"
+
+# address to break on, found using gdb
+break_address = "do_crash_test"
+
+# name of the file in which to break
+file_name = "a.out"
+
+# create the breakpoint for the executable specified
+FuzzBkpt(target_dir, break_address, file_name, sym_path=file_name)
diff --git a/linux_mode/crash_test/test.c b/linux_mode/crash_test/test.c
new file mode 100644
index 0000000..a0d8da1
--- /dev/null
+++ b/linux_mode/crash_test/test.c
@@ -0,0 +1,40 @@
+// Jason Crowder - February 2024
+#include
+#include
+
+void do_crash_test(char* input) {
+ if (input[0] == 'C' && input[1] == 'R' && input[2] == 'A' &&
+ input[3] == 'S' && input[4] == 'H') {
+ *(char*)NULL = '\0';
+ }
+}
+
+void end_crash_test() { printf("End crash test.\n"); }
+
+int main(int argc, char* argv[]) {
+ char* buf = NULL;
+ size_t cbBuf = 10;
+ ssize_t cbRead = 0;
+
+ buf = (char*)calloc(1, cbBuf);
+ if (!buf) {
+ printf("calloc failed.\n");
+ goto END;
+ }
+
+ printf("Enter some input.\n");
+ cbRead = getline(&buf, &cbBuf, stdin);
+ if (-1 == cbRead) {
+ perror("getline failure: ");
+ goto END;
+ }
+
+ do_crash_test(buf);
+
+ end_crash_test();
+
+END:
+ if (buf) {
+ free(buf);
+ }
+}
\ No newline at end of file
diff --git a/linux_mode/page_fault_test/README.md b/linux_mode/page_fault_test/README.md
new file mode 100755
index 0000000..69987ff
--- /dev/null
+++ b/linux_mode/page_fault_test/README.md
@@ -0,0 +1,2 @@
+Test page fault program for making sure memory locking instructions and code
+work correctly.
diff --git a/linux_mode/page_fault_test/bkpt.py b/linux_mode/page_fault_test/bkpt.py
new file mode 100755
index 0000000..f4605b8
--- /dev/null
+++ b/linux_mode/page_fault_test/bkpt.py
@@ -0,0 +1,16 @@
+# Jason Crowder - February 2024
+# imports
+import sys, os
+
+# import fuzzing breakpoint
+from gdb_fuzzbkpt import *
+
+target_dir = "linux_page_fault_test"
+
+break_address = "page_fault_test"
+
+# name of the file in which to break
+file_name = "a.out"
+
+# create the breakpoint for the executable specified
+FuzzBkpt(target_dir, break_address, file_name, bp_hits_required=1, sym_path=file_name)
diff --git a/linux_mode/page_fault_test/test.c b/linux_mode/page_fault_test/test.c
new file mode 100644
index 0000000..6f086a7
--- /dev/null
+++ b/linux_mode/page_fault_test/test.c
@@ -0,0 +1,34 @@
+// Jason Crowder - February 2024
+#include
+#include
+
+#define PAGE_SIZE 0x1000
+#define BUFF_SIZE (512 * 1024 * 1024)
+
+void page_fault_test(void* p) {
+ for (size_t i = 0; i < BUFF_SIZE; i += PAGE_SIZE) {
+ char* pc = (char*)p + i;
+ *pc = 'A';
+ }
+}
+
+void done_with_test() { printf("Done with test.\n"); }
+
+int main() {
+ void* p = malloc(BUFF_SIZE);
+
+ if (!p) {
+ perror("malloc failed.\n");
+ goto END;
+ }
+
+ printf("Press enter to do page fault test.\n");
+ getchar();
+ page_fault_test(p);
+ done_with_test();
+
+END:
+ if (p) {
+ free(p);
+ }
+}
\ No newline at end of file
diff --git a/linux_mode/qemu_snapshot/.gitignore b/linux_mode/qemu_snapshot/.gitignore
new file mode 100644
index 0000000..e08904f
--- /dev/null
+++ b/linux_mode/qemu_snapshot/.gitignore
@@ -0,0 +1,3 @@
+linux/
+__pycache__/
+qemu/
diff --git a/linux_mode/qemu_snapshot/gdb_client.sh b/linux_mode/qemu_snapshot/gdb_client.sh
new file mode 100755
index 0000000..2ad3919
--- /dev/null
+++ b/linux_mode/qemu_snapshot/gdb_client.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# get our environmental variables
+export LINUX_MODE_BASE=../
+export WTF=${LINUX_MODE_BASE}../
+export PYTHONPATH=${PYTHONPATH}:${LINUX_MODE_BASE}/qemu_snapshot
+export KERNEL=${LINUX_MODE_BASE}qemu_snapshot/target_vm/linux/vmlinux
+export LINUX_GDB=${LINUX_MODE_BASE}qemu_snapshot/target_vm/linux/scripts/gdb/vmlinux-gdb.py
+
+# initialize gdb
+gdb \
+ ${KERNEL} \
+ -q \
+ -ex "set pagination off" \
+ -iex "add-auto-load-safe-path ${LINUX_GDB}" \
+ -ex "set confirm off" \
+ -ex "target remote localhost:1234" \
+ -x ./bkpt.py \
+ -ex continue
diff --git a/linux_mode/qemu_snapshot/gdb_fuzzbkpt.py b/linux_mode/qemu_snapshot/gdb_fuzzbkpt.py
new file mode 100755
index 0000000..a02c0ca
--- /dev/null
+++ b/linux_mode/qemu_snapshot/gdb_fuzzbkpt.py
@@ -0,0 +1,512 @@
+# Jason Crowder - February 2024
+# imports
+import gdb
+import os
+import re
+import sys
+import inspect
+import time
+import socket
+import select
+import struct
+import subprocess
+import pathlib
+
+currentdir = pathlib.Path(inspect.getfile(inspect.currentframe()))
+currentdir = currentdir.absolute().parent
+sys.path.insert(0, str(currentdir.parent / "qemu_snapshot"))
+
+import gdb_utils
+
+# set environmental variables
+os.environ["PWNLIB_NOTERM"] = "1"
+import pwn
+
+# set architecture
+pwn.context.arch = "amd64"
+
+SYMSTORE_FILENAME = pathlib.Path("symbol-store.json")
+RAW_FILENAME = pathlib.Path("raw")
+DMP_FILENAME = pathlib.Path("mem.dmp")
+REGS_JSON_FILENAME = pathlib.Path("regs.json")
+
+
+class dump_file:
+ PAGE_SIZE = 0x1000
+
+ HEADER64_SIZE = 8248
+ EXPECTED_SIGNATURE = b"PAGE"
+ EXPECTED_VALID_DUMP = b"DU64"
+ BMPDUMP = 5
+
+ BMP_HEADER64_SIZE = 56
+ BMP_EXPECTED_SIGNATURE = b"SDMP"
+ BMP_EXPECTED_VALID_DUMP = b"DUMP"
+
+ def convert_raw_to_dmp(out_filename: pathlib.Path):
+ dump_size = RAW_FILENAME.stat().st_size
+ pages_count = int(dump_size / dump_file.PAGE_SIZE)
+ bitmap_size = int(pages_count / 8)
+
+ out_file = out_filename.open("wb")
+
+ print(f"Converting raw file '{RAW_FILENAME}' to dump file '{out_filename}'")
+
+ d = dump_file.EXPECTED_SIGNATURE
+ assert len(d) == 4
+
+ d += dump_file.EXPECTED_VALID_DUMP
+ assert len(d) == 8
+
+ d += b"\x00" * (3992 - len(d))
+ assert len(d) == 3992
+
+ d += struct.pack("comm")
+ )
+
+ process_name = process_name.replace('"', "")
+ process_name = process_name[: process_name.find("\\0")]
+
+ return process_name
+
+
+# Fuzzing Breakpoint Class, wrapper for gdb breakpoint
+class FuzzBkpt(gdb.Breakpoint):
+ # intializes the breakpoint
+ def __init__(
+ self,
+ target_dir,
+ addr,
+ program_name,
+ checkname=True,
+ bp_hits_required=1,
+ target_base=0x555555554000,
+ sym_path=None,
+ ):
+ target_syms_dict = {}
+
+ if sym_path:
+ text_offset = self.get_text_offset(sym_path)
+
+ gdb.execute(f"add-symbol-file {sym_path} {target_base + text_offset}")
+
+ target_syms_list = self.get_target_syms(sym_path)
+ for name, rva in target_syms_list:
+ va = target_base + rva
+ target_syms_dict[name] = hex(va)
+
+ target_syms_dict[program_name] = hex(target_base)
+
+ gdb_utils.write_to_store(target_syms_dict)
+
+ print(f"Removing '{REGS_JSON_FILENAME}' file if it exists...")
+ REGS_JSON_FILENAME.unlink(missing_ok=True)
+
+ # convert address into format that gdb takes: break *0xFFFF
+ loc = f"""*{addr}"""
+
+ # intialize the gdb breakpoint
+ gdb.Breakpoint.__init__(self, spec=loc, type=gdb.BP_HARDWARE_BREAKPOINT)
+
+ target_dir = pathlib.Path(os.environ["WTF"]) / "targets" / target_dir
+ target_dir = target_dir.absolute().resolve()
+ print(f"Using '{target_dir}' as target directory")
+ self.target_dir = target_dir
+
+ print(f"mkdir '{target_dir}'")
+ target_dir.mkdir(exist_ok=True)
+
+ dirs = ("crashes", "inputs", "outputs", "state")
+ for d in dirs:
+ new_dir = self.target_dir / d
+ print(f"mkdir '{new_dir}'")
+ new_dir.mkdir(exist_ok=True)
+
+ # set the program name and whether or not we should check the name
+ self.program_name = program_name
+ self.checkname = checkname
+ self.bp_hits = 0
+ self.bp_hits_required = bp_hits_required
+
+ self.got_base = False
+
+ # save the addresses for the kernel exception handlers
+ self.save_kernel_exception_handlers()
+
+ self.orig_bytes = None
+ self.start_orig_rip = None
+
+ self.mlock = False
+
+ self.did_snapshot = False
+
+ # function that is called when the breakpoint is hit
+ def stop(self):
+ if self.did_snapshot:
+ return False
+
+ print(f"In right process? {self.is_my_program()}")
+ if self.checkname and not self.is_my_program():
+ return False
+ self.bp_hits += 1
+ if self.bp_hits < self.bp_hits_required:
+ print(
+ f"Hit bp {self.bp_hits} time, but need to hit it {self.bp_hits_required} times"
+ )
+ return False
+
+ if not self.mlock:
+ self.call_mlockall()
+ self.mlock = True
+ return False
+
+ self.restore_orig_bytes()
+
+ def wait_for_cpu_regs_dump():
+ print("In the QEMU tab, press Ctrl+C, run the `cpu` command")
+ while not REGS_JSON_FILENAME.exists():
+ time.sleep(1)
+
+ file_size = REGS_JSON_FILENAME.stat()
+ # Make sure entirety of regs file has been written
+ while True:
+ time.sleep(1)
+ new_file_size = REGS_JSON_FILENAME.stat()
+ if file_size == new_file_size:
+ break
+ file_size = new_file_size
+ print(f"Detected cpu registers dumped to '{REGS_JSON_FILENAME}'")
+ # sleep for a few seconds to allow Qemu to continue
+ time.sleep(3)
+
+ wait_for_cpu_regs_dump()
+
+ qemu_monitor.write_phys_mem_file_to_disk()
+
+ out_filename = self.target_dir / "state" / DMP_FILENAME
+ dump_file.convert_raw_to_dmp(out_filename)
+
+ files = (REGS_JSON_FILENAME, SYMSTORE_FILENAME)
+ for f in files:
+ dst = self.target_dir / "state" / f
+ print(f"mv '{f}' '{dst}'")
+ f.replace(dst)
+ print("Snapshotting complete")
+ self.did_snapshot = True
+ return True
+
+ # checks if the current program is the program we wanted
+ # does this by checking if the program names match
+ def is_my_program(self):
+ curr_program_name = self.get_name()
+ return self.program_name in curr_program_name
+
+ def get_target_syms(self, target_file):
+ syms = []
+ output = subprocess.check_output(["nm", target_file]).decode().split("\n")
+ for line in output:
+ try:
+ (rva, t, name) = line.split(" ")
+ rva = int(rva, 16)
+ except ValueError:
+ continue
+
+ if t not in ("t", "T"):
+ continue
+
+ syms.append((name, rva))
+
+ return syms
+
+ def get_text_offset(self, target_file):
+ output = (
+ subprocess.check_output(["readelf", "-S", target_file]).decode().split("\n")
+ )
+
+ text_info_line = None
+ for line in filter(lambda x: ".text" in x, output):
+ text_info_line = line
+ break
+
+ # Example:
+ # [16] .text PROGBITS 0000000000001100 00001100
+ offset = int(text_info_line.split()[-2], 16)
+
+ return offset
+
+ # saves all of the kernel exception handlers
+ # used for context switch or crashes
+ def save_kernel_exception_handlers(self):
+ entry_syscall = kernel.parse_variable("entry_SYSCALL_64")
+ asm_exc_page_fault = kernel.parse_variable("asm_exc_page_fault")
+ asm_exc_divide_error = kernel.parse_variable("asm_exc_divide_error")
+ force_sigsegv = kernel.parse_variable("force_sigsegv")
+ page_fault_oops = kernel.parse_variable("page_fault_oops")
+
+ gdb_utils.write_to_store(
+ {
+ "entry_syscall": hex(entry_syscall),
+ "asm_exc_page_fault": hex(asm_exc_page_fault),
+ "asm_exc_divide_error": hex(asm_exc_divide_error),
+ "force_sigsegv": hex(force_sigsegv),
+ "page_fault_oops": hex(page_fault_oops),
+ }
+ )
+
+ # gets the name of the current running process
+ def get_name(self):
+ return kernel.task.get_name()
+
+ def save_orig_bytes(self, start_addr, num_bytes):
+ if self.orig_bytes is None:
+ print(f"Saving {num_bytes} bytes at 0x{start_addr:x}")
+ self.orig_bytes = []
+ self.start_orig_rip = start_addr
+ addr_to_read = start_addr
+ for i in range(num_bytes):
+ self.orig_bytes.append(kernel.read_byte(addr_to_read))
+ addr_to_read += 1
+ elif start_addr < self.start_orig_rip:
+ num_bytes = self.start_orig_rip - start_addr
+ print(
+ f"Saving {num_bytes} bytes from 0x{start_addr:x} to 0x{self.start_orig_rip:x}"
+ )
+ self.start_orig_rip = start_addr
+ addr_to_read = start_addr
+ prepend_bytes = []
+ for i in range(num_bytes):
+ prepend_bytes.append(kernel.read_byte(addr_to_read))
+ addr_to_read += 1
+ self.orig_bytes = prepend_bytes + self.orig_bytes
+
+ def restore_orig_bytes(self):
+ if self.orig_bytes is None:
+ return
+ addr_to_write = self.start_orig_rip
+ print(f"Restoring {len(self.orig_bytes)} bytes at 0x{addr_to_write:x}")
+ for b in self.orig_bytes:
+ kernel.write_byte(addr_to_write, b)
+ addr_to_write += 1
+ print("Restored")
+
+ def call_mlockall(self):
+ print("Calling mlockall")
+
+ # assembly code to call mlock. Saves and restores all registers so they are
+ # unaffected by the call.
+ mlockall = """
+ push rax
+ push rbx
+ push rcx
+ push rdx
+ push rbp
+ push rdi
+ push rsi
+ push r8
+ push r9
+ push r10
+ push r11
+ push r12
+ push r13
+ push r14
+ push r15
+ mov rax, 0x97
+ mov rdi, 0x3
+ syscall
+ """
+
+ # Used to detect when mlockall fails. You will see your target stop like this:
+ # $ ./a.out
+ # Press enter.
+ #
+ # Trace/breakpoint trap (core dumped)
+ mlockall += """
+ test eax, eax
+ jz no_err
+ int3
+ no_err:
+ """
+
+ mlockall += """
+ pop r15
+ pop r14
+ pop r13
+ pop r12
+ pop r11
+ pop r10
+ pop r9
+ pop r8
+ pop rsi
+ pop rdi
+ pop rbp
+ pop rdx
+ pop rcx
+ pop rbx
+ pop rax
+ """
+
+ # assemble the shellcode
+ shellcode = pwn.asm(mlockall)
+
+ # gets the current instruction pointer
+ rip = kernel.cpu.get_reg("rip")
+
+ # get the address for the shellcode
+ shellcode_addr = rip - len(shellcode)
+
+ self.save_orig_bytes(shellcode_addr, len(shellcode))
+
+ # write the shellcode
+
+ # run the original code that was ahead of the ip so that we can restore it.
+ addr_to_write = shellcode_addr
+ for b in shellcode:
+ kernel.write_byte(addr_to_write, b)
+ addr_to_write += 1
+
+ # set the instruction pointer to the start of the shellcode
+ kernel.cpu.set_reg("rip", shellcode_addr)
diff --git a/linux_mode/qemu_snapshot/gdb_qemu.py b/linux_mode/qemu_snapshot/gdb_qemu.py
new file mode 100755
index 0000000..42d2180
--- /dev/null
+++ b/linux_mode/qemu_snapshot/gdb_qemu.py
@@ -0,0 +1,238 @@
+# Jason Crowder - February 2024
+# imports
+import gdb, json, sys, pathlib
+
+sys.path.insert(1, str(pathlib.Path(__file__).parent))
+
+import gdb_utils
+
+REGS_JSON_FILENAME = pathlib.Path("regs.json")
+
+cpu_state = 0
+
+
+class QemuBkpt(gdb.Breakpoint):
+ def __init__(self, function):
+ gdb.Breakpoint.__init__(
+ self, function=function, type=gdb.BP_HARDWARE_BREAKPOINT
+ )
+
+ def stop(self):
+ global cpu_state
+ cpu_state = gdb.parse_and_eval("cpu")
+
+
+# creates the cpu command used to dump the cpu state
+class DumpCPUStateCommand(gdb.Command):
+ # function init
+ def __init__(self):
+ super(DumpCPUStateCommand, self).__init__(
+ "cpu", gdb.COMMAND_USER, gdb.COMPLETE_FILENAME
+ )
+
+ # dump state to the file passed to the function
+ def dump(self, f):
+ # grabs a register
+ def get_reg(x):
+ global cpu_state
+ return gdb.parse_and_eval(
+ f"""((CPUX86State*)((CPUState*)({cpu_state}))->env_ptr)->{x}"""
+ )
+
+ data = {}
+
+ data["rax"] = hex(get_reg("regs[0]"))
+ data["rcx"] = hex(get_reg("regs[1]"))
+ data["rdx"] = hex(get_reg("regs[2]"))
+ data["rbx"] = hex(get_reg("regs[3]"))
+ data["rsp"] = hex(get_reg("regs[4]"))
+ data["rbp"] = hex(get_reg("regs[5]"))
+ data["rsi"] = hex(get_reg("regs[6]"))
+ data["rdi"] = hex(get_reg("regs[7]"))
+ data["r8"] = hex(get_reg("regs[8]"))
+ data["r9"] = hex(get_reg("regs[9]"))
+ data["r10"] = hex(get_reg("regs[10]"))
+ data["r11"] = hex(get_reg("regs[11]"))
+ data["r12"] = hex(get_reg("regs[12]"))
+ data["r13"] = hex(get_reg("regs[13]"))
+ data["r14"] = hex(get_reg("regs[14]"))
+ data["r15"] = hex(get_reg("regs[15]"))
+
+ data["rip"] = hex(get_reg("eip"))
+ data["rflags"] = hex(get_reg("eflags"))
+ data["dr0"] = hex(get_reg("dr[0]"))
+ data["dr1"] = hex(get_reg("dr[1]"))
+ data["dr2"] = hex(get_reg("dr[2]"))
+ data["dr3"] = hex(get_reg("dr[3]"))
+ data["dr6"] = hex(get_reg("dr[6]"))
+ data["dr7"] = hex(get_reg("dr[7]"))
+
+ def update_attr(val, limit):
+ # Satisfy wtf sanity checks
+ # https://github.com/0vercl0k/wtf/blob/main/src/wtf/utils.cc#L237
+ val = val >> 8
+ return val | ((limit & 0xF0000) >> 8)
+
+ limit = get_reg("segs[0].limit")
+ attr = update_attr(get_reg("segs[0].flags"), limit)
+ data["es"] = {
+ "present": True,
+ "selector": hex(get_reg("segs[0].selector")),
+ "base": hex(get_reg("segs[0].base")),
+ "limit": hex(limit),
+ "attr": hex(attr),
+ }
+
+ limit = get_reg("segs[1].limit")
+ attr = update_attr(get_reg("segs[1].flags"), limit)
+ data["cs"] = {
+ "present": True,
+ "selector": hex(get_reg("segs[1].selector")),
+ "base": hex(get_reg("segs[1].base")),
+ "limit": hex(limit),
+ "attr": hex(attr),
+ }
+
+ limit = get_reg("segs[2].limit")
+ attr = update_attr(get_reg("segs[2].flags"), limit)
+ data["ss"] = {
+ "present": True,
+ "selector": hex(get_reg("segs[2].selector")),
+ "base": hex(get_reg("segs[2].base")),
+ "limit": hex(limit),
+ "attr": hex(attr),
+ }
+
+ limit = get_reg("segs[3].limit")
+ attr = update_attr(get_reg("segs[3].flags"), limit)
+ data["ds"] = {
+ "present": True,
+ "selector": hex(get_reg("segs[3].selector")),
+ "base": hex(get_reg("segs[3].base")),
+ "limit": hex(limit),
+ "attr": hex(attr),
+ }
+
+ limit = get_reg("segs[4].limit")
+ attr = update_attr(get_reg("segs[4].flags"), limit)
+ data["fs"] = {
+ "present": True,
+ "selector": hex(get_reg("segs[4].selector")),
+ "base": hex(get_reg("segs[4].base")),
+ "limit": hex(limit),
+ "attr": hex(attr),
+ }
+
+ limit = get_reg("segs[5].limit")
+ attr = update_attr(get_reg("segs[5].flags"), limit)
+ data["gs"] = {
+ "present": True,
+ "selector": hex(get_reg("segs[5].selector")),
+ "base": hex(get_reg("segs[5].base")),
+ "limit": hex(limit),
+ "attr": hex(attr),
+ }
+
+ limit = get_reg("tr.limit")
+ attr = update_attr(get_reg("tr.flags"), limit)
+ # https://github.com/awslabs/snapchange/blob/a3db58d2545a34a18fcf3128d403deb0f78b3bba/src/cmdline.rs#L1047
+ # Ensure TR.access rights has the 64-bit busy TSS enabled
+ attr |= 0xB
+ data["tr"] = {
+ "present": True,
+ "selector": hex(get_reg("tr.selector")),
+ "base": hex(get_reg("tr.base")),
+ "limit": hex(limit),
+ "attr": hex(attr),
+ }
+
+ limit = get_reg("ldt.limit")
+ attr = update_attr(get_reg("ldt.flags"), limit)
+ data["ldtr"] = {
+ "present": True,
+ "selector": hex(get_reg("ldt.selector")),
+ "base": hex(get_reg("ldt.base")),
+ "limit": hex(limit),
+ "attr": hex(attr),
+ }
+
+ data["tsc"] = hex(get_reg("tsc"))
+
+ data["sysenter_cs"] = hex(get_reg("sysenter_cs"))
+ data["sysenter_esp"] = hex(get_reg("sysenter_esp"))
+ data["sysenter_eip"] = hex(get_reg("sysenter_eip"))
+
+ data["pat"] = hex(get_reg("pat"))
+
+ data["efer"] = hex(get_reg("efer"))
+
+ data["star"] = hex(get_reg("star"))
+ data["lstar"] = hex(get_reg("lstar"))
+
+ data["cstar"] = hex(get_reg("cstar"))
+ data["fmask"] = hex(get_reg("fmask"))
+ data["kernel_gs_base"] = hex(get_reg("kernelgsbase"))
+ data["tsc_aux"] = hex(get_reg("tsc_aux"))
+
+ data["mxcsr"] = hex(get_reg("mxcsr"))
+
+ data["cr0"] = hex(get_reg("cr[0]"))
+ data["cr2"] = hex(get_reg("cr[2]"))
+ data["cr3"] = hex(get_reg("cr[3]"))
+ data["cr4"] = hex(get_reg("cr[4]"))
+ data["cr8"] = "0x0"
+
+ data["xcr0"] = hex(get_reg("xcr0"))
+
+ data["gdtr"] = {
+ "base": hex(get_reg("gdt.base")),
+ "limit": hex(get_reg("gdt.limit")),
+ }
+
+ data["idtr"] = {
+ "base": hex(get_reg("idt.base")),
+ "limit": hex(get_reg("idt.limit")),
+ }
+
+ data["fpop"] = hex(get_reg("fpop"))
+
+ data["apic_base"] = "0xfee00900"
+ data["sfmask"] = "0x4700"
+ data["fpcw"] = "0x27f"
+ data["fpsw"] = "0x0"
+ data["fptw"] = "0xffff"
+ data["mxcsr_mask"] = "0x0"
+ data["fpst"] = [{"fraction": "0x0", "exp": "0x0"}] * 8
+
+ # writes the data dictionary to the file
+ json.dump(data, f)
+
+ # updates entry_syscall in symbol-store.json
+ gdb_utils.write_to_store({"entry_syscall": data["lstar"]})
+
+ # function that gets called when the cpu command has been called
+ def invoke(self, args, from_tty):
+ global cpu_state
+
+ self.dont_repeat()
+
+ print(f"cpu_state: 0x{cpu_state}")
+ print(f"Writing register information to '{REGS_JSON_FILENAME}'")
+
+ # dump the cpu state to regs.json
+ with REGS_JSON_FILENAME.open("w") as f:
+ self.dump(f)
+
+ print("Done...continuing debuggee")
+ gdb.execute("continue")
+
+
+DumpCPUStateCommand()
+
+# When HW acceleration is enabled
+QemuBkpt("kvm_cpu_exec")
+
+# When SW emulation is used
+QemuBkpt("cpu_exec")
+
+gdb.execute("continue")
diff --git a/linux_mode/qemu_snapshot/gdb_server.sh b/linux_mode/qemu_snapshot/gdb_server.sh
new file mode 100755
index 0000000..523dfb0
--- /dev/null
+++ b/linux_mode/qemu_snapshot/gdb_server.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# get our environmental variables
+export LINUX_MODE_BASE=../
+export GDB_QEMU_PY_SCRIPT=${LINUX_MODE_BASE}qemu_snapshot/gdb_qemu.py
+export QEMU=${LINUX_MODE_BASE}qemu_snapshot/target_vm/qemu/build/qemu-system-x86_64
+export KERNEL=${LINUX_MODE_BASE}qemu_snapshot/target_vm/linux/arch/x86_64/boot/bzImage
+export IMAGE=${LINUX_MODE_BASE}qemu_snapshot/target_vm/image/bookworm.img
+
+gdb \
+ -q \
+ --ex "set pagination off" \
+ --ex "set confirm off" \
+ --ex "starti" \
+ --ex "handle SIGUSR1 noprint nostop" \
+ -x ${GDB_QEMU_PY_SCRIPT} \
+ --args ${QEMU} \
+ -m 2G \
+ -smp 1 \
+ -kernel ${KERNEL} \
+ -append "console=ttyS0 root=/dev/sda earlyprintk=serial noapic ibpb=off ibrs=off kpti=0 l1tf=off mds=off mitigations=off no_stf_barrier noibpb noibrs pcil" \
+ -machine type=pc,accel=kvm \
+ -drive file=${IMAGE} \
+ -net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \
+ -monitor tcp:127.0.0.1:55555,server,nowait \
+ -s \
+ -net nic,model=e1000 \
+ -nographic \
+ -pidfile vm.pid \
+ 2>&1 | tee vm.log
diff --git a/linux_mode/qemu_snapshot/gdb_utils.py b/linux_mode/qemu_snapshot/gdb_utils.py
new file mode 100755
index 0000000..1eb8442
--- /dev/null
+++ b/linux_mode/qemu_snapshot/gdb_utils.py
@@ -0,0 +1,23 @@
+# Jason Crowder - February 2024
+# This file contains shared code between Python modules used for Qemu and the
+# Linux kernel
+import json, pathlib
+
+# symbol store filename
+SYMBOL_STORE = pathlib.Path("symbol-store.json")
+
+
+# write data to the symbol store file
+def write_to_store(content):
+ # if the file doesn't exist then create it
+ if not SYMBOL_STORE.exists():
+ SYMBOL_STORE.write_text("{}")
+
+ # read the symbol store data into data variable
+ data = json.loads(SYMBOL_STORE.read_text("utf-8"))
+
+ # update the dictionary
+ data.update(content)
+
+ # write the data to the symbol store file
+ SYMBOL_STORE.write_text(json.dumps(data))
diff --git a/linux_mode/qemu_snapshot/setup.sh b/linux_mode/qemu_snapshot/setup.sh
new file mode 100755
index 0000000..50c724e
--- /dev/null
+++ b/linux_mode/qemu_snapshot/setup.sh
@@ -0,0 +1,3 @@
+pushd ./target_vm
+./init.sh
+popd
diff --git a/linux_mode/qemu_snapshot/target_vm/LICENSE b/linux_mode/qemu_snapshot/target_vm/LICENSE
new file mode 100644
index 0000000..19dc35b
--- /dev/null
+++ b/linux_mode/qemu_snapshot/target_vm/LICENSE
@@ -0,0 +1,175 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
\ No newline at end of file
diff --git a/linux_mode/qemu_snapshot/target_vm/NOTICE b/linux_mode/qemu_snapshot/target_vm/NOTICE
new file mode 100644
index 0000000..f48b352
--- /dev/null
+++ b/linux_mode/qemu_snapshot/target_vm/NOTICE
@@ -0,0 +1 @@
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
\ No newline at end of file
diff --git a/linux_mode/qemu_snapshot/target_vm/README.md b/linux_mode/qemu_snapshot/target_vm/README.md
new file mode 100644
index 0000000..30e8a06
--- /dev/null
+++ b/linux_mode/qemu_snapshot/target_vm/README.md
@@ -0,0 +1,4 @@
+This code is based on code from Snapchange[^1], and is used to create a target
+Linux VM.
+
+[^1]: https://github.com/awslabs/snapchange
\ No newline at end of file
diff --git a/linux_mode/qemu_snapshot/target_vm/connect.sh b/linux_mode/qemu_snapshot/target_vm/connect.sh
new file mode 100755
index 0000000..e249b39
--- /dev/null
+++ b/linux_mode/qemu_snapshot/target_vm/connect.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+ssh -i ./image/bookworm.id_rsa -p 10021 -o "StrictHostKeyChecking no" root@localhost
diff --git a/linux_mode/qemu_snapshot/target_vm/image/.gitignore b/linux_mode/qemu_snapshot/target_vm/image/.gitignore
new file mode 100644
index 0000000..b7ca8f4
--- /dev/null
+++ b/linux_mode/qemu_snapshot/target_vm/image/.gitignore
@@ -0,0 +1,4 @@
+bookworm.id_rsa
+bookworm.id_rsa.pub
+bookworm.img
+chroot/
\ No newline at end of file
diff --git a/linux_mode/qemu_snapshot/target_vm/image/create-image.sh b/linux_mode/qemu_snapshot/target_vm/image/create-image.sh
new file mode 100755
index 0000000..eff58c9
--- /dev/null
+++ b/linux_mode/qemu_snapshot/target_vm/image/create-image.sh
@@ -0,0 +1,200 @@
+#!/usr/bin/env bash
+# Copyright 2016 syzkaller project authors. All rights reserved.
+# Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+# create-image.sh creates a minimal Debian Linux image suitable for syzkaller.
+
+set -eux
+
+# Create a minimal Debian distribution in a directory.
+DIR=chroot
+PREINSTALL_PKGS=openssh-server,curl,tar,gcc,libc6-dev,time,strace,sudo,less,psmisc,selinux-utils,policycoreutils,checkpolicy,selinux-policy-default,debian-ports-archive-keyring,vim,tmux,make,git,libc6-dbg,gcc-multilib
+
+# Variables affected by options
+ARCH=$(uname -m)
+RELEASE=bookworm
+FEATURE=minimal
+SEEK=3072
+PERF=false
+
+# Display help function
+display_help() {
+ echo "Usage: $0 [option...] " >&2
+ echo
+ echo " -a, --arch Set architecture"
+ echo " -d, --distribution Set on which debian distribution to create"
+ echo " -f, --feature Check what packages to install in the image, options are minimal, full"
+ echo " -s, --seek Image size (MB), default 2048 (2G)"
+ echo " -h, --help Display help message"
+ echo " -p, --add-perf Add perf support with this option enabled. Please set envrionment variable \$KERNEL at first"
+ echo
+}
+
+while true; do
+ if [ $# -eq 0 ];then
+ echo $#
+ break
+ fi
+ case "$1" in
+ -h | --help)
+ display_help
+ exit 0
+ ;;
+ -a | --arch)
+ ARCH=$2
+ shift 2
+ ;;
+ -d | --distribution)
+ RELEASE=$2
+ shift 2
+ ;;
+ -f | --feature)
+ FEATURE=$2
+ shift 2
+ ;;
+ -s | --seek)
+ SEEK=$(($2 - 1))
+ shift 2
+ ;;
+ -p | --add-perf)
+ PERF=true
+ shift 1
+ ;;
+ -*)
+ echo "Error: Unknown option: $1" >&2
+ exit 1
+ ;;
+ *) # No more options
+ break
+ ;;
+ esac
+done
+
+# Handle cases where qemu and Debian use different arch names
+case "$ARCH" in
+ ppc64le)
+ DEBARCH=ppc64el
+ ;;
+ aarch64)
+ DEBARCH=arm64
+ ;;
+ arm)
+ DEBARCH=armel
+ ;;
+ x86_64)
+ DEBARCH=amd64
+ ;;
+ *)
+ DEBARCH=$ARCH
+ ;;
+esac
+
+# Foreign architecture
+
+FOREIGN=false
+if [ $ARCH != $(uname -m) ]; then
+ # i386 on an x86_64 host is exempted, as we can run i386 binaries natively
+ if [ $ARCH != "i386" -o $(uname -m) != "x86_64" ]; then
+ FOREIGN=true
+ fi
+fi
+
+if [ $FOREIGN = "true" ]; then
+ # Check for according qemu static binary
+ if ! which qemu-$ARCH-static; then
+ echo "Please install qemu static binary for architecture $ARCH (package 'qemu-user-static' on Debian/Ubuntu/Fedora)"
+ exit 1
+ fi
+ # Check for according binfmt entry
+ if [ ! -r /proc/sys/fs/binfmt_misc/qemu-$ARCH ]; then
+ echo "binfmt entry /proc/sys/fs/binfmt_misc/qemu-$ARCH does not exist"
+ exit 1
+ fi
+fi
+
+# Double check KERNEL when PERF is enabled
+if [ $PERF = "true" ] && [ -z ${KERNEL+x} ]; then
+ echo "Please set KERNEL environment variable when PERF is enabled"
+ exit 1
+fi
+
+# If full feature is chosen, install more packages
+if [ $FEATURE = "full" ]; then
+ PREINSTALL_PKGS=$PREINSTALL_PKGS","$ADD_PACKAGE
+fi
+
+sudo rm -rf $DIR
+sudo mkdir -p $DIR
+sudo chmod 0755 $DIR
+
+# 1. debootstrap stage
+
+DEBOOTSTRAP_PARAMS="--arch=$DEBARCH --include=$PREINSTALL_PKGS --components=main,contrib,non-free $RELEASE $DIR"
+if [ $FOREIGN = "true" ]; then
+ DEBOOTSTRAP_PARAMS="--foreign $DEBOOTSTRAP_PARAMS"
+fi
+
+# riscv64 is hosted in the debian-ports repository
+# debian-ports doesn't include non-free, so we exclude firmware-atheros
+if [ $DEBARCH == "riscv64" ]; then
+ DEBOOTSTRAP_PARAMS="--keyring /usr/share/keyrings/debian-ports-archive-keyring.gpg --exclude firmware-atheros $DEBOOTSTRAP_PARAMS http://deb.debian.org/debian-ports"
+fi
+sudo debootstrap $DEBOOTSTRAP_PARAMS
+
+# 2. debootstrap stage: only necessary if target != host architecture
+
+if [ $FOREIGN = "true" ]; then
+ sudo cp $(which qemu-$ARCH-static) $DIR/$(which qemu-$ARCH-static)
+ sudo chroot $DIR /bin/bash -c "/debootstrap/debootstrap --second-stage"
+fi
+
+# Set some defaults and enable promtless ssh to the machine for root.
+sudo sed -i '/^root/ { s/:x:/::/ }' $DIR/etc/passwd
+echo 'T0:23:respawn:/sbin/getty -L ttyS0 115200 vt100' | sudo tee -a $DIR/etc/inittab
+printf '\nauto enp0s3\niface enp0s3 inet dhcp\n' | sudo tee -a $DIR/etc/network/interfaces
+echo '/dev/root / ext4 defaults 0 0' | sudo tee -a $DIR/etc/fstab
+echo 'debugfs /sys/kernel/debug debugfs defaults 0 0' | sudo tee -a $DIR/etc/fstab
+echo 'securityfs /sys/kernel/security securityfs defaults 0 0' | sudo tee -a $DIR/etc/fstab
+echo 'configfs /sys/kernel/config/ configfs defaults 0 0' | sudo tee -a $DIR/etc/fstab
+echo 'binfmt_misc /proc/sys/fs/binfmt_misc binfmt_misc defaults 0 0' | sudo tee -a $DIR/etc/fstab
+echo -en "127.0.0.1\tlocalhost\n" | sudo tee $DIR/etc/hosts
+echo "nameserver 8.8.8.8" | sudo tee -a $DIR/etc/resolve.conf
+echo 'kernel.randomize_va_space = 0' | sudo tee -a $DIR/etc/sysctl.d/01-disable-aslr.conf
+echo "linux" | sudo tee $DIR/etc/hostname
+echo "* hard memlock unlimited" | sudo tee -a $DIR/etc/security/limits.conf
+echo "* soft memlock unlimited" | sudo tee -a $DIR/etc/security/limits.conf
+echo "root hard memlock unlimited" | sudo tee -a $DIR/etc/security/limits.conf
+echo "root soft memlock unlimited" | sudo tee -a $DIR/etc/security/limits.conf
+# Example for setting afl-system-config.sh to run automatically on reboot
+#sudo cp ./afl-system-config.sh $DIR/root
+#sudo chroot $DIR /bin/bash -c "(crontab -l 2>/dev/null; echo \"@reboot /root/afl-system-config.sh\") | crontab -"
+ssh-keygen -f $RELEASE.id_rsa -t rsa -N ''
+sudo mkdir -p $DIR/root/.ssh/
+cat $RELEASE.id_rsa.pub | sudo tee $DIR/root/.ssh/authorized_keys
+
+# Add perf support
+if [ $PERF = "true" ]; then
+ cp -r $KERNEL $DIR/tmp/
+ BASENAME=$(basename $KERNEL)
+ sudo chroot $DIR /bin/bash -c "apt-get update; apt-get install -y flex bison python-dev libelf-dev libunwind8-dev libaudit-dev libslang2-dev libperl-dev binutils-dev liblzma-dev libnuma-dev"
+ sudo chroot $DIR /bin/bash -c "cd /tmp/$BASENAME/tools/perf/; make"
+ sudo chroot $DIR /bin/bash -c "cp /tmp/$BASENAME/tools/perf/perf /usr/bin/"
+ rm -r $DIR/tmp/$BASENAME
+fi
+
+# Add GDB
+sudo chroot $DIR /bin/bash -c "apt-get update; apt-get install -y gdb"
+
+# Add a new user
+sudo chroot $DIR /bin/bash -c "/sbin/useradd -G sudo user"
+sudo chroot $DIR /bin/bash -c "echo 'user ALL=(ALL:ALL) NOPASSWD: /usr/bin/apt-get' >> /etc/sudoers"
+sudo chroot $DIR /bin/bash -c "su user -c 'sudo apt-get update'"
+sudo chroot $DIR /bin/bash -c "su user -c 'sudo apt-get install -y gdb vim tmux make git libc6-dbg python3'"
+
+# Build a disk image
+dd if=/dev/zero of=$RELEASE.img bs=1M seek=$SEEK count=1
+sudo mkfs.ext4 -F $RELEASE.img
+sudo mkdir -p /mnt/$DIR
+sudo mount -o loop $RELEASE.img /mnt/$DIR
+sudo cp -a $DIR/. /mnt/$DIR/.
+sudo umount /mnt/$DIR
diff --git a/linux_mode/qemu_snapshot/target_vm/init.sh b/linux_mode/qemu_snapshot/target_vm/init.sh
new file mode 100755
index 0000000..6570a86
--- /dev/null
+++ b/linux_mode/qemu_snapshot/target_vm/init.sh
@@ -0,0 +1,145 @@
+#!/bin/bash
+
+DEPTH="--depth 1"
+
+# Default to v5.15, latest version of Linux that's supported in fuzzbkpt.py
+# Earliest Linux version tested is v5.15
+# Latest Linux version tested is 6.7.0-rc3
+
+# Parse the command arguments
+# --kernel-version v5.4
+# --with-kasan
+# --full
+# -h | --help
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ --kernel-version)
+ VERSION="$2"
+ # Shift past the argument
+ shift
+ # Shift past the value
+ shift
+ ;;
+ --with-kasan)
+ KASAN=1
+ # Shift past the argument
+ shift
+ ;;
+ --full)
+ DEPTH=
+ # Shift past the argument
+ shift
+ ;;
+ -h|--help)
+ echo "Usage: "
+ echo "./init.sh [--kernel-version ] [--with-kasan]"
+ echo "Example:"
+ echo "./init.sh"
+ echo "./init.sh --kernel-version v5.4 --with-kasan"
+ exit 0
+ ;;
+ *)
+ echo "Unknown argument: $1 | Options [--kernel-version|--with-kasan]"
+ exit 0
+ ;;
+ esac
+done
+
+# Immediately stop execution if an error occurs
+set -e
+
+# Use GCC 9 for Ubuntu 22 and GCC 8 for everything else
+if cat /etc/*rel* | grep "Ubuntu 22"; then
+ GCC=9
+else
+ GCC=8
+fi
+
+
+download_prereqs() {
+ # Ensure prereqs are installed
+ sudo apt install -y gcc-$GCC g++-$GCC clang make ninja-build debootstrap libelf-dev \
+ libssl-dev pkg-config flex bison gdb
+
+ sudo apt-get install -y libglib2.0-dev libpixman-1-dev python3-pip cmake
+
+ pip3 install pwntools
+
+ # If there isn't a bookworm script for debootstrap (like in Ubuntu 18.04), copy
+ # over the bullseye script as it is the same
+ if [ ! -f /usr/share/debootstrap/scripts/bookworm ]; then
+ sudo cp /usr/share/debootstrap/scripts/bullseye /usr/share/debootstrap/scripts/bookworm
+ fi
+}
+
+# Download and build an Linux image for use in QEMU snapshots
+download_linux() {
+ # If the bzImage already exists, no need to rebuild
+ if [ -f ./linux/arch/x86/boot/bzImage ]; then
+ return
+ fi
+
+ # If no specific linux kernel given, download the entire kernel
+ if [ -z "$VERSION" ]; then
+ echo "Downloading latest linux kernel"
+ git clone $DEPTH https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
+ else
+ echo "Downloading kernel version: $VERSION"
+ git clone $DEPTH --branch "$VERSION" https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
+ fi
+
+ pushd linux
+ make defconfig
+ echo CONFIG_CONFIGFS_FS=y >> .config
+ echo CONFIG_SECURITYFS=y >> .config
+ echo CONFIG_DEBUG_INFO=y >> .config
+ echo CONFIG_DEBUG_INFO_DWARF4=y >> .config
+ echo CONFIG_RELOCATABLE=n >> .config
+ echo CONFIG_RANDOMIZE_BASE=n >> .config
+ echo CONFIG_GDB_SCRIPTS=y >> .config
+ echo CONFIG_DEBUG_INFO_REDUCED=n >> .config
+
+ # Only enable KASAN if asked to
+ if [[ "$KASAN" ]]; then
+ echo CONFIG_KASAN=y >> .config
+ fi
+
+ # If gcc is not in the path already, set gcc to the active gcc
+ if ! which gcc; then
+ sudo ln -s `which gcc-$GCC` /usr/bin/gcc
+ fi
+
+ yes "" | make -j`nproc` bzImage
+ make scripts_gdb
+ popd
+}
+
+download_qemu() {
+ if [ -f ./qemu/build/qemu-system-x86_64 ]; then
+ return
+ fi
+
+ # Set up Qemu with debug build
+ git clone -b v7.1.0 https://github.com/qemu/qemu
+ pushd qemu
+ mkdir build
+ cd build
+ CXXFLAGS="-g" CFLAGS="-g" ../configure --cpu=x86_64 --target-list="x86_64-softmmu x86_64-linux-user"
+ make
+ popd
+}
+
+init_debian_image() {
+ pushd image
+ ./create-image.sh
+ popd
+}
+
+download_prereqs
+
+download_qemu
+
+# Pass the command line arguments to check for specific kernel version
+download_linux $*
+
+init_debian_image
diff --git a/linux_mode/qemu_snapshot/target_vm/scp.sh b/linux_mode/qemu_snapshot/target_vm/scp.sh
new file mode 100755
index 0000000..a8cb275
--- /dev/null
+++ b/linux_mode/qemu_snapshot/target_vm/scp.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+scp -i ./image/bookworm.id_rsa -P 10021 -o "StrictHostKeyChecking no" $1 root@localhost:/root
diff --git a/linux_mode/qemu_snapshot/target_vm/start.sh b/linux_mode/qemu_snapshot/target_vm/start.sh
new file mode 100755
index 0000000..666a84b
--- /dev/null
+++ b/linux_mode/qemu_snapshot/target_vm/start.sh
@@ -0,0 +1,14 @@
+$PWD/QEMU/build/qemu-system-x86_64 \
+ -m 4G \
+ -smp 1 \
+ -kernel $PWD/linux/arch/x86_64/boot/bzImage \
+ -append "console=ttyS0 root=/dev/sda earlyprintk=serial noapic ibpb=off ibrs=off kpti=0 l1tf=off mds=off mitigations=off no_stf_barrier noibpb noibrs pcil" \
+ -machine type=pc,accel=kvm \
+ -drive file=$PWD/image/bookworm.img \
+ -net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \
+ -net nic,model=e1000 \
+ -nographic \
+ -pidfile vm.pid \
+ 2>&1 | tee vm.log
+
+# machine type=q35,accel=kvm,dump-guest-core=on
diff --git a/pics/wtf-linux-snapshot.webp b/pics/wtf-linux-snapshot.webp
new file mode 100644
index 0000000..4f0386a
Binary files /dev/null and b/pics/wtf-linux-snapshot.webp differ
diff --git a/pics/wtf-linux.gif b/pics/wtf-linux.gif
new file mode 100644
index 0000000..1b703ce
Binary files /dev/null and b/pics/wtf-linux.gif differ
diff --git a/scripts/gen_coveragefile_ida.py b/scripts/gen_coveragefile_ida.py
index 1848f24..db08bbc 100644
--- a/scripts/gen_coveragefile_ida.py
+++ b/scripts/gen_coveragefile_ida.py
@@ -6370,47 +6370,59 @@ def adjust_SectionAlignment( self, val, section_alignment, file_alignment ):
return cache_adjust_SectionAlignment(val, section_alignment, file_alignment)
# Axel '0vercl0k' Souchet - November 1 2020
+# Jason Crowder - Feburary 2024
import json
import pathlib
import idaapi
import idautils
import idc
-def main():
- idaapi.auto_wait()
- img_base = idaapi.get_imagebase()
- filepath = pathlib.Path(idc.get_input_file_path())
- pe = PE(filepath)
- addrs = set()
- is_ntos = filepath.name == 'ntoskrnl.exe'
+def get_bbl_rvas():
for fea in idautils.Functions():
for b in idaapi.FlowChart(idaapi.get_func(fea)):
ea = b.start_ea
is_code = idaapi.is_code(idaapi.get_full_flags(ea))
rva = ea - img_base
- sect = pe.get_section_by_rva(rva)
- discardable = (sect.Characteristics & SECTION_CHARACTERISTICS['IMAGE_SCN_MEM_DISCARDABLE']) != 0
- # After a bunch of experiment, it seems that ntos.INITKDBG is the section
- # that patchguard uses to hide some of its tricks. It gets copied somewhere
- # else at runtime and gets removed from memory. So special-casing it, and ignoring
- # code residing in this section.
- initkdbg = is_ntos and sect.Name == b'INITKDBG'
- if is_code and not discardable and not initkdbg:
- addrs.add(rva)
+ if is_code:
+ yield rva
+def main_lin(filepath):
+ addrs = set(get_bbl_rvas())
+ return addrs, filepath.with_suffix('').name
+
+def main_win(filepath):
+ pe = PE(filepath)
+ addrs = set()
+ is_ntos = filepath.name == 'ntoskrnl.exe'
+ for rva in get_bbl_rvas():
+ sect = pe.get_section_by_rva(rva)
+ discardable = (sect.Characteristics & SECTION_CHARACTERISTICS['IMAGE_SCN_MEM_DISCARDABLE']) != 0
+ # After a bunch of experiment, it seems that ntos.INITKDBG is the section
+ # that patchguard uses to hide some of its tricks. It gets copied somewhere
+ # else at runtime and gets removed from memory. So special-casing it, and ignoring
+ # code residing in this section.
+ initkdbg = is_ntos and sect.Name == b'INITKDBG'
+ if not discardable and not initkdbg:
+ addrs.add(rva)
+
+ name = filepath.with_suffix('').name
+ if is_ntos:
+ name = 'nt'
+
+ return addrs, name
+
+if __name__ == '__main__':
+ idaapi.auto_wait()
+ img_base = idaapi.get_imagebase()
+ filepath = pathlib.Path(idc.get_input_file_path())
+ addrs, name = main_win(filepath) if filepath.suffix == '.exe' else main_lin(filepath)
cov = {
- 'name': filepath.with_suffix('').name,
+ 'name': name,
'addresses': sorted(addrs)
}
- if is_ntos:
- cov['name'] = 'nt'
-
outfile = filepath.with_suffix('.cov')
with open(outfile, 'w') as fd:
json.dump(cov, fd)
print(f'Done, {outfile}')
-
-if __name__ == '__main__':
- main()
\ No newline at end of file
diff --git a/src/libs/bochscpu-bins/include/bochscpu.hpp b/src/libs/bochscpu-bins/include/bochscpu.hpp
index b346a47..bc5b4d4 100644
--- a/src/libs/bochscpu-bins/include/bochscpu.hpp
+++ b/src/libs/bochscpu-bins/include/bochscpu.hpp
@@ -454,4 +454,4 @@ int32_t bochscpu_mem_virt_read(uint64_t cr3, uint64_t gva, uint8_t *hva, uintptr
void bochscpu_log_set_level(uintptr_t level);
-} // extern "C"
+} // extern "C"
\ No newline at end of file
diff --git a/src/wtf/backend.cc b/src/wtf/backend.cc
index 48d1f6b..7419d04 100644
--- a/src/wtf/backend.cc
+++ b/src/wtf/backend.cc
@@ -237,7 +237,7 @@ bool Backend_t::SaveCrash(const Gva_t ExceptionAddress,
bool Backend_t::SetBreakpoint(const char *Symbol,
const BreakpointHandler_t Handler) {
- const Gva_t Gva = Gva_t(g_Dbg.GetSymbol(Symbol));
+ const Gva_t Gva = Gva_t(g_Dbg->GetSymbol(Symbol));
if (Gva == Gva_t(0)) {
fmt::print("Could not set a breakpoint at {}.\n", Symbol);
return false;
@@ -330,6 +330,11 @@ uint64_t Backend_t::R15() { return GetReg(Registers_t::R15); }
void Backend_t::R15(const uint64_t Value) { SetReg(Registers_t::R15, Value); }
void Backend_t::R15(const Gva_t Value) { R15(Value.U64()); }
+uint64_t Backend_t::Cr2() { return GetReg(Registers_t::Cr2); }
+void Backend_t::Cr2(const uint64_t Value) { SetReg(Registers_t::Cr2, Value); }
+void Backend_t::Cr2(const Gva_t Value) { Cr2(Value.U64()); }
+
+
void Backend_t::PrintRegisters() {
const uint64_t Rax = GetReg(Registers_t::Rax), Rbx = GetReg(Registers_t::Rbx),
Rcx = GetReg(Registers_t::Rcx);
diff --git a/src/wtf/backend.h b/src/wtf/backend.h
index c28b2f8..61cbd0a 100644
--- a/src/wtf/backend.h
+++ b/src/wtf/backend.h
@@ -581,6 +581,10 @@ class Backend_t {
void R15(const uint64_t Value);
void R15(const Gva_t Value);
+ [[nodiscard]] uint64_t Cr2();
+ void Cr2(const uint64_t Value);
+ void Cr2(const Gva_t Value);
+
//
// Gets the last new coverage generated by the last executed test-case.
//
diff --git a/src/wtf/bochscpu_backend.cc b/src/wtf/bochscpu_backend.cc
index 5370c9d..c3197f1 100644
--- a/src/wtf/bochscpu_backend.cc
+++ b/src/wtf/bochscpu_backend.cc
@@ -1320,4 +1320,4 @@ void BochscpuBackend_t::DumpTenetDelta(const bool Force) {
if (NeedNewLine) {
fmt::print(TraceFile_, "\n");
}
-}
+}
\ No newline at end of file
diff --git a/src/wtf/crash_detection_umode.cc b/src/wtf/crash_detection_umode.cc
index 59100db..b66fe09 100644
--- a/src/wtf/crash_detection_umode.cc
+++ b/src/wtf/crash_detection_umode.cc
@@ -151,7 +151,7 @@ bool SetupUsermodeCrashDetectionHooks() {
return false;
}
- if (g_Dbg.GetModuleBase("verifier") > 0) {
+ if (g_Dbg->GetModuleBase("verifier") > 0) {
if (!g_Backend->SetBreakpoint(
"verifier!VerifierStopMessage", [](Backend_t *Backend) {
const uint64_t Unique = Backend->Rsp();
diff --git a/src/wtf/debugger.cc b/src/wtf/debugger.cc
index 719533d..4877395 100644
--- a/src/wtf/debugger.cc
+++ b/src/wtf/debugger.cc
@@ -1,4 +1,11 @@
// Axel '0vercl0k' Souchet - June 7 2020
#include "debugger.h"
-Debugger_t g_Dbg;
\ No newline at end of file
+DebuggerLess_t g_NoDbg;
+
+#ifdef WINDOWS
+WindowsDebugger_t WindowsDebugger;
+Debugger_t *g_Dbg = &WindowsDebugger;
+#else
+Debugger_t *g_Dbg = &g_NoDbg;
+#endif
\ No newline at end of file
diff --git a/src/wtf/debugger.h b/src/wtf/debugger.h
index d194757..2d1d912 100644
--- a/src/wtf/debugger.h
+++ b/src/wtf/debugger.h
@@ -6,6 +6,7 @@
#include "tsl/robin_map.h"
#include
#include
+#include
#include
#include
#include
@@ -14,6 +15,59 @@
namespace fs = std::filesystem;
namespace json = nlohmann;
+struct Debugger_t {
+ virtual bool Init(const fs::path &DumpPath,
+ const fs::path &SymbolFilePath) = 0;
+
+ virtual uint64_t GetModuleBase(const char *Name) const = 0;
+
+ virtual uint64_t GetSymbol(const char *Name) const = 0;
+
+ virtual const std::string &GetName(const uint64_t SymbolAddress,
+ const bool Symbolized) = 0;
+};
+
+class DebuggerLess_t : public Debugger_t {
+ std::unordered_map Symbols_;
+
+public:
+ explicit DebuggerLess_t() = default;
+ bool Init(const fs::path &DumpPath, const fs::path &SymbolFilePath) {
+ json::json Json;
+ std::ifstream SymbolFile(SymbolFilePath);
+ SymbolFile >> Json;
+ for (const auto &[Key, Value] : Json.items()) {
+ const uint64_t Address =
+ std::strtoull(Value.get().c_str(), nullptr, 0);
+ Symbols_.emplace(Key, Address);
+ }
+
+ fmt::print("The debugger instance is loaded with {} items\n",
+ Symbols_.size());
+ return true;
+ }
+
+ uint64_t GetModuleBase(const char *Name) const { return GetSymbol(Name); }
+
+ uint64_t GetSymbol(const char *Name) const {
+ if (!Symbols_.contains(Name)) {
+ fmt::print("{} could not be found in the symbol store\n", Name);
+ exit(0);
+ return 0;
+ }
+
+ return Symbols_.at(Name);
+ }
+
+ const std::string &GetName(const uint64_t SymbolAddress,
+ const bool Symbolized) {
+ static const std::string foo("hello");
+ fmt::print("GetName does not work on Linux\n");
+ exit(0);
+ return foo;
+ }
+};
+
#ifdef WINDOWS
#include "globals.h"
#include
@@ -54,7 +108,7 @@ class StdioOutputCallbacks : public IDebugOutputCallbacks {
}
};
-class Debugger_t {
+class WindowsDebugger_t : public Debugger_t {
IDebugClient *Client_ = nullptr;
IDebugControl *Control_ = nullptr;
IDebugRegisters *Registers_ = nullptr;
@@ -65,9 +119,9 @@ class Debugger_t {
tsl::robin_map SymbolCache_;
public:
- explicit Debugger_t() = default;
+ explicit WindowsDebugger_t() = default;
- ~Debugger_t() {
+ ~WindowsDebugger_t() {
if (Client_) {
Client_->EndSession(DEBUG_END_ACTIVE_DETACH);
Client_->Release();
@@ -86,8 +140,8 @@ class Debugger_t {
}
}
- Debugger_t(const Debugger_t &) = delete;
- Debugger_t &operator=(const Debugger_t &) = delete;
+ WindowsDebugger_t(const WindowsDebugger_t &) = delete;
+ WindowsDebugger_t &operator=(const WindowsDebugger_t &) = delete;
[[nodiscard]] bool AddSymbol(const char *Name, const uint64_t Address) const {
json::json Json;
@@ -204,13 +258,13 @@ class Debugger_t {
// Turn the below on to debug issues.
//
- //#define SYMOPT_DEBUG 0x80000000
- // Status = Symbols_->SetSymbolOptions(SYMOPT_DEBUG);
- // if (FAILED(Status)) {
- // fmt::print("IDebugSymbols::SetSymbolOptions failed with
- // hr={:#x}\n", Status); return false;
- // }
- // Client_->SetOutputCallbacks(&StdioCallbacks_);
+ // #define SYMOPT_DEBUG 0x80000000
+ // Status = Symbols_->SetSymbolOptions(SYMOPT_DEBUG);
+ // if (FAILED(Status)) {
+ // fmt::print("IDebugSymbols::SetSymbolOptions failed with
+ // hr={:#x}\n", Status); return false;
+ // }
+ // Client_->SetOutputCallbacks(&StdioCallbacks_);
const std::string &DumpFileString = DumpPath.string();
const char *DumpFileA = DumpFileString.c_str();
@@ -341,48 +395,8 @@ class Debugger_t {
}
};
#else
-#include
-
-class Debugger_t {
- std::unordered_map Symbols_;
-
-public:
- explicit Debugger_t() = default;
- bool Init(const fs::path &DumpPath, const fs::path &SymbolFilePath) {
- json::json Json;
- std::ifstream SymbolFile(SymbolFilePath);
- SymbolFile >> Json;
- for (const auto &[Key, Value] : Json.items()) {
- const uint64_t Address =
- std::strtoull(Value.get().c_str(), nullptr, 0);
- Symbols_.emplace(Key, Address);
- }
-
- fmt::print("The debugger instance is loaded with {} items\n",
- Symbols_.size());
- return true;
- }
-
- uint64_t GetModuleBase(const char *Name) const { return GetSymbol(Name); }
- uint64_t GetSymbol(const char *Name) const {
- if (!Symbols_.contains(Name)) {
- fmt::print("{} could not be found in the symbol store\n", Name);
- exit(0);
- return 0;
- }
-
- return Symbols_.at(Name);
- }
-
- const std::string &GetName(const uint64_t SymbolAddress,
- const bool Symbolized) {
- static const std::string foo("hello");
- fmt::print("GetName does not work on Linux\n");
- exit(0);
- return foo;
- }
-};
#endif
-extern Debugger_t g_Dbg;
\ No newline at end of file
+extern Debugger_t *g_Dbg;
+extern DebuggerLess_t g_NoDbg;
\ No newline at end of file
diff --git a/src/wtf/fuzzer_hevd.cc b/src/wtf/fuzzer_hevd.cc
index a7e35b0..a755350 100644
--- a/src/wtf/fuzzer_hevd.cc
+++ b/src/wtf/fuzzer_hevd.cc
@@ -93,7 +93,7 @@ bool Init(const Options_t &Opts, const CpuState_t &) {
// kd> ub fffff805`3b8287c4 l1
// nt!ExGenRandom+0xe0:
// fffff805`3b8287c0 480fc7f2 rdrand rdx
- const Gva_t ExGenRandom = Gva_t(g_Dbg.GetSymbol("nt!ExGenRandom") + 0xe0 + 4);
+ const Gva_t ExGenRandom = Gva_t(g_Dbg->GetSymbol("nt!ExGenRandom") + 0xe0 + 4);
if (g_Backend->VirtRead4(ExGenRandom - Gva_t(4)) != 0xf2c70f48) {
fmt::print("It seems that nt!ExGenRandom's code has changed, update the "
"offset!\n");
diff --git a/src/wtf/fuzzer_ioctl.cc b/src/wtf/fuzzer_ioctl.cc
index 2285564..a58d941 100644
--- a/src/wtf/fuzzer_ioctl.cc
+++ b/src/wtf/fuzzer_ioctl.cc
@@ -192,7 +192,7 @@ bool Init(const Options_t &Opts, const CpuState_t &) {
// kd> ub fffff805`3b8287c4 l1
// nt!ExGenRandom+0xe0:
// fffff805`3b8287c0 480fc7f2 rdrand rdx
- const Gva_t ExGenRandom = Gva_t(g_Dbg.GetSymbol("nt!ExGenRandom") + 0xe0 + 4);
+ const Gva_t ExGenRandom = Gva_t(g_Dbg->GetSymbol("nt!ExGenRandom") + 0xe0 + 4);
if (g_Backend->VirtRead4(ExGenRandom - Gva_t(4)) != 0xf2c70f48) {
fmt::print("It seems that nt!ExGenRandom's code has changed, update the "
"offset!\n");
diff --git a/src/wtf/fuzzer_linux_crash_test.cc b/src/wtf/fuzzer_linux_crash_test.cc
new file mode 100644
index 0000000..12fdf8e
--- /dev/null
+++ b/src/wtf/fuzzer_linux_crash_test.cc
@@ -0,0 +1,67 @@
+// Jason Crowder - February 2024
+#include "backend.h"
+
+namespace linux_crash_test {
+Crash_t GetCrashTestcaseName(const char *Prefix, Backend_t *Backend) {
+ return Crash_t(fmt::format("crash-{}-{:#x}", Prefix, Backend->Cr2()));
+}
+
+bool InsertTestcase(const uint8_t *Buffer, const size_t BufferSize) {
+ if (BufferSize > 10) {
+ return true;
+ }
+
+ if (!g_Backend->VirtWriteDirty(Gva_t(g_Backend->Rdi()), Buffer, BufferSize)) {
+ fmt::print("Failed to write payload.\n");
+ return false;
+ }
+
+ return true;
+}
+
+bool Init(const Options_t &Opts, const CpuState_t &) {
+
+ if (!g_Backend->SetBreakpoint("asm_exc_page_fault", [](Backend_t *Backend) {
+ Backend->Stop(GetCrashTestcaseName("asm_exc_page_fault", Backend));
+ })) {
+ fmt::print("Failed to insert crash breakpoint.\n");
+ return false;
+ }
+
+ if (!g_Backend->SetBreakpoint("asm_exc_divide_error", [](Backend_t *Backend) {
+ Backend->Stop(GetCrashTestcaseName("asm_exc_divide_error", Backend));
+ })) {
+ fmt::print("Failed to insert crash breakpoint.\n");
+ return false;
+ }
+
+ if (!g_Backend->SetBreakpoint("force_sigsegv", [](Backend_t *Backend) {
+ Backend->Stop(GetCrashTestcaseName("force_sigsegv", Backend));
+ })) {
+ fmt::print("Failed to insert crash breakpoint.\n");
+ return false;
+ }
+
+ if (!g_Backend->SetBreakpoint("page_fault_oops", [](Backend_t *Backend) {
+ Backend->Stop(GetCrashTestcaseName("page_fault_oops", Backend));
+ })) {
+ fmt::print("Failed to insert crash breakpoint.\n");
+ return false;
+ }
+
+ if (!g_Backend->SetBreakpoint("end_crash_test", [](Backend_t *Backend) {
+ Backend->Stop(Ok_t());
+ })) {
+ return false;
+ }
+
+ return true;
+}
+
+//
+// Register the target.
+//
+
+Target_t linux_crash_test("linux_crash_test", Init, InsertTestcase);
+
+} // namespace linux_crash_test
diff --git a/src/wtf/fuzzer_linux_page_fault_test.cc b/src/wtf/fuzzer_linux_page_fault_test.cc
new file mode 100644
index 0000000..36485bc
--- /dev/null
+++ b/src/wtf/fuzzer_linux_page_fault_test.cc
@@ -0,0 +1,54 @@
+// Jason Crowder - February 2024
+#include "backend.h"
+
+namespace linux_page_fault_test {
+Crash_t GetCrashTestcaseName(const char *Prefix, Backend_t *Backend) {
+ return Crash_t(fmt::format("crash-{}-{:#x}", Prefix, Backend->Cr2()));
+}
+
+bool InsertTestcase(const uint8_t *Buffer, const size_t BufferSize) {
+ return true;
+}
+
+bool Init(const Options_t &Opts, const CpuState_t &) {
+ if (!g_Backend->SetBreakpoint("asm_exc_page_fault", [](Backend_t *Backend) {
+ Backend->Stop(GetCrashTestcaseName("asm_exc_page_fault", Backend));
+ })) {
+ fmt::print("Failed to insert crash breakpoint.\n");
+ return false;
+ }
+
+ if (!g_Backend->SetBreakpoint("asm_exc_divide_error", [](Backend_t *Backend) {
+ Backend->Stop(GetCrashTestcaseName("asm_exc_divide_error", Backend));
+ })) {
+ fmt::print("Failed to insert crash breakpoint.\n");
+ return false;
+ }
+
+ if (!g_Backend->SetBreakpoint("force_sigsegv", [](Backend_t *Backend) {
+ Backend->Stop(GetCrashTestcaseName("force_sigsegv", Backend));
+ })) {
+ fmt::print("Failed to insert crash breakpoint.\n");
+ return false;
+ }
+
+ if (!g_Backend->SetBreakpoint("page_fault_oops", [](Backend_t *Backend) {
+ Backend->Stop(GetCrashTestcaseName("page_fault_oops", Backend));
+ })) {
+ fmt::print("Failed to insert crash breakpoint.\n");
+ return false;
+ }
+
+ if (!g_Backend->SetBreakpoint("done_with_test", [](Backend_t *Backend) {
+ Backend->Stop(Ok_t());
+ })) {
+ fmt::print("Failed to insert breakpoint.\n");
+ return false;
+ }
+
+ return true;
+}
+
+Target_t linux_page_fault_test("linux_page_fault_test", Init, InsertTestcase);
+
+} // namespace linux_page_fault_test
diff --git a/src/wtf/globals.h b/src/wtf/globals.h
index bccf216..bed9188 100644
--- a/src/wtf/globals.h
+++ b/src/wtf/globals.h
@@ -1441,4 +1441,4 @@ struct Options_t {
//
MasterOptions_t Master;
-};
+};
\ No newline at end of file
diff --git a/src/wtf/kvm_backend.cc b/src/wtf/kvm_backend.cc
index e0d5166..f5cdbfd 100644
--- a/src/wtf/kvm_backend.cc
+++ b/src/wtf/kvm_backend.cc
@@ -2361,4 +2361,4 @@ void KvmBackend_t::StaticSignalAlarm(int, siginfo_t *, void *) {
KvmBackend->SignalAlarm();
}
-#endif
+#endif
\ No newline at end of file
diff --git a/src/wtf/utils.cc b/src/wtf/utils.cc
index 6660501..e2c4c0b 100644
--- a/src/wtf/utils.cc
+++ b/src/wtf/utils.cc
@@ -348,7 +348,7 @@ ParseCovFiles(const Backend_t &Backend, const fs::path &CovFilesDir) {
File >> Json;
const std::string &ModuleName = Json["name"].get();
- const uint64_t Base = g_Dbg.GetModuleBase(ModuleName.c_str());
+ const uint64_t Base = g_Dbg->GetModuleBase(ModuleName.c_str());
if (Base == 0) {
fmt::print("Failed to find the base of {}\n", ModuleName);
return std::nullopt;
@@ -483,4 +483,4 @@ ExceptionCodeToStr(const uint32_t ExceptionCode) {
return "EXCEPTION_ACCESS_VIOLATION_EXECUTE";
}
return "UNKNOWN";
-}
+}
\ No newline at end of file
diff --git a/src/wtf/whv_backend.cc b/src/wtf/whv_backend.cc
index 67be7ff..2a977af 100644
--- a/src/wtf/whv_backend.cc
+++ b/src/wtf/whv_backend.cc
@@ -1365,4 +1365,4 @@ uint64_t WhvBackend_t::SetReg(const Registers_t Reg, const uint64_t Value) {
return Value;
}
-#endif
+#endif
\ No newline at end of file
diff --git a/src/wtf/wtf.cc b/src/wtf/wtf.cc
index 2ac1bc4..67b9bcc 100644
--- a/src/wtf/wtf.cc
+++ b/src/wtf/wtf.cc
@@ -80,7 +80,7 @@ int main(int argc, const char *argv[]) {
MasterCmd->add_option("--runs", Opts.Master.Runs, "Runs")
->description("Number of mutations done.")
- ->required();
+ ->default_val(std::numeric_limits::max());
MasterCmd
->add_option("--max_len", Opts.Master.TestcaseBufferMaxSize,
@@ -400,25 +400,50 @@ int main(int argc, const char *argv[]) {
return EXIT_FAILURE;
}
+ switch (Opts.Backend) {
#ifdef WINDOWS
- if (Opts.Backend == BackendType_t::Whv) {
+ case BackendType_t::Whv: {
g_Backend = new WhvBackend_t();
+ break;
}
#endif
+
#ifdef LINUX
- if (Opts.Backend == BackendType_t::Kvm) {
+ case BackendType_t::Kvm: {
g_Backend = new KvmBackend_t();
+ break;
}
#endif
- if (Opts.Backend == BackendType_t::Bochscpu) {
+
+ case BackendType_t::Bochscpu: {
g_Backend = new BochscpuBackend_t();
+ break;
+ }
+
+ default: {
+ return EXIT_FAILURE;
+ }
}
+ //
+ // If the target name starts with 'linux', then assume that we won't be able
+ // to have WinDbg operate on the dump file, so let's swap the debugger
+ // instance.
+ //
+
+#ifdef WINDOWS
+ if (Opts.TargetName.starts_with("linux_")) {
+ fmt::print("Target name starts with 'linux_' so turning off the Windows "
+ "debugger..\n");
+ g_Dbg = &g_NoDbg;
+ }
+#endif
+
//
// Initialize the debugger instance.
//
- if (!g_Dbg.Init(Opts.DumpPath, Opts.SymbolFilePath)) {
+ if (!g_Dbg->Init(Opts.DumpPath, Opts.SymbolFilePath)) {
return EXIT_FAILURE;
}