coreminer 0.5.2

A debugger which can be used to debug programs that do not want to be debugged
Documentation
//! # SIGTRAP Guard Plugin
//!
//! A plugin that prevents detection of the debugger by programs that use
//! `SIGTRAP` signals and register a signal handler for them for anti-debugging techniques.
//!
//! ## Overview
//!
//! Some programs implement anti-debugging techniques by inserting `int3` instructions
//! (which generate SIGTRAP signals) into their own code and registering signal handlers
//! for them. They can then detect if they're being debugged by checking if their signal
//! handler was called - if it wasn't, a debugger likely intercepted the signal.
//!
//! This plugin detects whether a SIGTRAP signal was generated by a debugger breakpoint
//! or by the debuggee itself. If the signal originated from the debuggee, the plugin
//! forwards the signal to the debuggee's own signal handler, making the debugger
//! transparent to these detection techniques.
//!
//! ## How It Works
//!
//! When a `SIGTRAP` is received, the plugin:
//! 1. Checks if there's a breakpoint at the current instruction pointer
//! 2. If there is a breakpoint, lets the debugger handle it normally
//! 3. If there isn't a breakpoint, forwards the `SIGTRAP` to the debuggee
//!
//! ## Examples
//!
//! The plugin is automatically loaded and enabled by the default plugin manager:
//!
//! ```no_run
//! use coreminer::plugins::default_plugin_manager;
//!
//! // Create a plugin manager with the SIGTRAP guard enabled
//! let plugin_manager = default_plugin_manager();
//! ```
//!
//! For a program using anti-debugging techniques:
//!
//! ```c
//! #include <signal.h>
//! #include <stdio.h>
//! #include <unistd.h>
//!
//! static int g_sigterm_selftrigger = 0;
//!
//! void handle_sigtrap(int signum) {
//!   if (signum == SIGTRAP) {
//!     g_sigterm_selftrigger++;
//!   }
//! }
//!
//! int main(int argc, char **argv) {
//!
//!   signal(SIGTRAP, handle_sigtrap);
//!
//!   __asm__("int3;");
//!
//!   if (g_sigterm_selftrigger != 1) {
//!     fprintf(stderr, "NONONO EVIL DEBUGGER DETECTED\n");
//!     return 1;
//!   } else {
//!     printf("OK :) No evil debugger.\n");
//!   }
//! }
//! ```
//!
//! (See `examples/sigtrap_self.c`)
//!
//! With the SIGTRAP guard plugin enabled, the debuggee's handler will be called
//! and the anti-debugging check will be bypassed.

use nix::sys::signal::Signal::SIGTRAP;
use steckrs::simple_plugin;
use tracing::{info, trace, warn};

use crate::addr::Addr;
use crate::breakpoint::Breakpoint;
use crate::feedback::{Feedback, Status};

use super::extension_points::{EPreSigtrap, EPreSigtrapF};

simple_plugin!(
    /// This plugin detects if a `SIGTRAP` comes from the debugger or the debuggee.
    ///
    /// The plugin distinguishes between `SIGTRAP` signals that are generated by debugger
    /// breakpoints (which should be handled by the debugger) and `SIGTRAP`s that
    /// are generated by the debuggee itself (which should be forwarded to the
    /// debuggee's signal handler).
    ///
    /// This functionality prevents the detection of the debugger by programs that
    /// insert `int3` instructions into their own code, register a signal handler,
    /// and check if their handler was executed. By forwarding legitimate `SIGTRAP`s
    /// to the debuggee, the debugger becomes transparent to these detection mechanisms.
    SigtrapGuardPlugin,
    "sigtrap_guard",
    "Handles programs that use int3 on their own and register their own signal handler for SIGTRAP",
    hooks: [(EPreSigtrap, SigtrapInjectionGuard::default())]
);

