From eda0273c1b36b4959e5c61e3d209029e1d8de088 Mon Sep 17 00:00:00 2001 From: Johannes Stoelp Date: Sun, 8 Aug 2021 16:50:14 +0200 Subject: add vdso proxy poc --- src/lib.rs | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 src/lib.rs (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..18c1b74 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,184 @@ +//! Collection of some abstractions and the error definition used by this PoC. + +/// Errors that can occur in this PoC. +#[derive(Debug)] +pub enum Error { + /// Failed to open and read the `/proc/self/maps` file. + FailedToReadMaps, + /// Failed to a parse line from the `/proc/self/maps` file. + /// Captures line that failed to parse. + ParseMapEntryError(String), + /// No `[vdso]` segment found in `/proc/self/maps`. + VdsoSegmentNotFound, + /// Failed to parse bytes as ELF file. + FailedToParseAsElf, + /// No `PT_LOAD` program header found in the ELF file. + LoadPhdrNotFound, + /// Requested symbol not found in the ELF file. + /// Captures the name of the requested symbol. + SymbolNotFound(String), +} + +/// Representation of an 64-bit virtual address. +#[derive(Debug, Copy, Clone)] +pub struct VirtAddr(pub u64); + +/// Represents an entry from the `/proc/self/maps` file with the information needed by this PoC. +/// ```text +/// man 5 proc +/// address perms offset dev inode pathname +/// 00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/dbus-daemon +/// ``` +#[derive(Debug)] +pub struct MapEntry { + /// Start address of the memory segment. + pub addr: u64, + /// Length of the memory segment. + pub len: u64, + /// Optional name of the memory segment. + pub name: Option, +} + +impl MapEntry { + /// Try to parse a [`MapEntry`] from the `line` passed as argument. + pub fn from_line<'a>(line: &'a str) -> Result { + let expect = |tok: Option<&'a str>| tok.ok_or(Error::ParseMapEntryError(line.into())); + + // Tokenize the line. + let mut toks = line.split_whitespace(); + let addr = expect(toks.next())?; + let _perms = expect(toks.next())?; + let _offset = expect(toks.next())?; + let _dev = expect(toks.next())?; + let _inode = expect(toks.next())?; + let name = toks.next().map(|name| String::from(name)); + + // Parse the address token. + let (addr, len) = { + let tou64 = |s: &'a str| { + u64::from_str_radix(s, 16) + .map_err(|e| Error::ParseMapEntryError(format!("{}\n{}", line, e))) + }; + + let mut toks = addr.split('-'); + let start = tou64(expect(toks.next())?)?; + let end = tou64(expect(toks.next())?)?; + + (start, end - start) + }; + + Ok(MapEntry { addr, len, name }) + } +} + +/// Owned [`libc::mmap`] allocation. +pub struct Mmap { + ptr: *mut libc::c_void, + len: usize, + map: MapEntry, +} + +impl Mmap { + /// Create a new allocation with `read | write | execute` permissions big enough to hold a copy + /// of `bytes` and initialize it with the `bytes` passed as argument. + pub fn new_rwx_from(bytes: &[u8]) -> Option { + use libc::{ + memcpy, mmap, sysconf, MAP_ANONYMOUS, MAP_FAILED, MAP_PRIVATE, PROT_EXEC, PROT_READ, + PROT_WRITE, _SC_PAGESIZE, + }; + + // Get the page size. + let page_size = unsafe { sysconf(_SC_PAGESIZE) } as usize; + + // Compute required size for the new allocation by rounding up to the next page size. + let len = ((bytes.len() + page_size - 1) / page_size) * page_size; + + // Allocate new `rwx` memory segment. + let ptr = unsafe { + mmap( + std::ptr::null_mut(), + len, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, /* fd */ + 0, /* offset */ + ) + }; + + if ptr == MAP_FAILED { + return None; + } + + unsafe { + // Initialize new allocation with `bytes` passed as argument. + memcpy(ptr, bytes.as_ptr().cast(), bytes.len()); + } + + Some(Mmap { + ptr, + len, + map: MapEntry { + addr: ptr as u64, + len: len as u64, + name: Some("mmap_rwx".into()), + }, + }) + } +} + +impl Drop for Mmap { + fn drop(&mut self) { + unsafe { libc::munmap(self.ptr, self.len) }; + } +} + +impl AsRef for Mmap { + fn as_ref(&self) -> &'_ MapEntry { + &self.map + } +} + +impl AsMut<[u8]> for Mmap { + fn as_mut(&mut self) -> &mut [u8] { + unsafe { std::slice::from_raw_parts_mut(self.ptr.cast(), self.len) } + } +} + +/// An `x86_64` jump pad (trampoline) that that can be installed at a [`VirtAddr`]. +/// +/// The jump pad is implemented as: +/// ```asm +/// mov rax, imm64 ; target +/// jmp rax +/// ``` +#[allow(dead_code)] +#[cfg(target_arch = "x86_64")] +#[repr(packed)] +pub struct JmpPad { + movabs: u16, + target: u64, + jmp_rax: u16, +} + +#[cfg(target_arch = "x86_64")] +impl JmpPad { + /// Initialize a new jump pad to the destination virtual address `target`. + /// This does not install the jump pad. + pub fn to(target: VirtAddr) -> JmpPad { + JmpPad { + movabs: 0xb848, // REX.W + mov rax, imm64 + target: target.0, + jmp_rax: 0xe0ff, // jmp rax + } + } + + /// Install the jump pad at the virtual address `addr`. + /// + /// # Safety + /// The caller must guarantee the following constraints: + /// - `addr` must be a valid virtual address referring to writeable memory. + /// - There must be enough space to store [`size_of::()`](core::mem::size_of) bytes. + pub unsafe fn install_at(self, addr: VirtAddr) { + std::ptr::write(addr.0 as *mut JmpPad, self); + } +} -- cgit v1.2.3