Skip to content

zorks56/WinTrace

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WinTrace banner

WinTrace — Windows User-Mode API Tracer

A transparent debugger that intercepts Windows API calls and reports exact call sites, RVA offsets, decoded parameters, and disassembly context.

Python Platform License

For legitimate debugging, reverse engineering, and security research only. Use exclusively on processes you own or have explicit written authorization to analyze.


What it does

WinTrace attaches to any Windows process (or launches one under its control) and silently intercepts calls to monitored APIs using software breakpoints (INT3). For every hit it reports:

Field Description
API DLL + function name (user32.dll!MessageBoxW)
RVA / Offset Exact offset of the CALL instruction inside the caller module — paste directly into IDA Pro, Ghidra, x64dbg
Call site address Absolute virtual address of the CALL instruction
Return address Instruction that executes after the call returns
Parameters Decoded from registers / stack (strings, handles, integers)
Disassembly ±10 instructions around the call site
Stack trace Return-address chain
PID / TID Process and thread identifiers

Supports both x86 and x64 targets from a single 64-bit Python host.


Installation

Requirements

  • Windows 10 / 11
  • Python 3.8+ 64-bitdownload
  • Administrator privileges for --attach mode

Step 1 — Clone

git clone https://github.com/zorks56/WinTrace.git
cd WinTrace

Step 2 — Install Python packages

pip install -r requirements.txt
Package Version Purpose
capstone ≥ 4.0.2 x86 / x64 disassembly engine
pywin32 ≥ 305 Windows API types and constants
pefile ≥ 2023.2.7 PE file parsing
colorama ≥ 0.4.6 Colored console output
pyyaml ≥ 6.0 YAML config file support

Usage

GUI (recommended)

python gui.py

The dark-themed interface provides:

  • Target panel — browse for EXE or enter PID to attach
  • Config panel — select API watchlist, optional text/JSON log paths
  • Filters — restrict by module name or API name
  • Options — verbosity, break-on-hit, trace-only mode
  • Output Log tab — real-time colored event stream
  • API Stats tab — hit frequency table, sorted by count

CLI

# Launch a target executable under the debugger
python main.py --launch "C:\path\to\target.exe"

# Attach to an already-running process by PID
python main.py --attach 4321

# Use a custom API config + save output to a text log
python main.py --launch target.exe --config myapis.json --log trace.txt

# Full verbosity: show disassembly and parameter details
python main.py --launch target.exe -vv

# Pause at every API hit (press Enter to continue)
python main.py --launch target.exe --break-on-hit

# Intercept specific APIs only
python main.py --launch target.exe --filter-api MessageBoxW,LoadLibraryA

# Intercept calls from a specific module only
python main.py --launch target.exe --filter-module target.exe

# Stop after the very first hit
python main.py --launch target.exe --first-hit

# Export events as JSON
python main.py --launch target.exe --json trace.json

# List all APIs in the current config and exit
python main.py --list-apis

All CLI options

Flag Default Description
--launch EXE Path to executable to launch under debugger
--attach PID PID of running process to attach to
--args "..." "" Command-line arguments passed to launched process
--config FILE config/api_watch.json API watchlist JSON or YAML file
--log FILE Save formatted text log to file
--json FILE Save structured JSON log to file
-v / -vv normal Verbosity: -v shows params, -vv adds disasm + stack
--quiet off Minimal output (API name + call site only)
--break-on-hit off Pause at every hit, resume with Enter
--trace-only off Do not re-arm breakpoints after first hit per API
--first-hit off Stop the entire trace after the first hit
--filter-module all Comma-separated list of caller modules to include
--filter-api all Comma-separated list of API names to include
--list-apis Print watchlist from config and exit

Sample output

────────────────────────────────────────────────────────────────────────
[14:22:31.447]  PID=7312  TID=7316
API       : user32.dll!MessageBoxW
API_ADDR  : 0x00007ffa8a1c3e40
CALL_SITE : 0x00007ff6ab123456
RET_ADDR  : 0x00007ff6ab12345b
MODULE    : target.exe
RVA/OFFSET: 0x00023456
ARGS:
  hWnd      = 0x0
  lpText    = L"Enter your license key"
  lpCaption = L"Activation"
  uType     = 1
DISASM:
     0x00007ff6ab12343a:  mov      rcx, 0x0
     0x00007ff6ab123441:  lea      rdx, [rip+0x12ef8]
     0x00007ff6ab123448:  lea      r8, [rip+0x12f01]
     0x00007ff6ab12344f:  mov      r9d, 0x1
 >>> 0x00007ff6ab123453:  call     qword ptr [rip+0x4a3b7]
     0x00007ff6ab12345b:  test     eax, eax
     0x00007ff6ab12345d:  je       0x7ff6ab123471

RVA 0x00023456 → open target.exe in IDA Pro or Ghidra and jump to offset 0x23456 to land exactly on the calling code.


Custom API watchlist

Edit or create a JSON file to control which APIs are monitored and which parameters are decoded:

{
  "apis": [
    {
      "module": "user32.dll",
      "name": "MessageBoxW",
      "params": [
        {"name": "hWnd",      "type": "HWND"},
        {"name": "lpText",    "type": "LPCWSTR"},
        {"name": "lpCaption", "type": "LPCWSTR"},
        {"name": "uType",     "type": "UINT"}
      ]
    },
    {
      "module": "kernel32.dll",
      "name": "CreateFileW",
      "params": [
        {"name": "lpFileName",            "type": "LPCWSTR"},
        {"name": "dwDesiredAccess",       "type": "DWORD"},
        {"name": "dwShareMode",           "type": "DWORD"},
        {"name": "lpSecurityAttributes",  "type": "HANDLE"},
        {"name": "dwCreationDisposition", "type": "DWORD"},
        {"name": "dwFlagsAndAttributes",  "type": "DWORD"},
        {"name": "hTemplateFile",         "type": "HANDLE"}
      ]
    }
  ]
}

