morpheus_persistent/arch/
x86_64.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//! x86_64-specific relocation handling
//!
//! PE32+ format with IMAGE_REL_BASED_DIR64 relocations.
//! Simple pointer fixups - just add/subtract delta from 64-bit values.

use crate::pe::reloc::{RelocationEngine, RelocationEntry, RelocationType};
use crate::pe::{PeArch, PeError, PeResult};

/// x86_64 relocation engine
///
/// Implements the `RelocationEngine` trait for x86_64 PE32+ binaries.
/// Uses simple 64-bit pointer fixups (no instruction encoding required).
pub struct X64RelocationEngine;

impl RelocationEngine for X64RelocationEngine {
    fn apply_relocation(
        &self,
        image_data: &mut [u8],
        entry: RelocationEntry,
        page_rva: u32,
        delta: i64,
    ) -> PeResult<()> {
        match entry.reloc_type() {
            RelocationType::Absolute => Ok(()), // Skip padding entries
            RelocationType::Dir64 => {
                let rva = page_rva as usize + entry.offset() as usize;
                if rva + 8 > image_data.len() {
                    return Err(PeError::InvalidOffset);
                }

                // Read current 64-bit value
                let current = u64::from_le_bytes([
                    image_data[rva],
                    image_data[rva + 1],
                    image_data[rva + 2],
                    image_data[rva + 3],
                    image_data[rva + 4],
                    image_data[rva + 5],
                    image_data[rva + 6],
                    image_data[rva + 7],
                ]);

                // Apply relocation: add delta
                let relocated = (current as i64 + delta) as u64;
                let bytes = relocated.to_le_bytes();
                image_data[rva..rva + 8].copy_from_slice(&bytes);

                Ok(())
            }
            _ => Err(PeError::UnsupportedFormat),
        }
    }

    fn unapply_relocation(
        &self,
        image_data: &mut [u8],
        entry: RelocationEntry,
        page_rva: u32,
        delta: i64,
    ) -> PeResult<()> {
        match entry.reloc_type() {
            RelocationType::Absolute => Ok(()), // Skip padding entries
            RelocationType::Dir64 => {
                let rva = page_rva as usize + entry.offset() as usize;
                if rva + 8 > image_data.len() {
                    return Err(PeError::InvalidOffset);
                }

                // Read current 64-bit value
                let current = u64::from_le_bytes([
                    image_data[rva],
                    image_data[rva + 1],
                    image_data[rva + 2],
                    image_data[rva + 3],
                    image_data[rva + 4],
                    image_data[rva + 5],
                    image_data[rva + 6],
                    image_data[rva + 7],
                ]);

                // Unapply relocation: subtract delta
                let original = (current as i64 - delta) as u64;
                let bytes = original.to_le_bytes();
                image_data[rva..rva + 8].copy_from_slice(&bytes);

                Ok(())
            }
            _ => Err(PeError::UnsupportedFormat),
        }
    }

    fn arch(&self) -> PeArch {
        PeArch::X64
    }
}

// Platform-specific notes:
//
// x86_64 UEFI uses PE32+ format (magic 0x20B)
// ImageBase is typically 0x400000 (linker default)
// UEFI loader picks actual load address (often different)
//
// Relocation delta = actual_load_address - original_ImageBase
//
// Example:
//   Original ImageBase: 0x0000000000400000
//   Actual load addr:   0x0000000076E4C000
//   Delta:              0x0000000076A4C000
//
// All DIR64 relocations get this delta added.
// To reverse: subtract the delta.