wraith/navigation/
module_iter.rs

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