panda/plugins/
cosi.rs

1//! Bindings and helpers for working with the OSI2 plugin, allowing kernel
2//! introspection via Volatility 3 Profiles.
3//!
4//! This allows for easily building off of and taking advantage of the amazing work done by
5//! the Volatility and greater memory forensics communities but in a dynamic analysis
6//! setting.
7//!
8//! See [`OsiType`] and [`osi_static`] for high-level usage.
9//!
10//! [`OsiType`]: macro@panda::plugins::cosi::OsiType
11//! [`osi_static`]: panda::plugins::cosi::osi_static
12use crate::mem::read_guest_type;
13use crate::plugin_import;
14use crate::prelude::*;
15use crate::GuestReadFail;
16
17use std::ffi::{CStr, CString};
18use std::os::raw::c_char;
19
20mod osi_statics;
21pub use osi_statics::*;
22
23#[doc(inline)]
24/// A macro for declaring global kernel data structures accessible via OSI2. The
25/// type of which must implement/derive [`OsiType`], which is pulled from the currently
26/// loaded Volatility Profile.
27///
28/// The static provides one main method: `read`, which takes an [`&mut CPUState`](CPUState)
29/// and returns a `Result<T, GuestReadFail>`, where `T` is the type of the static.
30///
31/// Also provided for structs which derive [`OsiType`] is an accessor method for each
32/// field.
33///
34/// For more information, see the [`OsiType`] derive macro.
35///
36/// ## Attributes
37///
38/// * `symbol` (required) - specify the symbol within the volatility profile that describes
39/// the storage location of the given type. Takes the form of `#[symbol = "..."]`.
40/// * `per_cpu` (optional) - specify that the given symbol is a CPU-local kernel structure and
41/// should be handled accordingly
42///
43/// ## Example
44///
45/// ```
46/// use panda::plugins::cosi::{OsiType, osi_static};
47///
48/// #[derive(OsiType, Debug)]
49/// #[osi(type_name = "task_struct")]
50/// struct TaskStruct {
51///     comm: [u8; 0x10],
52/// }
53///
54/// osi_static! {
55///     #[per_cpu]
56///     #[symbol = "current_task"]
57///     static CURRENT_TASK: TaskStruct;
58/// }
59///
60/// # let cpu = unsafe { &mut *panda::sys::get_cpu() };
61/// // Read the entire structure
62/// let current_task = CURRENT_TASK.read(cpu).unwrap();
63///
64/// // Read a single field `comm`
65/// let process_name = CURRENT_TASK.comm(cpu).unwrap();
66/// ```
67///
68/// [`OsiType`]: macro@OsiType
69pub use panda_macros::osi_static;
70
71/// A derive macro for allowing a given structure to be used as a type for OS introspection.
72///
73/// The recommended usage is to declare instances of these types using the [`osi_static`]
74/// macro, however [`OsiType::osi_read`] is also available for when an OS data structure
75/// is not global.
76///
77/// ## Attributes
78///
79/// |     Name    | Field/Struct Level | Required | Description |
80/// |:-----------:|:------------------:|:--------:|:------------|
81/// | `type_name` |    Struct-Level    |    ✔️     | Sets the name of the type to pull info from within the volatility profile |
82/// |   `rename`  |    Field-Level     |          | By default the name of the field within the volatility profile will be assumed to be identical to the field within the Rust type, the `rename` attribute allows overriding this to have the volatility name and Rust field name be separate.
83/// |  `osi_type` |    Field-Level     |          | Treat as a nested [`OsiType`], not a [`GuestType`]
84///
85/// ## Example
86///
87/// ```
88/// #[derive(OsiType, Debug)]
89/// #[osi(type_name = "task_struct")]
90/// struct TaskStruct {
91///     #[osi(rename = "comm")]
92///     process_name: [u8; 0x10],
93/// }
94/// ```
95///
96/// ## How it works
97///
98/// OSI 2 is based around a system of using volatility 3 profiles (also known as "Symbol Tables")
99/// in order to have a semantic understanding of operating system types, in order to leverage
100/// the infrastructure of memory forensics to enable high-quality runtime analysis.
101///
102/// To work with these profiles directly would require parsing them, extracting the
103/// offsets/sizes/etc of the data types of interest to the user, and then manually
104/// performing address/offset calculations before reading kernel memory and then parsing
105/// the resulting bytes. This results in a lot of boilerplate, poor ergonomics, and hard
106/// to read and maintain code.
107///
108/// The goal of this derive macro is to handle address calculation of both global and
109/// per-CPU symbols as well as handle pulling symbols from the Volatility Profile and
110/// even handling the parsing of bytes from memory.
111///
112/// The `OsiType` derive macro generates two things:
113///
114/// 1. It generates an implementation of the [`OsiType`](trait@OsiType) trait for your
115/// given type. This specifies how to read the entirety of the type from memory.
116///
117/// 2. It generates a "method delegator" type. This type has one function: hold onto
118/// the symbol of an instance of the structure as well as whether or not the given
119/// symbol is per-CPU (such as the current process) or OS-global (such as the syscall
120/// table). It then provides a set of methods, one for each field of the type `OsiType` is
121/// being derived for. This allows for reading individual fields of a structure without
122/// parsing the entire type out of memory.
123///
124/// To create an instance of the method delegator type, the following can be done:
125///
126/// ```
127/// let symbol = "current_task";
128/// let is_per_cpu = true;
129///
130/// let delegate = <T as OsiType>::MethodDelegator::new(symbol, is_per_cpu);
131/// ```
132///
133/// This is what allows for the [`osi_static`] macro to be used in order to read individual
134/// fields of a given type.
135pub use panda_macros::OsiType;
136
137plugin_import! {
138    /// Raw bindings to the cosi plugin. It is not recommended to use these directly
139    static OSI2: Osi2 = extern "cosi" {
140        fn kaslr_offset(cpu: &mut CPUState) -> target_ptr_t;
141        fn current_cpu_offset(cpu: &mut CPUState) -> target_ulong;
142        fn free_cosi_str(string: *mut c_char);
143
144        fn symbol_from_name(name: *const c_char) -> Option<&'static VolatilitySymbol>;
145        fn symbol_addr_from_name(name: *const c_char) -> target_ptr_t;
146        fn symbol_value_from_name(name: *const c_char) -> target_ptr_t;
147        fn addr_of_symbol(symbol: &VolatilitySymbol) -> target_ptr_t;
148        fn value_of_symbol(symbol: &VolatilitySymbol) -> target_ptr_t;
149        fn name_of_symbol(symbol: &VolatilitySymbol) -> *mut c_char;
150
151        fn type_from_name(name: *const c_char) -> Option<&'static VolatilityStruct>;
152        fn name_of_struct(ty: &VolatilityStruct) -> *mut c_char;
153        fn size_of_struct(vol_struct: &VolatilityStruct) -> target_ulong;
154        fn offset_of_field(
155            vol_struct: &VolatilityStruct,
156            name: *const c_char
157        ) -> target_long;
158        fn type_of_field(
159            vol_struct: &VolatilityStruct,
160            name: *const c_char
161        ) -> *mut c_char;
162        fn get_field_by_index(ty: &VolatilityStruct, index: usize) -> *mut c_char;
163
164        fn enum_from_name(name: *const c_char) -> Option<&'static VolatilityEnum>;
165        fn name_of_enum(ty: &VolatilityEnum) -> *mut c_char;
166
167        fn base_type_from_name(name: *const c_char) -> Option<&'static VolatilityBaseType>;
168        fn name_of_base_type(ty: &VolatilityBaseType) -> *mut c_char;
169        fn size_of_base_type(ty: &VolatilityBaseType) -> target_ptr_t;
170        fn is_base_type_signed(ty: &VolatilityBaseType) -> bool;
171    };
172}
173
174// See https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs for
175// more info on why this is the way it is.
176macro_rules! opaque_types {
177    ($($(#[$meta:meta])* $name:ident),*) => {
178        $(
179            $(#[$meta])*
180            ///
181            /// **Note:** This type is opaque due to having an undefined layout and thus
182            /// may only be accessed behind a reference.
183            pub struct $name {
184                _data: [u8; 0],
185                _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
186            }
187        )*
188    };
189}
190
191opaque_types! {
192    /// An enum within a volatility profile
193    ///
194    /// Can be obtained via the [`enum_from_name`] function.
195    VolatilityEnum,
196
197    /// A base/primitive type within a volatility profile
198    ///
199    /// Can be obtained via the [`base_type_from_name`] function.
200    VolatilityBaseType,
201
202    /// A global symbol declared within the loaded volatility profile
203    ///
204    /// Can be obtained via the [`symbol_from_name`] function.
205    VolatilitySymbol,
206
207    /// An opaque type representing the layout of a given type within the guest OS
208    ///
209    /// Can be obtained via the [`type_from_name`] function.
210    VolatilityStruct
211}
212
213impl VolatilitySymbol {
214    /// Get the address of the given symbol relative to the KASLR offset. Note that
215    /// additional calculations may be required afterwards to handle per-CPU structs.
216    pub fn addr(&self) -> target_ptr_t {
217        OSI2.addr_of_symbol(self)
218    }
219
220    /// Get the raw value of the given symbol. Note that additional calculations may be
221    /// required afterwards to handle per-CPU structs.
222    pub fn raw_value(&self) -> target_ptr_t {
223        OSI2.value_of_symbol(self)
224    }
225
226    /// Get the symbol name from the volatility structure if it can be found
227    pub fn name(&self) -> Option<String> {
228        let name_ptr = OSI2.name_of_symbol(self);
229
230        if name_ptr.is_null() {
231            return None;
232        }
233
234        let name = unsafe { CStr::from_ptr(name_ptr) }
235            .to_str()
236            .expect("Invalid volatility symbol name, invalid UTF-8")
237            .to_owned();
238
239        OSI2.free_cosi_str(name_ptr);
240
241        Some(name)
242    }
243}
244
245impl VolatilityStruct {
246    /// Get the size of the given type in bytes
247    pub fn size(&self) -> target_ulong {
248        OSI2.size_of_struct(self)
249    }
250
251    /// Get the offset of a given field within the structure given the name of the field
252    pub fn offset_of(&self, field: &str) -> target_long {
253        let field_name = CString::new(field).unwrap();
254
255        OSI2.offset_of_field(self, field_name.as_ptr())
256    }
257
258    /// Get the type of a given field within the structure given the name of the field
259    pub fn type_of(&self, field: &str) -> String {
260        let field_name = CString::new(field).unwrap();
261
262        let type_ptr = OSI2.type_of_field(self, field_name.as_ptr());
263
264        if type_ptr.is_null() {
265            panic!("Failed to get type of VolatilityStruct field");
266        }
267
268        let type_name = unsafe { CStr::from_ptr(type_ptr) }
269            .to_str()
270            .expect("Invalid volatility struct field type name, invalid UTF-8")
271            .to_owned();
272
273        OSI2.free_cosi_str(type_ptr);
274
275        type_name
276    }
277
278    /// Get the name of a the struct
279    ///
280    /// **Note:** this requires an O(n) reverse lookup and is not efficient. Limit
281    /// usage when possible.
282    pub fn name(&self) -> String {
283        let name_ptr = OSI2.name_of_struct(self);
284
285        if name_ptr.is_null() {
286            panic!("Failed to get name of VolatilityStruct");
287        }
288
289        let name = unsafe { CStr::from_ptr(name_ptr) }
290            .to_str()
291            .expect("Invalid volatility struct name, invalid UTF-8")
292            .to_owned();
293
294        OSI2.free_cosi_str(name_ptr);
295
296        name
297    }
298
299    /// Iterate over the fields of the given struct
300    pub fn fields(&self) -> VolatilityFieldIter<'_> {
301        VolatilityFieldIter(self, 0)
302    }
303}
304
305/// An iterator over the fields of a VolatilityStruct
306pub struct VolatilityFieldIter<'a>(&'a VolatilityStruct, usize);
307
308impl Iterator for VolatilityFieldIter<'_> {
309    type Item = (String, target_ptr_t);
310
311    fn next(&mut self) -> Option<(String, target_ptr_t)> {
312        let name_ptr = OSI2.get_field_by_index(self.0, self.1);
313
314        self.1 += 1;
315
316        if name_ptr.is_null() {
317            return None;
318        }
319
320        let offset = OSI2.offset_of_field(self.0, name_ptr);
321
322        let name = unsafe { CStr::from_ptr(name_ptr) }
323            .to_str()
324            .expect("Invalid volatility field name, invalid UTF-8")
325            .to_owned();
326
327        OSI2.free_cosi_str(name_ptr);
328
329        Some((name, offset as target_ptr_t))
330    }
331}
332
333impl VolatilityEnum {
334    /// Get the name of a the enum
335    ///
336    /// **Note:** this requires an O(n) reverse lookup and is not efficient. Limit
337    /// usage when possible.
338    pub fn name(&self) -> String {
339        let name_ptr = OSI2.name_of_enum(self);
340
341        if name_ptr.is_null() {
342            panic!("Failed to get name of VolatilityEnum");
343        }
344
345        let name = unsafe { CStr::from_ptr(name_ptr) }
346            .to_str()
347            .expect("Invalid volatility struct name, invalid UTF-8")
348            .to_owned();
349
350        OSI2.free_cosi_str(name_ptr);
351
352        name
353    }
354}
355
356impl VolatilityBaseType {
357    /// Get the name of a the base type
358    ///
359    /// **Note:** this requires an O(n) reverse lookup and is not efficient. Limit
360    /// usage when possible.
361    pub fn name(&self) -> String {
362        let name_ptr = OSI2.name_of_base_type(self);
363
364        if name_ptr.is_null() {
365            panic!("Failed to get name of VolatilityBaseType");
366        }
367
368        let name = unsafe { CStr::from_ptr(name_ptr) }
369            .to_str()
370            .expect("Invalid volatility struct name, invalid UTF-8")
371            .to_owned();
372
373        OSI2.free_cosi_str(name_ptr);
374
375        name
376    }
377
378    /// Get the size, in bytes, of the base type
379    pub fn size(&self) -> target_ptr_t {
380        OSI2.size_of_base_type(self)
381    }
382
383    /// Get whether the type is signed or unsigned
384    pub fn signed(&self) -> bool {
385        OSI2.is_base_type_signed(self)
386    }
387}
388
389/// Get a reference to an opaque object for accessing information about a given enum based
390/// on the volatility symbols currently loaded by OSI2
391pub fn enum_from_name(name: &str) -> Option<&'static VolatilityEnum> {
392    let name = CString::new(name).unwrap();
393
394    OSI2.enum_from_name(name.as_ptr())
395}
396
397/// Get a reference to an opaque object for accessing information about a given base type
398/// from the volatility symbols currently loaded by OSI2
399pub fn base_type_from_name(name: &str) -> Option<&'static VolatilityBaseType> {
400    let name = CString::new(name).unwrap();
401
402    OSI2.base_type_from_name(name.as_ptr())
403}
404
405/// Get a reference to an opaque object for accessing information about a given symbol
406/// present in the volatility symbols currently loaded by OSI2
407pub fn symbol_from_name(name: &str) -> Option<&'static VolatilitySymbol> {
408    let name = CString::new(name).unwrap();
409
410    OSI2.symbol_from_name(name.as_ptr())
411}
412
413/// Get a reference to an opaque object for accessing information about a given type
414/// present in the volatility symbols currently loaded by OSI2
415pub fn type_from_name(name: &str) -> Option<&'static VolatilityStruct> {
416    let name = CString::new(name).unwrap();
417
418    OSI2.type_from_name(name.as_ptr())
419}
420
421/// Get the symbol address of a type including the KASLR base offset from the volatility profile
422/// currently loaded by OSI2. This offset may need additional modification if it points
423/// to a per-CPU structure.
424pub fn symbol_addr_from_name(name: &str) -> target_ptr_t {
425    let name = CString::new(name).unwrap();
426
427    OSI2.symbol_addr_from_name(name.as_ptr())
428}
429
430/// Get the symbol address of a type, not including the KASLR base offset, from the volatility profile
431/// currently loaded by OSI2.
432pub fn symbol_value_from_name(name: &str) -> target_ptr_t {
433    let name = CString::new(name).unwrap();
434
435    OSI2.symbol_value_from_name(name.as_ptr())
436}
437
438/// Get the KASLR offset of the system, calculating and caching it if it has not already
439/// been found. For systems without KASLR this will be 0.
440pub fn kaslr_offset(cpu: &mut CPUState) -> target_ptr_t {
441    OSI2.kaslr_offset(cpu)
442}
443
444/// Get the current per-CPU offset for kernel data structures such as the current task
445/// struct
446pub fn current_cpu_offset(cpu: &mut CPUState) -> target_ulong {
447    OSI2.current_cpu_offset(cpu)
448}
449
450/// Get the address from a given symbol
451pub fn addr_of_symbol(symbol: &VolatilitySymbol) -> target_ptr_t {
452    OSI2.addr_of_symbol(symbol)
453}
454
455/// Get the offset of a field given the structure it is within and the name of the field
456pub fn offset_of_field(vol_struct: &VolatilityStruct, name: &str) -> target_long {
457    let name = CString::new(name).unwrap();
458
459    OSI2.offset_of_field(vol_struct, name.as_ptr())
460}
461
462/// Get the size of a given structure
463pub fn size_of_struct(vol_struct: &VolatilityStruct) -> target_ulong {
464    OSI2.size_of_struct(vol_struct)
465}
466
467/// Get the per-cpu address for a given symbol where the underlying type is stored
468pub fn find_per_cpu_address(
469    cpu: &mut CPUState,
470    symbol: &str,
471) -> Result<target_ptr_t, GuestReadFail> {
472    let symbol_offset = symbol_value_from_name(symbol);
473    let ptr_to_ptr = current_cpu_offset(cpu) + symbol_offset;
474
475    read_guest_type(cpu, ptr_to_ptr)
476}