Introduction
The Z x86_64 Linux Anti-Anti-Debugger is a powerful debugging tool written in C,
engineered specifically to bypass anti-debugging techniques employed by many Linux binaries.
Traditional debuggers can be detected and countered by malware or protected software.
This tool circumvents those mechanisms using a combination of advanced ptrace
strategies,
hardware breakpoint management, dynamic memory patching, and the injection of custom LD_PRELOAD libraries.
This documentation is intended for users and developers who wish to understand the inner workings, extend the capabilities, or contribute to the project. It covers the overall architecture, installation and build instructions, detailed usage examples, an exhaustive API reference, and advanced troubleshooting tips.
Whether you are using the tool to debug a challenging application or to study advanced debugging techniques, this guide will provide you with all the necessary details.
Features
The Z anti-anti-debugger comes packed with advanced features, including:
- Bypass Anti-Debugging: Specifically designed to counter common anti-debugging measures.
- Full Process Control: Leverages
ptrace
to manipulate target processes at a low level. -
Breakpoint & Watchpoint Management:
- Software Breakpoints using
INT3
instruction patching with restoration of original code. - Hardware Breakpoints via debug registers (DR0–DR3) without modifying code.
- Memory Watchpoints to monitor read/write operations on specific addresses.
- Catchpoints to intercept signals and process events (fork, clone, exec, exit).
- Software Breakpoints using
- Instruction Stepping: Support for single stepping, stepping over function calls, and stepping out of functions.
- Memory Inspection & Patching: Dump memory contents and dynamically patch code at runtime.
- Disassembly Support: Integration with the Capstone disassembly engine to display human-readable assembly.
-
Symbol Resolution & Backtracing:
- ELF symbol parsing to resolve function names and addresses.
- Generate call stack backtraces.
-
Dynamic LD_PRELOAD Injection: Inject custom libraries to intercept critical system calls (e.g.,
fopen
,getenv
,prctl
,setvbuf
), thereby evading anti-debugging traps. - User-Friendly CLI: Command-line interface with color-coded output and extensive commands for complete control.
Installation & Build Instructions
Step 1: Clone the Repository
git clone --recurse-submodules https://github.com/JavaHammes/Z.git
Step 2: Install Dependencies
For Ubuntu/Debian-based systems, run:
sudo apt-get update
sudo apt-get install libcapstone-dev libcriterion-dev build-essential pkg-config cmake gcc
Step 3: Build the Project
You can use CMake to configure and build:
cmake -B build
cmake --build build
The build output should generate an executable named z
.
Step 4: Running the Debugger
To start a debugging session, run:
cd bin/
./z target_executable [optional_ld_preload_libraries...]
If you do not supply custom LD_PRELOAD libraries, the tool automatically loads a default set (e.g., libptrace_intercept.so
, libfopen_intercept.so
, etc.).
Project Architecture
High-Level Overview
The Z anti-anti-debugger is divided into several interdependent modules:
-
debuggee.c
: Implements low-level ptrace wrappers, memory access routines, register manipulation, breakpoint insertion/removal, disassembly functions, and symbol resolution. -
debuggger.c
: Contains the core debugger loop, process management, event handling (signals, ptrace events), and LD_PRELOAD configuration. It orchestrates the overall debugging session.
Detailed Component Relationships
The debugger spawns a child process that executes the target binary with ptrace(PTRACE_TRACEME)
and sets the LD_PRELOAD
environment variable. The parent process (debugger) then monitors and controls the child process, responding to events (e.g., breakpoints, signals) via a carefully structured main loop.
Breakpoint handling is a two-stage process: first, the tool inserts breakpoints (either software or hardware) by modifying code or registers; then, upon triggering, it restores original instructions and continues execution as specified by the user commands.
Additionally, symbol resolution is performed by parsing ELF symbol tables, which enables features such as function address lookup and stack backtracing.
Usage Guide
Getting Started
To start debugging, run the tool with your target application. For example:
./z target_executable
You may add custom LD_PRELOAD libraries after the target program’s path to further extend functionality.
Interactive Commands
The debugger supports an extensive set of commands. Some of the key ones include:
help
– Displays the full list of commands and their usage.exit
– Terminates the debugging session.run
– Starts or resumes execution of the target process.step
– Executes the next machine instruction (single step).over
– Steps over function calls by setting temporary breakpoints at return addresses.out
– Steps out of the current function.skip <n>
– Skips the next n instructions.jump <addr>
– Sets a temporary breakpoint at a given address and continues execution until hit.-
Breakpoint commands:
break <addr>
– Insert a software breakpoint.hbreak <addr>
– Insert a hardware breakpoint.watch <addr>
– Set a watchpoint on a memory address.catch <sig/event>
– Monitor specific signals or process events.remove <index>
– Remove a breakpoint by its index.
-
Inspection commands:
regs
– Display the CPU registers (general and debug registers).dump
– Dump memory contents around the current instruction pointer.dis
– Disassemble code at the current execution address using Capstone.funcs
– List all functions extracted from the ELF symbol tables.addr <func_name>
– Retrieve the address of a function by name.backt
– Generate a full backtrace of the call stack.
-
Modification commands:
set <reg>=<value>
– Modify a register value.patch <addr>=<hex>
– Patch memory with new opcodes.
Example Session
The following is an example of a debugging session:
# Start the debugger with a target application
./z target_executable
# At the interactive prompt:
help # Show available commands
break 0x400123 # Set a software breakpoint at address 0x400123
run # Start the application
regs # Dump registers after hitting a breakpoint
dis # Disassemble code at the current instruction pointer
step # Single-step through instructions
backt # Generate a backtrace
exit # End the debugging session
Detailed API Reference
This section provides a comprehensive breakdown of the primary functions in the codebase. The API is divided into two major groups: Debuggee Functions and Debugger Functions.
Debuggee Functions (src/debuggee.c
)
Process Control & Execution
-
int Run(debuggee *dbgee)
Starts or resumes the target process. If temporary breakpoints exist, it handles their removal and resumes execution via
ptrace(PTRACE_CONT)
. -
int Continue(debuggee *dbgee)
Continues execution after a stop (e.g., after handling a breakpoint or signal).
-
int Step(debuggee *dbgee)
Performs a single instruction step using
ptrace(PTRACE_SINGLESTEP)
. -
int StepOver(debuggee *dbgee)
Steps over function calls by setting a temporary breakpoint at the return address before executing the call.
-
int StepOut(debuggee *dbgee)
Steps out of the current function by obtaining the return address from the stack and setting a temporary breakpoint there.
-
int Skip(debuggee *dbgee, const char *arg)
Skips a specified number of instructions by repeatedly single-stepping.
-
int Jump(debuggee *dbgee, const char *arg)
Sets a temporary breakpoint at a user-specified address (or symbol) and continues execution until that point is reached.
-
int Trace(debuggee *dbgee, const char *arg)
Combines jump functionality with single-stepping to trace execution from a given point while disassembling instructions on the fly.
Register & Memory Inspection
-
int Registers(debuggee *dbgee)
Retrieves CPU general-purpose registers and debug registers using
ptrace(PTRACE_GETREGS)
and displays them in a formatted output. -
int Dump(debuggee *dbgee)
Dumps memory starting at the current instruction pointer in both hexadecimal and ASCII formats.
-
int Disassemble(debuggee *dbgee)
Utilizes the Capstone disassembly library to convert raw machine code into human-readable assembly instructions.
Breakpoint Management
-
int SetSoftwareBreakpoint(debuggee *dbgee, const char *arg)
Inserts an
INT3
-based breakpoint at a specified address and stores the original instruction byte for later restoration. -
int SetHardwareBreakpoint(debuggee *dbgee, const char *arg)
Configures an available CPU debug register (DR0–DR3) to monitor a given address without modifying code.
-
int SetWatchpoint(debuggee *dbgee, const char *arg)
Sets a watchpoint on a memory address to monitor read/write access, using hardware debug registers.
-
int SetCatchpoint(debuggee *dbgee, const char *arg)
Allows setting of catchpoints to intercept specific signals or process events (e.g., fork, exec, exit).
-
int RemoveBreakpoint(debuggee *dbgee, const char *arg)
Removes an active breakpoint (software or hardware) and restores any modified code or registers.
Dynamic Patching & Symbol Resolution
-
int Patch(debuggee *dbgee, const char *arg)
Patches memory by replacing instructions at a given address with new hexadecimal opcodes.
-
int Address(debuggee *dbgee, const char *arg)
Resolves the absolute address of a symbol by parsing ELF symbol tables, which is useful for setting breakpoints by function name.
-
unsigned long get_entry_absolute_address(debuggee *dbgee)
Determines the target process’s entry point, properly handling Position Independent Executables (PIE).
Breakpoint Handlers
-
int handle_software_breakpoint(debuggee *dbgee, size_t bp_index)
When a software breakpoint is hit, this function restores the original instruction, adjusts the instruction pointer, and optionally re-inserts the breakpoint if it is persistent.
-
int handle_hardware_breakpoint(debuggee *dbgee, size_t bp_index)
Processes hardware breakpoint events, printing diagnostic information to the user.
-
int handle_catchpoint_signal(debuggee *dbgee, size_t bp_index)
Handles caught signals by determining if they correspond to user-defined catchpoints and dispatching the appropriate action.
-
int handle_catchpoint_event(debuggee *dbgee, size_t bp_index)
Handles process events (e.g., fork, exec) when catchpoints are configured.
-
int handle_watchpoint(debuggee *dbgee, size_t bp_index)
Responds to watchpoint triggers and informs the user about memory access events.
Debugger Functions (src/debuggger.c
)
Initialization & Setup
-
void init_debugger(debugger *dbg, const char *debuggee_name, int argc, char **argv)
Initializes the debugger state including the breakpoint handler, the LD_PRELOAD library list, and processes any command-line arguments for custom library injection.
-
void free_debugger(debugger *dbg)
Cleans up resources, terminates the target process if necessary, and resets the debugger state.
Process Launch & Main Loop
-
int start_debuggee(debugger *dbg)
Forks a new process, sets up the
ptrace
environment, configures LD_PRELOAD, and starts the target application. -
int trace_debuggee(debugger *dbg)
Implements the core debugging loop. It waits for
ptrace
events (e.g., breakpoints, signals), dispatches them to the appropriate handlers, and manages the overall session flow.
Utility Functions
-
static int _add_default_preload_libraries(debugger *dbg)
If no custom libraries are provided, this function adds a default set of LD_PRELOAD libraries to intercept system calls.
-
static void _process_ld_preload_args(debugger *dbg, int argc, char **argv)
Parses command-line arguments to include additional LD_PRELOAD libraries, enhancing the tool’s functionality.
Advanced Topics
Bypassing Anti-Debugging Techniques
Many modern applications employ anti-debugging techniques such as timing checks, interrupt detection, and environment scanning to thwart traditional debuggers. This tool combats these methods by:
- Injecting custom LD_PRELOAD libraries that override common system functions (like
fopen
,getenv
, andprctl
) to hide debugging artifacts. - Utilizing hardware breakpoints, which are far less detectable since they do not require modifying code.
- Carefully managing signal and event handling to avoid leaving traces that anti-debug mechanisms could detect.
Breakpoint Strategies & Single-Stepping
Breakpoint insertion is central to the tool’s operation:
-
Software Breakpoints: The tool patches the code by replacing an instruction with the
INT3
opcode (0xCC) and preserves the original byte for later restoration. - Hardware Breakpoints: By using available debug registers (DR0–DR3) and configuring DR7 appropriately, the tool can monitor addresses without modifying the program code.
-
Single-Stepping: Precision control is achieved using
ptrace(PTRACE_SINGLESTEP)
—critical when re-inserting breakpoints after a hit or when tracing execution flow.
Backtracing & Symbol Resolution
Using ELF symbol tables, the tool maps function names to their addresses. This allows:
- Displaying function names alongside addresses in the backtrace.
- Providing meaningful output even for stripped binaries by computing offsets relative to module base addresses.
Custom LD_PRELOAD Libraries
Z can be extended by supplying your own custom LD_PRELOAD libraries. These libraries allow you to override
system functions (e.g., fopen
, getenv
, prctl
) to tailor the behavior of the target
process and further conceal the presence of a debugger.
Important: Every custom LD_PRELOAD library must include the following marker function:
void zZz() {}
This marker acts as a signature, signaling to Z’s default preload libraries that your library is custom—especially
crucial when combining default and custom libraries. For example, in the fopen
override, the library processes
files such as /proc/self/maps
by filtering out only those lines corresponding to libraries that include the
zZz
marker. This ensures that any of the debuggee’s own libraries aren’t mistakenly filtered out, which could alert
the target process to the debugger’s presence.
Without this marker, your custom library may not integrate properly, potentially leading to unfiltered behavior or unexpected side effects in your overrides.
Example Custom Library
Below is an alternative example for a custom LD_PRELOAD library that overrides the gettimeofday
function—this function is not used by the default libraries:
#include <sys/time.h>
#include <dlfcn.h>
// Marker function required for custom libraries.
void zZz() {}
// Custom gettimeofday override.
int gettimeofday(struct timeval *tv, struct timezone *tz) {
// Retrieve the original gettimeofday function.
int (*orig_gettimeofday)(struct timeval*, struct timezone*) = dlsym(RTLD_NEXT, "gettimeofday");
int ret = orig_gettimeofday(tv, tz);
// Your custom implementation to modify the return value.
return ret;
}
Compiling Your Custom Library
To compile your custom library, use the following command:
gcc -shared -fPIC -o libcustom.so custom.c -ldl
Here:
-shared creates a shared library.
-fPIC generates position-independent code necessary for shared libraries.
-ldl links against the dynamic linking library.
After compiling, supply your custom library as an additional argument when launching Z:
./z target_executable ./libcustom.so
Your custom library will be appended to the LD_PRELOAD list, thereby extending the debugger’s functionality.
Troubleshooting
Common Issues
- ptrace Failures: Ensure that your user account has sufficient privileges. On some systems, security modules like SELinux or AppArmor might block ptrace operations.
- LD_PRELOAD Problems: Verify that default LD_PRELOAD libraries are present in the expected directory. If you supply custom libraries, ensure they are compiled correctly.
- Breakpoint Not Triggering: Confirm that the address is valid, mapped, and executable.
- Disassembly Issues: If Capstone fails to disassemble code, ensure that the target architecture is correctly specified (x86_64) and that the binary is not corrupted.
Performance & Limitations
Performance Considerations
Due to the overhead of ptrace
and the need for frequent system calls (especially during single-stepping),
debugging sessions can be slower than normal execution. Consider these factors:
- Single-stepping can significantly slow down the target process; use it selectively.
- Hardware breakpoints are less intrusive but are limited in number by the CPU.
Known Limitations
- The tool is designed for x86_64 Linux and may require modifications to work on other architectures.
- Some anti-debugging techniques (e.g., timing-based or obfuscation) are not bypassed.
- High-security environments may restrict ptrace operations regardless of user privileges.
Developer Guide
Code Structure & Conventions
The project follows standard C99 conventions. When contributing, adhere to the following guidelines:
- Follow the existing naming conventions (e.g.,
dbgee
for debuggee structures,dbg
for debugger structures). - Document new functions and changes with clear comments.
- Add tests or example usage if modifying core functionality.
Extending Functionality
Developers are encouraged to add new features, such as:
- Additional LD_PRELOAD libraries for intercepting more system calls.
- Enhanced user interface improvements (e.g., support for scripting commands).
- Support for more architectures or operating systems.
- Better error handling and logging mechanisms.
Frequently Asked Questions (FAQ)
Q: What makes this tool an Anti-Anti-Debugger?
A: It is engineered specifically to bypass common anti-debugging techniques by intercepting system calls, using non-invasive hardware breakpoints, and carefully managing signal and event handling to avoid detection.
Q: Do I need root privileges to use it?
A: Not necessarily, but you must have sufficient privileges to use ptrace
and access certain /proc
interfaces.
Q: Can I add my own LD_PRELOAD libraries?
A: Yes, you can supply custom libraries via command-line arguments. These libraries will be appended to the LD_PRELOAD list.
Q: Does it work with all x86_64 binaries?
A: The tool is designed for standard x86_64 Linux binaries. However, heavily obfuscated or highly protected binaries may still employ techniques that limit debugging.
Contributing Guidelines
Contributions to Z are very welcome! Please follow these steps:
- Fork the repository on GitHub.
- Create a new branch for your feature or bug fix.
- Ensure that your code adheres to the existing coding conventions.
- Add tests for any new functionality where applicable.
- Submit a pull request with a clear description of your changes.
License
This project is licensed under the MIT License. You are free to use, modify, and distribute the code in accordance with the license terms.