wraith/manipulation/inline_hook/arch/
x86.rs

1//! x86 (32-bit) architecture implementation
2
3#[cfg(all(not(feature = "std"), feature = "alloc"))]
4use alloc::vec::Vec;
5
6#[cfg(feature = "std")]
7use std::vec::Vec;
8
9use super::Architecture;
10use crate::manipulation::inline_hook::asm::{
11    iced_decoder::InstructionDecoder,
12    iced_relocator::InstructionRelocator,
13};
14
15/// x86 (32-bit) architecture
16pub struct X86;
17
18impl Architecture for X86 {
19    // E9 rel32 - 5 bytes
20    const JMP_REL_SIZE: usize = 5;
21
22    // push imm32; ret - 6 bytes
23    const JMP_ABS_SIZE: usize = 6;
24
25    const PTR_SIZE: usize = 4;
26    const CODE_ALIGNMENT: usize = 4;
27
28    // x86 can always use 5-byte jmp rel32 (full address space reachable)
29    const MIN_HOOK_SIZE: usize = 5;
30
31    fn encode_jmp_rel(source: usize, target: usize) -> Option<Vec<u8>> {
32        // on x86, all addresses are reachable with rel32
33        let offset = (target as i32).wrapping_sub((source as i32).wrapping_add(5));
34
35        let mut bytes = Vec::with_capacity(5);
36        bytes.push(0xE9);
37        bytes.extend_from_slice(&offset.to_le_bytes());
38        Some(bytes)
39    }
40
41    fn encode_jmp_abs(target: usize) -> Vec<u8> {
42        // push imm32; ret
43        let mut bytes = Vec::with_capacity(6);
44        bytes.push(0x68); // push imm32
45        bytes.extend_from_slice(&(target as u32).to_le_bytes());
46        bytes.push(0xC3); // ret
47        bytes
48    }
49
50    fn encode_call_rel(source: usize, target: usize) -> Option<Vec<u8>> {
51        let offset = (target as i32).wrapping_sub((source as i32).wrapping_add(5));
52
53        let mut bytes = Vec::with_capacity(5);
54        bytes.push(0xE8);
55        bytes.extend_from_slice(&offset.to_le_bytes());
56        Some(bytes)
57    }
58
59    fn encode_nop_sled(size: usize) -> Vec<u8> {
60        let mut bytes = Vec::with_capacity(size);
61        let mut remaining = size;
62
63        while remaining > 0 {
64            match remaining {
65                1 => {
66                    bytes.push(0x90);
67                    remaining -= 1;
68                }
69                2 => {
70                    bytes.extend_from_slice(&[0x66, 0x90]);
71                    remaining -= 2;
72                }
73                3 => {
74                    bytes.extend_from_slice(&[0x0F, 0x1F, 0x00]);
75                    remaining -= 3;
76                }
77                4 => {
78                    bytes.extend_from_slice(&[0x0F, 0x1F, 0x40, 0x00]);
79                    remaining -= 4;
80                }
81                5 => {
82                    bytes.extend_from_slice(&[0x0F, 0x1F, 0x44, 0x00, 0x00]);
83                    remaining -= 5;
84                }
85                6 => {
86                    bytes.extend_from_slice(&[0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00]);
87                    remaining -= 6;
88                }
89                _ => {
90                    bytes.extend_from_slice(&[0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00]);
91                    remaining -= 7;
92                }
93            }
94        }
95
96        bytes
97    }
98
99    fn find_instruction_boundary(code: &[u8], required_size: usize) -> Option<usize> {
100        // use iced-x86 for accurate instruction boundary detection
101        let decoder = InstructionDecoder::x86();
102        decoder.find_boundary(0, code, required_size)
103    }
104
105    fn relocate_instruction(
106        instruction: &[u8],
107        old_address: usize,
108        new_address: usize,
109    ) -> Option<Vec<u8>> {
110        if instruction.is_empty() {
111            return None;
112        }
113
114        // use iced-x86 for accurate instruction relocation
115        let relocator = InstructionRelocator::x86();
116        let result = relocator.relocate_instruction(
117            instruction,
118            old_address as u64,
119            new_address as u64,
120        );
121
122        if result.success {
123            Some(result.bytes)
124        } else {
125            None
126        }
127    }
128
129    fn needs_relocation(instruction: &[u8]) -> bool {
130        if instruction.is_empty() {
131            return false;
132        }
133
134        // use iced-x86 to check if instruction uses relative addressing
135        crate::manipulation::inline_hook::asm::iced_relocator::instruction_needs_relocation(
136            instruction,
137            0,
138        )
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_encode_jmp_rel() {
148        let bytes = X86::encode_jmp_rel(0x1000, 0x1100).unwrap();
149        assert_eq!(bytes.len(), 5);
150        assert_eq!(bytes[0], 0xE9);
151        let offset = i32::from_le_bytes(bytes[1..5].try_into().unwrap());
152        assert_eq!(offset, 0xFB);
153    }
154
155    #[test]
156    fn test_encode_jmp_abs() {
157        let bytes = X86::encode_jmp_abs(0xDEADBEEF);
158        assert_eq!(bytes.len(), 6);
159        assert_eq!(bytes[0], 0x68); // push
160        let addr = u32::from_le_bytes(bytes[1..5].try_into().unwrap());
161        assert_eq!(addr, 0xDEADBEEF);
162        assert_eq!(bytes[5], 0xC3); // ret
163    }
164
165    #[test]
166    fn test_find_instruction_boundary() {
167        // push ebp; mov ebp, esp; sub esp, 0x10
168        let code = [0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x10];
169        let boundary = X86::find_instruction_boundary(&code, 5).unwrap();
170        assert!(boundary >= 5);
171    }
172
173    #[test]
174    fn test_relocate_jmp_rel32() {
175        // jmp +0x100 from 0x1000 (target: 0x1105)
176        let jmp = [0xE9, 0x00, 0x01, 0x00, 0x00];
177
178        // relocate to 0x2000, target should still be 0x1105
179        let result = X86::relocate_instruction(&jmp, 0x1000, 0x2000).unwrap();
180        assert_eq!(result.len(), 5);
181        assert_eq!(result[0], 0xE9);
182    }
183
184    #[test]
185    fn test_needs_relocation() {
186        // JMP rel32 - needs relocation
187        assert!(X86::needs_relocation(&[0xE9, 0x00, 0x00, 0x00, 0x00]));
188
189        // CALL rel32 - needs relocation
190        assert!(X86::needs_relocation(&[0xE8, 0x00, 0x00, 0x00, 0x00]));
191
192        // PUSH EBP - doesn't need relocation
193        assert!(!X86::needs_relocation(&[0x55]));
194
195        // NOP - doesn't need relocation
196        assert!(!X86::needs_relocation(&[0x90]));
197    }
198}