Skip to content

Commit

Permalink
refactor thread initialization, TEB per thread
Browse files Browse the repository at this point in the history
Make initial process and CreateThread share a create_thread code path,
which allows each to set up its own TEB.

Fixes #70.
  • Loading branch information
evmar committed Oct 16, 2024
1 parent 5905538 commit 15d8cf2
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 126 deletions.
43 changes: 16 additions & 27 deletions win32/src/machine_emu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use crate::{
machine::{LoadedAddrs, MachineX, Status},
pe,
shims::{Handler, Shims},
winapi,
winapi::{
self,
kernel32::{create_thread, NewThread},
},
};
use memory::{Extensions, ExtensionsMut, Mem};
use std::{collections::HashMap, path::Path};
Expand Down Expand Up @@ -79,42 +82,28 @@ impl MachineX<Emulator> {
self.emu.memory.mem()
}

/// Initialize a memory mapping for the stack and return the initial stack pointer.
pub fn create_stack(&mut self, desc: String, stack_size: u32) -> u32 {
let stack = self
.state
.kernel32
.mappings
.alloc(stack_size, desc, &mut self.emu.memory);
let stack_pointer = stack.addr + stack.size - 4;
stack_pointer
}

pub fn load_exe(
&mut self,
buf: &[u8],
path: &Path,
relocate: Option<Option<u32>>,
) -> anyhow::Result<LoadedAddrs> {
let exe = pe::load_exe(self, buf, path, relocate)?;
let retrowin32_main = winapi::kernel32::get_kernel32_builtin(self, "retrowin32_main");

let stack_pointer = self.create_stack("stack".into(), exe.stack_size);
let regs = &mut self.emu.x86.cpu_mut().regs;
regs.set32(x86::Register::ESP, stack_pointer);
regs.set32(x86::Register::EBP, stack_pointer);
regs.fs_addr = self.state.kernel32.teb;
let NewThread {
thread,
stack_pointer,
} = create_thread(self, exe.stack_size);

// To make CPU traces match more closely, set up some registers to what their
// initial values appear to be from looking in a debugger.
regs.set32(x86::Register::ECX, exe.entry_point);
regs.set32(x86::Register::EDX, exe.entry_point);
regs.set32(x86::Register::ESI, exe.entry_point);
regs.set32(x86::Register::EDI, exe.entry_point);
let cpu = &mut self.emu.x86.cpus[thread.index];
cpu.regs.set32(x86::Register::ESP, stack_pointer);
cpu.regs.set32(x86::Register::EBP, stack_pointer);
cpu.regs.fs_addr = thread.teb;

let retrowin32_main = winapi::kernel32::get_kernel32_builtin(self, "retrowin32_main");
let cpu = self.emu.x86.cpu_mut();
x86::ops::push(cpu, self.emu.memory.mem(), exe.entry_point);
x86::ops::push(cpu, self.emu.memory.mem(), 0); // return address
let mem = self.emu.memory.mem();
x86::ops::push(cpu, mem, exe.entry_point);
x86::ops::push(cpu, mem, 0); // return address
cpu.regs.eip = retrowin32_main;

self.exe_path = path.to_path_buf();
Expand Down
88 changes: 6 additions & 82 deletions win32/src/winapi/kernel32/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,6 @@ struct RTL_USER_PROCESS_PARAMETERS {
}
unsafe impl ::memory::Pod for RTL_USER_PROCESS_PARAMETERS {}

#[repr(C)]
struct _EXCEPTION_REGISTRATION_RECORD {
Prev: DWORD,
Handler: DWORD,
}
unsafe impl ::memory::Pod for _EXCEPTION_REGISTRATION_RECORD {}

fn init_peb(cmdline: &mut CommandLine, arena: &mut Arena, mem: Mem) -> u32 {
// RTL_USER_PROCESS_PARAMETERS
let params_addr = arena.alloc(
Expand Down Expand Up @@ -89,29 +82,6 @@ fn init_peb(cmdline: &mut CommandLine, arena: &mut Arena, mem: Mem) -> u32 {
peb_addr
}

/// Set up TEB, PEB, and other process info.
/// The FS register points at the TEB (thread info), which points at the PEB (process info).
fn init_teb(peb_addr: u32, arena: &mut Arena, mem: Mem) -> u32 {
// SEH chain
let seh_addr = arena.alloc(
std::mem::size_of::<_EXCEPTION_REGISTRATION_RECORD>() as u32,
4,
);
let seh = mem.get_aligned_ref_mut::<_EXCEPTION_REGISTRATION_RECORD>(seh_addr);
seh.Prev = 0xFFFF_FFFF;
seh.Handler = 0xFF5E_5EFF; // Hopefully easier to spot.

// TEB
let teb_addr = arena.alloc(std::cmp::max(std::mem::size_of::<TEB>() as u32, 0x100), 4);
let teb = mem.get_aligned_ref_mut::<TEB>(teb_addr);
teb.Tib.ExceptionList = seh_addr;
teb.Tib._Self = teb_addr; // Confusing: it points to itself.
teb.Peb = peb_addr;

teb_addr
// log::info!("params {params_addr:x} peb {peb_addr:x} teb {teb_addr:x}");
}

/// Result of setting up the GDT, with initial values for all the relevant segment registers.
pub struct GDTEntries {
/// Address of GDT itself.
Expand Down Expand Up @@ -143,8 +113,8 @@ pub struct State {
pub arena: Arena,
/// Address image was loaded at.
pub image_base: u32,
/// Address of TEB (what FS register-relative addresses refer to).
pub teb: u32,
/// Address of PEB (process information exposed to executable).
pub peb: u32,
pub mappings: Mappings,
/// Heaps created by HeapAlloc().
heaps: HashMap<u32, Heap>,
Expand Down Expand Up @@ -204,12 +174,11 @@ impl State {

let mut cmdline = CommandLine::new(cmdline);
let peb = init_peb(&mut cmdline, &mut arena, mem.mem());
let teb = init_teb(peb, &mut arena, mem.mem());

State {
arena,
image_base: 0,
teb,
peb,
process_heap: 0,
mappings,
heaps: HashMap::new(),
Expand Down Expand Up @@ -288,7 +257,7 @@ impl State {

let fs = (3 << 3) | 0b011;
gdt[3] = SegmentDescriptor {
base: self.teb,
base: 0, // TODO: get teb into here
limit: 0x1000,
granularity: false,
db: true, // 32 bit
Expand Down Expand Up @@ -329,19 +298,10 @@ impl State {
}
}

pub fn teb(machine: &Machine) -> &TEB {
machine
.mem()
.get_aligned_ref::<TEB>(machine.state.kernel32.teb)
}
pub fn teb_mut(machine: &mut Machine) -> &mut TEB {
pub fn peb_mut(machine: &mut Machine) -> &mut PEB {
machine
.mem()
.get_aligned_ref_mut::<TEB>(machine.state.kernel32.teb)
}
pub fn peb_mut(machine: &mut Machine) -> &mut PEB {
let peb_addr = teb(machine).Peb;
machine.mem().get_aligned_ref_mut::<PEB>(peb_addr)
.get_aligned_ref_mut::<PEB>(machine.state.kernel32.peb)
}

#[repr(C)]
Expand All @@ -365,42 +325,6 @@ pub struct PEB {
}
unsafe impl ::memory::Pod for PEB {}

#[repr(C)]
pub struct NT_TIB {
ExceptionList: DWORD,
StackBase: DWORD,
StackLimit: DWORD,
SubSystemTib: DWORD,
FiberData: DWORD,
ArbitraryUserPointer: DWORD,
_Self: DWORD,
}
unsafe impl ::memory::Pod for NT_TIB {}

#[repr(C)]
pub struct TEB {
pub Tib: NT_TIB,
pub EnvironmentPointer: DWORD,
pub ClientId_UniqueProcess: DWORD,
pub ClientId_UniqueThread: DWORD,
pub ActiveRpcHandle: DWORD,
pub ThreadLocalStoragePointer: DWORD,
pub Peb: DWORD,
pub LastErrorValue: DWORD,
pub CountOfOwnedCriticalSections: DWORD,
pub CsrClientThread: DWORD,
pub Win32ThreadInfo: DWORD,
pub User32Reserved: [DWORD; 26],
pub UserReserved: [DWORD; 5],
pub WOW32Reserved: DWORD,
pub CurrentLocale: DWORD,
// TODO: ... there are many more fields here

// This is at the wrong offset, but it shouldn't matter.
pub TlsSlots: [DWORD; 64],
}
unsafe impl ::memory::Pod for TEB {}

/// This function is not part of the Windows API, but is rather just the entry
/// point for when retrowin32 starts/stops a process, initializing DLLs and calling main.
/// It probably has some better name within ntdll.dll.
Expand Down
5 changes: 0 additions & 5 deletions win32/src/winapi/kernel32/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,6 @@ pub fn RaiseException(
todo!();
}

#[win32_derive::dllexport]
pub fn NtCurrentTeb(machine: &mut Machine) -> u32 {
machine.state.kernel32.teb
}

// TODO: this has a bunch of synchronization magic that I haven't implemented,
// but I did at least make this struct the right size (128 bits).
#[repr(C)]
Expand Down
136 changes: 127 additions & 9 deletions win32/src/winapi/kernel32/thread.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,121 @@
use super::{peb_mut, teb_mut};
use super::peb_mut;
use crate::{
machine::Machine,
winapi,
winapi::types::{Str16, HANDLE},
winapi::{
self,
alloc::Arena,
types::{Str16, HANDLE},
},
};
use memory::Pod;
use memory::{Extensions, Mem, Pod};

#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct HTHREADT;
/// state.objects[HTHREAD] maps to a Thread object.
pub type HTHREAD = HANDLE<HTHREADT>;

pub struct Thread {}
#[repr(C)]
struct _EXCEPTION_REGISTRATION_RECORD {
Prev: u32,
Handler: u32,
}
unsafe impl ::memory::Pod for _EXCEPTION_REGISTRATION_RECORD {}

#[repr(C)]
pub struct NT_TIB {
ExceptionList: u32,
StackBase: u32,
StackLimit: u32,
SubSystemTib: u32,
FiberData: u32,
ArbitraryUserPointer: u32,
_Self: u32,
}
unsafe impl ::memory::Pod for NT_TIB {}

#[repr(C)]
pub struct TEB {
pub Tib: NT_TIB,
pub EnvironmentPointer: u32,
pub ClientId_UniqueProcess: u32,
pub ClientId_UniqueThread: u32,
pub ActiveRpcHandle: u32,
pub ThreadLocalStoragePointer: u32,
pub Peb: u32,
pub LastErrorValue: u32,
pub CountOfOwnedCriticalSections: u32,
pub CsrClientThread: u32,
pub Win32ThreadInfo: u32,
pub User32Reserved: [u32; 26],
pub UserReserved: [u32; 5],
pub WOW32Reserved: u32,
pub CurrentLocale: u32,
// TODO: ... there are many more fields here

// This is at the wrong offset, but it shouldn't matter.
pub TlsSlots: [u32; 64],
}
unsafe impl ::memory::Pod for TEB {}

pub struct Thread {
pub index: usize,

/// address of TEB
pub teb: u32,
}

/// Set up TEB, PEB, and other process info.
/// The FS register points at the TEB (thread info), which points at the PEB (process info).
fn init_teb(peb_addr: u32, arena: &mut Arena, mem: Mem) -> u32 {
// SEH chain
let seh_addr = arena.alloc(
std::mem::size_of::<_EXCEPTION_REGISTRATION_RECORD>() as u32,
4,
);
let seh = mem.get_aligned_ref_mut::<_EXCEPTION_REGISTRATION_RECORD>(seh_addr);
seh.Prev = 0xFFFF_FFFF;
seh.Handler = 0xFF5E_5EFF; // Hopefully easier to spot.

// TEB
let teb_addr = arena.alloc(std::cmp::max(std::mem::size_of::<TEB>() as u32, 0x100), 4);
let teb = mem.get_aligned_ref_mut::<TEB>(teb_addr);
teb.Tib.ExceptionList = seh_addr;
teb.Tib._Self = teb_addr; // Confusing: it points to itself.
teb.Peb = peb_addr;

teb_addr
}

/// Information about a newly-created thread; info that persists after the thread is created
/// is kept in Thread.
pub struct NewThread {
pub thread: Thread,
/// initial esp
pub stack_pointer: u32,
}

pub fn create_thread(machine: &mut Machine, stack_size: u32) -> NewThread {
let index = machine.emu.x86.new_cpu();

let stack = machine.state.kernel32.mappings.alloc(
stack_size,
format!("thread {index} stack"),
&mut machine.emu.memory,
);
let stack_pointer = stack.addr + stack.size - 4;

let mem = machine.emu.memory.mem();
let teb = init_teb(
machine.state.kernel32.peb,
&mut machine.state.kernel32.arena,
mem,
);

NewThread {
thread: Thread { index, teb },
stack_pointer,
}
}

#[win32_derive::dllexport]
pub fn GetCurrentThread(machine: &mut Machine) -> HTHREAD {
Expand All @@ -32,6 +136,17 @@ pub fn GetCurrentThreadId(machine: &mut Machine) -> u32 {
}
}

#[win32_derive::dllexport]
pub fn NtCurrentTeb(machine: &mut Machine) -> u32 {
machine.emu.x86.cpu().regs.fs_addr
}

pub fn teb_mut(machine: &mut Machine) -> &mut TEB {
// TODO: read directly from local Thread, don't believe exe's fs address.
let fs = machine.emu.x86.cpu().regs.fs_addr;
machine.emu.memory.mem().get_aligned_ref_mut::<TEB>(fs)
}

#[win32_derive::dllexport]
pub fn TlsAlloc(machine: &mut Machine) -> u32 {
let peb = peb_mut(machine);
Expand Down Expand Up @@ -79,19 +194,22 @@ pub async fn CreateThread(

#[cfg(feature = "x86-emu")]
{
let id = 1; // TODO
let stack_pointer = machine.create_stack(format!("thread{id} stack"), dwStackSize);
// TODO: should reuse a CPU from a previous thread that has exited
let cpu = machine.emu.x86.new_cpu();
let NewThread {
thread,
stack_pointer,
} = create_thread(machine, dwStackSize);
let cpu = &mut machine.emu.x86.cpus[thread.index];
cpu.regs.set32(x86::Register::ESP, stack_pointer);
cpu.regs.set32(x86::Register::EBP, stack_pointer);
cpu.regs.fs_addr = thread.teb;
let mem = machine.emu.memory.mem();
x86::ops::push(cpu, mem, lpParameter);
x86::ops::push(cpu, mem, lpStartAddress);
x86::ops::push(cpu, mem, 0);
cpu.regs.eip = retrowin32_thread_main;

HTHREAD::from_raw(id)
HTHREAD::from_raw(thread.index as u32 + 1)
}

#[cfg(not(feature = "x86-emu"))]
Expand Down
Loading

0 comments on commit 15d8cf2

Please sign in to comment.