This commit is contained in:
2026-02-26 16:59:30 +08:00
commit 3339111ff2
31 changed files with 4635 additions and 0 deletions

624
src/stub.rs Normal file
View File

@@ -0,0 +1,624 @@
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<u8> {
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);
}
}