Skip to main content

libinject/
lib.rs

1//! # Libinject
2//! A static library crate which is linked with inject.dll and winafl.dll. Note that libinject can
3//! only be compiled for a Windows target.
4//!
5//! Contains:
6//!
7//! - Rust bindings for the DynamoRIO C library (auto-generated from the C bindings using rust-bindgen).
8//! - A CLI interface for initiating DynamoRIO runs along a given trajectory of the Network Event Tree (NET).
9//! - Instrumentation to interpose on network API calls, using the parsed trajectory to decide how to respond to each network event (e.g. whether to accept or reject a connect call, and what responses to provide to a given request).
10//! - Instrumentation to collect additional, fine-grained coverage information when fuzzing.
11
12pub mod cli;
13pub mod cmp;
14pub mod connections;
15pub mod drcore;
16pub mod drwrap;
17pub mod ffi;
18pub mod fuzzer;
19pub mod instrument;
20pub mod network;
21pub mod pipe;
22pub mod socket;
23pub mod trajectory;
24pub mod utils;
25pub mod wrappers;
26
27use drcore::{get_proc_address, log, utf8_name_of_module};
28use enum_iterator::all;
29use ffi::{app_pc, module_data_t};
30use navigator::pipe_event::PipeEvent;
31use std::ffi::CStr;
32use std::os::raw::{c_char, c_void};
33use std::panic;
34use std::sync::{LazyLock, Mutex};
35use utils::Boolean;
36
37static MOD_BASE_ADDR: LazyLock<Mutex<Option<usize>>> = LazyLock::new(|| Mutex::new(None));
38static MOD_SIZE: LazyLock<Mutex<Option<usize>>> = LazyLock::new(|| Mutex::new(None));
39
40unsafe extern "C" {
41    pub static tls_idx: ::std::os::raw::c_int;
42}
43
44#[unsafe(no_mangle)]
45pub extern "C" fn libinject_init(id: ffi::client_id_t) {
46    // Override the panic hook to log the DynamoRio logger when a panic occurs.
47    panic::set_hook(Box::new(|panic_info| {
48        log(&format!("panic occurred: {panic_info}"));
49    }));
50
51    // Parse the cli args.
52    let args = unsafe { cli::parse(id) };
53    log(&format!("CLI args parsed as:\n {:?}", args));
54
55    // Initialise the trajectory module with the run config containing
56    // the serialised graph trajectory for this run.
57    if let Some(trajectory) = args.trajectory {
58        trajectory::init(trajectory);
59    } else if let Some(trajectory) = args.trajectory_file {
60        trajectory::init(trajectory);
61    }
62    // Initialise the pipe back to the parent process.
63    pipe::init(args.pipe);
64}
65
66#[unsafe(no_mangle)]
67pub extern "C" fn libinject_exit() {
68    network::dump(network::DumpFormat::CSV)
69        .expect("Failed to dump traffic captured on the spoofed network");
70}
71
72#[unsafe(no_mangle)]
73pub extern "C" fn libinject_warn(msg: *const c_char) {
74    unsafe {
75        if msg.is_null() {
76            pipe::send(&PipeEvent::Warning("Warning msg ptr is null!".to_string()));
77        } else {
78            if let Ok(s) = CStr::from_ptr(msg).to_str() {
79                pipe::send(&PipeEvent::Warning(s.to_string()));
80            } else {
81                pipe::send(&PipeEvent::Warning(
82                    "Warning msg is an invalid UTF-8 string!".to_string(),
83                ));
84            }
85        }
86    }
87}
88
89/// Called by DynamoRio when each C module is loaded.
90/// Each .dll will be a module, and the main binary itself will be comprised of one or more
91/// modules.
92#[unsafe(no_mangle)]
93pub extern "C" fn module_load_event(
94    _drctx: *mut c_void,
95    module_ptr: *const module_data_t,
96    _loaded: bool,
97) {
98    let module;
99    unsafe {
100        module = *module_ptr;
101    }
102    let mod_name = match utf8_name_of_module(module) {
103        Ok(name) => name,
104        Err(utf8_name_error) => {
105            log(&format!(
106                "[module load] failed to read the module name: {:?}",
107                utf8_name_error
108            ));
109            return;
110        }
111    };
112    log(&format!("[module load] loading {mod_name}"));
113
114    let target_mod_name = cli::get_args()
115        .expect(
116            "Args parsed in libinject_init, which will have been called before module_load_event",
117        )
118        .target_module
119        .to_lowercase();
120
121    if &mod_name.to_lowercase() == &target_mod_name {
122        let (mod_base_addr, mod_end, internal_size);
123        unsafe {
124            mod_base_addr = module.__bindgen_anon_1.start as app_pc;
125            mod_end = module.end as app_pc;
126            internal_size = module.module_internal_size as usize;
127        }
128        let computed_size =
129            internal_size.max((mod_end as usize).saturating_sub(mod_base_addr as usize));
130        log(&format!(
131            "[module load] TARGET_MODULE ({}) base=0x{:X} end=0x{:X} size=0x{:X}",
132            target_mod_name, mod_base_addr as usize, mod_end as usize, computed_size
133        ));
134        {
135            let mut base_lock = MOD_BASE_ADDR
136                .lock()
137                .expect("can't get lock on MOD_BASE_ADDR");
138            *base_lock = Some(mod_base_addr as usize);
139        }
140        {
141            let mut size_lock = MOD_SIZE.lock().expect("can't get lock on MOD_SIZE");
142            *size_lock = Some(computed_size);
143        }
144    }
145
146    if module.entry_point.is_null() {
147        log(
148            "[module load] module entry point is a null pointer, will not search for proc_addresses.",
149        );
150        return;
151    };
152
153    // Only hook functions in ws2_32.dll to avoid additionally hooking re-exported versions of
154    // the functinos in other modules.
155    if &mod_name.to_lowercase() == "ws2_32.dll" {
156        let mod_base_addr = module.entry_point as app_pc;
157        unsafe {
158            wrap_network_symbols(mod_base_addr, "ws2_32.dll");
159        }
160    };
161    return;
162}
163
164/// Helper function to wrap the network symbols defined (with  wrapper functions) in the
165/// `wrappers` module.
166unsafe fn wrap_network_symbols(mod_base_addr: app_pc, mod_name: &str) {
167    unsafe {
168        all::<wrappers::Symbols>().for_each(|symbol| {
169            if let Some(addr) = get_proc_address(mod_base_addr, symbol.to_str()) {
170                match ffi::drwrap_wrap(addr as *mut u8, Some(symbol.pre_fn()), symbol.post_fn())
171                    .as_bool()
172                {
173                    true => {
174                        log(&format!(
175                            "[module load] wrapped {} in module {mod_name} @ 0x{:?}",
176                            symbol.to_str(),
177                            addr
178                        ));
179                    }
180                    false => log(&format!(
181                        "[module load] failed to wrap {} in module {mod_name} @ 0x{:?}",
182                        symbol.to_str(),
183                        addr
184                    )),
185                }
186            };
187        });
188    }
189}
190
191#[unsafe(no_mangle)]
192pub unsafe extern "C" fn wrap_network_symbols_extern(
193    mod_base_addr: app_pc,
194    module_name: *const c_char,
195) {
196    unsafe {
197        if module_name.is_null() {
198            log("[wrap_network_symbols_extern] module_name is a null pointer!");
199            return;
200        }
201
202        let module_name_str = match CStr::from_ptr(module_name).to_str() {
203            Ok(s) => s,
204            Err(_) => return,
205        };
206        wrap_network_symbols(mod_base_addr, module_name_str);
207    }
208}
209
210#[unsafe(no_mangle)]
211pub unsafe extern "C" fn event_thread_init(drcontext: *mut c_void) {
212    log("[event_thread_init] new thread!");
213    let stack = Box::new(instrument::RawCallStack::new());
214
215    unsafe {
216        // Convert to raw pointer and store in TLS
217        ffi::drmgr_set_tls_field(drcontext, tls_idx, Box::into_raw(stack) as *mut c_void);
218    }
219}
220
221#[unsafe(no_mangle)]
222pub unsafe extern "C" fn event_thread_exit(drcontext: *mut c_void) {
223    unsafe {
224        let ptr = ffi::drmgr_get_tls_field(drcontext, tls_idx) as *mut instrument::RawCallStack;
225        drop(Box::from_raw(ptr)); // frees memory safely
226    }
227}