Skip to content

Commit

Permalink
Merge pull request libdebug#101 from libdebug/dev
Browse files Browse the repository at this point in the history
Provola
  • Loading branch information
MrIndeciso authored Sep 12, 2024
2 parents e9e7b7a + 324e379 commit 3699dcf
Show file tree
Hide file tree
Showing 240 changed files with 10,303 additions and 976 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ jobs:
- name: Test with pytest
run: |
cd test && pytest --ignore=other_tests
cd test/amd64 && pytest --ignore=other_tests
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
![logo](https://github.com/libdebug/libdebug/blob/dev/media/libdebug_header.png?raw=true)
# libdebug
# libdebug [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.13151549.svg)](https://doi.org/10.5281/zenodo.13151549)

libdebug is an open source Python library to automate the debugging of a binary executable.

With libdebug you have full control of the flow of your debugged executable. With it you can:
Expand Down Expand Up @@ -128,5 +129,6 @@ If you intend to use libdebug in your work, please cite this repository using th
publisher = {libdebug.org},
author = {Digregorio, Gabriele and Bertolini, Roberto Alessandro and Panebianco, Francesco and Polino, Mario},
year = {2024},
doi = {10.5281/zenodo.13151549},
}
```
38 changes: 36 additions & 2 deletions docs/source/basic_features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ After creating the debugger object, you can start the execution of the program u
The `run()` command returns a `PipeManager` object, which you can use to interact with the program's standard input, output, and error. To read more about the PipeManager interface, please refer to the PipeManager documentation :class:`libdebug.utils.pipe_manager.PipeManager`. Please note that breakpoints are not kept between different runs of the program. If you want to set a breakpoint again, you should do so after the program has restarted.

Any process will be automatically killed when the debugging script exits. If you want to prevent this behavior, you can set the `kill_on_exit` parameter to False when creating the debugger object, or set the companion attribute `kill_on_exit` to False at runtime.

The command queue
-----------------
Control flow commands, register access and memory access are all done through the command queue. This is a FIFO queue of commands that are executed in order.
Expand Down Expand Up @@ -83,8 +85,11 @@ Register Access
===============
.. _register-access-paragraph:

libdebug offers a simple register access interface for supported architectures. The registers are accessed through the regs attribute of the debugger object. The field includes both general purpose and special registers, as well as the flags register. Effectively, any register that can be accessed by an assembly instruction, can also be accessed through the regs attribute. The debugger specifically exposes properties of the main thread, including the registers. See :doc:`multithreading` to learn how to access registers and other properties from different threads.
libdebug offers a simple register access interface for supported architectures. The registers are accessed through the `regs`` attribute of the debugger object. The field includes both general purpose and special registers, as well as the flags register. Effectively, any register that can be accessed by an assembly instruction, can also be accessed through the regs attribute. The debugger specifically exposes properties of the main thread, including the registers. See :doc:`multithreading` to learn how to access registers and other properties from different threads.

Floating point and vector registers are available as well. The syntax is identical to the one used for integer registers.
For amd64, the list of available AVX registers is determined during installation by checking the CPU capabilities, thus special registers, such as `zmm0` to `zmm31`, are available only on CPUs that support the specific ISA extension.
If you believe that your target CPU supports AVX registers, but they are not available during debugging, please file an issue on the GitHub repository and include your precise hardware details, so that we can investigate and resolve the issue.

Memory Access
====================================
Expand Down Expand Up @@ -158,6 +163,23 @@ If you specify a full or a substring of a file name, libdebug will search for th
You can also use the wildcard string "binary" to use the base address of the binary as the base address for the relative addressing. The same behavior is applied if you pass a string corresponding to the binary name.

Faster Memory Access
-------------------

By default, libdebug uses the kernel's ptrace interface to access memory. This is guaranteed to work, but it might be slow during large memory transfers.
To speed up memory access, we provide a secondary system that relies on /proc/$pid/mem for read and write operations. You can enable this feature by setting `fast_memory` to True when instancing the debugger.
The final behavior is identical, but the speed is significantly improved.

