Implement macOS memory reading via mach APIs
This commit is contained in:
@@ -211,9 +211,7 @@ pub fn validate_pe_header(pid: u32, base_address: usize) -> anyhow::Result<PEHea
|
|||||||
return Ok(PEHeaderValidation::CorruptedHeader);
|
return Ok(PEHeaderValidation::CorruptedHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
let dos_header = unsafe {
|
let dos_header = unsafe { std::ptr::read(dos_header_bytes.as_ptr() as *const ImageDosHeader) };
|
||||||
std::ptr::read(dos_header_bytes.as_ptr() as *const ImageDosHeader)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validate DOS signature
|
// Validate DOS signature
|
||||||
if dos_header.e_magic != IMAGE_DOS_SIGNATURE {
|
if dos_header.e_magic != IMAGE_DOS_SIGNATURE {
|
||||||
@@ -254,22 +252,21 @@ pub fn validate_pe_header(pid: u32, base_address: usize) -> anyhow::Result<PEHea
|
|||||||
return Ok(PEHeaderValidation::CorruptedHeader);
|
return Ok(PEHeaderValidation::CorruptedHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_header = unsafe {
|
let file_header =
|
||||||
std::ptr::read(file_header_bytes.as_ptr() as *const ImageFileHeader)
|
unsafe { std::ptr::read(file_header_bytes.as_ptr() as *const ImageFileHeader) };
|
||||||
};
|
|
||||||
|
|
||||||
// Read optional header (64-bit)
|
// Read optional header (64-bit)
|
||||||
let optional_header_address = file_header_address + file_header_size;
|
let optional_header_address = file_header_address + file_header_size;
|
||||||
let optional_header_size = mem::size_of::<ImageOptionalHeader64>();
|
let optional_header_size = mem::size_of::<ImageOptionalHeader64>();
|
||||||
let optional_header_bytes = read_process_memory(pid, optional_header_address, optional_header_size)?;
|
let optional_header_bytes =
|
||||||
|
read_process_memory(pid, optional_header_address, optional_header_size)?;
|
||||||
|
|
||||||
if optional_header_bytes.len() < optional_header_size {
|
if optional_header_bytes.len() < optional_header_size {
|
||||||
return Ok(PEHeaderValidation::CorruptedHeader);
|
return Ok(PEHeaderValidation::CorruptedHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
let optional_header = unsafe {
|
let optional_header =
|
||||||
std::ptr::read(optional_header_bytes.as_ptr() as *const ImageOptionalHeader64)
|
unsafe { std::ptr::read(optional_header_bytes.as_ptr() as *const ImageOptionalHeader64) };
|
||||||
};
|
|
||||||
|
|
||||||
// Validate image base matches memory address
|
// Validate image base matches memory address
|
||||||
if optional_header.image_base != base_address as u64 {
|
if optional_header.image_base != base_address as u64 {
|
||||||
@@ -308,9 +305,7 @@ pub fn read_pe_header_info(pid: u32, base_address: usize) -> anyhow::Result<Opti
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let dos_header = unsafe {
|
let dos_header = unsafe { std::ptr::read(dos_header_bytes.as_ptr() as *const ImageDosHeader) };
|
||||||
std::ptr::read(dos_header_bytes.as_ptr() as *const ImageDosHeader)
|
|
||||||
};
|
|
||||||
|
|
||||||
if dos_header.e_magic != IMAGE_DOS_SIGNATURE {
|
if dos_header.e_magic != IMAGE_DOS_SIGNATURE {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -348,22 +343,21 @@ pub fn read_pe_header_info(pid: u32, base_address: usize) -> anyhow::Result<Opti
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_header = unsafe {
|
let file_header =
|
||||||
std::ptr::read(file_header_bytes.as_ptr() as *const ImageFileHeader)
|
unsafe { std::ptr::read(file_header_bytes.as_ptr() as *const ImageFileHeader) };
|
||||||
};
|
|
||||||
|
|
||||||
// Read optional header
|
// Read optional header
|
||||||
let optional_header_address = file_header_address + file_header_size;
|
let optional_header_address = file_header_address + file_header_size;
|
||||||
let optional_header_size = mem::size_of::<ImageOptionalHeader64>();
|
let optional_header_size = mem::size_of::<ImageOptionalHeader64>();
|
||||||
let optional_header_bytes = read_process_memory(pid, optional_header_address, optional_header_size)?;
|
let optional_header_bytes =
|
||||||
|
read_process_memory(pid, optional_header_address, optional_header_size)?;
|
||||||
|
|
||||||
if optional_header_bytes.len() < optional_header_size {
|
if optional_header_bytes.len() < optional_header_size {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let optional_header = unsafe {
|
let optional_header =
|
||||||
std::ptr::read(optional_header_bytes.as_ptr() as *const ImageOptionalHeader64)
|
unsafe { std::ptr::read(optional_header_bytes.as_ptr() as *const ImageOptionalHeader64) };
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(PEHeaderInfo {
|
Ok(Some(PEHeaderInfo {
|
||||||
dos_signature: dos_header.e_magic,
|
dos_signature: dos_header.e_magic,
|
||||||
@@ -391,7 +385,10 @@ pub struct PEHeaderInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
pub fn read_pe_header_info(_pid: u32, _base_address: usize) -> anyhow::Result<Option<PEHeaderInfo>> {
|
pub fn read_pe_header_info(
|
||||||
|
_pid: u32,
|
||||||
|
_base_address: usize,
|
||||||
|
) -> anyhow::Result<Option<PEHeaderInfo>> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,8 +532,8 @@ mod platform {
|
|||||||
|
|
||||||
pub fn enumerate_memory_regions(pid: u32) -> Result<Vec<MemoryRegion>> {
|
pub fn enumerate_memory_regions(pid: u32) -> Result<Vec<MemoryRegion>> {
|
||||||
let maps_path = format!("/proc/{}/maps", pid);
|
let maps_path = format!("/proc/{}/maps", pid);
|
||||||
let content = fs::read_to_string(&maps_path)
|
let content =
|
||||||
.context(format!("Failed to read {}", maps_path))?;
|
fs::read_to_string(&maps_path).context(format!("Failed to read {}", maps_path))?;
|
||||||
|
|
||||||
let mut regions = Vec::new();
|
let mut regions = Vec::new();
|
||||||
|
|
||||||
@@ -612,8 +609,7 @@ mod platform {
|
|||||||
|
|
||||||
pub fn read_process_memory(pid: u32, address: usize, size: usize) -> Result<Vec<u8>> {
|
pub fn read_process_memory(pid: u32, address: usize, size: usize) -> Result<Vec<u8>> {
|
||||||
let mem_path = format!("/proc/{}/mem", pid);
|
let mem_path = format!("/proc/{}/mem", pid);
|
||||||
let mut file = fs::File::open(&mem_path)
|
let mut file = fs::File::open(&mem_path).context(format!("Failed to open {}", mem_path))?;
|
||||||
.context(format!("Failed to open {}", mem_path))?;
|
|
||||||
|
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
file.seek(SeekFrom::Start(address as u64))
|
file.seek(SeekFrom::Start(address as u64))
|
||||||
@@ -630,20 +626,194 @@ mod platform {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
mod platform {
|
mod platform {
|
||||||
use super::{MemoryProtection, MemoryRegion};
|
use super::{MemoryProtection, MemoryRegion};
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
|
use libc::{c_int, pid_t, size_t};
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
// TODO: macOS implementation requires vm_region_basic_info_64 which is not available
|
// Mach types and constants
|
||||||
// in all libc versions. This is a stub implementation.
|
type mach_port_t = u32;
|
||||||
pub fn enumerate_memory_regions(_pid: u32) -> Result<Vec<MemoryRegion>> {
|
type vm_address_t = usize;
|
||||||
Err(anyhow::anyhow!(
|
type vm_size_t = usize;
|
||||||
"macOS memory enumeration not yet fully implemented for this platform"
|
type vm_prot_t = c_int;
|
||||||
))
|
type kern_return_t = c_int;
|
||||||
|
|
||||||
|
const KERN_SUCCESS: kern_return_t = 0;
|
||||||
|
const VM_PROT_READ: vm_prot_t = 0x01;
|
||||||
|
const VM_PROT_WRITE: vm_prot_t = 0x02;
|
||||||
|
const VM_PROT_EXECUTE: vm_prot_t = 0x04;
|
||||||
|
|
||||||
|
// External mach functions
|
||||||
|
extern "C" {
|
||||||
|
fn task_for_pid(
|
||||||
|
target_tport: mach_port_t,
|
||||||
|
pid: pid_t,
|
||||||
|
t: *mut mach_port_t,
|
||||||
|
) -> kern_return_t;
|
||||||
|
|
||||||
|
fn mach_task_self() -> mach_port_t;
|
||||||
|
|
||||||
|
fn mach_vm_read_overwrite(
|
||||||
|
target_task: mach_port_t,
|
||||||
|
address: vm_address_t,
|
||||||
|
size: vm_size_t,
|
||||||
|
data: vm_address_t,
|
||||||
|
out_size: *mut vm_size_t,
|
||||||
|
) -> kern_return_t;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_process_memory(_pid: u32, _address: usize, _size: usize) -> Result<Vec<u8>> {
|
pub fn enumerate_memory_regions(pid: u32) -> Result<Vec<MemoryRegion>> {
|
||||||
Err(anyhow::anyhow!(
|
use libc::{c_int, mach_msg_type_number_t};
|
||||||
"macOS memory reading not yet fully implemented for this platform"
|
|
||||||
))
|
// Mach VM structures and constants
|
||||||
|
const VM_REGION_BASIC_INFO_64: c_int = 9;
|
||||||
|
const VM_REGION_BASIC_INFO_COUNT_64: mach_msg_type_number_t = 9;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct vm_region_basic_info_64 {
|
||||||
|
protection: c_int,
|
||||||
|
max_protection: c_int,
|
||||||
|
inheritance: c_int,
|
||||||
|
shared: c_int,
|
||||||
|
reserved: c_int,
|
||||||
|
offset: u64,
|
||||||
|
behavior: c_int,
|
||||||
|
user_wired_count: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn task_for_pid(
|
||||||
|
target_tport: mach_port_t,
|
||||||
|
pid: libc::pid_t,
|
||||||
|
t: *mut mach_port_t,
|
||||||
|
) -> kern_return_t;
|
||||||
|
|
||||||
|
fn mach_task_self() -> mach_port_t;
|
||||||
|
|
||||||
|
fn mach_vm_region(
|
||||||
|
target_task: mach_port_t,
|
||||||
|
address: *mut vm_address_t,
|
||||||
|
size: *mut vm_size_t,
|
||||||
|
flavor: c_int,
|
||||||
|
info: *mut c_int,
|
||||||
|
info_count: *mut mach_msg_type_number_t,
|
||||||
|
object_name: *mut mach_port_t,
|
||||||
|
) -> kern_return_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut task: mach_port_t = 0;
|
||||||
|
let kr = task_for_pid(mach_task_self(), pid as libc::pid_t, &mut task);
|
||||||
|
|
||||||
|
if kr != KERN_SUCCESS {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Failed to get task port for pid {}. Requires sudo or proper entitlements. Error: {}",
|
||||||
|
pid,
|
||||||
|
kr
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut regions = Vec::new();
|
||||||
|
let mut address: vm_address_t = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut size: vm_size_t = 0;
|
||||||
|
let mut info: vm_region_basic_info_64 = std::mem::zeroed();
|
||||||
|
let mut info_count = VM_REGION_BASIC_INFO_COUNT_64;
|
||||||
|
let mut object_name: mach_port_t = 0;
|
||||||
|
|
||||||
|
let kr = mach_vm_region(
|
||||||
|
task,
|
||||||
|
&mut address,
|
||||||
|
&mut size,
|
||||||
|
VM_REGION_BASIC_INFO_64,
|
||||||
|
&mut info as *mut _ as *mut c_int,
|
||||||
|
&mut info_count,
|
||||||
|
&mut object_name,
|
||||||
|
);
|
||||||
|
|
||||||
|
// End of memory space
|
||||||
|
if kr != KERN_SUCCESS {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert mach protection to our MemoryProtection enum
|
||||||
|
let protection = match info.protection {
|
||||||
|
0 => MemoryProtection::NoAccess,
|
||||||
|
1 => MemoryProtection::ReadOnly,
|
||||||
|
2 => MemoryProtection::ReadWrite, // Write implies read on most systems
|
||||||
|
3 => MemoryProtection::ReadWrite,
|
||||||
|
4 => MemoryProtection::Execute,
|
||||||
|
5 => MemoryProtection::ReadExecute,
|
||||||
|
6 => MemoryProtection::ReadWriteExecute, // WX -> RWX
|
||||||
|
7 => MemoryProtection::ReadWriteExecute,
|
||||||
|
_ => MemoryProtection::NoAccess,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine region type based on protection and shared status
|
||||||
|
let region_type = if info.shared != 0 {
|
||||||
|
"SHARED".to_string()
|
||||||
|
} else if info.protection & 4 != 0 {
|
||||||
|
// Executable regions are likely IMAGE
|
||||||
|
"IMAGE".to_string()
|
||||||
|
} else {
|
||||||
|
"PRIVATE".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
regions.push(MemoryRegion {
|
||||||
|
base_address: address,
|
||||||
|
size,
|
||||||
|
protection,
|
||||||
|
region_type,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Move to next region
|
||||||
|
address += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(regions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_process_memory(pid: u32, address: usize, size: usize) -> Result<Vec<u8>> {
|
||||||
|
unsafe {
|
||||||
|
// Get task port for the target process
|
||||||
|
let mut task: mach_port_t = 0;
|
||||||
|
let kr = task_for_pid(mach_task_self(), pid as pid_t, &mut task);
|
||||||
|
|
||||||
|
if kr != KERN_SUCCESS {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Failed to get task port for pid {}. Make sure to run with sudo or have proper entitlements. Error code: {}",
|
||||||
|
pid,
|
||||||
|
kr
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate buffer for reading
|
||||||
|
let mut buffer = vec![0u8; size];
|
||||||
|
let mut out_size: vm_size_t = 0;
|
||||||
|
|
||||||
|
// Read memory from target process
|
||||||
|
let kr = mach_vm_read_overwrite(
|
||||||
|
task,
|
||||||
|
address as vm_address_t,
|
||||||
|
size as vm_size_t,
|
||||||
|
buffer.as_mut_ptr() as vm_address_t,
|
||||||
|
&mut out_size,
|
||||||
|
);
|
||||||
|
|
||||||
|
if kr != KERN_SUCCESS {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Failed to read process memory at address {:#x}. Error code: {}",
|
||||||
|
address,
|
||||||
|
kr
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate to actual bytes read
|
||||||
|
buffer.truncate(out_size);
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user