├── .cargo └── config.toml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Makefile ├── README.md ├── felfserv ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── qemu ├── mipsel_bios.bin └── run.sh ├── shellcode_client ├── Makefile ├── client.exe ├── server.exe └── srcs │ ├── client.c │ └── server.c └── src ├── entry.rs ├── main.rs ├── mman.rs ├── panic.rs ├── print.rs ├── rand.rs ├── syscall.rs └── syscall └── mips.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "mipsel-unknown-none" 3 | 4 | [target.mipsel-unknown-none] 5 | rustflags = ["-Clink-arg=--nmagic", "-Clink-arg=--image-base=0x13370000", "-Crelocation-model=static", "-Ccode-model=large", "-Ctarget-feature=+mips4,-mips32,-mips32r2"] 6 | 7 | [unstable] 8 | build-std = ["core", "alloc"] 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /out.felf 3 | -------------------------------------------------------------------------------- /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 = "mipstest" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mipstest" 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 | 10 | [profile.release] 11 | panic = "abort" 12 | debug = true 13 | opt-level = "z" 14 | 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cargo build --release 3 | elfloader target/mipsel-unknown-none/release/mipstest out.felf 4 | nc -w 0 127.0.0.1 5555 5 | 6 | clippy: 7 | cargo clippy -- -F clippy::missing_docs_in_private_items 8 | 9 | objdump: all 10 | objdump --demangle -d target/mipsel-unknown-none/release/mipstest 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | This is a project which allows us to run Rust "shellcode" in a MIPS 4 | environment on NT 4.0. 5 | 6 | # TL;DR 7 | 8 | ## Setup NT 9 | 10 | Install NT 4.0 MIPS in QEMU using the command you see in `qemu/run.sh`. 11 | 12 | ### Create disk and run system 13 | 14 | ``` 15 | qemu-img create –f qcow2 nt4.disk 2G 16 | ./qemu/run.sh 17 | ``` 18 | 19 | ### Setup system so you can access CD 20 | 21 | ``` 22 | Run Setup > Initialize system > Set default configuration > (choose your res) 23 | > Floppy 3.5 24 | > Second floppy: No 25 | > SCSI host ID 7 26 | ``` 27 | 28 | ### Setup ethernet address so network works in Windows 29 | 30 | ``` 31 | Run Setup > Initialize system > Set ethernet address 32 | > Pick an address (MUST BE A UNICAST MAC ADDRESS OR WINDOWS GETS MAD) 33 | > I used be2d08345673 with great success 34 | ``` 35 | 36 | ### Boot partition 37 | 38 | You must configure a small boot partition for the bootloader 39 | 40 | Go to run program: 41 | 42 | ``` 43 | cd:\mips\arcinst 44 | ``` 45 | 46 | A 5 MiB partition will do 47 | 48 | ### Install Windows 49 | 50 | ``` 51 | cd:\mips\setupldr 52 | ``` 53 | 54 | ### Configure time 55 | 56 | The time in Windows doesn't persist, set it inside Windows to something 57 | reasonable otherwise you'll get weird errors and `cl.exe` will not work so 58 | you won't be able to compile anything. 59 | 60 | ## Use the tool 61 | 62 | Deploy `server.exe` and `client.exe` to the system, then run `server.exe` 63 | inside QEMU. 64 | 65 | Install `felfserv` to your path `cd felfserv && cargo install --path .` 66 | 67 | Run `felfserv` (supplies code to guest over network and stdout prints from Rust 68 | running in guest) `felfserv 0.0.0.0:1234 ./out.felf` 69 | 70 | Run `make` to build and deploy to MIPS! 71 | 72 | Optionally run `cargo watch -- make` to get your code to re-deploy and run 73 | every time you change the Rust project. 74 | 75 | # Toolchain 76 | 77 | To use this you need to copy the `shellcode_client` into a MIPS guest build 78 | and run `server.exe` (included without any backdoors). 79 | 80 | The server binds to `0.0.0.0:42069` and waits for a TCP connection. Upon a TCP 81 | connection the server inside the guest will launch `client.exe` in the same 82 | directory in a new process, which will then connect to the host via 83 | `192.168.1.2:1234` to download the payload. 84 | 85 | The reason we have `client.exe` in a separate process is so that we can crash 86 | it without problems on the server. This provides a seamless development 87 | experience when you use something like `cargo watch -- make` which will 88 | automatically use `nc` to tickle the server, causing the client to connect 89 | to the hosted `felfserv` which then causes the payload to execute in the guest. 90 | 91 | The comms from the guest are sent to the `felfserv` over the socket that was 92 | used to download the shellcode. 93 | 94 | # Felfserv 95 | 96 | `felfserv` is a server for FELF files. You can find the FELF converter at 97 | [elfloader](https://github.com/gamozolabs/elfloader). You need to install this 98 | to your path as the `Makefile` invokes `elfloader` to convert the MIPS ELF into 99 | MIPS shellcode in the FELF file format. 100 | 101 | `felfserv` simply runs like `felfserv 0.0.0.0:1234 ./out.felf`. It will listen 102 | to connections on IP and port you specified, and when connected to will 103 | serve up the specific felf over a very basic protocol. This is what the 104 | `client.exe` in the guest communicates with to download the Rust shellcode. 105 | 106 | -------------------------------------------------------------------------------- /felfserv/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /felfserv/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 = "felfserv" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /felfserv/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "felfserv" 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 | -------------------------------------------------------------------------------- /felfserv/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::{Read, Write}; 3 | use std::path::PathBuf; 4 | use std::net::{TcpStream, TcpListener}; 5 | 6 | fn handle_client(mut sock: TcpStream, file: PathBuf) -> io::Result<()> { 7 | let payload = std::fs::read(file)?; 8 | 9 | print!("\n\nServing {} bytes to {:?}\n---------------------------------\n", 10 | payload.len(), sock.peer_addr()?); 11 | 12 | // Write the header 13 | sock.write_all(&(payload.len() as u32).to_le_bytes())?; 14 | sock.write_all(&payload)?; 15 | 16 | // Read data from user 17 | loop { 18 | let mut bytes = [0u8; 1024]; 19 | let bread = sock.read(&mut bytes)?; 20 | if bread == 0 { 21 | return Ok(()); 22 | } 23 | 24 | print!("{}", std::str::from_utf8(&bytes[..bread]).unwrap()); 25 | } 26 | } 27 | 28 | fn main() -> io::Result<()> { 29 | let args = std::env::args().collect::>(); 30 | if args.len() != 3 { 31 | print!("usage: felfserv \n"); 32 | return Ok(()); 33 | } 34 | 35 | let listener = TcpListener::bind(&args[1])?; 36 | for stream in listener.incoming() { 37 | let path = PathBuf::from(&args[2]); 38 | std::thread::spawn(move || { 39 | handle_client(stream.unwrap(), path).unwrap() 40 | }); 41 | } 42 | 43 | Ok(()) 44 | } 45 | 46 | -------------------------------------------------------------------------------- /qemu/mipsel_bios.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamozolabs/rust_mips_nt4/202856814ef42a89b8928934f9a7547da70b55af/qemu/mipsel_bios.bin -------------------------------------------------------------------------------- /qemu/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #ISO="winnt40wks_sp1_en.iso" 4 | #ISO="./Microsoft Visual C++ 4.0a RISC Edition for MIPS (ISO)/VCPP-4.00-RISC-MIPS.iso" 5 | 6 | qemu-system-mips64el \ 7 | -M magnum \ 8 | -cpu VR5432 \ 9 | -m 128 \ 10 | -net nic \ 11 | -net user,hostfwd=tcp::5555-:42069 \ 12 | -global ds1225y.filename=nvram \ 13 | -global ds1225y.size=8200 \ 14 | -L . \ 15 | -hda nt4.qcow2 \ 16 | -cdrom "$ISO" 17 | 18 | -------------------------------------------------------------------------------- /shellcode_client/Makefile: -------------------------------------------------------------------------------- 1 | all: server.exe client.exe 2 | 3 | server.exe: objs srcs/server.c 4 | cl /nologo /Foobjs/server.obj srcs/server.c wsock32.lib 5 | 6 | client.exe: objs srcs/client.c 7 | cl /nologo /Foobjs/client.obj srcs/client.c wsock32.lib 8 | 9 | objs: 10 | mkdir objs 11 | 12 | clean: 13 | -rmdir /s /q objs client.exe server.exe 2> NUL 14 | 15 | -------------------------------------------------------------------------------- /shellcode_client/client.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamozolabs/rust_mips_nt4/202856814ef42a89b8928934f9a7547da70b55af/shellcode_client/client.exe -------------------------------------------------------------------------------- /shellcode_client/server.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamozolabs/rust_mips_nt4/202856814ef42a89b8928934f9a7547da70b55af/shellcode_client/server.exe -------------------------------------------------------------------------------- /shellcode_client/srcs/client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int 6 | main(void) 7 | { 8 | SOCKET sock; 9 | WSADATA wsaData; 10 | unsigned int len; 11 | unsigned char *buf; 12 | unsigned int off = 0; 13 | struct sockaddr_in sockaddr = { 0 }; 14 | 15 | // Initialize WSA 16 | if(WSAStartup(MAKEWORD(2, 0), &wsaData)) { 17 | fprintf(stderr, "WSAStartup() error : %d", WSAGetLastError()); 18 | return -1; 19 | } 20 | 21 | // Create TCP socket 22 | sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 23 | if(sock == INVALID_SOCKET) { 24 | fprintf(stderr, "socket() error : %d", WSAGetLastError()); 25 | return -1; 26 | } 27 | 28 | sockaddr.sin_family = AF_INET; 29 | sockaddr.sin_addr.s_addr = inet_addr("192.168.1.2"); 30 | sockaddr.sin_port = htons(1234); 31 | 32 | // Connect to the socket 33 | if(connect(sock, (const struct sockaddr*)&sockaddr, sizeof(sockaddr)) == SOCKET_ERROR) { 34 | fprintf(stderr, "connect() error : %d", WSAGetLastError()); 35 | return -1; 36 | } 37 | 38 | // Read the payload length 39 | if(recv(sock, (char*)&len, sizeof(len), 0) != sizeof(len)) { 40 | fprintf(stderr, "recv() error : %d", WSAGetLastError()); 41 | return -1; 42 | } 43 | 44 | // Read the payload 45 | buf = malloc(len); 46 | if(!buf) { 47 | perror("malloc() error "); 48 | return -1; 49 | } 50 | 51 | while(off < len) { 52 | int bread; 53 | unsigned int remain = len - off; 54 | bread = recv(sock, buf + off, remain, 0); 55 | if(bread <= 0) { 56 | fprintf(stderr, "recv(pl) error : %d", WSAGetLastError()); 57 | return -1; 58 | } 59 | 60 | off += bread; 61 | } 62 | 63 | printf("Read everything %u\n", off); 64 | 65 | // FELF0001 + u64 entry + u64 base 66 | if(len < 3 * 8) { 67 | fprintf(stderr, "Invalid FELF\n"); 68 | return -1; 69 | } 70 | 71 | { 72 | char *ptr = buf; 73 | unsigned int entry, base, hi, end; 74 | 75 | if(memcmp(ptr, "FELF0001", 8)) { 76 | fprintf(stderr, "Missing FELF header\n"); 77 | return -1; 78 | } 79 | ptr += 8; 80 | 81 | entry = *((unsigned int*)ptr)++; 82 | hi = *((unsigned int*)ptr)++; 83 | if(hi) { 84 | fprintf(stderr, "Unhandled 64-bit address\n"); 85 | return -1; 86 | } 87 | 88 | base = *((unsigned int*)ptr)++; 89 | hi = *((unsigned int*)ptr)++; 90 | if(hi) { 91 | fprintf(stderr, "Unhandled 64-bit address\n"); 92 | return -1; 93 | } 94 | 95 | end = base + (len - 3 * 8); 96 | printf("Loading at %x-%x (%x) entry %x\n", base, end, end - base, entry); 97 | 98 | { 99 | unsigned int align_base = base & ~0xffff; 100 | unsigned int align_end = (end + 0xffff) & ~0xffff; 101 | char *alloc = VirtualAlloc((void*)align_base, 102 | align_end - align_base, MEM_COMMIT | MEM_RESERVE, 103 | PAGE_EXECUTE_READWRITE); 104 | printf("Alloc attempt %x-%x (%x) | Got %p\n", 105 | align_base, align_end, align_end - align_base, alloc); 106 | if(alloc != (void*)align_base) { 107 | fprintf(stderr, "VirtualAlloc() error : %d\n", GetLastError()); 108 | return -1; 109 | } 110 | 111 | // Copy in the code 112 | memcpy((void*)base, ptr, end - base); 113 | } 114 | 115 | // Jump to the entry 116 | ((void (*)(SOCKET))entry)(sock); 117 | } 118 | 119 | return 0; 120 | } 121 | -------------------------------------------------------------------------------- /shellcode_client/srcs/server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int 6 | main(void) 7 | { 8 | SOCKET sock; 9 | WSADATA wsaData; 10 | struct sockaddr_in sockaddr = { 0 }; 11 | 12 | // Initialize WSA 13 | if(WSAStartup(MAKEWORD(2, 0), &wsaData)) { 14 | fprintf(stderr, "WSAStartup() error : %d\n", WSAGetLastError()); 15 | return -1; 16 | } 17 | 18 | // Create TCP socket 19 | sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 20 | if(sock == INVALID_SOCKET) { 21 | fprintf(stderr, "socket() error : %d\n", WSAGetLastError()); 22 | return -1; 23 | } 24 | 25 | sockaddr.sin_family = AF_INET; 26 | sockaddr.sin_addr.s_addr = inet_addr("0.0.0.0"); 27 | sockaddr.sin_port = htons(42069); 28 | 29 | if(bind(sock, (const struct sockaddr*)&sockaddr, sizeof(sockaddr)) == SOCKET_ERROR) { 30 | fprintf(stderr, "bind() error : %d\n", WSAGetLastError()); 31 | return -1; 32 | } 33 | 34 | // Listen for connections 35 | if(listen(sock, 5) == SOCKET_ERROR) { 36 | fprintf(stderr, "listen() error : %d\n", WSAGetLastError()); 37 | return -1; 38 | } 39 | 40 | // Wait for a client 41 | for( ; ; ) { 42 | STARTUPINFO si = { 0 }; 43 | PROCESS_INFORMATION pi = { 0 }; 44 | SOCKET client = accept(sock, NULL, NULL); 45 | 46 | // Upon getting a TCP connection, just start 47 | // a separate client process. This way the 48 | // client can crash and burn and this server 49 | // stays running just fine. 50 | CreateProcess( 51 | "client.exe", 52 | NULL, 53 | NULL, 54 | NULL, 55 | FALSE, 56 | CREATE_NEW_CONSOLE, 57 | NULL, 58 | NULL, 59 | &si, 60 | &pi 61 | ); 62 | 63 | // We don't even transfer data, we just care about 64 | // the connection kicking off a client. 65 | closesocket(client); 66 | } 67 | 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /src/entry.rs: -------------------------------------------------------------------------------- 1 | //! Main program entry point 2 | 3 | use crate::syscall::{exit, Handle}; 4 | 5 | /// Exported entry point 6 | #[no_mangle] 7 | pub unsafe extern fn __start(socket: Handle) -> ! { 8 | // Register the socket with the print handler so that we can print! 9 | crate::print::register_socket(socket); 10 | 11 | // Invoke main 12 | if let Err(x) = crate::main() { 13 | panic!("main exited with error: {:?}", x); 14 | } 15 | 16 | // Exit the program 17 | exit(0); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! Main program entry point for Rust 2 | 3 | #![feature(asm, naked_functions, asm_experimental_arch)] 4 | #![feature(default_alloc_error_handler, new_uninit, asm_const)] 5 | #![no_std] 6 | #![no_main] 7 | 8 | #[allow(unused_imports)] 9 | #[macro_use] 10 | extern crate alloc; 11 | 12 | #[macro_use] pub mod print; 13 | mod entry; 14 | mod mman; 15 | mod rand; 16 | mod panic; 17 | mod syscall; 18 | 19 | use alloc::vec::Vec; 20 | use crate::rand::Rng; 21 | 22 | /// Worker thread for fuzzing 23 | fn worker(id: usize) { 24 | // Create an RNG 25 | let rng = Rng::new(0xe06fc2cdf7b80594 + id as u64); 26 | 27 | loop { 28 | unsafe { 29 | syscall::syscall9( 30 | rng.next() as usize, 31 | rng.next() as usize, 32 | rng.next() as usize, 33 | rng.next() as usize, 34 | rng.next() as usize, 35 | rng.next() as usize, 36 | rng.next() as usize, 37 | rng.next() as usize, 38 | rng.next() as usize, 39 | rng.next() as usize); 40 | } 41 | } 42 | } 43 | 44 | /// Run the fuzzer on multiple threads! 45 | fn fuzz() { 46 | // Thread handlers for workers 47 | let mut workers = Vec::new(); 48 | 49 | // Spawn worker threads 50 | for ii in 0..8 { 51 | workers.push( 52 | syscall::spawn(move || worker(ii)).expect("Failed to spawn thread") 53 | ); 54 | } 55 | 56 | // Wait for all threads to exit 57 | for thr in workers { 58 | thr.join().expect("Failed to join thread"); 59 | } 60 | } 61 | 62 | fn main() -> Result<(), ()> { 63 | fuzz(); 64 | Ok(()) 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/mman.rs: -------------------------------------------------------------------------------- 1 | //! Memory manager 2 | 3 | use alloc::alloc::{Layout, GlobalAlloc}; 4 | 5 | /// Implementation of the global allocator 6 | struct GlobalAllocator; 7 | 8 | /// Global allocator object 9 | #[global_allocator] 10 | static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator; 11 | 12 | unsafe impl GlobalAlloc for GlobalAllocator { 13 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 14 | crate::syscall::mmap(0, layout.size()).unwrap_or(core::ptr::null_mut()) 15 | } 16 | 17 | unsafe fn dealloc(&self, addr: *mut u8, _layout: Layout) { 18 | crate::syscall::munmap(addr as usize) 19 | .expect("Failed to deallocate memory"); 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/panic.rs: -------------------------------------------------------------------------------- 1 | //! Panic handler 2 | 3 | /// Panic handler 4 | #[panic_handler] 5 | fn panic_handler(pi: &core::panic::PanicInfo) -> ! { 6 | println!("{}", pi); 7 | crate::syscall::exit(!0); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/print.rs: -------------------------------------------------------------------------------- 1 | //! Print and debug routines 2 | 3 | use crate::syscall::Handle; 4 | 5 | /// Classic `print!()` macro 6 | #[macro_export] 7 | macro_rules! print { 8 | ($($arg:tt)*) => { 9 | let _ = core::fmt::Write::write_fmt( 10 | &mut $crate::print::Writer, core::format_args!($($arg)*)); 11 | } 12 | } 13 | 14 | /// Classic `println!()` macro 15 | #[macro_export] 16 | macro_rules! println { 17 | ($($arg:tt)*) => { 18 | print!($($arg)*); 19 | print!("\r\n"); 20 | } 21 | } 22 | 23 | /// Classic `dbg!()` macro 24 | #[macro_export] 25 | macro_rules! dbg { 26 | // NOTE: We cannot use `concat!` to make a static string as a format 27 | // argument of `println!` because `file!` could contain a `{` or `$val` 28 | // expression could be a block (`{ .. }`), in which case the `println!` 29 | // will be malformed. 30 | () => { 31 | $crate::println!("[{}:{}]", file!(), line!()) 32 | }; 33 | ($val:expr $(,)?) => { 34 | // Use of `match` here is intentional because it affects the lifetimes 35 | // of temporaries - https://stackoverflow.com/a/48732525/1063961 36 | match $val { 37 | tmp => { 38 | $crate::println!("[{}:{}] {} = {:#?}", 39 | file!(), line!(), stringify!($val), &tmp); 40 | tmp 41 | } 42 | } 43 | }; 44 | ($($val:expr),+ $(,)?) => { 45 | ($($crate::dbg!($val)),+,) 46 | }; 47 | } 48 | 49 | /// Writer structure that simply implements [`core::fmt::Write`] such that we 50 | /// can use `write_fmt` in our [`print!`] 51 | pub struct Writer; 52 | 53 | impl core::fmt::Write for Writer { 54 | fn write_str(&mut self, s: &str) -> core::fmt::Result { 55 | let _ = crate::syscall::write(unsafe { &SOCKET }, s); 56 | 57 | Ok(()) 58 | } 59 | } 60 | 61 | /// The socket handle 62 | static mut SOCKET: Handle = unsafe { Handle::from_raw(0) }; 63 | 64 | /// Register the initial socket 65 | pub(super) unsafe fn register_socket(socket: Handle) { 66 | core::ptr::write(&mut SOCKET, socket); 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/rand.rs: -------------------------------------------------------------------------------- 1 | //! Xorshift64 2 | 3 | use core::cell::Cell; 4 | 5 | /// Xorshift64 implementation 6 | pub struct Rng(Cell); 7 | 8 | impl Rng { 9 | /// Create a seeded RNG 10 | pub const fn new(seed: u64) -> Self { 11 | Self(Cell::new(seed)) 12 | } 13 | 14 | /// Get the next RNG value 15 | pub fn next(&self) -> u64 { 16 | let mut seed = self.0.get(); 17 | seed ^= seed << 13; 18 | seed ^= seed >> 17; 19 | seed ^= seed << 43; 20 | self.0.set(seed); 21 | seed 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/syscall.rs: -------------------------------------------------------------------------------- 1 | //! Generic syscalls for Windows, everything in here should be architecture 2 | //! agnostic. 3 | 4 | #[cfg(target_arch = "mips")] pub mod mips; 5 | #[cfg(target_arch = "mips")] pub use mips::*; 6 | 7 | use core::mem::MaybeUninit; 8 | use core::ptr::addr_of_mut; 9 | use core::cell::UnsafeCell; 10 | use alloc::sync::Arc; 11 | 12 | /// Attempted a syscall which may have failed with `NtStatus` 13 | pub type Result = core::result::Result; 14 | 15 | /// `NTSTATUS` 16 | #[derive(Clone, Copy, Debug, Default)] 17 | #[repr(transparent)] 18 | pub struct NtStatus(pub u32); 19 | 20 | impl NtStatus { 21 | /// Returns `true` if the status was successful 22 | pub fn success(self) -> bool { 23 | (self.0 as i32) >= 0 24 | } 25 | } 26 | 27 | /// `HANDLE` 28 | #[derive(Debug)] 29 | #[repr(transparent)] 30 | pub struct Handle(usize); 31 | 32 | impl Handle { 33 | /// Create a handle out of thin air 34 | pub const unsafe fn from_raw(raw: usize) -> Self { 35 | Self(raw) 36 | } 37 | } 38 | 39 | impl Drop for Handle { 40 | fn drop(&mut self) { 41 | close(self).expect("Failed to close handle"); 42 | } 43 | } 44 | 45 | /// `IO_STATUS_BLOCK` 46 | #[derive(Default)] 47 | #[repr(C)] 48 | pub struct IoStatusBlock { 49 | /// Status code from the command 50 | pub status: NtStatus, 51 | 52 | /// Request-dependent value about the request 53 | pub information: usize, 54 | } 55 | 56 | /// Allocate virtual memory in the current process 57 | pub fn mmap(mut addr: usize, mut size: usize) -> Result<*mut u8> { 58 | /// Commit memory 59 | const MEM_COMMIT: u32 = 0x1000; 60 | 61 | /// Reserve memory range 62 | const MEM_RESERVE: u32 = 0x2000; 63 | 64 | /// Readable and writable memory 65 | const PAGE_READWRITE: u32 = 0x4; 66 | 67 | // Perform syscall 68 | let status = NtStatus(unsafe { 69 | syscall6( 70 | // [in] HANDLE ProcessHandle, 71 | !0, 72 | 73 | // [in, out] PVOID *BaseAddress, 74 | addr_of_mut!(addr) as usize, 75 | 76 | // [in] ULONG_PTR ZeroBits, 77 | 0, 78 | 79 | // [in, out] PSIZE_T RegionSize, 80 | addr_of_mut!(size) as usize, 81 | 82 | // [in] ULONG AllocationType, 83 | (MEM_COMMIT | MEM_RESERVE) as usize, 84 | 85 | // [in] ULONG Protect 86 | PAGE_READWRITE as usize, 87 | 88 | // Syscall ID 89 | Syscall::AllocateVirtualMemory as usize, 90 | ) 91 | } as u32); 92 | 93 | // If success, return allocation otherwise return status 94 | if status.success() { 95 | Ok(addr as *mut u8) 96 | } else { 97 | Err(status) 98 | } 99 | } 100 | 101 | /// Release memory range 102 | const MEM_RELEASE: u32 = 0x8000; 103 | 104 | /// De-allocate virtual memory in the current process 105 | pub unsafe fn munmap(mut addr: usize) -> Result<()> { 106 | // Region size 107 | let mut size = 0usize; 108 | 109 | // Perform syscall 110 | let status = NtStatus(syscall4( 111 | // [in] HANDLE ProcessHandle, 112 | !0, 113 | 114 | // [in, out] PVOID *BaseAddress, 115 | addr_of_mut!(addr) as usize, 116 | 117 | // [in, out] PSIZE_T RegionSize, 118 | addr_of_mut!(size) as usize, 119 | 120 | // [in] ULONG AllocationType, 121 | MEM_RELEASE as usize, 122 | 123 | // Syscall ID 124 | Syscall::FreeVirtualMemory as usize, 125 | ) as u32); 126 | 127 | // Return error on error 128 | if status.success() { 129 | Ok(()) 130 | } else { 131 | Err(status) 132 | } 133 | } 134 | 135 | /// Write to a file 136 | pub fn write(fd: &Handle, bytes: impl AsRef<[u8]>) -> Result { 137 | let mut offset = 0u64; 138 | let mut iosb = IoStatusBlock::default(); 139 | 140 | // Perform syscall 141 | let status = NtStatus(unsafe { 142 | syscall9( 143 | // [in] HANDLE FileHandle 144 | fd.0, 145 | 146 | // [in, optional] HANDLE Event 147 | 0, 148 | 149 | // [in, optional] PIO_APC_ROUTINE ApcRoutine, 150 | 0, 151 | 152 | // [in, optional] PVOID ApcContext, 153 | 0, 154 | 155 | // [out] PIO_STATUS_BLOCK IoStatusBlock, 156 | addr_of_mut!(iosb) as usize, 157 | 158 | // [in] PVOID Buffer, 159 | bytes.as_ref().as_ptr() as usize, 160 | 161 | // [in] ULONG Length, 162 | bytes.as_ref().len(), 163 | 164 | // [in, optional] PLARGE_INTEGER ByteOffset, 165 | addr_of_mut!(offset) as usize, 166 | 167 | // [in, optional] PULONG Key 168 | 0, 169 | 170 | // Syscall number 171 | Syscall::WriteFile as usize) 172 | } as u32); 173 | 174 | // If success, return number of bytes written, otherwise return error 175 | if status.success() { 176 | Ok(iosb.information) 177 | } else { 178 | Err(status) 179 | } 180 | } 181 | 182 | /// Handle to a thread and a pointer to the return value 183 | pub struct JoinHandle(Handle, Arc>>); 184 | 185 | impl JoinHandle { 186 | /// Block until the thread exits and return the return value from the 187 | /// thread. 188 | pub fn join(self) -> Result { 189 | // Wait for thread to exit 190 | wait(self.0)?; 191 | 192 | // Try to unwrap the `Arc`, this is only possible if the thread has 193 | // exited (thus, there is only one reference to the `Arc`). This 194 | // atomically double-checks that not only has the thread exited because 195 | // the `wait()` above succeeded, but also the `Arc` was dropped inside 196 | // the thread 197 | let usc = Arc::try_unwrap(self.1).map_err(|_| ()).unwrap(); 198 | 199 | // Now that we have exclusive access to the return value, we can get 200 | // the inner part of the `UnsafeCell` and assume it is initialized. 201 | // It is impossible for the `Arc` in the thread to have been dropped 202 | // without initializing the value, thus this is safe. 203 | let inner = usc.into_inner(); 204 | 205 | // Assume init! 206 | Ok(unsafe { inner.assume_init() }) 207 | } 208 | } 209 | 210 | /// Block on a handle forever 211 | pub fn wait(handle: Handle) -> Result<()> { 212 | let status = NtStatus(unsafe { 213 | syscall3( 214 | // [in] HANDLE Handle, 215 | handle.0 as usize, 216 | 217 | // [in] BOOLEAN Alertable, 218 | 0, 219 | 220 | // [in, optional] PLARGE_INTEGER Timeout 221 | 0, 222 | Syscall::WaitForSingleObject as usize, 223 | ) 224 | } as u32); 225 | 226 | // Convert error to Rust error 227 | if status.success() { 228 | Ok(()) 229 | } else { 230 | Err(status) 231 | } 232 | } 233 | 234 | /// Close a handle 235 | fn close(handle: &Handle) -> Result<()> { 236 | // Close the handle 237 | let status = NtStatus(unsafe { 238 | syscall1(handle.0, Syscall::Close as usize) 239 | } as u32); 240 | 241 | // Convert error to Rust error 242 | if status.success() { 243 | Ok(()) 244 | } else { 245 | Err(status) 246 | } 247 | } 248 | 249 | /// Exit the current thread with `code` as the exit status 250 | #[allow(dead_code)] 251 | pub fn exit_thread(code: usize) -> ! { 252 | unsafe { 253 | syscall2((-2isize) as usize, code, Syscall::TerminateThread as usize); 254 | core::hint::unreachable_unchecked(); 255 | } 256 | } 257 | 258 | /// Exit the current process with `code` as the exit status 259 | pub fn exit(code: usize) -> ! { 260 | unsafe { 261 | syscall2(!0, code, Syscall::TerminateProcess as usize); 262 | core::hint::unreachable_unchecked(); 263 | } 264 | } 265 | 266 | -------------------------------------------------------------------------------- /src/syscall/mips.rs: -------------------------------------------------------------------------------- 1 | //! MIPS NT syscall conventions 2 | //! Ref: https://devblogs.microsoft.com/oldnewthing/20180417-00/?p=98525 3 | //! 4 | //! Very similar to MIPS o32 convention, with interleaved floats and skipped 5 | //! integer registers when floats are used. 6 | //! 7 | //! These wrappers use Rust naked functions and the fact that the o32 Rust-emit 8 | //! C ABI matches the Windows kernel ABI. This allows us to simply move the 9 | //! last parameter (syscall number in our Rust bindings) into the correct 10 | //! syscall ID register `$v0` and pass through all existing parameters. This 11 | //! decreases the amount of overhead and means we don't have to worry about 12 | //! things like register homing and stack alignment as those are handled for 13 | //! us. 14 | //! 15 | //! It also may be a bit confusing why we don't `ret` from the `syscall`, this 16 | //! is because `syscall` on MIPS actually returns to the user-provided `$lr`, 17 | //! meaning the `$lr` is set from the call to the naked function, thus the 18 | //! syscall directly returns back to the caller of the `syscall*()` wrapper 19 | //! function rather than to the instruction following the `syscall` 20 | 21 | use core::mem::MaybeUninit; 22 | use core::ptr::{addr_of, addr_of_mut}; 23 | use core::cell::UnsafeCell; 24 | use alloc::sync::Arc; 25 | use alloc::boxed::Box; 26 | use super::{NtStatus, Result, Handle, JoinHandle, MEM_RELEASE}; 27 | 28 | /// Syscall numbers 29 | #[allow(dead_code)] 30 | #[repr(usize)] 31 | pub enum Syscall { 32 | /// NtAllocateVirtualMemory() 33 | AllocateVirtualMemory = 0xa, 34 | 35 | /// NtClose() 36 | Close = 0xf, 37 | 38 | /// NtCreateThread() 39 | CreateThread = 0x24, 40 | 41 | /// NtFreeVirtualMemory() 42 | FreeVirtualMemory = 0x3a, 43 | 44 | /// NtOpenFile() 45 | OpenFile = 0x4f, 46 | 47 | /// NtTerminateProcess() 48 | TerminateProcess = 0xba, 49 | 50 | /// NtTerminateThread() 51 | TerminateThread = 0xbb, 52 | 53 | /// NtWaitForSingleObject() 54 | WaitForSingleObject = 0xc4, 55 | 56 | /// NtWriteFile() 57 | WriteFile = 0xc7, 58 | } 59 | 60 | /// Alignment structure for [`Context`] 61 | #[repr(C)] 62 | pub union ContextAlign { 63 | /// Argument? 64 | argument: u128, 65 | } 66 | 67 | /// Union of different bitness contexts 68 | #[repr(C)] 69 | pub union ContextBits { 70 | /// 32-bit context 71 | pub bits32: Context32, 72 | 73 | /// 64-bit context 74 | pub bits64: Context64, 75 | } 76 | 77 | /// Bitness-agnostic `_CONTEXT` 78 | #[repr(C)] 79 | pub struct Context { 80 | /// Alignment structure 81 | _align: ContextAlign, 82 | 83 | /// Contexts 84 | pub context: ContextBits, 85 | } 86 | 87 | /// 32-bit `_CONTEXT` 88 | #[derive(Clone, Copy)] 89 | #[repr(C)] 90 | pub struct Context32 { 91 | /// Floating point registers 92 | pub fp: [u32; 32], 93 | 94 | /// Integer registers 95 | pub int: [u32; 34], 96 | 97 | /// Status register? 98 | pub fsr: u32, 99 | 100 | /// Fault instruction continuation address 101 | pub fir: u32, 102 | 103 | /// Processor status 104 | pub psr: u32, 105 | 106 | /// Context update flags 107 | pub flags: u32, 108 | } 109 | 110 | /// 64-bit `_CONTEXT` 111 | #[derive(Clone, Copy)] 112 | #[repr(C)] 113 | pub struct Context64 { 114 | /// Floating point registers 115 | pub fp: [u64; 32], 116 | 117 | /// Filler 118 | pub _fill1: u32, 119 | 120 | /// Filler 121 | pub _fill2: u32, 122 | 123 | /// Status register? 124 | pub fsr: u32, 125 | 126 | /// Fault instruction continuation address 127 | pub fir: u32, 128 | 129 | /// Processor status 130 | pub psr: u32, 131 | 132 | /// Context update flags 133 | pub flags: u32, 134 | 135 | /// Integer registers 136 | pub int: [u64; 34], 137 | } 138 | 139 | /// 0-argument syscall 140 | #[allow(unused)] 141 | #[naked] 142 | pub unsafe extern fn syscall0(id: usize) -> usize { 143 | asm!(r#" 144 | move $v0, $a0 145 | syscall 146 | "#, options(noreturn)); 147 | } 148 | 149 | /// 1-argument syscall 150 | #[allow(unused)] 151 | #[naked] 152 | pub unsafe extern fn syscall1(_: usize, id: usize) -> usize { 153 | asm!(r#" 154 | move $v0, $a1 155 | syscall 156 | "#, options(noreturn)); 157 | } 158 | 159 | /// 2-argument syscall 160 | #[allow(unused)] 161 | #[naked] 162 | pub unsafe extern fn syscall2(_: usize, _: usize, id: usize) -> usize { 163 | asm!(r#" 164 | move $v0, $a2 165 | syscall 166 | "#, options(noreturn)); 167 | } 168 | 169 | /// 3-argument syscall 170 | #[allow(unused)] 171 | #[naked] 172 | pub unsafe extern fn syscall3(_: usize, _: usize, _: usize, id: usize) 173 | -> usize { 174 | asm!(r#" 175 | move $v0, $a3 176 | syscall 177 | "#, options(noreturn)); 178 | } 179 | 180 | /// 4-argument syscall 181 | #[allow(unused)] 182 | #[naked] 183 | pub unsafe extern fn syscall4(_: usize, _: usize, _: usize, _: usize, 184 | id: usize) -> usize { 185 | asm!(r#" 186 | lw $v0, 0x10($sp) 187 | syscall 188 | "#, options(noreturn)); 189 | } 190 | 191 | /// 5-argument syscall 192 | #[allow(unused)] 193 | #[naked] 194 | pub unsafe extern fn syscall5(_: usize, _: usize, _: usize, _: usize, 195 | _: usize, id: usize) -> usize { 196 | asm!(r#" 197 | lw $v0, 0x14($sp) 198 | syscall 199 | "#, options(noreturn)); 200 | } 201 | 202 | /// 6-argument syscall 203 | #[allow(unused)] 204 | #[naked] 205 | pub unsafe extern fn syscall6(_: usize, _: usize, _: usize, _: usize, 206 | _: usize, _: usize, id: usize) -> usize { 207 | asm!(r#" 208 | lw $v0, 0x18($sp) 209 | syscall 210 | "#, options(noreturn)); 211 | } 212 | 213 | /// 7-argument syscall 214 | #[allow(unused)] 215 | #[naked] 216 | pub unsafe extern fn syscall7(_: usize, _: usize, _: usize, _: usize, 217 | _: usize, _: usize, _: usize, id: usize) -> usize { 218 | asm!(r#" 219 | lw $v0, 0x1c($sp) 220 | syscall 221 | "#, options(noreturn)); 222 | } 223 | 224 | /// 8-argument syscall 225 | #[allow(unused)] 226 | #[naked] 227 | pub unsafe extern fn syscall8(_: usize, _: usize, _: usize, _: usize, 228 | _: usize, _: usize, _: usize, _: usize, id: usize) -> usize { 229 | asm!(r#" 230 | lw $v0, 0x20($sp) 231 | syscall 232 | "#, options(noreturn)); 233 | } 234 | 235 | /// 9-argument syscall 236 | #[allow(unused)] 237 | #[naked] 238 | pub unsafe extern fn syscall9(_: usize, _: usize, _: usize, _: usize, 239 | _: usize, _: usize, _: usize, _: usize, _: usize, id: usize) 240 | -> usize { 241 | asm!(r#" 242 | lw $v0, 0x24($sp) 243 | syscall 244 | "#, options(noreturn)); 245 | } 246 | 247 | /// Spawn a thread 248 | /// 249 | /// MIPS specific due to some inline assembly as well as MIPS-specific context 250 | /// structure creation. 251 | pub fn spawn(f: F) -> Result> 252 | where F: FnOnce() -> T, 253 | F: Send + 'static, 254 | T: Send + 'static { 255 | // Holder for returned client handle 256 | let mut handle = 0usize; 257 | 258 | // Placeholder for returned client ID 259 | let mut client_id = [0usize; 2]; 260 | 261 | // Create a new context 262 | let mut context: Context = unsafe { core::mem::zeroed() }; 263 | 264 | // Allocate and leak a stack for the thread 265 | let stack = vec![0u8; 4096].leak(); 266 | 267 | // Initial TEB, maybe some stack stuff in here!? 268 | let initial_teb = [0u32; 5]; 269 | 270 | /// External thread entry point 271 | extern fn entry(func: *mut F, 272 | ret: *mut UnsafeCell>, 273 | mut stack: usize) -> ! 274 | where F: FnOnce() -> T, 275 | F: Send + 'static, 276 | T: Send + 'static { 277 | // Create a scope so that we drop `Box` and `Arc` 278 | { 279 | // Re-box the FFI'd type 280 | let func: Box = unsafe { 281 | Box::from_raw(func) 282 | }; 283 | 284 | // Re-box the return type 285 | let ret: Arc>> = unsafe { 286 | Arc::from_raw(ret) 287 | }; 288 | 289 | // Call the closure and save the return 290 | unsafe { (&mut *ret.get()).write(func()); } 291 | } 292 | 293 | // Region size 294 | let mut rsize = 0usize; 295 | 296 | // Free the stack and then exit the thread. We do this in one assembly 297 | // block to ensure we don't touch any stack memory during this stage 298 | // as we are freeing the stack. 299 | unsafe { 300 | asm!(r#" 301 | // Set the link register 302 | jal 2f 303 | 304 | // Exit thread 305 | jal 3f 306 | break 307 | 308 | 2: 309 | // NtFreeVirtualMemory() 310 | li $v0, {free} 311 | syscall 312 | 313 | 3: 314 | // NtTerminateThread() 315 | li $v0, {terminate} 316 | li $a0, -2 // GetCurrentThread() 317 | li $a1, 0 // exit code 318 | syscall 319 | 320 | "#, terminate = const Syscall::TerminateThread as usize, 321 | free = const Syscall::FreeVirtualMemory as usize, 322 | in("$4") !0usize, 323 | in("$5") addr_of_mut!(stack), 324 | in("$6") addr_of_mut!(rsize), 325 | in("$7") MEM_RELEASE, options(noreturn)); 326 | } 327 | } 328 | 329 | let rbox = unsafe { 330 | /// Control context 331 | const CONTEXT_CONTROL: u32 = 1; 332 | 333 | /// Floating point context 334 | const CONTEXT_FLOATING_POINT: u32 = 2; 335 | 336 | /// Integer context 337 | const CONTEXT_INTEGER: u32 = 4; 338 | 339 | // Set the flags for the registers we want to control 340 | context.context.bits64.flags = 341 | CONTEXT_CONTROL | CONTEXT_FLOATING_POINT | CONTEXT_INTEGER; 342 | 343 | // Thread entry point 344 | context.context.bits64.fir = entry:: as usize as u32; 345 | 346 | // Set `$a0` argument 347 | let cbox: *mut F = Box::into_raw(Box::new(f)); 348 | context.context.bits64.int[4] = cbox as u64; 349 | 350 | // Create return storage in `$a1` 351 | let rbox: Arc>> = 352 | Arc::new(UnsafeCell::new(MaybeUninit::uninit())); 353 | context.context.bits64.int[5] = Arc::into_raw(rbox.clone()) as u64; 354 | 355 | // Pass in stack in `$a2` 356 | context.context.bits64.int[6] = stack.as_mut_ptr() as u64; 357 | 358 | // Set the 64-bit `$sp` to the end of the stack 359 | context.context.bits64.int[29] = 360 | stack.as_mut_ptr() as u64 + stack.len() as u64; 361 | 362 | rbox 363 | }; 364 | 365 | // Create the thread 366 | let status = NtStatus(unsafe { 367 | syscall8( 368 | // OUT PHANDLE ThreadHandle 369 | addr_of_mut!(handle) as usize, 370 | 371 | // IN ACCESS_MASK DesiredAccess 372 | 0x1f03ff, 373 | 374 | // IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL 375 | 0, 376 | 377 | // IN HANDLE ProcessHandle 378 | !0, 379 | 380 | // OUT PCLIENT_ID ClientId 381 | addr_of_mut!(client_id) as usize, 382 | 383 | // IN PCONTEXT ThreadContext, 384 | addr_of!(context) as usize, 385 | 386 | // IN PINITIAL_TEB InitialTeb 387 | addr_of!(initial_teb) as usize, 388 | 389 | // IN BOOLEAN CreateSuspended 390 | 0, 391 | 392 | // Syscall number 393 | Syscall::CreateThread as usize 394 | ) 395 | } as u32); 396 | 397 | // Convert error to Rust error 398 | if status.success() { 399 | Ok(JoinHandle(Handle(handle), rbox)) 400 | } else { 401 | Err(status) 402 | } 403 | } 404 | 405 | --------------------------------------------------------------------------------