Supported parameter types

Type Display
LPCWSTR / LPWSTR Unicode string read from pointer (L"...")
LPCSTR / LPSTR ANSI string read from pointer
HANDLE / HWND / HMODULE Hex value
DWORD / UINT / INT Decimal value

Pass the config with --config myapis.json or select it in the GUI.


Architecture

WinTrace/
├── main.py                     ← CLI entry point (argparse)
├── gui.py                      ← Dark-themed tkinter GUI
├── requirements.txt
├── assets/
│   └── banner.png
├── config/
│   └── api_watch.json          ← Default API watchlist (25 APIs)
├── debugger/
│   ├── core.py                 ← WaitForDebugEvent loop, event dispatch
│   ├── breakpoint_manager.py   ← INT3 write/restore, single-step re-arm
│   └── context.py              ← x86 / x64 / WOW64 CONTEXT structures
├── modules/
│   └── resolver.py             ← Module base tracking, RVA calculation
├── api_watch/
│   └── manager.py              ← Watchlist loader (JSON / YAML)
├── disasm/
│   └── engine.py               ← Capstone wrapper, backward CALL scan
└── logger/
    └── log.py                  ← Console (colored) + text + JSON output

Component responsibilities

Component Responsibility
debugger/core.py WaitForDebugEvent loop; dispatches create/exit/load/exception events
debugger/breakpoint_manager.py Writes INT3, saves original byte, single-steps to restore, re-arms
debugger/context.py Full x64 CONTEXT (1232 bytes, 16-byte aligned), x86 CONTEXT, WOW64
modules/resolver.py Maintains base→name map; computes call_site − module_base
api_watch/manager.py Loads JSON/YAML watchlist; maps addresses to ApiDef objects
disasm/engine.py Backward scan from return address (Δ 2,3,5,6,7); validates with Capstone
logger/log.py ApiHitEvent dataclass; text + JSON file output; colored console
gui.py tkinter GUI; background debug thread; queue.Queue for thread-safe updates

How call site RVA is calculated

1. Breakpoint placement

At LOAD_DLL_DEBUG_EVENT, for each watched API:

rva_in_our_process  = GetProcAddress(dll) - GetModuleHandle(dll)
api_addr_in_target  = target_dll_base + rva_in_our_process

WinTrace writes 0xCC (INT3) at api_addr_in_target.

2. Breakpoint fires

EXCEPTION_BREAKPOINT triggers with ExceptionAddress = api_addr + 1 (CPU steps past INT3).

3. Return address from stack

x64:  ret_addr = ReadProcessMemory(RSP, 8 bytes)
x86:  ret_addr = ReadProcessMemory(ESP, 4 bytes)

4. Backward scan for CALL instruction

ret_addr is the instruction after the CALL. Candidates:

Delta Form
−2 CALL reg (FF D0FF D7)
−3 CALL [reg+disp8] (FF 50 xx, etc.)
−5 CALL rel32 (E8 xx xx xx xx)
−6 CALL [rip+rel32] (FF 15 xx xx xx xx)
−7 CALL [mem] with REX prefix

Each candidate is disassembled with Capstone. First valid CALL that ends exactly at ret_addr wins.

5. RVA calculation

module = find_module_containing(call_site)
rva    = call_site - module.base_address

Load this RVA directly in IDA Pro / Ghidra / x64dbg.


x86 vs x64 technical notes

CONTEXT alignment (x64)

GetThreadContext requires the CONTEXT buffer to be 16-byte aligned. WinTrace allocates CONTEXT_SIZE + 15 bytes and aligns manually:

aligned_addr = (buf_addr + 15) & ~15
ctx = CONTEXT_x64.from_address(aligned_addr)

Calling conventions

Arch Convention Argument passing
x64 __fastcall Args 1–4: RCX, RDX, R8, R9 — further: RSP+0x28, RSP+0x30 …
x86 stdcall All args on stack: [ESP+4], [ESP+8], [ESP+12] …

WOW64 (32-bit target, 64-bit debugger)

WinTrace detects WOW64 via IsWow64Process at startup and automatically switches between GetThreadContext / Wow64GetThreadContext.

Note: A 32-bit Python host cannot debug a 64-bit target. Always use 64-bit Python on 64-bit Windows.


Known limitations

Limitation Notes
DLLs loaded before attach BPs not placed on APIs in already-loaded DLLs with --attach. Workaround: use --launch.
Multi-threaded race Two threads hitting the same API during BP restore may slip through. Mitigate with --filter-api.
Packed / obfuscated targets INT3 at IAT entries may not fire if the IAT is rebuilt at runtime.
PDB symbols Not implemented — caller shown as module+RVA. Import into IDA/Ghidra for symbol resolution.
Kernel-mode APIs Not supported — user-mode only by design.

Legal notice

WinTrace uses only documented Windows debugging APIs:

  • CreateProcess with DEBUG_ONLY_THIS_PROCESS
  • WaitForDebugEvent / ContinueDebugEvent
  • GetThreadContext / SetThreadContext
  • ReadProcessMemory / WriteProcessMemory
  • VirtualProtectEx / FlushInstructionCache

No kernel-mode operations, no AV evasion, no stealth techniques.

Use only on:

  • Processes you compiled or own
  • Systems you administer
  • Processes for which you have explicit written authorization from the software owner

About

The new Evolution of Debugger 32 and 64 bit

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages