From a77e84e3d6fa687b40ebaaa40bec25c65a167124 Mon Sep 17 00:00:00 2001
From: jasocrow <153134172+jasocrow@users.noreply.github.com>
Date: Mon, 1 Apr 2024 09:40:54 -0500
Subject: [PATCH] Add support for Linux userland ELF snapshots and fuzzing
(#192)
Co-authored-by: 0vercl0k <1476421+0vercl0k@users.noreply.github.com>
---
.github/workflows/wtf.yml | 17 +-
README.md | 20 +-
linux_mode/.gitignore | 11 +
linux_mode/README.md | 212 ++++++++
linux_mode/crash_test/README.md | 2 +
linux_mode/crash_test/bkpt.py | 17 +
linux_mode/crash_test/test.c | 40 ++
linux_mode/page_fault_test/README.md | 2 +
linux_mode/page_fault_test/bkpt.py | 16 +
linux_mode/page_fault_test/test.c | 34 ++
linux_mode/qemu_snapshot/.gitignore | 3 +
linux_mode/qemu_snapshot/gdb_client.sh | 19 +
linux_mode/qemu_snapshot/gdb_fuzzbkpt.py | 512 ++++++++++++++++++
linux_mode/qemu_snapshot/gdb_qemu.py | 238 ++++++++
linux_mode/qemu_snapshot/gdb_server.sh | 30 +
linux_mode/qemu_snapshot/gdb_utils.py | 23 +
linux_mode/qemu_snapshot/setup.sh | 3 +
linux_mode/qemu_snapshot/target_vm/LICENSE | 175 ++++++
linux_mode/qemu_snapshot/target_vm/NOTICE | 1 +
linux_mode/qemu_snapshot/target_vm/README.md | 4 +
linux_mode/qemu_snapshot/target_vm/connect.sh | 2 +
.../qemu_snapshot/target_vm/image/.gitignore | 4 +
.../target_vm/image/create-image.sh | 200 +++++++
linux_mode/qemu_snapshot/target_vm/init.sh | 145 +++++
linux_mode/qemu_snapshot/target_vm/scp.sh | 2 +
linux_mode/qemu_snapshot/target_vm/start.sh | 14 +
pics/wtf-linux-snapshot.webp | Bin 0 -> 1093030 bytes
pics/wtf-linux.gif | Bin 0 -> 80013 bytes
scripts/gen_coveragefile_ida.py | 58 +-
src/libs/bochscpu-bins/include/bochscpu.hpp | 2 +-
src/wtf/backend.cc | 7 +-
src/wtf/backend.h | 4 +
src/wtf/bochscpu_backend.cc | 2 +-
src/wtf/crash_detection_umode.cc | 2 +-
src/wtf/debugger.cc | 9 +-
src/wtf/debugger.h | 122 +++--
src/wtf/fuzzer_hevd.cc | 2 +-
src/wtf/fuzzer_ioctl.cc | 2 +-
src/wtf/fuzzer_linux_crash_test.cc | 67 +++
src/wtf/fuzzer_linux_page_fault_test.cc | 54 ++
src/wtf/globals.h | 2 +-
src/wtf/kvm_backend.cc | 2 +-
src/wtf/utils.cc | 4 +-
src/wtf/whv_backend.cc | 2 +-
src/wtf/wtf.cc | 35 +-
45 files changed, 2016 insertions(+), 106 deletions(-)
create mode 100644 linux_mode/.gitignore
create mode 100644 linux_mode/README.md
create mode 100644 linux_mode/crash_test/README.md
create mode 100644 linux_mode/crash_test/bkpt.py
create mode 100644 linux_mode/crash_test/test.c
create mode 100755 linux_mode/page_fault_test/README.md
create mode 100755 linux_mode/page_fault_test/bkpt.py
create mode 100644 linux_mode/page_fault_test/test.c
create mode 100644 linux_mode/qemu_snapshot/.gitignore
create mode 100755 linux_mode/qemu_snapshot/gdb_client.sh
create mode 100755 linux_mode/qemu_snapshot/gdb_fuzzbkpt.py
create mode 100755 linux_mode/qemu_snapshot/gdb_qemu.py
create mode 100755 linux_mode/qemu_snapshot/gdb_server.sh
create mode 100755 linux_mode/qemu_snapshot/gdb_utils.py
create mode 100755 linux_mode/qemu_snapshot/setup.sh
create mode 100644 linux_mode/qemu_snapshot/target_vm/LICENSE
create mode 100644 linux_mode/qemu_snapshot/target_vm/NOTICE
create mode 100644 linux_mode/qemu_snapshot/target_vm/README.md
create mode 100755 linux_mode/qemu_snapshot/target_vm/connect.sh
create mode 100644 linux_mode/qemu_snapshot/target_vm/image/.gitignore
create mode 100755 linux_mode/qemu_snapshot/target_vm/image/create-image.sh
create mode 100755 linux_mode/qemu_snapshot/target_vm/init.sh
create mode 100755 linux_mode/qemu_snapshot/target_vm/scp.sh
create mode 100755 linux_mode/qemu_snapshot/target_vm/start.sh
create mode 100644 pics/wtf-linux-snapshot.webp
create mode 100644 pics/wtf-linux.gif
create mode 100644 src/wtf/fuzzer_linux_crash_test.cc
create mode 100644 src/wtf/fuzzer_linux_page_fault_test.cc
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 0000000000000000000000000000000000000000..4f0386aa7a04ce608af50bb2474886d16621cec1
GIT binary patch
literal 1093030
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