wraith/navigation/
module_iter.rs

1//! Iterators over loaded modules
2
3use super::module::Module;
4use crate::error::{Result, WraithError};
5use crate::structures::ldr::{
6    IN_INITIALIZATION_ORDER_LINKS_OFFSET, IN_LOAD_ORDER_LINKS_OFFSET, IN_MEMORY_ORDER_LINKS_OFFSET,
7};
8use crate::structures::{LdrDataTableEntry, ListEntry, Peb};
9
10// re-export from error for consistency
11pub use crate::error::ModuleListType;
12
13// max iterations before assuming list is corrupted/circular
14const MAX_MODULES: usize = 4096;
15
16/// generic module iterator
17pub struct ModuleIterator<'a> {
18    head: *const ListEntry,
19    current: *const ListEntry,
20    offset: usize,
21    list_type: ModuleListType,
22    iterations: usize,
23    _peb: &'a Peb, // keep PEB borrowed
24}
25
26impl<'a> ModuleIterator<'a> {
27    /// create new iterator for specified list type
28    pub fn new(peb: &'a Peb, list_type: ModuleListType) -> Result<Self> {
29        let ldr = peb.ldr().ok_or(WraithError::NullPointer {
30            context: "PEB.Ldr",
31        })?;
32
33        let (head, offset) = match list_type {
34            ModuleListType::InLoadOrder => (
35                &ldr.in_load_order_module_list as *const ListEntry,
36                IN_LOAD_ORDER_LINKS_OFFSET,
37            ),
38            ModuleListType::InMemoryOrder => (
39                &ldr.in_memory_order_module_list as *const ListEntry,
40                IN_MEMORY_ORDER_LINKS_OFFSET,
41            ),
42            ModuleListType::InInitializationOrder => (
43                &ldr.in_initialization_order_module_list as *const ListEntry,
44                IN_INITIALIZATION_ORDER_LINKS_OFFSET,
45            ),
46        };
47
48        // start at first entry (Flink from head)
49        let current = unsafe { (*head).flink };
50
51        Ok(Self {
52            head,
53            current,
54            offset,
55            list_type,
56            iterations: 0,
57            _peb: peb,
58        })
59    }
60
61    /// get list type being iterated
62    pub fn list_type(&self) -> ModuleListType {
63        self.list_type
64    }
65}
66
67impl<'a> Iterator for ModuleIterator<'a> {
68    type Item = Module<'a>;
69
70    fn next(&mut self) -> Option<Self::Item> {
71        // stop when we wrap back to head
72        if core::ptr::eq(self.current, self.head) {
73            return None;
74        }
75
76        // protect against corrupted/circular lists
77        if self.iterations >= MAX_MODULES {
78            return None;
79        }
80        self.iterations += 1;
81
82        // validate current pointer is non-null
83        if self.current.is_null() {
84            return None;
85        }
86
87        // CONTAINING_RECORD: get LDR_DATA_TABLE_ENTRY from ListEntry
88        let entry_addr = (self.current as usize) - self.offset;
89        // SAFETY: entry is valid LDR_DATA_TABLE_ENTRY for loaded module
90        let entry = unsafe { &*(entry_addr as *const LdrDataTableEntry) };
91
92        // advance to next
93        self.current = unsafe { (*self.current).flink };
94
95        Some(Module::from_entry(entry))
96    }
97}
98
99/// iterator specifically for InLoadOrderModuleList
100pub struct InLoadOrderIter<'a>(ModuleIterator<'a>);
101
102impl<'a> InLoadOrderIter<'a> {
103    pub fn new(peb: &'a Peb) -> Result<Self> {
104        Ok(Self(ModuleIterator::new(peb, ModuleListType::InLoadOrder)?))
105    }
106}
107
108impl<'a> Iterator for InLoadOrderIter<'a> {
109    type Item = Module<'a>;
110
111    fn next(&mut self) -> Option<Self::Item> {
112        self.0.next()
113    }
114}
115
116/// iterator specifically for InMemoryOrderModuleList
117pub struct InMemoryOrderIter<'a>(ModuleIterator<'a>);
118
119impl<'a> InMemoryOrderIter<'a> {
120    pub fn new(peb: &'a Peb) -> Result<Self> {
121        Ok(Self(ModuleIterator::new(
122            peb,
123            ModuleListType::InMemoryOrder,
124        )?))
125    }
126}
127
128impl<'a> Iterator for InMemoryOrderIter<'a> {
129    type Item = Module<'a>;
130
131    fn next(&mut self) -> Option<Self::Item> {
132        self.0.next()
133    }
134}
135
136/// iterator specifically for InInitializationOrderModuleList
137pub struct InInitializationOrderIter<'a>(ModuleIterator<'a>);
138
139impl<'a> InInitializationOrderIter<'a> {
140    pub fn new(peb: &'a Peb) -> Result<Self> {
141        Ok(Self(ModuleIterator::new(
142            peb,
143            ModuleListType::InInitializationOrder,
144        )?))
145    }
146}
147
148impl<'a> Iterator for InInitializationOrderIter<'a> {
149    type Item = Module<'a>;
150
151    fn next(&mut self) -> Option<Self::Item> {
152        self.0.next()
153    }
154}
155
156/// count loaded modules
157pub fn module_count(peb: &Peb) -> Result<usize> {
158    Ok(ModuleIterator::new(peb, ModuleListType::InLoadOrder)?.count())
159}
160
161/// collect all modules into a Vec
162pub fn collect_modules(peb: &Peb) -> Result<Vec<ModuleInfo>> {
163    let iter = ModuleIterator::new(peb, ModuleListType::InLoadOrder)?;
164    Ok(iter
165        .map(|m| ModuleInfo {
166            name: m.name(),
167            path: m.full_path(),
168            base: m.base(),
169            size: m.size(),
170            entry_point: m.entry_point(),
171        })
172        .collect())
173}
174
175/// owned module information (doesn't borrow from PEB)
176#[derive(Debug, Clone)]
177pub struct ModuleInfo {
178    pub name: String,
179    pub path: String,
180    pub base: usize,
181    pub size: usize,
182    pub entry_point: usize,
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn test_module_count() {
191        let peb = Peb::current().expect("should get PEB");
192        let count = module_count(&peb).expect("should count modules");
193        assert!(count > 0, "should have at least one module");
194    }
195
196    #[test]
197    fn test_collect_modules() {
198        let peb = Peb::current().expect("should get PEB");
199        let modules = collect_modules(&peb).expect("should collect modules");
200        assert!(!modules.is_empty(), "should have modules");
201
202        // first module should be the executable
203        let first = &modules[0];
204        assert!(first.base > 0, "first module should have valid base");
205    }
206
207    #[test]
208    fn test_all_three_lists() {
209        let peb = Peb::current().expect("should get PEB");
210
211        let load_order: Vec<_> = ModuleIterator::new(&peb, ModuleListType::InLoadOrder)
212            .expect("load order iter")
213            .collect();
214        let memory_order: Vec<_> = ModuleIterator::new(&peb, ModuleListType::InMemoryOrder)
215            .expect("memory order iter")
216            .collect();
217        let init_order: Vec<_> = ModuleIterator::new(&peb, ModuleListType::InInitializationOrder)
218            .expect("init order iter")
219            .collect();
220
221        // all three lists should have same modules (maybe different order)
222        assert_eq!(
223            load_order.len(),
224            memory_order.len(),
225            "load and memory order should have same count"
226        );
227        // init order may have fewer (some DLLs don't have entry points)
228        assert!(
229            init_order.len() <= load_order.len(),
230            "init order should have <= load order count"
231        );
232    }
233}