1use std::{
4 ffi::{c_char, CStr},
5 mem,
6 ops::{Deref, DerefMut},
7 ptr::NonNull,
8};
9
10use fxhash::FxHashMap;
11use pelite::pe::Pe;
12use smallvec::{smallvec, SmallVec};
13
14#[derive(Clone, Debug)]
18pub struct FD4SingletonMap(FxHashMap<String, NonNull<*mut u8>>);
19
20#[derive(Clone, Debug)]
27pub struct FD4SingletonPartialResult {
28 map: FxHashMap<*mut u8, NonNull<*mut u8>>,
29 get_name: Option<unsafe extern "C" fn(*const u8) -> *const c_char>,
30}
31
32pub fn derived_singletons<'a, T: Pe<'a>>(pe: T) -> FD4SingletonMap {
37 let sections = pe.section_headers();
38
39 let text = sections
40 .by_name(".text")
41 .and_then(|s| pe.get_section_bytes(s).ok())
42 .expect(".text section missing or malformed");
43
44 let data_range = sections
45 .by_name(".data")
46 .map(|s| s.virtual_range())
47 .expect(".data section missing or malformed");
48
49 let image_base = pe.image().as_ptr();
50
51 let mut vk_map = Vec::<(u32, u32)>::new();
52
53 for [addr_disp32, name_disp32] in derived_singleton_pat_iter(text) {
54 let addr_bytes = <[u8; 4]>::try_from(addr_disp32).unwrap();
55
56 let addr = unsafe {
57 u32::wrapping_add(
58 addr_disp32.as_ptr_range().end.offset_from(image_base) as u32,
59 u32::from_le_bytes(addr_bytes),
60 )
61 };
62
63 let Err(index) = vk_map.binary_search_by_key(&addr, |(v, _)| *v) else {
64 continue;
65 };
66
67 if !data_range.contains(&addr) {
68 continue;
69 }
70
71 let name_bytes = <[u8; 4]>::try_from(name_disp32).unwrap();
72
73 let name = unsafe {
74 u32::wrapping_add(
75 name_disp32.as_ptr_range().end.offset_from(image_base) as u32,
76 u32::from_le_bytes(name_bytes),
77 )
78 };
79
80 vk_map.insert(index, (addr, name));
81 }
82
83 let map = vk_map
84 .into_iter()
85 .filter_map(|(v, k)| {
86 let addr = NonNull::new(image_base.wrapping_add(v as _) as *mut *mut u8)?;
87
88 if addr.is_aligned() {
89 let name = image_base.wrapping_add(k as _);
90
91 NonNull::new(name as _).map(|a| unsafe {
93 (
94 CStr::from_ptr(a.as_ptr()).to_string_lossy().to_string(),
95 addr,
96 )
97 })
98 } else {
99 None
100 }
101 })
102 .collect();
103
104 FD4SingletonMap(map)
105}
106
107pub fn fd4_singletons<'a, T: Pe<'a>>(pe: T) -> FD4SingletonPartialResult {
115 let sections = pe.section_headers();
116
117 let text = sections
118 .by_name(".text")
119 .and_then(|s| pe.get_section_bytes(s).ok())
120 .expect(".text section missing or malformed");
121
122 let data_range = sections
123 .by_name(".data")
124 .map(|s| s.virtual_range())
125 .expect(".data section missing or malformed");
126
127 let image_base = pe.image().as_ptr();
128
129 let mut vk_map = Vec::<(u32, u32)>::new();
130
131 let mut get_name: Option<unsafe extern "C" fn(*const u8) -> *const c_char> = None;
132
133 for [addr_disp32, reflection_disp32, fn_disp32] in fd4_singleton_pat_iter(text) {
134 let addr_bytes = <[u8; 4]>::try_from(addr_disp32).unwrap();
135
136 let addr = unsafe {
137 u32::wrapping_add(
138 addr_disp32.as_ptr_range().end.offset_from(image_base) as u32,
139 u32::from_le_bytes(addr_bytes),
140 )
141 };
142
143 let Err(index) = vk_map.binary_search_by_key(&addr, |(v, _)| *v) else {
144 continue;
145 };
146
147 if !data_range.contains(&addr) {
148 continue;
149 }
150
151 if get_name.is_none() {
152 let fn_bytes = <[u8; 4]>::try_from(fn_disp32).unwrap();
153
154 let fn_ptr = fn_disp32
155 .as_ptr_range()
156 .end
157 .wrapping_byte_add(u32::from_le_bytes(fn_bytes) as _);
158
159 if !text.as_ptr_range().contains(&fn_ptr) {
160 continue;
161 }
162
163 get_name = Some(unsafe { mem::transmute(fn_ptr) });
164 }
165
166 let reflection_bytes = <[u8; 4]>::try_from(reflection_disp32).unwrap();
167
168 let reflection = unsafe {
169 u32::wrapping_add(
170 reflection_disp32.as_ptr_range().end.offset_from(image_base) as _,
171 u32::from_le_bytes(reflection_bytes),
172 )
173 };
174
175 vk_map.insert(index, (addr, reflection));
176 }
177
178 let image_base = pe.image().as_ptr() as usize;
179
180 let map = vk_map
181 .into_iter()
182 .filter_map(|(v, k)| {
183 let addr = NonNull::new(image_base.wrapping_add(v as _) as *mut *mut u8)?;
184
185 if addr.is_aligned() {
186 let reflection = usize::wrapping_add(image_base, k as _);
187
188 Some((reflection as _, addr))
189 } else {
190 None
191 }
192 })
193 .collect();
194
195 FD4SingletonPartialResult { map, get_name }
196}
197
198impl FD4SingletonMap {
199 pub fn all_null(&self) -> bool {
203 self.0.iter().all(|(_, p)| unsafe { p.read().is_null() })
204 }
205}
206
207impl FD4SingletonPartialResult {
208 pub unsafe fn finish(self) -> FD4SingletonMap {
213 FD4SingletonMap(
216 self.map
217 .into_iter()
218 .filter_map(|(r, p)| unsafe {
219 let name = NonNull::new((self.get_name?)(r) as _)?;
220
221 Some((
222 CStr::from_ptr(name.as_ptr()).to_string_lossy().to_string(),
223 p,
224 ))
225 })
226 .collect(),
227 )
228 }
229
230 pub fn all_null(&self) -> bool {
234 self.map.iter().all(|(_, p)| unsafe { p.read().is_null() })
235 }
236}
237
238impl Deref for FD4SingletonMap {
239 type Target = FxHashMap<String, NonNull<*mut u8>>;
240
241 fn deref(&self) -> &Self::Target {
242 &self.0
243 }
244}
245
246impl DerefMut for FD4SingletonMap {
247 fn deref_mut(&mut self) -> &mut Self::Target {
248 &mut self.0
249 }
250}
251
252pub fn derived_singleton_pat_iter<'h>(
253 text: &'h [u8],
254) -> impl Iterator<Item = [&'h [u8]; 2]> + use<'h> {
255 candidates_iter(text).filter_map(|candidate| {
256 let Candidate::Derived(pos, cap2) = candidate else {
257 return None;
258 };
259
260 let cap2 = text.get(cap2 as usize..cap2 as usize + 4)?;
261
262 let pos = cond_jump(text, pos)?;
263
264 let pos = pos.checked_sub(3)?;
265 let test = text.get(pos as usize..pos as usize + 3)?;
266
267 let test_rex = test[0];
268 let test_modrm = test[2];
269
270 let test_rexb = test_rex & 1;
271
272 let test_mod = test_modrm & 0b11000000;
273
274 let test_reg1 = test_modrm & 0b111;
275 let test_reg2 = (test_modrm >> 3) & 0b111;
276
277 if test_rex & REX_MASK != REX_W || test_mod != 0xc0 || test_reg1 != test_reg2 {
278 return None;
279 }
280
281 for pad in 0..=3 {
282 let pos = pos.checked_sub(7 + pad)?;
283
284 let mov = text.get(pos as usize..pos as usize + 7)?;
285 let cap1 = &mov[3..];
286
287 let mov_rex = mov[0];
288 let mov_modrm = mov[2];
289
290 let mov_rexw = (mov_rex >> 2) & 1;
291 let mov_mod = mov_modrm & 0b11000000;
292
293 let mov_mem = mov_modrm & 0b111;
294 let mov_reg = (mov_modrm >> 3) & 0b111;
295
296 if mov_rex & REX_MASK == REX_W
297 && mov_mod == 0
298 && mov_mem == 5
299 && mov_rexw == test_rexb
300 && mov_reg == test_reg1
301 {
302 return Some([cap1, cap2]);
303 }
304 }
305
306 None
307 })
308}
309
310pub fn fd4_singleton_pat_iter<'h>(text: &'h [u8]) -> impl Iterator<Item = [&'h [u8]; 3]> + use<'h> {
311 candidates_iter(text).filter_map(|candidate| {
312 let Candidate::Fd4(pos) = candidate else {
313 return None;
314 };
315
316 for pad in 0..=1 {
317 let pos = pos.checked_sub(5 + pad)?;
318 if text.get(pos as usize) != Some(&CALL) {
319 continue;
320 }
321 let cap3 = text.get(pos as usize + 1..pos as usize + 5)?;
322
323 let pos = pos.checked_sub(7)?;
324 if text.get(pos as usize..pos as usize + 3) != Some(LEA_RCX) {
325 continue;
326 }
327 let cap2 = text.get(pos as usize + 3..pos as usize + 7)?;
328
329 let Some(pos) = cond_jump(text, pos) else {
330 continue;
331 };
332
333 let pos = pos.checked_sub(3)?;
334 let test = text.get(pos as usize..pos as usize + 3)?;
335
336 let test_rex = test[0];
337 let test_modrm = test[2];
338
339 let test_rexb = test_rex & 1;
340
341 let test_mod = test_modrm & 0b11000000;
342
343 let test_reg1 = test_modrm & 0b111;
344 let test_reg2 = (test_modrm >> 3) & 0b111;
345
346 if test_rex & REX_MASK != REX_W || test_mod != 0xc0 || test_reg1 != test_reg2 {
347 continue;
348 }
349
350 for pad in 0..=3 {
351 let pos = pos.checked_sub(7 + pad)?;
352
353 let mov = text.get(pos as usize..pos as usize + 7)?;
354 let cap1 = &mov[3..];
355
356 let mov_rex = mov[0];
357 let mov_modrm = mov[2];
358
359 let mov_rexw = (mov_rex >> 2) & 1;
360 let mov_mod = mov_modrm & 0b11000000;
361
362 let mov_mem = mov_modrm & 0b111;
363 let mov_reg = (mov_modrm >> 3) & 0b111;
364
365 if mov_rex & REX_MASK == REX_W
366 && mov_mod == 0
367 && mov_mem == 5
368 && mov_rexw == test_rexb
369 && mov_reg == test_reg1
370 {
371 return Some([cap1, cap2, cap3]);
372 }
373 }
374 }
375
376 None
377 })
378}
379
380const REX_W: u8 = 0x48;
381const REX_WRXB: u8 = 0x4f;
382const REX_MASK: u8 = !(REX_WRXB ^ REX_W);
383
384fn cond_jump(text: &[u8], pos: u32) -> Option<u32> {
385 let pos_short = pos.checked_sub(2)?;
386
387 if text.get(pos_short as usize) == Some(&JNE_SHORT) {
388 return Some(pos_short);
389 }
390
391 let pos = pos.checked_sub(6)?;
392
393 (text.get(pos as usize..pos as usize + 2) == Some(JNE)).then_some(pos)
394}
395
396const JNE_SHORT: u8 = 0x75;
397const JNE: &[u8; 2] = &[0x0f, 0x85];
398
399pub enum Candidate {
400 Derived(u32, u32),
401 Fd4(u32),
402}
403
404pub fn candidates_iter<'h>(text: &'h [u8]) -> impl Iterator<Item = Candidate> + use<'h> {
405 assert!(text.len() <= u32::MAX as usize, "text is too long!");
406
407 memchr::memchr_iter(MOV_EDX, text).filter_map(|pos| {
408 let mut instructions: SmallVec<[Instruction; 4]> =
409 smallvec![Instruction::MovEdx(pos as u32)];
410
411 while instructions.len() < 4 {
412 let next_pos = instructions.last()?.next_pos();
413 let check_next = text.get(next_pos as usize..next_pos as usize + 3)?;
414
415 match check_next {
416 LEA_RCX => instructions.push(Instruction::LeaRcx(next_pos)),
417 LEA_R8 => instructions.push(Instruction::LeaR8(next_pos)),
418 LEA_R9 => instructions.push(Instruction::LeaR9(next_pos)),
419 MOV_R9 => instructions.push(Instruction::MovR9(next_pos)),
420 &[CALL, ..] => break,
421 _ => return None,
422 }
423 }
424
425 while instructions.len() < 4 {
426 let pos = instructions.first()?.pos();
427 let prev_pos = pos.checked_sub(7)?;
428
429 let check_prev = text.get(prev_pos as usize..pos as usize)?;
430
431 match &check_prev[..3] {
432 LEA_RCX => instructions.insert(0, Instruction::LeaRcx(prev_pos)),
433 LEA_R8 => instructions.insert(0, Instruction::LeaR8(prev_pos)),
434 LEA_R9 => instructions.insert(0, Instruction::LeaR9(prev_pos)),
435 _ if &check_prev[4..] == MOV_R9 => {
436 instructions.insert(0, Instruction::MovR9(prev_pos + 4))
437 }
438 _ => return None,
439 }
440 }
441
442 let instructions = instructions.into_inner().ok()?;
443
444 let mask = instructions
445 .iter()
446 .fold(0, |mask, instruction| match instruction {
447 Instruction::LeaRcx(_) => mask | 1,
448 Instruction::LeaR8(_) => mask | 2,
449 Instruction::LeaR9(_) => mask | 4,
450 Instruction::MovR9(_) => mask | 8,
451 _ => mask,
452 });
453
454 match mask {
455 7 => {
456 let capture = instructions
457 .iter()
458 .find_map(|instruction| match instruction {
459 Instruction::LeaR9(pos) => Some(pos + 3),
460 _ => None,
461 })?;
462
463 Some(Candidate::Derived(instructions[0].pos(), capture))
464 }
465 11 => Some(Candidate::Fd4(instructions[0].pos())),
466 _ => None,
467 }
468 })
469}
470
471const CALL: u8 = 0xe8;
472const MOV_EDX: u8 = 0xba;
473
474const LEA_RCX: &[u8] = &[0x48, 0x8d, 0x0d];
475const LEA_R8: &[u8] = &[0x4c, 0x8d, 0x05];
476const LEA_R9: &[u8] = &[0x4c, 0x8d, 0x0d];
477const MOV_R9: &[u8] = &[0x4c, 0x8b, 0xc8];
478
479#[derive(Clone, Copy)]
480enum Instruction {
481 LeaRcx(u32),
482 LeaR8(u32),
483 LeaR9(u32),
484 MovR9(u32),
485 MovEdx(u32),
486}
487
488impl Instruction {
489 fn pos(self) -> u32 {
490 match self {
491 Self::MovEdx(pos) => pos,
492 Self::LeaRcx(pos) => pos,
493 Self::LeaR8(pos) => pos,
494 Self::LeaR9(pos) => pos,
495 Self::MovR9(pos) => pos,
496 }
497 }
498
499 fn next_pos(self) -> u32 {
500 match self {
501 Self::MovEdx(pos) => pos + 5,
502 Self::LeaRcx(pos) => pos + 7,
503 Self::LeaR8(pos) => pos + 7,
504 Self::LeaR9(pos) => pos + 7,
505 Self::MovR9(pos) => pos + 3,
506 }
507 }
508}
509
510unsafe impl Send for FD4SingletonMap {}
511
512unsafe impl Sync for FD4SingletonMap {}
513
514unsafe impl Send for FD4SingletonPartialResult {}
515
516unsafe impl Sync for FD4SingletonPartialResult {}