from_singleton/
find.rs

1//! `FD4DerivedSingleton` and `FD4Singleton` search routines.
2
3use 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/// A complete `FD4Singleton` mapping with names.
15///
16/// Implements [`Deref`] to the underlying [`HashMap`].
17#[derive(Clone, Debug)]
18pub struct FD4SingletonMap(FxHashMap<String, NonNull<*mut u8>>);
19
20/// "Unfinished" `FD4Singleton` mapping without names.
21///
22/// Dantelion2 reflection data is needed to finish mapping reflection
23/// primitives to the instance names, which must be initialized.
24///
25/// For that reason, [`FD4SingletonPartialResult::finish`] is unsafe.
26#[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
32/// Build a map of all `FD4DerivedSingleton` names and addresses.
33///
34/// # Panics
35/// If `pe` does not contain valid ".text" and ".data" sections.
36pub 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                // SAFETY: using valid C strings wrapped in `NonNull`.
92                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
107/// Build a partial result of a map of all `FD4Singleton` names and addresses.
108///
109/// Finishing the map is unsafe, as it requires the Dantelion2 reflection data
110/// to have been initialized during the startup of the process.
111///
112/// # Panics
113/// If `pe` does not contain valid ".text" and ".data" sections.
114pub 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    /// Check whether all `FD4Singleton` instances are uninitialized.
200    ///
201    /// It is a good sign [`FD4SingletonPartialResult::finish`] is not safe to be called.
202    pub fn all_null(&self) -> bool {
203        self.0.iter().all(|(_, p)| unsafe { p.read().is_null() })
204    }
205}
206
207impl FD4SingletonPartialResult {
208    /// Finish mapping `FD4Singleton` instances by retrieving their names.
209    ///
210    /// # Safety
211    /// The process must have finished initializing Dantelion2 reflection data.
212    pub unsafe fn finish(self) -> FD4SingletonMap {
213        // SAFETY: preconditions should be met for `self.get_name`,
214        // returned names are wrapped in `NonNull` and are valid C strings.
215        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    /// Check whether all `FD4Singleton` instances are uninitialized.
231    ///
232    /// It is a good sign [`Self::finish`] is not safe to be called.
233    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 {}