memflow/os/
util.rs

1//! Helpers for implementing several OS functions.
2
3use crate::error::*;
4use crate::mem::MemoryView;
5use crate::os::*;
6use crate::types::umem;
7use cglue::prelude::v1::ReprCString;
8use dataview::PodMethods;
9use std::vec::Vec;
10
11#[cfg(feature = "goblin")]
12use goblin::{
13    container::Ctx,
14    elf::{dynamic, Dynamic, Elf, ProgramHeader, RelocSection, Symtab},
15    mach::{exports::ExportInfo as MachExportInfo, Mach, MachO},
16    pe::{options::ParseOptions, PE},
17    strtab::Strtab,
18    Object,
19};
20
21fn aligned_alloc(bytes: usize) -> Vec<u64> {
22    vec![0; (bytes + 8 - 1) / 8]
23}
24
25#[cfg(feature = "goblin")]
26fn parse_elf(bytes: &[u8]) -> goblin::error::Result<Elf<'_>> {
27    let header = Elf::parse_header(bytes)?;
28
29    let ctx = Ctx {
30        container: header.container()?,
31        le: header.endianness()?,
32    };
33
34    let program_headers =
35        ProgramHeader::parse(bytes, header.e_phoff as usize, header.e_phnum as usize, ctx)?;
36
37    let dynamic = Dynamic::parse(bytes, &program_headers, ctx)?;
38
39    let mut dynsyms = Symtab::default();
40    let mut dynstrtab = Strtab::default();
41    let mut dynrelas = RelocSection::default();
42    let mut dynrels = RelocSection::default();
43    let mut pltrelocs = RelocSection::default();
44
45    if let Some(ref dynamic) = dynamic {
46        let dyn_info = &dynamic.info;
47
48        dynstrtab = Strtab::parse(bytes, dyn_info.strtab, dyn_info.strsz, 0x0)?;
49
50        /*if dyn_info.soname != 0 {
51            // FIXME: warn! here
52            soname = dynstrtab.get_at(dyn_info.soname);
53        }
54        if dyn_info.needed_count > 0 {
55            libraries = dynamic.get_libraries(&dynstrtab);
56        }*/
57        // parse the dynamic relocations
58        if let Ok(relas) = RelocSection::parse(bytes, dyn_info.rela, dyn_info.relasz, true, ctx) {
59            dynrelas = relas;
60            dynrels = RelocSection::parse(bytes, dyn_info.rel, dyn_info.relsz, false, ctx)?;
61            let is_rela = dyn_info.pltrel as u64 == dynamic::DT_RELA;
62            pltrelocs =
63                RelocSection::parse(bytes, dyn_info.jmprel, dyn_info.pltrelsz, is_rela, ctx)?;
64
65            // TODO: support these from goblin
66            let mut num_syms = /*if let Some(gnu_hash) = dyn_info.gnu_hash {
67                gnu_hash_len(bytes, gnu_hash as usize, ctx)?
68            } else if let Some(hash) = dyn_info.hash {
69                hash_len(bytes, hash as usize, header.e_machine, ctx)?
70            } else*/ {
71                0
72            };
73            let max_reloc_sym = dynrelas
74                .iter()
75                .chain(dynrels.iter())
76                .chain(pltrelocs.iter())
77                .fold(0, |num, reloc| core::cmp::max(num, reloc.r_sym));
78            if max_reloc_sym != 0 {
79                num_syms = core::cmp::max(num_syms, max_reloc_sym + 1);
80            }
81
82            dynsyms = Symtab::parse(bytes, dyn_info.symtab, num_syms, ctx)?;
83        }
84    }
85
86    let mut elf = Elf::lazy_parse(header)?;
87
88    elf.program_headers = program_headers;
89    elf.dynamic = dynamic;
90    elf.dynsyms = dynsyms;
91    elf.dynstrtab = dynstrtab;
92    elf.dynrelas = dynrelas;
93    elf.dynrels = dynrels;
94    elf.pltrelocs = pltrelocs;
95
96    Ok(elf)
97}
98
99#[cfg(feature = "goblin")]
100fn custom_parse(buf: &[u8]) -> Result<Object<'_>> {
101    PE::parse_with_opts(
102        buf,
103        &ParseOptions {
104            resolve_rva: false,
105            parse_attribute_certificates: false,
106        },
107    )
108    .map(Object::PE)
109    .map_err(|e| {
110        log::debug!("PE: {}", e);
111        e
112    })
113    .or_else(|_| parse_elf(buf).map(Object::Elf))
114    .map_err(|e| {
115        log::debug!("Elf: {}", e);
116        e
117    })
118    .or_else(|_| {
119        // Until https://github.com/m4b/goblin/pull/386 is merged
120        #[cfg(feature = "unstable_goblin_lossy_macho")]
121        return Mach::parse_lossy(buf).map(Object::Mach);
122        #[cfg(not(feature = "unstable_goblin_lossy_macho"))]
123        return Mach::parse(buf).map(Object::Mach);
124    })
125    .map_err(|e| {
126        log::debug!("Mach: {}", e);
127        e
128    })
129    .map_err(|_| Error(ErrorOrigin::OsLayer, ErrorKind::InvalidExeFile))
130}
131
132#[cfg(feature = "goblin")]
133fn macho_base(bin: &MachO) -> Option<umem> {
134    let s = bin.segments.sections().flatten().next()?.ok()?.0;
135    Some(s.addr as umem)
136}
137
138#[inline]
139pub fn module_import_list_callback(
140    mem: &mut impl MemoryView,
141    info: &ModuleInfo,
142    callback: ImportCallback,
143) -> Result<()> {
144    import_list_callback(mem, info.base, info.size, callback)
145}
146
147pub fn import_list_callback(
148    mem: &mut impl MemoryView,
149    base: Address,
150    size: umem,
151    mut callback: ImportCallback,
152) -> Result<()> {
153    let mut module_image = aligned_alloc(size as usize);
154    let module_image = module_image.as_bytes_mut();
155
156    mem.read_raw_into(base, module_image).data_part()?;
157
158    fn import_call(iter: impl Iterator<Item = (umem, ReprCString)>, callback: &mut ImportCallback) {
159        iter.take_while(|(offset, name)| {
160            callback.call(ImportInfo {
161                name: name.clone(),
162                offset: *offset,
163            })
164        })
165        .for_each(|_| {});
166    }
167
168    let ret = Err(Error::from(ErrorKind::NotImplemented));
169
170    #[cfg(feature = "pelite")]
171    let ret = ret.or_else(|_| {
172        if let Ok(pe) = pelite::PeView::from_bytes(module_image) {
173            use pelite::pe32::imports::Import as Import32;
174            use pelite::pe64::imports::Import as Import64;
175            use pelite::Wrap::*;
176
177            if let Some(imports) = pe
178                .iat()
179                .map(Some)
180                .or_else(|e| {
181                    if let pelite::Error::Null = e {
182                        Ok(None)
183                    } else {
184                        Err(e)
185                    }
186                })
187                .map_err(|_| ErrorKind::InvalidExeFile)?
188            {
189                let iter = imports
190                    .iter()
191                    .filter_map(|w| match w {
192                        T32((addr, Ok(Import32::ByName { name, .. }))) => {
193                            Some((*addr as umem, name))
194                        }
195                        T64((addr, Ok(Import64::ByName { name, .. }))) => {
196                            Some((*addr as umem, name))
197                        }
198                        _ => None,
199                    })
200                    .filter_map(|(a, n)| n.to_str().ok().map(|n| (a, n.into())));
201
202                import_call(iter, &mut callback);
203            }
204
205            Ok(())
206        } else {
207            Err(Error::from(ErrorKind::InvalidExeFile))
208        }
209    });
210
211    #[cfg(feature = "goblin")]
212    let ret = ret.or_else(|_| match custom_parse(module_image)? {
213        Object::Elf(elf) => {
214            let iter = elf
215                .dynsyms
216                .iter()
217                .filter(|s| s.is_import())
218                .filter_map(|s| {
219                    elf.dynstrtab
220                        .get_at(s.st_name)
221                        .map(|n| (s.st_value as umem, ReprCString::from(n)))
222                });
223
224            import_call(iter, &mut callback);
225
226            Ok(())
227        }
228        Object::PE(pe) => {
229            let iter = pe
230                .imports
231                .iter()
232                .map(|e| (e.offset as umem, e.name.as_ref().into()));
233
234            import_call(iter, &mut callback);
235
236            Ok(())
237        }
238        Object::Mach(Mach::Binary(bin)) => {
239            let mbase = macho_base(&bin).unwrap_or_default();
240
241            let iter = bin
242                .imports()
243                .ok()
244                .into_iter()
245                .flatten()
246                .map(|v| ((v.address as umem) - mbase + base.to_umem(), v.name.into()));
247
248            import_call(iter, &mut callback);
249
250            Ok(())
251        }
252        _ => Err(ErrorKind::InvalidExeFile.into()),
253    });
254
255    ret
256}
257
258#[inline]
259pub fn module_export_list_callback(
260    mem: &mut impl MemoryView,
261    info: &ModuleInfo,
262    callback: ExportCallback,
263) -> Result<()> {
264    export_list_callback(mem, info.base, info.size, callback)
265}
266
267pub fn export_list_callback(
268    mem: &mut impl MemoryView,
269    base: Address,
270    size: umem,
271    mut callback: ExportCallback,
272) -> Result<()> {
273    let mut module_image = aligned_alloc(size as usize);
274    let module_image = module_image.as_bytes_mut();
275
276    mem.read_raw_into(base, module_image).data_part()?;
277
278    fn export_call(iter: impl Iterator<Item = (umem, ReprCString)>, callback: &mut ExportCallback) {
279        iter.take_while(|(offset, name)| {
280            callback.call(ExportInfo {
281                name: name.clone(),
282                offset: *offset,
283            })
284        })
285        .for_each(|_| {});
286    }
287
288    let ret = Err(Error::from(ErrorKind::NotImplemented));
289
290    #[cfg(feature = "pelite")]
291    let ret = ret.or_else(|_| {
292        if let Ok(pe) = pelite::PeView::from_bytes(module_image) {
293            use pelite::pe64::exports::Export;
294
295            if let Some(exports) = pe
296                .exports()
297                .map(Some)
298                .or_else(|e| {
299                    if let pelite::Error::Null = e {
300                        Ok(None)
301                    } else {
302                        Err(e)
303                    }
304                })
305                .map_err(|e| log::debug!("pelite: {}", e))
306                .map_err(|_| ErrorKind::InvalidExeFile)?
307            {
308                let exports = exports
309                    .by()
310                    .map_err(|e| log::debug!("pelite: {}", e))
311                    .map_err(|_| ErrorKind::InvalidExeFile)?;
312
313                let iter = exports
314                    .iter_names()
315                    .filter_map(|(n, e)| n.ok().zip(e.ok()))
316                    .filter_map(|(n, e)| match e {
317                        Export::Symbol(off) => Some((*off as umem, n)),
318                        _ => None,
319                    })
320                    .filter_map(|(o, n)| n.to_str().ok().map(|n| (o, n.into())));
321
322                export_call(iter, &mut callback);
323            }
324
325            Ok(())
326        } else {
327            Err(Error::from(ErrorKind::InvalidExeFile))
328        }
329    });
330
331    #[cfg(feature = "goblin")]
332    let ret = ret.or_else(|_| match custom_parse(module_image)? {
333        Object::Elf(elf) => {
334            let iter = elf
335                .dynsyms
336                .iter()
337                .filter(|s| !s.is_import())
338                .filter_map(|s| {
339                    elf.dynstrtab
340                        .get_at(s.st_name)
341                        .map(|n| (s.st_value as umem, ReprCString::from(n)))
342                });
343
344            export_call(iter, &mut callback);
345
346            Ok(())
347        }
348        Object::PE(pe) => {
349            let iter = pe.exports.iter().filter_map(|e| {
350                e.name
351                    .map(|name| (e.offset.unwrap_or(0usize) as umem, name.into()))
352            });
353
354            export_call(iter, &mut callback);
355
356            Ok(())
357        }
358        Object::Mach(Mach::Binary(bin)) => {
359            let mbase = macho_base(&bin).unwrap_or_default();
360
361            let iter = bin.exports().ok().into_iter().flatten().filter_map(|v| {
362                let MachExportInfo::Regular { address, .. } = v.info else {
363                    return None;
364                };
365
366                Some(((address as umem) - mbase + base.to_umem(), v.name.into()))
367            });
368
369            export_call(iter, &mut callback);
370
371            Ok(())
372        }
373        _ => Err(ErrorKind::InvalidExeFile.into()),
374    });
375
376    ret
377}
378
379#[inline]
380pub fn module_section_list_callback(
381    mem: &mut impl MemoryView,
382    info: &ModuleInfo,
383    callback: SectionCallback,
384) -> Result<()> {
385    section_list_callback(mem, info.base, info.size, callback)
386}
387
388pub fn section_list_callback(
389    mem: &mut impl MemoryView,
390    base: Address,
391    size: umem,
392    mut callback: SectionCallback,
393) -> Result<()> {
394    let mut module_image = aligned_alloc(size as usize);
395    let module_image = module_image.as_bytes_mut();
396
397    mem.read_raw_into(base, module_image).data_part()?;
398
399    fn section_call(
400        iter: impl Iterator<Item = (umem, umem, ReprCString)>,
401        callback: &mut SectionCallback,
402        base: Address,
403    ) {
404        iter.take_while(|(section_base, section_size, name)| {
405            callback.call(SectionInfo {
406                name: name.clone(),
407                base: base + *section_base,
408                size: *section_size,
409            })
410        })
411        .for_each(|_| {});
412    }
413
414    let ret = Err(Error::from(ErrorKind::NotImplemented));
415
416    #[cfg(feature = "pelite")]
417    let ret = ret.or_else(|_| {
418        if let Ok(pe) = pelite::PeView::from_bytes(module_image) {
419            let iter = pe.section_headers().iter().filter_map(|sh| {
420                sh.name().ok().map(|name| {
421                    (
422                        sh.virtual_range().start as umem,
423                        sh.virtual_range().end as umem,
424                        name.into(),
425                    )
426                })
427            });
428
429            section_call(iter, &mut callback, base);
430
431            Ok(())
432        } else {
433            Err(Error::from(ErrorKind::InvalidExeFile))
434        }
435    });
436
437    #[cfg(feature = "goblin")]
438    let ret = ret.or_else(|_| match custom_parse(module_image)? {
439        Object::Elf(elf) => {
440            let iter = elf.section_headers.iter().filter_map(|s| {
441                elf.shdr_strtab
442                    .get_at(s.sh_name)
443                    .map(|n| (s.sh_addr as umem, s.sh_size as umem, ReprCString::from(n)))
444            });
445
446            section_call(iter, &mut callback, base);
447
448            Ok(())
449        }
450        Object::PE(pe) => {
451            let iter = pe.sections.iter().filter_map(|e| {
452                e.real_name.as_ref().map(|name| {
453                    (
454                        e.virtual_address as umem,
455                        e.virtual_size as umem,
456                        name.as_str().into(),
457                    )
458                })
459            });
460
461            section_call(iter, &mut callback, base);
462
463            Ok(())
464        }
465        Object::Mach(Mach::Binary(bin)) => {
466            let mut base_off = None;
467
468            let iter = bin.segments.sections().flatten().filter_map(|v| {
469                let (s, _) = v.ok()?;
470                let name = &s.sectname;
471                let name = name.split(|&v| v == 0).next()?;
472                let name = std::str::from_utf8(name).ok()?;
473
474                let addr = s.addr as umem;
475
476                if base_off.is_none() {
477                    base_off = Some(addr);
478                }
479
480                Some((addr - base_off.unwrap(), s.size as umem, name.into()))
481            });
482
483            section_call(iter, &mut callback, base);
484
485            Ok(())
486        }
487        _ => Err(ErrorKind::InvalidExeFile.into()),
488    });
489
490    ret
491}