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