1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/*
 * Copyright © 2021 Keegan Saunders
 * Copyright © 2021 S Rubenstein
 *
 * Licence: wxWindows Library Licence, Version 3.1
 */

#![cfg_attr(
    any(target_arch = "x86_64", target_arch = "x86"),
    allow(clippy::unnecessary_cast)
)]

extern crate alloc;
use {
    crate::MemoryRange,
    alloc::string::String,
    core::{
        ffi::{c_void, CStr},
        marker::PhantomData,
    },
    frida_gum_sys as gum_sys,
};

#[cfg(not(feature = "module-names"))]
use alloc::boxed::Box;

/// The memory protection of an unassociated page.
#[derive(Clone, FromPrimitive)]
#[repr(u32)]
pub enum PageProtection {
    NoAccess = gum_sys::_GumPageProtection_GUM_PAGE_NO_ACCESS as u32,
    Read = gum_sys::_GumPageProtection_GUM_PAGE_READ as u32,
    Write = gum_sys::_GumPageProtection_GUM_PAGE_WRITE as u32,
    Execute = gum_sys::_GumPageProtection_GUM_PAGE_EXECUTE as u32,
    ReadWrite = gum_sys::_GumPageProtection_GUM_PAGE_READ as u32
        | gum_sys::_GumPageProtection_GUM_PAGE_WRITE as u32,
    ReadExecute = gum_sys::_GumPageProtection_GUM_PAGE_READ as u32
        | gum_sys::_GumPageProtection_GUM_PAGE_EXECUTE as u32,
    ReadWriteExecute = gum_sys::_GumPageProtection_GUM_PAGE_READ as u32
        | gum_sys::_GumPageProtection_GUM_PAGE_WRITE as u32
        | gum_sys::_GumPageProtection_GUM_PAGE_EXECUTE as u32,
}

/// The file association to a page.
#[derive(Clone)]
pub struct FileMapping<'a> {
    path: String,
    size: usize,
    offset: u64,
    phantom: PhantomData<&'a gum_sys::GumFileMapping>,
}

impl<'a> FileMapping<'a> {
    pub(crate) fn from_raw(file: *const gum_sys::GumFileMapping) -> Option<Self> {
        if file.is_null() {
            None
        } else {
            Some(unsafe {
                Self {
                    path: CStr::from_ptr((*file).path).to_string_lossy().into_owned(),
                    size: (*file).size as usize,
                    offset: (*file).offset,
                    phantom: PhantomData,
                }
            })
        }
    }

    /// The path of the file mapping on disk.
    pub fn path(&self) -> &str {
        &self.path
    }

    /// The offset into the file mapping.
    pub fn offset(&self) -> u64 {
        self.offset
    }

    /// The size of the mapping.
    pub fn size(&self) -> usize {
        self.size as usize
    }
}

struct SaveRangeDetailsByAddressContext<'a> {
    address: u64,
    details: Option<RangeDetails<'a>>,
}

unsafe extern "C" fn save_range_details_by_address(
    details: *const gum_sys::GumRangeDetails,
    context: *mut c_void,
) -> i32 {
    let context = &mut *(context as *mut SaveRangeDetailsByAddressContext);
    let range = (*details).range;
    let start = (*range).base_address as u64;
    let end = start + (*range).size as u64;
    if start <= context.address && context.address < end {
        context.details = Some(RangeDetails::from_raw(details));
        return 0;
    }

    1
}

unsafe extern "C" fn enumerate_ranges_stub(
    details: *const gum_sys::GumRangeDetails,
    context: *mut c_void,
) -> i32 {
    if !(*(context as *mut Box<&mut dyn FnMut(&RangeDetails) -> bool>))(&RangeDetails::from_raw(
        details,
    )) {
        return 0;
    }
    1
}

/// Details a range of virtual memory.
pub struct RangeDetails<'a> {
    range: MemoryRange,
    protection: PageProtection,
    file: Option<FileMapping<'a>>,
    phantom: PhantomData<&'a gum_sys::GumRangeDetails>,
}

impl<'a> RangeDetails<'a> {
    pub(crate) fn from_raw(range_details: *const gum_sys::GumRangeDetails) -> Self {
        unsafe {
            Self {
                range: MemoryRange::from_raw((*range_details).range),
                protection: num::FromPrimitive::from_u32((*range_details).protection).unwrap(),
                file: FileMapping::from_raw((*range_details).file),
                phantom: PhantomData,
            }
        }
    }

    /// Get a [`RangeDetails`] for the range containing the given address.
    pub fn with_address(address: u64) -> Option<RangeDetails<'a>> {
        let mut context = SaveRangeDetailsByAddressContext {
            address,
            details: None,
        };
        unsafe {
            gum_sys::gum_process_enumerate_ranges(
                gum_sys::_GumPageProtection_GUM_PAGE_NO_ACCESS as u32,
                Some(save_range_details_by_address),
                &mut context as *mut _ as *mut c_void,
            );
        }

        context.details
    }

    /// Enumerate all ranges which match the given [`PageProtection`], calling the callback
    /// function for each such range.
    pub fn enumerate_with_prot(
        prot: PageProtection,
        callback: &mut dyn FnMut(&RangeDetails) -> bool,
    ) {
        unsafe {
            gum_sys::gum_process_enumerate_ranges(
                prot as u32,
                Some(enumerate_ranges_stub),
                &mut Box::new(callback) as *mut _ as *mut c_void,
            );
        }
    }

    /// The range of memory that is detailed.
    pub fn memory_range(&self) -> MemoryRange {
        self.range.clone()
    }

    /// The page protection of the range.
    pub fn protection(&self) -> PageProtection {
        self.protection.clone()
    }

    /// The associated file mapping, if present.
    pub fn file_mapping(&self) -> Option<FileMapping> {
        self.file.clone()
    }
}