1use std::sync::Mutex;
2
3use decant_backend::{BackendError, MemoryBackend, Result};
4use decant_inject::guest::{
5 GuestCapabilities, GuestInjectError, GuestMemoryBackend, GuestMemoryRegion, GuestModuleInfo,
6 GuestProcessInfo,
7};
8use decant_protocol::{MemRegion, ModuleInfo, Pid, ProcessInfo};
9
10use memflow::prelude::v1::*;
11
12const PAGE_SIZE: usize = 0x1000;
13
14pub struct MemflowBackend {
15 os: Mutex<OsInstanceArcBox<'static>>,
16 proc_cache: Mutex<Option<(u32, IntoProcessInstanceArcBox<'static>)>>,
17 connector: String,
18}
19
20fn other<E: std::fmt::Debug>(e: E) -> BackendError {
21 BackendError::Other(format!("memflow: {e:?}"))
22}
23
24fn guest_other<E: std::fmt::Display>(e: E) -> GuestInjectError {
25 GuestInjectError::Backend(e.to_string())
26}
27
28impl MemflowBackend {
29 pub fn connect(connector: &str) -> anyhow::Result<Self> {
30 let mut inventory = Inventory::scan();
31 let connector_args = std::env::var("DECANT_CONNECTOR_ARGS")
32 .ok()
33 .filter(|s| !s.is_empty());
34 let os_args = std::env::var("DECANT_OS_ARGS")
35 .ok()
36 .filter(|s| !s.is_empty());
37
38 let mut builder = inventory.builder().connector(connector);
39 if let Some(a) = connector_args {
40 let args: ConnectorArgs = a
41 .parse()
42 .map_err(|e| anyhow::anyhow!("parsing DECANT_CONNECTOR_ARGS {a:?}: {e:?}"))?;
43 builder = builder.args(args);
44 }
45
46 let builder = builder.os("win32");
47 let os = if let Some(a) = os_args {
48 let args: OsArgs = a
49 .parse()
50 .map_err(|e| anyhow::anyhow!("parsing DECANT_OS_ARGS {a:?}: {e:?}"))?;
51 builder.args(args).build()
52 } else {
53 builder.build()
54 }
55 .map_err(|e| {
56 anyhow::anyhow!(
57 "building memflow OS via the {connector:?} connector: {e:?}. Check the {connector} \
58 plugin is in MEMFLOW_PLUGIN_PATH and the VM is running. The qemu connector needs \
59 ptrace access (CAP_SYS_PTRACE on the daemon, or root); the kvm connector needs root \
60 and the memflow kernel module."
61 )
62 })?;
63
64 Ok(MemflowBackend {
65 os: Mutex::new(os),
66 proc_cache: Mutex::new(None),
67 connector: connector.to_string(),
68 })
69 }
70
71 pub fn connector(&self) -> &str {
72 &self.connector
73 }
74
75 fn with_process<R>(
79 &self,
80 pid: Pid,
81 f: impl FnOnce(&mut IntoProcessInstanceArcBox<'static>) -> Result<R>,
82 ) -> Result<R> {
83 let os = self.os.lock().unwrap();
84 let mut cache = self.proc_cache.lock().unwrap();
85 if cache.as_ref().map(|(p, _)| *p) != Some(pid.0) {
86 let proc =
87 os.clone()
88 .into_process_by_pid(pid.0)
89 .map_err(|_| BackendError::NoSuchProcess {
90 pid: Some(pid.0),
91 name: None,
92 })?;
93 *cache = Some((pid.0, proc));
94 }
95 f(&mut cache.as_mut().unwrap().1)
96 }
97
98 fn clear_process_cache(&self, pid: Pid) {
99 let mut cache = self.proc_cache.lock().unwrap();
100 if cache.as_ref().map(|(p, _)| *p) == Some(pid.0) {
101 *cache = None;
102 }
103 }
104
105 fn write_once(&self, pid: Pid, addr: u64, data: &[u8]) -> Result<usize> {
106 self.with_process(pid, |proc| {
107 proc.write_raw(Address::from(addr), data)
108 .map_err(|e| BackendError::WriteFailed {
109 addr,
110 reason: format!("{e:?}"),
111 })?;
112 Ok(data.len())
113 })
114 }
115
116 fn write_paged(&self, pid: Pid, addr: u64, data: &[u8]) -> Result<usize> {
117 let mut offset = 0usize;
118 while offset < data.len() {
119 let cur = addr + offset as u64;
120 let page_remaining = PAGE_SIZE - (cur as usize & (PAGE_SIZE - 1));
121 let chunk_len = page_remaining.min(data.len() - offset);
122 self.write_once(pid, cur, &data[offset..offset + chunk_len])?;
123 offset += chunk_len;
124 }
125 Ok(data.len())
126 }
127}
128
129impl MemoryBackend for MemflowBackend {
130 fn list_processes(&self) -> Result<Vec<ProcessInfo>> {
131 let mut os = self.os.lock().unwrap();
132 let infos = os.process_info_list().map_err(other)?;
133 Ok(infos
134 .into_iter()
135 .map(|i| ProcessInfo {
136 pid: Pid(i.pid),
137 name: i.name.to_string(),
138 })
139 .collect())
140 }
141
142 fn process_by_pid(&self, pid: Pid) -> Result<ProcessInfo> {
143 let mut os = self.os.lock().unwrap();
144 match os.process_info_by_pid(pid.0) {
145 Ok(i) => Ok(ProcessInfo {
146 pid: Pid(i.pid),
147 name: i.name.to_string(),
148 }),
149 Err(_) => Err(BackendError::NoSuchProcess {
150 pid: Some(pid.0),
151 name: None,
152 }),
153 }
154 }
155
156 fn process_by_name(&self, name: &str) -> Result<ProcessInfo> {
157 let mut os = self.os.lock().unwrap();
158 match os.process_info_by_name(name) {
159 Ok(i) => Ok(ProcessInfo {
160 pid: Pid(i.pid),
161 name: i.name.to_string(),
162 }),
163 Err(_) => Err(BackendError::NoSuchProcess {
164 pid: None,
165 name: Some(name.to_string()),
166 }),
167 }
168 }
169
170 fn module_list(&self, pid: Pid) -> Result<Vec<ModuleInfo>> {
171 let mut os = self.os.lock().unwrap();
172 let mut proc = os
173 .process_by_pid(pid.0)
174 .map_err(|_| BackendError::NoSuchProcess {
175 pid: Some(pid.0),
176 name: None,
177 })?;
178 let mods = proc.module_list().map_err(other)?;
179 Ok(mods.into_iter().map(module_to_info).collect())
180 }
181
182 fn module_by_name(&self, pid: Pid, name: &str) -> Result<ModuleInfo> {
183 let mut os = self.os.lock().unwrap();
184 let mut proc = os
185 .process_by_pid(pid.0)
186 .map_err(|_| BackendError::NoSuchProcess {
187 pid: Some(pid.0),
188 name: None,
189 })?;
190 let m = module_by_name_ci(&mut proc, pid, name)?;
191 Ok(module_to_info(m))
192 }
193
194 fn module_exports(&self, pid: Pid, module: &str) -> Result<Vec<(String, u64)>> {
195 let mut os = self.os.lock().unwrap();
196 let mut proc = os
197 .process_by_pid(pid.0)
198 .map_err(|_| BackendError::NoSuchProcess {
199 pid: Some(pid.0),
200 name: None,
201 })?;
202 let m = module_by_name_ci(&mut proc, pid, module)?;
203 let exports = proc.module_export_list(&m).map_err(other)?;
204 let base = m.base.to_umem();
205 Ok(exports
206 .into_iter()
207 .map(|e| (e.name.to_string(), base + e.offset))
208 .collect())
209 }
210
211 fn read(&self, pid: Pid, addr: u64, len: usize) -> Result<Vec<u8>> {
212 self.with_process(pid, |proc| {
213 proc.read_raw(Address::from(addr), len)
214 .map_err(|e| BackendError::ReadFailed {
215 addr,
216 len: len as u64,
217 reason: format!("{e:?}"),
218 })
219 })
220 }
221
222 fn write(&self, pid: Pid, addr: u64, data: &[u8]) -> Result<usize> {
223 match self.write_once(pid, addr, data) {
224 Ok(written) => Ok(written),
225 Err(first) => {
226 self.clear_process_cache(pid);
227 match self.write_once(pid, addr, data) {
228 Ok(written) => Ok(written),
229 Err(second) => {
230 self.clear_process_cache(pid);
231 self.write_paged(pid, addr, data).map_err(|third| {
232 BackendError::WriteFailed {
233 addr,
234 reason: format!(
235 "{third}; after full-write retry failures: {first}; {second}"
236 ),
237 }
238 })
239 }
240 }
241 }
242 }
243 }
244
245 fn memory_map(&self, pid: Pid) -> Result<Vec<MemRegion>> {
246 let mut os = self.os.lock().unwrap();
247 let mut proc = os
248 .process_by_pid(pid.0)
249 .map_err(|_| BackendError::NoSuchProcess {
250 pid: Some(pid.0),
251 name: None,
252 })?;
253 let ranges = proc.mapped_mem_vec(-1);
254 Ok(ranges
255 .into_iter()
256 .map(
257 |CTup3(addr, size, page_type): CTup3<Address, umem, PageType>| MemRegion {
258 base: addr.to_umem(),
259 size,
260 readable: true,
261 writable: page_type.contains(PageType::WRITEABLE),
262 executable: !page_type.contains(PageType::NOEXEC),
263 },
264 )
265 .collect())
266 }
267}
268
269impl GuestMemoryBackend for MemflowBackend {
270 fn capabilities(&self) -> GuestCapabilities {
271 GuestCapabilities::memflow_guest_injection()
272 }
273
274 fn list_processes(&self) -> std::result::Result<Vec<GuestProcessInfo>, GuestInjectError> {
275 <Self as MemoryBackend>::list_processes(self)
276 .map(|processes| {
277 processes
278 .into_iter()
279 .map(|p| GuestProcessInfo {
280 pid: p.pid.0,
281 name: p.name,
282 })
283 .collect()
284 })
285 .map_err(guest_other)
286 }
287
288 fn module_list(&self, pid: u32) -> std::result::Result<Vec<GuestModuleInfo>, GuestInjectError> {
289 <Self as MemoryBackend>::module_list(self, Pid(pid))
290 .map(|modules| {
291 modules
292 .into_iter()
293 .map(|m| GuestModuleInfo {
294 name: m.name,
295 base: m.base,
296 size: m.size,
297 })
298 .collect()
299 })
300 .map_err(guest_other)
301 }
302
303 fn module_exports(
304 &self,
305 pid: u32,
306 module: &str,
307 ) -> std::result::Result<Vec<(String, u64)>, GuestInjectError> {
308 <Self as MemoryBackend>::module_exports(self, Pid(pid), module).map_err(guest_other)
309 }
310
311 fn memory_map(
312 &self,
313 pid: u32,
314 ) -> std::result::Result<Vec<GuestMemoryRegion>, GuestInjectError> {
315 <Self as MemoryBackend>::memory_map(self, Pid(pid))
316 .map(|regions| {
317 regions
318 .into_iter()
319 .map(|r| GuestMemoryRegion {
320 base: r.base,
321 size: r.size,
322 readable: r.readable,
323 writable: r.writable,
324 executable: r.executable,
325 })
326 .collect()
327 })
328 .map_err(guest_other)
329 }
330
331 fn read(
332 &self,
333 pid: u32,
334 addr: u64,
335 len: usize,
336 ) -> std::result::Result<Vec<u8>, GuestInjectError> {
337 <Self as MemoryBackend>::read(self, Pid(pid), addr, len).map_err(guest_other)
338 }
339
340 fn write(&self, pid: u32, addr: u64, data: &[u8]) -> std::result::Result<(), GuestInjectError> {
341 <Self as MemoryBackend>::write(self, Pid(pid), addr, data)
342 .map(|_| ())
343 .map_err(guest_other)
344 }
345
346 fn spoof_vad_type(
347 &self,
348 pid: u32,
349 base: u64,
350 size: u64,
351 ) -> std::result::Result<(), GuestInjectError> {
352 let _ = (pid, base, size);
353 Err(GuestInjectError::Unsupported {
354 operation: "VAD type spoofing",
355 reason: "the memflow backend does not expose a versioned, validated Windows VAD mutation API".into(),
356 })
357 }
358}
359
360fn module_by_name_ci<P: memflow::os::Process + ?Sized>(
361 proc: &mut P,
362 pid: Pid,
363 name: &str,
364) -> Result<ModuleInfo_> {
365 if let Ok(module) = proc.module_by_name(name) {
366 return Ok(module);
367 }
368 let modules = proc.module_list().map_err(other)?;
369 modules
370 .into_iter()
371 .find(|module| module_matches(module, name))
372 .ok_or_else(|| BackendError::NoSuchModule {
373 pid: pid.0,
374 module: name.to_string(),
375 })
376}
377
378fn module_matches(module: &ModuleInfo_, name: &str) -> bool {
379 let module_name = module.name.to_string();
380 if module_name.eq_ignore_ascii_case(name) {
381 return true;
382 }
383 module
384 .path
385 .to_string()
386 .rsplit(['\\', '/'])
387 .next()
388 .is_some_and(|base| base.eq_ignore_ascii_case(name))
389}
390
391fn module_to_info(m: ModuleInfo_) -> ModuleInfo {
392 ModuleInfo {
393 name: m.name.to_string(),
394 base: m.base.to_umem(),
395 size: m.size,
396 }
397}
398
399use memflow::os::module::ModuleInfo as ModuleInfo_;