Skip to content

Commit

Permalink
Add pthread_introspection_hook support on macos
Browse files Browse the repository at this point in the history
See-also: #68
  • Loading branch information
fabianfreyer committed Aug 14, 2021
1 parent f8aec32 commit f9b8626
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 0 deletions.
4 changes: 4 additions & 0 deletions libafl_frida/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ backtrace = { version = "0.3.58", default-features = false, features = ["std", "
num-traits = "0.2.14"
ahash = "0.7"
paste = "1.0"
lazy_static = "1.4"

[dev-dependencies]
serial_test = "*"
4 changes: 4 additions & 0 deletions libafl_frida/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ pub mod asan_errors;
/// The frida address sanitizer runtime
pub mod asan_rt;

/// Hooking thread lifecycle events. Seems like this is apple-only for now.
#[cfg(target_os = "macos")]
pub mod pthread_hook;

#[cfg(feature = "cmplog")]
/// The frida cmplog runtime
pub mod cmplog_rt;
Expand Down
231 changes: 231 additions & 0 deletions libafl_frida/src/pthread_hook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/// Reference: https://opensource.apple.com/source/libpthread/libpthread-218.20.1/pthread/introspection.h.auto.html

use lazy_static::lazy_static;
use libc;
use std::cell::UnsafeCell;
use std::convert::{TryFrom, TryInto};
use std::sync::RwLock;

const PTHREAD_INTROSPECTION_THREAD_CREATE: libc::c_uint = 1;
const PTHREAD_INTROSPECTION_THREAD_START: libc::c_uint = 2;
const PTHREAD_INTROSPECTION_THREAD_TERMINATE: libc::c_uint = 3;
const PTHREAD_INTROSPECTION_THREAD_DESTROY: libc::c_uint = 4;

#[allow(non_camel_case_types)]
pub type pthread_introspection_hook_t = extern "C" fn(
event: libc::c_uint,
thread: libc::pthread_t,
addr: *const libc::c_void,
size: libc::size_t,
);

extern "C" {
fn pthread_introspection_hook_install(
hook: *const libc::c_void,
) -> pthread_introspection_hook_t;
}

struct PreviousHook(UnsafeCell<Option<pthread_introspection_hook_t>>);

impl PreviousHook {
/// Dispatch to the previous hook, if it is set.
pub fn dispatch(
&self,
event: libc::c_uint,
thread: libc::pthread_t,
addr: *const libc::c_void,
size: libc::size_t,
) {
let inner = unsafe { *self.0.get() };
if inner.is_none() {
return;
}
let inner = inner.unwrap();
inner(event, thread, addr, size);
}

/// Set the previous hook.
pub fn set(&self, hook: pthread_introspection_hook_t) {
unsafe { *self.0.get() = Some(hook) };
}

/// Ensure the previous hook is installed again.
pub fn reset(&self) {
let inner = unsafe { *self.0.get() };
if inner.is_none() {
unsafe { pthread_introspection_hook_install(std::ptr::null()) };
return;
}
let inner = inner.unwrap();
unsafe { *self.0.get() = None };
unsafe { pthread_introspection_hook_install(inner as *const libc::c_void) };
}
}

// At the time where the inner is called, it will have been set.
// Mark it as sync.
unsafe impl Sync for PreviousHook {}

#[allow(non_upper_case_globals)]
static PREVIOUS_HOOK: PreviousHook = PreviousHook(UnsafeCell::new(None));

lazy_static! {
static ref CURRENT_HOOK: RwLock<Option<PthreadIntrospectionHook>> = RwLock::new(None);
}

extern "C" fn pthread_introspection_hook(
event: libc::c_uint,
thread: libc::pthread_t,
addr: *const libc::c_void,
size: libc::size_t,
) {
if let Some(ref hook) = *CURRENT_HOOK.read().unwrap() {
hook(event.try_into().unwrap(), thread, addr, size);
}
PREVIOUS_HOOK.dispatch(event, thread, addr, size);
}

pub type PthreadIntrospectionHook =
Box<dyn Fn(EventType, libc::pthread_t, *const libc::c_void, libc::size_t) + Sync + Send>;

#[derive(Debug, PartialEq, Eq)]
pub enum EventType {
Create,
Start,
Terminate,
Destroy,
}

impl TryFrom<libc::c_uint> for EventType {
type Error = ();

fn try_from(value: libc::c_uint) -> Result<Self, Self::Error> {
match value {
PTHREAD_INTROSPECTION_THREAD_CREATE => Ok(Self::Create),
PTHREAD_INTROSPECTION_THREAD_START => Ok(Self::Start),
PTHREAD_INTROSPECTION_THREAD_TERMINATE => Ok(Self::Terminate),
PTHREAD_INTROSPECTION_THREAD_DESTROY => Ok(Self::Destroy),
_ => Err(()),
}
}
}

impl std::convert::From<EventType> for libc::c_uint {
fn from(event: EventType) -> Self {
match event {
EventType::Create => PTHREAD_INTROSPECTION_THREAD_CREATE,
EventType::Start => PTHREAD_INTROSPECTION_THREAD_START,
EventType::Terminate => PTHREAD_INTROSPECTION_THREAD_TERMINATE,
EventType::Destroy => PTHREAD_INTROSPECTION_THREAD_DESTROY,
}
}
}

pub fn install<H>(hook: H)
where
H: Fn(EventType, libc::pthread_t, *const libc::c_void, libc::size_t) + Send + Sync + 'static,
{
let mut new_hook = CURRENT_HOOK.write().unwrap();
*new_hook = Some(Box::new(hook));

let prev = unsafe {
pthread_introspection_hook_install(pthread_introspection_hook as *const libc::c_void)
};
if !(prev as *const libc::c_void).is_null() && prev != pthread_introspection_hook {
PREVIOUS_HOOK.set(prev);
}
}

pub fn reset() {
PREVIOUS_HOOK.reset();
}

/// The following tests fail if they are not run sequentially.
#[cfg(test)]
mod test {
use serial_test::serial;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

#[test]
#[serial]
fn test_nohook_thread_create() {
let triggered: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));

thread::spawn(|| {
thread::sleep(Duration::from_millis(1));
});
thread::sleep(Duration::from_millis(50));

super::reset();
assert!(*triggered.lock().unwrap() == false);
}

#[test]
#[serial]
fn test_hook_thread_create() {
let triggered: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));