/// The hook implementation of the `SIGTRAP` guard extension point
///
/// This hook tracks the instruction pointer and determines whether a `SIGTRAP`
/// was generated by a debugger breakpoint or by the debuggee itself.
///
/// # Fields
///
/// * `rip` - The current instruction pointer address, cached to avoid repeated lookups
/// * `bp` - The breakpoint at the current instruction pointer, if any
#[derive(Default)]
struct SigtrapInjectionGuard {
    /// Cached instruction pointer address
    rip: Option<Addr>,
    /// Breakpoint at the current instruction pointer, if any
    ///
    /// This is a nested Option because we need to distinguish between:
    /// - `None`: We haven't checked for a breakpoint yet
    /// - `Some(None)`: We checked and there is no breakpoint
    /// - `Some(Some(bp))`: We checked and found a breakpoint
    #[allow(clippy::option_option)]
    bp: Option<Option<Breakpoint>>,
}

impl EPreSigtrapF for SigtrapInjectionGuard {
    /// Processes a SIGTRAP signal to determine its origin and whether it should be forwarded
    ///
    /// This function is repeatedly called by the debugger when a SIGTRAP signal is received. It
    /// determines whether the signal was generated by a debugger breakpoint or by the
    /// debuggee itself, and returns an appropriate status to the debugger.
    ///
    /// # Parameters
    ///
    /// * `feedback` - Current feedback from the debugger
    /// * `siginfo` - Signal information structure from the operating system
    /// * `sig` - The signal type that was received
    ///
    /// # Returns
    ///
    /// A tuple containing:
    /// * The next status for the debugger
    /// * A boolean indicating whether to return early from signal handling
    ///
    /// # Errors
    ///
    /// This function will return any errors propagated from the debugger operations.
    ///
    /// # State Machine
    ///
    /// This function implements a small state machine:
    /// 1. If not a SIGTRAP, return immediately
    /// 2. If we don't have the instruction pointer, request it with `Status::DumpRegisters`
    /// 3. If we don't have breakpoint information, request it with `Status::GetBreakpoint`
    /// 4. If a breakpoint exists at the location, let the debugger handle it normally
    /// 5. If no breakpoint exists, forward the signal to the debuggee
    fn pre_handle_sigtrap(
        &mut self,
        feedback: &crate::feedback::Feedback,
        siginfo: &nix::libc::siginfo_t,
        sig: &nix::sys::signal::Signal,
    ) -> crate::errors::Result<(crate::feedback::Status, bool)> {
        if *sig != SIGTRAP {
            return Ok((Status::PluginContinue, false));
        }

        let rip = match (feedback, self.rip) {
            (_, Some(addr)) => {
                trace!("using stored rip");
                addr
            }
            (crate::feedback::Feedback::Registers(regs), None) => {
                self.rip = Some(regs.rip.into());
                self.rip.unwrap()
            }
            (_, _) => return Ok((Status::DumpRegisters, false)),
        };

        let maybe_bp: Option<&Breakpoint> = match (feedback, &self.bp) {
            (_, Some(bp)) => {
                trace!("using stored bp");
                bp.as_ref()
            }
            (crate::feedback::Feedback::Breakpoint(bp), None) => {
                self.bp = Some(bp.clone());
                bp.as_ref()
            }
            _ => {
                return Ok((
                    Status::GetBreakpoint(
                        rip - 1, /* need to check the addr that was last executed, not the one that would get executed next */
                    ),
                    false,
                ));
            }
        };

        if let Some(bp) = maybe_bp {
            info!("It's just a regular breakpoint: {bp:?}");
            Ok((Status::PluginContinue, false))
        } else if matches!(feedback, Feedback::Ok) {
            // we're done
            Ok((Status::PluginContinue, true))
        } else {
            warn!("The debugger stopped with SIGTRAP, but there is no breakpoint there!");
            warn!("This is likely a self inserted interrupt by the debuggee program!");
            warn!("Forwarding the SIGTRAP to the debuggee");
            Ok((Status::SetLastSignal(siginfo.si_signo), false))
        }
    }
}