use std::collections::HashMap;
use std::path::Path;
use unicorn_engine::unicorn_const::{Arch, Mode, Prot};
use unicorn_engine::{RegisterX86, Unicorn};
use crate::disasm::{self, DisasmInstruction, DisasmStyle};
use crate::error::{Error, Result};
use crate::event_log::{EventKind, EventLog};
use crate::secret_scan::{SecretScanConfig, SecretScanner};
use crate::syscall_trace;
use crate::types::VirtAddr;
const STACK_BASE: u64 = 0x7FFF_0000_0000;
const STACK_SIZE: u64 = 8 * 1024 * 1024; const HEAP_BASE: u64 = 0x1000_0000;
const HEAP_SIZE: u64 = 16 * 1024 * 1024; const PAGE_SIZE: u64 = 0x1000;
const SYS_READ: u64 = 0;
const SYS_WRITE: u64 = 1;
const SYS_OPEN: u64 = 2;
const SYS_CLOSE: u64 = 3;
const SYS_FSTAT: u64 = 5;
const SYS_MMAP: u64 = 9;
const SYS_MPROTECT: u64 = 10;
const SYS_MUNMAP: u64 = 11;
const SYS_BRK: u64 = 12;
const SYS_RT_SIGACTION: u64 = 13;
const SYS_RT_SIGPROCMASK: u64 = 14;
const SYS_IOCTL: u64 = 16;
const SYS_ACCESS: u64 = 21;
const SYS_ALARM: u64 = 37;
const SYS_GETPID: u64 = 39;
const SYS_EXIT: u64 = 60;
const SYS_KILL: u64 = 62;
const SYS_UNAME: u64 = 63;
const SYS_GETTIMEOFDAY: u64 = 96;
const SYS_PTRACE: u64 = 101;
const SYS_GETUID: u64 = 102;
const SYS_GETGID: u64 = 104;
const SYS_GETEUID: u64 = 107;
const SYS_GETEGID: u64 = 108;
const SYS_ARCH_PRCTL: u64 = 158;
const SYS_GETTID: u64 = 186;
const SYS_CLOCK_GETTIME: u64 = 228;
const SYS_EXIT_GROUP: u64 = 231;
const SYS_OPENAT: u64 = 257;
const SYS_GETRANDOM: u64 = 318;
const SYS_SET_TID_ADDRESS: u64 = 218;
#[derive(Debug, Clone)]
struct EmulatedFd {
#[allow(dead_code)]
path: String,
data: Vec<u8>,
offset: usize,
}
#[derive(Debug, Clone)]
pub enum EmulatorStop {
Exited(i32),
InstructionLimit(u64),
Error(String),
Breakpoint(u64),
Running,
}
#[derive(Debug, Clone)]
pub struct EmulatorConfig {
pub max_instructions: u64,
pub trace_instructions: bool,
pub trace_syscalls: bool,
pub scan_secrets: bool,
pub fake_pid: u32,
}
impl Default for EmulatorConfig {
fn default() -> Self {
Self {
max_instructions: 10_000_000, trace_instructions: false,
trace_syscalls: true,
scan_secrets: true,
fake_pid: 1337,
}
}
}
pub struct Emulator {
config: EmulatorConfig,
elf_path: Option<std::path::PathBuf>,
event_log: EventLog,
secret_scanner: SecretScanner,
fds: HashMap<i32, EmulatedFd>,
next_fd: i32,
brk_current: u64,
mmap_next: u64,
instruction_count: u64,
stop_reason: EmulatorStop,
breakpoints: std::collections::HashSet<u64>,
stdout_buf: Vec<u8>,
stderr_buf: Vec<u8>,
}
impl Emulator {
pub fn new() -> Result<Self> {
Self::with_config(EmulatorConfig::default())
}
pub fn with_config(config: EmulatorConfig) -> Result<Self> {
Ok(Self {
config,
elf_path: None,
event_log: EventLog::new(),
secret_scanner: SecretScanner::new(SecretScanConfig {
scan_entropy: false, ..Default::default()
}),
fds: HashMap::new(),
next_fd: 3, brk_current: HEAP_BASE,
mmap_next: 0x4000_0000,
instruction_count: 0,
stop_reason: EmulatorStop::Running,
breakpoints: std::collections::HashSet::new(),
stdout_buf: Vec::new(),
stderr_buf: Vec::new(),
})
}
pub fn load_elf(&mut self, path: &Path) -> Result<()> {
if !path.exists() {
return Err(Error::Other(format!("file not found: {}", path.display())));
}
self.elf_path = Some(path.to_path_buf());
Ok(())
}
pub fn set_breakpoint(&mut self, addr: u64) {
self.breakpoints.insert(addr);
}
pub fn remove_breakpoint(&mut self, addr: u64) {
self.breakpoints.remove(&addr);
}
fn reset_state(&mut self) {
self.instruction_count = 0;
self.stop_reason = EmulatorStop::Running;
self.stdout_buf.clear();
self.stderr_buf.clear();
self.fds.clear();
self.next_fd = 3;
self.brk_current = HEAP_BASE;
self.mmap_next = 0x4000_0000;
}
pub fn run(&mut self) -> Result<EmulatorStop> {
self.reset_state();
let path = self
.elf_path
.as_ref()
.ok_or_else(|| Error::Other("no ELF loaded".into()))?
.clone();
let elf_data =
std::fs::read(&path).map_err(|e| Error::Other(format!("read ELF: {}", e)))?;
let elf = goblin::elf::Elf::parse(&elf_data)
.map_err(|e| Error::Other(format!("parse ELF: {}", e)))?;
let mut uc = Unicorn::new(Arch::X86, Mode::MODE_64)
.map_err(|e| Error::Other(format!("unicorn init: {:?}", e)))?;
self.load_segments(&mut uc, &elf, &elf_data)?;
uc.mem_map(
STACK_BASE - STACK_SIZE,
STACK_SIZE,
Prot::READ | Prot::WRITE,
)
.map_err(|e| Error::Other(format!("map stack: {:?}", e)))?;
let sp = STACK_BASE - 0x100; self.setup_stack(&mut uc, sp, &elf)?;
uc.mem_map(HEAP_BASE, HEAP_SIZE, Prot::READ | Prot::WRITE)
.map_err(|e| Error::Other(format!("map heap: {:?}", e)))?;
uc.reg_write(RegisterX86::RSP, sp)
.map_err(|e| Error::Other(format!("set RSP: {:?}", e)))?;
uc.reg_write(RegisterX86::RBP, 0)
.map_err(|e| Error::Other(format!("set RBP: {:?}", e)))?;
let entry = elf.entry;
self.stop_reason = EmulatorStop::Running;
self.execute_loop(&mut uc, entry)?;
Ok(self.stop_reason.clone())
}
pub fn run_shellcode(&mut self, code: &[u8], base: u64) -> Result<EmulatorStop> {
self.reset_state();
let mut uc = Unicorn::new(Arch::X86, Mode::MODE_64)
.map_err(|e| Error::Other(format!("unicorn init: {:?}", e)))?;
let code_size = align_up(code.len() as u64, PAGE_SIZE);
uc.mem_map(base, code_size, Prot::ALL)
.map_err(|e| Error::Other(format!("map code: {:?}", e)))?;
uc.mem_write(base, code)
.map_err(|e| Error::Other(format!("write code: {:?}", e)))?;
uc.mem_map(
STACK_BASE - STACK_SIZE,
STACK_SIZE,
Prot::READ | Prot::WRITE,
)
.map_err(|e| Error::Other(format!("map stack: {:?}", e)))?;
let sp = STACK_BASE - 0x100;
uc.reg_write(RegisterX86::RSP, sp)
.map_err(|e| Error::Other(format!("set RSP: {:?}", e)))?;
uc.mem_map(HEAP_BASE, HEAP_SIZE, Prot::READ | Prot::WRITE)
.map_err(|e| Error::Other(format!("map heap: {:?}", e)))?;
self.stop_reason = EmulatorStop::Running;
self.execute_loop(&mut uc, base)?;
Ok(self.stop_reason.clone())
}
fn execute_loop(&mut self, uc: &mut Unicorn<()>, start: u64) -> Result<()> {
let mut pc = start;
loop {
if self.config.max_instructions > 0
&& self.instruction_count >= self.config.max_instructions
{
self.stop_reason = EmulatorStop::InstructionLimit(self.instruction_count);
return Ok(());
}
if self.breakpoints.contains(&pc) && self.instruction_count > 0 {
self.stop_reason = EmulatorStop::Breakpoint(pc);
self.event_log.record(EventKind::BreakpointHit {
addr: VirtAddr(pc),
function: None,
location: None,
});
return Ok(());
}
let code = match uc.mem_read_as_vec(pc, 15) {
Ok(c) => c,
Err(_) => {
self.stop_reason =
EmulatorStop::Error(format!("cannot read instruction at 0x{:x}", pc));
return Ok(());
}
};
if code.len() >= 2 && code[0] == 0x0F && code[1] == 0x05 {
self.handle_syscall(uc)?;
if !matches!(self.stop_reason, EmulatorStop::Running) {
return Ok(());
}
pc = uc
.reg_read(RegisterX86::RIP)
.map_err(|e| Error::Other(format!("read RIP: {:?}", e)))?;
pc += 2;
uc.reg_write(RegisterX86::RIP, pc)
.map_err(|e| Error::Other(format!("set RIP: {:?}", e)))?;
self.instruction_count += 1;
continue;
}
let result = uc.emu_start(pc, 0, 0, 1);
match result {
Ok(()) => {}
Err(e) => {
self.stop_reason =
EmulatorStop::Error(format!("execution error at 0x{:x}: {:?}", pc, e));
return Ok(());
}
}
self.instruction_count += 1;
if self.config.trace_instructions {
let instrs = disasm::disassemble(&code, VirtAddr(pc), 1, DisasmStyle::Intel);
if let Some(instr) = instrs.first() {
self.event_log.record(EventKind::BreakpointHit {
addr: VirtAddr(pc),
function: None,
location: Some(instr.text.clone()),
});
}
}
pc = uc
.reg_read(RegisterX86::RIP)
.map_err(|e| Error::Other(format!("read RIP: {:?}", e)))?;
}
}
fn handle_syscall(&mut self, uc: &mut Unicorn<()>) -> Result<()> {
let rax = reg(uc, RegisterX86::RAX)?;
let rdi = reg(uc, RegisterX86::RDI)?;
let rsi = reg(uc, RegisterX86::RSI)?;
let rdx = reg(uc, RegisterX86::RDX)?;
let r10 = reg(uc, RegisterX86::R10)?;
let r8 = reg(uc, RegisterX86::R8)?;
let _r9 = reg(uc, RegisterX86::R9)?;
let args = [rdi, rsi, rdx, r10, r8, _r9];
if self.config.trace_syscalls {
let read_string = |addr: u64| -> crate::error::Result<String> {
match uc.mem_read_as_vec(addr, 256) {
Ok(bytes) => {
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
Ok(String::from_utf8_lossy(&bytes[..end]).into_owned())
}
Err(_) => Err(Error::Other("read failed".into())),
}
};
let formatted = syscall_trace::format_syscall_entry(rax, &args, &read_string);
self.event_log.record(EventKind::SyscallEntry {
number: rax,
name: crate::syscall::name(rax).unwrap_or("unknown").into(),
args_formatted: formatted,
});
}
let retval: i64 = match rax {
SYS_READ => self.sys_read(uc, rdi as i32, rsi, rdx as usize),
SYS_WRITE => self.sys_write(uc, rdi as i32, rsi, rdx as usize),
SYS_OPEN | SYS_OPENAT => {
let path_addr = if rax == SYS_OPENAT { rsi } else { rdi };
self.sys_open(uc, path_addr)
}
SYS_CLOSE => self.sys_close(rdi as i32),
SYS_FSTAT => 0, SYS_MMAP => self.sys_mmap(uc, rdi, rsi, rdx, r10),
SYS_MPROTECT => 0, SYS_MUNMAP => 0, SYS_BRK => self.sys_brk(rdi),
SYS_RT_SIGACTION | SYS_RT_SIGPROCMASK => 0, SYS_IOCTL => -25, SYS_ACCESS => -2, SYS_ALARM => {
self.event_log.record(EventKind::AntiDebugDetected {
technique: "alarm() watchdog".into(),
addr: None,
bypassed: true,
detail: format!("neutralized alarm({})", rdi),
});
0
}
SYS_GETPID | SYS_GETTID => self.config.fake_pid as i64,
SYS_KILL => {
let target_pid = rdi as i32;
if target_pid == self.config.fake_pid as i32 || target_pid == 0 {
self.event_log.record(EventKind::AntiDebugDetected {
technique: "self-signal (kill)".into(),
addr: None,
bypassed: true,
detail: format!("suppressed kill({}, {})", rdi, rsi),
});
0
} else {
-1 }
}
SYS_UNAME => self.sys_uname(uc, rdi),
SYS_GETTIMEOFDAY => self.sys_gettimeofday(uc, rdi),
SYS_PTRACE => {
self.event_log.record(EventKind::AntiDebugDetected {
technique: "ptrace(TRACEME)".into(),
addr: None,
bypassed: true,
detail: "emulated ptrace returned 0".into(),
});
0
}
SYS_GETUID | SYS_GETGID | SYS_GETEUID | SYS_GETEGID => 1000,
SYS_ARCH_PRCTL => {
if rdi == 0x1002 {
uc.reg_write(RegisterX86::FS_BASE, rsi)
.map_err(|e| Error::Other(format!("set FS_BASE: {:?}", e)))?;
0
} else {
-22 }
}
SYS_SET_TID_ADDRESS => self.config.fake_pid as i64,
SYS_CLOCK_GETTIME => self.sys_clock_gettime(uc, rsi),
SYS_GETRANDOM => self.sys_getrandom(uc, rdi, rsi as usize),
SYS_EXIT | SYS_EXIT_GROUP => {
self.stop_reason = EmulatorStop::Exited(rdi as i32);
self.event_log
.record(EventKind::ProcessExited { code: rdi as i32 });
return Ok(());
}
_ => {
-38
}
};
if self.config.trace_syscalls {
let retval_fmt = syscall_trace::format_syscall_return(rax, retval);
self.event_log.record(EventKind::SyscallExit {
number: rax,
name: crate::syscall::name(rax).unwrap_or("unknown").into(),
retval,
retval_formatted: retval_fmt,
});
}
if self.config.scan_secrets && (rax == SYS_WRITE || rax == 44) {
if let Ok(heap_data) = uc.mem_read_as_vec(HEAP_BASE, HEAP_SIZE as usize) {
self.secret_scanner
.scan_region(HEAP_BASE, &heap_data, &mut self.event_log);
}
}
uc.reg_write(RegisterX86::RAX, retval as u64)
.map_err(|e| Error::Other(format!("set RAX: {:?}", e)))?;
Ok(())
}
fn sys_read(&mut self, uc: &mut Unicorn<()>, fd: i32, buf: u64, count: usize) -> i64 {
if fd == 0 {
return 0; }
if let Some(efd) = self.fds.get_mut(&fd) {
let avail = efd.data.len().saturating_sub(efd.offset);
let n = count.min(avail);
if n > 0 {
let data = efd.data[efd.offset..efd.offset + n].to_vec();
efd.offset += n;
let _ = uc.mem_write(buf, &data);
}
n as i64
} else {
-9 }
}
fn sys_write(&mut self, uc: &mut Unicorn<()>, fd: i32, buf: u64, count: usize) -> i64 {
let count = count.min(4096);
let data = match uc.mem_read_as_vec(buf, count) {
Ok(d) => d,
Err(_) => return -14, };
match fd {
1 => self.stdout_buf.extend_from_slice(&data),
2 => self.stderr_buf.extend_from_slice(&data),
_ => {
if !self.fds.contains_key(&fd) {
return -9; }
}
}
data.len() as i64
}
fn sys_open(&mut self, uc: &mut Unicorn<()>, path_addr: u64) -> i64 {
let path = match read_cstring(uc, path_addr) {
Some(s) => s,
None => return -14, };
if path.contains("/proc/self/status") {
let fake_status = format!(
"Name:\temulated\nPid:\t{}\nTracerPid:\t0\nUid:\t1000\t1000\t1000\t1000\n",
self.config.fake_pid
);
let fd = self.next_fd;
self.next_fd += 1;
self.fds.insert(
fd,
EmulatedFd {
path,
data: fake_status.into_bytes(),
offset: 0,
},
);
self.event_log.record(EventKind::AntiDebugDetected {
technique: "/proc/self/status check".into(),
addr: None,
bypassed: true,
detail: format!("emulated open, fd={}, TracerPid=0", fd),
});
return fd as i64;
}
-2 }
fn sys_close(&mut self, fd: i32) -> i64 {
if fd <= 2 {
return 0; }
if self.fds.remove(&fd).is_some() {
0
} else {
-9
}
}
fn sys_brk(&mut self, addr: u64) -> i64 {
if addr == 0 {
return self.brk_current as i64;
}
if (HEAP_BASE..HEAP_BASE + HEAP_SIZE).contains(&addr) {
self.brk_current = addr;
return addr as i64;
}
self.brk_current as i64
}
fn sys_mmap(
&mut self,
uc: &mut Unicorn<()>,
addr: u64,
length: u64,
_prot: u64,
flags: u64,
) -> i64 {
let map_anonymous = 0x20;
if flags & map_anonymous == 0 {
return -22; }
let size = align_up(length, PAGE_SIZE);
let base = if addr != 0 { addr } else { self.mmap_next };
self.mmap_next = base + size;
match uc.mem_map(base, size, Prot::ALL) {
Ok(()) => base as i64,
Err(_) => -12, }
}
fn sys_uname(&self, uc: &mut Unicorn<()>, buf: u64) -> i64 {
let mut data = vec![0u8; 5 * 65];
let fields = [
"Linux", "emulated", "5.15.0-rnicro", "#1 SMP", "x86_64", ];
for (i, field) in fields.iter().enumerate() {
let offset = i * 65;
let bytes = field.as_bytes();
data[offset..offset + bytes.len()].copy_from_slice(bytes);
}
match uc.mem_write(buf, &data) {
Ok(()) => 0,
Err(_) => -14,
}
}
fn sys_gettimeofday(&self, uc: &mut Unicorn<()>, tv: u64) -> i64 {
if tv == 0 {
return 0;
}
let fixed_time: [u8; 16] = [0; 16]; match uc.mem_write(tv, &fixed_time) {
Ok(()) => 0,
Err(_) => -14,
}
}
fn sys_clock_gettime(&self, uc: &mut Unicorn<()>, tp: u64) -> i64 {
if tp == 0 {
return -14;
}
let fixed: [u8; 16] = [0; 16];
match uc.mem_write(tp, &fixed) {
Ok(()) => 0,
Err(_) => -14,
}
}
fn sys_getrandom(&self, uc: &mut Unicorn<()>, buf: u64, count: usize) -> i64 {
let count = count.min(4096);
let data: Vec<u8> = (0..count).map(|i| (i as u8).wrapping_mul(0x41)).collect();
match uc.mem_write(buf, &data) {
Ok(()) => count as i64,
Err(_) => -14,
}
}
fn load_segments(
&self,
uc: &mut Unicorn<()>,
elf: &goblin::elf::Elf,
data: &[u8],
) -> Result<()> {
for phdr in &elf.program_headers {
if phdr.p_type != goblin::elf::program_header::PT_LOAD {
continue;
}
let vaddr = phdr.p_vaddr & !0xFFF; let end = align_up(phdr.p_vaddr + phdr.p_memsz, PAGE_SIZE);
let size = end - vaddr;
let mut perms = Prot::NONE;
if phdr.p_flags & 1 != 0 {
perms |= Prot::EXEC;
}
if phdr.p_flags & 2 != 0 {
perms |= Prot::WRITE;
}
if phdr.p_flags & 4 != 0 {
perms |= Prot::READ;
}
uc.mem_map(vaddr, size, perms)
.map_err(|e| Error::Other(format!("map segment 0x{:x}: {:?}", vaddr, e)))?;
let file_offset = phdr.p_offset as usize;
let file_size = phdr.p_filesz as usize;
if file_offset + file_size <= data.len() {
uc.mem_write(phdr.p_vaddr, &data[file_offset..file_offset + file_size])
.map_err(|e| {
Error::Other(format!("write segment 0x{:x}: {:?}", phdr.p_vaddr, e))
})?;
}
}
Ok(())
}
fn setup_stack(&self, uc: &mut Unicorn<()>, sp: u64, _elf: &goblin::elf::Elf) -> Result<()> {
let mut cursor = sp;
write_u64(uc, cursor, 1)?;
cursor += 8;
let name_addr = cursor + 32; write_u64(uc, cursor, name_addr)?;
cursor += 8;
write_u64(uc, cursor, 0)?;
cursor += 8;
write_u64(uc, cursor, 0)?;
uc.mem_write(name_addr, b"./program\0")
.map_err(|e| Error::Other(format!("write argv: {:?}", e)))?;
Ok(())
}
pub fn event_log(&self) -> &EventLog {
&self.event_log
}
pub fn event_log_mut(&mut self) -> &mut EventLog {
&mut self.event_log
}
pub fn secret_scanner(&self) -> &SecretScanner {
&self.secret_scanner
}
pub fn stdout(&self) -> &[u8] {
&self.stdout_buf
}
pub fn stderr(&self) -> &[u8] {
&self.stderr_buf
}
pub fn stop_reason(&self) -> &EmulatorStop {
&self.stop_reason
}
pub fn instruction_count(&self) -> u64 {
self.instruction_count
}
pub fn config_mut(&mut self) -> &mut EmulatorConfig {
&mut self.config
}
pub fn read_memory(uc: &Unicorn<()>, addr: u64, len: usize) -> Result<Vec<u8>> {
uc.mem_read_as_vec(addr, len)
.map_err(|e| Error::Other(format!("read 0x{:x}: {:?}", addr, e)))
}
pub fn disassemble(
uc: &Unicorn<()>,
addr: u64,
count: usize,
) -> Result<Vec<DisasmInstruction>> {
let code = Self::read_memory(uc, addr, count * 15)?;
Ok(disasm::disassemble(
&code,
VirtAddr(addr),
count,
DisasmStyle::Intel,
))
}
}
fn reg(uc: &Unicorn<()>, r: RegisterX86) -> Result<u64> {
uc.reg_read(r)
.map_err(|e| Error::Other(format!("read reg: {:?}", e)))
}
fn write_u64(uc: &mut Unicorn<()>, addr: u64, val: u64) -> Result<()> {
uc.mem_write(addr, &val.to_le_bytes())
.map_err(|e| Error::Other(format!("write 0x{:x}: {:?}", addr, e)))
}
fn read_cstring(uc: &Unicorn<()>, addr: u64) -> Option<String> {
let bytes = uc.mem_read_as_vec(addr, 256).ok()?;
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
Some(String::from_utf8_lossy(&bytes[..end]).into_owned())
}
fn align_up(val: u64, align: u64) -> u64 {
(val + align - 1) & !(align - 1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn align_up_basic() {
assert_eq!(align_up(0, 4096), 0);
assert_eq!(align_up(1, 4096), 4096);
assert_eq!(align_up(4096, 4096), 4096);
assert_eq!(align_up(4097, 4096), 8192);
}
#[test]
fn emulator_creates() {
let emu = Emulator::new().unwrap();
assert_eq!(emu.instruction_count(), 0);
assert!(emu.stdout().is_empty());
assert!(emu.stderr().is_empty());
}
#[test]
fn breakpoint_management() {
let mut emu = Emulator::new().unwrap();
emu.set_breakpoint(0x401000);
assert!(emu.breakpoints.contains(&0x401000));
emu.remove_breakpoint(0x401000);
assert!(!emu.breakpoints.contains(&0x401000));
}
#[test]
fn default_config() {
let config = EmulatorConfig::default();
assert_eq!(config.max_instructions, 10_000_000);
assert!(!config.trace_instructions);
assert!(config.trace_syscalls);
assert!(config.scan_secrets);
assert_eq!(config.fake_pid, 1337);
}
#[test]
fn sys_brk_works() {
let mut emu = Emulator::new().unwrap();
assert_eq!(emu.sys_brk(0), HEAP_BASE as i64);
let new_brk = HEAP_BASE + 4096;
assert_eq!(emu.sys_brk(new_brk), new_brk as i64);
assert_eq!(emu.brk_current, new_brk);
}
#[test]
fn reset_state_clears_all() {
let mut emu = Emulator::new().unwrap();
emu.instruction_count = 100;
emu.stdout_buf = b"hello".to_vec();
emu.stderr_buf = b"error".to_vec();
emu.brk_current = HEAP_BASE + 4096;
emu.next_fd = 10;
emu.reset_state();
assert_eq!(emu.instruction_count, 0);
assert!(emu.stdout_buf.is_empty());
assert!(emu.stderr_buf.is_empty());
assert_eq!(emu.brk_current, HEAP_BASE);
assert_eq!(emu.next_fd, 3);
}
#[test]
fn sys_close_stdio_noop() {
let mut emu = Emulator::new().unwrap();
assert_eq!(emu.sys_close(0), 0); assert_eq!(emu.sys_close(1), 0); assert_eq!(emu.sys_close(2), 0); assert_eq!(emu.sys_close(99), -9); }
fn unicorn_can_execute() -> bool {
use std::sync::OnceLock;
static RESULT: OnceLock<bool> = OnceLock::new();
*RESULT.get_or_init(|| {
let exe = std::env::current_exe().unwrap();
let status = std::process::Command::new(exe)
.arg("--test-threads=1")
.arg("emulator::tests::__unicorn_probe")
.arg("--exact")
.arg("--ignored")
.arg("--nocapture")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
matches!(status, Ok(s) if s.success())
})
}
#[test]
#[ignore]
fn __unicorn_probe() {
let mut uc = Unicorn::new(Arch::X86, Mode::MODE_64).unwrap();
uc.mem_map(0x1000, 0x1000, Prot::ALL).unwrap();
uc.mem_write(0x1000, &[0x90]).unwrap(); uc.emu_start(0x1000, 0x1001, 0, 1).unwrap();
}
macro_rules! skip_if_no_unicorn {
() => {
if !unicorn_can_execute() {
eprintln!("SKIP: unicorn-engine cannot execute x86_64 on this platform");
return;
}
};
}
#[test]
fn shellcode_nop_ret() {
skip_if_no_unicorn!();
let code = [0x90, 0xC3];
let mut emu = Emulator::with_config(EmulatorConfig {
trace_syscalls: false,
scan_secrets: false,
..Default::default()
})
.unwrap();
let result = emu.run_shellcode(&code, 0x10000);
assert!(result.is_ok());
assert!(emu.instruction_count() >= 1);
}
#[test]
fn shellcode_syscall_exit() {
skip_if_no_unicorn!();
let code = [
0x48, 0xC7, 0xC0, 0x3C, 0x00, 0x00, 0x00, 0x48, 0xC7, 0xC7, 0x2A, 0x00, 0x00, 0x00, 0x0F, 0x05, ];
let mut emu = Emulator::new().unwrap();
let result = emu.run_shellcode(&code, 0x10000).unwrap();
match result {
EmulatorStop::Exited(code) => assert_eq!(code, 42),
other => panic!("expected Exited(42), got {:?}", other),
}
}
#[test]
fn shellcode_write_stdout() {
skip_if_no_unicorn!();
#[rustfmt::skip]
let code = [
0xC6, 0x04, 0x24, 0x48, 0xC6, 0x44, 0x24, 0x01, 0x69, 0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x48, 0xC7, 0xC7, 0x01, 0x00, 0x00, 0x00, 0x48, 0x89, 0xE6, 0x48, 0xC7, 0xC2, 0x02, 0x00, 0x00, 0x00, 0x0F, 0x05, 0x48, 0xC7, 0xC0, 0x3C, 0x00, 0x00, 0x00, 0x48, 0x31, 0xFF, 0x0F, 0x05, ];
let mut emu = Emulator::new().unwrap();
emu.run_shellcode(&code, 0x10000).unwrap();
assert_eq!(emu.stdout(), b"Hi");
}
#[test]
fn syscall_ptrace_bypassed() {
skip_if_no_unicorn!();
#[rustfmt::skip]
let code = [
0x48, 0xC7, 0xC0, 0x65, 0x00, 0x00, 0x00, 0x48, 0x31, 0xFF, 0x48, 0x31, 0xF6, 0x48, 0x31, 0xD2, 0x4D, 0x31, 0xD2, 0x0F, 0x05, 0x48, 0x89, 0xC7, 0x48, 0xC7, 0xC0, 0x3C, 0x00, 0x00, 0x00, 0x0F, 0x05, ];
let mut emu = Emulator::new().unwrap();
let result = emu.run_shellcode(&code, 0x10000).unwrap();
match result {
EmulatorStop::Exited(code) => assert_eq!(code, 0),
other => panic!("expected Exited(0), got {:?}", other),
}
let bypasses = emu
.event_log()
.events_by_category(crate::event_log::EventCategory::AntiDebug);
assert!(!bypasses.is_empty());
}
}