├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src ├── lib.rs ├── machine.rs └── virtualizer.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .devcontainer/ 3 | .idea/ 4 | .vscode/ -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.56" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.1.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "iced-x86" 25 | version = "1.17.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "158f5204401d08f91d19176112146d75e99b3cf745092e268fa7be33e09adcec" 28 | dependencies = [ 29 | "lazy_static", 30 | "static_assertions", 31 | ] 32 | 33 | [[package]] 34 | name = "lazy_static" 35 | version = "1.4.0" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 38 | 39 | [[package]] 40 | name = "libc" 41 | version = "0.2.121" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" 44 | 45 | [[package]] 46 | name = "mach" 47 | version = "0.3.2" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" 50 | dependencies = [ 51 | "libc", 52 | ] 53 | 54 | [[package]] 55 | name = "memoffset" 56 | version = "0.6.5" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 59 | dependencies = [ 60 | "autocfg", 61 | ] 62 | 63 | [[package]] 64 | name = "num_enum" 65 | version = "0.5.7" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" 68 | dependencies = [ 69 | "num_enum_derive", 70 | ] 71 | 72 | [[package]] 73 | name = "num_enum_derive" 74 | version = "0.5.7" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" 77 | dependencies = [ 78 | "proc-macro-crate", 79 | "proc-macro2", 80 | "quote", 81 | "syn", 82 | ] 83 | 84 | [[package]] 85 | name = "proc-macro-crate" 86 | version = "1.1.3" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" 89 | dependencies = [ 90 | "thiserror", 91 | "toml", 92 | ] 93 | 94 | [[package]] 95 | name = "proc-macro2" 96 | version = "1.0.36" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 99 | dependencies = [ 100 | "unicode-xid", 101 | ] 102 | 103 | [[package]] 104 | name = "quote" 105 | version = "1.0.17" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" 108 | dependencies = [ 109 | "proc-macro2", 110 | ] 111 | 112 | [[package]] 113 | name = "region" 114 | version = "3.0.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" 117 | dependencies = [ 118 | "bitflags", 119 | "libc", 120 | "mach", 121 | "winapi", 122 | ] 123 | 124 | [[package]] 125 | name = "serde" 126 | version = "1.0.136" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" 129 | 130 | [[package]] 131 | name = "static_assertions" 132 | version = "1.1.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 135 | 136 | [[package]] 137 | name = "syn" 138 | version = "1.0.90" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" 141 | dependencies = [ 142 | "proc-macro2", 143 | "quote", 144 | "unicode-xid", 145 | ] 146 | 147 | [[package]] 148 | name = "thiserror" 149 | version = "1.0.30" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 152 | dependencies = [ 153 | "thiserror-impl", 154 | ] 155 | 156 | [[package]] 157 | name = "thiserror-impl" 158 | version = "1.0.30" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 161 | dependencies = [ 162 | "proc-macro2", 163 | "quote", 164 | "syn", 165 | ] 166 | 167 | [[package]] 168 | name = "toml" 169 | version = "0.5.8" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 172 | dependencies = [ 173 | "serde", 174 | ] 175 | 176 | [[package]] 177 | name = "unicode-xid" 178 | version = "0.2.2" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 181 | 182 | [[package]] 183 | name = "winapi" 184 | version = "0.3.9" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 187 | dependencies = [ 188 | "winapi-i686-pc-windows-gnu", 189 | "winapi-x86_64-pc-windows-gnu", 190 | ] 191 | 192 | [[package]] 193 | name = "winapi-i686-pc-windows-gnu" 194 | version = "0.4.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 197 | 198 | [[package]] 199 | name = "winapi-x86_64-pc-windows-gnu" 200 | version = "0.4.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 203 | 204 | [[package]] 205 | name = "x64-vm" 206 | version = "0.1.0" 207 | dependencies = [ 208 | "anyhow", 209 | "iced-x86", 210 | "memoffset", 211 | "num_enum", 212 | "region", 213 | ] 214 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "x64-vm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | region = "3.0.0" 10 | anyhow = "1.0.56" 11 | num_enum = "0.5.7" 12 | memoffset = "0.6" 13 | 14 | [dependencies.iced-x86] 15 | version = "1.17.0" 16 | features = ["code_asm"] 17 | 18 | [profile.release] 19 | lto = true 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A toy x86-64 virtualizing obfuscator 2 | 3 | I plan on writing a blog or something about this soon going into more detail. Mostly used to familiarize myself with Rust and determine it's compatibility within this space (it's very good). This project is currently organized as a Rust library but the intention is to just run the test cases at the moment. 4 | 5 | This super simple virtualizing obfuscator operates on fully assembled x86-64 byte arrays. You feed it fully assembled x86-64 instructions and it will disassemble them, translate them to a simple stack machine instruction set, and JIT assemble a vmenter and vmexit routine. You can then call the vmenter routine to run the virtualized instructions. 6 | 7 | Currently this project only supports virtualization of a very few select x86-64 instructions (namely the ones specifically required by the function I chose to target), but adding support for more is easy. This could easily be used as the starting point for a more fully featured virtualizing obfuscation system. 8 | 9 | The instructions I chose were for the default godbolt function compiled by MSVC and GCC: 10 | 11 | ```cpp 12 | // Type your code here, or load an example. 13 | int square(int num) { 14 | return num * num; 15 | } 16 | ``` 17 | 18 | MSVC: 19 | 20 | ```asm 21 | mov DWORD PTR [rsp+8], ecx 22 | mov eax, DWORD PTR num$[rsp] 23 | imul eax, DWORD PTR num$[rsp] 24 | ret 0 25 | ``` 26 | 27 | GCC: 28 | 29 | 30 | ```asm 31 | push rbp 32 | mov rbp, rsp 33 | mov DWORD PTR [rbp-4], edi 34 | mov eax, DWORD PTR [rbp-4] 35 | imul eax, eax 36 | pop rbp 37 | ret 38 | ``` 39 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod machine; 2 | pub mod virtualizer; 3 | 4 | #[cfg(test)] 5 | mod tests { 6 | use crate::virtualizer::virtualize; 7 | use crate::machine::disassemble; 8 | 9 | #[test] 10 | fn virtualize_and_disassemble() { 11 | const SHELLCODE: &[u8] = &[ 12 | 0x55, 0x48, 0x89, 0xE5, 0x89, 0x7D, 0xFC, 0x8B, 0x45, 0xFC, 0x0F, 0xAF, 0xC0, 0x5D, 0xC3, 13 | ]; 14 | let program = &virtualize(SHELLCODE); 15 | println!("{}", disassemble(program).unwrap()); 16 | } 17 | } -------------------------------------------------------------------------------- /src/machine.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::ptr::read_unaligned; 3 | use anyhow::Result; 4 | use memoffset::offset_of; 5 | 6 | #[repr(u8)] 7 | #[derive(Debug, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] 8 | pub enum Opcode { 9 | Const, 10 | Load, 11 | Store, 12 | Add, 13 | Mul, 14 | Vmctx, 15 | Vmexit, 16 | } 17 | 18 | #[repr(u8)] 19 | #[derive(num_enum::IntoPrimitive)] 20 | pub enum Register { 21 | Rax, 22 | Rcx, 23 | Rdx, 24 | Rbx, 25 | Rsp, 26 | Rbp, 27 | Rsi, 28 | Rdi, 29 | R8, 30 | R9, 31 | R10, 32 | R11, 33 | R12, 34 | R13, 35 | R14, 36 | R15, 37 | } 38 | 39 | impl From for Register { 40 | fn from(reg: iced_x86::Register) -> Self { 41 | match reg { 42 | iced_x86::Register::RAX => Register::Rax, 43 | iced_x86::Register::RCX => Register::Rcx, 44 | iced_x86::Register::RDX => Register::Rdx, 45 | iced_x86::Register::RBX => Register::Rbx, 46 | iced_x86::Register::RSP => Register::Rsp, 47 | iced_x86::Register::RBP => Register::Rbp, 48 | iced_x86::Register::RSI => Register::Rsi, 49 | iced_x86::Register::RDI => Register::Rdi, 50 | iced_x86::Register::R8 => Register::R8, 51 | iced_x86::Register::R9 => Register::R9, 52 | iced_x86::Register::R10 => Register::R10, 53 | iced_x86::Register::R11 => Register::R11, 54 | iced_x86::Register::R12 => Register::R12, 55 | iced_x86::Register::R13 => Register::R13, 56 | iced_x86::Register::R14 => Register::R14, 57 | iced_x86::Register::R15 => Register::R15, 58 | iced_x86::Register::EAX => Register::Rax, 59 | iced_x86::Register::ECX => Register::Rcx, 60 | iced_x86::Register::EDX => Register::Rdx, 61 | iced_x86::Register::EBX => Register::Rbx, 62 | iced_x86::Register::ESP => Register::Rsp, 63 | iced_x86::Register::EBP => Register::Rbp, 64 | iced_x86::Register::ESI => Register::Rsi, 65 | iced_x86::Register::EDI => Register::Rdi, 66 | iced_x86::Register::R8D => Register::R8, 67 | iced_x86::Register::R9D => Register::R9, 68 | iced_x86::Register::R10D => Register::R10, 69 | iced_x86::Register::R11D => Register::R11, 70 | iced_x86::Register::R12D => Register::R12, 71 | iced_x86::Register::R13D => Register::R13, 72 | iced_x86::Register::R14D => Register::R14, 73 | iced_x86::Register::R15D => Register::R15, 74 | _ => panic!("unsupported register"), 75 | } 76 | } 77 | } 78 | 79 | #[repr(C)] 80 | pub struct Machine { 81 | pc: *const u8, 82 | sp: *mut u64, 83 | pub regs: [u64; 16], 84 | program: Vec, 85 | vmstack: Vec, 86 | cpustack: Vec, 87 | pub vmenter: region::Allocation, 88 | vmexit: region::Allocation, 89 | } 90 | 91 | impl Machine { 92 | #[cfg(target_env = "msvc")] 93 | #[allow(clippy::fn_to_numeric_cast)] 94 | pub fn new(program: &[u8]) -> Result { 95 | use iced_x86::code_asm::*; 96 | 97 | let mut m = Self { 98 | pc: std::ptr::null(), 99 | sp: std::ptr::null_mut(), 100 | regs: [0; 16], 101 | program: program.to_vec(), 102 | vmstack: [0; 0x1000].to_vec(), 103 | cpustack: [0; 0x1000].to_vec(), 104 | vmenter: region::alloc(region::page::size(), region::Protection::READ_WRITE_EXECUTE)?, 105 | vmexit: region::alloc(region::page::size(), region::Protection::READ_WRITE_EXECUTE)?, 106 | }; 107 | 108 | // Generate VMENTER. 109 | let regmap: &[(&AsmRegister64, u8)] = &[ 110 | (&rax, Register::Rax.into()), 111 | (&rcx, Register::Rcx.into()), 112 | (&rdx, Register::Rdx.into()), 113 | (&rbx, Register::Rbx.into()), 114 | (&rsp, Register::Rsp.into()), 115 | (&rbp, Register::Rbp.into()), 116 | (&rsi, Register::Rsi.into()), 117 | (&rdi, Register::Rdi.into()), 118 | (&r8, Register::R8.into()), 119 | (&r9, Register::R9.into()), 120 | (&r10, Register::R10.into()), 121 | (&r11, Register::R11.into()), 122 | (&r12, Register::R12.into()), 123 | (&r13, Register::R13.into()), 124 | (&r14, Register::R14.into()), 125 | (&r15, Register::R15.into()), 126 | ]; 127 | 128 | let mut a = CodeAssembler::new(64)?; 129 | 130 | a.mov(rax, &mut m as *mut _ as u64)?; 131 | 132 | // Store the GPRs 133 | for (reg, regid) in regmap.iter() { 134 | let offset = offset_of!(Machine, regs) + *regid as usize * 8; 135 | a.mov(qword_ptr(rax + offset), **reg)?; 136 | } 137 | 138 | // Switch to the VM's CPU stack. 139 | let vm_rsp = unsafe { 140 | m.cpustack 141 | .as_ptr() 142 | .add(m.cpustack.len() - 0x100 - (size_of::() * 2)) as u64 143 | }; 144 | a.mov(rsp, vm_rsp)?; 145 | 146 | a.mov(rcx, rax)?; 147 | a.mov(rax, Self::run as u64)?; 148 | a.jmp(rax)?; 149 | 150 | let insts = a.assemble(m.vmenter.as_ptr::() as u64)?; 151 | 152 | unsafe { 153 | std::ptr::copy(insts.as_ptr(), m.vmenter.as_mut_ptr(), insts.len()); 154 | }; 155 | 156 | // Generate VMEXIT. 157 | let regmap: &[(&AsmRegister64, u8)] = &[ 158 | (&rax, Register::Rax.into()), 159 | (&rdx, Register::Rdx.into()), 160 | (&rbx, Register::Rbx.into()), 161 | (&rsp, Register::Rsp.into()), 162 | (&rbp, Register::Rbp.into()), 163 | (&rsi, Register::Rsi.into()), 164 | (&rdi, Register::Rdi.into()), 165 | (&r8, Register::R8.into()), 166 | (&r9, Register::R9.into()), 167 | (&r10, Register::R10.into()), 168 | (&r11, Register::R11.into()), 169 | (&r12, Register::R12.into()), 170 | (&r13, Register::R13.into()), 171 | (&r14, Register::R14.into()), 172 | (&r15, Register::R15.into()), 173 | // Self ptr is stored in Rcx, so we will restore it last 174 | (&rcx, Register::Rcx.into()), 175 | ]; 176 | 177 | let mut a = CodeAssembler::new(64)?; 178 | 179 | // Restore the GPRs 180 | for (reg, regid) in regmap.iter() { 181 | let offset = offset_of!(Machine, regs) + *regid as usize * 8; 182 | a.mov(**reg, qword_ptr(rcx + offset))?; 183 | } 184 | 185 | a.ret()?; 186 | 187 | let insts = a.assemble(m.vmexit.as_ptr::() as u64)?; 188 | 189 | unsafe { 190 | std::ptr::copy(insts.as_ptr(), m.vmexit.as_mut_ptr(), insts.len()); 191 | }; 192 | 193 | Ok(m) 194 | } 195 | 196 | #[cfg(target_env = "gnu")] 197 | #[allow(clippy::fn_to_numeric_cast)] 198 | pub fn new(program: &[u8]) -> Result { 199 | use iced_x86::code_asm::*; 200 | 201 | let mut m = Self { 202 | pc: std::ptr::null(), 203 | sp: std::ptr::null_mut(), 204 | regs: [0; 16], 205 | program: program.to_vec(), 206 | vmstack: [0; 0x1000].to_vec(), 207 | cpustack: [0; 0x1000].to_vec(), 208 | vmenter: region::alloc(region::page::size(), region::Protection::READ_WRITE_EXECUTE)?, 209 | vmexit: region::alloc(region::page::size(), region::Protection::READ_WRITE_EXECUTE)?, 210 | }; 211 | 212 | // Generate VMENTER. 213 | let regmap: &[(&AsmRegister64, u8)] = &[ 214 | (&rax, Register::Rax.into()), 215 | (&rcx, Register::Rcx.into()), 216 | (&rdx, Register::Rdx.into()), 217 | (&rbx, Register::Rbx.into()), 218 | (&rsp, Register::Rsp.into()), 219 | (&rbp, Register::Rbp.into()), 220 | (&rsi, Register::Rsi.into()), 221 | (&rdi, Register::Rdi.into()), 222 | (&r8, Register::R8.into()), 223 | (&r9, Register::R9.into()), 224 | (&r10, Register::R10.into()), 225 | (&r11, Register::R11.into()), 226 | (&r12, Register::R12.into()), 227 | (&r13, Register::R13.into()), 228 | (&r14, Register::R14.into()), 229 | (&r15, Register::R15.into()), 230 | ]; 231 | 232 | let mut a = CodeAssembler::new(64)?; 233 | 234 | a.mov(rax, &mut m as *mut _ as u64)?; 235 | 236 | // Store the GPRs 237 | for (reg, regid) in regmap.iter() { 238 | let offset = offset_of!(Machine, regs) + *regid as usize * 8; 239 | a.mov(qword_ptr(rax + offset), **reg)?; 240 | } 241 | 242 | // Switch to the VM's CPU stack. 243 | let vm_rsp = unsafe { 244 | m.cpustack 245 | .as_ptr() 246 | .add(m.cpustack.len() - 0x100 - (size_of::() * 2)) as u64 247 | }; 248 | a.mov(rsp, vm_rsp)?; 249 | 250 | a.mov(rdi, rax)?; 251 | a.mov(rax, Self::run as u64)?; 252 | a.jmp(rax)?; 253 | 254 | let insts = a.assemble(m.vmenter.as_ptr::() as u64)?; 255 | 256 | unsafe { 257 | std::ptr::copy(insts.as_ptr(), m.vmenter.as_mut_ptr(), insts.len()); 258 | }; 259 | 260 | // Generate VMEXIT. 261 | let regmap: &[(&AsmRegister64, u8)] = &[ 262 | (&rax, Register::Rax.into()), 263 | (&rcx, Register::Rcx.into()), 264 | (&rdx, Register::Rdx.into()), 265 | (&rbx, Register::Rbx.into()), 266 | (&rsp, Register::Rsp.into()), 267 | (&rbp, Register::Rbp.into()), 268 | (&rsi, Register::Rsi.into()), 269 | (&r8, Register::R8.into()), 270 | (&r9, Register::R9.into()), 271 | (&r10, Register::R10.into()), 272 | (&r11, Register::R11.into()), 273 | (&r12, Register::R12.into()), 274 | (&r13, Register::R13.into()), 275 | (&r14, Register::R14.into()), 276 | (&r15, Register::R15.into()), 277 | (&rdi, Register::Rdi.into()), 278 | ]; 279 | 280 | let mut a = CodeAssembler::new(64)?; 281 | 282 | // Restore the GPRs 283 | for (reg, regid) in regmap.iter() { 284 | let offset = offset_of!(Machine, regs) + *regid as usize * 8; 285 | a.mov(**reg, qword_ptr(rdi + offset))?; 286 | } 287 | 288 | a.ret()?; 289 | 290 | let insts = a.assemble(m.vmexit.as_ptr::() as u64)?; 291 | 292 | unsafe { 293 | std::ptr::copy(insts.as_ptr(), m.vmexit.as_mut_ptr(), insts.len()); 294 | }; 295 | 296 | Ok(m) 297 | } 298 | 299 | #[inline(never)] 300 | unsafe fn stack_push(&mut self, value: T) { 301 | assert_eq!(size_of::() % 2, 0); 302 | // stack overflow 303 | assert_ne!(self.sp, self.vmstack.as_mut_ptr()); 304 | self.sp = self.sp.cast::().sub(1) as _; 305 | self.sp.cast::().write_unaligned(value); 306 | } 307 | 308 | #[inline(never)] 309 | unsafe fn stack_pop(&mut self) -> T { 310 | assert_eq!(size_of::() % 2, 0); 311 | let value = self.sp.cast::().read_unaligned(); 312 | *self.sp.cast::() = core::mem::zeroed(); 313 | self.sp = self.sp.cast::().add(1) as _; 314 | value 315 | } 316 | 317 | #[allow(clippy::missing_safety_doc)] 318 | pub unsafe extern "C" fn run(&mut self) { 319 | self.pc = self.program.as_ptr(); 320 | self.sp = self.vmstack.as_mut_ptr() 321 | .add((0x1000 - 0x100 - (size_of::() * 2)) / size_of::<*mut u64>()); 322 | 323 | while self.pc < self.program.as_ptr_range().end { 324 | let op = Opcode::try_from(*self.pc).unwrap(); 325 | self.pc = self.pc.add(1); 326 | 327 | match op { 328 | Opcode::Const => { 329 | self.stack_push(self.pc.cast::().read_unaligned()); 330 | self.pc = self.pc.add(size_of::()); 331 | } 332 | Opcode::Load => { 333 | let value = self.stack_pop::<*const u64>().read_unaligned(); 334 | self.stack_push::(value); 335 | }, 336 | Opcode::Store => { 337 | let target_addr = self.stack_pop::<*mut u64>(); 338 | let value = self.stack_pop::(); 339 | target_addr.write_unaligned(value); 340 | } 341 | Opcode::Add => { 342 | let (op0, op1) = (self.stack_pop::(), self.stack_pop::()); 343 | self.stack_push(op0.wrapping_add(op1)); 344 | } 345 | Opcode::Mul => { 346 | let (op0, op1) = (self.stack_pop::(), self.stack_pop::()); 347 | self.stack_push(op0.wrapping_mul(op1)); 348 | } 349 | Opcode::Vmctx => self.stack_push(self as *const _ as u64), 350 | Opcode::Vmexit => { 351 | let vmexit: extern "C" fn(&mut Machine) = 352 | std::mem::transmute(self.vmexit.as_ptr::<()>()); 353 | vmexit(self); 354 | } 355 | } 356 | } 357 | } 358 | } 359 | 360 | #[derive(Default)] 361 | pub struct Assembler { 362 | program: Vec, 363 | } 364 | 365 | impl Assembler { 366 | pub fn assemble(&self) -> Vec { 367 | self.program.clone() 368 | } 369 | 370 | pub fn const_(&mut self, v: u64) { 371 | self.emit(Opcode::Const); 372 | self.emit_u64(v); 373 | } 374 | 375 | pub fn load(&mut self) { 376 | self.emit(Opcode::Load); 377 | } 378 | 379 | pub fn store(&mut self) { 380 | self.emit(Opcode::Store); 381 | } 382 | 383 | pub fn add(&mut self) { 384 | self.emit(Opcode::Add); 385 | } 386 | 387 | pub fn mul(&mut self) { 388 | self.emit(Opcode::Mul); 389 | } 390 | 391 | pub fn vmctx(&mut self) { 392 | self.emit(Opcode::Vmctx); 393 | } 394 | 395 | pub fn vmexit(&mut self) { 396 | self.emit(Opcode::Vmexit); 397 | } 398 | 399 | fn emit(&mut self, op: Opcode) { 400 | self.program.push(op as u8); 401 | } 402 | 403 | fn emit_u64(&mut self, value: u64) { 404 | self.program.extend_from_slice(&value.to_le_bytes()); 405 | } 406 | } 407 | 408 | pub fn disassemble(program: &[u8]) -> Result { 409 | let mut s = String::new(); 410 | let mut pc = program.as_ptr(); 411 | 412 | while pc < program.as_ptr_range().end { 413 | let op = Opcode::try_from(unsafe { *pc })?; 414 | pc = unsafe { pc.add(1) }; 415 | 416 | s.push_str(format!("{:?}", op).as_str()); 417 | 418 | #[allow(clippy::single_match)] 419 | match op { 420 | Opcode::Const => unsafe { 421 | //let v = *(pc as *const u64); 422 | let v = read_unaligned(pc as *const u64); 423 | pc = pc.add(size_of::()); 424 | s.push_str(format!(" {}", v).as_str()); 425 | }, 426 | _ => {} 427 | } 428 | 429 | s.push('\n'); 430 | } 431 | 432 | Ok(s) 433 | } 434 | 435 | #[cfg(test)] 436 | mod tests { 437 | use super::*; 438 | 439 | #[test] 440 | fn assembler_and_machine() { 441 | let mut a = Assembler::default(); 442 | let x = 2u64; 443 | let y = 3u64; 444 | let z = 0u64; 445 | 446 | a.const_(&x as *const _ as u64); 447 | a.load(); 448 | a.const_(&y as *const _ as u64); 449 | a.load(); 450 | a.mul(); 451 | a.const_(&z as *const _ as u64); 452 | a.store(); 453 | 454 | unsafe { Machine::new(&a.assemble()).unwrap().run() }; 455 | assert_eq!(z, 6); 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /src/virtualizer.rs: -------------------------------------------------------------------------------- 1 | use iced_x86::{Decoder, Formatter, Instruction, Mnemonic, NasmFormatter, OpKind}; 2 | use memoffset::offset_of; 3 | 4 | use crate::machine::{Assembler, Machine, Register}; 5 | 6 | trait Asm { 7 | fn const_(&mut self, v: u64); 8 | fn load(&mut self); 9 | fn store(&mut self); 10 | fn add(&mut self); 11 | fn mul(&mut self); 12 | fn vmctx(&mut self); 13 | fn vmexit(&mut self); 14 | fn load_operand(&mut self, inst: &Instruction, operand: u32); 15 | fn store_operand(&mut self, inst: &Instruction, operand: u32); 16 | fn load_reg(&mut self, reg: iced_x86::Register); 17 | fn store_reg(&mut self, reg: iced_x86::Register); 18 | fn lea_operand(&mut self, inst: &Instruction); 19 | } 20 | 21 | /// A fun little macro that makes writing VM assembly more familiar. 22 | macro_rules! vmasm { 23 | ( 24 | $a:ident, 25 | $($inst:ident $($operand:expr),* );* $(;)* 26 | ) => {{ 27 | $( 28 | $a.$inst( 29 | $($operand),* 30 | ); 31 | )* 32 | }} 33 | } 34 | 35 | struct Virtualizer { 36 | asm: Assembler, 37 | } 38 | 39 | impl Virtualizer { 40 | pub fn new() -> Self { 41 | Self { 42 | asm: Assembler::default(), 43 | } 44 | } 45 | 46 | pub fn virtualize(&mut self, program: &[u8]) -> Vec { 47 | let mut decoder = Decoder::new(64, program, 0); 48 | 49 | for inst in &mut decoder { 50 | self.virtualize_inst(&inst); 51 | } 52 | 53 | self.asm.assemble() 54 | } 55 | 56 | fn virtualize_inst(&mut self, inst: &Instruction) { 57 | match inst.mnemonic() { 58 | Mnemonic::Mov => self.mov(inst), 59 | Mnemonic::Imul => self.imul(inst), 60 | Mnemonic::Ret => self.ret(), 61 | Mnemonic::Push => self.push(inst), 62 | Mnemonic::Pop => self.pop(inst), 63 | _ => { 64 | let mut output = String::new(); 65 | NasmFormatter::new().format(inst, &mut output); 66 | panic!("unsupported instruction: {}", output); 67 | } 68 | } 69 | } 70 | 71 | fn mov(&mut self, inst: &Instruction) { 72 | vmasm!(self, 73 | load_operand inst, 1; 74 | store_operand inst, 0; 75 | ); 76 | } 77 | 78 | fn imul(&mut self, inst: &Instruction) { 79 | assert_eq!(inst.op_count(), 2); 80 | 81 | vmasm!(self, 82 | load_operand inst, 1; 83 | load_operand inst, 0; 84 | mul; 85 | store_operand inst, 0; 86 | ); 87 | } 88 | 89 | fn ret(&mut self) { 90 | vmasm!(self, 91 | vmexit; 92 | ); 93 | } 94 | 95 | fn push(&mut self, inst: &Instruction) { 96 | use iced_x86::Register::RSP; 97 | 98 | vmasm!(self, 99 | load_reg RSP; 100 | const_ unsafe { std::mem::transmute(-8i64) }; 101 | add; 102 | store_reg RSP; 103 | 104 | load_operand inst, 0; 105 | load_reg RSP; 106 | store; 107 | ); 108 | } 109 | 110 | fn pop(&mut self, inst: &Instruction) { 111 | use iced_x86::Register::RSP; 112 | 113 | vmasm!(self, 114 | load_reg RSP; 115 | load; 116 | store_operand inst, 0; 117 | 118 | load_reg RSP; 119 | const_ 8; 120 | add; 121 | store_reg RSP; 122 | ); 123 | } 124 | } 125 | 126 | impl Asm for Virtualizer { 127 | fn const_(&mut self, v: u64) { 128 | self.asm.const_(v); 129 | } 130 | 131 | fn load(&mut self) { 132 | self.asm.load(); 133 | } 134 | 135 | fn store(&mut self) { 136 | self.asm.store(); 137 | } 138 | 139 | fn add(&mut self) { 140 | self.asm.add(); 141 | } 142 | 143 | fn mul(&mut self) { 144 | self.asm.mul(); 145 | } 146 | 147 | fn vmctx(&mut self) { 148 | self.asm.vmctx(); 149 | } 150 | 151 | fn vmexit(&mut self) { 152 | self.asm.vmexit(); 153 | } 154 | 155 | fn load_operand(&mut self, inst: &Instruction, operand: u32) { 156 | match inst.op_kind(operand) { 157 | OpKind::Register => self.load_reg(inst.op_register(operand)), 158 | OpKind::Memory => { 159 | self.lea_operand(inst); 160 | self.asm.load(); 161 | } 162 | _ => panic!("unsupported operand"), 163 | } 164 | } 165 | 166 | fn store_operand(&mut self, inst: &Instruction, operand: u32) { 167 | match inst.op_kind(operand) { 168 | OpKind::Register => self.store_reg(inst.op_register(operand)), 169 | OpKind::Memory => { 170 | self.lea_operand(inst); 171 | self.asm.store(); 172 | } 173 | _ => panic!("unsupported operand"), 174 | } 175 | } 176 | 177 | fn load_reg(&mut self, reg: iced_x86::Register) { 178 | let r: u8 = Register::from(reg).into(); 179 | let reg_offset = r as u64 * 8; 180 | self.asm.vmctx(); 181 | self.asm 182 | .const_(offset_of!(Machine, regs) as u64 + reg_offset); 183 | self.asm.add(); 184 | self.asm.load(); 185 | } 186 | 187 | fn store_reg(&mut self, reg: iced_x86::Register) { 188 | let r: u8 = Register::from(reg).into(); 189 | let reg_offset = r as u64 * 8; 190 | self.asm.vmctx(); 191 | self.asm 192 | .const_(offset_of!(Machine, regs) as u64 + reg_offset); 193 | self.asm.add(); 194 | self.asm.store(); 195 | } 196 | 197 | fn lea_operand(&mut self, inst: &Instruction) { 198 | if inst.memory_base() != iced_x86::Register::None { 199 | self.load_reg(inst.memory_base()); 200 | } 201 | 202 | if inst.memory_index() != iced_x86::Register::None { 203 | self.load_reg(inst.memory_index()); 204 | self.asm.const_(inst.memory_index_scale() as u64); 205 | self.asm.mul(); 206 | 207 | if inst.memory_base() != iced_x86::Register::None { 208 | self.asm.add(); 209 | } 210 | } 211 | 212 | self.asm.const_(inst.memory_displacement64()); 213 | 214 | if inst.memory_base() != iced_x86::Register::None 215 | || inst.memory_index() != iced_x86::Register::None 216 | { 217 | self.asm.add(); 218 | } 219 | } 220 | } 221 | 222 | pub fn virtualize(program: &[u8]) -> Vec { 223 | Virtualizer::new().virtualize(program) 224 | } 225 | 226 | #[cfg(test)] 227 | mod tests { 228 | use super::*; 229 | 230 | #[test] 231 | #[cfg(target_env = "msvc")] 232 | fn virtualizer_and_machine() { 233 | const SHELLCODE: &[u8] = &[ 234 | 0x89, 0x4c, 0x24, 0x08, 0x8b, 0x44, 0x24, 0x08, 0x0f, 0xaf, 0x44, 0x24, 0x08, 0xc2, 235 | 0x00, 0x00, 236 | ]; 237 | let m = Machine::new(&virtualize(SHELLCODE)).unwrap(); 238 | let f: extern "C" fn(i32) -> i32 = unsafe { std::mem::transmute(m.vmenter.as_ptr::<()>()) }; 239 | assert_eq!(f(2), 4); 240 | assert_eq!(f(4), 16); 241 | assert_eq!(f(6), 36); 242 | } 243 | 244 | #[test] 245 | #[cfg(target_env = "gnu")] 246 | fn virtualizer_and_machine() { 247 | const SHELLCODE: &[u8] = &[ 248 | 0x55, 0x48, 0x89, 0xE5, 0x89, 0x7D, 0xFC, 0x8B, 0x45, 0xFC, 0x0F, 0xAF, 0xC0, 0x5D, 249 | 0xC3, 250 | ]; 251 | let m = Machine::new(&virtualize(SHELLCODE)).unwrap(); 252 | let f: extern "C" fn(i32) -> i32 = unsafe { std::mem::transmute(m.vmenter.as_ptr::<()>()) }; 253 | assert_eq!(f(2), 4); 254 | assert_eq!(f(4), 16); 255 | } 256 | } 257 | --------------------------------------------------------------------------------