use std::collections::HashMap;
use crate::{
assembly::{decode_stream, Operand},
cilassembly::{changes::ChangeRef, writer::output::Output, AssemblyChanges},
metadata::{
method::{ExceptionHandler, ExceptionHandlerFlags, MethodBody},
tables::TableId,
},
Parser, Result,
};
const USERSTRING_TABLE_ID: u8 = 0x70;
const USERSTRING_PLACEHOLDER_ID: u8 = 0xF0;
pub fn write_method_body(
output: &mut Output,
offset: u64,
body: &MethodBody,
il_code: &[u8],
) -> Result<u64> {
let start_pos = if body.is_fat {
align_to_4(offset)
} else {
offset
};
let mut writer = output.writer_at(start_pos);
let bytes_written = body.write_to(&mut writer, il_code)?;
Ok(bytes_written)
}
#[inline]
fn align_to_4(pos: u64) -> u64 {
(pos + 3) & !3
}
pub fn remap_il_tokens(
il_bytes: &mut [u8],
token_map: &HashMap<u32, u32>,
userstring_map: &HashMap<u32, u32>,
changes: Option<&AssemblyChanges>,
) -> Result<()> {
if il_bytes.is_empty() {
return Ok(());
}
let mut parser = Parser::new(il_bytes);
let instructions = decode_stream(&mut parser, 0)?;
for instr in &instructions {
if let Operand::Token(token) = &instr.operand {
let token_value = token.value();
let table_id = token_value >> 24;
let row = token_value & 0x00FF_FFFF;
let new_token_value = if ChangeRef::is_placeholder(row) {
changes
.and_then(|c| c.lookup_by_placeholder(row))
.and_then(|cr| cr.token())
.map(|t| t.value())
} else if table_id == u32::from(USERSTRING_TABLE_ID) {
let offset = token_value & 0x00FF_FFFF;
userstring_map.get(&offset).map(|&new_offset| {
(u32::from(USERSTRING_TABLE_ID) << 24) | (new_offset & 0x00FF_FFFF)
})
} else if table_id == u32::from(USERSTRING_PLACEHOLDER_ID) {
if let Some(changes) = changes {
let offset_part = token_value & 0x00FF_FFFF;
let heap_placeholder = 0x8000_0000 | offset_part;
if let Some(change_ref) = changes.lookup_by_placeholder(heap_placeholder) {
change_ref.offset().map(|actual_offset| {
(u32::from(USERSTRING_TABLE_ID) << 24) | (actual_offset & 0x00FF_FFFF)
})
} else {
None
}
} else {
None
}
} else {
token_map.get(&token_value).copied()
};
if let Some(new_value) = new_token_value {
#[allow(clippy::cast_possible_truncation)]
let token_offset = instr.offset as usize + instr.size as usize - 4;
if token_offset + 4 <= il_bytes.len() {
il_bytes[token_offset..token_offset + 4]
.copy_from_slice(&new_value.to_le_bytes());
}
}
}
}
Ok(())
}
pub fn remap_method_body_tokens(
method_body: &mut [u8],
token_map: &HashMap<u32, u32>,
userstring_map: &HashMap<u32, u32>,
changes: Option<&AssemblyChanges>,
) -> Result<()> {
if method_body.is_empty() {
return Ok(());
}
let parsed = MethodBody::from(method_body)?;
if parsed.is_fat && parsed.local_var_sig_token != 0 {
let mut new_local_var_sig_token = parsed.local_var_sig_token;
let table_id = new_local_var_sig_token >> 24;
let row = new_local_var_sig_token & 0x00FF_FFFF;
if table_id == u32::from(TableId::StandAloneSig.token_type())
&& ChangeRef::is_placeholder(row)
{
if let Some(changes) = changes {
if let Some(change_ref) = changes.lookup_by_placeholder(row) {
if let Some(resolved_token) = change_ref.token() {
new_local_var_sig_token = resolved_token.value();
}
}
}
}
if let Some(&new_token) = token_map.get(&new_local_var_sig_token) {
new_local_var_sig_token = new_token;
}
if new_local_var_sig_token != parsed.local_var_sig_token && method_body.len() >= 12 {
method_body[8..12].copy_from_slice(&new_local_var_sig_token.to_le_bytes());
}
}
let il_start = parsed.size_header;
let il_end = il_start + parsed.size_code;
if il_end <= method_body.len() {
let il_slice = &mut method_body[il_start..il_end];
remap_il_tokens(il_slice, token_map, userstring_map, changes)?;
}
if !parsed.exception_handlers.is_empty() {
let aligned_offset = (il_end + 3) & !3;
if aligned_offset < method_body.len() {
remap_exception_handler_tokens(
&mut method_body[aligned_offset..],
&parsed.exception_handlers,
token_map,
);
}
}
Ok(())
}
fn remap_exception_handler_tokens(
exception_data: &mut [u8],
handlers: &[ExceptionHandler],
token_map: &HashMap<u32, u32>,
) {
if exception_data.is_empty() || handlers.is_empty() {
return;
}
let needs_fat = handlers.iter().any(|h| {
h.try_offset > 0xFFFF
|| h.try_length > 0xFF
|| h.handler_offset > 0xFFFF
|| h.handler_length > 0xFF
});
let header_size = 4;
let handler_size = if needs_fat { 24 } else { 12 };
let class_token_offset = if needs_fat { 16 } else { 8 };
for (i, handler) in handlers.iter().enumerate() {
if handler.flags != ExceptionHandlerFlags::EXCEPTION || handler.filter_offset == 0 {
continue;
}
let catch_token = handler.filter_offset;
if let Some(table_id) = TableId::from_token_type((catch_token >> 24) as u8) {
if matches!(
table_id,
TableId::TypeRef | TableId::TypeDef | TableId::TypeSpec
) {
if let Some(&new_token) = token_map.get(&catch_token) {
let token_pos = header_size + (i * handler_size) + class_token_offset;
if token_pos + 4 <= exception_data.len() {
exception_data[token_pos..token_pos + 4]
.copy_from_slice(&new_token.to_le_bytes());
}
}
}
}
}
}
pub fn remap_method_bodies_region(
region_data: &mut [u8],
method_offsets: &[usize],
userstring_map: &HashMap<u32, u32>,
token_map: &HashMap<u32, u32>,
) -> Result<()> {
if method_offsets.is_empty() || region_data.is_empty() {
return Ok(());
}
if token_map.is_empty() && userstring_map.is_empty() {
return Ok(());
}
for &offset in method_offsets {
if offset >= region_data.len() {
continue;
}
let method_body_size = {
let method_body_slice = ®ion_data[offset..];
match MethodBody::from(method_body_slice) {
Ok(body) => body.size(),
Err(_) => {
continue;
}
}
};
if offset + method_body_size > region_data.len() {
continue;
}
let method_body_bytes = &mut region_data[offset..offset + method_body_size];
remap_method_body_tokens(method_body_bytes, token_map, userstring_map, None)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Read;
use tempfile::tempdir;
#[test]
fn test_write_tiny_method() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("test.bin");
let mut output = Output::create(&path, 1024).unwrap();
let il_code = vec![0x02, 0x2A];
let body = MethodBody {
size_code: 2,
size_header: 1,
local_var_sig_token: 0,
max_stack: 8,
is_fat: false,
is_init_local: false,
is_exception_data: false,
exception_handlers: vec![],
};
let bytes_written = write_method_body(&mut output, 0, &body, &il_code).unwrap();
assert_eq!(bytes_written, 3);
output.finalize(None).unwrap();
let mut file = File::open(&path).unwrap();
let mut contents = Vec::new();
file.read_to_end(&mut contents).unwrap();
assert_eq!(contents[0], 0x0A);
assert_eq!(contents[1], 0x02);
assert_eq!(contents[2], 0x2A);
}
#[test]
fn test_write_fat_method() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path().join("test.bin");
let mut output = Output::create(&path, 1024).unwrap();
let il_code = vec![0x00; 100]; let body = MethodBody {
size_code: 100,
size_header: 12,
local_var_sig_token: 0x11000001,
max_stack: 8,
is_fat: true,
is_init_local: true,
is_exception_data: false,
exception_handlers: vec![],
};
let bytes_written = write_method_body(&mut output, 0, &body, &il_code).unwrap();
assert_eq!(bytes_written, 112);
output.finalize(None).unwrap();
let mut file = File::open(&path).unwrap();
let mut contents = Vec::new();
file.read_to_end(&mut contents).unwrap();
let parsed = MethodBody::from(&contents).unwrap();
assert!(parsed.is_fat);
assert!(parsed.is_init_local);
assert_eq!(parsed.size_code, 100);
assert_eq!(parsed.max_stack, 8);
assert_eq!(parsed.local_var_sig_token, 0x11000001);
}
fn read_token_at(bytes: &[u8], offset: usize) -> u32 {
u32::from_le_bytes([
bytes[offset],
bytes[offset + 1],
bytes[offset + 2],
bytes[offset + 3],
])
}
#[test]
fn test_remap_il_tokens_ldstr() {
let mut il_bytes = vec![
0x00, 0x72, 0x00, 0x01, 0x00, 0x70, 0x2A, ];
let mut userstring_map = HashMap::new();
userstring_map.insert(0x100, 0x200);
let token_map = HashMap::new();
remap_il_tokens(&mut il_bytes, &token_map, &userstring_map, None).unwrap();
let new_token = read_token_at(&il_bytes, 2);
assert_eq!(new_token, 0x70000200);
}
#[test]
fn test_remap_il_tokens_call() {
let mut il_bytes = vec![
0x00, 0x28, 0x01, 0x00, 0x00, 0x06, 0x2A, ];
let userstring_map = HashMap::new();
let mut token_map = HashMap::new();
token_map.insert(0x06000001, 0x06000005);
remap_il_tokens(&mut il_bytes, &token_map, &userstring_map, None).unwrap();
let new_token = read_token_at(&il_bytes, 2);
assert_eq!(new_token, 0x06000005);
}
#[test]
fn test_remap_il_tokens_multiple() {
let mut il_bytes = vec![
0x72, 0x00, 0x01, 0x00, 0x70, 0x28, 0x01, 0x00, 0x00, 0x06, 0x6F, 0x02, 0x00, 0x00, 0x06, 0x2A, ];
let mut userstring_map = HashMap::new();
userstring_map.insert(0x100, 0x200);
let mut token_map = HashMap::new();
token_map.insert(0x06000001, 0x06000010);
token_map.insert(0x06000002, 0x06000020);
remap_il_tokens(&mut il_bytes, &token_map, &userstring_map, None).unwrap();
assert_eq!(read_token_at(&il_bytes, 1), 0x70000200);
assert_eq!(read_token_at(&il_bytes, 6), 0x06000010);
assert_eq!(read_token_at(&il_bytes, 11), 0x06000020);
}
#[test]
fn test_remap_il_tokens_no_match() {
let mut il_bytes = vec![
0x72, 0x00, 0x01, 0x00, 0x70, 0x28, 0x01, 0x00, 0x00, 0x06, 0x2A, ];
let userstring_map = HashMap::new(); let token_map = HashMap::new();
remap_il_tokens(&mut il_bytes, &token_map, &userstring_map, None).unwrap();
assert_eq!(read_token_at(&il_bytes, 1), 0x70000100);
assert_eq!(read_token_at(&il_bytes, 6), 0x06000001);
}
#[test]
fn test_remap_il_tokens_two_byte_opcode() {
let mut il_bytes = vec![
0x00, 0xFE, 0x1C, 0x01, 0x00, 0x00, 0x02, 0x26, 0x2A, ];
let userstring_map = HashMap::new();
let mut token_map = HashMap::new();
token_map.insert(0x02000001, 0x02000005);
remap_il_tokens(&mut il_bytes, &token_map, &userstring_map, None).unwrap();
let new_token = read_token_at(&il_bytes, 3);
assert_eq!(new_token, 0x02000005);
}
}