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}