use core::panic;
use std::io::Write;
use std::{collections::HashMap, env::consts::ARCH};
use object::elf::{
R_AARCH64_ABS16, R_AARCH64_ABS32, R_AARCH64_ABS64, R_AARCH64_ADD_ABS_LO12_NC,
R_AARCH64_ADR_PREL_PG_HI21, R_AARCH64_ADR_PREL_PG_HI21_NC, R_AARCH64_CALL26, R_AARCH64_JUMP26,
R_AARCH64_LDST128_ABS_LO12_NC, R_AARCH64_LDST16_ABS_LO12_NC, R_AARCH64_LDST32_ABS_LO12_NC,
R_AARCH64_LDST64_ABS_LO12_NC, R_AARCH64_LDST8_ABS_LO12_NC,
};
use object::macho::ARM64_RELOC_UNSIGNED;
use object::{
macho::{ARM64_RELOC_BRANCH26, ARM64_RELOC_PAGE21, ARM64_RELOC_PAGEOFF12},
File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget,
Section, Symbol,
};
use object::{BinaryFormat, RelocationKind};
use anyhow::{anyhow, Result};
use super::{functions::function_resolver, mmap::MappedSection};
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
pub(crate) struct JumpTableEntry;
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
impl JumpTableEntry {
fn new(_addr: *const u8) -> Self {
panic!("JumpTableEntry not supported on this architecture")
}
fn jump_ptr(&self) -> *const u8 {
panic!("JumpTableEntry not supported on this architecture")
}
pub(crate) fn from_bytes(_bytes: &mut [u8]) -> &mut [Self] {
panic!("JumpTableEntry not supported on this architecture")
}
}
#[cfg(target_arch = "x86_64")]
#[repr(C)]
pub(crate) struct JumpTableEntry {
addr: *const u8,
instr: [u8; 6],
}
#[cfg(target_arch = "x86_64")]
impl JumpTableEntry {
fn new(addr: *const u8) -> Self {
let mut ret = Self {
addr,
instr: [0xFF, 0x25, 0xF2, 0xFF, 0xFF, 0xFF],
};
let addr_ptr = &ret.addr as *const *const u8 as *const u8;
let after_ptr = unsafe { (ret.instr.as_ptr()).add(ret.instr.len()) };
let offset = (-((after_ptr as usize - addr_ptr as usize) as i32)).to_ne_bytes();
ret.instr[2..6].copy_from_slice(&offset);
ret
}
fn jump_ptr(&self) -> *const u8 {
&self.instr[0] as *const u8
}
pub(crate) fn from_bytes(bytes: &mut [u8]) -> &mut [Self] {
let size = std::mem::size_of::<Self>();
let len = bytes.len() / size;
unsafe { std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut Self, len) }
}
}
#[cfg(target_arch = "aarch64")]
#[repr(C)]
pub(crate) struct JumpTableEntry {
instr: [u32; 4],
}
#[cfg(target_arch = "aarch64")]
impl JumpTableEntry {
fn new(addr: *const u8) -> Self {
let addr = addr as u64;
let mov = 0b11010010100000000000000000001001 | u32::try_from((addr << 48) >> 43).unwrap();
let movk1 =
0b11110010101000000000000000001001 | u32::try_from(((addr >> 16) << 48) >> 43).unwrap();
let movk2 =
0b11110010110000000000000000001001 | u32::try_from(((addr >> 32) << 48) >> 43).unwrap();
Self {
instr: [mov, movk1, movk2, 0xd61f0120],
}
}
fn jump_ptr(&self) -> *const u8 {
self.instr.as_ptr() as *const u8
}
pub(crate) fn from_bytes(bytes: &mut [u8]) -> &mut [Self] {
let size = std::mem::size_of::<Self>();
let len = bytes.len() / size;
unsafe { std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut Self, len) }
}
}
pub(crate) fn relocation_target_section<'file, 'data>(
file: &'file File<'data>,
rela: &Relocation,
) -> Option<Section<'data, 'file>> {
if let RelocationTarget::Symbol(symbol_index) = rela.target() {
let symbol = file.symbol_by_index(symbol_index).unwrap();
symbol
.section_index()
.map(|section_index| file.section_by_index(section_index).unwrap())
} else {
None
}
}
pub(crate) fn is_jump_table_entry(file: &File<'_>, rela: &Relocation) -> bool {
relocation_target_section(file, rela).is_none()
&& (rela.size() < u8::try_from(std::mem::size_of::<usize>() * 8).unwrap())
}
fn handle_relocation_generic_x86(rela: &Relocation, s: *const u8, p: *mut u8) -> Result<()> {
let a = rela.addend();
let size = rela.size();
let val = match rela.kind() {
RelocationKind::Absolute => i64::try_from(s as usize).unwrap() + a,
RelocationKind::Relative | RelocationKind::PltRelative => {
i64::try_from(s as usize).unwrap() + a - i64::try_from(p as usize).unwrap()
}
_ => {
return Err(anyhow!(
"Unsupported relocation type {:?} for generic x86",
rela.kind()
))
}
};
match size {
16 => unsafe { (p as *mut i16).write_unaligned(i16::try_from(val).unwrap()) },
32 => unsafe { (p as *mut i32).write_unaligned(i32::try_from(val).unwrap()) },
64 => unsafe { (p as *mut i64).write_unaligned(val) },
_ => return Err(anyhow!("Unsupported relocation size {:?}", size)),
}
Ok(())
}
fn handle_relocation_elf_aarch64(
s: *const u8,
a: i64,
p: *mut u8,
r_type: u32,
size: u8,
) -> Result<()> {
match r_type {
R_AARCH64_ABS64 | R_AARCH64_ABS32 | R_AARCH64_ABS16 => arm64_absolute(s, a, p, size),
R_AARCH64_ADD_ABS_LO12_NC
| R_AARCH64_LDST8_ABS_LO12_NC
| R_AARCH64_LDST16_ABS_LO12_NC
| R_AARCH64_LDST32_ABS_LO12_NC
| R_AARCH64_LDST64_ABS_LO12_NC
| R_AARCH64_LDST128_ABS_LO12_NC => arm64_page_offset(s, a, p),
R_AARCH64_ADR_PREL_PG_HI21 | R_AARCH64_ADR_PREL_PG_HI21_NC => arm64_relative_page(s, a, p),
R_AARCH64_JUMP26 | R_AARCH64_CALL26 => arm64_branch(s, a, p),
_ => {
return Err(anyhow!(
"Unsupported relocation type {:?} for elf aarch64",
r_type
))
}
}
std::io::stdout().flush().unwrap();
Ok(())
}
fn handle_relocation_macho_aarch64(
s: *const u8,
a: i64,
p: *mut u8,
r_type: u8,
_r_pcrel: bool,
r_length: u8,
) -> Result<()> {
match r_type {
ARM64_RELOC_UNSIGNED => {
let size = match r_length {
1 => 16,
2 => 32,
3 => 64,
_ => return Err(anyhow!("Unsupported relocation length {:?}", r_length)),
};
arm64_absolute(s, a, p, size);
}
ARM64_RELOC_PAGEOFF12 => arm64_page_offset(s, a, p),
ARM64_RELOC_PAGE21 => arm64_relative_page(s, a, p),
ARM64_RELOC_BRANCH26 => arm64_branch(s, a, p),
_ => {
return Err(anyhow!(
"Unsupported relocation type {:?} for macho aarch64",
r_type
))
}
}
std::io::stdout().flush().unwrap();
Ok(())
}
fn arm64_absolute(s: *const u8, a: i64, p: *mut u8, size: u8) {
let val = i64::try_from(s as usize).unwrap() + a;
match size {
16 => unsafe { (p as *mut u16).write_unaligned(val as u16) },
32 => unsafe { (p as *mut u32).write_unaligned(val as u32) },
64 => unsafe { (p as *mut u64).write_unaligned(val as u64) },
_ => panic!("Unsupported relocation length {size:?}"),
}
}
fn arm64_branch(s: *const u8, a: i64, p: *mut u8) {
let val = i64::try_from(s as usize).unwrap() + a - i64::try_from(p as usize).unwrap();
let mut val = (i32::try_from(val).unwrap() as u32) >> 2;
let mask: u32 = 0xffffffff << 26;
val &= !mask;
let mut instr = unsafe { (p as *const u32).read_unaligned() };
instr &= mask;
instr |= val;
unsafe { (p as *mut u32).write_unaligned(instr) };
}
fn arm64_relative_page(s: *const u8, a: i64, p: *mut u8) {
let val = ((i64::try_from(s as usize).unwrap() + a) >> 12) - ((p as i64) >> 12);
let val = val as u32;
let masklo = 0b00000000000000000000000000000011;
let maskhi = 0b00000000000111111111111111111100;
let immlo = (val & masklo) << 29;
let immhi = (val & maskhi) << (5 - 2);
let mask = 0b10011111000000000000000000011111;
let mut instr = unsafe { (p as *const u32).read_unaligned() };
instr &= mask;
instr |= immlo | immhi;
unsafe {
(p as *mut u32).write_unaligned(instr);
}
}
fn arm64_page_offset(s: *const u8, a: i64, p: *mut u8) {
let mask_add: u32 = 0b11111111110000000000001111111111;
let val = i64::try_from(s as usize).unwrap() + a;
let val = val as u32;
let mut val = (val << 10) & !mask_add;
let mut instr = unsafe { (p as *const u32).read_unaligned() };
if (instr & 0x3b00_0000) == 0x3900_0000 {
let mut scale = instr >> 30;
if scale == 0 && (instr & 0x0480_0000) == 0x0480_0000 {
scale = 4;
}
val = (val >> scale) & !mask_add;
}
instr &= mask_add;
instr |= val;
unsafe { (p as *mut u32).write_unaligned(instr) };
}
fn relocation(rela: &Relocation, s: *const u8, p: *mut u8) -> Result<()> {
let a = rela.addend();
match rela.flags() {
RelocationFlags::Coff { typ: _ } => match ARCH {
"x86_64" => handle_relocation_generic_x86(rela, s, p),
"x86" => handle_relocation_generic_x86(rela, s, p),
_ => Err(anyhow!(
"Unsupported architecture {} for Coff relocations",
ARCH
))?,
},
RelocationFlags::Elf { r_type } => match ARCH {
"x86_64" => handle_relocation_generic_x86(rela, s, p),
"x86" => handle_relocation_generic_x86(rela, s, p),
"aarch64" => handle_relocation_elf_aarch64(s, a, p, r_type, rela.size()),
_ => Err(anyhow!(
"Unsupported architecture {} for ELF relocations",
ARCH
))?,
},
RelocationFlags::MachO {
r_type,
r_pcrel,
r_length,
} => match ARCH {
"aarch64" => handle_relocation_macho_aarch64(s, a, p, r_type, r_pcrel, r_length),
_ => Err(anyhow!(
"Unsupported architecture {} for MachO relocations",
ARCH
))?,
},
_ => Err(anyhow!("Only ELF and MachO relocations are supported")),
}
}
pub(crate) fn symbol_offset(
file: &File,
symbol: &Symbol,
section: &Section<'_, '_>,
) -> Result<isize> {
match file.format() {
BinaryFormat::Elf | BinaryFormat::Coff => {
Ok(symbol.address() as isize)
}
BinaryFormat::MachO => {
Ok(symbol.address() as isize - section.address() as isize)
}
_ => Err(anyhow!(
"Unsupported binary format {:?}, only ELF and MachO are supported",
file.format()
)),
}
}
pub(crate) fn handle_relocation(
file: &File<'_>,
rela: &Relocation,
p: *mut u8,
mapped_sections: &HashMap<String, MappedSection>,
) -> Result<()> {
let symbol_index = match rela.target() {
RelocationTarget::Symbol(s) => s,
_ => Err(anyhow!(
"Only relocation targets that are symbols are supported"
))?,
};
let symbol = file.symbol_by_index(symbol_index).unwrap();
let s = match symbol.section_index() {
Some(section_index) => {
let section = file.section_by_index(section_index).unwrap();
let section_name = section.name().expect("Could not get section name");
let section_ptr = mapped_sections[section_name].as_ptr();
let offset = symbol_offset(file, &symbol, §ion)?;
unsafe { section_ptr.offset(offset) }
}
None => {
function_resolver(symbol.name().unwrap()).ok_or(anyhow!(
"Could not resolve function {}",
symbol.name().unwrap()
))?
}
};
relocation(rela, s, p)
}
pub(crate) fn handle_jump_entry(
file: &File<'_>,
rela: &Relocation,
p: *mut u8,
jumptable_entry: &mut JumpTableEntry,
) -> Result<()> {
let symbol_index = match rela.target() {
RelocationTarget::Symbol(s) => s,
_ => Err(anyhow!(
"Only relocation targets that are symbols are supported"
))?,
};
let symbol = file.symbol_by_index(symbol_index).unwrap();
let symbol_name = symbol.name().unwrap();
let addr = function_resolver(symbol_name)
.ok_or(anyhow!("Could not resolve function {}", symbol_name))?;
*jumptable_entry = JumpTableEntry::new(addr);
let s = jumptable_entry.jump_ptr();
relocation(rela, s, p)
}