Initial commit

This commit is contained in:
DB
2026-03-08 01:40:15 +05:30
commit 1c18c58b21
8 changed files with 356 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
[build]
target = "x86_64-pc-windows-gnu"
+1
View File
@@ -0,0 +1 @@
/target
Generated
+32
View File
@@ -0,0 +1,32 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "stackfinder"
version = "0.1.0"
dependencies = [
"winapi",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "stackfinder"
version = "0.1.0"
edition = "2024"
[dependencies]
winapi = { version = "0.3.9", features = [
"tlhelp32",
"processthreadsapi",
"handleapi",
"winnt",
"dbghelp",
"minwindef",
"basetsd",
"errhandlingapi",] }
+1
View File
@@ -0,0 +1 @@
# StackTracer
+93
View File
@@ -0,0 +1,93 @@
use std;
mod procenum;
mod trace;
use winapi::shared::minwindef::{FALSE, TRUE};
use winapi::um::dbghelp::{
SYMBOL_INFOW, SYMOPT_DEFERRED_LOADS, SYMOPT_UNDNAME, SymInitializeW, SymSetOptions,
};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::OpenProcess;
use winapi::um::winnt::HANDLE;
use winapi::um::winnt::{PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};
fn usage() {
println!("[+] Usage:\n\tstackfinder.exe PID <TID>\n");
println!("[+] Description:\n\tPrint the stack trace of a Thread of a given process\n");
println!(
"[+] Values:\n\tPID\tPID of process to target\n\tTID\t[Optional] TID of the target thread. If not specified,\n\t\tit will print the stack trace of all current threads."
);
std::process::exit(-1);
}
fn stacktrace(pid: u32, tid: u32) {
let target_tids = procenum::collect_threads(pid, tid);
unsafe {
// First open a handle to the thread
let h_process: HANDLE =
OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if h_process.is_null() {
eprintln!(
"[!] OpenProcess failed for PID {} (error {})",
pid,
GetLastError()
);
return;
}
println!("[+] Opened handle to Process:\t{:?}", h_process);
// Initialize symbols
SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
if SymInitializeW(h_process, std::ptr::null(), TRUE) == 0 {
eprintln!("[!] SymInitializeW failed (error {})", GetLastError());
CloseHandle(h_process);
return;
}
println!("[+] Initialized Symbols!");
for x in &target_tids {
trace::trace_thread_stack(h_process, *x);
}
if !h_process.is_null() {
CloseHandle(h_process);
}
}
}
fn main() {
let args: Vec<String> = std::env::args().collect();
if (args.len() < 2) || (args.len() > 3) {
usage();
}
let pid: u32;
let mut tid: u32 = 0;
pid = match args[1].parse() {
Ok(id) => id,
Err(_e) => {
eprintln!("[-] Provided PID is invalid: {:#?}", args[1]);
std::process::exit(-1);
}
};
println!("[+] Targeting PID:\t\t{}", pid);
if args.len() == 3 {
tid = match args[2].parse() {
Ok(id) => id,
Err(_e) => {
eprintln!("[-] Provided TID is invalid: {:#?}", args[1]);
std::process::exit(-1);
}
};
}
if pid == std::process::id() {
eprintln!("[-] DO NOT RUN THIS PROGRAM AGAINST THE CURRENT PROCESS");
std::process::exit(-1);
}
stacktrace(pid, tid);
}
+55
View File
@@ -0,0 +1,55 @@
use std;
use std::mem;
use winapi::um::tlhelp32::{
CreateToolhelp32Snapshot, Thread32First, Thread32Next,
THREADENTRY32, TH32CS_SNAPTHREAD,
};
use winapi::um::handleapi::CloseHandle;
use winapi::shared::minwindef::FALSE;
pub fn collect_threads(pid: u32, tid: u32) -> Vec<u32> {
let mut threads = Vec::new();
unsafe {
let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if snapshot.is_null() {
return threads;
}
let mut entry: THREADENTRY32 = mem::zeroed();
entry.dwSize = mem::size_of::<THREADENTRY32>() as u32;
if Thread32First(snapshot, &mut entry) == FALSE {
CloseHandle(snapshot);
return threads;
}
loop {
if entry.th32OwnerProcessID == pid {
if tid == 0 {
threads.push(entry.th32ThreadID);
}
else {
if tid == entry.th32ThreadID {
threads.push(tid);
CloseHandle(snapshot);
return threads;
}
}
}
if Thread32Next(snapshot, &mut entry) == FALSE {
break;
}
}
CloseHandle(snapshot);
}
if tid != 0 {
eprintln!("[-] Target thread not found!");
std::process::exit(-1);
}
threads
}
+157
View File
@@ -0,0 +1,157 @@
#![allow(unused_imports)]
use std::ffi::CStr;
use std::mem;
use std::path::Path;
use std::ptr;
use winapi::shared::minwindef::{DWORD, FALSE, TRUE};
use winapi::shared::ntdef::NULL;
use winapi::um::dbghelp::{
AddrModeFlat, IMAGEHLP_MODULEW64, STACKFRAME64, SYMBOL_INFOW, SYMOPT_DEFERRED_LOADS,
SYMOPT_UNDNAME, StackWalk64, SymCleanup, SymFromAddrW, SymFunctionTableAccess64,
SymGetModuleBase64, SymGetModuleInfoW64, SymInitializeW, SymSetOptions,
};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::handleapi::CloseHandle;
use winapi::um::processthreadsapi::{
GetThreadContext, OpenProcess, OpenThread, ResumeThread, SuspendThread,
};
use winapi::um::winnt::{
CONTEXT, CONTEXT_FULL, HANDLE, IMAGE_FILE_MACHINE_AMD64, PROCESS_QUERY_INFORMATION,
PROCESS_VM_READ, THREAD_GET_CONTEXT, THREAD_QUERY_INFORMATION, THREAD_SUSPEND_RESUME,
};
const MAX_SYM_NAME_LEN: usize = 512;
// ---------------------------------------------------------------------------
// Resolve a single PC address to "module.dll!Symbol+0xNN" / "module.dll+0xNN"
// ---------------------------------------------------------------------------
fn resolve_address(h_process: HANDLE, addr: u64) -> String {
unsafe {
// ── 1. Try to get the module that contains `addr` ──
let mut mod_info: IMAGEHLP_MODULEW64 = mem::zeroed();
mod_info.SizeOfStruct = mem::size_of::<IMAGEHLP_MODULEW64>() as DWORD;
let has_module = SymGetModuleInfoW64(h_process, addr, &mut mod_info) != 0;
// Extract just the file name from the wide-string image path
// e.g. "C:\Windows\System32\ntdll.dll" → "ntdll.dll"
let module_name = if has_module {
let raw = wide_to_string(&mod_info.ImageName);
Path::new(&raw)
.file_name()
.map(|f| f.to_string_lossy().into_owned())
.unwrap_or(raw)
} else {
String::new()
};
// ── 2. Try to resolve to a symbol via SymFromAddrW ──
// SYMBOL_INFOW has a trailing `Name[1]` flexible-array member;
// we over-allocate to hold up to MAX_SYM_NAME_LEN wide characters.
let buf_size = mem::size_of::<SYMBOL_INFOW>() + MAX_SYM_NAME_LEN * mem::size_of::<u16>();
let mut buf = vec![0u8; buf_size];
let sym = buf.as_mut_ptr() as *mut SYMBOL_INFOW;
(*sym).SizeOfStruct = mem::size_of::<SYMBOL_INFOW>() as u32;
(*sym).MaxNameLen = MAX_SYM_NAME_LEN as u32;
let mut sym_displacement: u64 = 0;
if SymFromAddrW(h_process, addr, &mut sym_displacement, sym) != 0 {
// We have a symbol name (wide)
let name_slice =
std::slice::from_raw_parts((*sym).Name.as_ptr(), (*sym).NameLen as usize);
let sym_name = String::from_utf16_lossy(name_slice);
if module_name.is_empty() {
format!("{sym_name}+0x{sym_displacement:x}")
} else {
format!("{module_name}!{sym_name}+0x{sym_displacement:x}")
}
} else if has_module {
// No exported/debug symbol fall back to module-relative offset
let offset = addr.wrapping_sub(mod_info.BaseOfImage);
format!("{module_name}+0x{offset:x}")
} else {
// Unknown region raw address
format!("0x{addr:x}")
}
}
}
/// Convert a null-terminated wide-char (`u16`) buffer to a Rust `String`.
fn wide_to_string(buf: &[u16]) -> String {
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
String::from_utf16_lossy(&buf[..len])
}
pub fn trace_thread_stack(h_process: HANDLE, tid: u32) {
let h_thread: HANDLE;
println!("[+] Targeting TID:\t\t{}", tid);
unsafe {
h_thread = OpenThread(
THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION,
FALSE,
tid,
);
if h_thread.is_null() {
eprintln!(
"[-] OpenThread failed for TID {} (error {})",
tid,
winapi::um::errhandlingapi::GetLastError()
);
return;
}
println!("[+] Opened handle to Thread:\t{:?}", h_thread);
loop {
let mut ctx: CONTEXT = mem::zeroed();
ctx.ContextFlags = CONTEXT_FULL;
if GetThreadContext(h_thread, &mut ctx) == 0 {
eprintln!("[!] GetThreadContext failed (error {})", GetLastError());
break;
}
println!("[+] Fetched thread context");
let mut frame: STACKFRAME64 = mem::zeroed();
frame.AddrPC.Offset = ctx.Rip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = ctx.Rbp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = ctx.Rsp;
frame.AddrStack.Mode = AddrModeFlat;
let mut entries: Vec<String> = Vec::new();
// ── Walk the stack ──
loop {
let ok = StackWalk64(
IMAGE_FILE_MACHINE_AMD64 as DWORD,
h_process,
h_thread as HANDLE,
&mut frame,
&mut ctx as *mut CONTEXT as *mut _,
None, // ReadMemoryRoutine
Some(SymFunctionTableAccess64), // FunctionTableAccessRoutine
Some(SymGetModuleBase64), // GetModuleBaseRoutine
None, // TranslateAddress
);
if ok == 0 || frame.AddrPC.Offset == 0 {
break;
}
entries.push(resolve_address(h_process, frame.AddrPC.Offset));
}
// ── Output ──
if entries.is_empty() {
println!("(no frames captured)");
} else {
println!("{}", entries.join(","));
}
break;
}
// Initialize DbgHelp symbol handler
// let opts = SymGetOptions();
if !h_thread.is_null() {
CloseHandle(h_thread);
}
}
}