wraith/navigation/
module_query.rs1use super::module::{Module, ModuleHandle};
4use super::module_iter::{ModuleIterator, ModuleListType};
5use crate::error::{Result, WraithError};
6use crate::structures::Peb;
7use crate::util::hash::djb2_hash_lowercase;
8
9pub struct ModuleQuery<'a> {
11 peb: &'a Peb,
12}
13
14impl<'a> ModuleQuery<'a> {
15 pub fn new(peb: &'a Peb) -> Self {
17 Self { peb }
18 }
19
20 pub fn find_by_name(&self, name: &str) -> Result<Module<'a>> {
24 let name_lower = name.to_lowercase();
25
26 for module in ModuleIterator::new(self.peb, ModuleListType::InLoadOrder)? {
27 if module.name_lowercase() == name_lower {
28 return Ok(module);
29 }
30 }
31
32 Err(WraithError::ModuleNotFound { name: name.into() })
33 }
34
35 pub fn find_by_hash(&self, hash: u32) -> Result<Module<'a>> {
39 for module in ModuleIterator::new(self.peb, ModuleListType::InLoadOrder)? {
40 let name = module.name();
41 let name_bytes = name.as_bytes();
42 if djb2_hash_lowercase(name_bytes) == hash {
43 return Ok(module);
44 }
45 }
46
47 Err(WraithError::ModuleNotFound {
48 name: format!("hash {hash:#x}"),
49 })
50 }
51
52 pub fn find_by_address(&self, address: usize) -> Result<Module<'a>> {
54 for module in ModuleIterator::new(self.peb, ModuleListType::InLoadOrder)? {
55 if module.contains(address) {
56 return Ok(module);
57 }
58 }
59
60 Err(WraithError::AddressNotInModule {
61 address: address as u64,
62 })
63 }
64
65 pub fn find_by_base(&self, base: usize) -> Result<Module<'a>> {
67 for module in ModuleIterator::new(self.peb, ModuleListType::InLoadOrder)? {
68 if module.base() == base {
69 return Ok(module);
70 }
71 }
72
73 Err(WraithError::AddressNotInModule {
74 address: base as u64,
75 })
76 }
77
78 pub fn find_by_path_contains(&self, substring: &str) -> Result<Module<'a>> {
80 let sub_lower = substring.to_lowercase();
81
82 for module in ModuleIterator::new(self.peb, ModuleListType::InLoadOrder)? {
83 if module.full_path().to_lowercase().contains(&sub_lower) {
84 return Ok(module);
85 }
86 }
87
88 Err(WraithError::ModuleNotFound {
89 name: substring.into(),
90 })
91 }
92
93 pub fn ntdll(&self) -> Result<Module<'a>> {
95 self.find_by_name("ntdll.dll")
96 }
97
98 pub fn kernel32(&self) -> Result<Module<'a>> {
100 self.find_by_name("kernel32.dll")
101 }
102
103 pub fn kernelbase(&self) -> Result<Module<'a>> {
105 self.find_by_name("kernelbase.dll")
106 }
107
108 pub fn current_module(&self) -> Result<Module<'a>> {
110 let image_base = self.peb.image_base() as usize;
111 self.find_by_base(image_base)
112 }
113
114 pub fn find_all<F>(&self, predicate: F) -> Result<Vec<Module<'a>>>
116 where
117 F: Fn(&Module) -> bool,
118 {
119 let mut results = Vec::new();
120
121 for module in ModuleIterator::new(self.peb, ModuleListType::InLoadOrder)? {
122 if predicate(&module) {
123 results.push(module);
124 }
125 }
126
127 Ok(results)
128 }
129
130 pub fn is_loaded(&self, name: &str) -> bool {
132 self.find_by_name(name).is_ok()
133 }
134}
135
136pub fn get_ntdll() -> Result<ModuleHandle> {
138 let peb = Peb::current()?;
139 let query = ModuleQuery::new(&peb);
140 let module = query.ntdll()?;
141
142 unsafe {
144 ModuleHandle::from_raw(module.as_ldr_entry() as *const _ as *mut _)
145 .ok_or(WraithError::NullPointer {
146 context: "ntdll entry",
147 })
148 }
149}
150
151pub fn get_kernel32() -> Result<ModuleHandle> {
153 let peb = Peb::current()?;
154 let query = ModuleQuery::new(&peb);
155 let module = query.kernel32()?;
156
157 unsafe {
159 ModuleHandle::from_raw(module.as_ldr_entry() as *const _ as *mut _)
160 .ok_or(WraithError::NullPointer {
161 context: "kernel32 entry",
162 })
163 }
164}
165
166pub fn get_proc_address(module_name: &str, proc_name: &str) -> Result<usize> {
168 let peb = Peb::current()?;
169 let query = ModuleQuery::new(&peb);
170 let module = query.find_by_name(module_name)?;
171 module.get_export(proc_name)
172}
173
174pub fn get_ntdll_export(proc_name: &str) -> Result<usize> {
176 get_proc_address("ntdll.dll", proc_name)
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_find_ntdll() {
185 let peb = Peb::current().expect("should get PEB");
186 let query = ModuleQuery::new(&peb);
187 let ntdll = query.ntdll().expect("should find ntdll");
188 assert!(ntdll.name_lowercase().contains("ntdll"));
189 }
190
191 #[test]
192 fn test_find_kernel32() {
193 let peb = Peb::current().expect("should get PEB");
194 let query = ModuleQuery::new(&peb);
195 let kernel32 = query.kernel32().expect("should find kernel32");
196 assert!(kernel32.name_lowercase().contains("kernel32"));
197 }
198
199 #[test]
200 fn test_find_by_address() {
201 let peb = Peb::current().expect("should get PEB");
202 let query = ModuleQuery::new(&peb);
203
204 let addr = test_find_by_address as usize;
206 let module = query.find_by_address(addr).expect("should find module");
207
208 assert!(module.contains(addr));
210 }
211
212 #[test]
213 fn test_get_ntdll_handle() {
214 let handle = get_ntdll().expect("should get ntdll handle");
215 let module = handle.as_module();
216 assert!(module.name_lowercase().contains("ntdll"));
217 }
218
219 #[test]
220 fn test_get_export() {
221 let peb = Peb::current().expect("should get PEB");
222 let query = ModuleQuery::new(&peb);
223 let ntdll = query.ntdll().expect("should find ntdll");
224
225 let addr = ntdll.get_export("NtClose").expect("should find NtClose");
226 assert!(addr > ntdll.base());
227 assert!(addr < ntdll.base() + ntdll.size());
228 }
229
230 #[test]
231 fn test_is_loaded() {
232 let peb = Peb::current().expect("should get PEB");
233 let query = ModuleQuery::new(&peb);
234
235 assert!(query.is_loaded("ntdll.dll"));
236 assert!(query.is_loaded("kernel32.dll"));
237 assert!(!query.is_loaded("nonexistent_module_12345.dll"));
238 }
239}