mirror of
https://github.com/Momoko-Ayase/Senbei.git
synced 2026-09-19 03:57:59 -04:00
fix(android): support compact ELF dynamic table layouts
This commit is contained in:
@@ -37,6 +37,8 @@ External companion inputs are reconstructed as `stub[..4096]` followed by the ma
|
|||||||
|
|
||||||
Android protection primitives are in `senbei-crypto/src/android/`. Android metadata restoration is in `senbei-metadata/src/android/` and only rewrites MethodDef token fields. The Windows structural metadata transform is in `senbei-metadata/src/windows/`.
|
Android protection primitives are in `senbei-crypto/src/android/`. Android metadata restoration is in `senbei-metadata/src/android/` and only rewrites MethodDef token fields. The Windows structural metadata transform is in `senbei-metadata/src/windows/`.
|
||||||
|
|
||||||
|
Android ELF dynamic tables are located from the input section table and its actual file ranges. When the original gap is too small, restoration adds a validated read-only `PT_LOAD` after the existing load image and updates the dynamic tags; it never overwrites an adjacent section or emits a partial image.
|
||||||
|
|
||||||
## Scanning and Packages
|
## Scanning and Packages
|
||||||
|
|
||||||
Folder scanning uses platform target names to avoid opening bulk assets: Windows candidates are `.exe`, `.dll`, and `global-metadata.dat`; Android candidates are `.so` and `global-metadata.dat`. A Windows `.exe._` or `.dll._` companion is auxiliary input for its sibling stub and is excluded from the skipped count.
|
Folder scanning uses platform target names to avoid opening bulk assets: Windows candidates are `.exe`, `.dll`, and `global-metadata.dat`; Android candidates are `.so` and `global-metadata.dat`. A Windows `.exe._` or `.dll._` companion is auxiliary input for its sibling stub and is excluded from the skipped count.
|
||||||
|
|||||||
@@ -542,7 +542,6 @@ impl HuffmanLzDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the native word transform and optional AES-256-CBC decryption.
|
/// Apply the native word transform and optional AES-256-CBC decryption.
|
||||||
#[allow(clippy::chunks_exact_to_as_chunks)]
|
|
||||||
pub fn transform_segment(
|
pub fn transform_segment(
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
seed: u32,
|
seed: u32,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ pub(crate) const SHT_NOBITS: u32 = 8;
|
|||||||
pub(crate) const SHT_STRTAB: u32 = 3;
|
pub(crate) const SHT_STRTAB: u32 = 3;
|
||||||
pub(crate) const SHT_LOUSER: u32 = 0x8000_0000;
|
pub(crate) const SHT_LOUSER: u32 = 0x8000_0000;
|
||||||
pub(crate) const SHF_ALLOC: u64 = 2;
|
pub(crate) const SHF_ALLOC: u64 = 2;
|
||||||
|
const PT_LOAD: u32 = 1;
|
||||||
|
pub(crate) const PF_R: u32 = 4;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub(crate) struct LoadSegment {
|
pub(crate) struct LoadSegment {
|
||||||
@@ -12,6 +14,7 @@ pub(crate) struct LoadSegment {
|
|||||||
pub file_size: u64,
|
pub file_size: u64,
|
||||||
pub memory_size: u64,
|
pub memory_size: u64,
|
||||||
pub flags: u32,
|
pub flags: u32,
|
||||||
|
pub alignment: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -65,6 +68,9 @@ impl SectionHeader {
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub(crate) struct ElfLayout {
|
pub(crate) struct ElfLayout {
|
||||||
pub entrypoint: u64,
|
pub entrypoint: u64,
|
||||||
|
pub program_header_offset: usize,
|
||||||
|
pub program_header_size: usize,
|
||||||
|
pub program_header_count: usize,
|
||||||
pub program_headers: Vec<LoadSegment>,
|
pub program_headers: Vec<LoadSegment>,
|
||||||
pub section_headers: Vec<SectionHeader>,
|
pub section_headers: Vec<SectionHeader>,
|
||||||
pub section_name_index: usize,
|
pub section_name_index: usize,
|
||||||
@@ -95,7 +101,7 @@ impl ElfLayout {
|
|||||||
let mut program_headers = Vec::new();
|
let mut program_headers = Vec::new();
|
||||||
for index in 0..program_header_count {
|
for index in 0..program_header_count {
|
||||||
let offset = checked_index(program_header_offset, index, program_header_size)?;
|
let offset = checked_index(program_header_offset, index, program_header_size)?;
|
||||||
if read_u32(data, offset)? != 1 {
|
if read_u32(data, offset)? != PT_LOAD {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let segment = LoadSegment {
|
let segment = LoadSegment {
|
||||||
@@ -104,6 +110,7 @@ impl ElfLayout {
|
|||||||
virtual_address: read_u64(data, offset + 0x10)?,
|
virtual_address: read_u64(data, offset + 0x10)?,
|
||||||
file_size: read_u64(data, offset + 0x20)?,
|
file_size: read_u64(data, offset + 0x20)?,
|
||||||
memory_size: read_u64(data, offset + 0x28)?,
|
memory_size: read_u64(data, offset + 0x28)?,
|
||||||
|
alignment: read_u64(data, offset + 0x30)?,
|
||||||
};
|
};
|
||||||
let file_end = segment
|
let file_end = segment
|
||||||
.offset
|
.offset
|
||||||
@@ -148,6 +155,9 @@ impl ElfLayout {
|
|||||||
};
|
};
|
||||||
let layout = Self {
|
let layout = Self {
|
||||||
entrypoint,
|
entrypoint,
|
||||||
|
program_header_offset,
|
||||||
|
program_header_size,
|
||||||
|
program_header_count,
|
||||||
program_headers,
|
program_headers,
|
||||||
section_headers,
|
section_headers,
|
||||||
section_name_index,
|
section_name_index,
|
||||||
@@ -197,6 +207,116 @@ impl ElfLayout {
|
|||||||
.ok_or_else(|| Error::Invalid("ELF has no PT_LOAD file range".to_owned()))
|
.ok_or_else(|| Error::Invalid("ELF has no PT_LOAD file range".to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_alignment(&self) -> Result<u64> {
|
||||||
|
let alignment = self
|
||||||
|
.program_headers
|
||||||
|
.iter()
|
||||||
|
.map(|segment| segment.alignment)
|
||||||
|
.max()
|
||||||
|
.ok_or_else(|| Error::Invalid("ELF has no PT_LOAD alignment".to_owned()))?;
|
||||||
|
if alignment == 0 || !alignment.is_power_of_two() {
|
||||||
|
return invalid(format!("invalid PT_LOAD alignment 0x{alignment:x}"));
|
||||||
|
}
|
||||||
|
Ok(alignment)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_load_segment(&self, output: &mut [u8], segment: LoadSegment) -> Result<Self> {
|
||||||
|
if self.program_header_size != 0x38 {
|
||||||
|
return invalid("unexpected ELF program header size");
|
||||||
|
}
|
||||||
|
if segment.file_size == 0 {
|
||||||
|
return invalid("new PT_LOAD has no file contents");
|
||||||
|
}
|
||||||
|
if segment.memory_size < segment.file_size {
|
||||||
|
return invalid("new PT_LOAD memory size is smaller than file size");
|
||||||
|
}
|
||||||
|
if segment.alignment == 0 || !segment.alignment.is_power_of_two() {
|
||||||
|
return invalid(format!(
|
||||||
|
"invalid new PT_LOAD alignment 0x{:x}",
|
||||||
|
segment.alignment
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if segment.offset % segment.alignment != segment.virtual_address % segment.alignment {
|
||||||
|
return invalid("new PT_LOAD offset and address are misaligned");
|
||||||
|
}
|
||||||
|
let segment_file_end = segment
|
||||||
|
.offset
|
||||||
|
.checked_add(segment.file_size)
|
||||||
|
.ok_or_else(|| Error::Invalid("new PT_LOAD file range overflow".to_owned()))?;
|
||||||
|
let segment_memory_end = segment
|
||||||
|
.virtual_address
|
||||||
|
.checked_add(segment.memory_size)
|
||||||
|
.ok_or_else(|| Error::Invalid("new PT_LOAD memory range overflow".to_owned()))?;
|
||||||
|
if segment_file_end > output.len() as u64 {
|
||||||
|
return invalid("new PT_LOAD exceeds output mapping");
|
||||||
|
}
|
||||||
|
for existing in &self.program_headers {
|
||||||
|
let existing_file_end = existing
|
||||||
|
.offset
|
||||||
|
.checked_add(existing.file_size)
|
||||||
|
.ok_or_else(|| Error::Invalid("PT_LOAD file range overflow".to_owned()))?;
|
||||||
|
if segment.offset < existing_file_end && existing.offset < segment_file_end {
|
||||||
|
return invalid("new PT_LOAD overlaps an existing file range");
|
||||||
|
}
|
||||||
|
let existing_memory_end = existing
|
||||||
|
.virtual_address
|
||||||
|
.checked_add(existing.memory_size)
|
||||||
|
.ok_or_else(|| Error::Invalid("PT_LOAD memory range overflow".to_owned()))?;
|
||||||
|
if segment.virtual_address < existing_memory_end
|
||||||
|
&& existing.virtual_address < segment_memory_end
|
||||||
|
{
|
||||||
|
return invalid("new PT_LOAD overlaps an existing memory range");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let new_count = self
|
||||||
|
.program_header_count
|
||||||
|
.checked_add(1)
|
||||||
|
.ok_or_else(|| Error::Invalid("program header count overflow".to_owned()))?;
|
||||||
|
let new_count_u16 = u16::try_from(new_count)
|
||||||
|
.map_err(|_| Error::Invalid("program header count exceeds u16".to_owned()))?;
|
||||||
|
let header_offset = checked_index(
|
||||||
|
self.program_header_offset,
|
||||||
|
self.program_header_count,
|
||||||
|
self.program_header_size,
|
||||||
|
)?;
|
||||||
|
let header_end = header_offset
|
||||||
|
.checked_add(self.program_header_size)
|
||||||
|
.ok_or_else(|| Error::Invalid("new program header range overflow".to_owned()))?;
|
||||||
|
slice(output, header_offset, self.program_header_size)?;
|
||||||
|
let first_file_section = self
|
||||||
|
.section_headers
|
||||||
|
.iter()
|
||||||
|
.filter(|section| section.section_type != SHT_NOBITS && section.size != 0)
|
||||||
|
.map(|section| section.offset)
|
||||||
|
.min();
|
||||||
|
if first_file_section.is_some_and(|offset| header_end as u64 > offset) {
|
||||||
|
return invalid("no space for an additional program header");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut header = [0_u8; 0x38];
|
||||||
|
header[0..4].copy_from_slice(&PT_LOAD.to_le_bytes());
|
||||||
|
header[4..8].copy_from_slice(&segment.flags.to_le_bytes());
|
||||||
|
header[8..0x10].copy_from_slice(&segment.offset.to_le_bytes());
|
||||||
|
header[0x10..0x18].copy_from_slice(&segment.virtual_address.to_le_bytes());
|
||||||
|
header[0x18..0x20].copy_from_slice(&segment.virtual_address.to_le_bytes());
|
||||||
|
header[0x20..0x28].copy_from_slice(&segment.file_size.to_le_bytes());
|
||||||
|
header[0x28..0x30].copy_from_slice(&segment.memory_size.to_le_bytes());
|
||||||
|
header[0x30..0x38].copy_from_slice(&segment.alignment.to_le_bytes());
|
||||||
|
output
|
||||||
|
.get_mut(header_offset..header_end)
|
||||||
|
.ok_or_else(|| Error::Invalid("new program header exceeds output".to_owned()))?
|
||||||
|
.copy_from_slice(&header);
|
||||||
|
output
|
||||||
|
.get_mut(0x38..0x3a)
|
||||||
|
.ok_or_else(|| Error::Invalid("ELF header is truncated".to_owned()))?
|
||||||
|
.copy_from_slice(&new_count_u16.to_le_bytes());
|
||||||
|
|
||||||
|
let mut updated = self.clone();
|
||||||
|
updated.program_header_count = new_count;
|
||||||
|
updated.program_headers.push(segment);
|
||||||
|
Ok(updated)
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve every section's name from the ELF `shstrtab` section.
|
/// Resolve every section's name from the ELF `shstrtab` section.
|
||||||
///
|
///
|
||||||
/// The returned names are source data, not role labels supplied by the
|
/// The returned names are source data, not role labels supplied by the
|
||||||
@@ -349,6 +469,9 @@ mod tests {
|
|||||||
fn layout(name_index: u32) -> ElfLayout {
|
fn layout(name_index: u32) -> ElfLayout {
|
||||||
ElfLayout {
|
ElfLayout {
|
||||||
entrypoint: 0,
|
entrypoint: 0,
|
||||||
|
program_header_offset: 0,
|
||||||
|
program_header_size: 0x38,
|
||||||
|
program_header_count: 0,
|
||||||
program_headers: Vec::new(),
|
program_headers: Vec::new(),
|
||||||
section_headers: vec![
|
section_headers: vec![
|
||||||
SectionHeader {
|
SectionHeader {
|
||||||
@@ -437,4 +560,77 @@ mod tests {
|
|||||||
.expect_err("unterminated table");
|
.expect_err("unterminated table");
|
||||||
assert!(error.to_string().contains("not NUL terminated"));
|
assert!(error.to_string().contains("not NUL terminated"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn append_load_segment_updates_program_headers() {
|
||||||
|
let elf_layout = ElfLayout {
|
||||||
|
entrypoint: 0,
|
||||||
|
program_header_offset: 0,
|
||||||
|
program_header_size: 0x38,
|
||||||
|
program_header_count: 0,
|
||||||
|
program_headers: Vec::new(),
|
||||||
|
section_headers: Vec::new(),
|
||||||
|
section_name_index: 0,
|
||||||
|
private_section_index: usize::MAX,
|
||||||
|
};
|
||||||
|
let mut output = vec![0_u8; 0x2000];
|
||||||
|
let updated = elf_layout
|
||||||
|
.append_load_segment(
|
||||||
|
&mut output,
|
||||||
|
LoadSegment {
|
||||||
|
offset: 0x1000,
|
||||||
|
virtual_address: 0x2000,
|
||||||
|
file_size: 0x20,
|
||||||
|
memory_size: 0x20,
|
||||||
|
flags: PF_R,
|
||||||
|
alignment: 0x1000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("append segment");
|
||||||
|
assert_eq!(updated.program_header_count, 1);
|
||||||
|
assert_eq!(updated.program_headers[0].virtual_address, 0x2000);
|
||||||
|
assert_eq!(&output[0..4], &PT_LOAD.to_le_bytes());
|
||||||
|
assert_eq!(&output[0x38..0x3a], &1_u16.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn append_load_segment_rejects_program_header_overlap() {
|
||||||
|
let mut elf_layout = ElfLayout {
|
||||||
|
entrypoint: 0,
|
||||||
|
program_header_offset: 0,
|
||||||
|
program_header_size: 0x38,
|
||||||
|
program_header_count: 0,
|
||||||
|
program_headers: Vec::new(),
|
||||||
|
section_headers: Vec::new(),
|
||||||
|
section_name_index: 0,
|
||||||
|
private_section_index: usize::MAX,
|
||||||
|
};
|
||||||
|
elf_layout.section_headers.push(SectionHeader {
|
||||||
|
name: 0,
|
||||||
|
section_type: 1,
|
||||||
|
flags: 0,
|
||||||
|
address: 0,
|
||||||
|
offset: 0x20,
|
||||||
|
size: 1,
|
||||||
|
link: 0,
|
||||||
|
info: 0,
|
||||||
|
alignment: 1,
|
||||||
|
entry_size: 0,
|
||||||
|
});
|
||||||
|
let mut output = vec![0_u8; 0x100];
|
||||||
|
let error = elf_layout
|
||||||
|
.append_load_segment(
|
||||||
|
&mut output,
|
||||||
|
LoadSegment {
|
||||||
|
offset: 0x80,
|
||||||
|
virtual_address: 0x1080,
|
||||||
|
file_size: 0x20,
|
||||||
|
memory_size: 0x20,
|
||||||
|
flags: PF_R,
|
||||||
|
alignment: 0x1000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect_err("overlapping program header");
|
||||||
|
assert!(error.to_string().contains("additional program header"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ use super::artifact::load_artifacts;
|
|||||||
use super::error::{Error, Result, invalid};
|
use super::error::{Error, Result, invalid};
|
||||||
use super::hash::{build_gnu_hash, build_sysv_hash};
|
use super::hash::{build_gnu_hash, build_sysv_hash};
|
||||||
use super::layout::{
|
use super::layout::{
|
||||||
ElfLayout, SHF_ALLOC, SHT_LOUSER, SHT_NOBITS, SectionHeader, align_up, read_i64, read_u32,
|
ElfLayout, LoadSegment, PF_R, SHF_ALLOC, SHT_LOUSER, SHT_NOBITS, SectionHeader, align_up,
|
||||||
read_u64, slice, slice_u64, usize_from_u64,
|
read_i64, read_u32, read_u64, slice, slice_u64, usize_from_u64,
|
||||||
};
|
};
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 16 * 1024 * 1024;
|
const CHUNK_SIZE: usize = 16 * 1024 * 1024;
|
||||||
@@ -100,6 +100,12 @@ pub struct PlacementReport {
|
|||||||
pub size: usize,
|
pub size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TablePayload {
|
||||||
|
name: &'static str,
|
||||||
|
alignment: u64,
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
pub struct ElfMaterializationReport {
|
pub struct ElfMaterializationReport {
|
||||||
pub hidden_symbols: HiddenSymbolReport,
|
pub hidden_symbols: HiddenSymbolReport,
|
||||||
@@ -432,6 +438,9 @@ impl AuxiliaryElfImage {
|
|||||||
relocation2_offset: words[10],
|
relocation2_offset: words[10],
|
||||||
relocation2_count: words[11],
|
relocation2_count: words[11],
|
||||||
};
|
};
|
||||||
|
if result.dynsym_count == 0 {
|
||||||
|
return invalid("auxiliary dynamic symbol table has no null entry");
|
||||||
|
}
|
||||||
if result.relocation1_offset != 0x40 {
|
if result.relocation1_offset != 0x40 {
|
||||||
return invalid("auxiliary relocation table does not follow its header");
|
return invalid("auxiliary relocation table does not follow its header");
|
||||||
}
|
}
|
||||||
@@ -470,9 +479,6 @@ impl AuxiliaryElfImage {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if result.dynsym_count < 2 {
|
|
||||||
return invalid("auxiliary dynamic symbol table is empty");
|
|
||||||
}
|
|
||||||
if slice(data, result.dynsym_offset as usize, ELF64_SYMBOL_SIZE)?
|
if slice(data, result.dynsym_offset as usize, ELF64_SYMBOL_SIZE)?
|
||||||
.iter()
|
.iter()
|
||||||
.any(|&byte| byte != 0)
|
.any(|&byte| byte != 0)
|
||||||
@@ -743,8 +749,30 @@ fn patch_dynamic_tags(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dynamic_contains_tag(output: &[u8], dynamic: SectionHeader, wanted: u64) -> Result<bool> {
|
||||||
|
if dynamic.size % 0x10 != 0 {
|
||||||
|
return invalid(".dynamic size is not entry-aligned");
|
||||||
|
}
|
||||||
|
let start = usize_from_u64(dynamic.offset, ".dynamic offset")?;
|
||||||
|
let size = usize_from_u64(dynamic.size, ".dynamic size")?;
|
||||||
|
let end = start
|
||||||
|
.checked_add(size)
|
||||||
|
.ok_or_else(|| Error::Invalid(".dynamic end overflow".to_owned()))?;
|
||||||
|
slice(output, start, size)?;
|
||||||
|
for offset in (start..end).step_by(0x10) {
|
||||||
|
let tag = read_u64(output, offset)?;
|
||||||
|
if tag == wanted {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
if tag == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
fn required_section_indices(names: &[String]) -> Result<HashMap<&'static str, usize>> {
|
fn required_section_indices(names: &[String]) -> Result<HashMap<&'static str, usize>> {
|
||||||
const REQUIRED: [&str; 9] = [
|
const REQUIRED: [&str; 8] = [
|
||||||
".dynsym",
|
".dynsym",
|
||||||
".gnu.version",
|
".gnu.version",
|
||||||
".gnu.version_r",
|
".gnu.version_r",
|
||||||
@@ -753,7 +781,6 @@ fn required_section_indices(names: &[String]) -> Result<HashMap<&'static str, us
|
|||||||
".rela.dyn",
|
".rela.dyn",
|
||||||
".rela.plt",
|
".rela.plt",
|
||||||
".dynamic",
|
".dynamic",
|
||||||
".rodata",
|
|
||||||
];
|
];
|
||||||
let mut result = HashMap::with_capacity(REQUIRED.len());
|
let mut result = HashMap::with_capacity(REQUIRED.len());
|
||||||
for required in REQUIRED {
|
for required in REQUIRED {
|
||||||
@@ -785,13 +812,182 @@ fn required_section_indices(names: &[String]) -> Result<HashMap<&'static str, us
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn metadata_capacity_end(
|
||||||
|
layout: &ElfLayout,
|
||||||
|
indices: &HashMap<&'static str, usize>,
|
||||||
|
metadata_start: u64,
|
||||||
|
) -> Result<u64> {
|
||||||
|
let table_indices = indices.values().copied().collect::<HashSet<_>>();
|
||||||
|
let file_end = layout.private_section()?.offset;
|
||||||
|
let next_section = layout
|
||||||
|
.section_headers
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(index, section)| {
|
||||||
|
!table_indices.contains(index)
|
||||||
|
&& section.section_type != SHT_NOBITS
|
||||||
|
&& section.size != 0
|
||||||
|
&& section.offset >= metadata_start
|
||||||
|
})
|
||||||
|
.map(|(_, section)| section.offset)
|
||||||
|
.min()
|
||||||
|
.unwrap_or(file_end);
|
||||||
|
let capacity_end = next_section.min(file_end);
|
||||||
|
// A zero-length window is a valid result: the caller can move the whole
|
||||||
|
// table set to a new PT_LOAD instead of overwriting an adjacent section.
|
||||||
|
Ok(capacity_end.max(metadata_start))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn metadata_mapping_length(
|
||||||
|
source: &[u8],
|
||||||
|
layout: &ElfLayout,
|
||||||
|
auxiliary_data: &[u8],
|
||||||
|
) -> Result<usize> {
|
||||||
|
let names = layout.section_names(source)?;
|
||||||
|
let indices = required_section_indices(&names)?;
|
||||||
|
let section = |name: &'static str| -> SectionHeader { layout.section_headers[indices[name]] };
|
||||||
|
let dynsym = section(".dynsym");
|
||||||
|
let versym = section(".gnu.version");
|
||||||
|
let verneed = section(".gnu.version_r");
|
||||||
|
let dynstr = section(".dynstr");
|
||||||
|
let rela_dyn = section(".rela.dyn");
|
||||||
|
let rela_plt = section(".rela.plt");
|
||||||
|
if dynsym.entry_size != ELF64_SYMBOL_SIZE as u64
|
||||||
|
|| dynsym.size % ELF64_SYMBOL_SIZE as u64 != 0
|
||||||
|
|| versym.entry_size != 2
|
||||||
|
|| rela_dyn.entry_size != ELF64_RELA_SIZE as u64
|
||||||
|
|| rela_plt.entry_size != ELF64_RELA_SIZE as u64
|
||||||
|
|| rela_dyn.size % ELF64_RELA_SIZE as u64 != 0
|
||||||
|
|| rela_plt.size % ELF64_RELA_SIZE as u64 != 0
|
||||||
|
{
|
||||||
|
return invalid("unexpected dynamic-table entry layout");
|
||||||
|
}
|
||||||
|
let auxiliary = AuxiliaryElfImage::parse(auxiliary_data)?;
|
||||||
|
let old_symbol_count = usize_from_u64(
|
||||||
|
dynsym.size / ELF64_SYMBOL_SIZE as u64,
|
||||||
|
"dynamic symbol count",
|
||||||
|
)?;
|
||||||
|
let appended_count =
|
||||||
|
usize::try_from(auxiliary.dynsym_count.checked_sub(1).ok_or_else(|| {
|
||||||
|
Error::Invalid("auxiliary symbol table has no null entry".to_owned())
|
||||||
|
})?)
|
||||||
|
.map_err(|_| Error::Invalid("auxiliary symbol count exceeds usize".to_owned()))?;
|
||||||
|
let new_symbol_count = old_symbol_count
|
||||||
|
.checked_add(appended_count)
|
||||||
|
.ok_or_else(|| Error::Invalid("merged dynamic symbol count overflow".to_owned()))?;
|
||||||
|
let merged_dynstr_size = usize_from_u64(dynstr.size, ".dynstr size")?
|
||||||
|
.checked_add(auxiliary.dynstr_size as usize)
|
||||||
|
.ok_or_else(|| Error::Invalid("merged dynamic string size overflow".to_owned()))?;
|
||||||
|
let merged_rela_dyn_count =
|
||||||
|
usize_from_u64(rela_dyn.size / ELF64_RELA_SIZE as u64, ".rela.dyn count")?
|
||||||
|
.checked_add(auxiliary.relocation1_count as usize)
|
||||||
|
.and_then(|count| count.checked_add(auxiliary.relocation2_count as usize))
|
||||||
|
.ok_or_else(|| Error::Invalid("merged .rela.dyn count overflow".to_owned()))?;
|
||||||
|
let merged_rela_plt_count =
|
||||||
|
usize_from_u64(rela_plt.size / ELF64_RELA_SIZE as u64, ".rela.plt count")?
|
||||||
|
.checked_add(auxiliary.relocation2_count as usize)
|
||||||
|
.ok_or_else(|| Error::Invalid("merged .rela.plt count overflow".to_owned()))?;
|
||||||
|
let gnu_hash_size = 28_usize
|
||||||
|
.checked_add(
|
||||||
|
new_symbol_count
|
||||||
|
.checked_sub(1)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::Invalid("dynamic symbol table is unexpectedly empty".to_owned())
|
||||||
|
})?
|
||||||
|
.checked_mul(4)
|
||||||
|
.ok_or_else(|| Error::Invalid("GNU hash size overflow".to_owned()))?,
|
||||||
|
)
|
||||||
|
.ok_or_else(|| Error::Invalid("GNU hash size overflow".to_owned()))?;
|
||||||
|
let sysv_hash_size = indices.contains_key(".hash").then(|| {
|
||||||
|
new_symbol_count
|
||||||
|
.checked_mul(2)
|
||||||
|
.and_then(|count| count.checked_add(2))
|
||||||
|
.and_then(|count| count.checked_mul(4))
|
||||||
|
.ok_or_else(|| Error::Invalid("SysV hash size overflow".to_owned()))
|
||||||
|
});
|
||||||
|
let sysv_hash_size = match sysv_hash_size {
|
||||||
|
Some(size) => size?,
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
let mut cursor = 0_u64;
|
||||||
|
for (size, alignment) in [
|
||||||
|
(
|
||||||
|
new_symbol_count
|
||||||
|
.checked_mul(ELF64_SYMBOL_SIZE)
|
||||||
|
.ok_or_else(|| Error::Invalid("merged .dynsym size overflow".to_owned()))?,
|
||||||
|
8,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
usize_from_u64(versym.size, ".gnu.version size")?
|
||||||
|
.checked_add(appended_count.checked_mul(2).ok_or_else(|| {
|
||||||
|
Error::Invalid("merged .gnu.version size overflow".to_owned())
|
||||||
|
})?)
|
||||||
|
.ok_or_else(|| Error::Invalid("merged .gnu.version size overflow".to_owned()))?,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
(usize_from_u64(verneed.size, ".gnu.version_r size")?, 4),
|
||||||
|
(gnu_hash_size, 8),
|
||||||
|
(sysv_hash_size, 4),
|
||||||
|
(merged_dynstr_size, 1),
|
||||||
|
(
|
||||||
|
merged_rela_dyn_count
|
||||||
|
.checked_mul(ELF64_RELA_SIZE)
|
||||||
|
.ok_or_else(|| Error::Invalid("merged .rela.dyn size overflow".to_owned()))?,
|
||||||
|
8,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
merged_rela_plt_count
|
||||||
|
.checked_mul(ELF64_RELA_SIZE)
|
||||||
|
.ok_or_else(|| Error::Invalid("merged .rela.plt size overflow".to_owned()))?,
|
||||||
|
8,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
cursor = align_up(cursor, alignment)?;
|
||||||
|
cursor = cursor
|
||||||
|
.checked_add(size as u64)
|
||||||
|
.ok_or_else(|| Error::Invalid("dynamic-table reserve overflow".to_owned()))?;
|
||||||
|
}
|
||||||
|
let extension_alignment = layout.load_alignment()?;
|
||||||
|
let extension_start = align_up(layout.private_section()?.offset, extension_alignment)?;
|
||||||
|
let end = extension_start
|
||||||
|
.checked_add(cursor)
|
||||||
|
.ok_or_else(|| Error::Invalid("dynamic-table mapping end overflow".to_owned()))?;
|
||||||
|
usize_from_u64(end, "dynamic-table mapping length")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn table_placements(
|
||||||
|
tables: &[TablePayload],
|
||||||
|
start: u64,
|
||||||
|
) -> Result<(BTreeMap<String, PlacementReport>, u64)> {
|
||||||
|
let mut cursor = start;
|
||||||
|
let mut placements = BTreeMap::new();
|
||||||
|
for table in tables {
|
||||||
|
cursor = align_up(cursor, table.alignment)?;
|
||||||
|
placements.insert(
|
||||||
|
table.name.to_owned(),
|
||||||
|
PlacementReport {
|
||||||
|
offset: cursor,
|
||||||
|
size: table.data.len(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
cursor = cursor
|
||||||
|
.checked_add(table.data.len() as u64)
|
||||||
|
.ok_or_else(|| Error::Invalid("rebuilt ELF metadata end overflow".to_owned()))?;
|
||||||
|
}
|
||||||
|
Ok((placements, cursor))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn table_end(tables: &[TablePayload], start: u64) -> Result<u64> {
|
||||||
|
table_placements(tables, start).map(|(_, end)| end)
|
||||||
|
}
|
||||||
|
|
||||||
fn materialize_static_elf_tables(
|
fn materialize_static_elf_tables(
|
||||||
output: &mut [u8],
|
output: &mut [u8],
|
||||||
source: &[u8],
|
source: &[u8],
|
||||||
layout: &ElfLayout,
|
layout: &ElfLayout,
|
||||||
symbol_patch_data: &[u8],
|
symbol_patch_data: &[u8],
|
||||||
auxiliary_data: &[u8],
|
auxiliary_data: &[u8],
|
||||||
) -> Result<(ElfLayout, ElfMaterializationReport)> {
|
) -> Result<(ElfLayout, ElfMaterializationReport, u64)> {
|
||||||
let names = layout.section_names(source)?;
|
let names = layout.section_names(source)?;
|
||||||
let indices = required_section_indices(&names)?;
|
let indices = required_section_indices(&names)?;
|
||||||
let section = |name: &'static str| -> SectionHeader { layout.section_headers[indices[name]] };
|
let section = |name: &'static str| -> SectionHeader { layout.section_headers[indices[name]] };
|
||||||
@@ -802,7 +998,6 @@ fn materialize_static_elf_tables(
|
|||||||
let rela_dyn = section(".rela.dyn");
|
let rela_dyn = section(".rela.dyn");
|
||||||
let rela_plt = section(".rela.plt");
|
let rela_plt = section(".rela.plt");
|
||||||
let dynamic = section(".dynamic");
|
let dynamic = section(".dynamic");
|
||||||
let rodata = section(".rodata");
|
|
||||||
|
|
||||||
let (old_symbols, old_strings, hidden_symbols) =
|
let (old_symbols, old_strings, hidden_symbols) =
|
||||||
restore_hidden_symbols(output, dynsym, dynstr, symbol_patch_data)?;
|
restore_hidden_symbols(output, dynsym, dynstr, symbol_patch_data)?;
|
||||||
@@ -819,7 +1014,11 @@ fn materialize_static_elf_tables(
|
|||||||
auxiliary.dynstr_offset as usize,
|
auxiliary.dynstr_offset as usize,
|
||||||
auxiliary.dynstr_size as usize,
|
auxiliary.dynstr_size as usize,
|
||||||
)?;
|
)?;
|
||||||
let appended_count = auxiliary.dynsym_count as usize - 1;
|
let appended_count =
|
||||||
|
usize::try_from(auxiliary.dynsym_count.checked_sub(1).ok_or_else(|| {
|
||||||
|
Error::Invalid("auxiliary symbol table has no null entry".to_owned())
|
||||||
|
})?)
|
||||||
|
.map_err(|_| Error::Invalid("auxiliary symbol count exceeds usize".to_owned()))?;
|
||||||
let mut appended_symbols = Vec::with_capacity(appended_count * ELF64_SYMBOL_SIZE);
|
let mut appended_symbols = Vec::with_capacity(appended_count * ELF64_SYMBOL_SIZE);
|
||||||
for index in 1..auxiliary.dynsym_count as usize {
|
for index in 1..auxiliary.dynsym_count as usize {
|
||||||
let offset = auxiliary.dynsym_offset as usize + index * ELF64_SYMBOL_SIZE;
|
let offset = auxiliary.dynsym_offset as usize + index * ELF64_SYMBOL_SIZE;
|
||||||
@@ -926,11 +1125,6 @@ fn materialize_static_elf_tables(
|
|||||||
let rela_dyn_count = merged_rela_dyn.len() / ELF64_RELA_SIZE;
|
let rela_dyn_count = merged_rela_dyn.len() / ELF64_RELA_SIZE;
|
||||||
let rela_plt_count = merged_rela_plt.len() / ELF64_RELA_SIZE;
|
let rela_plt_count = merged_rela_plt.len() / ELF64_RELA_SIZE;
|
||||||
|
|
||||||
struct TablePayload {
|
|
||||||
name: &'static str,
|
|
||||||
alignment: u64,
|
|
||||||
data: Vec<u8>,
|
|
||||||
}
|
|
||||||
let mut tables = vec![
|
let mut tables = vec![
|
||||||
TablePayload {
|
TablePayload {
|
||||||
name: ".dynsym",
|
name: ".dynsym",
|
||||||
@@ -977,34 +1171,52 @@ fn materialize_static_elf_tables(
|
|||||||
data: merged_rela_plt,
|
data: merged_rela_plt,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
let metadata_start = dynsym.offset;
|
let mut placement_layout = layout.clone();
|
||||||
let mut cursor = metadata_start;
|
let mut metadata_start = dynsym.offset;
|
||||||
let mut placements = BTreeMap::new();
|
let (mut placements, mut cursor) = table_placements(&tables, metadata_start)?;
|
||||||
for table in &tables {
|
let mut capacity_end = metadata_capacity_end(layout, &indices, metadata_start)?;
|
||||||
cursor = align_up(cursor, table.alignment)?;
|
if cursor > capacity_end {
|
||||||
placements.insert(
|
let alignment = layout.load_alignment()?;
|
||||||
table.name.to_owned(),
|
let extension_start = align_up(layout.private_section()?.offset, alignment)?;
|
||||||
PlacementReport {
|
let extension_end = table_end(&tables, extension_start)?;
|
||||||
offset: cursor,
|
let extension_size = extension_end
|
||||||
size: table.data.len(),
|
.checked_sub(extension_start)
|
||||||
|
.ok_or_else(|| Error::Invalid("dynamic-table extension underflow".to_owned()))?;
|
||||||
|
let extension_address = align_up(layout.load_end()?, alignment)?;
|
||||||
|
placement_layout = layout.append_load_segment(
|
||||||
|
output,
|
||||||
|
LoadSegment {
|
||||||
|
offset: extension_start,
|
||||||
|
virtual_address: extension_address,
|
||||||
|
file_size: extension_size,
|
||||||
|
memory_size: extension_size,
|
||||||
|
flags: PF_R,
|
||||||
|
alignment,
|
||||||
},
|
},
|
||||||
);
|
)?;
|
||||||
cursor = cursor
|
metadata_start = extension_start;
|
||||||
.checked_add(table.data.len() as u64)
|
(placements, cursor) = table_placements(&tables, metadata_start)?;
|
||||||
.ok_or_else(|| Error::Invalid("rebuilt ELF metadata end overflow".to_owned()))?;
|
capacity_end = cursor;
|
||||||
}
|
}
|
||||||
if cursor > rodata.offset {
|
let zero_start = usize_from_u64(dynsym.offset, "metadata start")?;
|
||||||
return invalid(format!(
|
let zero_end = usize_from_u64(
|
||||||
"rebuilt ELF tables end at 0x{cursor:x}, beyond .rodata 0x{:x}",
|
metadata_capacity_end(layout, &indices, dynsym.offset)?,
|
||||||
rodata.offset
|
"metadata capacity end",
|
||||||
));
|
)?;
|
||||||
}
|
|
||||||
let zero_start = usize_from_u64(metadata_start, "metadata start")?;
|
|
||||||
let zero_end = usize_from_u64(rodata.offset, ".rodata offset")?;
|
|
||||||
output
|
output
|
||||||
.get_mut(zero_start..zero_end)
|
.get_mut(zero_start..zero_end)
|
||||||
.ok_or_else(|| Error::Invalid("metadata capacity exceeds output mapping".to_owned()))?
|
.ok_or_else(|| Error::Invalid("metadata capacity exceeds output mapping".to_owned()))?
|
||||||
.fill(0);
|
.fill(0);
|
||||||
|
if metadata_start != dynsym.offset {
|
||||||
|
let extension_start = usize_from_u64(metadata_start, "dynamic-table extension start")?;
|
||||||
|
let extension_end = usize_from_u64(cursor, "dynamic-table extension end")?;
|
||||||
|
output
|
||||||
|
.get_mut(extension_start..extension_end)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::Invalid("dynamic-table extension exceeds output mapping".to_owned())
|
||||||
|
})?
|
||||||
|
.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
let mut updated_sections = layout.section_headers.clone();
|
let mut updated_sections = layout.section_headers.clone();
|
||||||
for table in &tables {
|
for table in &tables {
|
||||||
@@ -1021,8 +1233,8 @@ fn materialize_static_elf_tables(
|
|||||||
.copy_from_slice(&table.data);
|
.copy_from_slice(&table.data);
|
||||||
let index = indices[table.name];
|
let index = indices[table.name];
|
||||||
let mut updated = updated_sections[index];
|
let mut updated = updated_sections[index];
|
||||||
updated.address =
|
updated.address = placement_layout
|
||||||
layout.file_offset_to_virtual_address(placement.offset, table.data.len() as u64)?;
|
.file_offset_to_virtual_address(placement.offset, table.data.len() as u64)?;
|
||||||
updated.offset = placement.offset;
|
updated.offset = placement.offset;
|
||||||
updated.size = table.data.len() as u64;
|
updated.size = table.data.len() as u64;
|
||||||
updated_sections[index] = updated;
|
updated_sections[index] = updated;
|
||||||
@@ -1039,16 +1251,23 @@ fn materialize_static_elf_tables(
|
|||||||
(DT_JMPREL, section_address(".rela.plt")),
|
(DT_JMPREL, section_address(".rela.plt")),
|
||||||
(DT_GNU_HASH, section_address(".gnu.hash")),
|
(DT_GNU_HASH, section_address(".gnu.hash")),
|
||||||
(DT_VERSYM, section_address(".gnu.version")),
|
(DT_VERSYM, section_address(".gnu.version")),
|
||||||
(DT_RELACOUNT, relative_count as u64),
|
|
||||||
(DT_VERNEED, section_address(".gnu.version_r")),
|
(DT_VERNEED, section_address(".gnu.version_r")),
|
||||||
]);
|
]);
|
||||||
|
if dynamic_contains_tag(output, dynamic, DT_RELACOUNT)? {
|
||||||
|
dynamic_values.insert(DT_RELACOUNT, relative_count as u64);
|
||||||
|
}
|
||||||
if indices.contains_key(".hash") {
|
if indices.contains_key(".hash") {
|
||||||
dynamic_values.insert(DT_HASH, section_address(".hash"));
|
dynamic_values.insert(DT_HASH, section_address(".hash"));
|
||||||
}
|
}
|
||||||
patch_dynamic_tags(output, dynamic, &dynamic_values)?;
|
patch_dynamic_tags(output, dynamic, &dynamic_values)?;
|
||||||
|
|
||||||
let mut restored_layout = layout.clone();
|
let mut restored_layout = placement_layout;
|
||||||
restored_layout.section_headers = updated_sections;
|
restored_layout.section_headers = updated_sections;
|
||||||
|
let data_end = if metadata_start == dynsym.offset {
|
||||||
|
layout.private_section()?.offset
|
||||||
|
} else {
|
||||||
|
cursor
|
||||||
|
};
|
||||||
Ok((
|
Ok((
|
||||||
restored_layout,
|
restored_layout,
|
||||||
ElfMaterializationReport {
|
ElfMaterializationReport {
|
||||||
@@ -1065,10 +1284,11 @@ fn materialize_static_elf_tables(
|
|||||||
relative_prefix_count: relative_count,
|
relative_prefix_count: relative_count,
|
||||||
metadata_start,
|
metadata_start,
|
||||||
metadata_end: cursor,
|
metadata_end: cursor,
|
||||||
metadata_capacity_end: rodata.offset,
|
metadata_capacity_end: capacity_end,
|
||||||
metadata_slack: rodata.offset - cursor,
|
metadata_slack: capacity_end.saturating_sub(cursor),
|
||||||
placements,
|
placements,
|
||||||
},
|
},
|
||||||
|
data_end,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1090,9 +1310,13 @@ fn finalize_clean_elf(
|
|||||||
temporary_path: &Path,
|
temporary_path: &Path,
|
||||||
source: &[u8],
|
source: &[u8],
|
||||||
layout: &ElfLayout,
|
layout: &ElfLayout,
|
||||||
|
data_start: u64,
|
||||||
preserve_entrypoint: bool,
|
preserve_entrypoint: bool,
|
||||||
) -> Result<CleaningReport> {
|
) -> Result<CleaningReport> {
|
||||||
let private = layout.private_section()?;
|
let private = layout.private_section()?;
|
||||||
|
if data_start < private.offset {
|
||||||
|
return invalid("ELF data start precedes the private section");
|
||||||
|
}
|
||||||
let names = layout.section_names(source)?;
|
let names = layout.section_names(source)?;
|
||||||
if layout.private_section_index + 1 != layout.section_headers.len() {
|
if layout.private_section_index + 1 != layout.section_headers.len() {
|
||||||
return invalid("SHT_LOUSER section is not the final section");
|
return invalid("SHT_LOUSER section is not the final section");
|
||||||
@@ -1100,7 +1324,7 @@ fn finalize_clean_elf(
|
|||||||
let retained = &layout.section_headers[..layout.private_section_index];
|
let retained = &layout.section_headers[..layout.private_section_index];
|
||||||
let mut updated = Vec::with_capacity(retained.len());
|
let mut updated = Vec::with_capacity(retained.len());
|
||||||
stream
|
stream
|
||||||
.seek(SeekFrom::Start(private.offset))
|
.seek(SeekFrom::Start(data_start))
|
||||||
.map_err(|error| Error::io("seek temporary output", temporary_path, error))?;
|
.map_err(|error| Error::io("seek temporary output", temporary_path, error))?;
|
||||||
for §ion in retained {
|
for §ion in retained {
|
||||||
if section.section_type == SHT_NOBITS || section.flags & SHF_ALLOC != 0 || section.size == 0
|
if section.section_type == SHT_NOBITS || section.flags & SHF_ALLOC != 0 || section.size == 0
|
||||||
@@ -1363,9 +1587,9 @@ pub fn restore_libil2cpp(options: &RestoreOptions) -> Result<RestoreReport> {
|
|||||||
.map_err(|error| Error::io("size temporary output", &temporary_path, error))?;
|
.map_err(|error| Error::io("size temporary output", &temporary_path, error))?;
|
||||||
|
|
||||||
let mut restored_layout = layout.clone();
|
let mut restored_layout = layout.clone();
|
||||||
let mut auxiliary_data = None;
|
|
||||||
let mut auxiliary_stats = None;
|
let mut auxiliary_stats = None;
|
||||||
let mut materialization = None;
|
let mut materialization = None;
|
||||||
|
let mut temporary_end = private.offset;
|
||||||
let primary_stats;
|
let primary_stats;
|
||||||
{
|
{
|
||||||
let mut output = map_mut(temporary.as_file(), private_size, &temporary_path)?;
|
let mut output = map_mut(temporary.as_file(), private_size, &temporary_path)?;
|
||||||
@@ -1384,35 +1608,50 @@ pub fn restore_libil2cpp(options: &RestoreOptions) -> Result<RestoreReport> {
|
|||||||
options.verbose,
|
options.verbose,
|
||||||
|address, data| writer.write(address, data),
|
|address, data| writer.write(address, data),
|
||||||
)?;
|
)?;
|
||||||
if !options.outer_only {
|
output
|
||||||
if options.verbose {
|
.flush()
|
||||||
eprintln!("Decoding auxiliary 0x9D ELF materialization container...");
|
.map_err(|error| Error::io("flush restored image", &temporary_path, error))?;
|
||||||
}
|
}
|
||||||
let mut decoded = vec![0_u8; auxiliary_header.output_size as usize];
|
|
||||||
let stats = decode_container(
|
if !options.outer_only {
|
||||||
&payload,
|
if options.verbose {
|
||||||
&auxiliary_header,
|
eprintln!("Decoding auxiliary 0x9D ELF materialization container...");
|
||||||
&config,
|
}
|
||||||
options.verbose,
|
let mut decoded = vec![0_u8; auxiliary_header.output_size as usize];
|
||||||
|offset, data| {
|
let stats = decode_container(
|
||||||
let start = usize_from_u64(offset, "auxiliary write offset")?;
|
&payload,
|
||||||
let end = start.checked_add(data.len()).ok_or_else(|| {
|
&auxiliary_header,
|
||||||
Error::Invalid("auxiliary decoded write overflow".to_owned())
|
&config,
|
||||||
})?;
|
options.verbose,
|
||||||
let destination = decoded.get_mut(start..end).ok_or_else(|| {
|
|offset, data| {
|
||||||
Error::Invalid("auxiliary decoded write is out of range".to_owned())
|
let start = usize_from_u64(offset, "auxiliary write offset")?;
|
||||||
})?;
|
let end = start
|
||||||
destination.copy_from_slice(data);
|
.checked_add(data.len())
|
||||||
Ok(data.len())
|
.ok_or_else(|| Error::Invalid("auxiliary decoded write overflow".to_owned()))?;
|
||||||
},
|
let destination = decoded.get_mut(start..end).ok_or_else(|| {
|
||||||
)?;
|
Error::Invalid("auxiliary decoded write is out of range".to_owned())
|
||||||
if let Some(path) = &options.dump_auxiliary {
|
})?;
|
||||||
write_atomic(&absolute(path)?, &decoded)?;
|
destination.copy_from_slice(data);
|
||||||
}
|
Ok(data.len())
|
||||||
if options.verbose {
|
},
|
||||||
eprintln!("Rebuilding static ELF dynamic-linker tables...");
|
)?;
|
||||||
}
|
if let Some(path) = &options.dump_auxiliary {
|
||||||
let (new_layout, report) = materialize_static_elf_tables(
|
write_atomic(&absolute(path)?, &decoded)?;
|
||||||
|
}
|
||||||
|
let mapping_length = metadata_mapping_length(&source, &layout, &decoded)?;
|
||||||
|
if mapping_length < private_size {
|
||||||
|
return invalid("dynamic-table mapping is shorter than the ELF image");
|
||||||
|
}
|
||||||
|
temporary
|
||||||
|
.as_file()
|
||||||
|
.set_len(mapping_length as u64)
|
||||||
|
.map_err(|error| Error::io("extend temporary output", &temporary_path, error))?;
|
||||||
|
if options.verbose {
|
||||||
|
eprintln!("Rebuilding static ELF dynamic-linker tables...");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut output = map_mut(temporary.as_file(), mapping_length, &temporary_path)?;
|
||||||
|
let (new_layout, report, data_end) = materialize_static_elf_tables(
|
||||||
&mut output,
|
&mut output,
|
||||||
&source,
|
&source,
|
||||||
&layout,
|
&layout,
|
||||||
@@ -1422,19 +1661,23 @@ pub fn restore_libil2cpp(options: &RestoreOptions) -> Result<RestoreReport> {
|
|||||||
restored_layout = new_layout;
|
restored_layout = new_layout;
|
||||||
materialization = Some(report);
|
materialization = Some(report);
|
||||||
auxiliary_stats = Some(stats);
|
auxiliary_stats = Some(stats);
|
||||||
auxiliary_data = Some(decoded);
|
temporary_end = data_end;
|
||||||
|
output
|
||||||
|
.flush()
|
||||||
|
.map_err(|error| Error::io("flush restored image", &temporary_path, error))?;
|
||||||
}
|
}
|
||||||
output
|
temporary
|
||||||
.flush()
|
.as_file()
|
||||||
.map_err(|error| Error::io("flush restored image", &temporary_path, error))?;
|
.set_len(temporary_end)
|
||||||
|
.map_err(|error| Error::io("trim temporary output", &temporary_path, error))?;
|
||||||
}
|
}
|
||||||
drop(auxiliary_data);
|
|
||||||
|
|
||||||
let cleaning = finalize_clean_elf(
|
let cleaning = finalize_clean_elf(
|
||||||
temporary.as_file_mut(),
|
temporary.as_file_mut(),
|
||||||
&temporary_path,
|
&temporary_path,
|
||||||
&source,
|
&source,
|
||||||
&restored_layout,
|
&restored_layout,
|
||||||
|
temporary_end,
|
||||||
options.preserve_entrypoint,
|
options.preserve_entrypoint,
|
||||||
)?;
|
)?;
|
||||||
let validation = {
|
let validation = {
|
||||||
@@ -1489,3 +1732,46 @@ fn hex_digest(data: &[u8]) -> String {
|
|||||||
}
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn auxiliary_image(symbol_count: u32) -> Vec<u8> {
|
||||||
|
let mut data = vec![0_u8; 0x279];
|
||||||
|
let words = [
|
||||||
|
0x40_u32,
|
||||||
|
0,
|
||||||
|
0x40,
|
||||||
|
0,
|
||||||
|
0x278,
|
||||||
|
1,
|
||||||
|
0x260,
|
||||||
|
symbol_count,
|
||||||
|
0x40,
|
||||||
|
3,
|
||||||
|
0x90,
|
||||||
|
19,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0xb7,
|
||||||
|
0,
|
||||||
|
];
|
||||||
|
for (index, word) in words.into_iter().enumerate() {
|
||||||
|
data[index * 4..index * 4 + 4].copy_from_slice(&word.to_le_bytes());
|
||||||
|
}
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auxiliary_accepts_null_only_dynamic_symbol_table() {
|
||||||
|
let parsed = AuxiliaryElfImage::parse(&auxiliary_image(1)).expect("null-only dynsym");
|
||||||
|
assert_eq!(parsed.dynsym_count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auxiliary_rejects_missing_null_dynamic_symbol() {
|
||||||
|
let error = AuxiliaryElfImage::parse(&auxiliary_image(0)).expect_err("missing null symbol");
|
||||||
|
assert!(error.to_string().contains("no null entry"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user