Additionally, you can mix the two memory access methods by changing the `fast_memory` attribute of the debugger at runtime:

.. code-block:: python
d.fast_memory = True
# ...
d.fast_memory = False
Control Flow Commands
====================================

Expand Down Expand Up @@ -219,6 +241,15 @@ The available heuristics are:

The default heuristic when none is specified is "backtrace".

Next
^^^^

The `next` command is similar to the `step` command, but when a ``call`` instruction is found, it will continue until the end of the function being called or until the process stops for other reasons. The syntax is as follows:

.. code-block:: python
d.next()
Detach and GDB Migration
====================================

Expand Down Expand Up @@ -246,6 +277,9 @@ An alternative to running the program from the beginning and to resume libdebug
d.attach(pid)
Do note that libdebug automatically kills any running process when the debugging script exits, even if the debugger has detached from it.
If you want to prevent this behavior, you can set the `kill_on_exit` parameter to False when creating the debugger object, or set the companion attribute `kill_on_exit` to False at runtime.

Graceful Termination
====================

Expand Down Expand Up @@ -291,4 +325,4 @@ You can also access registers after the process has died. This is useful for *po
Supported Architectures
=======================

libdebug currently only supports Linux under the x86_64 (AMD64) architecture. Support for other architectures is planned for future releases. Stay tuned.
libdebug currently only supports Linux under the x86_64 (AMD64) and AArch64 (ARM64) architectures. Support for other architectures is planned for future releases. Stay tuned.
13 changes: 10 additions & 3 deletions docs/source/breakpoints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ libdebug provides a simple API to set breakpoints in your debugged program. The

.. code-block:: python
from libdebug import Debugger
from libdebug import debugger
d = debugger("./test_program")
Expand Down Expand Up @@ -102,20 +102,27 @@ Features of watchpoints are shared with breakpoints, so you can set callbacks, c
callback=...) -> Breakpoint:
Again, the position can be specified both as a relative address or as a symbol.
The condition parameter specifies the type of access that triggers the watchpoint. The following values are supported:
The condition parameter specifies the type of access that triggers the watchpoint. The following values are supported in all architectures:

- ``"w"``: write access
- ``"rw"``: read/write access
- ``"x"``: execute access

AArch64 additionally supports:

- ``"r"``: read access

By default, the watchpoint is triggered only on write access.

The length parameter specifies the size of the word being watched. The following values are supported:
The length parameter specifies the size of the word being watched.
In x86_64 (amd64) the following values are supported:

- ``1``: byte
- ``2``: word
- ``4``: dword
- ``8``: qword

AArch64 supports any length from 1 to 8 bytes.

By default, the watchpoint is set to watch a byte.

2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
project = 'libdebug'
copyright = '2024, Gabriele Digregorio, Roberto Alessandro Bertolini, Francesco Panebianco'
author = 'JinBlack, Io_no, MrIndeciso, Frank01001'
release = '0.5.4'
release = '0.6.0'

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
23 changes: 21 additions & 2 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

----

.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.13151549.svg
:target: https://doi.org/10.5281/zenodo.13151549

Quick Start
====================================
Welcome to libdebug! This powerful Python library can be used to debug your binary executables programmatically, providing a robust, user-friendly interface.
Expand All @@ -25,7 +28,7 @@ e.g, for version 0.5.0, go to https://docs.libdebug.org/archive/0.5.0
Supported Architectures
-----------------------

libdebug currently supports Linux under the x86_64 architecture.
libdebug currently supports Linux under the x86_64 and AArch64 architectures.

Other operating systems and architectures are not supported at this time.

Expand Down Expand Up @@ -106,7 +109,7 @@ Now that you have libdebug installed, you can start using it in your scripts. He
The above script will run the binary `test` in the working directory and stop at the function corresponding to the symbol "function". It will then print the value of the RAX register and kill the process.

