├── Cargo.toml ├── README.md └── src └── loader ├── load_elf.rs ├── main.rs ├── parse_elf.rs └── stack_setup.rs /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "userspace_rust_loader" 3 | version = "0.1.0" 4 | authors = ["Simon Scannell "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "loader" 9 | path = "src/loader/main.rs" 10 | 11 | [dependencies] 12 | nix = "0.19.0" 13 | libc = "0.2.80" 14 | rand = "0.7" 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Linux x86_64 ELF loader in userspace 2 | 3 | This is the result of a fun little side project where the idea is to emulate the Linux kernel's behavior when loading ELF binaries but in user-space. 4 | 5 | This is achieved by using `mmap()` and setting up a new stack and then jumping to the entry point of either the 6 | ELF Interpreter that is requested by the ELF to be loaded or directly jumping to its entrypoint directly. 7 | 8 | This essentially can be used to have 2 images, along with all their libraries run independently of each other in the same address space, as they will use their own versions of the shared objects they require. 9 | This primitive could be used for: 10 | 11 | * Obfuscators 12 | * Packers 13 | * Fuzzers using Dynamic Binary Rewriting 14 | * Whatever you can think of 15 | 16 | One thing I still need to figure out is how to have two seperate libc heaps co-exist peacefully at 17 | the same time. 18 | 19 | ## Building 20 | 21 | Some low-level inline assembly is involved so Rust nightly is required, at least at the time of writing. 22 | 23 | In the build directory, execute 24 | 25 | ```shell 26 | rustup default nightly 27 | cargo build --release 28 | rustup default stable 29 | ``` 30 | 31 | 32 | ## Usage 33 | 34 | ```shell 35 | target/release/loader /path/to/bin arg1 arg2 arg3 ... argn 36 | ``` 37 | 38 | ### Example 39 | 40 | ```shell 41 | target/release/loader /bin/ls -la /usr/lib/ld-2.32.so 42 | ``` 43 | -------------------------------------------------------------------------------- /src/loader/load_elf.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | extern crate nix; 4 | extern crate libc; 5 | use nix::sys::mman::{ 6 | mmap, 7 | mprotect, 8 | ProtFlags, 9 | MapFlags 10 | }; 11 | use core::ffi::c_void; 12 | 13 | 14 | use crate::parse_elf::{Elf64Phdr, LoadInfo, ElfType}; 15 | 16 | // these values are used to translate ElfPhdr64 17 | const PF_X: u32 = 1; 18 | const PF_W: u32 = 2; 19 | const PF_R: u32 = 4; 20 | 21 | 22 | const ELF_MIN_ALIGNMENT: usize = 0x1000; 23 | const ELF_MIN_ALIGNMENT_MASK: usize = !(ELF_MIN_ALIGNMENT - 1); 24 | 25 | 26 | 27 | /// represents a PT_LOAD segment of the ELF to be loaded and holds the data to be loaded 28 | #[derive(Debug, Clone)] 29 | pub struct ElfSegment { 30 | pub virt_addr: usize, 31 | pub memsize: usize, 32 | pub offset: usize, 33 | filesize: usize, 34 | alignment: usize, 35 | data: Vec, 36 | prot: ProtFlags, 37 | } 38 | 39 | 40 | 41 | impl ElfSegment { 42 | 43 | /// takes a raw Elf64Phdr as parsed by the parsing module and converts 44 | /// it into a loadable segment 45 | pub fn new(hdr: &Elf64Phdr, data: Vec) -> Self { 46 | Self { 47 | virt_addr: hdr.vaddr as usize, 48 | memsize: hdr.memsz as usize, 49 | filesize: hdr.filesz as usize, 50 | alignment: hdr.align as usize, 51 | data: data, 52 | offset: hdr.offset as usize, 53 | prot: Self::get_prot_flags_from_progam_flags(hdr.pflags), 54 | } 55 | } 56 | 57 | fn get_prot_flags_from_progam_flags(program_flags: u32) -> ProtFlags { 58 | let mut prot_flags = ProtFlags::empty(); 59 | if (program_flags & PF_X) != 0 { 60 | prot_flags.insert(ProtFlags::PROT_EXEC); 61 | } 62 | 63 | if (program_flags & PF_W) != 0 { 64 | prot_flags.insert(ProtFlags::PROT_WRITE); 65 | } 66 | 67 | if (program_flags & PF_R) != 0 { 68 | prot_flags.insert(ProtFlags::PROT_READ); 69 | } 70 | 71 | prot_flags 72 | } 73 | } 74 | 75 | 76 | /// Statefully emulates the Linux kernel ELF loading logic 77 | pub struct ElfLoad { 78 | pub load_addr: usize, 79 | } 80 | 81 | impl ElfLoad { 82 | fn get_total_mapping_size(segments: &Vec) -> usize { 83 | let last_idx = segments.len() - 1; 84 | 85 | // logic from the linux kernel 86 | segments[last_idx].virt_addr + segments[last_idx].memsize - (segments[0].virt_addr & !15) 87 | } 88 | 89 | pub fn load(load_info: &LoadInfo) -> Self { 90 | /* There are two types of ELF files: 91 | * ET_EXEC and ET_DYN. ET_EXEC are position dependent and are given a load address by the compiler (for gcc it is usually 0x40000) 92 | * In the case of such an executable, simply obtain the virtual address of the first PT_LOAD program header and use it as 93 | * an address for a MAP_FIXED mmap() mapping. 94 | 95 | * In case of an ET_DYN ELF, the code is position independent and can be loaded anywhere. The Linux kernel usually chooses 96 | * 0x555555554aaa + ASLR offset. However, since we are already loaded at that address we just let mmap() chose a suitable location 97 | * for the new binary. This can be a little awkward as mmap() chooses an address in the 0x7fff... range and might map the file next 98 | * to another file. This is awkward because the libc heap uses brk() (the end of the loaded program) to initialize the heap. 99 | * Therefor, we need to set brk() to a safe location where the heap can grow. 100 | 101 | The corresponding Linux kernel code of the following logic is: 102 | 103 | if (interpreter) { 104 | load_bias = ELF_ET_DYN_BASE; 105 | if (current->flags & PF_RANDOMIZE) 106 | load_bias += arch_mmap_rnd(); 107 | alignment = maximum_alignment(elf_phdata, elf_ex->e_phnum); 108 | if (alignment) 109 | load_bias &= ~(alignment - 1); 110 | elf_flags |= MAP_FIXED; 111 | else 112 | load_bias = 0 113 | 114 | 115 | The kernel then loads each segment through load_bias + vaddr of section. This way, 116 | both static binaries (load bias of 0) that have a set load address and PIE binaries can be loaded with 117 | the same logic. 118 | */ 119 | 120 | let segments = &load_info.segments; 121 | 122 | let mut map_flags = MapFlags::empty(); 123 | map_flags.insert(MapFlags::MAP_PRIVATE); 124 | map_flags.insert(MapFlags::MAP_ANONYMOUS); 125 | 126 | // if the ELF is static, set the MAP_FIXED flag and set the load address to virtual address of the first segment 127 | // otherwise load at any location (address 0 without MAP_FIXED) 128 | let mmap_addr = match load_info.etype { 129 | ElfType::ElfExec => { 130 | map_flags.insert(MapFlags::MAP_FIXED); 131 | segments[0].virt_addr - segments[0].offset 132 | }, 133 | ElfType::ElfDyn => 0 134 | }; 135 | 136 | let map_flags = map_flags; 137 | 138 | // make an allocation large enough for the entire ELF binary, make it read and writable 139 | // and populate it with the content. Then change the protection flags for each segment accordingly. 140 | let total_mapping_size = Self::get_total_mapping_size(&segments); 141 | let mut prot_flags = ProtFlags::empty(); 142 | prot_flags.insert(ProtFlags::PROT_READ); 143 | prot_flags.insert(ProtFlags::PROT_WRITE); 144 | 145 | let load_addr = unsafe { 146 | mmap(mmap_addr as *mut c_void, total_mapping_size, prot_flags, map_flags, -1, 0).expect("Failed to map!") 147 | }; 148 | 149 | // use the load_addr to offset the virt address of each segment. If this is a 150 | // static binary, set it to 0 since the virtual addresses in the headers are absolute 151 | let load_base = if mmap_addr == 0 { 152 | load_addr as usize 153 | } else { 154 | 0 155 | }; 156 | 157 | // populate the pages of each segment and change the protection status 158 | for seg in segments.iter() { 159 | 160 | // this is the same logic as in the Linux kernel for aligning addresses of 161 | // program headers 162 | let addr = load_base + seg.virt_addr; 163 | let size = seg.filesize + (addr & (ELF_MIN_ALIGNMENT -1)); 164 | 165 | let addr = addr & ELF_MIN_ALIGNMENT_MASK; 166 | let size = (size + ELF_MIN_ALIGNMENT - 1) & ELF_MIN_ALIGNMENT_MASK; 167 | 168 | // copy the actual number of bytes in the file. The (size) might be larger than this 169 | // as the segment might contain uninitialized data 170 | unsafe { 171 | libc::memcpy(addr as *mut c_void, seg.data.as_ptr() as *const c_void, seg.data.len()); 172 | } 173 | 174 | // now change the protection of the segment (with the actual size) 175 | unsafe { 176 | mprotect(addr as *mut c_void, size, seg.prot).expect("mprotect() failed"); 177 | } 178 | } 179 | 180 | ElfLoad { 181 | load_addr: load_addr as usize, 182 | } 183 | } 184 | 185 | 186 | } 187 | -------------------------------------------------------------------------------- /src/loader/main.rs: -------------------------------------------------------------------------------- 1 | // enable asm to jump to the entry point of the program 2 | #![feature(asm)] 3 | 4 | mod load_elf; 5 | mod parse_elf; 6 | mod stack_setup; 7 | 8 | fn main() { 9 | 10 | // ensure that there is at least one argument to this program, it is the program that should be loaded 11 | let args: Vec = std::env::args().collect(); 12 | println!("{:?}", args); 13 | if args.len() == 1 { 14 | panic!("Usage: {} /PATH/TO/PROGRAM/TO/LOAD", args[0]); 15 | } 16 | 17 | // parse the ELF file to be loaded to obtain necessary load information 18 | let binary_info = parse_elf::parse_elf(&args[1]); 19 | 20 | // we will have to check if the ELF file uses an interpreter. If so, the entry point needs to be _start of that shared object file (usually ld.so) 21 | let (entry_point, interp_base) = if let Some(elf_interp) = &binary_info.elf_interp { 22 | let loader_info = parse_elf::parse_elf(elf_interp); 23 | let loader_load = load_elf::ElfLoad::load(&loader_info); 24 | 25 | // the loader is PIE so offsts such as the entry point are relative to its load address. Figure out where the loader will load it 26 | // to ensure we have the correct entry point and base address for the stack 27 | (loader_info.entry_point + loader_load.load_addr, loader_load.load_addr) 28 | } else { 29 | // otherwise the entry point is absolute and there is no ELF interpreter base (NULL) 30 | (binary_info.entry_point, 0) 31 | }; 32 | 33 | // load the binary into memory 34 | let binary_load = load_elf::ElfLoad::load(&binary_info); 35 | 36 | // setup a new execution stack. The initial stack layout is the same, wether this is a static ELF_EXEC, PIE ELF_DYN or anything else for that matter 37 | // save the RSP so that we can jump to it later 38 | let rsp = stack_setup::setup_stack(&binary_info, binary_load.load_addr, interp_base); 39 | 40 | 41 | // kick off execution by clearing all registers, switching to the new stack and jumping to the entry point 42 | unsafe { 43 | asm!(" 44 | mov rsp, rax 45 | push rbx 46 | 47 | xor rax, rax 48 | xor rbx, rbx 49 | xor rcx, rcx 50 | xor rdx, rdx 51 | xor rdi, rdi 52 | xor rsi, rsi 53 | 54 | xor r9, r9 55 | xor r10, r10 56 | xor r11, r11 57 | xor r12, r12 58 | xor r13, r13 59 | xor r14, r14 60 | xor r15, r15 61 | 62 | ret 63 | ", 64 | in("rax") rsp, 65 | in("rbx") entry_point 66 | ); 67 | } 68 | 69 | 70 | } -------------------------------------------------------------------------------- /src/loader/parse_elf.rs: -------------------------------------------------------------------------------- 1 | use std::io::prelude::*; 2 | use std::fs::File; 3 | 4 | use crate::load_elf::ElfSegment; 5 | 6 | /// value for a PT_LOAD program header type 7 | const PT_LOAD: u32 = 0x01; 8 | 9 | /// value for a PT_INTERP (ELF Interpreter) program header type 10 | const PT_INTERP: u32 = 0x03; 11 | 12 | /// standard size of a 64bit ELF header 13 | const SIZE_OF_ELF_HDR: usize = 64; 14 | 15 | /// standard size of a program header entry 16 | const SIZE_OF_PROGRAM_HDR: u16 = 56; 17 | 18 | /// the value of the e_ident[EI_CLASS] field for a 64bit ELF 19 | const CLASS_64_BIT: u8 = 0x2; 20 | 21 | /// the value for the ELF_EXEC type for the ELF type field 22 | const ELF_EXEC: u16 = 0x02; 23 | 24 | /// the value for the ELF_DYN type for the ELF type field 25 | const ELF_DYN: u16 = 0x03; 26 | 27 | 28 | /// the value for Linux ABI for the OS_ABI field 29 | const LINUX_ABI: u8 = 0x3; 30 | /// the value for System-V ABI for the OS_ABI field 31 | const SYSTEMV_ABI: u8 = 0x0; 32 | 33 | /// the value for x86 architecture for the machine field 34 | const X86_MACHINE: u16 = 0x3; 35 | 36 | /// the value for amd64 architecture for the machine field 37 | const AMD64_MACHINE: u16 = 0x3e; 38 | 39 | 40 | 41 | /// Represents an Elf64_Phdr as found in an actual ELF file 42 | #[repr(C, packed)] 43 | #[derive(Debug, Copy, Clone)] 44 | pub struct Elf64Phdr { 45 | pub ptype: u32, 46 | pub pflags: u32, 47 | pub offset: u64, 48 | pub vaddr: u64, 49 | pub paddr: u64, 50 | pub filesz: u64, 51 | pub memsz: u64, 52 | pub align: u64 53 | } 54 | 55 | /// Represents an ELF Header 56 | #[repr(C, packed)] 57 | #[derive(Debug, Copy, Clone)] 58 | struct ElfHdr { 59 | magic: u32, 60 | class: u8, 61 | endian: u8, 62 | elf_version: u8, 63 | os_abi: u8, 64 | abi_version: u8, 65 | padding: [u8; 7], 66 | etype: u16, 67 | machine: u16, 68 | version2: u32, 69 | entry_point: u64, 70 | program_headers: u64, 71 | section_table_off: u64, 72 | flags: u32, 73 | header_size: u16, 74 | pheader_size: u16, 75 | pheader_num: u16, 76 | shent_size: u16, 77 | shnum: u16, 78 | shstrnidx: u16 79 | } 80 | 81 | 82 | 83 | impl ElfHdr { 84 | 85 | /// Takes in a raw u8 buffer of the ELF file to parse and performs checks on it 86 | pub fn parse(buffer: &[u8]) -> Self { 87 | 88 | // Verify that the buffer is big enough to contain a header 89 | assert!(SIZE_OF_ELF_HDR <= buffer.len(), "the file is too small to contain an ELF header"); 90 | 91 | // Copy the buffer into a sized array, otherwise rustc will complain 92 | let mut buffer_clone: [u8; SIZE_OF_ELF_HDR] = [0; SIZE_OF_ELF_HDR]; 93 | buffer_clone.copy_from_slice(&buffer[..SIZE_OF_ELF_HDR]); 94 | 95 | // now transmute the buffer into an ELF struct and return it 96 | unsafe { 97 | std::mem::transmute(buffer_clone) 98 | } 99 | } 100 | 101 | /// Throws assertions incase of anything being off (not an ELF header, not an executable, incompatible architecture etc) 102 | pub fn verify(&self) { 103 | 104 | // check the ELF header 105 | assert!(u32::from_be(self.magic) == 0x7f454c46, "No ELF magic header was found in the target file"); 106 | 107 | // ensure that this is a 64 bit binary 108 | assert!(self.class == CLASS_64_BIT, "At this point, only 64b-it ELF are supported! :("); 109 | 110 | // ensure that this is either an ELF_EXEC or ELF_DYN 111 | assert!(self.etype == ELF_EXEC || self.etype == ELF_DYN, "At this point, only statically linked executables are supported :("); 112 | 113 | // ensure this ELF is for a supported OS 114 | assert!(self.os_abi == LINUX_ABI || self.os_abi == SYSTEMV_ABI, "At this point, only the Linux and System-V ABIs are supported :("); 115 | 116 | // ensure the architecture is x86 117 | assert!(self.machine == X86_MACHINE || self.machine == AMD64_MACHINE, "At this point, only x86-64 ELF's are supported :("); 118 | 119 | // ensure that the program header size is standardized. We don't have time for some fancy non-standard ELFs 120 | assert!(self.pheader_size == SIZE_OF_PROGRAM_HDR, "This ELF binary's Program Header Entry size differs from the standard Elf64_Phdr size :("); 121 | } 122 | 123 | /// Parse all PT_LOAD segments into a Vector ElfSegment's. These structs are used by the actual loader to 124 | /// load the ELF and start it! Also, return the file path of the ELF interpreter used by this application 125 | pub fn parse_segments(&self, buffer: &[u8]) -> (Option, Vec) { 126 | let mut current_offset = self.program_headers as usize; 127 | 128 | // verify that the current offset + all program headers are in bounds of the buffer representing the ELF file 129 | let max_offset = current_offset.checked_add((self.pheader_num * self.pheader_size) as usize).unwrap() as usize; 130 | assert!((max_offset) < buffer.len()); 131 | 132 | let mut elf_interp: Option = None; 133 | let mut res: Vec = Vec::new(); 134 | 135 | // iterate over each of the program headers and use transmute to get a parsed program header, which in turn will be 136 | // turned into a nice and safe Rust struct 137 | while current_offset < max_offset { 138 | // Copy the current slice of the buffer into a fixed size array with the size of a program header 139 | let mut buffer_clone: [u8; SIZE_OF_PROGRAM_HDR as usize] = [0; SIZE_OF_PROGRAM_HDR as usize]; 140 | buffer_clone.copy_from_slice(&buffer[current_offset..current_offset + SIZE_OF_PROGRAM_HDR as usize]); 141 | 142 | // then transmute 143 | let program_header: Elf64Phdr = unsafe { 144 | std::mem::transmute(buffer_clone) 145 | }; 146 | 147 | 148 | // Only parse this segment if it is loadable or an ELF interpreter 149 | if program_header.ptype == PT_LOAD || program_header.ptype == PT_INTERP { 150 | // if this program type is a loadable segment, add it to the list of segments returned here 151 | // otherwise interpret the contents of the section as a String that contains the path to the ELF interpreter 152 | // of this file 153 | if program_header.ptype == PT_LOAD { 154 | let offset = program_header.offset as usize - (program_header.vaddr as usize & (0x1000 -1)); 155 | let end_offset = offset.checked_add(program_header.filesz as usize + (program_header.vaddr as usize & (0x1000 -1))).unwrap(); 156 | assert!(end_offset < buffer.len()); 157 | 158 | res.push( 159 | ElfSegment::new(&program_header, buffer[offset..end_offset].to_vec()) 160 | ); 161 | } else { 162 | // if this is an interpreter segment, interpret the offset as "absolute" offset 163 | // and read the filename (-1) since it contains a NULL byte that RUST does not want to deal 164 | // with 165 | let offset = program_header.offset as usize; 166 | elf_interp = Some( 167 | String::from_utf8(buffer[offset..(offset + program_header.filesz as usize - 1)].to_vec()).expect("INTERP segment contains invalid filename") 168 | ); 169 | } 170 | 171 | } 172 | 173 | current_offset += SIZE_OF_PROGRAM_HDR as usize; 174 | } 175 | 176 | (elf_interp, res) 177 | } 178 | } 179 | 180 | 181 | pub enum ElfType { 182 | ElfExec, 183 | ElfDyn 184 | } 185 | 186 | impl ElfType { 187 | pub fn from(etype: u16) -> Self { 188 | match etype { 189 | ELF_EXEC => ElfType::ElfExec, 190 | ELF_DYN => ElfType::ElfDyn, 191 | _ => panic!("Invalid ELF type") 192 | } 193 | } 194 | } 195 | 196 | /// Holds all information the loader needs to set up the binary! 197 | pub struct LoadInfo { 198 | pub entry_point: usize, 199 | pub pheader_off: usize, 200 | pub pheader_num: usize, 201 | pub segments: Vec, 202 | pub elf_interp: Option, 203 | pub etype: ElfType, 204 | } 205 | 206 | 207 | 208 | /// Parses an ELF file and performs checks on it, such as verify the architecture, that is an executable and that it is 64bit. 209 | /// It then returns all necessary information needed by the loader (entry point and LOAD segments) 210 | pub fn parse_elf(file: &str) -> LoadInfo { 211 | let mut elf_file = File::open(file).expect("Could not find file"); 212 | 213 | // read the file into a dynamic sized buffer 214 | let mut buffer = Vec::new(); 215 | elf_file.read_to_end(&mut buffer).expect("Could not read ELF file!"); 216 | let buffer = buffer; 217 | 218 | // make sure this is a valid ELF and prepare to parse 219 | let hdr = ElfHdr::parse(&buffer); 220 | hdr.verify(); 221 | 222 | 223 | // parse all the loadable segments 224 | 225 | // parse the segments and pass them to the loader, as well as all necessary information 226 | //(hdr.program_headers as usize, hdr.entry_point as usize, hdr.parse_segments(&buffer)) 227 | let (elf_interp, segments) = hdr.parse_segments(&buffer); 228 | LoadInfo { 229 | entry_point: hdr.entry_point as usize, 230 | pheader_off: hdr.program_headers as usize, 231 | pheader_num: hdr.pheader_num as usize, 232 | segments: segments, 233 | elf_interp: elf_interp, 234 | etype: ElfType::from(hdr.etype) 235 | } 236 | } 237 | 238 | -------------------------------------------------------------------------------- /src/loader/stack_setup.rs: -------------------------------------------------------------------------------- 1 | 2 | use core::ffi::c_void; 3 | 4 | extern crate rand; 5 | use rand::Rng; 6 | 7 | extern crate nix; 8 | use nix::sys::mman::{ 9 | mmap, 10 | ProtFlags, 11 | MapFlags 12 | }; 13 | 14 | extern crate libc; 15 | 16 | use crate::parse_elf::{ 17 | LoadInfo, 18 | ElfType 19 | }; 20 | 21 | 22 | 23 | /// ELFAux IDs and values 24 | const AT_SYSINFO_EHDR: u64 = 33; 25 | const AT_HWCAP: u64 = 16; 26 | const AT_PAGESZ: u64 = 6; 27 | const AT_CLKTCK: u64 = 17; 28 | const AT_PHDR: u64 = 3; 29 | const AT_PHENT: u64 = 4; 30 | const AT_PHNUM: u64 = 5; 31 | const AT_BASE: u64 = 7; 32 | const AT_FLAGS: u64 = 8; 33 | const AT_ENTRY: u64 = 9; 34 | const AT_UID: u64 = 11; 35 | const AT_EUID: u64 = 12; 36 | const AT_GID: u64 = 13; 37 | const AT_EGID: u64 = 14; 38 | const AT_SECURE: u64 = 23; 39 | const AT_RANDOM: u64 = 25; 40 | const AT_HWCAP2: u64 = 26; 41 | const AT_EXECFN: u64 = 31; 42 | const AT_NULL: u64 = 0; 43 | 44 | 45 | /// the standard size of a program header and the only one we support 46 | const PHENT_SIZE: usize = 0x38; 47 | 48 | 49 | /// sets up an initial stack according to the System-V x86 ABI and passes information such 50 | /// as the entry point and location of the program headers of the application to tbe loaded 51 | /// to the ELF Interpreter or the CSU routines. 52 | pub fn setup_stack(load_info: &LoadInfo, load_address: usize, interp_base: usize) -> usize { 53 | // create a new stack for the application and set it up just like the kernel does 54 | 55 | // first, allocate the new stack area and give it 256KB of memory (just a random value I chose) 56 | let stack_flags = { 57 | let mut map_flags = MapFlags::empty(); 58 | map_flags.insert(MapFlags::MAP_PRIVATE); 59 | map_flags.insert(MapFlags::MAP_ANONYMOUS); 60 | map_flags.insert(MapFlags::MAP_STACK); 61 | 62 | map_flags 63 | }; 64 | 65 | 66 | let stack_prot = { 67 | let mut prot_flags = ProtFlags::empty(); 68 | prot_flags.insert(ProtFlags::PROT_WRITE); 69 | prot_flags.insert(ProtFlags::PROT_READ); 70 | 71 | prot_flags 72 | }; 73 | 74 | let stack_size = 1024 * 256; 75 | let stack_end = unsafe { 76 | mmap(0 as *mut c_void, stack_size, stack_prot, stack_flags, -1, 0) 77 | .expect("Failed to allocate stack!") 78 | }; 79 | 80 | // the stack grows downward, so setup a stack pointer to the beginnings 81 | let mut stack_pointer = stack_end as usize + stack_size; 82 | 83 | // 16 byte align the Stack pointer 84 | stack_pointer = (stack_pointer + 15) & (!15); 85 | 86 | // Copy the current environment onto the stack! 87 | let mut env: Vec = Vec::new(); 88 | for (env_name, env_val) in std::env::vars() { 89 | // format the environment variable as it would actually look like in memory and explicitly add a 0byte 90 | let env_var = format!("{}={}\0", env_name, env_val); 91 | stack_pointer -= env_var.len(); 92 | env.push(stack_pointer); 93 | write_data(stack_pointer, &env_var.as_bytes()) 94 | } 95 | 96 | 97 | // copy the contents of the program arguments onto the stack and build an argv[] pointer array 98 | // ignore argv[1], as it is the program to be loaded and is interpreted by this program itself 99 | let mut argv: Vec = Vec::new(); 100 | let mut args: Vec = std::env::args().collect(); 101 | args.remove(1); 102 | for arg in args.iter_mut().rev() { 103 | stack_pointer -= arg.len() + 1; // +1 for a NULLBYTE 104 | argv.push(stack_pointer); 105 | let argv_bytes = unsafe { 106 | arg.as_mut_vec() 107 | }; 108 | argv_bytes.push(0); // push a nullbyte 109 | write_data(stack_pointer, &argv_bytes); 110 | } 111 | 112 | 113 | // after copying the argument contents and environment variables, 16 byte align the stack pointer 114 | stack_pointer &= !0xf; 115 | 116 | 117 | // place the platform string on the stack 118 | stack_pointer -= "x86_64".len() + 1; 119 | write_data(stack_pointer, "x86_64\0".as_bytes()); 120 | 121 | // the next item are 16bytes of random data as a PRNG seed 122 | let seed_bytes = rand::thread_rng().gen::<[u8; 16]>(); 123 | stack_pointer -= seed_bytes.len(); 124 | write_data(stack_pointer, &seed_bytes); 125 | let prng_pointer = stack_pointer; 126 | 127 | 128 | // allocate space for the AUX vectors 129 | stack_pointer -= 0x120; 130 | 131 | // make space for the argv and envp char ** arrays + a NULL terminator for each of them 132 | let pointers = (argv.len() + 1) + (env.len() + 1) + 1; 133 | stack_pointer -= pointers * 8; 134 | 135 | // align the pointer again 136 | stack_pointer &= !15; 137 | 138 | // the current stack pointer is the one we will return! 139 | let rsp = stack_pointer; 140 | 141 | 142 | // put argc on the stack 143 | write_pointer(&mut stack_pointer, argv.len()); 144 | 145 | // write each of the argument pointers to the stack 146 | for arg in argv.iter().rev() { 147 | write_pointer(&mut stack_pointer, *arg); 148 | } 149 | 150 | // place a NULL pointer to signal this is the end of the argv array 151 | write_pointer(&mut stack_pointer, 0x0); 152 | 153 | 154 | // write each of the environemnt pointers to the stack 155 | for e in env.iter() { 156 | write_pointer(&mut stack_pointer, *e); 157 | } 158 | 159 | // place a NULL pointer to signify that this is the end of the environment pointer 160 | write_pointer(&mut stack_pointer, 0x0); 161 | 162 | 163 | // next are the AUX information needed for the ELF Interpreter and/or __libc_start_main 164 | // we can derive most of them via libc's getauxval() 165 | unsafe { 166 | 167 | // VDSO is a shared object mapped into userspace by the kernel that can be used by libc 168 | write_aux_val(&mut stack_pointer, AT_SYSINFO_EHDR, libc::getauxval(AT_SYSINFO_EHDR)); 169 | 170 | // some generic architecture / processor specific value we derive from our own auxvector 171 | write_aux_val(&mut stack_pointer, AT_HWCAP, libc::getauxval(AT_HWCAP)); 172 | write_aux_val(&mut stack_pointer, AT_PAGESZ, libc::getauxval(AT_PAGESZ)); 173 | write_aux_val(&mut stack_pointer, AT_CLKTCK, libc::getauxval(AT_CLKTCK)); 174 | write_aux_val(&mut stack_pointer, AT_HWCAP2, libc::getauxval(AT_HWCAP2)); 175 | 176 | // tell the CSU where to find the program headers of the binary to be loaded 177 | // to do this, we pass a pointer to them, the size of an entry and the number of entries 178 | write_aux_val(&mut stack_pointer, AT_PHDR, (load_address + load_info.pheader_off) as u64); 179 | write_aux_val(&mut stack_pointer, AT_PHENT, PHENT_SIZE as u64); 180 | write_aux_val(&mut stack_pointer, AT_PHNUM, load_info.pheader_num as u64); 181 | 182 | // base is the base address of the ELF Interpreter (ld.so) 183 | write_aux_val(&mut stack_pointer, AT_BASE, interp_base as u64); 184 | 185 | // the flags are hardcoded 0 by the kernel 186 | write_aux_val(&mut stack_pointer, AT_FLAGS, 0x0); 187 | 188 | // the entry point of this binary. It is used by (ld.so) to jump to the binary once relocations 189 | // have been performed 190 | // this might be relative or absolute, dependeing on the type of binary that is loaded! 191 | match load_info.etype { 192 | ElfType::ElfExec => write_aux_val(&mut stack_pointer, AT_ENTRY, load_info.entry_point as u64), 193 | ElfType::ElfDyn => write_aux_val(&mut stack_pointer, AT_ENTRY, (load_address + load_info.entry_point) as u64) 194 | } 195 | 196 | 197 | // pass some generic info about the user running the process deriving from our own auxval 198 | write_aux_val(&mut stack_pointer, AT_UID, libc::getauxval(AT_UID)); 199 | write_aux_val(&mut stack_pointer, AT_EUID, libc::getauxval(AT_EUID)); 200 | write_aux_val(&mut stack_pointer, AT_GID, libc::getauxval(AT_GID)); 201 | write_aux_val(&mut stack_pointer, AT_EGID, libc::getauxval(AT_EGID)); 202 | write_aux_val(&mut stack_pointer, AT_SECURE, libc::getauxval(AT_SECURE)); 203 | 204 | // pass a pointer to the initial 16 bytes of random memory for use by libc 205 | write_aux_val(&mut stack_pointer, AT_RANDOM, prng_pointer as u64); 206 | 207 | 208 | // store a pointer to the program name here 209 | write_aux_val(&mut stack_pointer, AT_EXECFN, argv[0] as u64); 210 | 211 | // end the aux vector 212 | write_aux_val(&mut stack_pointer, AT_NULL, 0x0); 213 | }; 214 | 215 | // that's it! We should now have a valid and clean stack for executing the new program 216 | // return the current stack pointer so that we can return it! 217 | rsp 218 | } 219 | 220 | 221 | 222 | fn write_data(sp: usize, data: &[u8]) { 223 | unsafe { 224 | libc::memcpy(sp as *mut c_void, data.as_ptr() as *const c_void, data.len()); 225 | } 226 | } 227 | 228 | // writes an AUX val to the stack and advances the stack pointer 229 | fn write_aux_val(sp: &mut usize, aux_id: u64, aux_val: u64) { 230 | 231 | write_pointer(sp, aux_id as usize); 232 | write_pointer(sp, aux_val as usize); 233 | 234 | } 235 | 236 | fn write_pointer(sp: &mut usize, value: usize) { 237 | let ptr = *sp as *mut usize; 238 | unsafe { 239 | *ptr = value 240 | }; 241 | *sp += 8; 242 | } --------------------------------------------------------------------------------