Skip to content

Commit

Permalink
feat: windows runtime support
Browse files Browse the repository at this point in the history
  • Loading branch information
melotic committed Jun 22, 2023
1 parent d682b7a commit 25298e5
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 14 deletions.
3 changes: 2 additions & 1 deletion .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
[build]
rustflags = ["-Ctarget-feature=+aes,+ssse3"]
rustflags = ["-Ctarget-feature=+aes,+ssse3"]
#target="x86_64-pc-windows-gnu"
14 changes: 13 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion stub/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ libnanomite = { path = "../libnanomite" }
nix = "0.25"
procfs = "0.14"

[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["handleapi", "processthreadsapi", "memoryapi", "synchapi", "debugapi"] }
rand = "0.8.5"
ntapi = "0.3"

[profile.release]
opt-level = 'z'
lto = true
panic = 'abort'
codegen-units = 1
codegen-units = 1
2 changes: 1 addition & 1 deletion stub/src/backends/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ fn parent(pid: Pid, jdt: JumpDataTable) {

// print RIP
// let rip = ptrace::getregs(pid).unwrap().rip;
// println!("{:#?}", status);
println!("{:#?}", status);

match status {
WaitStatus::Stopped(_, Signal::SIGSEGV) => break,
Expand Down
3 changes: 3 additions & 0 deletions stub/src/backends/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
#[cfg(target_os = "linux")]
pub mod linux;

#[cfg(target_os = "windows")]
pub mod windows;
191 changes: 189 additions & 2 deletions stub/src/backends/windows.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,192 @@
use std::{error::Error, fs, mem, ptr};

use libnanomite::JumpDataTable;
use ntapi::{
ntpebteb::PEB,
ntpsapi::{NtQueryInformationProcess, ProcessBasicInformation, PROCESS_BASIC_INFORMATION},
};
use winapi::{
shared::{
minwindef::{DWORD, FALSE, TRUE},
ntdef::{HANDLE, NT_SUCCESS}, winerror::SUCCEEDED,
},
um::{
debugapi::{ContinueDebugEvent, WaitForDebugEvent},
memoryapi::ReadProcessMemory,
processenv::GetCommandLineA,
processthreadsapi::{CreateProcessA, PROCESS_INFORMATION, STARTUPINFOA, OpenThread, SuspendThread, GetThreadContext, ResumeThread, SetThreadContext},
synchapi::WaitForSingleObject,
winbase::{DEBUG_PROCESS, INFINITE},
winnt::{DBG_CONTINUE, THREAD_ALL_ACCESS, CONTEXT, CONTEXT_CONTROL, CONTEXT_FULL}, handleapi::CloseHandle,
},
};

pub fn run(bin: &[u8], jdt: JumpDataTable) {
if let Ok(info) = run_binary(bin) {
run_handler(info, jdt);
}
}

const EXCEPTION_DEBUG_EVENT: DWORD = 1;
const EXIT_PROCESS_DEBUG_EVENT: DWORD = 5;

fn run_handler(info: (PROCESS_INFORMATION, String), jdt: JumpDataTable) {
let (pi, file_name) = info;

let base_addr = read_remote_peb(pi.hProcess).unwrap().ImageBaseAddress as usize;

unsafe {
let mut debug_event = mem::zeroed();
loop {
WaitForDebugEvent(&mut debug_event, INFINITE);

match debug_event.dwDebugEventCode {
EXEPTION_DEBUG_EVENT => handle_int3(debug_event.dwThreadId, base_addr, &jdt),
EXIT_PROCESS_DEBUG_EVENT => break,
_ => (),
}

ContinueDebugEvent(
debug_event.dwProcessId,
debug_event.dwThreadId,
DBG_CONTINUE,
);
}

ContinueDebugEvent(
debug_event.dwProcessId,
debug_event.dwThreadId,
DBG_CONTINUE,
);

WaitForSingleObject(pi.hProcess, INFINITE);

// Free handles
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

fs::remove_file(file_name).unwrap();
}
}

unsafe fn handle_int3(thread_id: u32, base_addr: usize, jdt: &JumpDataTable) {
// Open a handle to the thread.
let handle = OpenThread(THREAD_ALL_ACCESS, TRUE, thread_id);

if !SUCCEEDED(handle as i32) {
panic!();
}

// GetThreadContext requires the thread is suspended. It should already be suspended, this is for redundancy.
SuspendThread(handle);

let mut context = mem::zeroed::<CONTEXT>();
context.ContextFlags = CONTEXT_FULL;

// Get the thread context.
let ret = GetThreadContext(handle, &mut context);

// LdrpInitializeProcess will trigger a breakpoint if the process is being debugged when it is
// created. This function will handle that breakpoint, but GetThreadContext only allows us to
// get the context of threads we own. Thus, if GetThreadContext fails we should just move on.
if ret == 0 {
ResumeThread(handle);
CloseHandle(handle);
return;
}

let jump_data = jdt.get(context.Rip as usize - 1 - base_addr);

// If there was an error getting the jump data, jump to the next instruction and hope for the
// best.
if jump_data.is_none() {
context.Rip += 1;
SetThreadContext(handle, &context);
ResumeThread(handle);
CloseHandle(handle);
return;
}

let jump_data = jump_data.unwrap();

// Add the signed offset to RIP.
let offset = jump_data.eval_jump(context.EFlags as u64, context.Rcx as u64);
context.Rip = (context.Rip as i64 + offset as i64 - 1) as u64;

// Update RIP, resume the thread, and get rid of our handle.
SetThreadContext(handle, &context);
ResumeThread(handle);
CloseHandle(handle);
}

fn read_remote_peb(proc_handle: HANDLE) -> Option<PEB> {
unsafe {
let mut pbi = mem::zeroed::<PROCESS_BASIC_INFORMATION>();
let mut written = 0;

// Get the ProcessBasicInformation to locate the address of the PEB.
if !NT_SUCCESS(NtQueryInformationProcess(
proc_handle,
ProcessBasicInformation,
&mut pbi as *mut _ as _,
mem::size_of::<PROCESS_BASIC_INFORMATION>() as u32,
&mut written as *mut _ as _,
)) {
return None;
}

let mut peb = mem::zeroed::<PEB>();
let mut written = 0;

// Read the PEB.
if ReadProcessMemory(
proc_handle,
pbi.PebBaseAddress as *const _,
&mut peb as *mut _ as _,
mem::size_of::<PEB>(),
&mut written as *mut _ as _,
) == FALSE
{
return None;
}

Some(peb)
}
}

fn run_binary(bin: &[u8]) -> Result<(PROCESS_INFORMATION, String), Box<dyn Error>> {
// Get path to %temp%
let temp_dir = std::env::temp_dir();

// Create a random file name
let file_name = format!("{}.exe", rand::random::<u32>());

// Write the bin to that file
let file_path = temp_dir.join(file_name).to_str().unwrap().to_string();
std::fs::write(&file_path, bin).unwrap();

// Create the process with the DEBUG_PROCESS flag
unsafe {
let mut si = mem::zeroed::<STARTUPINFOA>();
let mut pi = mem::zeroed::<PROCESS_INFORMATION>();

let ret = CreateProcessA(
file_name.as_ptr() as *const _,
GetCommandLineA(),
ptr::null_mut(),
ptr::null_mut(),
TRUE,
DEBUG_PROCESS,
ptr::null_mut(),
ptr::null_mut(),
&mut si,
&mut pi,
);

pub fn run(_bin: &[u8], _jdt: JumpDataTable) {
// Err(()
if ret == TRUE {
Ok((pi, file_path))
} else {
Err("Failed to create process".into())
}
}
}
22 changes: 14 additions & 8 deletions stub/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@ extern crate alloc;

mod backends;

use backends::{linux, windows};
#[cfg(target_os = "linux")]
use backends::linux;

#[cfg(target_os = "windows")]
use backends::windows;

use libnanomite::JumpDataTable;

pub fn runtime_main(bin: &[u8], jdt: JumpDataTable) {
if cfg!(target_os = "linux") {
linux::run(bin, jdt);
} else if cfg!(target_os = "windows") {
windows::run(bin, jdt);
} else {
panic!("Unsupported OS");
}
#[cfg(target_os = "linux")]
linux::run(bin, jdt);

#[cfg(target_os = "windows")]
windows::run(bin, jdt);

// regex to match any base64 encoded string
// let re = Regex::new(r"([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?").unwrap();
}

0 comments on commit 25298e5

Please sign in to comment.