use std::{
fs::File,
io::{Cursor, Read, Seek, Write},
};
use iced_x86::{
Code, Decoder, DecoderOptions, Encoder, Formatter, Instruction, IntelFormatter, Register,
};
use mach_object::{
LoadCommand, MachCommand, OFile, CPU_SUBTYPE_ARM_ALL, CPU_SUBTYPE_X86_64_ALL, CPU_TYPE_ARM64,
CPU_TYPE_X86_64, MH_EXECUTE,
};
use disarm64::{
decoder::{self, Operation, ADDSUB_IMM, B_ADDR_PCREL26, CONDBRANCH},
registers::get_int_reg_name,
Opcode,
};
use memmap::MmapMut;
fn main() -> anyhow::Result<()> {
let args: Vec<_> = std::env::args().collect();
if args.len() < 2 {
return Err(anyhow::anyhow!("Usage: {} path/to/file", args[0]));
}
backup(&args[1])?;
let mut file = File::options().write(true).read(true).open(&args[1])?;
let mut mmap = unsafe { MmapMut::map_mut(&mut file) }?;
let payload = mmap.as_mut();
let mut cur = Cursor::new(payload);
let ofile = OFile::parse(&mut cur)?;
process_ofile(&ofile, &mut cur)?;
Ok(())
}
fn backup(path: &str) -> anyhow::Result<()> {
let backup_file = format!("{path}.bak");
if std::path::Path::new(&backup_file).try_exists()? {
println!("backup file found: {}", backup_file);
return Ok(());
}
println!("backup file creating ...");
std::fs::copy(path, &backup_file)?;
println!("backup file created: {}", backup_file);
Ok(())
}
fn process_ofile(ofile: &OFile, cursor: &mut Cursor<&mut [u8]>) -> anyhow::Result<()> {
match ofile {
OFile::FatFile {
magic: _,
ref files,
} => {
for &(ref arch, ref file) in files {
cursor.seek(std::io::SeekFrom::Start(arch.offset))?;
process_ofile(file, cursor)?;
}
}
OFile::MachFile {
ref header,
ref commands,
} => {
assert_eq!(header.filetype, MH_EXECUTE);
let posotion = cursor.position();
if let Some((idx, MachCommand(LoadCommand::CodeSignature(_cs), cmdsize))) = commands
.iter()
.enumerate()
.find(|(_, cmd)| matches!(cmd, MachCommand(LoadCommand::CodeSignature(_), _)))
{
let ncmds = header.ncmds - 1;
let sizeofcmds = header.sizeofcmds - *cmdsize as u32;
let (ncmds, sizeofcmds) = if header.is_bigend() {
(ncmds.to_be_bytes(), sizeofcmds.to_be_bytes())
} else {
(ncmds.to_le_bytes(), sizeofcmds.to_le_bytes())
};
cursor.seek(std::io::SeekFrom::Start(posotion + 16))?;
cursor.write(&ncmds)?;
cursor.write(&sizeofcmds)?;
let pre_cmds_size = commands
.iter()
.take(idx)
.map(|cmd| cmd.size())
.sum::<usize>();
let len = header.sizeofcmds as usize - pre_cmds_size - *cmdsize;
let mut buf = vec![0u8; len];
cursor.seek(std::io::SeekFrom::Start(
posotion + pre_cmds_size as u64 + *cmdsize as u64,
))?;
cursor.read_exact(&mut buf)?;
cursor.seek(std::io::SeekFrom::Start(posotion + pre_cmds_size as u64))?;
cursor.write_all(&buf)?;
}
if let Some(LoadCommand::EntryPoint { entryoff, .. }) = commands
.iter()
.map(|cmd| cmd.command())
.find(|cmd| matches!(cmd, LoadCommand::EntryPoint { .. }))
{
cursor.seek(std::io::SeekFrom::Start(posotion + *entryoff))?;
} else {
return Err(anyhow::anyhow!("No EntryPoint found."));
}
if header.cputype == CPU_TYPE_X86_64 && header.cpusubtype == CPU_SUBTYPE_X86_64_ALL {
x86(cursor)?;
}
if header.cputype == CPU_TYPE_ARM64 && header.cpusubtype == CPU_SUBTYPE_ARM_ALL {
aarch64(cursor)?;
}
}
o => return Err(anyhow::anyhow!("Unsupported ofile format: {:?}", o)),
}
Ok(())
}
pub fn x86(cursor: &mut Cursor<&mut [u8]>) -> anyhow::Result<()> {
let position = cursor.seek(std::io::SeekFrom::Current(0))?;
let mut text = [0u8; 1024];
cursor.read_exact(&mut text)?;
let mut encoder = Encoder::new(64);
let mut decoder = Decoder::new(64, &text, DecoderOptions::NONE);
let mut instruction = Instruction::new();
let mut formatter = IntelFormatter::new();
let mut output = String::new();
while decoder.can_decode() {
decoder.decode_out(&mut instruction);
if instruction.is_invalid() {
break;
}
if instruction.code() == Code::Cmp_rm64_imm8
&& instruction.memory_base() == Register::RBP
&& instruction.memory_displ_size() == 1
&& instruction.immediate8() == 2
{
output.clear();
formatter.format(&instruction, &mut output);
let ip = decoder.ip();
if decoder.can_decode() {
decoder.decode_out(&mut instruction);
if instruction.is_invalid() {
break;
}
output.clear();
formatter.format(&instruction, &mut output);
if instruction.code() == Code::Jb_rel32_64 {
instruction.set_code(Code::Jmp_rel32_64);
encoder.encode(&instruction, ip)?;
let opcode = encoder.take_buffer();
let mut new_instruction = String::new();
formatter.format(&instruction, &mut new_instruction);
println!("{output} => {new_instruction}");
cursor.seek(std::io::SeekFrom::Start(position + ip))?;
cursor.write(&opcode)?;
return Ok(());
}
}
}
}
Ok(())
}
pub fn aarch64(cursor: &mut Cursor<&mut [u8]>) -> anyhow::Result<()> {
let position = cursor.seek(std::io::SeekFrom::Current(0))?;
let mut code = [0u8; 512];
cursor.read_exact(&mut code)?;
let mut iter = code
.chunks_exact(4)
.map(|v| {
decoder::decode(u32::from_le_bytes(v.try_into().expect("Invalid [u8; 4]")))
.expect("Invalid instruction")
})
.enumerate();
while let Some((_, Opcode { operation, .. })) = iter.next() {
if let Operation::ADDSUB_IMM(ADDSUB_IMM::SUBS_Rd_Rn_SP_AIMM(s)) = operation {
if get_int_reg_name(true, s.rd() as u8, true) == "xzr" && s.imm12() == 0x2 {
if let Some((
i,
Opcode {
operation: Operation::CONDBRANCH(CONDBRANCH::B__ADDR_PCREL19(b)),
..
},
)) = iter.next()
{
if b.cond() == 3 {
let imm19 = b.imm19();
let branch = B_ADDR_PCREL26::DEFINITION.opcode
| B_ADDR_PCREL26::new().with_imm26(imm19).into_bits();
let bytes = branch.to_le_bytes();
println!(
"{} => {}",
decoder::decode(b.into_bits()).unwrap(),
decoder::decode(branch).unwrap()
);
cursor.seek(std::io::SeekFrom::Start(position + i as u64 * 4))?;
cursor.write(&bytes)?;
return Ok(());
}
}
}
}
}
Ok(())
}
#[test]
fn test_decoder() -> anyhow::Result<()> {
let mut decoder = Decoder::new(
64,
&[0x0f, 0x82, 0xce, 0x02, 0x00, 0x00],
DecoderOptions::NONE,
);
let ins = decoder.decode();
let mut encoder = Encoder::new(64);
encoder.encode(&ins, 0)?;
let bin = encoder.take_buffer();
println!("bin: {:02x?}", bin);
Ok(())
}