Conflicts with other python packages
Conflicts with other Python packages
------------------------------------

The current version of libdebug is incompatible with https://github.com/Gallopsled/pwntools.
Expand All @@ -120,6 +123,22 @@ Examples of some known issues include:
- Attaching libdebug to a process that was started with pwntools with ``shell=True`` will cause the process to attach to the shell process instead.
This behavior is described in https://github.com/libdebug/libdebug/issues/57.

Cite Us
-------
Need to cite libdebug in your research? Use the following BibTeX entry:

.. code-block:: bibtex
@software{libdebug_2024,
title = {libdebug: {Build} {Your} {Own} {Debugger}},
copyright = {MIT Licence},
url = {https://libdebug.org},
publisher = {libdebug.org},
author = {Digregorio, Gabriele and Bertolini, Roberto Alessandro and Panebianco, Francesco and Polino, Mario},
year = {2024},
doi = {10.5281/zenodo.13151549},
}
.. toctree::
:maxdepth: 2
:caption: Contents:
Expand Down
1 change: 1 addition & 0 deletions docs/source/multithreading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ The following is a list of behaviors to keep in mind when using control flow fun

- `cont` will continue all threads.
- `step` and `step_until` will step the selected thread.
- `next` will step on the selected thread or, if a call function is found, continue on all threads until the end of the called function or another stopping event.
- `finish` will have different behavior depending on the selected heuristic.
- `backtrace` will continue on all threads but will stop at any breakpoint that any of the threads hit.
- `step-mode` will step exclusively on the thread that has been specified.
Expand Down
Empty file.
24 changes: 24 additions & 0 deletions libdebug/architectures/aarch64/aarch64_breakpoint_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#
# This file is part of libdebug Python library (https://github.com/libdebug/libdebug).
# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from libdebug.data.breakpoint import Breakpoint


def validate_breakpoint_aarch64(bp: Breakpoint) -> None:
"""Validate a hardware breakpoint for the AARCH64 architecture."""
if bp.condition not in ["r", "w", "rw", "x"]:
raise ValueError("Invalid condition for watchpoints. Supported conditions are 'r', 'w', 'rw', 'x'.")

if not (1 <= bp.length <= 8):
raise ValueError("Invalid length for watchpoints. Supported lengths are between 1 and 8.")

if bp.condition != "x" and bp.address & 0x7:
raise ValueError("Watchpoint address must be aligned to 8 bytes on aarch64. This is a kernel limitation.")
38 changes: 38 additions & 0 deletions libdebug/architectures/aarch64/aarch64_call_utilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#
# This file is part of libdebug Python library (https://github.com/libdebug/libdebug).
# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

from __future__ import annotations

from libdebug.architectures.call_utilities_manager import CallUtilitiesManager


class Aarch64CallUtilities(CallUtilitiesManager):
"""Class that provides call utilities for the AArch64 architecture."""

def is_call(self: Aarch64CallUtilities, opcode_window: bytes) -> bool:
"""Check if the current instruction is a call instruction."""
# Check for BL instruction
if (opcode_window[3] & 0xFC) == 0x94:
return True

# Check for BLR instruction
if opcode_window[3] == 0xD6 and (opcode_window[2] & 0x3F) == 0x3F:
return True

return False

def compute_call_skip(self: Aarch64CallUtilities, opcode_window: bytes) -> int:
"""Compute the instruction size of the current call instruction."""
# Check for BL instruction
if self.is_call(opcode_window):
return 4

return 0

def get_call_and_skip_amount(self: Aarch64CallUtilities, opcode_window: bytes) -> tuple[bool, int]:
"""Check if the current instruction is a call instruction and compute the instruction size."""
skip = self.compute_call_skip(opcode_window)
return skip != 0, skip
Loading

0 comments on commit 3699dcf

Please sign in to comment.