aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
blob: 18c1b741de46331be108e18b49bcd3fa110797c9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
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<String>,
}

impl MapEntry {
    /// Try to parse a [`MapEntry`] from the `line` passed as argument.
    pub fn from_line<'a>(line: &'a str) -> Result<MapEntry, Error> {
        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<Mmap> {
        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<MapEntry> 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::<JmpPad>()`](core::mem::size_of) bytes.
    pub unsafe fn install_at(self, addr: VirtAddr) {
        std::ptr::write(addr.0 as *mut JmpPad, self);
    }
}