use std::fs::{self, OpenOptions}; use std::io::{Read, Write}; use std::time::{SystemTime, UNIX_EPOCH}; use unicorn_engine::{RegisterARM64, Unicorn}; use crate::constants::{ ENOENT, IMPORT_ADDRESS, IMPORT_LIBRARY_STRIDE, O_ACCMODE, O_CREAT, O_NOFOLLOW, O_RDWR, O_WRONLY, }; use crate::debug::{debug_print, debug_trace}; use crate::emu::{ ensure_errno_address, load_library_by_name, read_c_string, resolve_symbol_from_loaded_library_by_name, set_errno, }; use crate::errors::VmError; use crate::runtime::RuntimeState; use crate::util::bytes_to_hex; pub fn dispatch_import_stub( uc: &mut Unicorn<'_, RuntimeState>, address: u64, ) -> Result<(), VmError> { if address < IMPORT_ADDRESS { return Err(VmError::InvalidImportAddress(address)); } let offset = address - IMPORT_ADDRESS; let library_index = (offset / IMPORT_LIBRARY_STRIDE) as usize; let symbol_index = ((offset % IMPORT_LIBRARY_STRIDE) / 4) as usize; let symbol_name = { let state = uc.get_data(); let library = state .loaded_libraries .get(library_index) .ok_or(VmError::LibraryNotLoaded(library_index))?; let symbol = library.symbols.get(symbol_index).ok_or_else(|| { VmError::SymbolIndexOutOfRange { library: library.name.clone(), index: symbol_index, } })?; symbol.name.clone() }; handle_stub_by_name(uc, &symbol_name) } fn handle_stub_by_name( uc: &mut Unicorn<'_, RuntimeState>, symbol_name: &str, ) -> Result<(), VmError> { match symbol_name { "malloc" => stub_malloc(uc), "free" => stub_free(uc), "strncpy" => stub_strncpy(uc), "mkdir" => stub_mkdir(uc), "umask" => stub_umask(uc), "chmod" => stub_chmod(uc), "lstat" => stub_lstat(uc), "fstat" => stub_fstat(uc), "open" => stub_open(uc), "ftruncate" => stub_ftruncate(uc), "read" => stub_read(uc), "write" => stub_write(uc), "close" => stub_close(uc), "dlopen" => stub_dlopen(uc), "dlsym" => stub_dlsym(uc), "dlclose" => stub_dlclose(uc), "pthread_once" => stub_return_zero(uc), "pthread_create" => stub_return_zero(uc), "pthread_mutex_lock" => stub_return_zero(uc), "pthread_rwlock_unlock" => stub_return_zero(uc), "pthread_rwlock_destroy" => stub_return_zero(uc), "pthread_rwlock_wrlock" => stub_return_zero(uc), "pthread_rwlock_init" => stub_return_zero(uc), "pthread_mutex_unlock" => stub_return_zero(uc), "pthread_rwlock_rdlock" => stub_return_zero(uc), "gettimeofday" => stub_gettimeofday(uc), "__errno" => stub_errno_location(uc), "__system_property_get" => stub_system_property_get(uc), "arc4random" => stub_arc4random(uc), other => { debug_print(other); Err(VmError::UnhandledImport(other.to_string())) } } } fn stub_return_zero(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { uc.reg_write(RegisterARM64::X0, 0)?; Ok(()) } fn stub_malloc(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let request = uc.reg_read(RegisterARM64::X0)?; let address = { let state = uc.get_data_mut(); state.malloc_allocator.alloc(request)? }; debug_trace(format!("malloc(0x{request:X})=0x{address:X}")); uc.reg_write(RegisterARM64::X0, address)?; Ok(()) } fn stub_free(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { uc.reg_write(RegisterARM64::X0, 0)?; Ok(()) } fn stub_strncpy(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let dst = uc.reg_read(RegisterARM64::X0)?; let src = uc.reg_read(RegisterARM64::X1)?; let length = uc.reg_read(RegisterARM64::X2)? as usize; let input = uc.mem_read_as_vec(src, length)?; let copy_len = input .iter() .position(|byte| *byte == 0) .unwrap_or(length) .min(length); let mut output = vec![0_u8; length]; output[..copy_len].copy_from_slice(&input[..copy_len]); uc.mem_write(dst, &output)?; uc.reg_write(RegisterARM64::X0, dst)?; Ok(()) } fn stub_mkdir(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let path_ptr = uc.reg_read(RegisterARM64::X0)?; let mode = uc.reg_read(RegisterARM64::X1)?; let path = read_c_string(uc, path_ptr, 0x1000)?; debug_trace(format!("mkdir('{path}', {mode:#o})")); // Only allow creating ./anisette directory (matches Python reference impl) if path != "./anisette" { debug_print(format!("mkdir: rejecting invalid path '{path}'")); set_errno(uc, ENOENT)?; uc.reg_write(RegisterARM64::X0, u64::MAX)?; return Ok(()); } match fs::create_dir_all(&path) { Ok(()) => { uc.reg_write(RegisterARM64::X0, 0)?; } Err(_) => { set_errno(uc, ENOENT)?; uc.reg_write(RegisterARM64::X0, u64::MAX)?; } } Ok(()) } fn stub_umask(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { uc.reg_write(RegisterARM64::X0, 0o777)?; Ok(()) } fn stub_chmod(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let path_ptr = uc.reg_read(RegisterARM64::X0)?; let mode = uc.reg_read(RegisterARM64::X1)?; let path = read_c_string(uc, path_ptr, 0x1000)?; debug_trace(format!("chmod('{path}', {mode:#o})")); uc.reg_write(RegisterARM64::X0, 0)?; Ok(()) } fn build_python_stat_bytes(mode: u32, size: u64) -> Vec { let mut stat = Vec::with_capacity(128); stat.extend_from_slice(&[0_u8; 8]); // st_dev stat.extend_from_slice(&[0_u8; 8]); // st_ino stat.extend_from_slice(&mode.to_le_bytes()); // st_mode stat.extend_from_slice(&[0_u8; 4]); // st_nlink stat.extend_from_slice(&[0xA4, 0x81, 0x00, 0x00]); // st_uid stat.extend_from_slice(&[0_u8; 4]); // st_gid stat.extend_from_slice(&[0_u8; 8]); // st_rdev stat.extend_from_slice(&[0_u8; 8]); // __pad1 stat.extend_from_slice(&size.to_le_bytes()); // st_size stat.extend_from_slice(&[0_u8; 4]); // st_blksize stat.extend_from_slice(&[0_u8; 4]); // __pad2 stat.extend_from_slice(&[0_u8; 8]); // st_blocks stat.extend_from_slice(&[0_u8; 8]); // st_atime stat.extend_from_slice(&[0_u8; 8]); // st_atime_nsec stat.extend_from_slice(&[0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00]); // st_mtime stat.extend_from_slice(&[0_u8; 8]); // st_mtime_nsec stat.extend_from_slice(&[0_u8; 8]); // st_ctime stat.extend_from_slice(&[0_u8; 8]); // st_ctime_nsec stat.extend_from_slice(&[0_u8; 4]); // __unused4 stat.extend_from_slice(&[0_u8; 4]); // __unused5 stat } fn write_python_stat( uc: &mut Unicorn<'_, RuntimeState>, out_ptr: u64, mode: u32, size: u64, stat_blksize: u64, stat_blocks: u64, ) -> Result<(), VmError> { debug_print(format!("{size} {stat_blksize} {stat_blocks}")); let fake_blksize = 512_u64; let fake_blocks = size.div_ceil(512); debug_print(format!("{size} {fake_blksize} {fake_blocks}")); debug_print(format!("0x{mode:X} = {mode}")); let stat_bytes = build_python_stat_bytes(mode, size); debug_print(format!("{}", stat_bytes.len())); debug_print(format!("Write to ptr: 0x{out_ptr:X}")); uc.mem_write(out_ptr, &stat_bytes)?; debug_print("Stat struct written to guest memory"); Ok(()) } fn stat_path_into_guest( uc: &mut Unicorn<'_, RuntimeState>, path: &str, out_ptr: u64, ) -> Result<(), VmError> { let metadata = match fs::symlink_metadata(path) { Ok(metadata) => metadata, Err(_) => { debug_print(format!("Unable to stat '{path}'")); set_errno(uc, ENOENT)?; uc.reg_write(RegisterARM64::X0, u64::MAX)?; return Ok(()); } }; #[cfg(unix)] { use std::os::unix::fs::MetadataExt; write_python_stat( uc, out_ptr, metadata.mode(), metadata.size(), metadata.blksize(), metadata.blocks(), )?; } #[cfg(not(unix))] { write_python_stat(uc, out_ptr, 0, metadata.len(), 0, 0)?; } uc.reg_write(RegisterARM64::X0, 0)?; Ok(()) } fn stat_fd_into_guest( uc: &mut Unicorn<'_, RuntimeState>, fd: u64, out_ptr: u64, ) -> Result<(), VmError> { let fd_index = usize::try_from(fd).map_err(|_| VmError::InvalidFileDescriptor(fd))?; let metadata = { let state = uc.get_data_mut(); let slot = state .file_handles .get_mut(fd_index) .ok_or(VmError::InvalidFileDescriptor(fd))?; let file = slot.as_mut().ok_or(VmError::InvalidFileDescriptor(fd))?; file.metadata() }; let metadata = match metadata { Ok(metadata) => metadata, Err(_) => { debug_print(format!("Unable to stat '{fd}'")); set_errno(uc, ENOENT)?; uc.reg_write(RegisterARM64::X0, u64::MAX)?; return Ok(()); } }; #[cfg(unix)] { use std::os::unix::fs::MetadataExt; write_python_stat( uc, out_ptr, metadata.mode(), metadata.size(), metadata.blksize(), metadata.blocks(), )?; } #[cfg(not(unix))] { write_python_stat(uc, out_ptr, 0, metadata.len(), 0, 0)?; } uc.reg_write(RegisterARM64::X0, 0)?; Ok(()) } fn stub_lstat(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let path_ptr = uc.reg_read(RegisterARM64::X0)?; let out_ptr = uc.reg_read(RegisterARM64::X1)?; let path = read_c_string(uc, path_ptr, 0x1000)?; debug_trace(format!( "lstat(0x{path_ptr:X}:'{path}', [x1:0x{out_ptr:X}])" )); stat_path_into_guest(uc, &path, out_ptr) } fn stub_fstat(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let fd = uc.reg_read(RegisterARM64::X0)?; let out_ptr = uc.reg_read(RegisterARM64::X1)?; debug_trace(format!("fstat({fd}, [...])")); stat_fd_into_guest(uc, fd, out_ptr) } fn stub_open(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let path_ptr = uc.reg_read(RegisterARM64::X0)?; let flags = uc.reg_read(RegisterARM64::X1)?; let mode = uc.reg_read(RegisterARM64::X2)?; let path = read_c_string(uc, path_ptr, 0x1000)?; if path.is_empty() { return Err(VmError::EmptyPath); } debug_trace(format!("open('{path}', {flags:#o}, {mode:#o})")); // Only allow access to ./anisette/adi.pb (matches Python reference impl) if path != "./anisette/adi.pb" { debug_print(format!("open: rejecting invalid path '{path}'")); set_errno(uc, ENOENT)?; uc.reg_write(RegisterARM64::X0, u64::MAX)?; return Ok(()); } if flags != O_NOFOLLOW && flags != (O_NOFOLLOW | O_CREAT | O_WRONLY) { debug_print(format!("open: rejecting unsupported flags {flags:#o}")); set_errno(uc, ENOENT)?; uc.reg_write(RegisterARM64::X0, u64::MAX)?; return Ok(()); } let mut options = OpenOptions::new(); let access_mode = flags & O_ACCMODE; let _write_only = access_mode == O_WRONLY; let create = (flags & O_CREAT) != 0; match access_mode { 0 => { options.read(true); } O_WRONLY => { options.write(true).truncate(true); } O_RDWR => { options.read(true).write(true); } _ => { set_errno(uc, ENOENT)?; uc.reg_write(RegisterARM64::X0, u64::MAX)?; return Ok(()); } } if create { options.create(true).read(true).write(true); if let Some(parent) = std::path::Path::new(&path).parent() { let _ = fs::create_dir_all(parent); } } if (flags & O_NOFOLLOW) == 0 { debug_trace("open without O_NOFOLLOW"); } match options.open(&path) { Ok(file) => { let fd = { let state = uc.get_data_mut(); state.file_handles.push(Some(file)); (state.file_handles.len() - 1) as u64 }; uc.reg_write(RegisterARM64::X0, fd)?; } Err(_) => { set_errno(uc, ENOENT)?; uc.reg_write(RegisterARM64::X0, u64::MAX)?; } } Ok(()) } fn stub_ftruncate(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let fd = uc.reg_read(RegisterARM64::X0)?; let length = uc.reg_read(RegisterARM64::X1)?; debug_trace(format!("ftruncate({fd}, {length})")); let fd_index = usize::try_from(fd).map_err(|_| VmError::InvalidFileDescriptor(fd))?; let result = { let state = uc.get_data_mut(); let slot = state .file_handles .get_mut(fd_index) .ok_or(VmError::InvalidFileDescriptor(fd))?; let file = slot.as_mut().ok_or(VmError::InvalidFileDescriptor(fd))?; file.set_len(length) }; match result { Ok(()) => uc.reg_write(RegisterARM64::X0, 0)?, Err(_) => { set_errno(uc, ENOENT)?; uc.reg_write(RegisterARM64::X0, u64::MAX)?; } } Ok(()) } fn stub_read(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let fd = uc.reg_read(RegisterARM64::X0)?; let buf_ptr = uc.reg_read(RegisterARM64::X1)?; let count = uc.reg_read(RegisterARM64::X2)? as usize; let fd_index = usize::try_from(fd).map_err(|_| VmError::InvalidFileDescriptor(fd))?; let mut buffer = vec![0_u8; count]; let read_size = { let state = uc.get_data_mut(); let slot = state .file_handles .get_mut(fd_index) .ok_or(VmError::InvalidFileDescriptor(fd))?; let file = slot.as_mut().ok_or(VmError::InvalidFileDescriptor(fd))?; file.read(&mut buffer) }; debug_trace(format!("read({fd}, 0x{buf_ptr:X}, {count})={read_size:?}")); match read_size { Ok(read_size) => { uc.mem_write(buf_ptr, &buffer[..read_size])?; uc.reg_write(RegisterARM64::X0, read_size as u64)?; } Err(_) => { set_errno(uc, ENOENT)?; uc.reg_write(RegisterARM64::X0, u64::MAX)?; } } Ok(()) } fn stub_write(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let fd = uc.reg_read(RegisterARM64::X0)?; let buf_ptr = uc.reg_read(RegisterARM64::X1)?; let count = uc.reg_read(RegisterARM64::X2)? as usize; debug_trace(format!("write({fd}, 0x{buf_ptr:X}, {count})")); let fd_index = usize::try_from(fd).map_err(|_| VmError::InvalidFileDescriptor(fd))?; let bytes = uc.mem_read_as_vec(buf_ptr, count)?; let write_size = { let state = uc.get_data_mut(); let slot = state .file_handles .get_mut(fd_index) .ok_or(VmError::InvalidFileDescriptor(fd))?; let file = slot.as_mut().ok_or(VmError::InvalidFileDescriptor(fd))?; file.write_all(&bytes) }; match write_size { Ok(()) => uc.reg_write(RegisterARM64::X0, count as u64)?, Err(_) => { set_errno(uc, ENOENT)?; uc.reg_write(RegisterARM64::X0, u64::MAX)?; } } Ok(()) } fn stub_close(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let fd = uc.reg_read(RegisterARM64::X0)?; let fd_index = usize::try_from(fd).map_err(|_| VmError::InvalidFileDescriptor(fd))?; let state = uc.get_data_mut(); let slot = state .file_handles .get_mut(fd_index) .ok_or(VmError::InvalidFileDescriptor(fd))?; *slot = None; uc.reg_write(RegisterARM64::X0, 0)?; Ok(()) } fn stub_dlopen(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let path_ptr = uc.reg_read(RegisterARM64::X0)?; let path = read_c_string(uc, path_ptr, 0x1000)?; let library_name = path.rsplit('/').next().ok_or(VmError::EmptyPath)?; debug_trace(format!("dlopen('{path}' ({library_name}))")); let library_index = load_library_by_name(uc, library_name)?; uc.reg_write(RegisterARM64::X0, (library_index + 1) as u64)?; Ok(()) } fn stub_dlsym(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let handle = uc.reg_read(RegisterARM64::X0)?; if handle == 0 { return Err(VmError::InvalidDlopenHandle(handle)); } let symbol_ptr = uc.reg_read(RegisterARM64::X1)?; let symbol_name = read_c_string(uc, symbol_ptr, 0x1000)?; let library_index = (handle - 1) as usize; { let state = uc.get_data(); if let Some(library) = state.loaded_libraries.get(library_index) { debug_trace(format!( "dlsym({handle:X} ({}), '{}')", library.name, symbol_name )); } } let symbol_address = resolve_symbol_from_loaded_library_by_name(uc, library_index, &symbol_name)?; debug_print(format!("Found at 0x{symbol_address:X}")); uc.reg_write(RegisterARM64::X0, symbol_address)?; Ok(()) } fn stub_dlclose(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { uc.reg_write(RegisterARM64::X0, 0)?; Ok(()) } fn stub_gettimeofday(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let time_ptr = uc.reg_read(RegisterARM64::X0)?; let tz_ptr = uc.reg_read(RegisterARM64::X1)?; debug_trace(format!("gettimeofday(0x{time_ptr:X}, 0x{tz_ptr:X})")); if tz_ptr != 0 { return Err(VmError::UnhandledImport(format!( "gettimeofday tz pointer must be null, got 0x{tz_ptr:X}" ))); } let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default(); let sec = now.as_secs(); let usec = now.subsec_micros() as i64; let mut timeval = [0_u8; 16]; timeval[0..8].copy_from_slice(&sec.to_le_bytes()); timeval[8..16].copy_from_slice(&usec.to_le_bytes()); debug_print(format!( "{{'tv_sec': {sec}, 'tv_usec': {usec}}} {} {}", bytes_to_hex(&timeval), timeval.len() )); uc.mem_write(time_ptr, &timeval)?; uc.reg_write(RegisterARM64::X0, 0)?; Ok(()) } fn stub_errno_location(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { if uc.get_data().errno_address.is_none() { debug_print("Checking errno before first error (!)"); } let errno_address = ensure_errno_address(uc)?; uc.reg_write(RegisterARM64::X0, errno_address)?; Ok(()) } fn stub_system_property_get(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { let name_ptr = uc.reg_read(RegisterARM64::X0)?; let name = read_c_string(uc, name_ptr, 0x1000)?; debug_trace(format!("__system_property_get({name}, [...])")); let value_ptr = uc.reg_read(RegisterARM64::X1)?; let value = b"no s/n number"; uc.mem_write(value_ptr, value)?; uc.reg_write(RegisterARM64::X0, value.len() as u64)?; Ok(()) } fn stub_arc4random(uc: &mut Unicorn<'_, RuntimeState>) -> Result<(), VmError> { uc.reg_write(RegisterARM64::X0, 0xDEAD_BEEF)?; Ok(()) } #[cfg(test)] mod tests { use super::Allocator; #[test] fn allocator_aligns_to_pages() { let mut allocator = Allocator::new(0x1000_0000, 0x20_000); let a = allocator.alloc(1).expect("alloc 1"); let b = allocator.alloc(0x1500).expect("alloc 2"); assert_eq!(a, 0x1000_0000); assert_eq!(b, 0x1000_1000); } }