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    // never invoked in no_std builds
159    #[allow(unused)]
160    fn import_call(iter: impl Iterator<Item = (umem, ReprCString)>, callback: &mut ImportCallback) {
161        iter.take_while(|(offset, name)| {
162            callback.call(ImportInfo {
163                name: name.clone(),
164                offset: *offset,
165            })
166        })
167        .for_each(|_| {});
168    }
169
170    let ret = Err(Error::from(ErrorKind::NotImplemented));
171
172    #[cfg(feature = "pelite")]
173    let ret = ret.or_else(|_| {
174        if let Ok(pe) = pelite::PeView::from_bytes(module_image) {
175            use pelite::pe32::imports::Import as Import32;
176            use pelite::pe64::imports::Import as Import64;
177            use pelite::Wrap::*;
178
179            if let Some(imports) = pe
180                .iat()
181                .map(Some)
182                .or_else(|e| {
183                    if let pelite::Error::Null = e {
184                        Ok(None)
185                    } else {
186                        Err(e)
187                    }
188                })
189                .map_err(|_| ErrorKind::InvalidExeFile)?
190            {
191                let iter = imports
192                    .iter()
193                    .filter_map(|w| match w {
194                        T32((addr, Ok(Import32::ByName { name, .. }))) => {
195                            Some((*addr as umem, name))
196                        }
197                        T64((addr, Ok(Import64::ByName { name, .. }))) => {
198                            Some((*addr as umem, name))
199                        }
200                        _ => None,
201                    })
202                    .filter_map(|(a, n)| n.to_str().ok().map(|n| (a, n.into())));
203
204                import_call(iter, &mut callback);
205            }
206
207            Ok(())
208        } else {
209            Err(Error::from(ErrorKind::InvalidExeFile))
210        }
211    });
212
213    #[cfg(feature = "goblin")]
214    let ret = ret.or_else(|_| match custom_parse(module_image)? {
215        Object::Elf(elf) => {
216            let iter = elf
217                .dynsyms
218                .iter()
219                .filter(|s| s.is_import())
220                .filter_map(|s| {
221                    elf.dynstrtab
222                        .get_at(s.st_name)
223                        .map(|n| (s.st_value as umem, ReprCString::from(n)))
224                });
225
226            import_call(iter, &mut callback);
227
228            Ok(())
229        }
230        Object::PE(pe) => {
231            let iter = pe
232                .imports
233                .iter()
234                .map(|e| (e.offset as umem, e.name.as_ref().into()));
235
236            import_call(iter, &mut callback);
237
238            Ok(())
239        }
240        Object::Mach(Mach::Binary(bin)) => {
241            let mbase = macho_base(&bin).unwrap_or_default();
242
243            let iter = bin
244                .imports()
245                .ok()
246                .into_iter()
247                .flatten()
248                .map(|v| ((v.address as umem) - mbase + base.to_umem(), v.name.into()));
249
250            import_call(iter, &mut callback);
251
252            Ok(())
253        }
254        _ => Err(ErrorKind::InvalidExeFile.into()),
255    });
256
257    ret
258}
259
260#[inline]
261pub fn module_export_list_callback(
262    mem: &mut impl MemoryView,
263    info: &ModuleInfo,
264    callback: ExportCallback,
265) -> Result<()> {
266    export_list_callback(mem, info.base, info.size, callback)
267}
268
269pub fn export_list_callback(
270    mem: &mut impl MemoryView,
271    base: Address,
272    size: umem,
273    mut callback: ExportCallback,
274) -> Result<()> {
275    let mut module_image = aligned_alloc(size as usize);
276    let module_image = module_image.as_bytes_mut();
277
278    mem.read_raw_into(base, module_image).data_part()?;
279
280    // never invoked in no_std builds
281    #[allow(unused)]
282    fn export_call(iter: impl Iterator<Item = (umem, ReprCString)>, callback: &mut ExportCallback) {
283        iter.take_while(|(offset, name)| {
284            callback.call(ExportInfo {
285                name: name.clone(),
286                offset: *offset,
287            })
288        })
289        .for_each(|_| {});
290    }
291
292    let ret = Err(Error::from(ErrorKind::NotImplemented));
293
294    #[cfg(feature = "pelite")]
295    let ret = ret.or_else(|_| {
296        if let Ok(pe) = pelite::PeView::from_bytes(module_image) {
297            use pelite::pe64::exports::Export;
298
299            if let Some(exports) = pe
300                .exports()
301                .map(Some)
302                .or_else(|e| {
303                    if let pelite::Error::Null = e {
304                        Ok(None)
305                    } else {
306                        Err(e)
307                    }
308                })
309                .map_err(|e| log::debug!("pelite: {}", e))
310                .map_err(|_| ErrorKind::InvalidExeFile)?
311            {
312                let exports = exports
313                    .by()
314                    .map_err(|e| log::debug!("pelite: {}", e))
315                    .map_err(|_| ErrorKind::InvalidExeFile)?;
316
317                let iter = exports
318                    .iter_names()
319                    .filter_map(|(n, e)| n.ok().zip(e.ok()))
320                    .filter_map(|(n, e)| match e {
321                        Export::Symbol(off) => Some((*off as umem, n)),
322                        _ => None,
323                    })
324                    .filter_map(|(o, n)| n.to_str().ok().map(|n| (o, n.into())));
325
326                export_call(iter, &mut callback);
327            }
328
329            Ok(())
330        } else {
331            Err(Error::from(ErrorKind::InvalidExeFile))
332        }
333    });
334
335    #[cfg(feature = "goblin")]
336    let ret = ret.or_else(|_| match custom_parse(module_image)? {
337        Object::Elf(elf) => {
338            let iter = elf
339                .dynsyms
340                .iter()
341                .filter(|s| !s.is_import())
342                .filter_map(|s| {
343                    elf.dynstrtab
344                        .get_at(s.st_name)
345                        .map(|n| (s.st_value as umem, ReprCString::from(n)))
346                });
347
348            export_call(iter, &mut callback);
349
350            Ok(())
351        }
352        Object::PE(pe) => {
353            let iter = pe.exports.iter().filter_map(|e| {
354                e.name
355                    .map(|name| (e.offset.unwrap_or(0usize) as umem, name.into()))
356            });
357
358            export_call(iter, &mut callback);
359
360            Ok(())
361        }
362        Object::Mach(Mach::Binary(bin)) => {
363            let mbase = macho_base(&bin).unwrap_or_default();
364
365            let iter = bin.exports().ok().into_iter().flatten().filter_map(|v| {
366                let MachExportInfo::Regular { address, .. } = v.info else {
367                    return None;
368                };
369
370                Some(((address as umem) - mbase + base.to_umem(), v.name.into()))
371            });
372
373            export_call(iter, &mut callback);
374
375            Ok(())
376        }
377        _ => Err(ErrorKind::InvalidExeFile.into()),
378    });
379
380    ret
381}
382
383#[inline]
384pub fn module_section_list_callback(
385    mem: &mut impl MemoryView,
386    info: &ModuleInfo,
387    callback: SectionCallback,
388) -> Result<()> {
389    section_list_callback(mem, info.base, info.size, callback)
390}
391
392pub fn section_list_callback(
393    mem: &mut impl MemoryView,
394    base: Address,
395    size: umem,
396    mut callback: SectionCallback,
397) -> Result<()> {
398    let mut module_image = aligned_alloc(size as usize);
399    let module_image = module_image.as_bytes_mut();
400
401    mem.read_raw_into(base, module_image).data_part()?;
402
403    // never invoked in no_std builds
404    #[allow(unused)]
405    fn section_call(
406        iter: impl Iterator<Item = (umem, umem, ReprCString)>,
407        callback: &mut SectionCallback,
408        base: Address,
409    ) {
410        iter.take_while(|(section_base, section_size, name)| {
411            callback.call(SectionInfo {
412                name: name.clone(),
413                base: base + *section_base,
414                size: *section_size,
415            })
416        })
417        .for_each(|_| {});
418    }
419
420    let ret = Err(Error::from(ErrorKind::NotImplemented));
421
422    #[cfg(feature = "pelite")]
423    let ret = ret.or_else(|_| {
424        if let Ok(pe) = pelite::PeView::from_bytes(module_image) {
425            let iter = pe.section_headers().iter().filter_map(|sh| {
426                sh.name().ok().map(|name| {
427                    (
428                        sh.virtual_range().start as umem,
429                        sh.virtual_range().end as umem,
430                        name.into(),
431                    )
432                })
433            });
434
435            section_call(iter, &mut callback, base);
436
437            Ok(())
438        } else {
439            Err(Error::from(ErrorKind::InvalidExeFile))
440        }
441    });
442
443    #[cfg(feature = "goblin")]
444    let ret = ret.or_else(|_| match custom_parse(module_image)? {
445        Object::Elf(elf) => {
446            let iter = elf.section_headers.iter().filter_map(|s| {
447                elf.shdr_strtab
448                    .get_at(s.sh_name)
449                    .map(|n| (s.sh_addr as umem, s.sh_size as umem, ReprCString::from(n)))
450            });
451
452            section_call(iter, &mut callback, base);
453
454            Ok(())
455        }
456        Object::PE(pe) => {
457            let iter = pe.sections.iter().filter_map(|e| {
458                e.real_name.as_ref().map(|name| {
459                    (
460                        e.virtual_address as umem,
461                        e.virtual_size as umem,
462                        name.as_str().into(),
463                    )
464                })
465            });
466
467            section_call(iter, &mut callback, base);
468
469            Ok(())
470        }
471        Object::Mach(Mach::Binary(bin)) => {
472            let mut base_off = None;
473
474            let iter = bin.segments.sections().flatten().filter_map(|v| {
475                let (s, _) = v.ok()?;
476                let name = &s.sectname;
477                let name = name.split(|&v| v == 0).next()?;
478                let name = std::str::from_utf8(name).ok()?;
479
480                let addr = s.addr as umem;
481
482                if base_off.is_none() {
483                    base_off = Some(addr);
484                }
485
486                Some((addr - base_off.unwrap(), s.size as umem, name.into()))
487            });
488
489            section_call(iter, &mut callback, base);
490
491            Ok(())
492        }
493        _ => Err(ErrorKind::InvalidExeFile.into()),
494    });
495
496    ret
497}