Skip to content

Stalker bugs #214

@ghost

Description

I've found some bugs, not sure if they are caused by the skill issues of mine, the incorrect usage of bindings, or the frida itself

Writing to stdout in the callback passed to the Transformer::from_callback causes the program to panic:

thread '<unnamed>' panicked at library/std/src/io/stdio.rs:860:20:
RefCell already borrowed
stack backtrace:
   0: __rustc::rust_begin_unwind
             at /rustc/9748d87dc70a9a6725c5dbd76ce29d04752b4f90/library/std/src/panicking.rs:697:5
   1: core::panicking::panic_fmt
             at /rustc/9748d87dc70a9a6725c5dbd76ce29d04752b4f90/library/core/src/panicking.rs:75:14
   2: core::cell::panic_already_borrowed::do_panic::runtime
             at /rustc/9748d87dc70a9a6725c5dbd76ce29d04752b4f90/library/core/src/panic.rs:218:21
   3: core::cell::panic_already_borrowed::do_panic
             at /rustc/9748d87dc70a9a6725c5dbd76ce29d04752b4f90/library/core/src/intrinsics/mod.rs:2367:9
   4: core::cell::panic_already_borrowed
             at /rustc/9748d87dc70a9a6725c5dbd76ce29d04752b4f90/library/core/src/panic.rs:223:9
   5: core::cell::RefCell<T>::borrow_mut
             at /rustc/9748d87dc70a9a6725c5dbd76ce29d04752b4f90/library/core/src/cell.rs:1089:25
   6: <std::io::stdio::StdoutLock as std::io::Write>::write_all
             at /rustc/9748d87dc70a9a6725c5dbd76ce29d04752b4f90/library/std/src/io/stdio.rs:860:20
   7: <std::io::default_write_fmt::Adapter<T> as core::fmt::Write>::write_str
             at /rustc/9748d87dc70a9a6725c5dbd76ce29d04752b4f90/library/std/src/io/mod.rs:628:30
   8: core::fmt::write
             at /rustc/9748d87dc70a9a6725c5dbd76ce29d04752b4f90/library/core/src/fmt/mod.rs:1493:23
   ...

When attaching to a thread by id, instr.put_callout will make the program hang indefinitely, probably a segfault/panic; when attaching to the current thread from the interceptor callback, everything works fine (why does interceptor.attach accept a &mut ref, and not a box or something like this? This leads to UB if the value is dropped)

use std::sync::LazyLock;

use ctor::ctor;
use frida_gum::interceptor::{Interceptor, InvocationContext, InvocationListener};
use frida_gum::stalker::{Event, EventMask, EventSink, Stalker, Transformer};
use frida_gum::{Gum, Process};

static GUM: LazyLock<Gum> = LazyLock::new(Gum::obtain);

struct Sink;

impl EventSink for Sink {
    fn query_mask(&mut self) -> EventMask {
        EventMask::None
    }

    fn start(&mut self) {}

    fn process(&mut self, _event: &Event) {}

    fn flush(&mut self) {}

    fn stop(&mut self) {}
}

struct WriteListener {
    followed: bool,
}

impl InvocationListener for WriteListener {
    fn on_enter(&mut self, _context: InvocationContext) {
        if !self.followed {
            self.followed = true;
            println!("Following self...");
            let transformer = Transformer::from_callback(&GUM, |block, _| {
                for instr in block {
                    let ix = instr.instr();
                    let data = ix.bytes();

                    // syscall
                    if data == [0x0f, 0x05] {
                        // causes a panic
                        println!("damn, the program panicked");
                    }

                    instr.keep();
                }
            });
            let mut stalker = Stalker::new(&GUM);
            stalker.follow_me(&transformer, None::<&mut Sink>);
        }

        println!("entered");
    }

    fn on_leave(&mut self, _context: InvocationContext) {
        println!("left");
    }
}

#[ctor]
fn entry() {
    let process = Process::obtain(&GUM);
    let module = process.find_module_by_name("libc.so.6").unwrap();
    let write = module.find_export_by_name("write").unwrap();
    let mut interceptor = Box::leak(Box::new(Interceptor::obtain(&GUM)));

    // Follow a single thread once
    interceptor.attach(
        write,
        Box::leak(Box::new(WriteListener { followed: false })),
    );
}
use std::collections::HashSet;
use std::sync::{LazyLock, Mutex};
use std::thread;
use std::time::Duration;

use ctor::ctor;
use frida_gum::stalker::{Event, EventMask, EventSink, Stalker, Transformer};
use frida_gum::{Gum, Process};

static GUM: LazyLock<Gum> = LazyLock::new(Gum::obtain);

struct SampleEventSink;

impl EventSink for SampleEventSink {
    fn query_mask(&mut self) -> EventMask {
        EventMask::Exec
    }

    fn start(&mut self) {}

    fn process(&mut self, _event: &Event) {}

    fn flush(&mut self) {}

    fn stop(&mut self) {}
}

fn follow_thread(thread_id: usize) {
    let mut stalker = Stalker::new(&GUM);

    let transformer = Transformer::from_callback(&GUM, |basic_block, _output| {
        for instr in basic_block {
            // Uncommenting any of these makes the process hang
            // instr.put_callout(|_| {});

            instr.keep();

            // Uncommenting any of these makes the process hang
            // instr.put_callout(|_| {});
        }
    });

    stalker.follow(thread_id, &transformer, None::<&mut SampleEventSink>);
}

// a static is not needed here as entry() is called only one time
static FOLLOWED: LazyLock<Mutex<HashSet<usize>>> = LazyLock::new(Mutex::default);

fn entry() {
    println!("entry");

    let process = Process::obtain(&GUM);

    let mut followed = FOLLOWED.lock().unwrap();

    let this_thread_id = process.current_thread_id();
    followed.insert(process.current_thread_id() as usize); // do not follow the thread that was created for following

    // uses enumerate_threads() from PR #213, but I'm pretty sure the implementation returns the correct thread ids
    for thread in process.enumerate_threads() {
        let thread_id = thread.id() as usize;

        if !followed.contains(&thread_id) {
            println!("following {thread_id}; this thread id: {this_thread_id}");
            followed.insert(thread_id);

            // thread::spawn(move || follow_thread(thread_id));
            follow_thread(thread_id)
        }
    }
}

#[ctor]
fn main() {
    thread::spawn(entry);

    // wait enough time for the entry function to run
    thread::sleep(Duration::from_millis(100));
}

injection done using the LD_PRELOAD

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions