#![cfg(test)]
const EHDR: usize = 64;
const PHDR: usize = 56;
const SHDR: usize = 64;
const P_DYNSTR: u64 = 0x00e8;
const P_DYNSYM: u64 = 0x00f0;
const P_RELA: u64 = 0x0120;
const P_DYNAMIC: u64 = 0x0138;
const P_PLT: u64 = 0x01c0;
const P_TEXT: u64 = 0x01d0;
const P_GOT: u64 = 0x01e0;
const P_SHSTR: u64 = 0x01e8;
const P_SHDRS: u64 = 0x0208;
const FILE_END: u64 = P_SHDRS + 5 * SHDR as u64;
const DYNSTR: &[u8] = b"\0strcpy\0";
const SHSTR: &[u8] = b"\0.plt\0.text\0.got.plt\0.shstrtab\0";
pub fn elf_with_plt_call() -> Vec<u8> {
let mut f = vec![0u8; FILE_END as usize];
f[0..4].copy_from_slice(b"\x7fELF");
f[4] = 2; f[5] = 1; f[6] = 1; put16(&mut f, 16, 2); put16(&mut f, 18, 62); put32(&mut f, 20, 1); put64(&mut f, 24, P_TEXT); put64(&mut f, 32, EHDR as u64); put64(&mut f, 40, P_SHDRS); put16(&mut f, 52, EHDR as u16); put16(&mut f, 54, PHDR as u16); put16(&mut f, 56, 3); put16(&mut f, 58, SHDR as u16); put16(&mut f, 60, 5); put16(&mut f, 62, 4);
let ph = EHDR;
phdr(&mut f, ph, 1, 7, 0, 0, FILE_END, 0x1000); phdr(&mut f, ph + PHDR, 2, 6, P_DYNAMIC, P_DYNAMIC, 128, 8); phdr(&mut f, ph + 2 * PHDR, 0x6474_e551, 6, 0, 0, 0, 16);
let o = P_DYNSTR as usize;
f[o..o + DYNSTR.len()].copy_from_slice(DYNSTR);
let s = P_DYNSYM as usize + 24;
put32(&mut f, s, 1); f[s + 4] = 0x12; put16(&mut f, s + 6, 0);
let r = P_RELA as usize;
put64(&mut f, r, P_GOT); put64(&mut f, r + 8, (1u64 << 32) | 7);
let dyn_entries: [(u64, u64); 8] = [
(5, P_DYNSTR), (10, 8), (6, P_DYNSYM), (11, 24), (23, P_RELA), (2, 24), (20, 7), (0, 0), ];
for (i, (tag, val)) in dyn_entries.iter().enumerate() {
let d = P_DYNAMIC as usize + i * 16;
put64(&mut f, d, *tag);
put64(&mut f, d + 8, *val);
}
let p = P_PLT as usize;
f[p] = 0xff;
f[p + 1] = 0x25;
let disp = (P_GOT as i64 - (P_PLT as i64 + 6)) as i32;
f[p + 2..p + 6].copy_from_slice(&disp.to_le_bytes());
let t = P_TEXT as usize;
f[t] = 0xe8;
let rel = (P_PLT as i64 - (P_TEXT as i64 + 5)) as i32;
f[t + 1..t + 5].copy_from_slice(&rel.to_le_bytes());
f[t + 5] = 0xc3;
let sh = P_SHSTR as usize;
f[sh..sh + SHSTR.len()].copy_from_slice(SHSTR);
const PROGBITS: u32 = 1;
const STRTAB: u32 = 3;
const ALLOC_EXEC: u64 = 0x2 | 0x4;
const ALLOC_WRITE: u64 = 0x2 | 0x1;
let sd = P_SHDRS as usize;
shdr(&mut f, sd + SHDR, 1, PROGBITS, ALLOC_EXEC, P_PLT, 16); shdr(&mut f, sd + 2 * SHDR, 6, PROGBITS, ALLOC_EXEC, P_TEXT, 16); shdr(&mut f, sd + 3 * SHDR, 12, PROGBITS, ALLOC_WRITE, P_GOT, 8); shdr(&mut f, sd + 4 * SHDR, 21, STRTAB, 0, 0, SHSTR.len() as u64); put64(&mut f, sd + 4 * SHDR + 24, P_SHSTR);
f
}
fn put16(f: &mut [u8], at: usize, v: u16) {
f[at..at + 2].copy_from_slice(&v.to_le_bytes());
}
fn put32(f: &mut [u8], at: usize, v: u32) {
f[at..at + 4].copy_from_slice(&v.to_le_bytes());
}
fn put64(f: &mut [u8], at: usize, v: u64) {
f[at..at + 8].copy_from_slice(&v.to_le_bytes());
}
fn bl_word(pc: u64, target: u64) -> u32 {
let off = target.wrapping_sub(pc);
0x9400_0000 | ((off >> 2) as u32 & 0x03ff_ffff)
}
pub fn elf_aarch64_call() -> Vec<u8> {
let text_len = 0x1cusize;
let shstr = b"\0.text\0.shstrtab\0";
const SHDRS: u64 = 0x0208;
const SHSTR_OFF: u64 = 0x01f0;
let file_end = SHDRS + 3 * SHDR as u64;
let mut f = vec![0u8; file_end as usize];
f[0..4].copy_from_slice(b"\x7fELF");
f[4] = 2; f[5] = 1; f[6] = 1; put16(&mut f, 16, 2); put16(&mut f, 18, 183); put32(&mut f, 20, 1); put64(&mut f, 24, P_TEXT); put64(&mut f, 32, EHDR as u64); put64(&mut f, 40, SHDRS); put16(&mut f, 52, EHDR as u16); put16(&mut f, 54, PHDR as u16); put16(&mut f, 56, 2); put16(&mut f, 58, SHDR as u16); put16(&mut f, 60, 3); put16(&mut f, 62, 2);
phdr(&mut f, EHDR, 1, 7, 0, 0, file_end, 0x1000); phdr(&mut f, EHDR + PHDR, 0x6474_e551, 6, 0, 0, 0, 16);
let t = P_TEXT as usize;
put32(&mut f, t, bl_word(P_TEXT, P_TEXT + 0x10));
put32(&mut f, t + 4, 0xd280_0540); put32(&mut f, t + 8, 0xd65f_03c0); put32(&mut f, t + 12, 0xd503_201f); put32(&mut f, t + 16, 0x8b02_0020); put32(&mut f, t + 20, 0xf940_0001); put32(&mut f, t + 24, 0xd65f_03c0);
let s = SHSTR_OFF as usize;
f[s..s + shstr.len()].copy_from_slice(shstr);
const PROGBITS: u32 = 1;
const STRTAB: u32 = 3;
const ALLOC_EXEC: u64 = 0x2 | 0x4;
let sd = SHDRS as usize;
shdr(
&mut f,
sd + SHDR,
1,
PROGBITS,
ALLOC_EXEC,
P_TEXT,
text_len as u64,
); shdr(&mut f, sd + 2 * SHDR, 7, STRTAB, 0, 0, shstr.len() as u64); put64(&mut f, sd + 2 * SHDR + 24, SHSTR_OFF);
f
}
pub fn elf_aarch64_plt_call() -> Vec<u8> {
let mut f = vec![0u8; FILE_END as usize];
f[0..4].copy_from_slice(b"\x7fELF");
f[4] = 2; f[5] = 1; f[6] = 1; put16(&mut f, 16, 2); put16(&mut f, 18, 183); put32(&mut f, 20, 1); put64(&mut f, 24, P_TEXT); put64(&mut f, 32, EHDR as u64); put64(&mut f, 40, P_SHDRS); put16(&mut f, 52, EHDR as u16); put16(&mut f, 54, PHDR as u16); put16(&mut f, 56, 3); put16(&mut f, 58, SHDR as u16); put16(&mut f, 60, 5); put16(&mut f, 62, 4);
let ph = EHDR;
phdr(&mut f, ph, 1, 7, 0, 0, FILE_END, 0x1000); phdr(&mut f, ph + PHDR, 2, 6, P_DYNAMIC, P_DYNAMIC, 128, 8); phdr(&mut f, ph + 2 * PHDR, 0x6474_e551, 6, 0, 0, 0, 16);
let o = P_DYNSTR as usize;
f[o..o + DYNSTR.len()].copy_from_slice(DYNSTR);
let s = P_DYNSYM as usize + 24;
put32(&mut f, s, 1); f[s + 4] = 0x12; put16(&mut f, s + 6, 0);
let r = P_RELA as usize;
put64(&mut f, r, P_GOT); put64(&mut f, r + 8, (1u64 << 32) | 1026);
let dyn_entries: [(u64, u64); 8] = [
(5, P_DYNSTR), (10, 8), (6, P_DYNSYM), (11, 24), (23, P_RELA), (2, 24), (20, 7), (0, 0), ];
for (i, (tag, val)) in dyn_entries.iter().enumerate() {
let d = P_DYNAMIC as usize + i * 16;
put64(&mut f, d, *tag);
put64(&mut f, d + 8, *val);
}
let p = P_PLT as usize;
put32(&mut f, p, adrp_word(P_PLT, P_GOT & !0xfff, 17));
put32(&mut f, p + 4, ldr_imm_word(17, 16, P_GOT & 0xfff));
put32(&mut f, p + 8, br_word(16));
let t = P_TEXT as usize;
put32(&mut f, t, bl_word(P_TEXT, P_PLT));
put32(&mut f, t + 4, 0xd65f_03c0);
let sh = P_SHSTR as usize;
f[sh..sh + SHSTR.len()].copy_from_slice(SHSTR);
const PROGBITS: u32 = 1;
const STRTAB: u32 = 3;
const ALLOC_EXEC: u64 = 0x2 | 0x4;
const ALLOC_WRITE: u64 = 0x2 | 0x1;
let sd = P_SHDRS as usize;
shdr(&mut f, sd + SHDR, 1, PROGBITS, ALLOC_EXEC, P_PLT, 16); shdr(&mut f, sd + 2 * SHDR, 6, PROGBITS, ALLOC_EXEC, P_TEXT, 16); shdr(&mut f, sd + 3 * SHDR, 12, PROGBITS, ALLOC_WRITE, P_GOT, 8); shdr(&mut f, sd + 4 * SHDR, 21, STRTAB, 0, 0, SHSTR.len() as u64); put64(&mut f, sd + 4 * SHDR + 24, P_SHSTR);
f
}
fn adrp_word(pc: u64, page: u64, rd: u32) -> u32 {
let off = (page as i64 - (pc & !0xfff) as i64) >> 12;
let immlo = (off & 3) as u32;
let immhi = ((off >> 2) & 0x7ffff) as u32;
0x9000_0000 | (immhi << 5) | (immlo << 29) | rd
}
fn ldr_imm_word(rn: u32, rt: u32, offs: u64) -> u32 {
0xf940_0000 | (rt & 0x1f) | ((rn & 0x1f) << 5) | (((offs >> 3) as u32 & 0xfff) << 10)
}
fn br_word(rn: u32) -> u32 {
0xd61f_0000 | ((rn & 0x1f) << 5)
}
pub const PE_SIG: usize = 0x80;
pub const COFF: usize = PE_SIG + 0x04;
pub const OPT: usize = COFF + 0x14;
pub const DATA_DIRS: usize = OPT + 0x70;
pub const SECTION_TABLE: usize = DATA_DIRS + 16 * 8;
pub const PE_TEXT: u64 = 0x1000;
pub const PE_IDATA: u64 = 0x2000;
pub const PE_IMPORT_DIR: u64 = 0x2000;
pub const PE_INT: u64 = 0x2100;
pub const PE_IAT: u64 = 0x2210;
pub const PE_DLL_NAME: u64 = 0x2200;
pub const PE_HINT_NAME: u64 = 0x2300;
pub const PE_LOAD_CONFIG: u64 = 0x2400;
const PE_FILE_END: usize = 0x2500;
pub fn pe_with_iat_call() -> Vec<u8> {
let mut f = vec![0u8; PE_FILE_END];
f[0..2].copy_from_slice(b"MZ");
put32(&mut f, 0x3c, PE_SIG as u32);
f[PE_SIG..PE_SIG + 4].copy_from_slice(b"PE\0\0");
put16(&mut f, COFF, 0x8664); put16(&mut f, COFF + 0x02, 2); put32(&mut f, COFF + 0x04, 1); put16(&mut f, COFF + 0x10, 0xF0); put16(&mut f, COFF + 0x12, 0x22);
put16(&mut f, OPT, 0x20b); put32(&mut f, OPT + 0x04, 0x100); put32(&mut f, OPT + 0x08, 0x500); put32(&mut f, OPT + 0x10, PE_TEXT as u32); put32(&mut f, OPT + 0x14, PE_TEXT as u32); put64(&mut f, OPT + 0x18, 0); put32(&mut f, OPT + 0x20, 0x1000); put32(&mut f, OPT + 0x24, 0x200); put16(&mut f, OPT + 0x28, 6); put16(&mut f, OPT + 0x30, 6); put32(&mut f, OPT + 0x38, 0x3000); put32(&mut f, OPT + 0x3c, 0x200); put16(&mut f, OPT + 0x44, 3); put16(&mut f, OPT + 0x46, 0x40); put64(&mut f, OPT + 0x48, 0x10_0000); put64(&mut f, OPT + 0x50, 0x1000); put64(&mut f, OPT + 0x58, 0x10_0000); put64(&mut f, OPT + 0x60, 0x1000); put32(&mut f, OPT + 0x6c, 16); let dir = |i: usize| OPT + 0x70 + i * 8;
put32(&mut f, dir(1), PE_IMPORT_DIR as u32); put32(&mut f, dir(1) + 4, 0x30);
put32(&mut f, dir(10), PE_LOAD_CONFIG as u32); put32(&mut f, dir(10) + 4, 0x40);
put32(&mut f, dir(12), PE_IAT as u32); put32(&mut f, dir(12) + 4, 0x10);
const SECTION: u32 = 0x6000_0020; let sh = |i: usize| SECTION_TABLE + i * 40;
f[sh(0)..sh(0) + 8].copy_from_slice(b".text\x00\x00\x00");
put32(&mut f, sh(0) + 0x08, 0x200); put32(&mut f, sh(0) + 0x0c, PE_TEXT as u32); put32(&mut f, sh(0) + 0x10, 0x200); put32(&mut f, sh(0) + 0x14, PE_TEXT as u32); put32(&mut f, sh(0) + 0x24, SECTION); f[sh(1)..sh(1) + 8].copy_from_slice(b".idata\x00\x00");
put32(&mut f, sh(1) + 0x08, 0x500);
put32(&mut f, sh(1) + 0x0c, PE_IDATA as u32);
put32(&mut f, sh(1) + 0x10, 0x500);
put32(&mut f, sh(1) + 0x14, PE_IDATA as u32);
put32(&mut f, sh(1) + 0x24, 0xC000_0040);
let desc = PE_IMPORT_DIR as usize;
put32(&mut f, desc, PE_INT as u32); put32(&mut f, desc + 0x0c, PE_DLL_NAME as u32); put32(&mut f, desc + 0x10, PE_IAT as u32);
put32(&mut f, PE_INT as usize, PE_HINT_NAME as u32);
put32(&mut f, PE_INT as usize + 8, PE_HINT_NAME as u32 + 0x20);
f[PE_DLL_NAME as usize..PE_DLL_NAME as usize + 13].copy_from_slice(b"kernel32.dll\0");
put16(&mut f, PE_HINT_NAME as usize, 1);
f[PE_HINT_NAME as usize + 2..PE_HINT_NAME as usize + 14].copy_from_slice(b"ExitProcess\0");
put16(&mut f, PE_HINT_NAME as usize + 0x20, 2);
f[PE_HINT_NAME as usize + 0x22..PE_HINT_NAME as usize + 0x2e].copy_from_slice(b"MessageBoxA\0");
put32(&mut f, PE_LOAD_CONFIG as usize, 0x60); put64(
&mut f,
PE_LOAD_CONFIG as usize + 0x58,
0xdead_beef_cafe_f00d,
);
let t = PE_TEXT as usize;
f[t..t + 4].copy_from_slice(&[0x48, 0x83, 0xec, 0x28]); f[t + 4] = 0xff;
f[t + 5] = 0x15;
let disp = (PE_IAT as i64 - (PE_TEXT as i64 + 0x0a)) as i32;
f[t + 6..t + 0x0a].copy_from_slice(&disp.to_le_bytes());
f[t + 0x0a..t + 0x0e].copy_from_slice(&[0x48, 0x83, 0xc4, 0x28]); f[t + 0x0e] = 0xc3;
f
}
pub fn macho_with_bind() -> Vec<u8> {
let mut f = vec![0u8; 0x3000];
const PATH: &str = "/usr/lib/libSystem.B.dylib";
let path_len = PATH.len() + 1;
let path_padded = (path_len + 7) & !7;
let seg_size = 72 + 80; let dylib_size = 24 + path_padded; let dyld_size = 48; let main_size = 24; let total_cmds = seg_size + dylib_size + dyld_size + main_size;
let seg = 32usize;
let dylib = seg + seg_size;
let dyld = dylib + dylib_size;
let main = dyld + dyld_size;
put32(&mut f, 0, 0xfeed_facf); put32(&mut f, 4, 0x0100_0007); put32(&mut f, 8, 0x3); put32(&mut f, 12, 0x2); put32(&mut f, 16, 4); put32(&mut f, 20, total_cmds as u32); put32(&mut f, 24, 0x0020_0000);
put32(&mut f, seg, 0x19); put32(&mut f, seg + 4, seg_size as u32);
f[seg + 8..seg + 24].copy_from_slice(b"__TEXT\0\0\0\0\0\0\0\0\0\0");
put64(&mut f, seg + 24, 0x1000); put64(&mut f, seg + 32, 0x1000); put64(&mut f, seg + 40, 0x1000); put64(&mut f, seg + 48, 0x10); put32(&mut f, seg + 56, 0x5); put32(&mut f, seg + 60, 0x5); put32(&mut f, seg + 64, 1); put32(&mut f, seg + 68, 0); let s = seg + 72;
f[s..s + 16].copy_from_slice(b"__text\0\0\0\0\0\0\0\0\0\0");
f[s + 16..s + 32].copy_from_slice(b"__TEXT\0\0\0\0\0\0\0\0\0\0");
put64(&mut f, s + 32, 0x1000); put64(&mut f, s + 40, 0x10); put32(&mut f, s + 48, 0x1000); put32(&mut f, s + 52, 0x2); put32(&mut f, s + 56, 0); put32(&mut f, s + 60, 0); put32(&mut f, s + 64, 0x0000_0400);
put32(&mut f, dylib, 0xc); put32(&mut f, dylib + 4, dylib_size as u32);
put32(&mut f, dylib + 8, 24); put32(&mut f, dylib + 12, 0); put32(&mut f, dylib + 16, 0x10000); put32(&mut f, dylib + 20, 0x10000); f[dylib + 24..dylib + 24 + PATH.len()].copy_from_slice(PATH.as_bytes());
put32(&mut f, dyld, 0x8000_0022);
put32(&mut f, dyld + 4, 0x30);
put32(&mut f, dyld + 8, 0); put32(&mut f, dyld + 12, 0); put32(&mut f, dyld + 16, 0x2200); put32(&mut f, dyld + 20, 8); put32(&mut f, dyld + 24, 0); put32(&mut f, dyld + 28, 0); put32(&mut f, dyld + 32, 0); put32(&mut f, dyld + 36, 0); put32(&mut f, dyld + 40, 0); put32(&mut f, dyld + 44, 0);
put32(&mut f, main, 0x8000_0028); put32(&mut f, main + 4, main_size as u32);
put64(&mut f, main + 8, 0x1000); put64(&mut f, main + 16, 0x1000);
let b = 0x2200usize;
f[b] = 0x40; f[b + 1..b + 6].copy_from_slice(b"puts\0");
f[b + 6] = 0x90; f[b + 7] = 0x00;
f[0x1000..0x1007].copy_from_slice(&[0x48, 0xc7, 0xc0, 0, 0, 0, 0]);
f[0x1007] = 0xc3;
f
}
#[allow(clippy::too_many_arguments)]
fn phdr(f: &mut [u8], at: usize, ty: u32, flags: u32, off: u64, va: u64, sz: u64, align: u64) {
put32(f, at, ty);
put32(f, at + 4, flags);
put64(f, at + 8, off);
put64(f, at + 16, va);
put64(f, at + 24, va); put64(f, at + 32, sz); put64(f, at + 40, sz); put64(f, at + 48, align);
}
fn shdr(f: &mut [u8], at: usize, name: u32, ty: u32, flags: u64, addr: u64, size: u64) {
put32(f, at, name);
put32(f, at + 4, ty);
put64(f, at + 8, flags);
put64(f, at + 16, addr);
put64(f, at + 24, addr); put64(f, at + 32, size);
put64(f, at + 48, 1); }
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{Format, SymKind};
#[test]
fn the_fixture_parses_as_an_elf() {
let bytes = elf_with_plt_call();
let bin = crate::formats::analyze("fixture.elf", &bytes).expect("parses");
assert_eq!(bin.format, Format::Elf);
assert_eq!(bin.bits, 64);
assert_eq!(bin.entry, P_TEXT);
assert!(bin.sections.iter().any(|s| s.name == ".plt" && s.exec));
}
#[test]
fn the_relocation_names_the_got_slot() {
let bytes = elf_with_plt_call();
let bin = crate::formats::analyze("fixture.elf", &bytes).unwrap();
let slot = bin
.symbols
.iter()
.find(|s| s.kind == SymKind::Import)
.expect("an import symbol from .rela.plt");
assert_eq!(slot.addr, P_GOT);
assert_eq!(slot.name, "strcpy");
}
#[test]
fn the_pe_fixture_parses_and_imports() {
let bytes = pe_with_iat_call();
let bin = crate::formats::analyze("fixture.exe", &bytes).expect("parses");
assert_eq!(bin.format, Format::Pe);
assert_eq!(bin.bits, 64);
assert_eq!(bin.entry, PE_TEXT);
assert_eq!(bin.image_base, 0);
assert!(bin.sections.iter().any(|s| s.name == ".text" && s.exec));
let lib = bin
.imports
.iter()
.find(|l| l.name == "kernel32.dll")
.expect("the DLL is imported");
assert_eq!(lib.functions, ["ExitProcess", "MessageBoxA"]);
let slot = bin
.symbols
.iter()
.find(|s| s.name == "kernel32!ExitProcess")
.expect("IAT slot symbol");
assert_eq!(slot.addr, PE_IAT);
assert_eq!(slot.kind, SymKind::Import);
}
#[test]
fn the_pe_load_config_is_parseable() {
let bytes = pe_with_iat_call();
let bin = crate::formats::analyze("fixture.exe", &bytes).unwrap();
let lc = bin.hardening.load_config.expect("load config parsed");
assert_eq!(lc.security_cookie, 0xdead_beef_cafe_f00d);
}
#[test]
fn the_pe_engine_names_the_iat_call() {
let bytes = pe_with_iat_call();
let bin = crate::formats::analyze("fixture.exe", &bytes).unwrap();
let an = crate::analysis::engine::analyze(&bin, &bytes, 1000, &crate::db::Db::default());
let refs = an.xrefs_to.get(&PE_IAT).expect("the slot is referenced");
assert!(refs
.iter()
.any(|x| crate::analysis::engine::XrefKind::Call == x.kind));
let caller = an.functions.iter().find(|f| f.addr == PE_TEXT).unwrap();
assert_eq!(caller.calls, vec![PE_IAT]);
}
#[test]
fn the_macho_fixture_parses_and_binds() {
let bytes = macho_with_bind();
let bin = crate::formats::analyze("fixture.bin", &bytes).expect("parses");
assert_eq!(bin.format, Format::MachO);
assert_eq!(bin.bits, 64);
assert_eq!(bin.entry, 0x1000);
assert!(bin
.sections
.iter()
.any(|s| s.name == "__TEXT,__text" && s.exec));
assert!(!bin.imports.is_empty(), "bind bytecode produced imports");
assert!(bin
.imports
.iter()
.flat_map(|l| l.functions.iter())
.any(|n| n == "puts"));
assert!(bin.libs.iter().any(|l| l.contains("libSystem")));
}
#[test]
fn the_macho_engine_entries_into_text() {
let bytes = macho_with_bind();
let bin = crate::formats::analyze("fixture.bin", &bytes).unwrap();
let an = crate::analysis::engine::analyze(&bin, &bytes, 1000, &crate::db::Db::default());
assert!(
an.functions.iter().any(|f| f.addr == 0x1000),
"entry function at 0x1000"
);
}
}