├── LICENSE ├── README.md ├── TODO ├── bootloader └── grilled_cheese.asm ├── build.py └── srcs ├── boot └── boot.c ├── cpu ├── acpi.c ├── acpi.h ├── cpu.c └── cpu.h ├── disk ├── ide.c └── ide.h ├── disp ├── disp.c └── disp.h ├── dstruc ├── hash_table.c └── hash_table.h ├── emu ├── mips.c └── mips.h ├── fuzzers ├── chrome.dc ├── chrome.h ├── chrome │ ├── chrome_ipc.h │ ├── ipcgen.dc │ └── ipcgen.h ├── defender.dc ├── defender.h ├── font.dc ├── font.h ├── helpers.c ├── helpers.h ├── pdf.c ├── pdf.h ├── r2.dc ├── r2.h ├── word.c └── word.h ├── generic ├── locks.c ├── locks.h ├── stdlib.c └── stdlib.h ├── grilled_cheese.h ├── interrupts ├── interrupts.asm ├── interrupts.c └── interrupts.h ├── mm ├── mm.c └── mm.h ├── net ├── net.c ├── net.h ├── x540.c └── x540.h ├── perf ├── perf.c └── perf.h ├── rstate ├── rstate.c └── rstate.h ├── task ├── task.c └── task.h ├── time ├── time.c └── time.h └── vm ├── svm.asm ├── svm.c ├── svm.h ├── vm.c ├── vm.h ├── vm_x86.c ├── vm_x86.h ├── x86_passthrough.c ├── x86_passthrough.h ├── x86_user.c └── x86_user.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gamozo Labs, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Falkervisor (grilled_cheese) 2 | 3 | ## Twitter 4 | 5 | Follow me on Twitter, @gamozolabs 6 | Tweets go out before my reddit post spam, also I announce live streams there 7 | 8 | ## See it in action 9 | 10 | This was me using it to fuzz r2 back when this was under active development. This video should provide enough background to modify it for your own use. 11 | 12 | https://www.youtube.com/watch?v=AqFMSI8e9Qo 13 | 14 | ## History 15 | 16 | This is the latest C version of my hypervisor and probably some of the best C code I've ever written (I've since switched to Rust, you should too). This was used roughly between 2015-2016, and replaced with a Rust version in late 2016. 17 | 18 | ## Building 19 | 20 | Make sure you have `python`, `nasm`, `clang`, and `ld.lld` in your path. Then just type `python build.py`. Use `python build.py clean` to clean. 21 | 22 | It builds on Windows easily with the following (however there are no hard reqs on versions): 23 | 24 | ``` 25 | C:\dev\grilled_cheese>clang --version 26 | clang version 7.0.0 (trunk) 27 | Target: x86_64-pc-windows-msvc 28 | Thread model: posix 29 | InstalledDir: C:\Program Files\LLVM\bin 30 | 31 | C:\dev\grilled_cheese>python --version 32 | Python 3.6.5 33 | 34 | C:\dev\grilled_cheese>nasm --version 35 | NASM version 2.13.03 compiled on Feb 7 2018 36 | 37 | C:\dev\grilled_cheese>ld.lld --version 38 | LLD 7.0.0 (compatible with GNU linkers) 39 | ``` 40 | 41 | You might see some files with `*.dc` extensions. This because the build system builds any `*.c` file in the tree, thus to disable things I wasn't using I just threw a `d` in the extension. 42 | 43 | ## Feature differences from brownie 44 | 45 | See brownie here: https://github.com/gamozolabs/falkervisor_beta/ 46 | 47 | ### Improvements over brownie 48 | 49 | - It was written in C 50 | - Networking code was greatly improved 51 | - Kernel was portable to Intel (hypervisor not though) 52 | - DHCPv4 is used for getting an IP, no more hardcoded packets 53 | - Remote mapping of files 54 | - Generic VM support, designed to in theory support AMD, Intel, etc from one API 55 | - Many design features were made to reduce the chances of heisenbugs. Full page heap, full ASLR, strict mapping requirements really helped here 56 | - RSTATE error model allowed for human readable call stacks on errors without worrying about unwind info, symbols, or inlining. 57 | 58 | ### Similarities to brownie 59 | 60 | - Performance is about the same, there isn't room for growth in either tools 61 | - Only supports x540 as a network device 62 | - UDP only for networking 63 | - Custom server for communicating over UDP 64 | 65 | ## Cool features 66 | 67 | ### Clang support 68 | 69 | Early versions of my kernel used MSVC instead. Clang offers much better portability. By using `clang` and `ld.lld` this kernel is easily built on any system without special toolchain requirements. Further clang allows for inline assembly which is nice for kernel development, even though I keep usage to an absolute minimum. 70 | 71 | ### Remote mapping 72 | 73 | The remote mapping `net_map_remote()` allows for files to be mapped over the network and faulted in only when they are used. For VMs this is used to map the entire ~4 GiB snapshot. Combined with CoW, this meant that only pages that were ever touched during a fuzz case were present in memory, and only one copy. If memory was ever written during a fuzz case then this memory might be duplicated to each core. 74 | 75 | Typically for a medium size target (1-2 second fuzz case maximum), this meant usually no more than 2-4 MiB was used per VM. Leading to running 64 4GiB VMs with using only a few hundred MiB of RAM! 76 | 77 | ### Full ASLR 78 | 79 | Every bit of every allocation is fully ASLRed. The kernel base and stacks are also fully ASLRed. This ASLR is 36-bits (meaning every bit that isn't a page offset is random). This means you'll get allocations in the `0xffff...` range and the `0x0000...` range. 80 | 81 | ### Page heap 82 | 83 | All allocations are page heaped, their addresses are fully deleted from page tables on free and overflows and underflows can only corrupt data inside the structure itself. 84 | 85 | ### No identity/linear physical memory map 86 | 87 | The machine I developed this for had 512 GiB of RAM. Most kernels have a linear mapping of ALL of physical memory somewhere in the kernel. When dealing with large amounts of memory this uses a huge amount of the virtual address space. This is not really a huge issue, however it makes it so there's a "decent chance" a random address might actually hit valid memory. This makes it possible for heisenbugs for things that "sometimes work". 88 | 89 | Instead I use a design where the bootloader provides a 512-entry dynamic mapping mechanism. This consists of single page directory which is mapped at a random address. This means that using 4 KiB (PD) + 2 MiB of reserved page we are able to have a fast mechanism of accessing all physical memory. This greatly reduces the size of active addresses in the virtual address space, leaving more room for randomness and less chance of a random address hitting valid memory. 90 | 91 | Performance is about 20% slower with this model over a linear mapping, which is really not that bad. 92 | 93 | ### Generic VM model 94 | 95 | The VM model was starting to be designed to be more generic. This was going to allow for an Intel hypervisor being added while still using the same API for managing both. In this version there are actually 2 VM models, one for AMD's SVM, and another using user-VM. User VM was a made up VM model that makes a semi-isolated guest by using ring3 rather than a whole VM itself. This was great for things like JITs which needed a unique address space but I wanted to support on Intel. 96 | 97 | ### Rstate 98 | 99 | You'll see that all error handling is done through `RSTATES`. This model allows for all errors to chain together the origin of the error all the way up to the top which paniced or handled it. This does not rely on symbols or unwind unwalking so it's much more simple. Further it works correctly regardless of optimization levels or inlining as the stack is managed in the C code itself. Getting clean errors out of this really helped keep code quality up and bug fix times to a minimum. 100 | 101 | ### Probably some other cool stuff I forgot about 102 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Code cleanup 2 | - Since free lists are contained by the task. Do not nuke the task free 3 | lists on new task creations or destroys. 4 | 5 | - Why is there a mm_cow_pf_handler() and a manual implementation in 6 | page_fault()? 7 | 8 | - mm_for_each_dirty_page() could use a more in depth audit 9 | 10 | - Move rng_seed to task structure? 11 | 12 | ===================================================== 13 | Code Audit 14 | 15 | - Ensure prototypes are correct and sane 16 | - Ensure code does not exceed 80 character lines 17 | - Write documentation for any undocumented functions 18 | - Read through code and make sure usage is sane and safe 19 | - Add to the TODO list for any things that cannot be immediately fixed, or 20 | future ideas 21 | - Check for security bugs 22 | - Check for race conditions on globals and current_cpu 23 | - Make sure SAL annotations are correct and as verbose as possible 24 | ===================================================== 25 | 26 | Current code audit status 27 | Started 2016/10/24 28 | 29 | [complete] mm/mm.c 30 | [complete] time/time.c 31 | [complete] cpu/cpu.c 32 | [complete] cpu/acpi.c 33 | [complete] ide/ide.c 34 | [complete] disp/disp.c 35 | [complete] dstruc/hash_table.c 36 | [complete] generic/locks.c 37 | [complete] generic/stdlib.c 38 | 39 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | import os, subprocess, shlex, sys, re, shutil, threading, time 2 | 3 | # AMD 6376 is Piledriver march, use target-cpu=bdver2 4 | # Xeon E5-2630 v4 is Broadwell march, use target-cpu=broadwell 5 | 6 | CFLAGS = "-ffreestanding -target x86_64 -march=native -O2 -fPIE \ 7 | -g -ansi -pedantic -Wall -Werror -Isrcs -flto -Wno-long-long" 8 | LDFLAGS = "-pie --lto-O2" 9 | 10 | # Uncomment this line to make a debug friendly binary without optimizations and LTO 11 | CFLAGS += " -fno-lto -O2" 12 | 13 | OUTPUT_FILENAME = "grilled_cheese.kern.debug" 14 | OUTPUT_STRIP_FILENAME = "grilled_cheese.kern" 15 | BOOTLOADER_FILENAME = "grilled_cheese.boot" 16 | 17 | printlock = threading.Lock() 18 | 19 | def check_deps(out_file, deps): 20 | for dep in deps: 21 | # If the dependency was modified more recently than the output file 22 | try: 23 | if os.path.getmtime(dep) > os.path.getmtime(out_file): 24 | return True 25 | except: 26 | return True 27 | 28 | return False 29 | 30 | # Returns true of infile is newer than outfile, or if outfile doesn't exist. 31 | # Returns false otherwise. 32 | def is_older(outfile, infile): 33 | if not os.path.exists(outfile) or os.path.getmtime(infile) > os.path.getmtime(outfile): 34 | return True 35 | else: 36 | return False 37 | 38 | def get_cc_deps(in_c_fn): 39 | deps = [] 40 | 41 | cmd = "clang %s -M %s" % (CFLAGS, in_c_fn) 42 | 43 | sp = subprocess.Popen(shlex.split(cmd, posix=False), stdout=subprocess.PIPE) 44 | stdout = sp.communicate()[0] 45 | if sp.wait() != 0: 46 | return None 47 | 48 | # We only care about the dependencies, split off the target 49 | stdout = stdout.split(b': ')[1] 50 | 51 | for include in stdout.split(b' '): 52 | if not include.startswith(b"srcs"): 53 | continue; 54 | 55 | deps.append(include.strip()) 56 | 57 | return deps 58 | 59 | def cc(in_c_fn, out_obj_fn): 60 | cmd = "clang %s -o %s -c %s" % (CFLAGS, out_obj_fn, in_c_fn) 61 | 62 | with printlock: 63 | print("CC %s" % out_obj_fn) 64 | 65 | sp = subprocess.Popen(shlex.split(cmd, posix=False), stderr=subprocess.PIPE) 66 | stdout, stderr = sp.communicate() 67 | if sp.wait() != 0: 68 | with printlock: 69 | sp = subprocess.Popen(shlex.split(cmd, posix=False)) 70 | sp.wait() 71 | 72 | sys.exit(-1) 73 | 74 | return 75 | 76 | def asm(in_asm_fn, out_obj_fn): 77 | with printlock: 78 | print("ASM %s" % in_asm_fn) 79 | 80 | cmd = "nasm -f elf64 -o %s %s" % (out_obj_fn, in_asm_fn) 81 | 82 | sp = subprocess.Popen(shlex.split(cmd, posix=False)) 83 | if sp.wait() != 0: 84 | sys.exit(-1) 85 | 86 | return 87 | 88 | def link(in_link_objs, out_fn): 89 | cmd = "ld.lld %s -o %s %s" % (LDFLAGS, out_fn, in_link_objs) 90 | 91 | with printlock: 92 | print("LD %s" % out_fn) 93 | 94 | os.system(cmd) 95 | return 96 | 97 | def strip(in_link_objs, out_fn): 98 | cmd = "ld.lld %s -strip-all -o %s %s" % (LDFLAGS, out_fn, in_link_objs) 99 | 100 | with printlock: 101 | print("STRIP %s" % out_fn) 102 | 103 | os.system(cmd) 104 | return 105 | 106 | def buildfn(fn): 107 | if fn.find(' ') != -1: 108 | sys.stderr.write("Path \"%s\" contains a space\n" % fn) 109 | sys.exit(-1) 110 | 111 | obj = "objs" + fn[4:] + ".o" 112 | 113 | try: 114 | os.makedirs(os.path.split(obj)[0]) 115 | except: 116 | pass 117 | 118 | if fn.endswith(".c"): 119 | deps = get_cc_deps(fn) 120 | if deps == None or check_deps(obj, deps): 121 | cc(fn, obj) 122 | built_objs.append(obj) 123 | else: 124 | skipped_objs.append(obj) 125 | elif fn.endswith(".asm"): 126 | if is_older(obj, fn): 127 | asm(fn, obj) 128 | built_objs.append(obj) 129 | else: 130 | skipped_objs.append(obj) 131 | else: 132 | sys.stderr.write("Unsupported file type \"%s\"\n" % fn) 133 | sys.exit(-1) 134 | 135 | # Build every file with ext recursively in 'srcs' and place in 'objs'. Return 136 | # space separated string of all output objects. Calls sys.exit(-1) on any 137 | # error. 138 | def build_all(ext): 139 | global built_objs, skipped_objs 140 | 141 | for cur, dirs, files in os.walk('srcs'): 142 | for fn in files: 143 | fn = os.path.join(cur, fn) 144 | 145 | if not fn.endswith(ext): 146 | continue 147 | 148 | threading.Timer(0.0, buildfn, args=[fn]).start() 149 | 150 | while threading.active_count() != 1: 151 | time.sleep(0.01) 152 | 153 | if len(sys.argv) == 2 and sys.argv[1] == "clean": 154 | remove_dirs = ["objs"] 155 | remove_files = [OUTPUT_FILENAME, OUTPUT_STRIP_FILENAME, BOOTLOADER_FILENAME] 156 | 157 | for rmdir in remove_dirs: 158 | print("RMDIR %s" % rmdir) 159 | if os.path.exists(rmdir): 160 | shutil.rmtree(rmdir) 161 | 162 | for rmfile in remove_files: 163 | print("RM %s" % rmfile) 164 | if os.path.exists(rmfile): 165 | os.remove(rmfile) 166 | 167 | sys.exit(0) 168 | 169 | built_objs = [] 170 | skipped_objs = [] 171 | 172 | build_all(".c") 173 | build_all(".asm") 174 | 175 | if len(built_objs) or not os.path.exists(OUTPUT_FILENAME) or not os.path.exists(OUTPUT_STRIP_FILENAME): 176 | link(" ".join(built_objs + skipped_objs), OUTPUT_FILENAME) 177 | strip(" ".join(built_objs + skipped_objs), OUTPUT_STRIP_FILENAME) 178 | 179 | if is_older(BOOTLOADER_FILENAME, os.path.join("bootloader", "grilled_cheese.asm")): 180 | print("ASM %s" % BOOTLOADER_FILENAME) 181 | os.system("nasm -f bin -o %s bootloader/grilled_cheese.asm" % BOOTLOADER_FILENAME) 182 | 183 | sys.exit(0) 184 | 185 | -------------------------------------------------------------------------------- /srcs/boot/boot.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include