├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── README.md~ ├── bootloader ├── .cargo │ └── config ├── .gitignore ├── Cargo.toml ├── build.rs ├── src │ ├── asm_routines.asm │ ├── core_reqs.rs │ ├── main.rs │ ├── mm.rs │ ├── panic.rs │ ├── pe.rs │ ├── pxe.rs │ └── realmode.rs └── stage0.asm ├── check_address ├── Cargo.toml ├── README.md ├── src │ ├── main.rs │ ├── memreader.rs │ └── windows.rs └── versions │ └── win10.system32 ├── corpgen ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── coverage ├── Cargo.toml ├── README.md ├── coverage.gnuplot └── src │ ├── main.rs │ ├── memreader.rs │ └── windows.rs ├── diverge ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── emu ├── bochsrc ├── boot.ipxe └── slirp.conf ├── find_input ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── flatten_pe.py ├── kernel ├── .cargo │ └── config ├── .gitignore ├── Cargo.toml └── src │ ├── acpi.rs │ ├── core_reqs.rs │ ├── corpus.rs │ ├── coverage.rs │ ├── ept.rs │ ├── file.rs │ ├── fuzzers │ ├── example.rs │ ├── leadtools_ani.rs │ └── mod.rs │ ├── fuzzvm.rs │ ├── gdt.rs │ ├── idt.rs │ ├── interrupts.rs │ ├── main.rs │ ├── mm.rs │ ├── msr.rs │ ├── msr_bitmap.rs │ ├── mutations.rs │ ├── net │ ├── i219.rs │ └── mod.rs │ ├── ni.rs │ ├── panic.rs │ ├── pci.rs │ ├── rng.rs │ ├── stats.rs │ ├── time.rs │ ├── tools.rs │ ├── trace.rs │ ├── vga_buffer.rs │ ├── vmexit │ ├── descriptor_table.rs │ └── mod.rs │ ├── vmregs.rs │ ├── vmx.rs │ ├── vmxflags.rs │ └── volatile.rs ├── parse_trace ├── Cargo.toml ├── README.md └── src │ ├── main.rs │ ├── memreader.rs │ └── windows.rs ├── pci-ids-parser ├── Cargo.toml ├── README.md ├── pci.ids ├── pciids └── src │ └── main.rs ├── shared ├── cpu │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── mmu │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── noodle │ ├── .gitignore │ ├── Cargo.toml │ ├── serialize_tuple_gen.py │ ├── src │ │ ├── lib.rs │ │ ├── tuple_gen.rs │ │ └── tuple_match.rs │ └── tuple_match_gen.py ├── packets │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE-0BSD.txt │ ├── README.md │ └── src │ │ ├── ethernet.rs │ │ ├── ip.rs │ │ ├── ipv4.rs │ │ ├── lib.rs │ │ ├── macros.rs │ │ ├── phy.rs │ │ ├── tftp.rs │ │ ├── udp.rs │ │ └── wire.rs ├── rangeset │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── safecast │ ├── Cargo.toml │ ├── README.md │ ├── bytesafe_derive │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── src │ │ └── lib.rs ├── serial │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── vmx │ ├── Cargo.toml │ └── src │ ├── lib.rs │ ├── msr.rs │ └── vmxflags.rs ├── snapshot.sh ├── snapshot ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── src └── main.rs └── tftp-server ├── Cargo.toml ├── README.md └── src ├── coverage.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | stage1.flat 2 | orange_slice.boot 3 | Cargo.lock 4 | target 5 | cpuland.bat 6 | emu/*boot 7 | emu/*kern 8 | emu/SNAPSHOT* 9 | snapshot/barber* 10 | snapshot/SNAPSHOT* 11 | snapshot/.prev_snapshot_hash 12 | snapshot/*dmp 13 | snapshot/*phys 14 | snapshot/coverage* 15 | snapshot/corpus* 16 | coverage/lighthouse* 17 | tftp-server/SNAPSHOT_regs 18 | tftp-server/coverage* 19 | tftp-server/corpus* 20 | tftp-server/test.trace 21 | tftp-server/test.trace* 22 | tftp-server/barberslice* 23 | tftp-server/*crash.input 24 | tftp-server/corpus* 25 | tftp-server/project* 26 | parse_trace/trace.out* 27 | parse_trace/cache/* 28 | parse_trace/*out 29 | parse_trace/*trace 30 | **/*.un~ 31 | **/*.swp 32 | tftptest* 33 | fill_missing_memory/* 34 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bootloader_builder" 3 | version = "0.1.0" 4 | authors = ["gamozo "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Barbervisor 2 | 3 | Intel x86 bare metal hypervisor for researching snapshot fuzzing ideas. 4 | 5 | The blog describing the development can be found [here](https://blog.talosintelligence.com/2020/08/barbervisor.html) 6 | 7 | The underlying kernel for this hypervisor is [Orange Slice](https://github.com/gamozolabs/orange_slice) by [Brandon Falk](twitter.com/gamozolabs) and the packet structure for network traffic is a fork of [smoltcp](https://github.com/smoltcp-rs/smoltcp). 8 | 9 | ## Building 10 | 11 | Ensure `i586-pc-windows-msvc` and `x86_64-pc-windows-msvc` toolchains are installed and running nightly Rust. 12 | 13 | ``` 14 | > rustup target add i586-pc-windows-msvc 15 | > rustup target add x86_64-pc-windows-msvc 16 | 17 | > rustup toolchain list 18 | nightly-x86_64-pc-windows-msvc (default) 19 | ``` 20 | 21 | Download [LLVM](https://releases.llvm.org/download.html) and have `lld-link` in the path. 22 | Download [NASM](https://nasm.us/) and have `nasm` in the path. 23 | 24 | Change the IP address in `tftp-server/src/main` to bind to the wanted network address. 25 | 26 | ``` 27 | > cargo run 28 | ``` 29 | 30 | ## Snapshots 31 | 32 | Snapshots are currently gathered from VirtualBox. 33 | 34 | After snapshotting from VirtualBox, place the result of `writecore` at `snapshot/snapshot.dmp` and the result of `.pgmphystofile` at `snapshot/snapshot.phys`. These paths are hard coded and are required for most of the utilities. 35 | 36 | ## Deploying 37 | 38 | Copy `barberslice.boot` and `barberslice.kern` to a TFTPD server folder configured for PXE booting. Also set the PXE boot filename to `barberslice.boot` in your DHCP server. 39 | 40 | ## Bochs 41 | 42 | The kernel can be tested in [Bochs](http://bochs.sourceforge.net/) before testing on bare metal. 43 | 44 | ``` 45 | bochs -q -f emu/bochsrc 46 | ``` 47 | 48 | Be sure to change the following lines of the `bochsrc` to point to your local Bochs install: 49 | 50 | ``` 51 | romimage: file="C:\Users\user\git\bochs\bios\BIOS-bochs-latest", address=0x0, options=none 52 | vgaromimage: file="C:\Users\user\git\bochs\bios\VGABIOS-lgpl-latest" 53 | ata0-master: type=cdrom, path="C:\Users\user\git\barberslice\ipxe\src\bin\ipxe.iso", status=inserted 54 | e1000: enabled=1, mac=52:54:00:12:34:56, ethmod=vnet, ethdev="C:\Users\user\git\barberslice\emu" 55 | ``` 56 | 57 | ## iPXE 58 | 59 | iPXE build is included if wanted to test in Bochs using PXE. 60 | 61 | On linux: 62 | 63 | ``` 64 | sudo apt-get install liblzma liblzma-dev isolinux mkisofs 65 | git clone https://github.com/ipxe/ipxe 66 | cd ipxe/src 67 | make bin/ipxe.iso EMBED=../../emu/boot.ipxe 68 | ``` 69 | 70 | ## Utilities 71 | 72 | * `check_address`: Return the module+offset and instruction for a given address from the current snapshot 73 | * `corpgen`: Generates serialized corpus for shipping to the kernel 74 | * `coverage`: Dump `module+offset` coverage file to load into [lighthouse](https://github.com/gaasedelen/lighthouse) 75 | * `diverage`: Legacy utility used to diff a `windbg` single step trace and a trace dumped from Barberslice 76 | * `find_input`: Return all generated files that hit a given address 77 | * `parse_trace`: Parses the trace format sent from the hypervisor into a human readable form 78 | * `pci-ids-parser`: Parser for dumping PCI information that was going to be added to the kernel (but never was) 79 | * `snapshot`: Parses the VirtualBox core dump file and dumps the register state for the kernel to use. 80 | * `tftp-server`: Custom TFTP server for communicating with the hypervisor 81 | 82 | ## Docs 83 | 84 | The main kernel docs can be found: 85 | 86 | ``` 87 | cd kernel 88 | cargo doc --open 89 | ``` 90 | 91 | The utilities also have READMEs giving a high level overview of what the tool is used for. 92 | -------------------------------------------------------------------------------- /README.md~: -------------------------------------------------------------------------------- 1 | # Barbervisor 2 | 3 | Intel x86 bare metal hypervisor for researching snapshot fuzzing ideas. 4 | 5 | ## Building 6 | 7 | Ensure `i586-pc-windows-msvc` and `x86_64-pc-windows-msvc` toolchains are installed and running nightly Rust. 8 | 9 | ``` 10 | > rustup target add i586-pc-windows-msvc 11 | > rustup target add x86_64-pc-windows-msvc 12 | 13 | > rustup toolchain list 14 | nightly-x86_64-pc-windows-msvc (default) 15 | ``` 16 | 17 | Download [LLVM](https://releases.llvm.org/download.html) and have `lld-link` in the path. 18 | Download [NASM](https://nasm.us/) and have `nasm` in the path. 19 | 20 | Change the IP address in `tftp-server/src/main` to bind to the wanted network address. 21 | 22 | ``` 23 | > cargo run 24 | ``` 25 | 26 | ## Snapshots 27 | 28 | Snapshots are currently gathered from VirtualBox. 29 | 30 | After snapshotting from VirtualBox, place the result of `writecore` at `snapshot/snapshot.dmp` and the result of `.pgmphystofile` at `snapshot/snapshot.phys`. These paths are hard coded and are required for most of the utilities. 31 | 32 | ## Deploying 33 | 34 | Copy `barberslice.boot` and `barberslice.kern` to a TFTPD server folder configured for PXE booting. Also set the PXE boot filename to `barberslice.boot` in your DHCP server. 35 | 36 | ## Bochs 37 | 38 | The kernel can be tested in [Bochs](http://bochs.sourceforge.net/) before testing on bare metal. 39 | 40 | ``` 41 | bochs -q -f emu/bochsrc 42 | ``` 43 | 44 | Be sure to change the following lines of the `bochsrc` to point to your local Bochs install: 45 | 46 | ``` 47 | romimage: file="C:\Users\user\git\bochs\bios\BIOS-bochs-latest", address=0x0, options=none 48 | vgaromimage: file="C:\Users\user\git\bochs\bios\VGABIOS-lgpl-latest" 49 | ata0-master: type=cdrom, path="C:\Users\user\git\barberslice\ipxe\src\bin\ipxe.iso", status=inserted 50 | e1000: enabled=1, mac=52:54:00:12:34:56, ethmod=vnet, ethdev="C:\Users\user\git\barberslice\emu" 51 | ``` 52 | 53 | ## iPXE 54 | 55 | iPXE build is included if wanted to test in Bochs using PXE. 56 | 57 | On linux: 58 | 59 | ``` 60 | sudo apt-get install liblzma liblzma-dev isolinux mkisofs 61 | git clone https://github.com/ipxe/ipxe 62 | cd ipxe/src 63 | make bin/ipxe.dsk EMBED=../../emu/boot.ipxe 64 | ``` 65 | 66 | ## Utilities 67 | 68 | * `check_address`: Return the module+offset and instruction for a given address from the current snapshot 69 | * `corpgen`: Generates serialized corpus for shipping to the kernel 70 | * `coverage`: Dump `module+offset` coverage file to load into [lighthouse](https://github.com/gaasedelen/lighthouse) 71 | * `diverage`: Legacy utility used to diff a `windbg` single step trace and a trace dumped from Barberslice 72 | * `find_input`: Return all generated files that hit a given address 73 | * `parse_trace`: Parses the trace format sent from the hypervisor into a human readable form 74 | * `pci-ids-parser`: Parser for dumping PCI information that was going to be added to the kernel (but never was) 75 | * `snapshot`: Parses the VirtualBox core dump file and dumps the register state for the kernel to use. 76 | * `tftp-server`: Custom TFTP server for communicating with the hypervisor 77 | 78 | ## Docs 79 | 80 | The main kernel docs can be found: 81 | 82 | ``` 83 | cd kernel 84 | cargo doc --open 85 | ``` 86 | 87 | The utilities also have READMEs giving a high level overview of what the tool is used for. 88 | -------------------------------------------------------------------------------- /bootloader/.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "i586-pc-windows-msvc" 3 | 4 | [target.i586-pc-windows-msvc] 5 | rustflags = ["-Z", "thinlto=off", "-C", "relocation-model=static", "-C", "linker=lld-link", "-C", "link-args=/entry:entry /subsystem:native /base:0xf000 /fixed /debug /nodefaultlib target/asm_routines.obj"] 6 | -------------------------------------------------------------------------------- /bootloader/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | 4 | -------------------------------------------------------------------------------- /bootloader/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stage1" 3 | version = "0.1.0" 4 | authors = ["Brandon Falk "] 5 | 6 | [dependencies] 7 | serial = { path = "../shared/serial" } 8 | cpu = { path = "../shared/cpu" } 9 | rangeset = { path = "../shared/rangeset" } 10 | safecast = { path = "../shared/safecast" } 11 | bytesafe_derive = { path = "../shared/safecast/bytesafe_derive" } 12 | mmu = { path = "../shared/mmu" } 13 | 14 | [profile.release] 15 | panic = "abort" 16 | opt-level = "z" 17 | lto = true 18 | debug = true 19 | 20 | [profile.dev] 21 | panic = "abort" 22 | debug = true 23 | 24 | -------------------------------------------------------------------------------- /bootloader/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::process::Command; 3 | 4 | fn nasm(in_asm: &str, out_obj: &str) { 5 | if Path::new(out_obj).exists() { 6 | std::fs::remove_file(out_obj).expect("Failed to remove old object"); 7 | } 8 | 9 | let status = Command::new("nasm") 10 | .args(&["-f", "win32", "-o", out_obj, in_asm]) 11 | .status() 12 | .expect("Failed to run nasm"); 13 | 14 | /* Check for command success */ 15 | assert!(status.success(), "NASM command failed"); 16 | 17 | /* Ensure output file was created */ 18 | assert!( 19 | Path::new(out_obj).exists(), 20 | "NASM did not generate expected file" 21 | ); 22 | } 23 | 24 | fn main() { 25 | nasm("src/asm_routines.asm", "target/asm_routines.obj"); 26 | } 27 | -------------------------------------------------------------------------------- /bootloader/src/asm_routines.asm: -------------------------------------------------------------------------------- 1 | [bits 32] 2 | 3 | ; This is the program code segment base in bytes. Since we use real mode in 4 | ; this codebase we need to make sure we set CS correctly. 5 | ; Since this is in bytes, PROGRAM_BASE of 0x10000 would mean CS will be set to 6 | ; 0x1000 when in real-mode 7 | %define PROGRAM_BASE 0x10000 8 | 9 | struc register_state 10 | .eax: resd 1 11 | .ecx: resd 1 12 | .edx: resd 1 13 | .ebx: resd 1 14 | .esp: resd 1 15 | .ebp: resd 1 16 | .esi: resd 1 17 | .edi: resd 1 18 | .efl: resd 1 19 | 20 | .es: resw 1 21 | .ds: resw 1 22 | .fs: resw 1 23 | .gs: resw 1 24 | .ss: resw 1 25 | endstruc 26 | 27 | section .text 28 | 29 | global _invoke_realmode 30 | _invoke_realmode: 31 | pushad 32 | lgdt [rmgdt] 33 | 34 | ; Set all selectors to data segments 35 | mov ax, 0x10 36 | mov es, ax 37 | mov ds, ax 38 | mov fs, ax 39 | mov gs, ax 40 | mov ss, ax 41 | jmp 0x0008:(.foop - PROGRAM_BASE) 42 | 43 | [bits 16] 44 | .foop: 45 | ; Disable protected mode 46 | mov eax, cr0 47 | and eax, ~1 48 | mov cr0, eax 49 | 50 | ; Clear out all segments 51 | xor ax, ax 52 | mov es, ax 53 | mov ds, ax 54 | mov fs, ax 55 | mov gs, ax 56 | mov ss, ax 57 | 58 | ; Set up a fake iret to do a long jump to switch to new cs. 59 | pushfd ; eflags 60 | push dword (PROGRAM_BASE >> 4) ; cs 61 | push dword (.new_func - PROGRAM_BASE) ; eip 62 | iretd 63 | 64 | .new_func: 65 | ; Get the arguments passed to this function 66 | movzx ebx, byte [esp + (4*0x9)] ; arg1, interrupt number 67 | shl ebx, 2 68 | mov eax, dword [esp + (4*0xa)] ; arg2, pointer to registers 69 | 70 | ; Set up interrupt stack frame. This is what the real mode routine will 71 | ; pop off the stack during its iret. 72 | mov ebp, (.retpoint - PROGRAM_BASE) 73 | pushfw 74 | push cs 75 | push bp 76 | 77 | ; Set up the call for the interrupt by loading the contents of the IVT 78 | ; based on the interrupt number specified 79 | pushfw 80 | push word [bx+2] 81 | push word [bx+0] 82 | 83 | ; Load the register state specified 84 | mov ecx, dword [eax + register_state.ecx] 85 | mov edx, dword [eax + register_state.edx] 86 | mov ebx, dword [eax + register_state.ebx] 87 | mov ebp, dword [eax + register_state.ebp] 88 | mov esi, dword [eax + register_state.esi] 89 | mov edi, dword [eax + register_state.edi] 90 | mov eax, dword [eax + register_state.eax] 91 | 92 | ; Perform a long jump to the interrupt entry point, simulating a software 93 | ; interrupt instruction 94 | iretw 95 | .retpoint: 96 | ; Save off all registers 97 | push eax 98 | push ecx 99 | push edx 100 | push ebx 101 | push ebp 102 | push esi 103 | push edi 104 | pushfd 105 | push es 106 | push ds 107 | push fs 108 | push gs 109 | push ss 110 | 111 | ; Get a pointer to the registers 112 | mov eax, dword [esp + (4*0xa) + (4*8) + (5*2)] ; arg2, pointer to registers 113 | 114 | ; Update the register state with the post-interrupt register state. 115 | pop word [eax + register_state.ss] 116 | pop word [eax + register_state.gs] 117 | pop word [eax + register_state.fs] 118 | pop word [eax + register_state.ds] 119 | pop word [eax + register_state.es] 120 | pop dword [eax + register_state.efl] 121 | pop dword [eax + register_state.edi] 122 | pop dword [eax + register_state.esi] 123 | pop dword [eax + register_state.ebp] 124 | pop dword [eax + register_state.ebx] 125 | pop dword [eax + register_state.edx] 126 | pop dword [eax + register_state.ecx] 127 | pop dword [eax + register_state.eax] 128 | 129 | ; Load data segment for lgdt 130 | mov ax, (PROGRAM_BASE >> 4) 131 | mov ds, ax 132 | 133 | ; Enable protected mode 134 | mov eax, cr0 135 | or eax, 1 136 | mov cr0, eax 137 | 138 | ; Load 32-bit protected mode GDT 139 | mov eax, (pmgdt - PROGRAM_BASE) 140 | lgdt [eax] 141 | 142 | ; Set all segments to data segments 143 | mov ax, 0x10 144 | mov es, ax 145 | mov ds, ax 146 | mov fs, ax 147 | mov gs, ax 148 | mov ss, ax 149 | 150 | ; Long jump back to protected mode. 151 | pushfd ; eflags 152 | push dword 0x0008 ; cs 153 | push dword backout ; eip 154 | iretd 155 | 156 | [bits 32] 157 | 158 | global _pxecall 159 | _pxecall: 160 | mov word [0xb8000], 0x0461 161 | 162 | pushad 163 | lgdt [rmgdt] 164 | 165 | ; Set all selectors to data segments 166 | mov ax, 0x10 167 | mov es, ax 168 | mov ds, ax 169 | mov fs, ax 170 | mov gs, ax 171 | mov ss, ax 172 | 173 | jmp 0x0008:(.foop - PROGRAM_BASE) 174 | 175 | [bits 16] 176 | .foop: 177 | ; Disable protected mode 178 | mov eax, cr0 179 | and eax, ~1 180 | mov cr0, eax 181 | 182 | ; Clear all segments 183 | xor ax, ax 184 | mov es, ax 185 | mov ds, ax 186 | mov fs, ax 187 | mov gs, ax 188 | mov ss, ax 189 | 190 | ; Perform a long jump to real-mode 191 | pushfd ; eflags 192 | push dword (PROGRAM_BASE >> 4) ; cs 193 | push dword (.new_func - PROGRAM_BASE) ; eip 194 | iretd 195 | 196 | .new_func: 197 | ; pub fn pxecall(seg: u16, off: u16, pxe_call: u16, param_seg: u16, param_off: u16); 198 | movzx eax, word [esp + (4*0x9)] ; arg1, seg 199 | movzx ebx, word [esp + (4*0xa)] ; arg2, offset 200 | movzx ecx, word [esp + (4*0xb)] ; arg3, pxe_call 201 | movzx edx, word [esp + (4*0xc)] ; arg4, param_seg 202 | movzx esi, word [esp + (4*0xd)] ; arg5, param_off 203 | 204 | ; Set up PXE call parameters (opcode, offset, seg) 205 | push dx 206 | push si 207 | push cx 208 | 209 | ; Set up our return address from the far call 210 | mov ebp, (.retpoint - PROGRAM_BASE) 211 | push cs 212 | push bp 213 | 214 | ; Set up a far call via iretw 215 | pushfw 216 | push ax 217 | push bx 218 | 219 | iretw 220 | .retpoint: 221 | ; Hyper-V has been observed to set the interrupt flag in PXE routines. We 222 | ; clear it ASAP. 223 | cli 224 | 225 | ; Clean up the stack from the 3 word parameters we passed to PXE 226 | add sp, 6 227 | 228 | ; Load data segment for lgdt 229 | mov ax, (PROGRAM_BASE >> 4) 230 | mov ds, ax 231 | 232 | ; Enable protected mode 233 | mov eax, cr0 234 | or eax, 1 235 | mov cr0, eax 236 | 237 | ; Load 32-bit protected mode GDT 238 | mov eax, (pmgdt - PROGRAM_BASE) 239 | lgdt [eax] 240 | 241 | ; Set all segments to data segments 242 | mov ax, 0x10 243 | mov es, ax 244 | mov ds, ax 245 | mov fs, ax 246 | mov gs, ax 247 | mov ss, ax 248 | 249 | ; Jump back to protected mode 250 | pushfd ; eflags 251 | push dword 0x0008 ; cs 252 | push dword backout ; eip 253 | iretd 254 | 255 | [bits 32] 256 | backout: 257 | mov word [0xb8000], 0x0462 258 | popad 259 | ret 260 | 261 | section .data 262 | 263 | ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 264 | 265 | ; 16-bit real mode GDT 266 | 267 | align 8 268 | rmgdt_base: 269 | ; Null descriptor 270 | dq 0x0000000000000000 271 | 272 | ; 16-bit RO code, base PROGRAM_BASE, limit 0x0000ffff 273 | dq 0x00009a000000ffff | (PROGRAM_BASE << 16) 274 | 275 | ; 16-bit RW data, base 0, limit 0x0000ffff 276 | dq 0x000092000000ffff 277 | 278 | rmgdt: 279 | dw (rmgdt - rmgdt_base) - 1 280 | dd rmgdt_base 281 | 282 | ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 283 | 284 | ; 32-bit protected mode GDT 285 | 286 | align 8 287 | pmgdt_base: 288 | dq 0x0000000000000000 ; Null descriptor 289 | dq 0x00CF9A000000FFFF 290 | dq 0x00CF92000000FFFF 291 | 292 | pmgdt: 293 | dw (pmgdt - pmgdt_base) - 1 294 | dd pmgdt_base 295 | 296 | ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 297 | 298 | ; 64-bit long mode GDT 299 | 300 | align 8 301 | lmgdt_base: 302 | dq 0x0000000000000000 ; Null descriptor 303 | dq 0x00209a0000000000 ; 64-bit, present, code 304 | dq 0x0000920000000000 ; Present, data r/w 305 | 306 | lmgdt: 307 | dw (lmgdt - lmgdt_base) - 1 308 | dd lmgdt_base 309 | dd 0 310 | 311 | [bits 32] 312 | 313 | global _enter64 314 | _enter64: 315 | ; qword [esp + 0x04] - Entry 316 | ; qword [esp + 0x0c] - Stack 317 | ; qword [esp + 0x14] - Param 318 | ; dword [esp + 0x1c] - New cr3 319 | 320 | ; Get the parameters passed in to this function 321 | mov esi, [esp+0x1c] ; New cr3 322 | 323 | ; Set up CR3 324 | mov cr3, esi 325 | 326 | ; Set NXE (NX enable) and LME (long mode enable) 327 | mov edx, 0 328 | mov eax, 0x00000900 329 | mov ecx, 0xc0000080 330 | wrmsr 331 | 332 | xor eax, eax 333 | or eax, (1 << 9) ; OSFXSR 334 | or eax, (1 << 10) ; OSXMMEXCPT 335 | or eax, (1 << 5) ; PAE 336 | or eax, (1 << 3) ; DE 337 | mov cr4, eax 338 | 339 | xor eax, eax 340 | and eax, ~(1 << 2) ; Clear Emulation flag 341 | or eax, (1 << 0) ; Protected mode enable 342 | or eax, (1 << 1) ; Monitor co-processor 343 | or eax, (1 << 16) ; Write protect 344 | or eax, (1 << 31) ; Paging enable 345 | mov cr0, eax 346 | 347 | ; Load the 64-bit long mode GDT 348 | lgdt [lmgdt] 349 | 350 | ; Long jump to enable long mode! 351 | jmp 0x0008:lm_entry 352 | 353 | [bits 64] 354 | 355 | lm_entry: 356 | ; Set all selectors to 64-bit data segments 357 | mov ax, 0x10 358 | mov es, ax 359 | mov ds, ax 360 | mov fs, ax 361 | mov gs, ax 362 | mov ss, ax 363 | 364 | mov rdi, qword [rsp + 0x4] ; Entry point 365 | mov rbp, qword [rsp + 0xc] ; Stack 366 | sub rbp, 0x28 ; MSFT 64-bit calling convention requires 0x20 homing space 367 | ; We also need 8 bytes for the fake 'return address' since we 368 | ; iretq rather than call. 369 | 370 | ; Parameter 371 | mov rcx, qword [esp + 0x14] 372 | 373 | ; Set up a long jump via an iretq to jump to long mode. 374 | push qword 0x0010 ; ss 375 | push qword rbp ; rsp 376 | pushfq ; rflags 377 | push qword 0x0008 ; cs 378 | push qword rdi ; rip 379 | iretq 380 | 381 | cli 382 | .halt: 383 | hlt 384 | jmp short .halt 385 | 386 | -------------------------------------------------------------------------------- /bootloader/src/core_reqs.rs: -------------------------------------------------------------------------------- 1 | /// libc `memcpy` implementation in rust 2 | /// 3 | /// This implementation of `memcpy` is overlap safe, making it technically 4 | /// `memmove`. 5 | /// 6 | /// # Parameters 7 | /// 8 | /// * `dest` - Pointer to memory to copy to 9 | /// * `src` - Pointer to memory to copy from 10 | /// * `n` - Number of bytes to copy 11 | /// 12 | #[no_mangle] 13 | pub unsafe extern fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 14 | { 15 | memmove(dest, src, n) 16 | } 17 | 18 | /// libc `memmove` implementation in rust 19 | /// 20 | /// # Parameters 21 | /// 22 | /// * `dest` - Pointer to memory to copy to 23 | /// * `src` - Pointer to memory to copy from 24 | /// * `n` - Number of bytes to copy 25 | /// 26 | #[no_mangle] 27 | pub unsafe extern fn memmove(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 28 | { 29 | if src < dest as *const u8 { 30 | /* copy backwards */ 31 | let mut ii = n; 32 | while ii != 0 { 33 | ii -= 1; 34 | *dest.offset(ii as isize) = *src.offset(ii as isize); 35 | } 36 | } else { 37 | /* copy forwards */ 38 | let mut ii = 0; 39 | while ii < n { 40 | *dest.offset(ii as isize) = *src.offset(ii as isize); 41 | ii += 1; 42 | } 43 | } 44 | 45 | dest 46 | } 47 | 48 | /// libc `memset` implementation in rust 49 | /// 50 | /// # Parameters 51 | /// 52 | /// * `s` - Pointer to memory to set 53 | /// * `c` - Character to set `n` bytes in `s` to 54 | /// * `n` - Number of bytes to set 55 | /// 56 | #[no_mangle] 57 | pub unsafe extern fn memset(s: *mut u8, c: i32, n: usize) -> *mut u8 58 | { 59 | let mut ii = 0; 60 | while ii < n { 61 | *s.offset(ii as isize) = c as u8; 62 | ii += 1; 63 | } 64 | 65 | s 66 | } 67 | 68 | /// libc `memcmp` implementation in rust 69 | /// 70 | /// # Parameters 71 | /// 72 | /// * `s1` - Pointer to memory to compare with s2 73 | /// * `s2` - Pointer to memory to compare with s1 74 | /// * `n` - Number of bytes to set 75 | #[no_mangle] 76 | pub unsafe extern fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 77 | { 78 | let mut ii = 0; 79 | while ii < n { 80 | let a = *s1.offset(ii as isize); 81 | let b = *s2.offset(ii as isize); 82 | if a != b { 83 | return a as i32 - b as i32 84 | } 85 | ii += 1; 86 | } 87 | 88 | 0 89 | } 90 | 91 | /* --------------------------------------------------------------------------- 92 | * Microsoft specific intrinsics 93 | * 94 | * These intrinsics use the stdcall convention however are not decorated 95 | * with an @ suffix. To override LLVM from appending this suffix we 96 | * have an \x01 escape byte before the name, which prevents LLVM from all 97 | * name mangling. 98 | * --------------------------------------------------------------------------- 99 | */ 100 | 101 | /// Perform n % d 102 | #[export_name="\x01__aullrem"] 103 | pub extern "stdcall" fn __aullrem(n: u64, d: u64) -> u64 104 | { 105 | ::compiler_builtins::int::udiv::__umoddi3(n, d) 106 | } 107 | 108 | /// Perform n / d 109 | #[export_name="\x01__aulldiv"] 110 | pub extern "stdcall" fn __aulldiv(n: u64, d: u64) -> u64 111 | { 112 | ::compiler_builtins::int::udiv::__udivdi3(n, d) 113 | } 114 | 115 | /// Perform n % d 116 | #[export_name="\x01__allrem"] 117 | pub extern "stdcall" fn __allrem(n: i64, d: i64) -> i64 118 | { 119 | ::compiler_builtins::int::sdiv::__moddi3(n, d) 120 | } 121 | 122 | /// Perform n / d 123 | #[export_name="\x01__alldiv"] 124 | pub extern "stdcall" fn __alldiv(n: i64, d: i64) -> i64 125 | { 126 | ::compiler_builtins::int::sdiv::__divdi3(n, d) 127 | } 128 | 129 | -------------------------------------------------------------------------------- /bootloader/src/mm.rs: -------------------------------------------------------------------------------- 1 | use core; 2 | 3 | use realmode; 4 | use rangeset::{Range, RangeSet}; 5 | use core::alloc::{GlobalAlloc, Layout}; 6 | 7 | /// Global containing the contents of the E820 table 8 | /// 9 | /// First value of the tuple is a bool indicating whether allocations are 10 | /// allowed. This is set to false once the MM table has been cloned to pass 11 | /// to the kernel, disabling allocations. 12 | /// 13 | /// Second value indicates if the MM subsystem has been initialized. 14 | /// 15 | /// Third value is the E820 table in a RangeSet 16 | pub static mut MM_TABLE: (bool, bool, RangeSet) = (false, false, RangeSet::new()); 17 | 18 | /// Packed structure describing E820 entries 19 | #[repr(C, packed)] 20 | #[derive(Clone, Copy, PartialEq, Eq)] 21 | struct E820Entry { 22 | base: u64, 23 | size: u64, 24 | typ: u32, 25 | } 26 | 27 | /// Clone the MM table, further disabling allocations 28 | pub fn clone_mm_table() -> RangeSet 29 | { 30 | unsafe { 31 | /* Make sure MM is initialized and allocations are enabled */ 32 | assert!(MM_TABLE.1, "MM subsystem has not been initialized"); 33 | assert!(MM_TABLE.0, "MM table has already been cloned"); 34 | 35 | /* Disable allocations */ 36 | MM_TABLE.0 = false; 37 | 38 | /* Return copy of MM table */ 39 | MM_TABLE.2.clone() 40 | } 41 | } 42 | 43 | pub unsafe fn remove_range(addr: u64, size: u64) 44 | { 45 | let rs = &mut MM_TABLE.2; 46 | assert!(size > 0, "Invalid size for remove_range()"); 47 | rs.remove(Range { start: addr, end: addr.checked_add(size).unwrap() - 1 }); 48 | } 49 | 50 | /// Initialize the memory managment state. This requests the e820 table from 51 | /// the BIOS and checks for overlapping/double mapped ranges. 52 | pub unsafe fn init() 53 | { 54 | let rs = &mut MM_TABLE.2; 55 | 56 | /* Loop through the E820 twice. The first time we loop we want to 57 | * accumulate free sections into the RangeSet. The second loop we want 58 | * to remove nonfree sections. 59 | */ 60 | for &add_entries in &[true, false] { 61 | /* Continuation code, starts off at 0. BIOS implementation specific 62 | * after first call to e820. 63 | */ 64 | let mut cont = 0; 65 | 66 | /* Get the E820 table from the BIOS, entry by entry */ 67 | loop { 68 | let mut ent = E820Entry { base: 0, size: 0, typ: 0 }; 69 | 70 | /* Set up the register state for the BIOS call */ 71 | let mut regs = realmode::RegisterState { 72 | eax: 0xe820, /* Function 0xE820 */ 73 | ecx: 20, /* Entry size (in bytes) */ 74 | edx: 0x534d4150, /* Magic number 'PAMS' */ 75 | ebx: cont, /* Continuation number */ 76 | edi: &mut ent as *const _ as u32, /* Pointer to buffer */ 77 | ..Default::default() 78 | }; 79 | 80 | /* Invoke BIOS int 0x15, function 0xE820 to get the memory 81 | * entries 82 | */ 83 | realmode::invoke_realmode(0x15, &mut regs); 84 | 85 | /* Validate eax contains correct 'SMAP' magic signature */ 86 | assert!(regs.eax == 0x534d4150, 87 | "E820 did not report correct magic"); 88 | 89 | /* Validate size of E820 entry is >= what we expect */ 90 | assert!(regs.ecx as usize >= core::mem::size_of_val(&ent), 91 | "E820 entry structure was too small"); 92 | 93 | assert!(ent.size > 0, "E820 entry of zero size"); 94 | 95 | /* Safely compute end of memory region */ 96 | let ent_end = match ent.base.checked_add(ent.size - 1) { 97 | Some(x) => x, 98 | None => panic!("E820 entry integer overflow"), 99 | }; 100 | 101 | /* Either insert free regions on the first iteration of the loop 102 | * or remove used regions in the second iteration. 103 | */ 104 | if add_entries && ent.typ == 1 { 105 | rs.insert(Range { start: ent.base, end: ent_end }); 106 | } else if !add_entries && ent.typ != 1 { 107 | rs.remove(Range { start: ent.base, end: ent_end }); 108 | } 109 | 110 | /* If ebx (continuation number) is zero or CF (error) was set, 111 | * break out of the loop. 112 | */ 113 | if regs.ebx == 0 || (regs.efl & 1) == 1 { 114 | break; 115 | } 116 | 117 | /* Update continuation */ 118 | cont = regs.ebx; 119 | } 120 | } 121 | 122 | /* Remove the first 1MB of memory from allocatable memory. This is to 123 | * prevent BIOS data structures and our PXE image from being removed. 124 | */ 125 | rs.remove(Range { start: 0, end: 0xFFFFF }); 126 | 127 | /* Mark MM as initialized and allocations enabled */ 128 | MM_TABLE.0 = true; 129 | MM_TABLE.1 = true; 130 | } 131 | 132 | /// Structure representing global allocator 133 | /// 134 | /// All state is handled elsewhere so this is empty. 135 | pub struct GlobalAllocator; 136 | 137 | unsafe impl GlobalAlloc for GlobalAllocator { 138 | /// Global allocator. Grabs free memory from E820 and removes it from 139 | /// the table. 140 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 141 | { 142 | assert!(MM_TABLE.1, "Attempted to allocate with mm uninitialized"); 143 | assert!(MM_TABLE.0, "Attempted to allocate with allocations disabled"); 144 | 145 | let rs = &mut MM_TABLE.2; 146 | 147 | /* All the actual work is done in alloc_rangeset() */ 148 | let ret = rs.allocate(layout.size() as u64, layout.align() as u64); 149 | if ret.is_null() { 150 | panic!("Allocation failure"); 151 | } else { 152 | ret as *mut u8 153 | } 154 | } 155 | 156 | /// No free implementation. 157 | /// 158 | /// We really have no reason to free in the bootloader, so we do not 159 | /// support a free. We could easily add support if really needed, but 160 | /// having free panic will prevent us from accidentally allocating data 161 | /// and passing it to the next stage by pointer, and letting it drop. 162 | /// Given we don't free anything in the bootloader, anything we pass to 163 | /// the next stage is always valid. 164 | unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) 165 | { 166 | panic!("Dealloc attempted\n"); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /bootloader/src/panic.rs: -------------------------------------------------------------------------------- 1 | use serial; 2 | use cpu; 3 | use core::panic::PanicInfo; 4 | 5 | /// Panic implementation 6 | /// 7 | /// This currently breaks ABI. This is supposed to be "pub extern fn". By 8 | /// breaking the ABI we let LTO happen, which deletes much code being used 9 | /// to generate formatted parameters for panic. To make this safe we use no 10 | /// parameters passed in here at all. 11 | #[panic_handler] 12 | #[no_mangle] 13 | pub fn panic(_info: &PanicInfo) -> ! { 14 | serial::write("!!! PANIC !!!\n"); 15 | serial::write("Hit rust_begin_unwind()\n"); 16 | cpu::halt(); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /bootloader/src/realmode.rs: -------------------------------------------------------------------------------- 1 | extern { 2 | /// Invoke a realmode software interrupt, for BIOS calls 3 | /// 4 | /// # Summary 5 | /// 6 | /// When this function is invoked, the register state is populated with 7 | /// the fields supplied from `register_state`, excluding segments, efl and 8 | /// esp. Once this context is loaded, a software interrupt of `int_num` is 9 | /// performed. Once the software interrupt is complete, the new register 10 | /// state is saved off to `register_state`, including segments, efl and 11 | /// esp fields. 12 | /// 13 | /// # Parameters 14 | /// 15 | /// * `int_num` - Software interrupt number to invoke 16 | /// * `register_state` - Input/output context for interrupt 17 | /// 18 | pub fn invoke_realmode(int_num: u8, regs: &mut RegisterState); 19 | 20 | /// Invoke a PXE call using the real-mode PXE stack. 21 | /// 22 | /// # Summary 23 | /// 24 | /// This function is used to invoke the real-mode PXE APIs provided by the 25 | /// EntryPointSP entry of the !PXE structure. The seg:off provided by 26 | /// EntryPointSP is what should be used for the first 2 parameters of this 27 | /// function. Provided the right real-mode stack, you then provide a PXE 28 | /// opcode in the `pxe_call` parameter, and point `param_seg`:`param_off` 29 | /// at the buffer describing the structure used by the opcode specified. 30 | /// 31 | /// # Parameters 32 | /// 33 | /// * `seg` - Code segment of the real-mode PXE stack 34 | /// * `off` - Code offset of the real-mode PXE stack 35 | /// * `pxe_call` - PXE call opcode 36 | /// * `param_seg` - Data segment for the PXE parameter 37 | /// * `param_off` - Data offset for the PXE parameter 38 | /// 39 | pub fn pxecall(seg: u16, off: u16, pxe_call: u16, 40 | param_seg: u16, param_off: u16); 41 | } 42 | 43 | /// Structure representing general purpose i386 register state 44 | #[repr(C, packed)] 45 | #[derive(Clone, Copy, Debug, Default)] 46 | pub struct RegisterState { 47 | pub eax: u32, 48 | pub ecx: u32, 49 | pub edx: u32, 50 | pub ebx: u32, 51 | pub esp: u32, 52 | pub ebp: u32, 53 | pub esi: u32, 54 | pub edi: u32, 55 | pub efl: u32, 56 | 57 | pub es: u16, 58 | pub ds: u16, 59 | pub fs: u16, 60 | pub gs: u16, 61 | pub ss: u16, 62 | } 63 | 64 | /// Simple SegOff structure for things that contain a segment and offset. 65 | #[repr(C, packed)] 66 | pub struct SegOff { 67 | pub off: u16, 68 | pub seg: u16, 69 | } 70 | 71 | impl SegOff { 72 | /// Convert a seg:off real-mode address into a linear 32-bit address 73 | pub fn to_linear(&self) -> usize 74 | { 75 | ((self.seg as usize) << 4) + (self.off as usize) 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /bootloader/stage0.asm: -------------------------------------------------------------------------------- 1 | [bits 16] 2 | [org 0x7c00] 3 | 4 | struc flatpe 5 | .entry: resd 1 6 | .sections: resd 1 7 | .payload: 8 | endstruc 9 | 10 | struc flatpe_section 11 | .vaddr: resd 1 12 | .size: resd 1 13 | .data: 14 | endstruc 15 | 16 | entry: 17 | ; Disable interrupts and clear the direction flag 18 | cli 19 | cld 20 | 21 | ; Set the A20 line 22 | in al, 0x92 23 | or al, 2 24 | out 0x92, al 25 | 26 | ; Zero out DS for the lgdt 27 | xor ax, ax 28 | mov ds, ax 29 | 30 | ; Load the gdt (for 32-bit protected mode) 31 | lgdt [ds:pm_gdt] 32 | 33 | ; Set the protection bit 34 | mov eax, cr0 35 | or eax, (1 << 0) 36 | mov cr0, eax 37 | 38 | ; Jump to protected mode! 39 | jmp 0x0008:pm_entry 40 | 41 | [bits 32] 42 | 43 | pm_entry: 44 | ; Set data segments for protected mode 45 | mov ax, 0x10 46 | mov es, ax 47 | mov ds, ax 48 | mov fs, ax 49 | mov gs, ax 50 | mov ss, ax 51 | 52 | ; Set up a stack 53 | mov esp, 0x7c00 54 | 55 | ; Zero out entire range where kernel can be loaded [0x10000, 0x20000) 56 | ; This is our way of initializing all sections to zero so we only populate 57 | ; sections with raw data 58 | mov edi, 0x10000 59 | mov ecx, 0x20000 - 0x10000 60 | xor eax, eax 61 | rep stosb 62 | 63 | ; Get number of sections 64 | mov eax, [rust_entry + flatpe.sections] 65 | lea ebx, [rust_entry + flatpe.payload] 66 | .lewp: 67 | test eax, eax 68 | jz short .end 69 | 70 | mov edi, [ebx + flatpe_section.vaddr] 71 | lea esi, [ebx + flatpe_section.data] 72 | mov ecx, [ebx + flatpe_section.size] 73 | rep movsb 74 | 75 | add ebx, [ebx + flatpe_section.size] 76 | add ebx, flatpe_section_size 77 | dec eax 78 | jmp short .lewp 79 | 80 | .end: 81 | ; Jump into Rust! 82 | push dword kernel_buffer ; kernel_buffer: *mut KernelBuffer 83 | push dword [first_boot] ; first_boot: bool 84 | push dword soft_reboot_entry ; soft_reboot_entry: u32 85 | 86 | ; Set that this is no longer the first boot 87 | mov dword [first_boot], 0 88 | 89 | call dword [rust_entry + flatpe.entry] 90 | 91 | ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 92 | 93 | ; 16-bit real mode GDT 94 | 95 | align 8 96 | rmgdt_base: 97 | dq 0x0000000000000000 ; Null descriptor 98 | dq 0x00009a000000ffff ; 16-bit RO code, base 0, limit 0x0000ffff 99 | dq 0x000092000000ffff ; 16-bit RW data, base 0, limit 0x0000ffff 100 | 101 | rmgdt: 102 | dw (rmgdt - rmgdt_base) - 1 103 | dq rmgdt_base 104 | 105 | ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 106 | 107 | ; 32-bit protected mode GDT 108 | 109 | align 8 110 | pm_gdt_base: 111 | dq 0x0000000000000000 112 | dq 0x00CF9A000000FFFF 113 | dq 0x00CF92000000FFFF 114 | 115 | pm_gdt: 116 | dw (pm_gdt - pm_gdt_base) - 1 117 | dd pm_gdt_base 118 | 119 | ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 120 | 121 | align 8 122 | reentry_longjmp: 123 | dd rmmode_again 124 | dw 0x0008 125 | 126 | align 8 127 | rm_idt: 128 | dw 0xffff 129 | dq 0 130 | 131 | align 8 132 | rm_gdt: 133 | dw 0xffff 134 | dq 0 135 | 136 | ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 137 | 138 | ; Tracks if this is the first boot or not. This gets cleared to zero on the 139 | ; first boot, allowing the bootloader to know if it is in a soft reboot or 140 | ; not. This changes whether or not it needs to start up PXE again. 141 | first_boot: dd 1 142 | 143 | kernel_buffer: dq 0 144 | kernel_buffer_size: dq 0 145 | kernel_buffer_max_size: dq 0 146 | 147 | ; Boot magic 148 | times 510-($-$$) db 0 149 | dw 0xAA55 150 | 151 | times 0x400-($-$$) db 0 152 | 153 | [bits 16] 154 | 155 | ; Address 0x8000 156 | ap_entry: 157 | ; Disable interrupts and clear the direction flag 158 | cli 159 | cld 160 | 161 | ; Zero out DS for the lgdt 162 | xor ax, ax 163 | mov ds, ax 164 | 165 | ; Load the gdt (for 32-bit protected mode) 166 | lgdt [ds:pm_gdt] 167 | 168 | ; Set the protection bit 169 | mov eax, cr0 170 | or eax, (1 << 0) 171 | mov cr0, eax 172 | 173 | ; Jump to protected mode! 174 | jmp 0x0008:ap_pm_entry 175 | 176 | times 0x500-($-$$) db 0 177 | 178 | [bits 16] 179 | 180 | ; Addres 0x8100 181 | vm_entry: 182 | mov di, 0xb800 183 | mov es, di 184 | xor di, di 185 | mov cx, 80 * 25 186 | xor ax, ax 187 | rep stosw 188 | 189 | mov di, 0xb800 190 | mov es, di 191 | xor di, di 192 | mov cx, 80 193 | mov ax, 0x0f41 194 | rep stosw 195 | 196 | cli 197 | hlt 198 | jmp vm_entry 199 | 200 | [bits 64] 201 | 202 | soft_reboot_entry: 203 | cli 204 | 205 | ; Set up a stack 206 | mov esp, 0x7c00 207 | 208 | ; Clear registers 209 | xor rax, rax 210 | mov rbx, rax 211 | mov rcx, rax 212 | mov rdx, rax 213 | mov rsi, rax 214 | mov rdi, rax 215 | mov rbp, rax 216 | mov r8, rax 217 | mov r9, rax 218 | mov r10, rax 219 | mov r11, rax 220 | mov r12, rax 221 | mov r13, rax 222 | mov r14, rax 223 | mov r15, rax 224 | 225 | lgdt [rmgdt] 226 | 227 | xor eax, eax 228 | mov dword [eax], eax 229 | 230 | ; Must be far dword for Intel/AMD compatibility. AMD does not support 231 | ; 64-bit offsets in far jumps in long mode, Intel does however. Force 232 | ; it to be 32-bit as it works in both. 233 | jmp far dword [reentry_longjmp] 234 | 235 | [bits 16] 236 | 237 | align 16 238 | rmmode_again: 239 | ; Disable paging 240 | mov eax, cr0 241 | btr eax, 31 242 | mov cr0, eax 243 | 244 | ; Disable long mode 245 | mov ecx, 0xc0000080 246 | rdmsr 247 | btr eax, 8 248 | wrmsr 249 | 250 | ; Load up the segments to be 16-bit segments 251 | mov ax, 0x10 252 | mov es, ax 253 | mov ds, ax 254 | mov fs, ax 255 | mov gs, ax 256 | mov ss, ax 257 | 258 | ; Disable protected mode 259 | mov eax, cr0 260 | btr eax, 0 261 | mov cr0, eax 262 | 263 | ; Zero out all GPRs (clear out high parts for when we go into 16-bit) 264 | xor eax, eax 265 | mov ebx, eax 266 | mov ecx, eax 267 | mov edx, eax 268 | mov esi, eax 269 | mov edi, eax 270 | mov ebp, eax 271 | mov esp, 0x7c00 272 | 273 | ; Reset the GDT and IDT to their original boot states 274 | lgdt [rm_gdt] 275 | lidt [rm_idt] 276 | 277 | ; Jump back to the start of the bootloader 278 | jmp 0x0000:0x7c00 279 | 280 | [bits 32] 281 | 282 | ap_pm_entry: 283 | ; Set data segments for protected mode 284 | mov ax, 0x10 285 | mov es, ax 286 | mov ds, ax 287 | mov fs, ax 288 | mov gs, ax 289 | mov ss, ax 290 | 291 | ; Set up a stack 292 | mov esp, 0x7c00 293 | 294 | ; Jump into Rust! 295 | push dword 0 ; kernel_buffer: *mut KernelBuffer 296 | push dword 0 ; first_boot: bool 297 | push dword soft_reboot_entry ; soft_reboot_entry: u32 298 | call dword [rust_entry + flatpe.entry] 299 | 300 | rust_entry: 301 | incbin "stage1.flat" 302 | 303 | -------------------------------------------------------------------------------- /check_address/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "check_address" 3 | version = "0.1.0" 4 | authors = ["randomlshb@gmail.com "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | capstone = "0.6.0" 11 | bitflags = "1.2.1" 12 | pdb = "0.5.0" 13 | noodle = { path = "../shared/noodle" } 14 | serde = "*" 15 | serde_json = "*" 16 | -------------------------------------------------------------------------------- /check_address/README.md: -------------------------------------------------------------------------------- 1 | ### Check address from snapshot 2 | 3 | Utility to dump the module and instruction at a given address from the current snapshot. 4 | 5 | ``` 6 | cargo run --release -- 0xfffff8027ba0d1ef 7 | ``` 8 | 9 | ``` 10 | ntoskrnl.exe!KiSystemCall64Shadow+0x2f (ntoskrnl.exe+0xa0d1ef) 0xfffff8027ba0d1ef: push qword ptr gs:[0x9010] 11 | ``` 12 | 13 | ## Notes 14 | 15 | PDB listing is needed in `./versions/win10.system32` as a mapping of modules to their hash. 16 | 17 | Example: 18 | 19 | ``` 20 | user32.pdb HASH 21 | win32k.pdb HASH 22 | win32kbase.pdb HASH 23 | win32kfull.pdb HASH 24 | win32u.pdb HASH 25 | ``` 26 | -------------------------------------------------------------------------------- /check_address/src/memreader.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Seek, SeekFrom, Read}; 2 | use std::string::String; 3 | use std::fs::File; 4 | 5 | #[derive(Debug)] 6 | pub struct Module { 7 | name: String, 8 | fullname: String, 9 | address: u64, 10 | length: u64 11 | } 12 | 13 | #[derive(Debug, Clone, Copy)] 14 | pub enum Address { 15 | /// Physical address 16 | Physical(u64), 17 | 18 | /// Virtual address and CR3 19 | Virtual(u64, u64) 20 | } 21 | 22 | pub trait MemReader: Read + Seek { 23 | /// Read a u64 at the given Address 24 | fn read_u64(&mut self, addr: Address) -> u64 { 25 | let mut buffer = [0u8; 8]; 26 | self.read_mem(addr, &mut buffer).expect("Error on read_u64"); 27 | u64::from_le_bytes(buffer) 28 | } 29 | 30 | /// Read a u32 at the given Address 31 | fn read_u32(&mut self, addr: Address) -> u32 { 32 | let mut buffer = [0u8; 4]; 33 | self.read_mem(addr, &mut buffer).expect("Error on read_u64"); 34 | u32::from_le_bytes(buffer) 35 | } 36 | 37 | /// Read into a buffer at the given address. Assumes wanting to read into the 38 | /// entire buffer. 39 | fn read_mem(&mut self, addr: Address, buffer: &mut [u8]) -> Result<(), ()> { 40 | let paddr = match addr { 41 | Address::Physical(paddr) => paddr, 42 | Address::Virtual(_vaddr, _cr3) => { 43 | let res = self.translate(addr.clone()); 44 | if res.is_none() { return Err(()); } 45 | res.unwrap() as u64 46 | } 47 | }; 48 | 49 | self.seek(SeekFrom::Start(paddr)).expect("Unable to seek in read"); 50 | self.read_exact(buffer).expect("Unable to read_exact for read"); 51 | Ok(()) 52 | } 53 | 54 | /// Returns the virtual addresses and the virt to phys tranlations mapping from 55 | /// the data of a vbox core file 56 | fn translate(&mut self, addr: Address) -> Option { 57 | let (vaddr, mut curr_page) = match addr { 58 | Address::Virtual(vaddr, curr_page) => (vaddr, curr_page), 59 | Address::Physical(_x) => panic!("Cannot translate from Physical address") 60 | }; 61 | 62 | // print!("[translate] addr: {:x?}\n", addr); 63 | 64 | /* Calculate the components for each level of the page table from the vaddr. */ 65 | let cr_offsets: [u64; 4] = [ 66 | ((vaddr >> 39) & 0x1ff), /* 512 GiB */ 67 | ((vaddr >> 30) & 0x1ff), /* 1 GiB */ 68 | ((vaddr >> 21) & 0x1ff), /* 2 MiB */ 69 | ((vaddr >> 12) & 0x1ff), /* 4 KiB */ 70 | ]; 71 | 72 | // print!("Vaddr: {:x?}\n", addr); 73 | /* For each level in the page table */ 74 | for (_depth, curr_offset) in cr_offsets.iter().enumerate() { 75 | /* Get the page table entry */ 76 | let start_offset = (curr_page + (curr_offset * 8)) as usize; 77 | 78 | // print!("start_offset: {:#x} -- ", start_offset); 79 | let entry = self.read_u64(Address::Physical(start_offset as u64)); 80 | // print!("{:#x}\n", entry); 81 | if entry & 1 == 0 { 82 | // Entry not present 83 | if _depth > 0 { 84 | print!("[{}][{:x?}] {:#x} NOT PRESENT\n", _depth, cr_offsets, vaddr); 85 | } 86 | return None; 87 | } 88 | 89 | /* Get the physical address of the next level */ 90 | if entry >> 7 & 1 == 1 { 91 | curr_page = (entry & 0xffff_ffe0_0000) | (vaddr & 0x1f_ffff); 92 | // print!("[LargePage] {:#x}", curr_page); 93 | return Some(curr_page as usize); 94 | } 95 | 96 | // println!("entry: {:#x}", entry); 97 | curr_page = entry & 0xffff_ffff_f000; 98 | // println!("entry: {:#x}", curr_page); 99 | if curr_page == 0 { 100 | print!("NOT FOUND\n"); 101 | return None 102 | } 103 | } 104 | 105 | Some(curr_page as usize + (vaddr as usize & 0xfff)) 106 | } 107 | } 108 | 109 | impl MemReader for File {} 110 | -------------------------------------------------------------------------------- /check_address/versions/win10.system32: -------------------------------------------------------------------------------- 1 | user32.pdb HASH 2 | win32k.pdb HASH 3 | win32kbase.pdb HASH 4 | win32kfull.pdb HASH 5 | win32u.pdb HASH 6 | -------------------------------------------------------------------------------- /corpgen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "corpgen" 3 | version = "0.1.0" 4 | authors = ["rando"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | noodle = { path = "../shared/noodle" } 11 | -------------------------------------------------------------------------------- /corpgen/README.md: -------------------------------------------------------------------------------- 1 | # Corpus Generator 2 | 3 | Generates a serialized corpus to disk that can be shipped to the kernel. 4 | 5 | ## Usage 6 | 7 | Drop the binary into a directory containing corpus files. Run the binary to generate a `corpgen.corpus` file that can be shipped over to the kernel. The kernel can then deserialize the file to have a `Vec>` 8 | 9 | ## Example 10 | 11 | In corpus directory: 12 | 13 | ``` 14 | (ins)$ echo aaa > a 15 | (ins)$ echo bbbbbb > B 16 | (ins)$ echo cccccccccc > c 17 | (ins)$ ./corpgen 18 | "./a" 4 19 | "./B" 7 20 | "./c" 11 21 | Number of files: 3 22 | Largest: 11 -- "./c" 23 | Size of serialized: 54 24 | Corpus written to corpus.corpgen 25 | ``` 26 | 27 | In the kernel: 28 | 29 | ```rust 30 | let corpus_data = net::get_file("corpgen.corpus"); 31 | if corpus_data.len() == 0 { return; } 32 | let corpus = > as Deserialize>::deserialize(&mut corpus_data.as_slice()).unwrap(); 33 | ``` 34 | -------------------------------------------------------------------------------- /corpgen/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate noodle; 2 | use noodle::{Serialize, Deserialize}; 3 | 4 | use std::fs; 5 | use std::io::{Read, Write}; 6 | use std::path::PathBuf; 7 | 8 | fn main() -> std::io::Result<()> { 9 | let mut largest_size = 0; 10 | let mut largest_file = PathBuf::new(); 11 | let mut corpus: Vec> = Vec::new(); 12 | const OUTPUT_FILE: &'static str = "corpus.corpgen"; 13 | for entry in fs::read_dir(".")? { 14 | let curr_path = entry?.path(); 15 | if curr_path.is_dir() { continue; } 16 | 17 | // No need to add the binary or the output file to the corpus 18 | if curr_path.to_str().unwrap().contains("corpgen") { continue; } 19 | 20 | // Read the current file 21 | let mut data = Vec::new(); 22 | let mut file = fs::File::open(&curr_path)?; 23 | file.read_to_end(&mut data)?; 24 | 25 | // Check if this file is the largest in the corpus 26 | print!("{:?} {}\n", curr_path, data.len()); 27 | if largest_size < data.len() { 28 | largest_size = data.len(); 29 | largest_file = curr_path; 30 | } 31 | 32 | // Add the current file to the corpus 33 | corpus.push(data); 34 | } 35 | 36 | // Serialize the corpus to disk 37 | let mut ser = Vec::new(); 38 | corpus.serialize(&mut ser); 39 | 40 | let mut output = fs::File::create(OUTPUT_FILE)?; 41 | output.write_all(&ser)?; 42 | 43 | // Write the stats of the corpus 44 | print!("Number of files: {}\n", corpus.len()); 45 | print!("Largest: {} -- {:?}\n", largest_size, largest_file); 46 | print!("Size of serialized: {}\n", ser.len()); 47 | print!("Corpus written to {}\n", OUTPUT_FILE); 48 | 49 | Ok(()) 50 | } 51 | 52 | -------------------------------------------------------------------------------- /coverage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coverage" 3 | version = "0.1.0" 4 | authors = ["rando"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | # bitflags = "1.2.1" 11 | 12 | [profile.release] 13 | debug = true 14 | -------------------------------------------------------------------------------- /coverage/README.md: -------------------------------------------------------------------------------- 1 | ## Coverage 2 | 3 | Generates a `module+offset` file for loading into Lighthouse. Relies on the `../snapshot/snapshot.[dmp|phys]` files to be in place. 4 | 5 | ``` 6 | cargo run --release 7 | ``` 8 | -------------------------------------------------------------------------------- /coverage/coverage.gnuplot: -------------------------------------------------------------------------------- 1 | # set terminal pngcairo 2 | # set terminal png size 1920,1080 3 | set logscale x 10 4 | plot '..\tftp-server\coverage.txt.graph' with lines 5 | pause mouse close 6 | -------------------------------------------------------------------------------- /coverage/src/memreader.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Seek, SeekFrom, Read}; 2 | use std::string::String; 3 | use std::fs::File; 4 | 5 | #[derive(Debug)] 6 | pub struct Module { 7 | name: String, 8 | fullname: String, 9 | address: u64, 10 | length: u64 11 | } 12 | 13 | #[derive(Debug)] 14 | pub enum Address { 15 | /// Physical address 16 | Physical(u64), 17 | 18 | /// Virtual address and CR3 19 | Virtual(u64, u64) 20 | } 21 | 22 | pub trait MemReader: Read + Seek { 23 | /// Read a u64 at the given Address 24 | fn read_u64(&mut self, addr: Address) -> u64 { 25 | // print!("read_u64: {:x?}\n", addr); 26 | let mut buffer = [0u8; 8]; 27 | self.read_mem(addr, &mut buffer).expect("Error on read_u64"); 28 | u64::from_le_bytes(buffer) 29 | } 30 | 31 | /// Read a u32 at the given Address 32 | fn read_u32(&mut self, addr: Address) -> u32 { 33 | let mut buffer = [0u8; 4]; 34 | self.read_mem(addr, &mut buffer).expect("Error on read_u64"); 35 | u32::from_le_bytes(buffer) 36 | } 37 | 38 | /// Read into a buffer at the given address. Assumes wanting to read into the 39 | /// entire buffer. 40 | fn read_mem(&mut self, addr: Address, buffer: &mut [u8]) -> Result<(), ()> { 41 | let paddr = match addr { 42 | Address::Physical(paddr) => paddr, 43 | Address::Virtual(_vaddr, _cr3) => { 44 | let res = self.translate(addr); 45 | if res.is_none() { return Err(()); } 46 | res.unwrap() as u64 47 | } 48 | }; 49 | 50 | self.seek(SeekFrom::Start(paddr)).expect("Unable to seek in read"); 51 | self.read_exact(buffer).expect("Unable to read_exact for read"); 52 | Ok(()) 53 | } 54 | 55 | /// Returns the virtual addresses and the virt to phys tranlations mapping from 56 | /// the data of a vbox core file 57 | fn translate(&mut self, addr: Address) -> Option { 58 | let (vaddr, mut curr_page) = match addr { 59 | Address::Virtual(vaddr, curr_page) => (vaddr, curr_page), 60 | Address::Physical(_x) => panic!("Cannot translate from Physical address") 61 | }; 62 | 63 | // print!("[translate] addr: {:x?}\n", addr); 64 | 65 | /* Calculate the components for each level of the page table from the vaddr. */ 66 | let cr_offsets: [u64; 4] = [ 67 | ((vaddr >> 39) & 0x1ff), /* 512 GiB */ 68 | ((vaddr >> 30) & 0x1ff), /* 1 GiB */ 69 | ((vaddr >> 21) & 0x1ff), /* 2 MiB */ 70 | ((vaddr >> 12) & 0x1ff), /* 4 KiB */ 71 | ]; 72 | 73 | /* For each level in the page table */ 74 | for (_depth, curr_offset) in cr_offsets.iter().enumerate() { 75 | /* Get the page table entry */ 76 | let start_offset = (curr_page + (curr_offset * 8)) as usize; 77 | 78 | // print!("start_offset: {:#x} -- ", start_offset); 79 | let entry = self.read_u64(Address::Physical(start_offset as u64)); 80 | // print!("entry: {:#x}\n", entry); 81 | if entry & 1 == 0 { 82 | // Entry not present 83 | return None; 84 | } 85 | 86 | /* Get the physical address of the next level */ 87 | if entry >> 7 & 1 == 1 { 88 | // if entry & 1 << 7 > 0 { 89 | curr_page = (entry & 0xffff_ffe0_0000) | (vaddr & 0x1f_ffff); 90 | // print!("[LargePage] "); 91 | return Some(curr_page as usize); 92 | } 93 | 94 | curr_page = entry & 0xffff_ffff_f000; 95 | // println!("entry: {:#x}", curr_page); 96 | if curr_page == 0 { 97 | return None 98 | } 99 | } 100 | 101 | // println!(" -> {:#x}", curr_page); 102 | Some(curr_page as usize + (vaddr as usize & 0xfff)) 103 | } 104 | } 105 | 106 | impl MemReader for File {} 107 | 108 | /* 109 | fn main() -> std::io::Result<()> { 110 | let mut file = std::fs::File::open("/Users/user/workspace/barberslice/snapshot/snapshot.phys")?; 111 | 112 | let mut buffer = [0u8; 0x1000]; 113 | let needle = "\\SystemRoot\\system32\\nt"; 114 | let needle: Vec = needle.bytes().collect(); 115 | let needle_len = needle.len(); 116 | let kdbg; 117 | let mut kernel_cr3 = None; 118 | 119 | // From Volatility3's documentation (DTB - Directory Table Base aka Kernel cr3) 120 | // 121 | // New versions of windows, with randomized self-referential pointers, appear to 122 | // always load their dtb within a small specific range (`0x1a0000` and `0x1b0000`), 123 | // so instead we scan for all self-referential pointers in that range, and ignore any 124 | // that contain multiple self-references (since the DTB is very unlikely to point to 125 | // itself more than once). 126 | for counter in (0x1a0000..0x1b0000).step_by(0x1000) { 127 | for i in (0..0x1000).step_by(8) { 128 | let curr_ptr = file.read_u64( 129 | Address::Physical(counter + i)) & 0xffff_ffff_f000; 130 | if curr_ptr == counter { 131 | print!("DTB (kernel_cr3) [{:#x}]: {:#x}\n", counter + i as u64, counter); 132 | kernel_cr3 = Some(counter); 133 | break; 134 | } 135 | } 136 | } 137 | 138 | print!("KCR3: {:x?}\n", kernel_cr3); 139 | let kernel_cr3 = kernel_cr3.unwrap(); 140 | let mut counter = 0; 141 | 142 | 'leave: loop { 143 | file.read_mem(Address::Physical(counter), &mut buffer) 144 | .expect(&format!("Unable to read addr looking for KDBG: {:#x}", counter)); 145 | for i in 0..0x1000-needle_len { 146 | if &buffer[i..i+needle_len] == needle.as_slice() { 147 | let check_addr = (counter + i as u64).saturating_sub(0x18); 148 | let possible_kdbg = file.read_u64(Address::Physical(check_addr)) 149 | & 0xffff_ffff_ffff; 150 | let mut fileheader = [0; 2]; 151 | let res = file.read_mem(Address::Virtual(possible_kdbg, kernel_cr3), 152 | &mut fileheader); 153 | if res.is_err() { continue; } 154 | if fileheader == ['M' as u8, 'Z' as u8] { 155 | print!("KDBG [{:#x}]: {:#x}\n", counter + i as u64, possible_kdbg); 156 | kdbg = Some(possible_kdbg); 157 | break 'leave; 158 | } 159 | } 160 | } 161 | counter += 0x1000; 162 | } 163 | 164 | print!("KDBG: {:x?}\n", kdbg); 165 | 166 | // Attempt to brute force the offset of PsLoadedModuleList rather than 167 | // downloading the PDB for this ntoskrnl.exe and using that 168 | let mut psloadedmodulelist_offset = 0; 169 | for offset in (0..0x1000_0000).step_by(2) { 170 | let psloadedmodulelist = kdbg.unwrap() + offset; 171 | let psloadedmodulelist_phys = file.translate( 172 | Address::Virtual(psloadedmodulelist, kernel_cr3)); 173 | 174 | // If translate_phys was null, continue to look 175 | if psloadedmodulelist_phys.is_none() { continue; } 176 | 177 | // Guarenteed to be some now 178 | let psloadedmodulelist_phys = psloadedmodulelist_phys.unwrap() as u64; 179 | 180 | // print!("_phys {:#x}\n", psloadedmodulelist_phys); 181 | let ldr_data_table_entry = file.read_u64( 182 | Address::Physical(psloadedmodulelist_phys)) & 0xffff_ffff_ffff; 183 | let ldr_data_table_entry_phys = file.translate( 184 | Address::Virtual(ldr_data_table_entry, kernel_cr3)); 185 | 186 | // If translate_phys was null, continue to look 187 | if ldr_data_table_entry_phys.is_none() { continue; } 188 | 189 | // Guarenteed to be some now 190 | let ldr_data_table_entry_phys = ldr_data_table_entry_phys.unwrap() as u64; 191 | 192 | let data_entry = KldrDataTableEntry::from_reader(ldr_data_table_entry_phys, 193 | &mut file); 194 | 195 | if data_entry.flink >> 63 == 0 { continue; } 196 | if data_entry.blink >> 63 == 0 { continue; } 197 | 198 | let curr_base = data_entry.base_dll(kernel_cr3, &mut file); 199 | if curr_base.is_none() { continue; } 200 | 201 | // If the first dll is ntoskrnl.exe, we assume we have found the right offset 202 | if "ntoskrnl.exe" == curr_base.unwrap() { 203 | print!("Found PsLoadedModuleList: {:#x}\n", offset); 204 | psloadedmodulelist_offset = offset; 205 | break; 206 | } 207 | } 208 | 209 | assert!(psloadedmodulelist_offset > 0, "Failed to find PsLoadedModuleList offset"); 210 | print!("PSML offset: {:x?}\n", psloadedmodulelist_offset); 211 | 212 | let mut psloadedmodulelist = kdbg.unwrap() + psloadedmodulelist_offset; 213 | let mut found = HashSet::new(); 214 | let mut modlist = HashMap::new(); 215 | 216 | loop { 217 | let psloadedmodulelist_phys = file.translate( 218 | Address::Virtual(psloadedmodulelist, kernel_cr3)).unwrap() as u64; 219 | let data_entry = KldrDataTableEntry::from_reader(psloadedmodulelist_phys, 220 | &mut file); 221 | 222 | // Set the next loop address in case we can't resolve the base dll or full dll 223 | psloadedmodulelist = data_entry.flink; 224 | let base_dll = match data_entry.base_dll(kernel_cr3, &mut file) { 225 | None => continue, 226 | Some(dll) => dll 227 | }; 228 | 229 | let _full_dll = match data_entry.full_dll(kernel_cr3, &mut file) { 230 | None => continue, 231 | Some(dll) => dll 232 | }; 233 | 234 | let dll_base = data_entry.dll_base; 235 | let size = data_entry.size_of_image; 236 | modlist.insert(dll_base..dll_base+size, base_dll.to_string()); 237 | 238 | if !found.insert(base_dll) { 239 | break; 240 | } 241 | } 242 | 243 | print!("{:x?}\n", modlist); 244 | print!("{}\n", modlist.len()); 245 | 246 | Ok(()) 247 | } 248 | */ 249 | -------------------------------------------------------------------------------- /diverge/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "diverge" 3 | version = "0.1.0" 4 | authors = ["randomlshb@gmail.com "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | regex = "*" 11 | -------------------------------------------------------------------------------- /diverge/README.md: -------------------------------------------------------------------------------- 1 | ## Diverge 2 | 3 | Small utility used for diffing userland execution traces between Barbervisor and `windbg`. Was used in early testing to confirm register states matched execution in Barbervisor vs the same in windbg. 4 | 5 | Kept in the repo for legacy purposes. 6 | 7 | ## Windbg trace 8 | 9 | Windbg trace was dumped via the slowest possible mechanic. 10 | 11 | Trace until hit `0x7ff77393b375`. 12 | 13 | ``` 14 | .logopen single_step.txt 15 | Opened log file 'single_step.txt' 16 | 0:000> r; t; z(@rip != 0x7ff77393b375) 17 | ``` 18 | -------------------------------------------------------------------------------- /emu/bochsrc: -------------------------------------------------------------------------------- 1 | # configuration file generated by Bochs 2 | plugin_ctrl: unmapped=1, biosdev=1, speaker=1, extfpuirq=1, parallel=1, serial=1, gameport=1, e1000=1 3 | config_interface: win32config 4 | display_library: win32 5 | memory: host=1024, guest=1024 6 | romimage: file="C:\Users\user\git\bochs\bios\BIOS-bochs-latest", address=0x0, options=none 7 | vgaromimage: file="C:\Users\user\git\bochs\bios\VGABIOS-lgpl-latest" 8 | boot: cdrom 9 | floppy_bootsig_check: disabled=0 10 | # no floppyb 11 | # ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 12 | # ata0-master: type=none 13 | magic_break: enabled=1 14 | ata0-master: type=cdrom, path="C:\Users\user\git\barberslice\ipxe\src\bin\ipxe.iso", status=inserted 15 | ata0-slave: type=none 16 | ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15 17 | ata1-master: type=none 18 | ata1-slave: type=none 19 | ata2: enabled=0 20 | ata3: enabled=0 21 | optromimage1: file=none 22 | optromimage2: file=none 23 | optromimage3: file=none 24 | optromimage4: file=none 25 | optramimage1: file=none 26 | optramimage2: file=none 27 | optramimage3: file=none 28 | optramimage4: file=none 29 | pci: enabled=1, chipset=i440fx 30 | vga: extension=vbe, update_freq=5, realtime=1 31 | cpu: count=1, ips=4000000, model=corei7_haswell_4770, reset_on_triple_fault=0, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0 32 | # cpu: count=1, ips=4000000, model=bx_generic, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0 33 | print_timestamps: enabled=0 34 | port_e9_hack: enabled=0 35 | private_colormap: enabled=0 36 | clock: sync=realtime, time0=local, rtc_sync=0 37 | # no cmosimage 38 | # no loader 39 | log: bochs2.log 40 | logprefix: %t%e%d 41 | debug: action=report 42 | info: action=report 43 | error: action=report 44 | panic: action=ask 45 | keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none 46 | mouse: type=ps2, enabled=0, toggle=ctrl+mbutton 47 | # sound: waveoutdrv=win, waveout=none, waveindrv=win, wavein=none, midioutdrv=win, midiout=none 48 | # speaker: enabled=1, mode=sound 49 | parport1: enabled=0 50 | parport2: enabled=0 51 | com1: enabled=1, mode=file, dev="CON" 52 | com2: enabled=0 53 | com3: enabled=0 54 | com4: enabled=0 55 | # e1000: enabled=1, mac=b0:c4:20:00:00:00, ethmod=vnet, ethdev=".", script=none, bootrom=none 56 | e1000: enabled=1, mac=52:54:00:12:34:56, ethmod=vnet, ethdev="C:\\Users\\user\\git\\barberslice\\emu" 57 | # ne2k: ioaddr=0x300, irq=10, mac=b0:c4:20:00:00:01, ethmod=vnet, ethdev="." 58 | -------------------------------------------------------------------------------- /emu/boot.ipxe: -------------------------------------------------------------------------------- 1 | #!ipxe 2 | ifopen net0 3 | 4 | set net0/ip 192.168.10.2 5 | set net0/netmask 255.255.255.0 6 | set net0/dns 192.168.10.1 7 | set net0/gateway ${net0/dns} 8 | set net0/next-server ${net0/dns} 9 | set net0/filename barberslice.boot 10 | 11 | # Verify settings with user 12 | # config net0 13 | 14 | chain ${net0/filename} 15 | -------------------------------------------------------------------------------- /emu/slirp.conf: -------------------------------------------------------------------------------- 1 | # slirp config 2 | # The line above is mandatory 3 | 4 | # Supported options: 5 | # 6 | # RESTRICTED if set to 1, only built-in services are available 7 | # NET base IP address of the virtual network 8 | # MASK netmask of the virtual network 9 | # HOST IP address of the DHCP and TFTP server 10 | # HOSTNAME DHCP client hostname 11 | # DHCPSTART start address of DHCP pool 12 | # DNS IP address of the virtual DNS server 13 | # BOOTFILE boot filename returned by DHCP 14 | # DNSSEARCH comma-separated list of DNS suffixes to search (DHCP extension) 15 | # SMB_EXPORT absolute path to the shared folder (non-Windows SMB support) 16 | # SMB_SRV alternative IP address of the SMB server (default is 10.0.2.4) 17 | # HOSTFWD map guest port to host port for host-to-guest access 18 | # (format: protocol:hostaddr:hostport-guestaddr:guestport) 19 | 20 | # This is the default (classic slirp) setup 21 | # restricted = 0 22 | # net = 10.0.2.0 23 | # mask = 255.255.255.0 24 | # host = 10.0.2.2 25 | # dhcpstart = 10.0.2.15 26 | # dns = 10.0.2.3 27 | 28 | # This is the vnet setup 29 | # restricted = 1 30 | # net = 192.168.10.0 31 | # mask = 255.255.255.0 32 | # host = 192.168.10.1 33 | # dhcpstart = 192.168.10.2 34 | # dns = 0.0.0.0 35 | # bootfile = pxelinux.0 36 | 37 | # Host forwarding example (access guest SSH server from host port 12345) 38 | # hostfwd = tcp::12345-:22 39 | -------------------------------------------------------------------------------- /find_input/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "find_input" 3 | version = "0.1.0" 4 | authors = ["ctfhacker "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | glob = "0.3.0" 11 | rayon = "1.3.0" 12 | noodle = { path = "../shared/noodle" } 13 | -------------------------------------------------------------------------------- /find_input/README.md: -------------------------------------------------------------------------------- 1 | # Find input 2 | 3 | Given an address, will dump all of the found inputs that have hit the same coverage location. 4 | -------------------------------------------------------------------------------- /find_input/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate glob; 2 | extern crate rayon; 3 | extern crate noodle; 4 | 5 | use noodle::{Serialize, Deserialize}; 6 | use std::fs::File; 7 | use std::io::Read; 8 | use std::path::{PathBuf, Path}; 9 | use glob::{glob_with, MatchOptions}; 10 | use rayon::prelude::*; 11 | 12 | const WANTED: u64 = 0x7ffb_6791_0000 + 0x4bfd1; 13 | 14 | fn find_coverage(path: &PathBuf) -> bool { 15 | let mut file = File::open(path).expect("Failed to open file"); 16 | let mut coverage = Vec::new(); 17 | file.read_to_end(&mut coverage).expect("Failed to read coverage"); 18 | let cov = as Deserialize>::deserialize(&mut coverage.as_slice()).unwrap(); 19 | cov.iter().filter(|x| x[0] == WANTED).count() > 0 20 | } 21 | 22 | fn main() -> Result<(), ()> { 23 | let options: MatchOptions = Default::default(); 24 | let files: Vec<_> = glob_with("../tftp-server/project/coverages/*.coverage", options).unwrap() 25 | .filter_map(|x| x.ok()) 26 | .collect(); 27 | 28 | if files.len() == 0 { 29 | panic!("No coverage files found"); 30 | } 31 | 32 | let found: Vec<_> = files.par_iter() 33 | .map(|path| (find_coverage(path), path)) 34 | .filter(|(x, path)| *x) 35 | .map(|(x, path)| path) 36 | .collect(); 37 | 38 | let mut found_files = Vec::new(); 39 | for f in found { 40 | let filename = f.as_path().file_stem().unwrap().to_str().unwrap(); 41 | let new_path = format!("../tftp-server/project/inputs/{}", filename); 42 | let file_size = std::fs::metadata(&new_path).unwrap().len(); 43 | found_files.push((file_size, new_path)); 44 | } 45 | found_files.sort(); 46 | for i in found_files { 47 | print!("{:?}\n", i); 48 | } 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /flatten_pe.py: -------------------------------------------------------------------------------- 1 | import sys, struct 2 | 3 | IMAGE_FILE_MACHINE_I386 = 0x014c 4 | 5 | MIN_ADDR = 0x10000 6 | MAX_ADDR = 0x20000 7 | 8 | if len(sys.argv) != 3: 9 | print("Usage: flatten_pe.py ") 10 | 11 | pe_file = open(sys.argv[1], "rb").read() 12 | 13 | # Check for MZ 14 | assert pe_file[:2] == b"MZ", "No MZ header present" 15 | 16 | # Grab pointer to PE header 17 | pe_ptr = struct.unpack(" vaddr 54 | assert raw_data_size <= rounded_vsize 55 | assert vaddr >= MIN_ADDR and vaddr < MAX_ADDR 56 | assert vend > MIN_ADDR and vend <= MAX_ADDR 57 | 58 | # Skip zero sized raw data sections 59 | if raw_data_size <= 0: 60 | continue 61 | 62 | sections.append((vaddr, vend, \ 63 | pe_file[raw_data_ptr:raw_data_ptr+raw_data_size])) 64 | 65 | flattened = bytearray() 66 | for (vaddr, vend, raw_data) in sections: 67 | # Should never happen as this is checked above 68 | assert len(raw_data) > 0 69 | 70 | print("%.8x %.8x" % (vaddr, len(raw_data))) 71 | 72 | flattened += struct.pack(""] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | serial = { path = "../shared/serial" } 9 | cpu = { path = "../shared/cpu" } 10 | rangeset = { path = "../shared/rangeset" } 11 | mmu = { path = "../shared/mmu" } 12 | safecast = { path = "../shared/safecast" } 13 | bytesafe_derive = { path = "../shared/safecast/bytesafe_derive" } 14 | packets = { path = "../shared/packets" } 15 | noodle = { path = "../shared/noodle" } 16 | bitflags = "1.2.1" 17 | log = "0.4.6" 18 | x86_64 = "0.11.1" 19 | linked_list_allocator = "0.8.2" 20 | spin = "0.5.2" 21 | pic8259_simple = "0.2.0" 22 | pc-keyboard = "0.3.1" 23 | cpuio = "0.3.0" 24 | sha2 = "0.5.2" 25 | bit_field = "0.9.0" 26 | volatile = "0.2.6" 27 | 28 | [dependencies.hashbrown] 29 | version = "0.6.2" 30 | features = ["nightly"] 31 | 32 | [dependencies.lazy_static] 33 | version = "1.0" 34 | features = ["spin_no_std"] 35 | 36 | [profile.release] 37 | panic = "abort" 38 | lto = false 39 | # debug = true 40 | 41 | [profile.dev] 42 | panic = "abort" 43 | 44 | [dependencies.num] 45 | version = "0.2" 46 | default-features = false 47 | 48 | [dependencies.num-traits] 49 | version = "0.2" 50 | default-features = false 51 | 52 | [dependencies.num-derive] 53 | version = "0.2" 54 | default-features = false 55 | -------------------------------------------------------------------------------- /kernel/src/core_reqs.rs: -------------------------------------------------------------------------------- 1 | /// libc `memcpy` implementation in rust 2 | /// 3 | /// This implementation of `memcpy` is overlap safe, making it technically 4 | /// `memmove`. 5 | /// 6 | /// # Parameters 7 | /// 8 | /// * `dest` - Pointer to memory to copy to 9 | /// * `src` - Pointer to memory to copy from 10 | /// * `n` - Number of bytes to copy 11 | /// 12 | #[no_mangle] 13 | pub unsafe extern fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 14 | { 15 | memmove(dest, src, n) 16 | } 17 | 18 | /// libc `memmove` implementation in rust 19 | /// 20 | /// # Parameters 21 | /// 22 | /// * `dest` - Pointer to memory to copy to 23 | /// * `src` - Pointer to memory to copy from 24 | /// * `n` - Number of bytes to copy 25 | /// 26 | #[no_mangle] 27 | pub unsafe extern fn memmove(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 28 | { 29 | if src < dest as *const u8 { 30 | /* copy backwards */ 31 | let mut ii = n; 32 | while ii != 0 { 33 | ii -= 1; 34 | *dest.offset(ii as isize) = *src.offset(ii as isize); 35 | } 36 | } else { 37 | /* copy forwards */ 38 | let mut ii = 0; 39 | while ii < n { 40 | *dest.offset(ii as isize) = *src.offset(ii as isize); 41 | ii += 1; 42 | } 43 | } 44 | 45 | dest 46 | } 47 | 48 | /// libc `memset` implementation in rust 49 | /// 50 | /// # Parameters 51 | /// 52 | /// * `s` - Pointer to memory to set 53 | /// * `c` - Character to set `n` bytes in `s` to 54 | /// * `n` - Number of bytes to set 55 | /// 56 | #[no_mangle] 57 | pub unsafe extern fn memset(s: *mut u8, c: i32, n: usize) -> *mut u8 58 | { 59 | let mut ii = 0; 60 | while ii < n { 61 | *s.offset(ii as isize) = c as u8; 62 | ii += 1; 63 | } 64 | 65 | s 66 | } 67 | 68 | /// libc `memcmp` implementation in rust 69 | /// 70 | /// # Parameters 71 | /// 72 | /// * `s1` - Pointer to memory to compare with s2 73 | /// * `s2` - Pointer to memory to compare with s1 74 | /// * `n` - Number of bytes to set 75 | #[no_mangle] 76 | pub unsafe extern fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 77 | { 78 | let mut ii = 0; 79 | while ii < n { 80 | let a = *s1.offset(ii as isize); 81 | let b = *s2.offset(ii as isize); 82 | if a != b { 83 | return a as i32 - b as i32 84 | } 85 | ii += 1; 86 | } 87 | 88 | 0 89 | } 90 | 91 | /// Fake `__chkstk()` stub. This is just a nop. If we run out of stack we will 92 | /// crash with a page fault, but that'll have to do. 93 | #[no_mangle] 94 | pub unsafe extern fn __chkstk() {} 95 | 96 | // Making a fake __CxxFrameHandler3 in Rust causes a panic, this is hacky 97 | // workaround where we declare it as a function that will just crash if it 98 | // gets called. 99 | // We should never hit this so it doesn't matter. 100 | global_asm!(r#" 101 | .global __CxxFrameHandler3 102 | __CxxFrameHandler3: 103 | ud2 104 | "#); 105 | 106 | #[no_mangle] 107 | pub unsafe extern fn cos() -> ! { panic!("Unhandled cos"); } 108 | 109 | #[no_mangle] 110 | pub unsafe extern fn cosf() -> ! { panic!("Unhandled cosf"); } 111 | 112 | #[no_mangle] 113 | pub unsafe extern fn sinf() -> ! { panic!("Unhandled sinf"); } 114 | 115 | #[no_mangle] 116 | pub unsafe extern fn sin() -> ! { panic!("Unhandled sin"); } 117 | 118 | -------------------------------------------------------------------------------- /kernel/src/corpus.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use spin::Mutex; 3 | use crate::Rng; 4 | use crate::ni; 5 | use crate::net; 6 | use crate::Deserialize; 7 | 8 | lazy_static! { 9 | pub static ref CORPUS: Mutex = Mutex::new(Corpus::new()); 10 | } 11 | 12 | pub struct Corpus { 13 | pub inputs: Vec> 14 | } 15 | 16 | impl Corpus { 17 | pub fn new() -> Corpus { 18 | Corpus { 19 | inputs: Vec::new() 20 | } 21 | } 22 | 23 | /// Initialize the corpus with a file from the server. 24 | /// 25 | /// This file should be created via the `corpgen` utility. 26 | pub fn init(&mut self, filename: &str) { 27 | print!("Getting corpus.. {}\n", filename); 28 | let corpus_data = net::get_file(filename); 29 | if corpus_data.len() == 0 { return; } 30 | let files = > as Deserialize>::deserialize(&mut corpus_data.as_slice()).unwrap(); 31 | for f in files { 32 | self.insert(f); 33 | } 34 | print!("Init corpus with {} items\n", self.len()); 35 | } 36 | 37 | /// Insert a sample into the corpus 38 | pub fn insert(&mut self, input: Vec) { 39 | self.inputs.push(input); 40 | } 41 | 42 | /// Get the number of samples in the corpus 43 | pub fn len(&self) -> usize { 44 | self.inputs.len() 45 | } 46 | 47 | /// Get a random sample from the corpus 48 | pub fn rand_input(&self, rng: &mut Rng) -> Vec { 49 | let offset = rng.next() as usize % self.len(); 50 | self.inputs[offset].clone() 51 | } 52 | 53 | /// Get a mutated sample from the corpus 54 | /// 55 | /// This will randomly choose one sample from the corpus and mutate it based on the C version 56 | /// of Radamsa (which was ported to Rust). 57 | pub fn mutated_input(&self) -> Vec { 58 | ni::mutate_samples(&self.inputs) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /kernel/src/coverage.rs: -------------------------------------------------------------------------------- 1 | use crate::HashSet; 2 | use spin::Mutex; 3 | use core::ops::Range; 4 | 5 | use crate::vmregs::VmRegs; 6 | use crate::net; 7 | use alloc::vec::Vec; 8 | use alloc::string::String; 9 | use core::convert::TryInto; 10 | use crate::time; 11 | use crate::stats::LAST_COVERAGE; 12 | use core::sync::atomic::Ordering; 13 | 14 | pub enum CoverageType { 15 | All, 16 | User, 17 | Kernel, 18 | Ranges(Vec>), 19 | None 20 | } 21 | 22 | fn as_u8_slice(data: &T) -> &[u8] { 23 | unsafe { 24 | ::core::slice::from_raw_parts((data as *const T) as *const u8, ::core::mem::size_of::()) 25 | } 26 | } 27 | 28 | lazy_static! { 29 | pub static ref COVERAGE: Mutex = Mutex::new(Coverage::new()); 30 | } 31 | 32 | pub struct Coverage { 33 | pub seen: HashSet, 34 | // seen: HashSet, 35 | graph: Vec, 36 | } 37 | 38 | impl Coverage { 39 | /// Create a new Coverage 40 | pub fn new() -> Coverage { 41 | Coverage { 42 | seen: HashSet::new(), 43 | graph: Vec::new(), 44 | } 45 | } 46 | 47 | /// Initialize the Coverage based on a file 48 | pub fn init(&mut self, filename: &str) { 49 | let coverage_data = net::get_file(filename); 50 | if coverage_data.len() == 0 { return; } 51 | for i in (0..coverage_data.len()).step_by(8) { 52 | let data = u64::from_le_bytes(coverage_data[i..i+8].try_into().unwrap()); 53 | self.seen.insert(data); 54 | } 55 | 56 | print!("Init coverage with {} items\n", self.len()); 57 | 58 | } 59 | 60 | /// Get the length of the current coverage 61 | pub fn len(&self) -> usize { 62 | self.seen.len() 63 | } 64 | 65 | /// Insert RIP to the current coverage 66 | pub fn insert(&mut self, regs: &VmRegs) -> bool { 67 | let res = self.seen.insert(regs.get_rip()); 68 | if res { 69 | // If we have new cov, mark down the current time for stats to print the time since 70 | // last coverage 71 | LAST_COVERAGE.store(time::rdtsc(), Ordering::SeqCst); 72 | } 73 | res 74 | } 75 | 76 | /// Insert a given address into the coverage 77 | pub fn insert_addr(&mut self, addr: u64) -> bool { 78 | let res = self.seen.insert(addr); 79 | if res { 80 | // If we have new cov, mark down the current time for stats to print the time since 81 | // last coverage 82 | LAST_COVERAGE.store(time::rdtsc(), Ordering::SeqCst); 83 | } 84 | res 85 | } 86 | 87 | /// Add another entry in the coverage graph for the current amount of coverage. Used for 88 | /// plotting graph 89 | pub fn mark_graph(&mut self) { 90 | self.graph.push(self.len() as u32); 91 | } 92 | 93 | /// Put the current trace on the TFTP server with the given filename 94 | pub fn put(&mut self, filename: &str) { 95 | let mut data = Vec::new(); 96 | for addr in self.seen.iter() { 97 | data.extend(&addr.to_le_bytes()); 98 | } 99 | 100 | // Generate the .graph file for GNUPlot 101 | let mut graph_data = String::new(); 102 | for num in self.graph.iter() { 103 | graph_data.push_str(&format!("{}\n", num)); 104 | } 105 | 106 | // Put the coverage address information 107 | net::put_file(&filename, &data); 108 | 109 | // Put the coverage len by second data 110 | net::put_file(&format!("{}.graph", filename), graph_data.as_bytes()); 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /kernel/src/fuzzers/example.rs: -------------------------------------------------------------------------------- 1 | //! Example fuzzer 2 | 3 | use crate::fuzzers::{Breakpoint, FuzzFunc, Fuzzer}; 4 | use crate::fuzzvm::FuzzVm; 5 | use alloc::boxed::Box; 6 | use alloc::vec::Vec; 7 | 8 | use crate::coverage::CoverageType; 9 | 10 | pub struct ExampleFuzzer; 11 | 12 | const IMAGE_LEN: u64 = 32886; 13 | const IMAGE_ADDR: u64 = 0x221_c993_af80; 14 | 15 | impl Fuzzer for ExampleFuzzer { 16 | /// Sanity check to make sure the snapshot matches the fuzzer 17 | fn start_rip(&self) -> u64 { 18 | if cpu::is_bsp() { 19 | // CORPUS.lock().init("corpus.corpgen"); 20 | // COVERAGE.lock().init("coverage.txt"); 21 | } 22 | 23 | 0x7ff6_1ca4_11e4 24 | } 25 | 26 | /// Timeout after 1 sec 27 | fn duration_timeout(&self) -> u64 { 1_000_000 } 28 | 29 | /// Timeout after 20M instructions 30 | fn instruction_timeout(&self) -> u64 { 20_000_000 } 31 | 32 | fn coverage_type(&self) -> CoverageType { 33 | CoverageType::None 34 | // CoverageType::All 35 | // CoverageType::User 36 | // CoverageType::Kernel 37 | } 38 | 39 | /// Fuzz the memory after reset 40 | fn fuzz_fn(&self) -> Option { 41 | Some(Box::new(|_vm: &mut FuzzVm| { 42 | })) 43 | } 44 | 45 | /// Set of breakpoints to signify the end of a fuzz case 46 | fn exit_breakpoints(&self) -> Vec { 47 | vec![ 48 | // Breakpoint::Virtual(GuestVirtual(0x1a4d)), 49 | // Breakpoint::Virtual(GuestVirtual(0x1ad9)), 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /kernel/src/fuzzers/leadtools_ani.rs: -------------------------------------------------------------------------------- 1 | //! Leadtools with Gflags snapshot example 2 | //! 3 | //! Example: 4 | //! 5 | //! The snapshot starts at address 0x7ff7ce331a0e 6 | //! Image loaded at address 0x221c993af80 and had a length of 32886 7 | //! The module containing the parser in question was loaded at 0x7ffab6e70000 8 | //! The two breakpoints, if hit, that signify end of fuzz case are in `exit_breakpoints` 9 | use crate::fuzzers::{Breakpoint, FuzzFunc, Fuzzer, FuzzHookFunc}; 10 | use crate::fuzzvm::FuzzVm; 11 | use alloc::boxed::Box; 12 | use alloc::vec::Vec; 13 | 14 | use crate::CORPUS; 15 | use crate::GuestVirtual; 16 | use crate::coverage::CoverageType; 17 | 18 | pub struct LeadtoolsFuzzer; 19 | 20 | const IMAGE_LEN: u64 = 32886; 21 | const IMAGE_ADDR: u64 = 0x221_c993_af80; 22 | const LFANIX: u64 = 0x7ffa_b6e7_0000; 23 | const MEMORY_2017: u64 = 0x7ff7_ce33_0000; 24 | 25 | impl Fuzzer for LeadtoolsFuzzer { 26 | /// Sanity check to make sure the snapshot matches the fuzzer 27 | fn start_rip(&self) -> u64 { 28 | if cpu::is_bsp() { 29 | CORPUS.lock().init("corpus.corpgen"); 30 | // COVERAGE.lock().init("coverage.txt"); 31 | } 32 | 33 | 0x7ff7_ce33_1a0e 34 | } 35 | 36 | /// Timeout after 5 sec 37 | fn duration_timeout(&self) -> u64 { 5_000_000 } 38 | 39 | /// Timeout after 20M instructions 40 | fn instruction_timeout(&self) -> u64 { 20_000_000 } 41 | 42 | fn coverage_type(&self) -> CoverageType { 43 | // CoverageType::All 44 | // CoverageType::User 45 | // CoverageType::Kernel 46 | CoverageType::Ranges(vec![ 47 | LFANIX..LFANIX+0x1c000 48 | ]) 49 | } 50 | 51 | /// Fuzz the memory after reset 52 | fn fuzz_fn(&self) -> Option { 53 | Some(Box::new(|vm: &mut FuzzVm| { 54 | // Write one input file from the CORPUS 55 | if CORPUS.lock().len() > 0 { 56 | loop { 57 | let new_input = CORPUS.lock().mutated_input(); 58 | if new_input.len() < IMAGE_LEN as usize { 59 | vm.write_bytes(GuestVirtual(IMAGE_ADDR), &[0; IMAGE_LEN as usize]); 60 | vm.write_bytes(GuestVirtual(IMAGE_ADDR), &new_input); 61 | vm.input_file = Some(new_input); 62 | break; 63 | } 64 | } 65 | } else { 66 | panic!("No corpus found!"); 67 | } 68 | })) 69 | } 70 | 71 | /// Set of breakpoints to signify the end of a fuzz case 72 | fn exit_breakpoints(&self) -> Vec { 73 | vec![ 74 | Breakpoint::Virtual(GuestVirtual(MEMORY_2017+0x1a4d)), // dead0000 75 | Breakpoint::Virtual(GuestVirtual(MEMORY_2017+0x1ad9)), // dead0001 76 | ] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /kernel/src/fuzzers/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::coverage::CoverageType; 2 | use crate::fuzzvm::FuzzVm; 3 | use crate::{GuestPhysical, GuestVirtual}; 4 | use alloc::boxed::Box; 5 | use alloc::vec::Vec; 6 | 7 | /* Available fuzzers */ 8 | // pub mod leadtools_ani; 9 | pub mod example; 10 | 11 | pub enum Breakpoint { 12 | Virtual(GuestVirtual), 13 | Physical(GuestPhysical), 14 | } 15 | 16 | /// Closure called to fuzz the snapshot 17 | pub type FuzzFunc = Box; 18 | 19 | /// Closure called to return the input file of the current fuzz run 20 | pub type FuzzFileFunc = Box Vec>; 21 | 22 | /// Closure called to insert patches into the snapshot 23 | pub type FuzzPatchFunc = Box; 24 | 25 | /// Closure called to insert hooks into the snapshot 26 | pub type FuzzHookFunc = Box; 27 | 28 | /// Generic Fuzzer implementation. 29 | /// 30 | /// The FuzzVm will take a Fuzzer as input and use it as follows: 31 | /// 32 | /// In order to tell when a VM has finished execution, a set of `exit_breakpoints` can 33 | /// be set. If these breakpoints are hit, they immediately trigger a VM reset. 34 | /// 35 | /// On each VM reset, `fuzz_fn` will be called which should fuzz the current memory of 36 | /// the VM, specific to the fuzz case. After the VM has been "fuzzed", `input_file_fn` 37 | /// which should return the current fuzzed input. This will be sent over TFTP on the 38 | /// event of a crash. 39 | pub trait Fuzzer { 40 | /// Safety check to make sure the fuzzer matches the given snapshot 41 | fn start_rip(&self) -> u64; 42 | 43 | /// Timeout based on time (in microseconds) 44 | fn duration_timeout(&self) -> u64 { 45 | 0 46 | } 47 | 48 | /// Timeout based on instructions executed 49 | fn instruction_timeout(&self) -> u64 { 50 | 0 51 | } 52 | 53 | /// Returns the type of coverage to gather for this fuzzer 54 | fn coverage_type(&self) -> CoverageType { 55 | CoverageType::All 56 | } 57 | 58 | /// Function which will fuzz the current VM 59 | fn fuzz_fn(&self) -> Option { 60 | None 61 | } 62 | 63 | /// Function which returns the current input fuzz case 64 | fn input_file_fn(&self) -> Option { 65 | None 66 | } 67 | 68 | /// Breakpoints that are set to tell the VM that the fuzz case is finished. 69 | /// These breakpoints can only be hit once. 70 | /// 71 | /// By default, this will return an empty Vec to signify no exit breakpoints. 72 | fn exit_breakpoints(&self) -> Vec { 73 | Vec::new() 74 | } 75 | 76 | /// List of patch function called one time to hard patch bytes in memory in the 77 | /// local page cache 78 | /// 79 | /// (GuestVirtual, bytes to write) 80 | fn patches(&self) -> Vec<(GuestVirtual, Vec)> { 81 | Vec::new() 82 | } 83 | 84 | /// List of addresses to hook along with callbacks for when that hook is hit. 85 | /// DOES NOT CURRENTLY HANDLE REPLACING THE BREAKPOINT BYTE. 86 | fn hooks(&self) -> Vec<(Breakpoint, FuzzHookFunc)> { 87 | Vec::new() 88 | } 89 | 90 | /// Function called before the fuzzing function is called 91 | fn pre_fuzz_fn(&self) -> Option { 92 | None 93 | } 94 | 95 | /// Function called after the fuzzing function is called 96 | fn post_fuzz_fn(&self) -> Option { 97 | None 98 | } 99 | 100 | /// Function called during the stats time slot 101 | fn stats_fn(&self) -> Option { 102 | None 103 | } 104 | 105 | /// Function called during single stepping 106 | fn single_step_fn(&self) -> Option { 107 | None 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /kernel/src/gdt.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector}; 3 | use x86_64::structures::tss::TaskStateSegment; 4 | use x86_64::VirtAddr; 5 | 6 | pub const DOUBLE_FAULT_IST_INDEX: u16 = 0; 7 | 8 | lazy_static! { 9 | static ref TSS: TaskStateSegment = { 10 | let mut tss = TaskStateSegment::new(); 11 | tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = { 12 | const STACK_SIZE: usize = 4096; 13 | static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; 14 | 15 | let stack_start = VirtAddr::from_ptr(unsafe { &STACK }); 16 | let stack_end = stack_start + STACK_SIZE; 17 | stack_end 18 | }; 19 | tss 20 | }; 21 | } 22 | 23 | lazy_static! { 24 | static ref GDT: (GlobalDescriptorTable, Selectors) = { 25 | let mut gdt = GlobalDescriptorTable::new(); 26 | let code_selector = gdt.add_entry(Descriptor::kernel_code_segment()); 27 | let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS)); 28 | ( 29 | gdt, 30 | Selectors { 31 | code_selector, 32 | tss_selector, 33 | }, 34 | ) 35 | }; 36 | } 37 | 38 | struct Selectors { 39 | code_selector: SegmentSelector, 40 | tss_selector: SegmentSelector, 41 | } 42 | 43 | pub fn init() { 44 | use x86_64::instructions::segmentation::set_cs; 45 | use x86_64::instructions::tables::load_tss; 46 | 47 | GDT.0.load(); 48 | unsafe { 49 | set_cs(GDT.1.code_selector); 50 | load_tss(GDT.1.tss_selector); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /kernel/src/interrupts.rs: -------------------------------------------------------------------------------- 1 | use crate::println; 2 | use lazy_static::lazy_static; 3 | use pic8259_simple::ChainedPics; 4 | use spin; 5 | // use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; 6 | use crate::idt::{InterruptDescriptorTable, InterruptStackFrame}; 7 | 8 | pub const PIC_1_OFFSET: u8 = 32; 9 | pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; 10 | 11 | #[derive(Debug, Clone, Copy)] 12 | #[repr(u8)] 13 | pub enum InterruptIndex { 14 | Timer = PIC_1_OFFSET, 15 | Keyboard, 16 | } 17 | 18 | impl InterruptIndex { 19 | fn as_u8(self) -> u8 { 20 | self as u8 21 | } 22 | 23 | fn as_usize(self) -> usize { 24 | usize::from(self.as_u8()) 25 | } 26 | } 27 | 28 | pub static PICS: spin::Mutex = 29 | spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) }); 30 | 31 | lazy_static! { 32 | static ref IDT: InterruptDescriptorTable = { 33 | let mut idt = InterruptDescriptorTable::new(); 34 | // print!("Init IDT\n"); 35 | // idt.breakpoint.set_handler_fn(breakpoint_handler); 36 | /* 37 | unsafe { 38 | idt.double_fault 39 | .set_handler_fn(double_fault_handler) 40 | .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); 41 | } 42 | */ 43 | idt[InterruptIndex::Timer.as_usize()].set_handler_fn(timer_interrupt_handler); 44 | // idt[InterruptIndex::Keyboard.as_usize()].set_handler_fn(keyboard_interrupt_handler); 45 | idt 46 | }; 47 | } 48 | 49 | pub fn init_idt() { 50 | IDT.load(); 51 | } 52 | 53 | extern "x86-interrupt" fn gp_fault_handler(stack_frame: &mut InterruptStackFrame) { 54 | println!("EXCEPTION: GENERAL PROTECTION\n{:#?}", stack_frame); 55 | } 56 | 57 | 58 | extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut InterruptStackFrame) { 59 | println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); 60 | } 61 | 62 | extern "x86-interrupt" fn double_fault_handler(stack_frame: &mut InterruptStackFrame) 63 | { 64 | panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); 65 | } 66 | 67 | extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut InterruptStackFrame) { 68 | // print!("."); 69 | unsafe { 70 | PICS.lock().notify_end_of_interrupt(InterruptIndex::Timer.as_u8()); 71 | } 72 | } 73 | 74 | extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut InterruptStackFrame) { 75 | use x86_64::instructions::port::Port; 76 | 77 | let mut port = Port::new(0x60); 78 | let _scancode: u8 = unsafe { port.read() }; 79 | 80 | unsafe { 81 | PICS.lock().notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8()); 82 | } 83 | 84 | if crate::CORES != crate::Cores::Single { 85 | // crate::stats::print_debug(); 86 | // crate::stats::print(); 87 | crate::stats::reset(); 88 | return; 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | use crate::{serial_print, serial_println}; 94 | 95 | #[test_case] 96 | fn test_breakpoint_exception() { 97 | serial_print!("test_breakpoint_exception..."); 98 | // invoke a breakpoint exception 99 | x86_64::instructions::interrupts::int3(); 100 | serial_println!("[ok]"); 101 | } 102 | -------------------------------------------------------------------------------- /kernel/src/mm.rs: -------------------------------------------------------------------------------- 1 | use crate::acpi; 2 | use alloc::alloc::{GlobalAlloc, Layout}; 3 | use core::sync::atomic::{AtomicUsize, Ordering, AtomicBool}; 4 | use mmu::PhysMem; 5 | 6 | pub struct GlobalAllocator; 7 | 8 | /// Physical memory implementation 9 | /// 10 | /// This is used during page table operations 11 | pub struct Pmem; 12 | 13 | /// Physical memory implementation 14 | /// 15 | /// This is used during page table operations 16 | pub struct Ept; 17 | 18 | pub static mut PMEM: Pmem = Pmem; 19 | static PMEM_LOCK: AtomicBool = AtomicBool::new(false); 20 | 21 | /// Allocate a new zeroed out page and return it 22 | pub fn alloc_page() -> Option<&'static mut [u8; 4096]> { 23 | let res = unsafe { 24 | if let Some(page) = PMEM.alloc_page() { 25 | let page = &mut *(page as *mut [u8; 4096]); 26 | *page = [0x0u8; 4096]; 27 | Some(page) 28 | } else { 29 | None 30 | } 31 | }; 32 | 33 | 34 | res 35 | } 36 | 37 | /// Allocate n bytes 38 | pub fn alloc(_size: usize) -> Option { 39 | panic!("Calling alloc N\n"); 40 | } 41 | 42 | impl mmu::PhysMem for Pmem { 43 | /// Allocate a page 44 | fn alloc_page(&mut self) -> Option<*mut u8> { 45 | let res = unsafe { 46 | // Get current node id 47 | let node_id = acpi::get_node_id(cpu::get_apic_id()); 48 | 49 | // Get an allocation on the current node 50 | let alloc = acpi::node_alloc_page(node_id.unwrap_or(0)); 51 | if alloc.is_null() { 52 | None 53 | } else { 54 | Some(alloc as *mut u8) 55 | } 56 | }; 57 | 58 | 59 | res 60 | } 61 | 62 | /// Read a 64-bit value at the physical address specified 63 | fn read_phys(&mut self, addr: *mut u64) -> Result { 64 | unsafe { Ok(core::ptr::read(addr)) } 65 | } 66 | 67 | /// Write a 64-bit value to the physical address specified 68 | fn write_phys(&mut self, addr: *mut u64, val: u64) -> Result<(), &'static str> { 69 | let res = unsafe { Ok(core::ptr::write(addr, val)) }; 70 | res 71 | } 72 | 73 | /// This is used to let the MMU know if we reserve memory outside of 74 | /// the page tables. Since we do not do this at all we always return true 75 | /// allowing any address not in use in the page tables to be used for 76 | /// ASLR. 77 | fn probe_vaddr(&mut self, _addr: usize, _length: usize) -> bool { 78 | true 79 | } 80 | } 81 | 82 | static PAGE_ALLOC_LOCK: AtomicUsize = AtomicUsize::new(0); 83 | static PAGE_ALLOC_LOCK_REL: AtomicUsize = AtomicUsize::new(0); 84 | 85 | unsafe impl GlobalAlloc for GlobalAllocator { 86 | /// Global allocator. Grabs free memory from E820 and removes it from the table. 87 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 88 | let size = layout 89 | .size() 90 | .checked_add(0xfff) 91 | .expect("layout.size() checked_add failed") 92 | & !0xfff; 93 | assert!(size > 0, "Zero size allocations not allowed"); 94 | 95 | while PMEM_LOCK.compare_exchange(false, true, 96 | Ordering::Acquire, Ordering::Acquire).is_err() { 97 | } 98 | 99 | // Get access to the current page table 100 | let mut page_table = mmu::PageTable::from_existing(cpu::read_cr3() as *mut _, &mut PMEM); 101 | 102 | // Pick a random 64-bit address to return as the allocation 103 | let alc_base = page_table 104 | .rand_addr(size as u64) 105 | .expect("page_table rand_addr failed"); 106 | 107 | page_table 108 | .add_memory(alc_base, size as u64) 109 | .expect("page_table.add_memory failed"); 110 | 111 | // print!("Releasing lock in alloc\n"); 112 | PMEM_LOCK.store(false, Ordering::Release); 113 | 114 | alc_base as *mut u8 115 | } 116 | 117 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 118 | let size = layout 119 | .size() 120 | .checked_add(0xfff) 121 | .expect("dealloc checked_add failed") 122 | & !0xfff; 123 | assert!(size > 0, "Zero size allocations not allowed"); 124 | let size = size as u64; 125 | 126 | while PMEM_LOCK.compare_exchange(false, true, 127 | Ordering::Acquire, Ordering::Acquire).is_err() { 128 | // print!("Stuck in dealloc\n"); 129 | } 130 | 131 | // Get access to the current page table 132 | let mut page_table = mmu::PageTable::from_existing(cpu::read_cr3() as *mut _, &mut PMEM); 133 | 134 | // Go through each page in the allocation and unmap it 135 | for ii in (0..size).step_by(4096) { 136 | let addr = ptr as u64 + ii; 137 | assert!((addr & 0xfff) == 0, "Non-page-aligned allocation"); 138 | 139 | // Go through all physical pages that were removed 140 | for ppage in &page_table.unmap_page(addr).expect("Failed to unmap"){ 141 | if let Some(ppage) = ppage { 142 | // Get current node id 143 | let node_id = acpi::get_node_id(cpu::get_apic_id()).unwrap_or(0); 144 | acpi::node_free_page(node_id, (*ppage) as *mut u8); 145 | } 146 | } 147 | } 148 | 149 | PMEM_LOCK.store(false, Ordering::Release); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /kernel/src/msr_bitmap.rs: -------------------------------------------------------------------------------- 1 | //! MsrBitmap helper module 2 | //! Intel Manual: 24.6.9 MSR-Bitmap Address 3 | //! 4 | //! Example: 5 | //! 6 | //! // Default to all MSRs exit (all enabled) 7 | //! let bitmap = MsrBitmap::new(); 8 | //! 9 | //! // Clear 0xe7 msr so it doesn't VMExit on read or write 10 | //! bitmap.clear_read(0xe7); 11 | //! bitmap.clear_write(0xe7); 12 | //! 13 | use crate::mm; 14 | use crate::tools::memcpy_slice; 15 | 16 | pub struct MsrBitmap { 17 | backing: u64, 18 | } 19 | 20 | enum Quantity { 21 | Low, 22 | High, 23 | } 24 | 25 | enum Access { 26 | Read, 27 | Write, 28 | } 29 | 30 | impl MsrBitmap { 31 | pub fn new() -> MsrBitmap { 32 | let backing = mm::alloc_page() 33 | .expect("Failed to allocate msr low bitmap") 34 | .as_mut_ptr(); 35 | 36 | /* Default to VMExit on NO MSRs*/ 37 | unsafe { 38 | core::ptr::write_bytes(backing, 0, 4096); 39 | } 40 | 41 | MsrBitmap { backing: backing as u64 } 42 | } 43 | 44 | pub fn get_backing(&self) -> u64 { 45 | self.backing 46 | } 47 | 48 | pub fn enable_all(&self) { 49 | memcpy_slice(self.backing as *mut u8, &[0xff; 0x1000]); 50 | } 51 | 52 | pub fn disable_all(&self) { 53 | memcpy_slice(self.backing as *mut u8, &[0x00; 0x1000]); 54 | } 55 | 56 | pub fn clear_read(&self, msr: u64) { 57 | self.clear_bit(Access::Read, msr); 58 | } 59 | 60 | pub fn clear_write(&self, msr: u64) { 61 | self.clear_bit(Access::Write, msr); 62 | } 63 | 64 | pub fn set_read(&self, msr: u64) { 65 | self.set_bit(Access::Read, msr); 66 | } 67 | 68 | pub fn set_write(&self, msr: u64) { 69 | self.set_bit(Access::Write, msr); 70 | } 71 | 72 | /// Clear the bit for the given MSR with the given Access 73 | fn clear_bit(&self, access: Access, msr: u64) { 74 | let quantity = match msr { 75 | 0..0x1fff => Quantity::Low, 76 | 0xc0000000..0xc0001fff => Quantity::High, 77 | _ => panic!("Unknown msr given"), 78 | }; 79 | 80 | let address = match (access, quantity) { 81 | (Access::Read, Quantity::Low) => self.backing, 82 | (Access::Read, Quantity::High) => self.backing + 1024, 83 | (Access::Write, Quantity::Low) => self.backing + 2048, 84 | (Access::Write, Quantity::High) => self.backing + 3072, 85 | }; 86 | 87 | let msr = msr & 0x1fff; 88 | let byte_offset = msr >> 3; 89 | let bit_offset = msr & 7; 90 | 91 | unsafe { 92 | *((address + (byte_offset as u64)) as *mut u8) &= !(1 << bit_offset); 93 | } 94 | } 95 | 96 | /// Set the bit for the given MSR with the given Access 97 | fn set_bit(&self, access: Access, msr: u64) { 98 | let quantity = match msr { 99 | 0..0x1fff => Quantity::Low, 100 | 0xc0000000..0xc0001fff => Quantity::High, 101 | _ => panic!("Unknown msr given"), 102 | }; 103 | 104 | let address = match (access, quantity) { 105 | (Access::Read, Quantity::Low) => self.backing, 106 | (Access::Read, Quantity::High) => self.backing + 1024, 107 | (Access::Write, Quantity::Low) => self.backing + 2048, 108 | (Access::Write, Quantity::High) => self.backing + 3072, 109 | }; 110 | 111 | let msr = msr & 0x1fff; 112 | let byte_offset = msr >> 3; 113 | let bit_offset = msr & 7; 114 | 115 | unsafe { 116 | *((address + (byte_offset as u64)) as *mut u8) |= 1 << bit_offset; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /kernel/src/mutations.rs: -------------------------------------------------------------------------------- 1 | //! Test helper for storing mutations used to fuzz one file rather than storing the entire 2 | //! input test case. 3 | use alloc::vec::Vec; 4 | use spin::Mutex; 5 | use crate::Rng; 6 | use crate::net; 7 | use crate::{Serialize, Deserialize}; 8 | 9 | lazy_static! { 10 | pub static ref MUTATIONS: Mutex = Mutex::new(Mutations::new()); 11 | } 12 | 13 | noodle!(serialize, deserialize, 14 | #[derive(Clone, Copy)] 15 | pub struct Mutation { 16 | /// Offset in file to mutate 17 | offset: u32, 18 | 19 | /// Byte to replace 20 | byte: u8 21 | } 22 | ); 23 | 24 | impl Mutation { 25 | pub fn new(offset: u32, byte: u8) -> Mutation { 26 | Mutation { offset, byte } 27 | } 28 | } 29 | 30 | 31 | pub struct Mutations { 32 | /// List of mutations [address; byte] 33 | pub mutations: Vec> 34 | } 35 | 36 | impl Mutations { 37 | pub fn new() -> Mutations { 38 | Mutations { 39 | mutations: Vec::new() 40 | } 41 | } 42 | 43 | /// Initialize the corpus with a file from the server. 44 | /// 45 | /// This file should be created via the `corpgen` utility. 46 | pub fn init(&mut self, filename: &str) { 47 | print!("Getting corpus.. {}\n", filename); 48 | let corpus_data = net::get_file(filename); 49 | if corpus_data.len() == 0 { return; } 50 | let files = > as Deserialize>::deserialize(&mut corpus_data.as_slice()); 51 | let files = files.unwrap(); 52 | for f in files { 53 | self.insert(f); 54 | } 55 | print!("Init corpus with {} items\n", self.len()); 56 | } 57 | 58 | /// Insert a sample into the corpus 59 | pub fn insert(&mut self, input: Vec) { 60 | self.mutations.push(input); 61 | } 62 | 63 | /// Get the number of samples in the corpus 64 | pub fn len(&self) -> usize { 65 | self.mutations.len() 66 | } 67 | 68 | /// Get a random sample from the corpus 69 | pub fn rand_input(&self, rng: &mut Rng) -> Vec { 70 | if self.len() == 0 { 71 | return Vec::new(); 72 | } 73 | let offset = rng.next() as usize % self.len(); 74 | self.mutations[offset].clone() 75 | } 76 | 77 | /// Sends the current mutations corpus to the server 78 | pub fn put_to_server(&self) { 79 | let mut data = Vec::new(); 80 | self.mutations.serialize(&mut data); 81 | crate::net::put_file("corpus_mutations", &data); 82 | } 83 | } 84 | 85 | -------------------------------------------------------------------------------- /kernel/src/panic.rs: -------------------------------------------------------------------------------- 1 | use crate::vga_print; 2 | use core::panic::PanicInfo; 3 | use cpu; 4 | 5 | /// Panic implementation 6 | #[panic_handler] 7 | #[no_mangle] 8 | pub fn panic(info: &PanicInfo) -> ! { 9 | if let Some(location) = info.location() { 10 | vga_print!("!!! PANIC !!! {}:{}\n", location.file(), location.line(),); 11 | } else { 12 | vga_print!("!!! PANIC !!! Panic with no location info\n"); 13 | } 14 | 15 | if let Some(&args) = info.message() { 16 | vga_print!("{}", args); 17 | // use core::fmt::write; 18 | // let _ = write(&mut crate::Writer, args); 19 | vga_print!("\n"); 20 | } else { 21 | vga_print!("No arguments\n"); 22 | } 23 | 24 | cpu::halt(); 25 | } 26 | -------------------------------------------------------------------------------- /kernel/src/rng.rs: -------------------------------------------------------------------------------- 1 | //! Psueudo random number generator based on 2 | //! 3 | /// Reimplementation of https://github.com/eqv/rand_romu 4 | pub struct Rng { 5 | xstate: u64, 6 | ystate: u64, 7 | } 8 | 9 | impl Rng { 10 | /// Creates a new RandRumo rng initialized with values from Lehmer64 initialized with `rdtsc`. 11 | pub fn new() -> Rng { 12 | // Generate the random state from Lehmer64 13 | let mut lehmer64 = Lehmer64::new(); 14 | let mut res = Rng { 15 | xstate: lehmer64.next(), 16 | ystate: lehmer64.next(), 17 | }; 18 | 19 | // Cycle through to create some chaos 20 | for _ in 0..100 { 21 | let _ = res.next(); 22 | } 23 | 24 | res 25 | } 26 | 27 | pub fn next(&mut self) -> u64 { 28 | let xp = self.xstate; 29 | self.xstate = 15241094284759029579u64.wrapping_mul(self.ystate); 30 | self.ystate = self.ystate.wrapping_sub(xp); 31 | self.ystate = self.ystate.rotate_left(27); 32 | return xp; 33 | } 34 | 35 | /// Returns true 50% of the time 36 | pub fn chance_50(&mut self) -> bool { 37 | self.next() & 0x1 == 0 38 | } 39 | 40 | /// Returns true 25% of the time 41 | pub fn chance_25(&mut self) -> bool { 42 | self.next() & 0x3 == 0 43 | } 44 | 45 | /// Returns true 12% of the time 46 | pub fn chance_12(&mut self) -> bool { 47 | self.next() & 0x7 == 0 48 | } 49 | 50 | /// Returns true 6% of the time 51 | pub fn chance_6(&mut self) -> bool { 52 | self.next() & 0xf == 0 53 | } 54 | 55 | /// Returns true 3% of the time 56 | pub fn chance_3(&mut self) -> bool { 57 | self.next() & 0x1f == 0 58 | } 59 | 60 | /// Returns true 3% of the time 61 | pub fn chance_1(&mut self) -> bool { 62 | self.next() & 0x3f == 0 63 | } 64 | 65 | /// Returns true 75% of the time 66 | pub fn chance_75(&mut self) -> bool { 67 | !self.chance_25() 68 | } 69 | 70 | /// Returns true 88% of the time 71 | pub fn chance_88(&mut self) -> bool { 72 | !self.chance_12() 73 | } 74 | 75 | /// Returns true 94% of the time 76 | pub fn chance_94(&mut self) -> bool { 77 | !self.chance_6() 78 | } 79 | 80 | /// Returns true 97% of the time 81 | pub fn chance_97(&mut self) -> bool { 82 | !self.chance_3() 83 | } 84 | 85 | /// Returns true 99% of the time 86 | pub fn chance_99(&mut self) -> bool { 87 | !self.chance_1() 88 | } 89 | } 90 | 91 | /// Rng seeded with rdtsc that is generated using Lehmer64 92 | pub struct Lehmer64 { 93 | value: u128, 94 | } 95 | 96 | impl Lehmer64 { 97 | pub fn new() -> Lehmer64 { 98 | let mut res = Lehmer64 { 99 | value: unsafe { core::arch::x86_64::_rdtsc() } as u128, 100 | }; 101 | 102 | // Cycle through to create some chaos 103 | for _ in 0..100 { 104 | let _ = res.next(); 105 | } 106 | 107 | res 108 | } 109 | 110 | pub fn next(&mut self) -> u64 { 111 | self.value = self.value.wrapping_mul(0xda942042e4dd58b5); 112 | (self.value >> 64) as u64 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /kernel/src/time.rs: -------------------------------------------------------------------------------- 1 | //! Various time functions relative to the current CPU frequency 2 | //! 3 | //! Example: 4 | //! 5 | //! ``` 6 | //! // 5 second timeout 7 | //! let mut timeout = time::future(5_000_000); 8 | //! 9 | //! loop { 10 | //! // Check for timeout 11 | //! if timeout < time::rdtsc() { 12 | //! /* Handle timeout */ 13 | //! 14 | //! // Reset the timer to the next 5seconds 15 | //! timeout = time::future(5_000_000); 16 | //! } 17 | //! } 18 | //! ``` 19 | use core::sync::atomic::{AtomicUsize, Ordering}; 20 | use cpu; 21 | 22 | pub static BOOT_TIME: AtomicUsize = AtomicUsize::new(0); 23 | pub static RDTSC_RATE: AtomicUsize = AtomicUsize::new(2100); 24 | 25 | /// Return the timestamp after `microseconds` have elapsed in the future. Used for timeouts. 26 | pub fn future(microseconds: u64) -> u64 { 27 | cpu::rdtsc() + (microseconds * RDTSC_RATE.load(Ordering::SeqCst) as u64) 28 | } 29 | 30 | /// Returns system uptime in seconds as a float 31 | pub fn uptime() -> f64 { 32 | rdtsc_elapsed(BOOT_TIME.load(Ordering::SeqCst) as u64) 33 | } 34 | 35 | /// Get the number of microseconds since the `start_time` 36 | pub fn rdtsc_elapsed(start_time: u64) -> f64 { 37 | (cpu::rdtsc() - start_time) as f64 / RDTSC_RATE.load(Ordering::SeqCst) as f64 / 1_000_000.0 38 | } 39 | 40 | /// Sleep for a number of microseconds 41 | pub fn sleep(microseconds: u64) { 42 | // print!("Sleeping: {}\n", microseconds); 43 | let waitval = future(microseconds); 44 | while cpu::rdtsc() < waitval { 45 | unsafe { 46 | llvm_asm!("pause" :::: "volatile"); 47 | } 48 | } 49 | } 50 | 51 | /// Get the current `rdtsc` 52 | pub fn rdtsc() -> u64 { 53 | unsafe { core::arch::x86_64::_rdtsc() } 54 | } 55 | 56 | /// Using the PIT, determine the frequency of rdtsc. Round this frequency to 57 | /// the nearest 100MHz. 58 | pub unsafe fn calibrate() { 59 | // Store off the current rdtsc value 60 | let start = cpu::rdtsc(); 61 | 62 | print!("rdtsc at boot is: {}\n", start); 63 | 64 | // Store this off as the system boot time 65 | BOOT_TIME.store(start as usize, Ordering::SeqCst); 66 | 67 | let start = cpu::rdtsc(); 68 | 69 | // Program the PIT to use mode 0 (interrupt after countdown) to 70 | // count down from 65535. This causes an interrupt to occur after 71 | // about 54.92 milliseconds (65535 / 1193182). We mask interrupts 72 | // from the PIT, thus we poll by sending the read back command 73 | // to check whether the output pin is set to 1, indicating the 74 | // countdown completed. 75 | cpu::out8(0x43, 0x30); 76 | cpu::out8(0x40, 0xff); 77 | cpu::out8(0x40, 0xff); 78 | 79 | loop { 80 | // Send the read back command to latch status on channel 0 81 | cpu::out8(0x43, 0xe2); 82 | 83 | // If the output pin is high, then we know the countdown is 84 | // done. Break from the loop. 85 | if (cpu::in8(0x40) & 0x80) != 0 { 86 | break; 87 | } 88 | } 89 | 90 | // Compute the time, in seconds, that the countdown was supposed to 91 | // take 92 | let elapsed = 65535f64 / 1193182f64; 93 | 94 | // Compute MHz / second for the rdtsc 95 | let computed_rate = ((cpu::rdtsc() - start) as f64) / elapsed / 1000000.0; 96 | 97 | // Round to the nearest 100MHz value 98 | let rounded_rate = (((computed_rate / 100.0) + 0.5) as u64) * 100; 99 | 100 | print!("CPU frequency calibrated to be {} MHz\n", rounded_rate); 101 | 102 | // Store the rounded rate in RDSTC_RATE 103 | RDTSC_RATE.store(rounded_rate as usize, Ordering::SeqCst); 104 | } 105 | -------------------------------------------------------------------------------- /kernel/src/tools.rs: -------------------------------------------------------------------------------- 1 | //! Various utilities like hexdump for debugging 2 | use crate::print; 3 | use crate::KernelPhysical; 4 | 5 | /// Display the given physical address address in a hexdump format 6 | /// 7 | /// Example: 8 | /// 9 | /// hexdump(0x106a1000, 0x40); 10 | /// 11 | /// 0x106a1000: 48 ff c0 48 ff c3 48 ff c1 48 ff c2 48 ff c6 48 : H..H..H..H..H..H 12 | /// 0x106a1010: ff c7 49 ff c0 49 ff c1 49 ff c2 49 ff c3 49 ff : ..I..I..I..I..I. 13 | /// 0x106a1020: c4 49 ff c5 49 ff c6 49 ff c7 48 ff c4 48 ff c5 : .I..I..I..H..H.. 14 | /// 0x106a1030: 48 ff c8 0f 01 c1 00 00 00 00 00 00 00 00 00 00 : H............... 15 | pub fn hexdump(addr: u64, n: u64) { 16 | let curr_addr = addr; 17 | 18 | for i in (0..n).step_by(0x10) { 19 | print!("{:#x}: ", curr_addr + i); 20 | 21 | for x in i..i + 0x10 { 22 | unsafe { 23 | print!("{:0>2x} ", *((curr_addr + x) as *mut u8)); 24 | } 25 | } 26 | 27 | print!("\n"); 28 | } 29 | } 30 | 31 | /// Hexdump for &[u8] 32 | pub fn dump(data: &[u8]) { 33 | for i in (0..data.len()).step_by(0x10) { 34 | let max = if i + 0x10 > data.len() { 35 | data.len() 36 | } else { 37 | i + 0x10 38 | }; 39 | 40 | for x in i..max { 41 | print!("{:0>2x} ", data[x]); 42 | } 43 | 44 | // Padding in case the slice isn't a multiple of 0x10 45 | if max % 16 != 0 { 46 | for _ in 0..(0x10 - (max % 16)) { 47 | print!("00 "); 48 | } 49 | } 50 | 51 | print!(": "); 52 | 53 | for x in i..max { 54 | let c = data[x]; 55 | match c { 56 | (0x20..0x7e) => print!("{}", c as char), 57 | _ => print!("."), 58 | } 59 | } 60 | 61 | // Padding in case the slice isn't a multiple of 0x10 62 | if max % 16 != 0 { 63 | for _ in 0..(0x10 - max % 16) { 64 | print!("."); 65 | } 66 | } 67 | 68 | print!("\n"); 69 | } 70 | } 71 | 72 | /// Memcpy with C's arguments 73 | #[inline(always)] 74 | pub fn memcpy(dest: *mut u8, src: *const u8, n: usize) { 75 | unsafe { 76 | core::intrinsics::copy_nonoverlapping(src, dest, n); 77 | } 78 | } 79 | 80 | /// Memcpy with C's arguments 81 | pub fn memcpy_page(dest: KernelPhysical, src: *const [u8; 4096]) { 82 | assert!(dest.0 & 0xfff == 0); 83 | 84 | unsafe { 85 | core::intrinsics::copy_nonoverlapping(src, dest.0 as *mut [u8; 4096], 4096); 86 | } 87 | } 88 | 89 | /// Memcpy with slice 90 | pub fn memcpy_slice(dest: *mut u8, src: &[u8]) { 91 | memcpy(dest, src.as_ptr(), src.len()); 92 | } 93 | -------------------------------------------------------------------------------- /kernel/src/trace.rs: -------------------------------------------------------------------------------- 1 | //! Primitive trace mechanic used to sending VmReg traces to the TFTP server 2 | use crate::net; 3 | use crate::vmregs::VmRegs; 4 | use alloc::vec::Vec; 5 | 6 | fn as_u8_slice(data: &T) -> &[u8] { 7 | unsafe { 8 | ::core::slice::from_raw_parts((data as *const T) as *const u8, ::core::mem::size_of::()) 9 | } 10 | } 11 | 12 | pub struct Trace { 13 | buff: alloc::vec::Vec, 14 | } 15 | 16 | impl Trace { 17 | pub fn new() -> Trace { 18 | Trace { 19 | buff: alloc::vec::Vec::new(), 20 | } 21 | } 22 | 23 | /// Put the current trace on the TFTP server with the given filename 24 | pub fn put(&self, filename: &str) { 25 | let mut data: Vec = Vec::new(); 26 | let mut file_index = 0; 27 | let mut curr_filename = format!("{}{}", filename, file_index); 28 | 29 | let chunk_size = 0xffff_ffff; 30 | let chunks = self.buff.len() / chunk_size; 31 | print!("Total chunks: {}\n", chunks); 32 | 33 | for (index, i) in self.buff.iter().enumerate() { 34 | for ch in as_u8_slice(&*i) { 35 | data.push(*ch); 36 | } 37 | if index > 0 && index % chunk_size == 0 { 38 | net::put_file(&curr_filename, &data); 39 | data.clear(); 40 | file_index += 1; 41 | curr_filename = format!("{}{}", filename, file_index); 42 | } 43 | } 44 | 45 | net::put_file(&curr_filename, &data); 46 | } 47 | 48 | /// Put the last N items from current trace on the TFTP server with the given filename 49 | pub fn put_n(&self, filename: &str, n: usize) { 50 | let n = if self.buff.len() < n { 51 | self.buff.len() 52 | } else { 53 | n 54 | }; 55 | let mut data: Vec = Vec::new(); 56 | for i in self.buff[self.buff.len() - n..].iter() { 57 | for ch in as_u8_slice(&*i) { 58 | data.push(*ch); 59 | } 60 | } 61 | net::put_file(filename, &data); 62 | } 63 | 64 | /// Returns the number of elements in the trace 65 | pub fn len(&self) -> usize { 66 | self.buff.len() 67 | } 68 | 69 | /// Add a VmRegs to the trace 70 | pub fn push(&mut self, regs: VmRegs) { 71 | self.buff.push(regs); 72 | } 73 | 74 | /// Pops the last element off the trace buffer 75 | pub fn pop(&mut self) { 76 | let _ = self.buff.pop(); 77 | } 78 | 79 | /// Clears the current trace buffer 80 | pub fn clear(&mut self) { 81 | self.buff.clear(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /kernel/src/vga_buffer.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use lazy_static::lazy_static; 3 | use spin::Mutex; 4 | use volatile::Volatile; 5 | 6 | #[cfg(test)] 7 | use crate::{serial_print, serial_println}; 8 | 9 | lazy_static! { 10 | /// A global `Writer` instance that can be used for printing to the VGA text buffer. 11 | /// 12 | /// Used by the `print!` and `println!` macros. 13 | pub static ref WRITER: Mutex = Mutex::new(Writer { 14 | column_position: 0, 15 | color_code: ColorCode::new(Color::Green, Color::Black), 16 | buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, 17 | }); 18 | } 19 | 20 | /// The standard color palette in VGA text mode. 21 | #[allow(dead_code)] 22 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 23 | #[repr(u8)] 24 | pub enum Color { 25 | Black = 0, 26 | Blue = 1, 27 | Green = 2, 28 | Cyan = 3, 29 | Red = 4, 30 | Magenta = 5, 31 | Brown = 6, 32 | LightGray = 7, 33 | DarkGray = 8, 34 | LightBlue = 9, 35 | LightGreen = 10, 36 | LightCyan = 11, 37 | LightRed = 12, 38 | Pink = 13, 39 | Yellow = 14, 40 | White = 15, 41 | } 42 | 43 | /// A combination of a foreground and a background color. 44 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 45 | #[repr(transparent)] 46 | pub struct ColorCode(u8); 47 | 48 | impl ColorCode { 49 | /// Create a new `ColorCode` with the given foreground and background colors. 50 | fn new(foreground: Color, background: Color) -> ColorCode { 51 | ColorCode((background as u8) << 4 | (foreground as u8)) 52 | } 53 | } 54 | 55 | /// A screen character in the VGA text buffer, consisting of an ASCII character and a `ColorCode`. 56 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 57 | #[repr(C)] 58 | struct ScreenChar { 59 | ascii_character: u8, 60 | color_code: ColorCode, 61 | } 62 | 63 | /// The height of the text buffer (normally 25 lines). 64 | const BUFFER_HEIGHT: usize = 25; 65 | /// The width of the text buffer (normally 80 columns). 66 | const BUFFER_WIDTH: usize = 80; 67 | 68 | /// A structure representing the VGA text buffer. 69 | #[repr(transparent)] 70 | struct Buffer { 71 | chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], 72 | } 73 | 74 | /// A writer type that allows writing ASCII bytes and strings to an underlying `Buffer`. 75 | /// 76 | /// Wraps lines at `BUFFER_WIDTH`. Supports newline characters and implements the 77 | /// `core::fmt::Write` trait. 78 | pub struct Writer { 79 | column_position: usize, 80 | pub color_code: ColorCode, 81 | buffer: &'static mut Buffer, 82 | } 83 | 84 | impl Writer { 85 | /// Writes an ASCII byte to the buffer. 86 | /// 87 | /// Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character. 88 | pub fn write_byte(&mut self, byte: u8) { 89 | match byte { 90 | b'\n' => self.new_line(), 91 | byte => { 92 | if self.column_position >= BUFFER_WIDTH { 93 | self.new_line(); 94 | } 95 | 96 | let row = BUFFER_HEIGHT - 1; 97 | let col = self.column_position; 98 | 99 | let color_code = self.color_code; 100 | self.buffer.chars[row][col].write(ScreenChar { 101 | ascii_character: byte, 102 | color_code, 103 | }); 104 | self.column_position += 1; 105 | } 106 | } 107 | } 108 | 109 | /// Writes the given ASCII string to the buffer. 110 | /// 111 | /// Wraps lines at `BUFFER_WIDTH`. Supports the `\n` newline character. Does **not** 112 | /// support strings with non-ASCII characters, since they can't be printed in the VGA text 113 | /// mode. 114 | fn write_string(&mut self, s: &str) { 115 | for byte in s.bytes() { 116 | match byte { 117 | // printable ASCII byte or newline 118 | 0x20..=0x7e | b'\n' => self.write_byte(byte), 119 | // not part of printable ASCII range 120 | _ => self.write_byte(0xfe), 121 | } 122 | } 123 | } 124 | 125 | /// Shifts all lines one line up and clears the last row. 126 | fn new_line(&mut self) { 127 | for row in 1..BUFFER_HEIGHT { 128 | for col in 0..BUFFER_WIDTH { 129 | let character = self.buffer.chars[row][col].read(); 130 | self.buffer.chars[row - 1][col].write(character); 131 | } 132 | } 133 | self.clear_row(BUFFER_HEIGHT - 1); 134 | self.column_position = 0; 135 | } 136 | 137 | /// Clears a row by overwriting it with blank characters. 138 | fn clear_row(&mut self, row: usize) { 139 | let blank = ScreenChar { 140 | ascii_character: b' ', 141 | color_code: self.color_code, 142 | }; 143 | for col in 0..BUFFER_WIDTH { 144 | self.buffer.chars[row][col].write(blank); 145 | } 146 | } 147 | } 148 | 149 | impl fmt::Write for Writer { 150 | fn write_str(&mut self, s: &str) -> fmt::Result { 151 | self.write_string(s); 152 | Ok(()) 153 | } 154 | } 155 | 156 | /// Like the `print!` macro in the standard library, but prints to the VGA text buffer. 157 | #[macro_export] 158 | macro_rules! print { 159 | ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); 160 | } 161 | 162 | /// Like the `print!` macro in the standard library, but prints to the VGA text buffer. 163 | #[macro_export] 164 | macro_rules! print_red { 165 | ($($arg:tt)*) => ($crate::vga_buffer::_print_red(format_args!($($arg)*))); 166 | } 167 | 168 | /// Like the `print!` macro in the standard library, but prints to the VGA text buffer. 169 | #[macro_export] 170 | macro_rules! print_blue { 171 | ($($arg:tt)*) => ($crate::vga_buffer::_print_blue(format_args!($($arg)*))); 172 | } 173 | 174 | /// Like the `println!` macro in the standard library, but prints to the VGA text buffer. 175 | #[macro_export] 176 | macro_rules! println { 177 | () => ($crate::print!("\n")); 178 | ($($arg:tt)*) => ($crate::vga_print!("{}\n", format_args!($($arg)*))); 179 | } 180 | 181 | /// Like the `print!` macro in the standard library, but prints to the VGA text buffer. 182 | #[macro_export] 183 | macro_rules! vga_print { 184 | ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); 185 | } 186 | 187 | /// Like the `println!` macro in the standard library, but prints to the VGA text buffer. 188 | #[macro_export] 189 | macro_rules! vga_println { 190 | () => ($crate::print!("\n")); 191 | ($($arg:tt)*) => ($crate::vga_print!("{}\n", format_args!($($arg)*))); 192 | } 193 | 194 | /// Prints the given formatted string to the VGA text buffer 195 | /// through the global `WRITER` instance. 196 | #[doc(hidden)] 197 | pub fn _print(args: fmt::Arguments) { 198 | use core::fmt::Write; 199 | use x86_64::instructions::interrupts; 200 | 201 | interrupts::without_interrupts(|| { 202 | WRITER.lock().write_fmt(args).unwrap(); 203 | }); 204 | } 205 | 206 | /// Prints the given formatted string to the VGA text buffer 207 | /// through the global `WRITER` instance. 208 | #[doc(hidden)] 209 | pub fn _print_red(args: fmt::Arguments) { 210 | use core::fmt::Write; 211 | use x86_64::instructions::interrupts; 212 | 213 | interrupts::without_interrupts(|| { 214 | let mut writer = WRITER.lock(); 215 | writer.color_code = ColorCode::new(Color::Red, Color::Black); 216 | writer.write_fmt(args).unwrap(); 217 | writer.color_code = ColorCode::new(Color::Green, Color::Black); 218 | }); 219 | } 220 | 221 | /// Prints the given formatted string to the VGA text buffer 222 | /// through the global `WRITER` instance. 223 | #[doc(hidden)] 224 | pub fn _print_blue(args: fmt::Arguments) { 225 | use core::fmt::Write; 226 | use x86_64::instructions::interrupts; 227 | 228 | interrupts::without_interrupts(|| { 229 | let mut writer = WRITER.lock(); 230 | writer.color_code = ColorCode::new(Color::Blue, Color::Black); 231 | writer.write_fmt(args).unwrap(); 232 | writer.color_code = ColorCode::new(Color::Green, Color::Black); 233 | }); 234 | } 235 | 236 | 237 | #[test_case] 238 | fn test_println_simple() { 239 | serial_print!("test_println... "); 240 | println!("test_println_simple output"); 241 | serial_println!("[ok]"); 242 | } 243 | 244 | #[test_case] 245 | fn test_println_many() { 246 | serial_print!("test_println_many... "); 247 | for _ in 0..200 { 248 | println!("test_println_many output"); 249 | } 250 | serial_println!("[ok]"); 251 | } 252 | 253 | #[test_case] 254 | fn test_println_output() { 255 | use core::fmt::Write; 256 | use x86_64::instructions::interrupts; 257 | 258 | serial_print!("test_println_output... "); 259 | 260 | let s = "Some test string that fits on a single line"; 261 | interrupts::without_interrupts(|| { 262 | let mut writer = WRITER.lock(); 263 | writeln!(writer, "\n{}", s).expect("writeln failed"); 264 | for (i, c) in s.chars().enumerate() { 265 | let screen_char = writer.buffer.chars[BUFFER_HEIGHT - 2][i].read(); 266 | assert_eq!(char::from(screen_char.ascii_character), c); 267 | } 268 | }); 269 | 270 | serial_println!("[ok]"); 271 | } 272 | -------------------------------------------------------------------------------- /kernel/src/vmexit/descriptor_table.rs: -------------------------------------------------------------------------------- 1 | use crate::num::FromPrimitive; 2 | use crate::{Register, Segment}; 3 | 4 | #[derive(Debug)] 5 | pub struct DescriptorTableInfo { 6 | pub scaling: Scaling, 7 | pub address_size: AddressSize, 8 | pub operand_size: OperandSize, 9 | pub segment: Segment, 10 | pub index_register: Option, 11 | pub base_register: Option, 12 | pub identity: Identity, 13 | } 14 | 15 | #[derive(Debug, FromPrimitive)] 16 | pub enum Scaling { 17 | NoScale = 0, 18 | ScaleBy2 = 1, 19 | ScaleBy4 = 2, 20 | ScaleBy8 = 3, 21 | } 22 | 23 | #[derive(Debug, FromPrimitive, Eq, PartialEq)] 24 | pub enum AddressSize { 25 | Bit16 = 0, 26 | Bit32 = 1, 27 | Bit64 = 2, 28 | } 29 | 30 | #[derive(Debug, FromPrimitive)] 31 | pub enum OperandSize { 32 | Bit16 = 0, 33 | Bit32 = 1, 34 | } 35 | 36 | #[derive(Debug, FromPrimitive)] 37 | pub enum Identity { 38 | SGDT = 0, 39 | SIDT = 1, 40 | LGDT = 2, 41 | LIDT = 3, 42 | } 43 | 44 | impl DescriptorTableInfo { 45 | pub fn from_instruction_info(value: u64) -> Self { 46 | /* 47 | * See: Table 27-10. Format of the VM-Exit Instruction-Information Field as Used for LIDT, LGDT, SIDT, or SGDT 48 | * Scaling: 1:0 49 | * Address size: 9:7 50 | * Operand size: 11 51 | * Segment register: 17:15 52 | * Index register: 21:18 53 | * Index register invalid: 22 (0 = valid; 1 = invalid) 54 | * Base register: 26:23 55 | * Base register invalid: 27 (0 = valid; 1 = invalid) 56 | * Identity: 29:28 57 | */ 58 | 59 | let index_register = match value >> 22 & 1 { 60 | 0 => Some( 61 | Register::from_u64(value >> 18 & 0b1111) 62 | .expect("Register::from_u64 failed to parse"), 63 | ), 64 | 1 => None, 65 | _ => unreachable!(), 66 | }; 67 | let base_register = match value >> 27 & 1 { 68 | 0 => Some( 69 | Register::from_u64(value >> 23 & 0b1111) 70 | .expect("Register::from_u64 failed for >> 23"), 71 | ), 72 | 1 => None, 73 | _ => unreachable!(), 74 | }; 75 | DescriptorTableInfo { 76 | scaling: Scaling::from_u64(value & 0b11).expect("Scaling::from_u64 failed for 0b11"), 77 | address_size: AddressSize::from_u64(value >> 7 & 0b111) 78 | .expect("AddressSize from_u64 failed for >> 7"), 79 | operand_size: OperandSize::from_u64(value >> 11 & 0b1) 80 | .expect("OperandSize failed for >> 11"), 81 | segment: Segment::from_u64(value >> 15 & 0b111) 82 | .expect("Segment::from_u64 failed for >> 15"), 83 | index_register, 84 | base_register, 85 | identity: Identity::from_u64(value >> 28 & 0b11) 86 | .expect("Identity::from_u64 failed for >> 28"), 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /kernel/src/volatile.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "const_fn", feature(const_fn))] 2 | 3 | //! Provides wrapper types `Volatile`, `ReadOnly`, `WriteOnly`, `ReadWrite`, which wrap any copy-able type and allows for 4 | //! volatile memory access to wrapped value. Volatile memory accesses are never optimized away by 5 | //! the compiler, and are useful in many low-level systems programming and concurrent contexts. 6 | //! 7 | //! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider 8 | //! looking at the `Atomic` wrapper type found in `libcore` or `libstd`. 9 | //! 10 | //! These wrappers do not depend on the standard library and never panic. 11 | //! 12 | //! # Dealing with Volatile Pointers 13 | //! 14 | //! Frequently, one may have to deal with volatile pointers, eg, writes to specific memory 15 | //! locations. The canonical way to solve this is to cast the pointer to a volatile wrapper 16 | //! directly, eg: 17 | //! 18 | //! ```rust 19 | //! use volatile::Volatile; 20 | //! 21 | //! let mut_ptr = 0xFEE00000 as *mut u32; 22 | //! 23 | //! let volatile_ptr = mut_ptr as *mut Volatile; 24 | //! ``` 25 | //! 26 | //! and then perform operations on the pointer as usual in a volatile way. This method works as all 27 | //! of the volatile wrapper types are the same size as their contained values. 28 | 29 | use core::ptr; 30 | 31 | /// A wrapper type around a volatile variable, which allows for volatile reads and writes 32 | /// to the contained value. The stored type needs to be `Copy`, as volatile reads and writes 33 | /// take and return copies of the value. 34 | /// 35 | /// The size of this struct is the same as the size of the contained type. 36 | #[derive(Debug)] 37 | #[repr(transparent)] 38 | pub struct Volatile(T); 39 | 40 | impl Volatile { 41 | /// Construct a new volatile instance wrapping the given value. 42 | /// 43 | /// ```rust 44 | /// use volatile::Volatile; 45 | /// 46 | /// let value = Volatile::new(0u32); 47 | /// ``` 48 | /// 49 | /// # Panics 50 | /// 51 | /// This method never panics. 52 | #[cfg(feature = "const_fn")] 53 | pub const fn new(value: T) -> Volatile { 54 | Volatile(value) 55 | } 56 | 57 | /// Construct a new volatile instance wrapping the given value. 58 | /// 59 | /// ```rust 60 | /// use volatile::Volatile; 61 | /// 62 | /// let value = Volatile::new(0u32); 63 | /// ``` 64 | /// 65 | /// # Panics 66 | /// 67 | /// This method never panics. 68 | #[cfg(not(feature = "const_fn"))] 69 | pub fn new(value: T) -> Volatile { 70 | Volatile(value) 71 | } 72 | 73 | /// Performs a volatile read of the contained value, returning a copy 74 | /// of the read value. Volatile reads are guaranteed not to be optimized 75 | /// away by the compiler, but by themselves do not have atomic ordering 76 | /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper type. 77 | /// 78 | /// ```rust 79 | /// use volatile::Volatile; 80 | /// 81 | /// let value = Volatile::new(42u32); 82 | /// 83 | /// assert_eq!(value.read(), 42u32); 84 | /// ``` 85 | /// 86 | /// # Panics 87 | /// 88 | /// This method never panics. 89 | pub fn read(&self) -> T { 90 | // UNSAFE: Safe, as we know that our internal value exists. 91 | unsafe { ptr::read_volatile(&self.0) } 92 | } 93 | 94 | /// Performs a volatile write, setting the contained value to the given value `value`. Volatile 95 | /// writes are guaranteed to not be optimized away by the compiler, but by themselves do not 96 | /// have atomic ordering guarantees. To also get atomicity, consider looking at the `Atomic` 97 | /// wrapper type. 98 | /// 99 | /// ```rust 100 | /// use volatile::Volatile; 101 | /// 102 | /// let mut value = Volatile::new(0u32); 103 | /// 104 | /// value.write(42u32); 105 | /// 106 | /// assert_eq!(value.read(), 42u32); 107 | /// ``` 108 | /// 109 | /// # Panics 110 | /// 111 | /// This method never panics. 112 | pub fn write(&mut self, value: T) { 113 | // UNSAFE: Safe, as we know that our internal value exists. 114 | unsafe { ptr::write_volatile(&mut self.0, value) }; 115 | } 116 | 117 | /// Performs a volatile read of the contained value, passes a mutable reference to it to the 118 | /// function `f`, and then performs a volatile write of the (potentially updated) value back to 119 | /// the contained value. 120 | /// 121 | /// ```rust 122 | /// use volatile::Volatile; 123 | /// 124 | /// let mut value = Volatile::new(21u32); 125 | /// 126 | /// value.update(|val_ref| *val_ref *= 2); 127 | /// 128 | /// assert_eq!(value.read(), 42u32); 129 | /// ``` 130 | /// 131 | /// # Panics 132 | /// 133 | /// Ths method never panics. 134 | pub fn update(&mut self, f: F) 135 | where 136 | F: FnOnce(&mut T), 137 | { 138 | let mut value = self.read(); 139 | f(&mut value); 140 | self.write(value); 141 | } 142 | } 143 | 144 | impl Clone for Volatile { 145 | fn clone(&self) -> Self { 146 | Volatile(self.read()) 147 | } 148 | } 149 | 150 | /// A volatile wrapper which only allows read operations. 151 | /// 152 | /// The size of this struct is the same as the contained type. 153 | #[derive(Debug, Clone)] 154 | pub struct ReadOnly(Volatile); 155 | 156 | impl ReadOnly { 157 | /// Construct a new read-only volatile wrapper wrapping the given value. 158 | /// 159 | /// ```rust 160 | /// use volatile::ReadOnly; 161 | /// 162 | /// let value = ReadOnly::new(42u32); 163 | /// ``` 164 | /// 165 | /// # Panics 166 | /// 167 | /// This function never panics. 168 | #[cfg(feature = "const_fn")] 169 | pub const fn new(value: T) -> ReadOnly { 170 | ReadOnly(Volatile::new(value)) 171 | } 172 | 173 | /// Construct a new read-only volatile wrapper wrapping the given value. 174 | /// 175 | /// ```rust 176 | /// use volatile::ReadOnly; 177 | /// 178 | /// let value = ReadOnly::new(42u32); 179 | /// ``` 180 | /// 181 | /// # Panics 182 | /// 183 | /// This function never panics. 184 | #[cfg(not(feature = "const_fn"))] 185 | pub fn new(value: T) -> ReadOnly { 186 | ReadOnly(Volatile::new(value)) 187 | } 188 | 189 | /// Perform a volatile read of the contained value, returning a copy of the read value. 190 | /// Functionally equivalent to `Volatile::read`. 191 | /// 192 | /// ```rust 193 | /// use volatile::ReadOnly; 194 | /// 195 | /// let value = ReadOnly::new(42u32); 196 | /// assert_eq!(value.read(), 42u32); 197 | /// ``` 198 | /// 199 | /// # Panics 200 | /// 201 | /// This function never panics. 202 | pub fn read(&self) -> T { 203 | self.0.read() 204 | } 205 | } 206 | 207 | /// A volatile wrapper which only allows write operations. 208 | /// 209 | /// The size of this struct is the same as the contained type. 210 | #[derive(Debug, Clone)] 211 | pub struct WriteOnly(Volatile); 212 | 213 | impl WriteOnly { 214 | /// Constructs a new write only volatile wrapper around the given value. 215 | /// 216 | /// ```rust 217 | /// use volatile::WriteOnly; 218 | /// 219 | /// let value = WriteOnly::new(0u32); 220 | /// ``` 221 | /// 222 | /// # Panics 223 | /// 224 | /// This function never panics. 225 | #[cfg(feature = "const_fn")] 226 | pub const fn new(value: T) -> WriteOnly { 227 | WriteOnly(Volatile::new(value)) 228 | } 229 | 230 | /// Constructs a new write only volatile wrapper around the given value. 231 | /// 232 | /// ```rust 233 | /// use volatile::WriteOnly; 234 | /// 235 | /// let value = WriteOnly::new(0u32); 236 | /// ``` 237 | /// 238 | /// # Panics 239 | /// 240 | /// This function never panics. 241 | #[cfg(not(feature = "const_fn"))] 242 | pub fn new(value: T) -> WriteOnly { 243 | WriteOnly(Volatile::new(value)) 244 | } 245 | 246 | /// Performs a volatile write of value `value` into the contained value. Functionally identical 247 | /// to `Volatile::write`. 248 | /// 249 | /// ```rust 250 | /// use volatile::WriteOnly; 251 | /// 252 | /// let mut value = WriteOnly::new(0u32); 253 | /// 254 | /// value.write(42u32); 255 | /// ``` 256 | /// 257 | /// # Panics 258 | /// 259 | /// This method never panics. 260 | pub fn write(&mut self, value: T) { 261 | self.0.write(value) 262 | } 263 | } 264 | 265 | /// A volatile wrapper which allows both read and write operations; 266 | /// functionally equivalent to the `Volatile` type, as it is a type 267 | /// alias for it. 268 | /// 269 | /// The size of this struct is the same as the contained type. 270 | pub type ReadWrite = Volatile; 271 | 272 | #[cfg(test)] 273 | mod tests { 274 | use super::Volatile; 275 | 276 | #[test] 277 | fn test_read() { 278 | assert_eq!(Volatile(42).read(), 42); 279 | } 280 | 281 | #[test] 282 | fn test_write() { 283 | let mut volatile = Volatile(42); 284 | volatile.write(50); 285 | assert_eq!(volatile.0, 50); 286 | } 287 | 288 | #[test] 289 | fn test_update() { 290 | let mut volatile = Volatile(42); 291 | volatile.update(|v| *v += 1); 292 | assert_eq!(volatile.0, 43); 293 | } 294 | 295 | #[test] 296 | fn test_pointer_recast() { 297 | let mut target_value = 0u32; 298 | 299 | let target_ptr: *mut u32 = &mut target_value; 300 | let volatile_ptr = target_ptr as *mut Volatile; 301 | 302 | // UNSAFE: Safe, as we know the value exists on the stack. 303 | unsafe { 304 | (*volatile_ptr).write(42u32); 305 | } 306 | 307 | assert_eq!(target_value, 42u32); 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /parse_trace/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parse_trace" 3 | version = "0.1.0" 4 | authors = ["randomlshb@gmail.com "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | capstone = "0.6.0" 11 | bitflags = "1.2.1" 12 | pdb = "0.5.0" 13 | noodle = { path = "../shared/noodle" } 14 | -------------------------------------------------------------------------------- /parse_trace/README.md: -------------------------------------------------------------------------------- 1 | # Parse trace 2 | 3 | Parses the returned trace from the hypervisor into a readable format. Relies on `../snapshot/snapshot.[dmp|phys]` for discovering modules and the instruction. 4 | 5 | ``` 6 | cargo run --release # Parses the ../tftp-server/test.trace 7 | cargo run --release -- yourtrace.txt # Parses the given trace 8 | ``` 9 | 10 | ## Output 11 | 12 | ``` 13 | cr3: 0x498e5000 cs: 0x33 14 | TEB PEB: 0x496b712000 15 | { 16 | 0x7ffda8840000..0x7ffda886a000: "GDI32.dll", 17 | 0x7ffda7a50000..0x7ffda7b25000: "OLEAUT32.dll", 18 | ... 19 | } 20 | 21 | User modules hash: 0x25ff119b564000 22 | DTB (kernel_cr3) [0x1aaaa0]: 0x1aa000 23 | Kernel cr3: 0x1aa000 24 | DTB (kernel_cr3) [0x1aaaa0]: 0x1aa000 25 | KCR3: 1aa000 26 | KDBG [0x2cd19444]: 0xf8027b000000 27 | KDBG: f8027b000000 28 | Found PsLoadedModuleList: 0xc2a4f0 29 | PLML offset: c2a4f0 30 | { 31 | 0xfffff8027f520000..0xfffff8027f560000: "Wof.sys", 32 | 0xfffff8027ebf0000..0xfffff8027ecd1000: "CI.dll", 33 | 0xfffff8027ee80000..0xfffff8027ee93000: "WDFLDR.SYS", 34 | 0xfffff80281570000..0xfffff80281595000: "HDAudBus.sys", 35 | 0xfffff8027ef70000..0xfffff8027f03c000: "ACPI.sys", 36 | 0xfffff802798a0000..0xfffff802798c5000: "bowser.sys", 37 | ... 38 | } 39 | 40 | ... 41 | [2433] verifier.dll+0x000021bd, (0x7ffcea4e21bd) 0x7ffcea4e21bd: mov rdx, rbx 42 | [2434] verifier.dll+0x000021c0, (0x7ffcea4e21c0) 0x7ffcea4e21c0: or rcx, 0xffffffffffffffff 43 | [2435] verifier.dll+0x000021c4, (0x7ffcea4e21c4) 0x7ffcea4e21c4: call qword ptr [rip + 0x28255] 44 | [2436] ntdll.dll!ZwAllocateVirtualMemory+0x0, (ntdll.dll+0x9b980) 0x7ffd0ffcb980: mov r10, rcx 45 | [2437] ntdll.dll!ZwAllocateVirtualMemory+0x3, (ntdll.dll+0x9b983) 0x7ffd0ffcb983: mov eax, 0x18 46 | [2438] ntdll.dll!ZwAllocateVirtualMemory+0x8, (ntdll.dll+0x9b988) 0x7ffd0ffcb988: test byte ptr [0x7ffe0308], 1 47 | [2439] ntdll.dll!ZwAllocateVirtualMemory+0x10, (ntdll.dll+0x9b990) 0x7ffd0ffcb990: jne 0x7ffd0ffcb995 48 | [2440] ntdll.dll!ZwAllocateVirtualMemory+0x12, (ntdll.dll+0x9b992) 0x7ffd0ffcb992: syscall 49 | [2441] ntoskrnl.exe!KiSystemCall64Shadow+0x0, (ntoskrnl.exe+0xa0d1c0) 0xfffff8027ba0d1c0: swapgs 50 | [2442] ntoskrnl.exe!KiSystemCall64Shadow+0x3, (ntoskrnl.exe+0xa0d1c3) 0xfffff8027ba0d1c3: mov qword ptr gs:[0x9010], rsp 51 | [2443] ntoskrnl.exe!KiSystemCall64Shadow+0xc, (ntoskrnl.exe+0xa0d1cc) 0xfffff8027ba0d1cc: mov rsp, qword ptr gs:[0x9000] 52 | [2444] ntoskrnl.exe!KiSystemCall64Shadow+0x15, (ntoskrnl.exe+0xa0d1d5) 0xfffff8027ba0d1d5: bt dword ptr gs:[0x9018], 1 53 | [2445] ntoskrnl.exe!KiSystemCall64Shadow+0x1f, (ntoskrnl.exe+0xa0d1df) 0xfffff8027ba0d1df: jb 0xfffff8027ba0d1e4 54 | [2446] ntoskrnl.exe!KiSystemCall64Shadow+0x24, (ntoskrnl.exe+0xa0d1e4) 0xfffff8027ba0d1e4: mov rsp, qword ptr gs:[0x9008] 55 | [2447] ntoskrnl.exe!KiSystemCall64Shadow+0x2d, (ntoskrnl.exe+0xa0d1ed) 0xfffff8027ba0d1ed: push 0x2b 56 | [2448] ntoskrnl.exe!KiSystemCall64Shadow+0x2f, (ntoskrnl.exe+0xa0d1ef) 0xfffff8027ba0d1ef: push qword ptr gs:[0x9010] 57 | [2449] ntoskrnl.exe!KiSystemCall64Shadow+0x37, (ntoskrnl.exe+0xa0d1f7) 0xfffff8027ba0d1f7: push r11 58 | [2450] ntoskrnl.exe!KiSystemCall64Shadow+0x39, (ntoskrnl.exe+0xa0d1f9) 0xfffff8027ba0d1f9: push 0x33 59 | [2451] ntoskrnl.exe!KiSystemCall64Shadow+0x3b, (ntoskrnl.exe+0xa0d1fb) 0xfffff8027ba0d1fb: push rcx 60 | [2452] ntoskrnl.exe!KiSystemCall64Shadow+0x3c, (ntoskrnl.exe+0xa0d1fc) 0xfffff8027ba0d1fc: mov rcx, r10 61 | [2453] ntoskrnl.exe!KiSystemCall64Shadow+0x3f, (ntoskrnl.exe+0xa0d1ff) 0xfffff8027ba0d1ff: sub rsp, 8 62 | [2454] ntoskrnl.exe!KiSystemCall64Shadow+0x43, (ntoskrnl.exe+0xa0d203) 0xfffff8027ba0d203: push rbp 63 | [2455] ntoskrnl.exe!KiSystemCall64Shadow+0x44, (ntoskrnl.exe+0xa0d204) 0xfffff8027ba0d204: sub rsp, 0x158 64 | [2456] ntoskrnl.exe!KiSystemCall64Shadow+0x4b, (ntoskrnl.exe+0xa0d20b) 0xfffff8027ba0d20b: lea rbp, [rsp + 0x80] 65 | [2457] ntoskrnl.exe!KiSystemCall64Shadow+0x53, (ntoskrnl.exe+0xa0d213) 0xfffff8027ba0d213: mov qword ptr [rbp + 0xc0], rbx 66 | [2458] ntoskrnl.exe!KiSystemCall64Shadow+0x5a, (ntoskrnl.exe+0xa0d21a) 0xfffff8027ba0d21a: mov qword ptr [rbp + 0xc8], rdi 67 | ``` 68 | -------------------------------------------------------------------------------- /parse_trace/src/memreader.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Seek, SeekFrom, Read}; 2 | use std::string::String; 3 | use std::fs::File; 4 | 5 | #[derive(Debug)] 6 | pub struct Module { 7 | name: String, 8 | fullname: String, 9 | address: u64, 10 | length: u64 11 | } 12 | 13 | #[derive(Debug)] 14 | pub enum Address { 15 | /// Physical address 16 | Physical(u64), 17 | 18 | /// Virtual address and CR3 19 | Virtual(u64, u64) 20 | } 21 | 22 | pub trait MemReader: Read + Seek { 23 | /// Read a u64 at the given Address 24 | fn read_u64(&mut self, addr: Address) -> u64 { 25 | let mut buffer = [0u8; 8]; 26 | self.read_mem(addr, &mut buffer).expect("Error on read_u64"); 27 | u64::from_le_bytes(buffer) 28 | } 29 | 30 | /// Read a u32 at the given Address 31 | fn read_u32(&mut self, addr: Address) -> u32 { 32 | let mut buffer = [0u8; 4]; 33 | self.read_mem(addr, &mut buffer).expect("Error on read_u64"); 34 | u32::from_le_bytes(buffer) 35 | } 36 | 37 | /// Read into a buffer at the given address. Assumes wanting to read into the 38 | /// entire buffer. 39 | fn read_mem(&mut self, addr: Address, buffer: &mut [u8]) -> Result<(), ()> { 40 | let paddr = match addr { 41 | Address::Physical(paddr) => paddr, 42 | Address::Virtual(_vaddr, _cr3) => { 43 | let res = self.translate(addr); 44 | if res.is_none() { return Err(()); } 45 | res.unwrap() as u64 46 | } 47 | }; 48 | 49 | self.seek(SeekFrom::Start(paddr)).expect("Unable to seek in read"); 50 | self.read_exact(buffer).expect("Unable to read_exact for read"); 51 | Ok(()) 52 | } 53 | 54 | /// Returns the virtual addresses and the virt to phys tranlations mapping from 55 | /// the data of a vbox core file 56 | fn translate(&mut self, addr: Address) -> Option { 57 | let (vaddr, mut curr_page) = match addr { 58 | Address::Virtual(vaddr, curr_page) => (vaddr, curr_page), 59 | Address::Physical(_x) => panic!("Cannot translate from Physical address") 60 | }; 61 | 62 | // print!("[translate] addr: {:x?}\n", addr); 63 | 64 | /* Calculate the components for each level of the page table from the vaddr. */ 65 | let cr_offsets: [u64; 4] = [ 66 | ((vaddr >> 39) & 0x1ff), /* 512 GiB */ 67 | ((vaddr >> 30) & 0x1ff), /* 1 GiB */ 68 | ((vaddr >> 21) & 0x1ff), /* 2 MiB */ 69 | ((vaddr >> 12) & 0x1ff), /* 4 KiB */ 70 | ]; 71 | 72 | /* For each level in the page table */ 73 | for (_depth, curr_offset) in cr_offsets.iter().enumerate() { 74 | /* Get the page table entry */ 75 | let start_offset = (curr_page + (curr_offset * 8)) as usize; 76 | 77 | // print!("start_offset: {:#x} -- ", start_offset); 78 | let entry = self.read_u64(Address::Physical(start_offset as u64)); 79 | // print!("entry: {:#x}\n", entry); 80 | if entry & 1 == 0 { 81 | // Entry not present 82 | return None; 83 | } 84 | 85 | /* Get the physical address of the next level */ 86 | if entry >> 7 & 1 == 1 { 87 | // if entry & 1 << 7 > 0 { 88 | curr_page = (entry & 0xffff_ffe0_0000) | (vaddr & 0x1f_ffff); 89 | // print!("[LargePage] "); 90 | return Some(curr_page as usize); 91 | } 92 | 93 | curr_page = entry & 0xffff_ffff_f000; 94 | // println!("entry: {:#x}", curr_page); 95 | if curr_page == 0 { 96 | return None 97 | } 98 | } 99 | 100 | // println!(" -> {:#x}", curr_page); 101 | Some(curr_page as usize + (vaddr as usize & 0xfff)) 102 | } 103 | } 104 | 105 | impl MemReader for File {} 106 | 107 | /* 108 | fn main() -> std::io::Result<()> { 109 | let mut file = std::fs::File::open("/Users/user/workspace/barberslice/snapshot/snapshot.phys")?; 110 | 111 | let mut buffer = [0u8; 0x1000]; 112 | let needle = "\\SystemRoot\\system32\\nt"; 113 | let needle: Vec = needle.bytes().collect(); 114 | let needle_len = needle.len(); 115 | let kdbg; 116 | let mut kernel_cr3 = None; 117 | 118 | // From Volatility3's documentation (DTB - Directory Table Base aka Kernel cr3) 119 | // 120 | // New versions of windows, with randomized self-referential pointers, appear to 121 | // always load their dtb within a small specific range (`0x1a0000` and `0x1b0000`), 122 | // so instead we scan for all self-referential pointers in that range, and ignore any 123 | // that contain multiple self-references (since the DTB is very unlikely to point to 124 | // itself more than once). 125 | for counter in (0x1a0000..0x1b0000).step_by(0x1000) { 126 | for i in (0..0x1000).step_by(8) { 127 | let curr_ptr = file.read_u64( 128 | Address::Physical(counter + i)) & 0xffff_ffff_f000; 129 | if curr_ptr == counter { 130 | print!("DTB (kernel_cr3) [{:#x}]: {:#x}\n", counter + i as u64, counter); 131 | kernel_cr3 = Some(counter); 132 | break; 133 | } 134 | } 135 | } 136 | 137 | print!("KCR3: {:x?}\n", kernel_cr3); 138 | let kernel_cr3 = kernel_cr3.unwrap(); 139 | let mut counter = 0; 140 | 141 | 'leave: loop { 142 | file.read_mem(Address::Physical(counter), &mut buffer) 143 | .expect(&format!("Unable to read addr looking for KDBG: {:#x}", counter)); 144 | for i in 0..0x1000-needle_len { 145 | if &buffer[i..i+needle_len] == needle.as_slice() { 146 | let check_addr = (counter + i as u64).saturating_sub(0x18); 147 | let possible_kdbg = file.read_u64(Address::Physical(check_addr)) 148 | & 0xffff_ffff_ffff; 149 | let mut fileheader = [0; 2]; 150 | let res = file.read_mem(Address::Virtual(possible_kdbg, kernel_cr3), 151 | &mut fileheader); 152 | if res.is_err() { continue; } 153 | if fileheader == ['M' as u8, 'Z' as u8] { 154 | print!("KDBG [{:#x}]: {:#x}\n", counter + i as u64, possible_kdbg); 155 | kdbg = Some(possible_kdbg); 156 | break 'leave; 157 | } 158 | } 159 | } 160 | counter += 0x1000; 161 | } 162 | 163 | print!("KDBG: {:x?}\n", kdbg); 164 | 165 | // Attempt to brute force the offset of PsLoadedModuleList rather than 166 | // downloading the PDB for this ntoskrnl.exe and using that 167 | let mut psloadedmodulelist_offset = 0; 168 | for offset in (0..0x1000_0000).step_by(2) { 169 | let psloadedmodulelist = kdbg.unwrap() + offset; 170 | let psloadedmodulelist_phys = file.translate( 171 | Address::Virtual(psloadedmodulelist, kernel_cr3)); 172 | 173 | // If translate_phys was null, continue to look 174 | if psloadedmodulelist_phys.is_none() { continue; } 175 | 176 | // Guarenteed to be some now 177 | let psloadedmodulelist_phys = psloadedmodulelist_phys.unwrap() as u64; 178 | 179 | // print!("_phys {:#x}\n", psloadedmodulelist_phys); 180 | let ldr_data_table_entry = file.read_u64( 181 | Address::Physical(psloadedmodulelist_phys)) & 0xffff_ffff_ffff; 182 | let ldr_data_table_entry_phys = file.translate( 183 | Address::Virtual(ldr_data_table_entry, kernel_cr3)); 184 | 185 | // If translate_phys was null, continue to look 186 | if ldr_data_table_entry_phys.is_none() { continue; } 187 | 188 | // Guarenteed to be some now 189 | let ldr_data_table_entry_phys = ldr_data_table_entry_phys.unwrap() as u64; 190 | 191 | let data_entry = KldrDataTableEntry::from_reader(ldr_data_table_entry_phys, 192 | &mut file); 193 | 194 | if data_entry.flink >> 63 == 0 { continue; } 195 | if data_entry.blink >> 63 == 0 { continue; } 196 | 197 | let curr_base = data_entry.base_dll(kernel_cr3, &mut file); 198 | if curr_base.is_none() { continue; } 199 | 200 | // If the first dll is ntoskrnl.exe, we assume we have found the right offset 201 | if "ntoskrnl.exe" == curr_base.unwrap() { 202 | print!("Found PsLoadedModuleList: {:#x}\n", offset); 203 | psloadedmodulelist_offset = offset; 204 | break; 205 | } 206 | } 207 | 208 | assert!(psloadedmodulelist_offset > 0, "Failed to find PsLoadedModuleList offset"); 209 | print!("PSML offset: {:x?}\n", psloadedmodulelist_offset); 210 | 211 | let mut psloadedmodulelist = kdbg.unwrap() + psloadedmodulelist_offset; 212 | let mut found = HashSet::new(); 213 | let mut modlist = HashMap::new(); 214 | 215 | loop { 216 | let psloadedmodulelist_phys = file.translate( 217 | Address::Virtual(psloadedmodulelist, kernel_cr3)).unwrap() as u64; 218 | let data_entry = KldrDataTableEntry::from_reader(psloadedmodulelist_phys, 219 | &mut file); 220 | 221 | // Set the next loop address in case we can't resolve the base dll or full dll 222 | psloadedmodulelist = data_entry.flink; 223 | let base_dll = match data_entry.base_dll(kernel_cr3, &mut file) { 224 | None => continue, 225 | Some(dll) => dll 226 | }; 227 | 228 | let _full_dll = match data_entry.full_dll(kernel_cr3, &mut file) { 229 | None => continue, 230 | Some(dll) => dll 231 | }; 232 | 233 | let dll_base = data_entry.dll_base; 234 | let size = data_entry.size_of_image; 235 | modlist.insert(dll_base..dll_base+size, base_dll.to_string()); 236 | 237 | if !found.insert(base_dll) { 238 | break; 239 | } 240 | } 241 | 242 | print!("{:x?}\n", modlist); 243 | print!("{}\n", modlist.len()); 244 | 245 | Ok(()) 246 | } 247 | */ 248 | -------------------------------------------------------------------------------- /pci-ids-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pci-ids-parser" 3 | version = "0.1.0" 4 | authors = ["thebarbershopper "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | regex = "1.1.6" 9 | lazy_static = "1.3.0" 10 | -------------------------------------------------------------------------------- /pci-ids-parser/README.md: -------------------------------------------------------------------------------- 1 | ### PCI ID Parser 2 | 3 | Parses the PCI IDs from `pciutils` and dumps them in a linear format of `(VendorID, DeviceID) => Name`: 4 | 5 | ``` 6 | (0x10, 0x8139) => "Allied Telesis, Inc (Wrong ID):AT-2500TX V3 Ethernet", 7 | (0x14, 0x7a00) => "Loongson Technology LLC:Hyper Transport Bridge Controller", 8 | (0x14, 0x7a02) => "Loongson Technology LLC:APB (Advanced Peripheral Bus) Controller", 9 | ``` 10 | 11 | Results are in `pciids`. But if you want to rebuild: 12 | 13 | ```rust 14 | cargo run --release 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /pci-ids-parser/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate lazy_static; 3 | extern crate regex; 4 | 5 | use regex::Regex; 6 | use std::fs::File; 7 | use std::io::prelude::*; 8 | use std::u16; 9 | 10 | fn main() -> std::io::Result<()> { 11 | let mut results = File::create("pciids")?; 12 | 13 | match File::open("pci.ids") { 14 | Err(_) => { 15 | println!("pci.ids file not found"); 16 | println!("wget https://raw.githubusercontent.com/pciutils/pciids/master/pci.ids"); 17 | } 18 | 19 | Ok(mut file) => { 20 | let mut data = String::new(); 21 | file.read_to_string(&mut data)?; 22 | 23 | lazy_static! { 24 | static ref GET_VENDOR: Regex = Regex::new(r"^([0-9a-f]{4}) (.*)$").unwrap(); 25 | static ref GET_DEVICE: Regex = Regex::new(r"^\t([0-9a-f]{4}) (.*)$").unwrap(); 26 | static ref GET_SUBDEVICE: Regex = 27 | Regex::new(r"^\t\t([0-9a-f]{4}) ([0-9a-f]{4}) (.*)$").unwrap(); 28 | } 29 | 30 | let mut curr_vendor = "FFFFFFFF".to_string(); 31 | let mut curr_vendor_desc = "Not found".to_string(); 32 | 33 | let mut curr_device; 34 | let mut curr_device_desc; 35 | 36 | for line in data.lines() { 37 | if line.starts_with("#") { 38 | // Ignoring comments 39 | continue; 40 | } 41 | 42 | for cap in GET_VENDOR.captures_iter(line) { 43 | curr_vendor = cap[1].to_string().replace("\"", "'").replace("\\", "\\\\"); 44 | curr_vendor_desc = cap[2].to_string().replace("\"", "'").replace("\\", "\\\\"); 45 | } 46 | 47 | for cap in GET_DEVICE.captures_iter(line) { 48 | curr_device = cap[1].to_string().replace("\"", "'").replace("\\", "\\\\"); 49 | curr_device_desc = cap[2].to_string().replace("\"", "'").replace("\\", "\\\\"); 50 | results.write_all( 51 | format!( 52 | "({:#x}, {:#x}) => \"{}:{}\",\n", 53 | u16::from_str_radix(&curr_vendor, 16).unwrap(), 54 | u16::from_str_radix(&curr_device, 16).unwrap(), 55 | curr_vendor_desc, 56 | curr_device_desc 57 | ) 58 | .as_bytes(), 59 | )?; 60 | } 61 | 62 | for cap in GET_SUBDEVICE.captures_iter(line) { 63 | let _subdevice_id = &cap[2]; 64 | let _subdevice_descr = &cap[3]; 65 | } 66 | } 67 | } 68 | } 69 | 70 | println!("Results written to pciids"); 71 | Ok(()) 72 | } 73 | -------------------------------------------------------------------------------- /shared/cpu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cpu" 3 | version = "0.1.0" 4 | authors = ["gamozo "] 5 | 6 | [dependencies] 7 | rangeset = { path = "../rangeset" } 8 | 9 | -------------------------------------------------------------------------------- /shared/mmu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mmu" 3 | version = "0.1.0" 4 | authors = ["gamozo "] 5 | 6 | [dependencies] 7 | cpu = { path = "../cpu" } 8 | 9 | -------------------------------------------------------------------------------- /shared/noodle/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /shared/noodle/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "noodle" 3 | version = "0.1.0" 4 | authors = ["Brandon Falk "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /shared/noodle/serialize_tuple_gen.py: -------------------------------------------------------------------------------- 1 | # Handles generation of the `handle_serialize_named_tuple` macro in 2 | # `tuple_gen.rs` 3 | 4 | print("""/// AUTOGENERATED BY serialize_tuple_gen.py DO NOT MODIFY! 5 | /// Handles serialization of tuples by accessing each field as part of a 6 | /// different matching rule indicating the depth of the macro""") 7 | 8 | # Maximum number of tuple fields 9 | NUM_TUPLES_ALLOWED = 64 10 | 11 | # Generate matches for different depths of enums 12 | print("#[macro_export]") 13 | print("macro_rules! handle_serialize_named_tuple {") 14 | for num_fields in range(NUM_TUPLES_ALLOWED + 1): 15 | impl = " ($self:ident, $buf:expr" 16 | for field_id in range(num_fields): 17 | impl += ", $ty%d:ty" % field_id 18 | impl += ") => {\n" 19 | 20 | for field_id in range(num_fields): 21 | impl += " Serialize::serialize(&$self.%d, $buf);\n" % field_id 22 | impl += " };" 23 | print(impl) 24 | print("}") 25 | 26 | -------------------------------------------------------------------------------- /shared/noodle/tuple_match_gen.py: -------------------------------------------------------------------------------- 1 | # Handles generation of the `handle_serialize_tuple_match` macro in 2 | # `tuple_match.rs` 3 | 4 | print("""/// AUTOGENERATED BY tuple_match_gen.py DO NOT MODIFY! 5 | /// Handles matching of tuples based on the number of types they have. This 6 | /// is kinda ugly but it seems to be required as there's no way to dynamically 7 | /// construct an indentifer in macros. Since we can't make identifiers, we 8 | /// cannot construct names for tuple variants which can be used during binding.""") 9 | 10 | # Maximum number of tuple fields 11 | NUM_TUPLES_ALLOWED = 64 12 | 13 | NAMES = [] 14 | 15 | # Generate unique identifiers 16 | for ii in range(NUM_TUPLES_ALLOWED+1): 17 | name = "" 18 | name += "abcdefghijklmnopqrstuvwxyz"[ii % 26] 19 | ii = int(ii / 26) 20 | name += "abcdefghijklmnopqrstuvwxyz"[ii % 26] 21 | NAMES.append(name) 22 | 23 | # Generate matches for different depths of enums 24 | print("macro_rules! handle_serialize_tuple_match {") 25 | for name_count in range(len(NAMES)): 26 | names = NAMES[:name_count] 27 | 28 | impl = " ($self:ident, $count:expr, $buf:expr, $enumname:ident, $enumident:ident" 29 | for name in names: 30 | impl += ", $ty%s:ty" % name 31 | impl += ") => {\n" 32 | 33 | impl += " if let $enumname::$enumident( " 34 | for name in names: 35 | impl += "%s," % name 36 | impl += ") = $self {\n" 37 | impl += " Serialize::serialize($count, $buf);\n" 38 | 39 | for name in names: 40 | impl += " Serialize::serialize(%s, $buf);\n" % name 41 | impl += " }\n" 42 | impl += " };" 43 | print(impl) 44 | print("}") 45 | 46 | -------------------------------------------------------------------------------- /shared/packets/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /shared/packets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "packets" 3 | version = "0.1.0" 4 | authors = ["randomlshb@gmail.com "] 5 | edition = "2018" 6 | 7 | [features] 8 | "proto-ipv4" = [] 9 | 10 | default = [ 11 | "proto-ipv4" 12 | ] 13 | 14 | [dependencies] 15 | byteorder = { version = "1.0", default-features = false } 16 | 17 | -------------------------------------------------------------------------------- /shared/packets/LICENSE-0BSD.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016 whitequark@whitequark.org 2 | 3 | Permission to use, copy, modify, and/or distribute this software for 4 | any purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 7 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 8 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 9 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 10 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 11 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 12 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 13 | 14 | -------------------------------------------------------------------------------- /shared/packets/README.md: -------------------------------------------------------------------------------- 1 | Forked copy of [smoltcp](https://github.com/smoltcp-rs/smoltcp) 2 | -------------------------------------------------------------------------------- /shared/packets/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | #[macro_use] 4 | mod macros; 5 | 6 | extern crate alloc; 7 | 8 | pub mod ethernet; 9 | pub mod ip; 10 | pub mod ipv4; 11 | pub mod phy; 12 | pub mod tftp; 13 | pub mod udp; 14 | 15 | mod field { 16 | pub type Field = core::ops::Range; 17 | pub type Rest = core::ops::RangeFrom; 18 | } 19 | 20 | /// The error type for the networking stack. 21 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 22 | pub enum Error { 23 | /// An operation cannot proceed because a buffer is empty or full. 24 | Exhausted, 25 | /// An operation is not permitted in the current state. 26 | Illegal, 27 | /// An endpoint or address of a remote host could not be translated to a lower level address. 28 | /// E.g. there was no an Ethernet address corresponding to an IPv4 address in the ARP cache, 29 | /// or a TCP connection attempt was made to an unspecified endpoint. 30 | Unaddressable, 31 | 32 | /// An incoming packet could not be parsed because some of its fields were out of bounds 33 | /// of the received data. 34 | Truncated, 35 | /// An incoming packet had an incorrect checksum and was dropped. 36 | Checksum, 37 | /// An incoming packet could not be recognized and was dropped. 38 | /// E.g. an Ethernet packet with an unknown EtherType. 39 | Unrecognized, 40 | /// An incoming IP packet has been split into several IP fragments and was dropped, 41 | /// since IP reassembly is not supported. 42 | Fragmented, 43 | /// An incoming packet was recognized but was self-contradictory. 44 | /// E.g. a TCP packet with both SYN and FIN flags set. 45 | Malformed, 46 | /// An incoming packet was recognized but contradicted internal state. 47 | /// E.g. a TCP packet addressed to a socket that doesn't exist. 48 | Dropped, 49 | 50 | #[doc(hidden)] 51 | __Nonexhaustive, 52 | } 53 | 54 | /// The result type for the networking stack. 55 | pub type Result = core::result::Result; 56 | 57 | impl core::fmt::Display for Error { 58 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 59 | match self { 60 | &Error::Exhausted => write!(f, "buffer space exhausted"), 61 | &Error::Illegal => write!(f, "illegal operation"), 62 | &Error::Unaddressable => write!(f, "unaddressable destination"), 63 | &Error::Truncated => write!(f, "truncated packet"), 64 | &Error::Checksum => write!(f, "checksum error"), 65 | &Error::Unrecognized => write!(f, "unrecognized packet"), 66 | &Error::Fragmented => write!(f, "fragmented packet"), 67 | &Error::Malformed => write!(f, "malformed packet"), 68 | &Error::Dropped => write!(f, "dropped by socket"), 69 | &Error::__Nonexhaustive => unreachable!(), 70 | } 71 | } 72 | } 73 | 74 | pub struct Ipv4Address(pub [u8; 4]); 75 | -------------------------------------------------------------------------------- /shared/packets/src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! enum_with_unknown { 2 | ( 3 | $( #[$enum_attr:meta] )* 4 | pub enum $name:ident($ty:ty) { 5 | $( $variant:ident = $value:expr ),+ $(,)* 6 | } 7 | ) => { 8 | enum_with_unknown! { 9 | $( #[$enum_attr] )* 10 | pub doc enum $name($ty) { 11 | $( #[doc(shown)] $variant = $value ),+ 12 | } 13 | } 14 | }; 15 | ( 16 | $( #[$enum_attr:meta] )* 17 | pub doc enum $name:ident($ty:ty) { 18 | $( 19 | $( #[$variant_attr:meta] )+ 20 | $variant:ident = $value:expr $(,)* 21 | ),+ 22 | } 23 | ) => { 24 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 25 | $( #[$enum_attr] )* 26 | pub enum $name { 27 | $( 28 | $( #[$variant_attr] )* 29 | $variant 30 | ),*, 31 | Unknown($ty) 32 | } 33 | 34 | impl ::core::convert::From<$ty> for $name { 35 | fn from(value: $ty) -> Self { 36 | match value { 37 | $( $value => $name::$variant ),*, 38 | other => $name::Unknown(other) 39 | } 40 | } 41 | } 42 | 43 | impl ::core::convert::From<$name> for $ty { 44 | fn from(value: $name) -> Self { 45 | match value { 46 | $( $name::$variant => $value ),*, 47 | $name::Unknown(other) => other 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /shared/packets/src/phy.rs: -------------------------------------------------------------------------------- 1 | /// A description of checksum behavior for a particular protocol. 2 | #[derive(Debug, Clone, Copy)] 3 | pub enum Checksum { 4 | /// Verify checksum when receiving and compute checksum when sending. 5 | Both, 6 | /// Verify checksum when receiving. 7 | Rx, 8 | /// Compute checksum before sending. 9 | Tx, 10 | /// Ignore checksum completely. 11 | None, 12 | } 13 | 14 | impl Default for Checksum { 15 | fn default() -> Checksum { 16 | Checksum::Both 17 | } 18 | } 19 | 20 | impl Checksum { 21 | /// Returns whether checksum should be verified when receiving. 22 | pub fn rx(&self) -> bool { 23 | match *self { 24 | Checksum::Both | Checksum::Rx => true, 25 | _ => false, 26 | } 27 | } 28 | 29 | /// Returns whether checksum should be verified when sending. 30 | pub fn tx(&self) -> bool { 31 | match *self { 32 | Checksum::Both | Checksum::Tx => true, 33 | _ => false, 34 | } 35 | } 36 | } 37 | 38 | /// A description of checksum behavior for every supported protocol. 39 | #[derive(Debug, Clone, Default)] 40 | pub struct ChecksumCapabilities { 41 | pub ipv4: Checksum, 42 | pub udp: Checksum, 43 | pub tcp: Checksum, 44 | #[cfg(feature = "proto-ipv4")] 45 | pub icmpv4: Checksum, 46 | #[cfg(feature = "proto-ipv6")] 47 | pub icmpv6: Checksum, 48 | dummy: (), 49 | } 50 | 51 | impl ChecksumCapabilities { 52 | /// Checksum behavior that results in not computing or verifying checksums 53 | /// for any of the supported protocols. 54 | pub fn ignored() -> Self { 55 | ChecksumCapabilities { 56 | ipv4: Checksum::None, 57 | udp: Checksum::None, 58 | tcp: Checksum::None, 59 | #[cfg(feature = "proto-ipv4")] 60 | icmpv4: Checksum::None, 61 | #[cfg(feature = "proto-ipv6")] 62 | icmpv6: Checksum::None, 63 | ..Self::default() 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /shared/packets/src/wire.rs: -------------------------------------------------------------------------------- 1 | /*! Low-level packet access and construction. 2 | 3 | The `wire` module deals with the packet *representation*. It provides two levels 4 | of functionality. 5 | 6 | * First, it provides functions to extract fields from sequences of octets, 7 | and to insert fields into sequences of octets. This happens `Packet` family of 8 | structures, e.g. [EthernetFrame] or [Ipv4Packet]. 9 | * Second, in cases where the space of valid field values is much smaller than the space 10 | of possible field values, it provides a compact, high-level representation 11 | of packet data that can be parsed from and emitted into a sequence of octets. 12 | This happens through the `Repr` family of structs and enums, e.g. [ArpRepr] or [Ipv4Repr]. 13 | 14 | [EthernetFrame]: struct.EthernetFrame.html 15 | [Ipv4Packet]: struct.Ipv4Packet.html 16 | [ArpRepr]: enum.ArpRepr.html 17 | [Ipv4Repr]: struct.Ipv4Repr.html 18 | 19 | The functions in the `wire` module are designed for use together with `-Cpanic=abort`. 20 | 21 | The `Packet` family of data structures guarantees that, if the `Packet::check_len()` method 22 | returned `Ok(())`, then no accessor or setter method will panic; however, the guarantee 23 | provided by `Packet::check_len()` may no longer hold after changing certain fields, 24 | which are listed in the documentation for the specific packet. 25 | 26 | The `Packet::new_checked` method is a shorthand for a combination of `Packet::new_unchecked` 27 | and `Packet::check_len`. 28 | When parsing untrusted input, it is *necessary* to use `Packet::new_checked()`; 29 | so long as the buffer is not modified, no accessor will fail. 30 | When emitting output, though, it is *incorrect* to use `Packet::new_checked()`; 31 | the length check is likely to succeed on a zeroed buffer, but fail on a buffer 32 | filled with data from a previous packet, such as when reusing buffers, resulting 33 | in nondeterministic panics with some network devices but not others. 34 | The buffer length for emission is not calculated by the `Packet` layer. 35 | 36 | In the `Repr` family of data structures, the `Repr::parse()` method never panics 37 | as long as `Packet::new_checked()` (or `Packet::check_len()`) has succeeded, and 38 | the `Repr::emit()` method never panics as long as the underlying buffer is exactly 39 | `Repr::buffer_len()` octets long. 40 | 41 | # Examples 42 | 43 | To emit an IP packet header into an octet buffer, and then parse it back: 44 | 45 | ```rust 46 | # #[cfg(feature = "proto-ipv4")] 47 | # { 48 | use smoltcp::phy::ChecksumCapabilities; 49 | use smoltcp::wire::*; 50 | let repr = Ipv4Repr { 51 | src_addr: Ipv4Address::new(10, 0, 0, 1), 52 | dst_addr: Ipv4Address::new(10, 0, 0, 2), 53 | protocol: IpProtocol::Tcp, 54 | payload_len: 10, 55 | hop_limit: 64 56 | }; 57 | let mut buffer = vec![0; repr.buffer_len() + repr.payload_len]; 58 | { // emission 59 | let mut packet = Ipv4Packet::new_unchecked(&mut buffer); 60 | repr.emit(&mut packet, &ChecksumCapabilities::default()); 61 | } 62 | { // parsing 63 | let packet = Ipv4Packet::new_checked(&buffer) 64 | .expect("truncated packet"); 65 | let parsed = Ipv4Repr::parse(&packet, &ChecksumCapabilities::default()) 66 | .expect("malformed packet"); 67 | assert_eq!(repr, parsed); 68 | } 69 | # } 70 | ``` 71 | */ 72 | 73 | pub mod pretty_print; 74 | 75 | #[cfg(feature = "proto-ipv4")] 76 | mod arp; 77 | #[cfg(feature = "proto-dhcpv4")] 78 | pub(crate) mod dhcpv4; 79 | mod ethernet; 80 | #[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))] 81 | mod icmp; 82 | #[cfg(feature = "proto-ipv4")] 83 | mod icmpv4; 84 | #[cfg(feature = "proto-ipv6")] 85 | mod icmpv6; 86 | #[cfg(feature = "proto-igmp")] 87 | mod igmp; 88 | pub(crate) mod ip; 89 | #[cfg(feature = "proto-ipv4")] 90 | mod ipv4; 91 | #[cfg(feature = "proto-ipv6")] 92 | mod ipv6; 93 | #[cfg(feature = "proto-ipv6")] 94 | mod ipv6fragment; 95 | #[cfg(feature = "proto-ipv6")] 96 | mod ipv6hopbyhop; 97 | #[cfg(feature = "proto-ipv6")] 98 | mod ipv6option; 99 | #[cfg(feature = "proto-ipv6")] 100 | mod ipv6routing; 101 | #[cfg(feature = "proto-ipv6")] 102 | mod mld; 103 | #[cfg(feature = "proto-ipv6")] 104 | mod ndisc; 105 | #[cfg(feature = "proto-ipv6")] 106 | mod ndiscoption; 107 | mod tcp; 108 | mod udp; 109 | 110 | pub use self::pretty_print::PrettyPrinter; 111 | 112 | pub use self::ethernet::{ 113 | Address as EthernetAddress, EtherType as EthernetProtocol, Frame as EthernetFrame, 114 | Repr as EthernetRepr, 115 | }; 116 | 117 | #[cfg(feature = "proto-ipv4")] 118 | pub use self::arp::{ 119 | Hardware as ArpHardware, Operation as ArpOperation, Packet as ArpPacket, Repr as ArpRepr, 120 | }; 121 | 122 | pub use self::ip::{ 123 | Address as IpAddress, Cidr as IpCidr, Endpoint as IpEndpoint, Protocol as IpProtocol, 124 | Repr as IpRepr, Version as IpVersion, 125 | }; 126 | 127 | #[cfg(feature = "proto-ipv4")] 128 | pub use self::ipv4::{ 129 | Address as Ipv4Address, Cidr as Ipv4Cidr, Packet as Ipv4Packet, Repr as Ipv4Repr, 130 | MIN_MTU as IPV4_MIN_MTU, 131 | }; 132 | 133 | #[cfg(feature = "proto-ipv6")] 134 | pub use self::ipv6::{ 135 | Address as Ipv6Address, Cidr as Ipv6Cidr, Packet as Ipv6Packet, Repr as Ipv6Repr, 136 | MIN_MTU as IPV6_MIN_MTU, 137 | }; 138 | 139 | #[cfg(feature = "proto-ipv6")] 140 | pub use self::ipv6option::{ 141 | FailureType as Ipv6OptionFailureType, Ipv6Option, Repr as Ipv6OptionRepr, 142 | Type as Ipv6OptionType, 143 | }; 144 | 145 | #[cfg(feature = "proto-ipv6")] 146 | pub use self::ipv6hopbyhop::{Header as Ipv6HopByHopHeader, Repr as Ipv6HopByHopRepr}; 147 | 148 | #[cfg(feature = "proto-ipv6")] 149 | pub use self::ipv6fragment::{Header as Ipv6FragmentHeader, Repr as Ipv6FragmentRepr}; 150 | 151 | #[cfg(feature = "proto-ipv6")] 152 | pub use self::ipv6routing::{Header as Ipv6RoutingHeader, Repr as Ipv6RoutingRepr}; 153 | 154 | #[cfg(feature = "proto-ipv4")] 155 | pub use self::icmpv4::{ 156 | DstUnreachable as Icmpv4DstUnreachable, Message as Icmpv4Message, Packet as Icmpv4Packet, 157 | ParamProblem as Icmpv4ParamProblem, Redirect as Icmpv4Redirect, Repr as Icmpv4Repr, 158 | TimeExceeded as Icmpv4TimeExceeded, 159 | }; 160 | 161 | #[cfg(feature = "proto-igmp")] 162 | pub use self::igmp::{IgmpVersion, Packet as IgmpPacket, Repr as IgmpRepr}; 163 | 164 | #[cfg(feature = "proto-ipv6")] 165 | pub use self::icmpv6::{ 166 | DstUnreachable as Icmpv6DstUnreachable, Message as Icmpv6Message, Packet as Icmpv6Packet, 167 | ParamProblem as Icmpv6ParamProblem, Repr as Icmpv6Repr, TimeExceeded as Icmpv6TimeExceeded, 168 | }; 169 | 170 | #[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))] 171 | pub use self::icmp::Repr as IcmpRepr; 172 | 173 | #[cfg(feature = "proto-ipv6")] 174 | pub use self::ndisc::{ 175 | NeighborFlags as NdiscNeighborFlags, Repr as NdiscRepr, RouterFlags as NdiscRouterFlags, 176 | }; 177 | 178 | #[cfg(feature = "proto-ipv6")] 179 | pub use self::ndiscoption::{ 180 | NdiscOption, PrefixInfoFlags as NdiscPrefixInfoFlags, 181 | PrefixInformation as NdiscPrefixInformation, RedirectedHeader as NdiscRedirectedHeader, 182 | Repr as NdiscOptionRepr, Type as NdiscOptionType, 183 | }; 184 | 185 | #[cfg(feature = "proto-ipv6")] 186 | pub use self::mld::{AddressRecord as MldAddressRecord, Repr as MldRepr}; 187 | 188 | pub use self::udp::{Packet as UdpPacket, Repr as UdpRepr}; 189 | 190 | pub use self::tcp::{ 191 | Control as TcpControl, Packet as TcpPacket, Repr as TcpRepr, SeqNumber as TcpSeqNumber, 192 | TcpOption, 193 | }; 194 | 195 | #[cfg(feature = "proto-dhcpv4")] 196 | pub use self::dhcpv4::{MessageType as DhcpMessageType, Packet as DhcpPacket, Repr as DhcpRepr}; 197 | -------------------------------------------------------------------------------- /shared/rangeset/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rangeset" 3 | version = "0.1.0" 4 | authors = ["gamozo "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /shared/safecast/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "safecast" 3 | version = "0.1.0" 4 | authors = ["Brandon Falk "] 5 | 6 | [dependencies] 7 | 8 | -------------------------------------------------------------------------------- /shared/safecast/README.md: -------------------------------------------------------------------------------- 1 | # safecast 2 | 3 | An attempt to make a procedural macro to support safe casting in Rust. 4 | 5 | ## Goals 6 | 7 | This library is designed to allow for copying raw underlying data between different types in Rust. 8 | This is helpful for handling things like binary files or network protocols. Using this library you 9 | are able to safely create structures and cast/copy between them. 10 | 11 | ## Safety 12 | 13 | This casting/copying is safe given the following: 14 | 15 | - The structure is composed only of types which have no invalid/unsafe underlying binary encodings 16 | - Currently only `u8`, `u16`, `u32`, `u64`, `usize`, `i8`, `i16`, `i32`, `i64`, `isize` are considered 17 | to have these properties. 18 | - Structures may have structures in them which are also packed and contain only the aforementioned 19 | types. 20 | - Fixed sized arrays are also allowed. 21 | - The current implementation is designed to be extra strict. Things like tuples and such would 22 | be fine in practice but the goal is to keep things simple for now to make it easier to 23 | verify. 24 | - The structure is packed such that no padding occurs between fields 25 | - Since the padding between fields contains undefined values this interface could potentially 26 | expose them if cast to another type where the padding is readable. Thus we disallow use 27 | of padding in structures. This doesn't matter much anyways as if you're working with binary 28 | data it's probably packed anyways. 29 | 30 | ## Interface 31 | 32 | `SafeCast::cast_copy_into(&self, dest: &mut T)` 33 | 34 | This routine allows the casting from an existing structure to another type given the other 35 | type also implemented ByteSafe. This method is the one used when `T` is `?Sized`, allowing for 36 | us to cast into things like slices/Vecs. This is the core implementation and is used by 37 | `cast()`. 38 | 39 | This method will panic unless both self and T are equal in size (in bytes). 40 | 41 | `SafeCast::cast_copy(&self) -> T` 42 | 43 | Creates an uninitialized value of type T, and calls `cast_into` on self 44 | to cast it into T. Returns the new value. 45 | 46 | This method will panic unless both self and T are equal in size (in bytes). 47 | 48 | `SafeCast::cast(&self) -> &[T]` 49 | 50 | Casts `Self` to a slice of `T`s, where `Self` is evenly divisible by `T`. 51 | 52 | `SafeCast::cast_mut(&mut self) -> &mut [T]` 53 | 54 | Casts `Self` to a mutable slice of `T`s, where `Self` is evenly divisible by `T`. 55 | 56 | ## Endianness 57 | 58 | I'm not sure if it matches Rust's definition, however I think it is fine for the endianness 59 | to be up to the user to handle. There is no safety violation by having an unexpected 60 | endian swap, thus I'm okay with this not handling endian swaps for you. It is up 61 | to the user to manually swap fields as they use them. 62 | 63 | ## Enforcement / Internals 64 | 65 | To make this library easy to safely use we use a procedural macro to `#[derive(ByteSafe)]` on 66 | a structure. 67 | 68 | Interally we have two traits: `ByteSafe` and `SafeCast`. `ByteSafe` is the unsafe trait which is 69 | used to specify that a type is safe for use for casting and byte-level copies to other types 70 | marked `ByteSafe`. `SafeCast` is the trait which implements the casting/copying funtions for 71 | a given type, if the type implements `ByteSafe`. `SafeCast` is automatically implemented for 72 | any type which is `ByteSafe`. 73 | 74 | The `ByteSafe` trait is the unsafe one which is either manually implemented (developer must verify 75 | it is safe), or is automatically implemented safely by `#[derive(ByteSafe)]`. 76 | 77 | `ByteSafe` contains a dummy function `bytesafe()` which is core to the derive implementation. 78 | `bytesafe()` does nothing, nor does it return anything. It is simply there so that the 79 | automatic derive can attempt to call this function to determine if the trait is implemented. 80 | 81 | Interally the custom derive does 2 simple things. 82 | 83 | - Verifies the structure is marked as packed 84 | - Implements `ByteSafe` for the structure with a custom `ByteSafe::bytesafe()` which attempts to call 85 | `ByteSafe::bytesafe()` on every member of the structure. This behavior verifies that 86 | every member is marked `ByteSafe`. If all members are marked as `ByteSafe`, then the structure 87 | itself can also be marked as `ByteSafe`. 88 | 89 | For this to all work a few manual `ByteSafe` implementations must be done on the core types we 90 | want to allow in structures. In our case this list is `u8`, `u16`, `u32`, `u64`, `usize`, `i8`, `i16`, `i32`, `i64`, `isize`. 91 | Further `ByteSafe` is implemented for slices `[T: ByteSafe]` and fixed-sized arrays up to and including 32-elements 92 | `[T: ByteSafe; 0..33]`. 93 | Our custom derive verifies that each member of the structure is either a `syn::Ty::Path` (raw type), or a `syn::Ty::Array` 94 | (fixed sized array). Thus even though we allow slices for `ByteSafe`, they are not allowed in the structures in a custom 95 | derive, only fixed sized arrays and raw types are. 96 | 97 | The implementation of `ByteSafe` for slices allows for casting slices to structures, and structures back to slices. However 98 | does not allow for slices to be used inside structures that are being cast to/from. 99 | -------------------------------------------------------------------------------- /shared/safecast/bytesafe_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bytesafe_derive" 3 | version = "0.1.0" 4 | authors = ["Brandon Falk "] 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | syn = "0.11.11" 11 | quote = "0.3.15" 12 | 13 | -------------------------------------------------------------------------------- /shared/safecast/bytesafe_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate quote; 2 | 3 | extern crate proc_macro; 4 | extern crate syn; 5 | 6 | use proc_macro::TokenStream; 7 | use quote::ToTokens; 8 | 9 | /// Derive the ByteSafe trait for a given structure 10 | /// 11 | /// This procedural macro ensures that all members of the structure being 12 | /// derived as ByteSafe are also ByteSafe. It also verifies that the structure 13 | /// contains no padding. 14 | #[proc_macro_derive(ByteSafe)] 15 | pub fn derive_bytesafe(input: TokenStream) -> TokenStream { 16 | /* Construct a string representation of the type definition */ 17 | let s = input.to_string(); 18 | 19 | /* Parse the string representation */ 20 | let ast = syn::parse_derive_input(&s).unwrap(); 21 | 22 | /* Build the impl */ 23 | let gen = impl_derive_bytesafe(&ast); 24 | 25 | /* Return the generated impl */ 26 | gen.parse().unwrap() 27 | } 28 | 29 | /// Internal implementation of the ByteSafe derive 30 | fn impl_derive_bytesafe(ast: &syn::DeriveInput) -> quote::Tokens { 31 | let name = &ast.ident; 32 | let (impl_generics, ty_generics, where_clause) = 33 | ast.generics.split_for_impl(); 34 | 35 | let mut stuff = Vec::new(); 36 | 37 | /* There is probably a better/cleaner way of doing this, but check if 38 | * this structure is marked as repr(C). If it is not repr(C) we might 39 | * not be able to directly copy bits as the representation could be 40 | * different than what we expect. 41 | */ 42 | let mut is_repr_c = false; 43 | for attr in &ast.attrs { 44 | if let syn::MetaItem::List(ref ident, ref items) = attr.value { 45 | if ident == "repr" { 46 | for item in items { 47 | if let &syn::NestedMetaItem::MetaItem(ref item) = item { 48 | if item.name() == "C" || item.name() == "packed" { 49 | is_repr_c = true; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | assert!(is_repr_c); 57 | 58 | /* We only support structures */ 59 | if let syn::Body::Struct(ref variants) = ast.body { 60 | /* For each field in the structure call bytesafe() on it, this will 61 | * fail if it does not implement the bytesafe trait. 62 | * 63 | * However currently with automatic dereferencing this allows for 64 | * references to be used to types that are ByteSafe, which is an issue. 65 | * We need a workaround for this. 66 | */ 67 | for field in variants.fields().iter() { 68 | match field.ty { 69 | /* We allow Path types */ 70 | syn::Ty::Path(_, _) => {} 71 | 72 | /* We allow fixed sized arrays */ 73 | syn::Ty::Array(_, _) => {} 74 | 75 | /* Anything else in the structure is not allowed */ 76 | _ => panic!("Unsupported type {:?}", field.ty) 77 | } 78 | 79 | let mut typey = quote::Tokens::new(); 80 | field.ty.to_tokens(&mut typey); 81 | 82 | //eprint!("{}\n", typey); 83 | 84 | /* Attempt to call bytesafe() dummy routine on member. This will 85 | * fail at compile time if this structure member doesn't implement 86 | * ByteSafe. 87 | */ 88 | stuff.push(quote! { 89 | /* Accumulate the size of all the raw elements */ 90 | calculated_size += core::mem::size_of::<#typey>(); 91 | <#typey>::bytesafe(); 92 | }); 93 | } 94 | } else { 95 | panic!("Expected struct only for ByteSafe"); 96 | } 97 | 98 | /* Implement ByteSafe! */ 99 | quote! { 100 | unsafe impl #impl_generics ::safecast::ByteSafe for #name #ty_generics #where_clause { 101 | fn bytesafe() 102 | { 103 | /* Normalize so we can use core even in std projects */ 104 | extern crate core; 105 | 106 | let mut calculated_size = 0usize; 107 | 108 | #(#stuff)* 109 | 110 | /* Validate that the size of each individual member adds up 111 | * to the structure size. If this is a mismatch then there was 112 | * padding in the structure and it is not safe to cast this 113 | * structure. 114 | */ 115 | assert!(calculated_size == core::mem::size_of::<#name #ty_generics #where_clause>(), 116 | "Structure contained padding bytes, not safe for cast"); 117 | } 118 | } 119 | } 120 | } 121 | 122 | -------------------------------------------------------------------------------- /shared/serial/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serial" 3 | version = "0.1.0" 4 | authors = ["gamozo "] 5 | 6 | [dependencies] 7 | cpu = { path = "../cpu" } 8 | 9 | -------------------------------------------------------------------------------- /shared/serial/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | extern crate cpu; 4 | 5 | /* COM devices 6 | * 7 | * (address, is_present, is_active) 8 | * 9 | * address - I/O address of port 10 | * is_present - Set if scratchpad and loopback tests pass 11 | * is_active - Set if ??? is set 12 | * is_init - Set if port has been probed 13 | */ 14 | 15 | struct ComStruct { 16 | pub address: u16, 17 | pub is_present: bool, 18 | pub is_active: bool, 19 | pub is_init: bool, 20 | } 21 | 22 | static mut COM1: ComStruct = ComStruct { 23 | address: 0x3f8, 24 | is_present: false, 25 | is_active: false, 26 | is_init: false, 27 | }; 28 | static mut COM2: ComStruct = ComStruct { 29 | address: 0x2f8, 30 | is_present: false, 31 | is_active: false, 32 | is_init: false, 33 | }; 34 | static mut COM3: ComStruct = ComStruct { 35 | address: 0x3e8, 36 | is_present: false, 37 | is_active: false, 38 | is_init: false, 39 | }; 40 | static mut COM4: ComStruct = ComStruct { 41 | address: 0x2e8, 42 | is_present: false, 43 | is_active: false, 44 | is_init: false, 45 | }; 46 | 47 | #[macro_export] 48 | macro_rules! print { 49 | ( $($arg:tt)* ) => ({ 50 | use core::fmt::Write; 51 | let _ = write!(&mut $crate::Writer, $($arg)*); 52 | }) 53 | } 54 | 55 | /// Writer implementation used by the `print!` macro 56 | pub struct Writer; 57 | 58 | impl core::fmt::Write for Writer { 59 | fn write_str(&mut self, s: &str) -> core::fmt::Result { 60 | write(s); 61 | Ok(()) 62 | } 63 | } 64 | 65 | unsafe fn init(port: &mut ComStruct) { 66 | /* Set port to initialized state */ 67 | port.is_active = true; 68 | 69 | /* Set scratchpad to contain 0x41, and check if it reads it back */ 70 | cpu::out8(port.address + 7, 0x41); 71 | if cpu::in8(port.address + 7) != 0x41 { 72 | port.is_present = false; 73 | port.is_active = false; 74 | return; 75 | } 76 | 77 | /* Mark port as present */ 78 | port.is_present = true; 79 | 80 | /* Disable all interrupts */ 81 | cpu::out8(port.address + 1, 0); 82 | 83 | /* Set DLAB */ 84 | cpu::out8(port.address + 3, 0x80); 85 | 86 | /* Write low divisor byte */ 87 | cpu::out8(port.address + 0, 1); 88 | 89 | /* Write high divisor byte */ 90 | cpu::out8(port.address + 1, 0); 91 | 92 | /* Clear DLAB, set word length to 8 bits, one stop bit, no parity */ 93 | cpu::out8(port.address + 3, 3); 94 | 95 | /* Disable FIFOs entirely */ 96 | cpu::out8(port.address + 2, 0xc7); 97 | 98 | /* Set RTS and DTR */ 99 | cpu::out8(port.address + 4, 0x0b); 100 | 101 | /* If clear to send, data set ready, and data carrier detect are set 102 | * mark this port as active! 103 | */ 104 | if cpu::in8(port.address + 6) & 0b10110000 == 0b10110000 { 105 | /* Mark port as active */ 106 | port.is_active = true; 107 | } 108 | } 109 | 110 | /// Invoke a closure on each port which has been identified 111 | fn for_each_port(mut func: F) { 112 | unsafe { 113 | /* If ports are not initialized, initialize them */ 114 | if !COM1.is_active { 115 | init(&mut COM1) 116 | } 117 | if !COM2.is_active { 118 | init(&mut COM2) 119 | } 120 | if !COM3.is_active { 121 | init(&mut COM3) 122 | } 123 | if !COM4.is_active { 124 | init(&mut COM4) 125 | } 126 | 127 | if COM1.is_present { 128 | func(COM1.address) 129 | } 130 | if COM2.is_present { 131 | func(COM2.address) 132 | } 133 | if COM3.is_present { 134 | func(COM3.address) 135 | } 136 | if COM4.is_present { 137 | func(COM4.address) 138 | } 139 | } 140 | } 141 | 142 | /// Write a byte to the serial port data port 143 | pub fn write_byte(byte: u8) { 144 | /* LF implies CR+LF */ 145 | if byte == b'\n' { 146 | write_byte(b'\r'); 147 | } 148 | 149 | for_each_port(|port| unsafe { 150 | while (cpu::in8(port + 5) & 0x20) == 0 {} 151 | cpu::out8(port, byte); 152 | }); 153 | } 154 | 155 | /// Write bytes to the serial device 156 | pub fn write_bytes(data: &[u8]) { 157 | for &byte in data { 158 | write_byte(byte); 159 | } 160 | } 161 | 162 | /// Write a string to the serial device as UTF-8 bytes 163 | pub fn write(string: &str) { 164 | write_bytes(string.as_bytes()); 165 | } 166 | 167 | /// Returns Some(byte) if a byte is present on the serial port, otherwise 168 | /// returns None 169 | pub fn probe_byte() -> Option { 170 | let mut byte = None; 171 | 172 | for_each_port(|port| unsafe { 173 | if byte.is_none() && (cpu::in8(port + 5) & 1) != 0 { 174 | byte = Some(cpu::in8(port)); 175 | } 176 | }); 177 | 178 | byte 179 | } 180 | -------------------------------------------------------------------------------- /shared/vmx/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vmx" 3 | version = "0.1.0" 4 | authors = ["thebarbershopper "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | x86_64 = "0.7.1" 9 | -------------------------------------------------------------------------------- /shared/vmx/src/msr.rs: -------------------------------------------------------------------------------- 1 | pub const IA32_FEATURE_CONTROL: u32 = 0x3A; 2 | pub const IA32_VMX_BASIC: u32 = 0x480; 3 | pub const IA32_VMX_PINBASED_CTLS: u32 = 0x481; 4 | pub const IA32_VMX_PROCBASED_CTLS: u32 = 0x482; 5 | pub const IA32_VMX_EXIT_CTLS: u32 = 0x483; 6 | pub const IA32_VMX_ENTRY_CTLS: u32 = 0x484; 7 | pub const IA32_VMX_PROCBASED_CTLS2: u32 = 0x48B; 8 | 9 | pub const IA32_VMX_CR0_FIXED0: u32 = 0x486; 10 | pub const IA32_VMX_CR0_FIXED1: u32 = 0x487; 11 | pub const IA32_VMX_CR4_FIXED0: u32 = 0x488; 12 | pub const IA32_VMX_CR4_FIXED1: u32 = 0x489; 13 | 14 | pub const VMCS_ADDR_IO_BITMAP_A: u32 = 0x2000; 15 | pub const VMCS_ADDR_IO_BITMAP_A_HIGH: u32 = 0x2001; 16 | pub const VMCS_ADDR_IO_BITMAP_B: u32 = 0x2002; 17 | pub const VMCS_ADDR_IO_BITMAP_B_HIGH: u32 = 0x2003; 18 | 19 | pub const VMCS_VMEXIT_MSR_STORE_ADDR: u32 = 0x2006; 20 | pub const VMCS_VMEXIT_MSR_STORE_ADDR_HIGH: u32 = 0x2007; 21 | pub const VMCS_VMEXIT_MSR_LOAD_ADDR: u32 = 0x2008; 22 | pub const VMCS_VMEXIT_MSR_LOAD_ADDR_HIGH: u32 = 0x2009; 23 | pub const VMCS_VMENTRY_MSR_LOAD_ADDR: u32 = 0x200a; 24 | pub const VMCS_VMENTRY_MSR_LOAD_ADDR_HIGH: u32 = 0x200b; 25 | pub const VMCS_EXEC_VMCS_PTR: u32 = 0x200c; 26 | pub const VMCS_EXEC_VMCS_PTR_HIGH: u32 = 0x200d; 27 | pub const VMCS_TSC_OFFSET: u32 = 0x2010; 28 | pub const VMCS_TSC_OFFSET_HIGH: u32 = 0x2011; 29 | pub const VMCS_EPT_PTR: u32 = 0x201a; 30 | pub const VMCS_EPT_PTR_HIGH: u32 = 0x201b; 31 | 32 | pub const VMCS_PIN_BASED_VMEXEC_CTL: u32 = 0x4000; 33 | pub const VMCS_PROC_BASED_VMEXEC_CTL: u32 = 0x4002; 34 | pub const VMCS_SECONDARY_VMEXEC_CTL: u32 = 0x401e; 35 | pub const VMCS_EXCEPTION_BITMAP: u32 = 0x4004; 36 | pub const VMCS_PAGEFAULT_ERRCODE_MASK: u32 = 0x4006; 37 | pub const VMCS_PAGEFAULT_ERRCODE_MATCH: u32 = 0x4008; 38 | pub const VMCS_CR3_TARGET_COUNT: u32 = 0x400a; 39 | 40 | pub const VMCS_VMEXIT_CTL: u32 = 0x400c; 41 | pub const VMCS_VMEXIT_MSR_STORE_COUNT: u32 = 0x400e; 42 | pub const VMCS_VMEXIT_MSR_LOAD_COUNT: u32 = 0x4010; 43 | 44 | pub const VMCS_VMENTRY_CTL: u32 = 0x4012; 45 | pub const VMCS_VMENTRY_MSR_LOAD_COUNT: u32 = 0x4014; 46 | pub const VMCS_VMENTRY_INTR_INFO_FIELD: u32 = 0x4016; 47 | pub const VMCS_VMENTRY_EXCEPTION_ERRCODE: u32 = 0x4018; 48 | pub const VMCS_VMENTRY_INSTRUCTION_LEN: u32 = 0x401a; 49 | 50 | pub const VMCS_TPR_THRESHOLD: u32 = 0x401c; 51 | 52 | pub const VMCS_CR0_GUESTHOST_MASK: u32 = 0x6000; 53 | pub const VMCS_CR4_GUESTHOST_MASK: u32 = 0x6002; 54 | pub const VMCS_CR0_READ_SHADOW: u32 = 0x6004; 55 | pub const VMCS_CR4_READ_SHADOW: u32 = 0x6006; 56 | pub const VMCS_CR3_TARGET_VALUE_0: u32 = 0x6008; 57 | pub const VMCS_CR3_TARGET_VALUE_1: u32 = 0x600a; 58 | pub const VMCS_CR3_TARGET_VALUE_2: u32 = 0x600c; 59 | pub const VMCS_CR3_TARGET_VALUE_3: u32 = 0x600e; 60 | 61 | pub const VMCS_HOST_ES_SEL: u32 = 0xc00; 62 | pub const VMCS_HOST_CS_SEL: u32 = 0xc02; 63 | pub const VMCS_HOST_SS_SEL: u32 = 0xc04; 64 | pub const VMCS_HOST_DS_SEL: u32 = 0xc06; 65 | pub const VMCS_HOST_FS_SEL: u32 = 0xc08; 66 | pub const VMCS_HOST_GS_SEL: u32 = 0xc0a; 67 | pub const VMCS_HOST_TR_SEL: u32 = 0xc0c; 68 | 69 | pub const VMCS_HOST_CR0: u32 = 0x6c00; 70 | pub const VMCS_HOST_CR3: u32 = 0x6c02; 71 | pub const VMCS_HOST_CR4: u32 = 0x6c04; 72 | pub const VMCS_HOST_FS_BASE: u32 = 0x6c06; 73 | pub const VMCS_HOST_GS_BASE: u32 = 0x6c08; 74 | pub const VMCS_HOST_TR_BASE: u32 = 0x6c0a; 75 | pub const VMCS_HOST_GDTR_BASE: u32 = 0x6c0c; 76 | pub const VMCS_HOST_IDTR_BASE: u32 = 0x6c0e; 77 | pub const VMCS_HOST_RSP: u32 = 0x6c14; 78 | pub const VMCS_HOST_RIP: u32 = 0x6c16; 79 | 80 | pub const VMCS_HOST_SYSENTER_ESP: u32 = 0x6c10; 81 | pub const VMCS_HOST_SYSENTER_EIP: u32 = 0x6c12; 82 | 83 | pub const VMCS_GUEST_ES_SEL: u32 = 0x800; 84 | pub const VMCS_GUEST_CS_SEL: u32 = 0x802; 85 | pub const VMCS_GUEST_SS_SEL: u32 = 0x804; 86 | pub const VMCS_GUEST_DS_SEL: u32 = 0x806; 87 | pub const VMCS_GUEST_FS_SEL: u32 = 0x808; 88 | pub const VMCS_GUEST_GS_SEL: u32 = 0x80a; 89 | pub const VMCS_GUEST_LDTR_SEL: u32 = 0x80c; 90 | pub const VMCS_GUEST_TR_SEL: u32 = 0x80e; 91 | 92 | pub const VMCS_VMCS_LINK_PTR: u32 = 0x2800; 93 | pub const VMCS_VMCS_LINK_PTR_HIGH: u32 = 0x2801; 94 | pub const VMCS_GUEST_IA32_DEBUGCTL: u32 = 0x2802; 95 | pub const VMCS_GUEST_IA32_DEBUGCTL_HIGH: u32 = 0x2803; 96 | 97 | pub const VMCS_GUEST_ES_LIMIT: u32 = 0x4800; 98 | pub const VMCS_GUEST_CS_LIMIT: u32 = 0x4802; 99 | pub const VMCS_GUEST_SS_LIMIT: u32 = 0x4804; 100 | pub const VMCS_GUEST_DS_LIMIT: u32 = 0x4806; 101 | pub const VMCS_GUEST_FS_LIMIT: u32 = 0x4808; 102 | pub const VMCS_GUEST_GS_LIMIT: u32 = 0x480a; 103 | pub const VMCS_GUEST_LDTR_LIMIT: u32 = 0x480c; 104 | pub const VMCS_GUEST_TR_LIMIT: u32 = 0x480e; 105 | pub const VMCS_GUEST_GDTR_LIMIT: u32 = 0x4810; 106 | pub const VMCS_GUEST_IDTR_LIMIT: u32 = 0x4812; 107 | pub const VMCS_GUEST_ES_ACCESS_RIGHTS: u32 = 0x4814; 108 | pub const VMCS_GUEST_CS_ACCESS_RIGHTS: u32 = 0x4816; 109 | pub const VMCS_GUEST_SS_ACCESS_RIGHTS: u32 = 0x4818; 110 | pub const VMCS_GUEST_DS_ACCESS_RIGHTS: u32 = 0x481a; 111 | pub const VMCS_GUEST_FS_ACCESS_RIGHTS: u32 = 0x481c; 112 | pub const VMCS_GUEST_GS_ACCESS_RIGHTS: u32 = 0x481e; 113 | pub const VMCS_GUEST_LDTR_ACCESS_RIGHTS: u32 = 0x4820; 114 | pub const VMCS_GUEST_TR_ACCESS_RIGHTS: u32 = 0x4822; 115 | pub const VMCS_GUEST_INTERRUPTIBILITY_STATE: u32 = 0x4824; 116 | pub const VMCS_GUEST_ACTIVITY_STATE: u32 = 0x4826; 117 | 118 | pub const VMCS_GUEST_CR0: u32 = 0x6800; 119 | pub const VMCS_GUEST_CR3: u32 = 0x6802; 120 | pub const VMCS_GUEST_CR4: u32 = 0x6804; 121 | pub const VMCS_GUEST_ES_BASE: u32 = 0x6806; 122 | pub const VMCS_GUEST_CS_BASE: u32 = 0x6808; 123 | pub const VMCS_GUEST_SS_BASE: u32 = 0x680a; 124 | pub const VMCS_GUEST_DS_BASE: u32 = 0x680c; 125 | pub const VMCS_GUEST_FS_BASE: u32 = 0x680e; 126 | pub const VMCS_GUEST_GS_BASE: u32 = 0x6810; 127 | pub const VMCS_GUEST_LDTR_BASE: u32 = 0x6812; 128 | pub const VMCS_GUEST_TR_BASE: u32 = 0x6814; 129 | pub const VMCS_GUEST_GDTR_BASE: u32 = 0x6816; 130 | pub const VMCS_GUEST_IDTR_BASE: u32 = 0x6818; 131 | pub const VMCS_GUEST_DR7: u32 = 0x681a; 132 | pub const VMCS_GUEST_RSP: u32 = 0x681c; 133 | pub const VMCS_GUEST_RIP: u32 = 0x681e; 134 | pub const VMCS_GUEST_RFLAGS: u32 = 0x6820; 135 | pub const VMCS_GUEST_PENDING_DEBUG_EXCEPTIONS: u32 = 0x6822; 136 | 137 | pub const VMCS_GUEST_SYSENTER_ESP: u32 = 0x6824; 138 | pub const VMCS_GUEST_SYSENTER_EIP: u32 = 0x6826; 139 | 140 | pub const VMCS_VM_INSTRUCTION_ERROR: u32 = 0x4400; 141 | pub const VMCS_VMEXIT_REASON: u32 = 0x4402; 142 | pub const VMCS_VMEXIT_INTERRUPTION_INFO: u32 = 0x4404; 143 | pub const VMCS_VMEXIT_INTERRUPTION_ERROR_CODE: u32 = 0x4406; 144 | pub const VMCS_VMEXIT_INSTR_LENGTH: u32 = 0x440c; 145 | pub const VMCS_VMEXIT_INSTR_INFO: u32 = 0x440e; 146 | 147 | pub const VMCS_EXIT_QUALIFICATION: u32 = 0x6400; 148 | pub const VMCS_IO_RCX: u32 = 0x6402; 149 | pub const VMCS_IO_RSI: u32 = 0x6404; 150 | pub const VMCS_IO_RDI: u32 = 0x6406; 151 | pub const VMCS_IO_RIP: u32 = 0x6408; 152 | pub const VMCS_GUEST_LINEAR_ADDR: u32 = 0x640a; 153 | 154 | pub const VMCS_IDT_INFORMATION: u32 = 0x4408; 155 | pub const VMCS_IDT_ERROR_CODE: u32 = 0x440a; 156 | 157 | pub const VMCS_GUEST_PHYSICAL_ADDRESS: u32 = 0x2400; 158 | 159 | bitflags! { 160 | pub struct CTLS: u32 { 161 | const CR3_LOAD_EXIT = 1 << 15; 162 | const CR3_STORE_EXIT = 1 << 16; 163 | const SECONDARY = 1 << 31; 164 | } 165 | } 166 | 167 | bitflags! { 168 | pub struct CTLS2: u32 { 169 | const VIRTUALIZE_APIC = 1 << 0; 170 | const ENABLE_EPT = 1 << 1; 171 | const NA2 = 1 << 2; 172 | const NA3 = 1 << 3; 173 | const NA4 = 1 << 4; 174 | const NA5 = 1 << 5; 175 | const NA6 = 1 << 6; 176 | const UNRESTRICTED_GUEST = 1 << 7; 177 | const NA8 = 1 << 8; 178 | const NA9 = 1 << 9; 179 | const NA10 = 1 << 10; 180 | const NA11 = 1 << 11; 181 | const NA12 = 1 << 12; 182 | const NA13 = 1 << 13; 183 | const NA14 = 1 << 14; 184 | const NA15 = 1 << 15; 185 | const NA16 = 1 << 16; 186 | const NA17 = 1 << 17; 187 | const NA18 = 1 << 18; 188 | const NA19 = 1 << 19; 189 | const NA20 = 1 << 20; 190 | const RESERVED21 = 1 << 21; 191 | const NA22 = 1 << 22; 192 | const RESERVED22 = 1 << 23; 193 | const RESERVED23 = 1 << 24; 194 | const NA25 = 1 << 25; 195 | const RESERVED26 = 1 << 26; 196 | const RESERVED27 = 1 << 27; 197 | const RESERVED28 = 1 << 28; 198 | const RESERVED29 = 1 << 29; 199 | const RESERVED30 = 1 << 30; 200 | const RESERVED31 = 1 << 31; 201 | } 202 | } 203 | 204 | bitflags! { 205 | pub struct FeatureControl: u32 { 206 | const LOCK = 1 << 0; 207 | const VMXON = 1 << 2; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /snapshot.sh: -------------------------------------------------------------------------------- 1 | "/mnt/c/Program Files/Oracle/VirtualBox/VirtualBox.exe" --debug-command-line --start-running --startvm "Win10.Old" 2 | -------------------------------------------------------------------------------- /snapshot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "snapshot" 3 | version = "0.1.0" 4 | authors = ["thebarbershopper "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /snapshot/README.md: -------------------------------------------------------------------------------- 1 | # Snapshot 2 | 3 | Basic VirtualBox coredump parser to extract the CPU state used by the hypervisor for initializing 4 | a VM. Drops the resulting `VmRegs` file in `SNAPSHOT_regs`. 5 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::process::Command; 3 | 4 | const BOOTFILE_NAME: &'static str = "barberslice.boot"; 5 | const KERNEL_NAME: &'static str = "barberslice.kern"; 6 | const KERNEL_PATH: &'static str = "kernel/target/x86_64-pc-windows-msvc/release/kernel.exe"; 7 | const SNAPSHOT_REGS: &'static str = "SNAPSHOT_regs"; 8 | 9 | fn main() -> std::io::Result<()> { 10 | const DEPLOY_PATHS: &[&str] = &[ 11 | "C:/Users/rando/tftp", 12 | "C:/Users/rando/workspace/barberslice/tftp-server", 13 | "C:/Users/rando/workspace/barberslice/emu", 14 | ]; 15 | 16 | let args: Vec = std::env::args().collect(); 17 | 18 | if args.len() == 2 && args[1] == "clean" { 19 | /* Remove files */ 20 | for filename in &["stage1.flat", BOOTFILE_NAME] { 21 | if Path::new(filename).exists() { 22 | print!("Removing {}...\n", filename); 23 | std::fs::remove_file(filename).expect("Failed to remove file"); 24 | } 25 | } 26 | 27 | /* Clean bootloader */ 28 | print!("Cleaning bootloader...\n"); 29 | std::env::set_current_dir("bootloader").expect("Failed to chdir to bootloader"); 30 | let status = Command::new("cargo") 31 | .arg("clean") 32 | .status() 33 | .expect("Failed to invoke bootloader clean"); 34 | assert!(status.success(), "Failed to clean bootloader"); 35 | 36 | /* Clean kernel */ 37 | print!("Cleaning kernel...\n"); 38 | std::env::set_current_dir("../kernel").expect("Failed to chdir to kernel"); 39 | let status = Command::new("cargo") 40 | .arg("clean") 41 | .status() 42 | .expect("Failed to invoke kernel clean"); 43 | assert!(status.success(), "Failed to clean kernel"); 44 | 45 | print!("Cleaned\n"); 46 | return Ok(()); 47 | } else if args.len() == 2 && args[1] == "olddoc" { 48 | print!("Documenting bootloader...\n"); 49 | std::env::set_current_dir("bootloader").expect("Failed to chdir to bootloader"); 50 | let bootloader_status = Command::new("cargo") 51 | .args(&["doc", "--release"]) 52 | .env("RUSTDOCFLAGS", "--document-private-items") 53 | .status() 54 | .expect("Failed to invoke doc of bootloader"); 55 | assert!(bootloader_status.success(), "Failed to doc bootloader"); 56 | 57 | print!("Documenting kernel...\n"); 58 | std::env::set_current_dir("../kernel").expect("Failed to chdir to kernel"); 59 | let bootloader_status = Command::new("cargo") 60 | .args(&["doc", "--release"]) 61 | .env("RUSTDOCFLAGS", "--document-private-items") 62 | .status() 63 | .expect("Failed to invoke doc of kernel"); 64 | assert!(bootloader_status.success(), "Failed to doc kernel"); 65 | 66 | print!("Documenting done!\n"); 67 | return Ok(()); 68 | } 69 | 70 | /* Build stage1. This is the rust portion of the bootloader */ 71 | print!("Building stage1...\n"); 72 | std::env::set_current_dir("bootloader").expect("Failed to chdir to bootloader"); 73 | let bootloader_status = Command::new("cargo") 74 | .args(&["build", "--release"]) 75 | .status() 76 | .expect("Failed to invoke build of bootloader"); 77 | assert!(bootloader_status.success(), "Failed to build bootloader"); 78 | 79 | /* Flatten the bootloader. This will take the PE produced by the bootloader 80 | * and convert it to an in-memory loaded representation such that it can 81 | * be incbined by the stage0. 82 | */ 83 | print!("Flattening bootloader...\n"); 84 | std::env::set_current_dir("..").expect("Failed to chdir to original dir"); 85 | let flatten_status = Command::new("python") 86 | .args(&[ 87 | "flatten_pe.py", 88 | "bootloader/target/i586-pc-windows-msvc/release/stage1.exe", 89 | "stage1.flat", 90 | ]) 91 | .status() 92 | .expect("Failed to invoke flatten script"); 93 | assert!(flatten_status.success(), "Failed to flatten bootloader"); 94 | 95 | /* Assemble stage0. This produces the final bootable bootloader. This 96 | * is a tiny trampoline 16-bit assembly snippit that switches to protected 97 | * mode and jumps into the incbined flattened PE file. 98 | */ 99 | print!("Assembling bootloader...\n"); 100 | let stage0_status = Command::new("nasm") 101 | .args(&["-f", "bin", "-o", BOOTFILE_NAME, "bootloader/stage0.asm"]) 102 | .status() 103 | .expect("Failed to invoke NASM for stage0"); 104 | assert!(stage0_status.success(), "Failed to assemble bootloader"); 105 | 106 | print!("Bootloader successfully built\n"); 107 | 108 | let md = std::fs::metadata(BOOTFILE_NAME).expect("Failed to get metadata for bootloader"); 109 | assert!(md.is_file(), "Bootloader is not a file!?"); 110 | 111 | print!( 112 | "Bootloader size is {} bytes ({:8.4}%)\n", 113 | md.len(), 114 | md.len() as f64 / (32. * 1024.) * 100.0 115 | ); 116 | 117 | assert!(md.len() <= (32 * 1024), "Bootloader is too large!"); 118 | 119 | print!("Deploying bootloader...\n"); 120 | 121 | /* Attempt to deploy bootloader to various different TFTP directories. 122 | * Since I work with this codebase on multiple networks and systems, this 123 | * is just a list of the paths that work on each for deployment. It'll try 124 | * to deploy to all of them. 125 | */ 126 | for tftpd_dir in DEPLOY_PATHS { 127 | if !Path::new(tftpd_dir).exists() { 128 | continue; 129 | } 130 | 131 | print!("Deploying bootloader to {}...\n", tftpd_dir); 132 | std::fs::copy(BOOTFILE_NAME, Path::new(tftpd_dir).join(BOOTFILE_NAME)) 133 | .expect("Failed to copy file"); 134 | } 135 | 136 | print!("Bootloader successfully deployed\n"); 137 | 138 | /* Build kernel */ 139 | print!("Building kernel...\n"); 140 | 141 | std::env::set_current_dir("kernel").expect("Failed to chdir to kernel"); 142 | 143 | let kernel_status = Command::new("cargo") 144 | .args(&["build", "--release"]) 145 | .status() 146 | .expect("Failed to invoke build of kernel"); 147 | assert!(kernel_status.success(), "Failed to build kernel"); 148 | 149 | std::env::set_current_dir("..").expect("Failed to chdir to original dir"); 150 | 151 | /* Deploy kernel, same as bootloader */ 152 | for tftpd_dir in DEPLOY_PATHS { 153 | if !Path::new(tftpd_dir).exists() { 154 | continue; 155 | } 156 | 157 | print!("Deploying kernel to {}...\n", tftpd_dir); 158 | std::fs::copy(KERNEL_PATH, Path::new(tftpd_dir).join(KERNEL_NAME)) 159 | .expect("Failed to copy file"); 160 | } 161 | 162 | /* Setup snapshot files in the TFTP dir */ 163 | std::env::set_current_dir("snapshot").expect("Failed to chdir to snapshot"); 164 | let snapshot_status = Command::new("cargo") 165 | .args(&["run", "--release"]) 166 | .status() 167 | .expect("Failed to invoke build of snapshot"); 168 | assert!(snapshot_status.success(), "Failed to build snapshot"); 169 | std::env::set_current_dir("..").expect("Failed to chdir to original dir"); 170 | 171 | std::fs::copy(Path::new("snapshot").join(SNAPSHOT_REGS), Path::new("tftp-server").join(SNAPSHOT_REGS)) 172 | .expect("Failed to copy SNAPSHOT_regs"); 173 | 174 | /* Setup snapshot files in the TFTP dir */ 175 | std::env::set_current_dir("tftp-server").expect("Failed to chdir to rustftp"); 176 | let snapshot_status = Command::new("cargo") 177 | .args(&["run", "--release"]) 178 | .status() 179 | .expect("Failed to invoke build of snapshot"); 180 | assert!(snapshot_status.success(), "Failed to build ftp server"); 181 | 182 | Ok(()) 183 | } 184 | -------------------------------------------------------------------------------- /tftp-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tftp-server" 3 | version = "0.1.0" 4 | authors = ["randomlshb@gmail.com "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | packets = { path = "../shared/packets" } 11 | noodle = { path = "../shared/noodle" } 12 | lazy_static = "*" 13 | sha2 = "0.5.2" 14 | chrono = "0.4.10" 15 | -------------------------------------------------------------------------------- /tftp-server/README.md: -------------------------------------------------------------------------------- 1 | # TFTP Server 2 | 3 | Custom TFTP server that is automatically started during `cargo run` from the main repo. Should be 4 | modified to point to the IP address that it should bind to that is on the same network as the 5 | hardware that the hypervisor is running on. Defaults to running on port `9898`. 6 | 7 | Uses the current snapshot found in `../snapshot/snapshot.[dmp|phys]` to aid in analysis. 8 | 9 | ## Custom commands 10 | 11 | Two additional features have been added to the TFTP server to help the hypervisor: 12 | 13 | * `SNAPSHOT_translate_0xc3000_0x120000`: Translates the guest virtual address (second) using the CR3 (first) 14 | * `SNAPSHOT_page_0x2000`: Returns the page-aligned physical page located at the given guest physical address 15 | 16 | ## Manually run 17 | 18 | ``` 19 | cargo run --release 20 | ``` 21 | -------------------------------------------------------------------------------- /tftp-server/src/coverage.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::vec::Vec; 3 | use std::path::{Path, PathBuf}; 4 | use noodle::Deserialize; 5 | 6 | #[derive(Debug)] 7 | pub struct Coverage { 8 | /// (Input name, vector of addresses for coverage) 9 | pub inputs: HashMap>, 10 | 11 | /// All possible addresses currently seen by these inputs 12 | pub seen_coverage: HashSet, 13 | 14 | /// Mapping of addresses to all inputs that have seen that address 15 | /// 16 | /// (Address, Set of all inputs that have hit this address) 17 | pub total_coverage: HashMap> 18 | } 19 | 20 | impl Coverage { 21 | /// Populates the coverage struct from ./project/inputs and ./project/coverages 22 | pub fn from_current_dir() -> Coverage { 23 | let mut res = Coverage { 24 | inputs: HashMap::new(), 25 | seen_coverage: HashSet::new(), 26 | total_coverage: HashMap::new(), 27 | }; 28 | 29 | if !Path::new("./project").exists() { 30 | std::fs::create_dir("./project").expect("Unable to create ./project dir"); 31 | } 32 | 33 | if !Path::new("./project/inputs").exists() { 34 | std::fs::create_dir("./project/inputs").expect("Unable to create ./project/inputs dir"); 35 | } 36 | 37 | if !Path::new("./project/coverages").exists() { 38 | std::fs::create_dir("./project/coverages").expect("Unable to create ./project/coverages dir"); 39 | } 40 | 41 | for input in Path::new("./project/inputs").read_dir().expect("Couldn't open ./project/inputs") { 42 | if let Ok(entry) = input { 43 | let path = entry.path(); 44 | let filename = path.file_name().unwrap().to_str().unwrap(); 45 | let mut cov_path = PathBuf::new(); 46 | cov_path.push("."); 47 | cov_path.push("project"); 48 | cov_path.push("coverages"); 49 | cov_path.push(filename); 50 | cov_path.set_extension("coverage"); 51 | if cov_path.as_path().exists() { 52 | let data = std::fs::read(&cov_path).expect(&format!("Unable to read {:?}", cov_path)); 53 | let cov = as Deserialize>::deserialize(&mut data.as_slice()).unwrap(); 54 | res.insert(String::from(cov_path.to_str().unwrap()), cov); 55 | } else { 56 | print!("Coverage {:?} not found\n", cov_path); 57 | } 58 | } 59 | } 60 | 61 | print!("Init Coverage: inputs {} total {}\n", res.inputs.keys().len(), res.total_coverage.len()); 62 | // print!("{:#x?}\n", res); 63 | res.minset(); 64 | res 65 | 66 | } 67 | 68 | pub fn insert(&mut self, name: String, input: Vec<[u64; 2]>) { 69 | let mut new_coverage = false; 70 | // Check if this input should be added to the database 71 | // For now, only adding inputs that add strictly new coverage to the database 72 | let mut curr_cov = HashMap::new(); 73 | for entry in &input { 74 | let addr = entry[0]; 75 | let _curr_hit_count = entry[1]; 76 | let count = curr_cov.entry(addr).or_insert(0); 77 | *count += 1; 78 | 79 | // Add the current entry 80 | let found_in = self.total_coverage.entry(addr).or_insert(HashSet::new()); 81 | found_in.insert(name.clone()); 82 | 83 | if self.seen_coverage.insert(addr) { 84 | new_coverage = true; 85 | } 86 | } 87 | 88 | if new_coverage { 89 | self.inputs.insert(name, curr_cov); 90 | } 91 | } 92 | 93 | pub fn minset(&mut self) -> Vec { 94 | let mut total_coverage: HashMap> = self.total_coverage.clone(); 95 | let mut files_coverage: HashMap> = self.inputs.clone(); 96 | 97 | print!("In minset.. "); 98 | loop { 99 | if total_coverage.keys().len() == 0 { 100 | // println!("DONE"); 101 | break; 102 | } 103 | 104 | let mut single_file: Option = None; 105 | for (_symbol, files) in &total_coverage { 106 | if files.len() == 1 { 107 | // single_file = Some(files[0].clone()); 108 | single_file = Some(files.iter().next().unwrap().to_string()); 109 | break; 110 | } 111 | } 112 | 113 | if single_file.is_none() { 114 | // Choose the file with the most coverage currently 115 | let mut files_by_coverage_count = files_coverage 116 | .iter() 117 | .map(|(k, v)| (v.len(), k)) 118 | .collect::>(); 119 | files_by_coverage_count.sort(); 120 | single_file = Some(files_by_coverage_count.last().unwrap().1.to_string()); 121 | } 122 | 123 | if let Some(curr_file) = single_file { 124 | // println!("Removing entries in {:?}", curr_file); 125 | for symbol in files_coverage.get(&curr_file).unwrap().keys() { 126 | total_coverage.remove(symbol); 127 | } 128 | files_coverage.remove(&curr_file); 129 | } else { 130 | println!("Did not find a file to remove entries.."); 131 | break; 132 | } 133 | } 134 | 135 | if files_coverage.keys().len() > 0 { 136 | print!("inputs after: {:?}\n", files_coverage.keys().len()); 137 | } 138 | print!("out minset\n"); 139 | Vec::new() 140 | } 141 | } 142 | --------------------------------------------------------------------------------