fn parse_c_str(buf: &[u8]) -> Option<String> {
for (i, &byte) in buf.iter().enumerate() {
if byte == 0 {
return String::from_utf8(buf[..i].to_vec()).ok();
}
}
None
}
fn read_u64_le(buf: &[u8], offset: usize) -> u64 {
let bytes: [u8; 8] = buf[offset..offset + 8].try_into().unwrap();
u64::from_le_bytes(bytes)
}
fn write_u64_le(buf: &mut [u8], offset: usize, value: u64) {
let bytes = value.to_le_bytes();
buf[offset..offset + 8].copy_from_slice(&bytes);
}
fn read_u32_le(buf: &[u8], offset: usize) -> u32 {
let bytes: [u8; 4] = buf[offset..offset + 4].try_into().unwrap();
u32::from_le_bytes(bytes)
}
fn write_u32_le(buf: &mut [u8], offset: usize, value: u32) {
let bytes = value.to_le_bytes();
buf[offset..offset + 4].copy_from_slice(&bytes);
}
fn patch_command(cmd_type: u32, buf: &mut [u8], file_len: usize) {
if cmd_type == 0x19 {
if let Some(name) = parse_c_str(&buf[..16]) {
if name == "__LINKEDIT" {
let fileoff = read_u64_le(buf, 32);
let vmsize_patched = file_len as u64 - fileoff;
let filesize_patched = vmsize_patched;
write_u64_le(buf, 24, vmsize_patched);
write_u64_le(buf, 40, filesize_patched);
}
}
}
if cmd_type == 0x2 {
let stroff = read_u32_le(buf, 8);
let strsize_patched = file_len as u32 - stroff;
write_u32_le(buf, 12, strsize_patched);
}
}
pub fn find_section() -> std::io::Result<Option<&'static [u8]>> {
use std::io::{Read, Seek, SeekFrom};
const SENTINEL: &[u8] = b"<~sui-data~>";
let exe = std::env::current_exe()?;
let mut file = std::fs::File::open(exe)?;
let file_size = file.seek(SeekFrom::End(0))?;
let search_size = std::cmp::min(file_size, 1024 * 1024); file.seek(SeekFrom::End(-(search_size as i64)))?;
let mut buf = vec![0u8; search_size as usize];
file.read_exact(&mut buf)?;
for i in (0..=(buf.len() - SENTINEL.len())).rev() {
if &buf[i..i + SENTINEL.len()] == SENTINEL {
if i + SENTINEL.len() + 8 > buf.len() {
return Ok(None);
}
let len_bytes: [u8; 8] = buf[i + SENTINEL.len()..i + SENTINEL.len() + 8]
.try_into()
.map_err(|_| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid length")
})?;
let data_len = u64::from_le_bytes(len_bytes) as usize;
let data_start = i + SENTINEL.len() + 8;
if data_start + data_len > buf.len() {
return Ok(None);
}
let data = buf[data_start..data_start + data_len].to_vec();
return Ok(Some(Box::leak(data.into_boxed_slice())));
}
}
Ok(None)
}
pub fn patch_macho_executable(file: &mut [u8]) -> bool {
const ALIGN: usize = 8;
const HSIZE: usize = 32;
if file.len() < HSIZE + 4 {
return false;
}
let ncmds = read_u32_le(file, 16);
let file_len = file.len();
let mut offset = HSIZE;
for _ in 0..ncmds {
if offset + 8 > file.len() {
return false;
}
let cmd_type = read_u32_le(file, offset);
offset += 4;
let size = read_u32_le(file, offset) as usize - 8;
offset += 4;
if offset + size > file.len() {
return false;
}
let (_, rest) = file.split_at_mut(offset);
let cmd_buf = &mut rest[..size];
patch_command(cmd_type, cmd_buf, file_len);
offset += size;
if offset & ALIGN != 0 {
offset += ALIGN - (offset & ALIGN);
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_c_str() {
let buf = b"__LINKEDIT\0extra";
assert_eq!(parse_c_str(buf), Some("__LINKEDIT".to_string()));
let buf = b"test";
assert_eq!(parse_c_str(buf), None);
let buf = b"\0";
assert_eq!(parse_c_str(buf), Some("".to_string()));
}
#[test]
fn test_read_write_u64_le() {
let mut buf = vec![0u8; 16];
write_u64_le(&mut buf, 0, 0x0123456789ABCDEF);
assert_eq!(read_u64_le(&buf, 0), 0x0123456789ABCDEF);
write_u64_le(&mut buf, 8, 0xFEDCBA9876543210);
assert_eq!(read_u64_le(&buf, 8), 0xFEDCBA9876543210);
}
#[test]
fn test_read_write_u32_le() {
let mut buf = vec![0u8; 8];
write_u32_le(&mut buf, 0, 0x01234567);
assert_eq!(read_u32_le(&buf, 0), 0x01234567);
write_u32_le(&mut buf, 4, 0xFEDCBA98);
assert_eq!(read_u32_le(&buf, 4), 0xFEDCBA98);
}
#[test]
fn test_patch_macho_executable_invalid() {
let mut buf = vec![0u8; 10];
assert_eq!(patch_macho_executable(&mut buf), false);
}
}