let inner_triggered = triggered.clone();
super::install(move |event, _, _, _| {
if event == super::EventType::Create {
let mut triggered = inner_triggered.lock().unwrap();
*triggered = true;
}
});

thread::spawn(|| {
thread::sleep(Duration::from_millis(1));
});
thread::sleep(Duration::from_millis(50));

super::reset();
assert!(*triggered.lock().unwrap() == true);
}

#[test]
#[serial]
fn test_hook_thread_start() {
let triggered: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));

let inner_triggered = triggered.clone();
super::install(move |event, _, _, _| {
if event == super::EventType::Start {
let mut triggered = inner_triggered.lock().unwrap();
*triggered = true;
}
});

thread::spawn(|| {
thread::sleep(Duration::from_millis(1));
});
thread::sleep(Duration::from_millis(50));

super::reset();
assert!(*triggered.lock().unwrap() == true);
}

#[test]
#[serial]
fn test_hook_reset() {
let triggered: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));

let inner_triggered = triggered.clone();
super::install(move |event, _, _, _| {
if event == super::EventType::Start {
let mut triggered = inner_triggered.lock().unwrap();
*triggered = true;
}
});

super::reset();

thread::spawn(|| {
thread::sleep(Duration::from_millis(1));
});
thread::sleep(Duration::from_millis(50));

assert!(*triggered.lock().unwrap() == false);
}
}

0 comments on commit f9b8626

Please sign in to comment.