libdebug


Namelibdebug JSON
Version 0.4.2 PyPI version JSON
download
home_pageNone
SummaryA Python library for the debugging of binary executables.
upload_time2024-04-30 22:01:35
maintainerNone
docs_urlNone
authorJinBlack, Io_no, MrIndeciso, Frank01001, MarcoDige
requires_python>=3.10
licenseMIT License Copyright (c) 2023 - 2024 Mario Polino, Gabriele Digregorio, Roberto Bertolini, Francesco Panebianco. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords libdebug debugger elf ptrace gdb debug ctf reverse-engineering reverse rev scriptable script
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # libdebug
libdebug is a Python library to automate the debugging of a binary executable.

## Installation
```bash
python3 -m pip install git+https://github.com/libdebug/libdebug.git
```
PyPy3 is supported but not recommended, as it performs worse on most of our tests.

### Installation Requirements:
Ubuntu: `sudo apt install -y python3 python3-dev libdwarf-dev libelf-dev libiberty-dev linux-headers-generic libc6-dbg`
Debian: `sudo apt install -y python3 python3-dev libdwarf-dev libelf-dev libiberty-dev linux-headers-generic libc6-dbg`
Fedora: `sudo dnf install -y python3 python3-devel kernel-devel binutils-devel libdwarf-devel`
Arch Linux: `sudo pacman -S python libelf libdwarf gcc make debuginfod`

## Run and Attach
The first step of a libdebug script is creating a debugger object. This can be done with the function `debugger(argv,...)`. You can either provide a path or an array of arguments. Once your debugger object has been created, you can use the method `run` to start it
```python
from libdebug import debugger

d = debugger("./test")

d.run()
```

Alternatively, you can attach to an already running process using `attach` and specifying the PID.
```python
d = debugger()

d.attach(1234)
```

The debugger has many more options that can be configured by the user:
```python
d = debugger(argv=<"./test" | ["./test", ...]>,
    [enable_aslr=<True | False>], # defaults to False
    [env={...}], # defaults to the same environment in which the debugging script is run
    [continue_to_binary_entrypoint=<True | False>], # defaults to True
    [auto_interrupt_on_command=<True | False>], #defaults to False
)
```
By setting `continue_to_binary_entrypoint` to False, the `run()` command will stop at the first instruction executed by the loader instead of reaching the entrypoint of the binary.

---

The flag `auto_interrupt_on_command` fundamentally changes the way you use libdebug. By default it is set to False. In this setting, issued commands will not be performed until a breakpoint is hit or any other tracing signal stops the process (e.g, SIGSEGV).
This is an example extract of code in the default mode:

```python
d = debugger("./binary")

bp = d.breakpoint("function")

d.run()
d.cont()

# Here the register access is performed after the breakpoint is hit
print(hex(d.rip))

d.kill()
```

Instead, when set to True, every debugging command transparenty stops the execution of the program to perform the requested action as soon as possible.

```python
d = debugger("./binary", auto_interrupt_on_command=True)

bp = d.breakpoint("function")

d.run()
d.cont()

# If you do not call d.wait() here, the register access will be performed
# shortly after the process is allowed to continue
d.wait()
print(hex(d.rip))

d.kill()
```

## Register Access
Registers are provided as properties of the debugger object. You can perform read and write operations on them, which by default are handled when the process is stopped by a breakpoint or another tracing signal.
```python
d = debugger("./test")

d.run()

print(d.rax)
d.rax = 0
```

## Memory Access
The debugger property `memory` is used read to and write from a memory address or range in the virtual memory of the debugged program.
We provide multiple elegant ways of accessing it, such as:

```python
d = debugger("./test")

d.run()

print("[rsp]: ", d.memory[d.rsp])
print("[rsp]: ", d.memory[d.rsp:d.rsp+0x10])
print("[rsp]: ", d.memory[d.rsp, 0x10])

print("[main_arena]: ", d.memory["main_arena"])
print("[main_arena+8:main_arena+18]: ", d.memory["main_arena+8", 0x10])

d.memory[d.rsp, 0x10] = b"AAAAAAABC"
d.memory["main_arena"] = b"12345678"
```

## Control Flow
`step()` will execute a single instruction stepping into function calls

`cont()` will continue the execution, without blocking the main Python script.

`wait()` will block the execution of the Python script until the debugging process interrupts.

`step_until(<address | symbol>, [max_steps=-1])` will step until the desired address is reached or for `max_steps` steps, whichever comes first.

`breakpoint(<address | symbol>, [hardware=False])` will set a breakpoint, which can be hardware-assisted.

`watchpoint(<address | symbol>, [condition='w'])`
will set a watchpoint for the requested stopping condition: on write (`w`), on read and write (`rw`) or on execution (`x`, which basically corresponds to a hardware breakpoint).
```python
bp = d.breakpoint(0x1234)

d.cont()

assert d.rip == bp.address
```

## Asynchronous Callbacks
Breakpoints can be asynchronous: instead of interrupting the main Python script, they can register a small callback function that is run upon hitting the breakpoint. Execution of the debugged process is continued automatically.
```python
d = debugger("./test")
d.run()

def callback(d, bp):
    print(hex(d.rip))
    assert d.rip == bp.address
    print(hex(d.memory[d.rax, 0x10]))

d.breakpoint(0x1234, callback=callback)

d.cont()
```

## Multithreading Support
Libdebug supports multithreaded applications: each time the process clones itself, the new thread is automatically traced and registered in the `threads` property of the `debugger`.

Each thread exposes its own set of register access properties. Control flow is synchronous between threads: they either are all stopped or all running, and every time a thread stops, all the others are stopped. This is done to avoid concurrency issues.

```python
d = debugger("./threaded_test")
d.run()
d.cont()

for _ in range(15):
    for thread in d.threads:
        print(thread.thread_id, hex(thread.rip))

    d.cont()

d.kill()
```

## Breakpoints
The `Breakpoint` class represents a breakpoint for the traced process. It can be created with a function of the debugger object in the following way:
```python
bp = d.breakpoint(position=0x1234, hardware=False, condition=None, length=1, callback=None)
```
`position` represents a memory address or a symbol of the ELF. The `hardware` flag trivially controls whether or not the breakpoint is hardware assisted (a maximum of 4 hardware breakpoints are allowed). `condition` and `length` are used to specify properties of hardware watchpoints (see next section). For any type of breakpoint, a `callback` function can be specified. When set, a breakpoint hit will trigger the callback and automatically resume the execution of the program.

For your convenience, a Breakpoint object counts the number of times the breakpoint has been hit. The current count can be accessed though the `hit_count` property:
```python
bp = d.breakpoint(0x1234)
d.cont()

for i in range(15):
    d.wait()

    assert d.rip == bp.address
    assert bp.hit_count == i

    d.cont()
```

Since breakpoints in the program text are shared between threads, you can check if a breakpoint was hit on a specific thread with the `hit_on` function:
```python
# Assuming to have thread n.3
chosen_thread = d.threads[3]

bp = d.breakpoint(0x1234)
d.cont()

for i in range(15):
    
    assert d.rip == bp.address
    assert bp.hit_on(chosen_thread)

    if bp.hit_on(chosen_thread):
        ...

    d.cont()
```

## Watchpoints
The suggested way to insert a watchpoint is the following:

```python
wp = d.watchpoint(position=0x1234, condition='rw', length=8, callback=None)
```
The function returns a `Breakpoint` object, which can be interacted with in the same manner as traditional breakpoints. Valid conditions for the breakpoint are `w`, `rw` and `x` (default is `w`). It is also possible to specify the length of the word being watched (default is 1 byte).

## Syscall Hooking
libdebug supports hooking system calls in the debugged binary in the following way:
```python
def on_enter_open(d: ThreadContext, syscall_number: int):
    print("entering open")
    d.syscall_arg0 = 0x1

def on_exit_open(d: ThreadContext, syscall_number: int):
    print("exiting open")
    d.syscall_return = 0x0

sys_hook = d.hook_syscall(syscall="open", on_enter=on_enter_open, on_exit=on_exit_open)
```
`hook_syscall` accepts either a number or a string.
If the user provides a string, a syscall definition list is downloaded from [syscalls.mebeim.net](https://syscalls.mebeim.net/?table=x86/64/x64/latest) and cached internally in order to convert it into the corresponding syscall number.
`on_enter` and `on_exit` are optional: they are called only if present. At least one callback is required between `on_enter` and `on_exit` to make the hook meaningful.

Syscall hooks, just like breakpoints, can be enabled and disabled, and automatically count the number of invocations:
```py
sys_hook.disable()
sys_hook.enable()

print(sys_hook.hit_count)
```
Note: there can be at most one hook for each syscall.

## Builtin Hooks
libdebug provides some easy-to-use builtin hooks for syscalls:
- antidebug_escaping
Automatically patches binaries which use the return value of `ptrace(PTRACE_TRACEME, 0, 0, 0)` to verify that no external debugger is present.
Usage:
```py
from libdebug import debugger
from libdebug.builtin import antidebug_escaping

d = debugger(...)
d.run()

antidebug_escaping(d)

d.cont()
[...]
```

- pretty_print_syscall
Installs a hook on any syscall that automatically prints the input arguments and the corresponding return values, just like strace does.
By default, it hooks every syscall. The user can specify either a list of syscalls to hook onto, or a list of syscalls to exclude from hooking.
Usage:
```py
from libdebug import debugger
from libdebug.builtin import pretty_print_syscall

d = debugger("/usr/bin/ls")
d.run()

pretty_print_syscall(d,
    # syscalls = ["execve", "open", "getcwd"],
    # exclude = ["fork", "vfork", "exit_group"]
)

d.cont()
[...]
```
This results in an output similar to:
```
openat(int dfd = 0xffffff9c, const char *filename = 0x7ffff7f241b0, int flags = 0x80000, umode_t mode = 0x0) = 0x3
newfstatat(int dfd = 0x3, const char *filename = 0x7ffff7f1abd5, struct stat *statbuf = 0x7ffff7f53840, int flag = 0x1000) = 0x0
mmap(unsigned long addr = 0x0, unsigned long len = 0xd5f8ef0, unsigned long prot = 0x1, unsigned long flags = 0x2, unsigned long fd = 0x3, unsigned long off = 0x0) = 0x7fffea600000
close(unsigned int fd = 0x3) = 0x0
ioctl(unsigned int fd = 0x1, unsigned int cmd = 0x5401, unsigned long arg = 0x7fffffffd3a0) = 0x0
ioctl(unsigned int fd = 0x1, unsigned int cmd = 0x5413, unsigned long arg = 0x7fffffffd4c0) = 0x0
openat(int dfd = 0xffffff9c, const char *filename = 0x5555555806c0, int flags = 0x90800, umode_t mode = 0x0) = 0x3
newfstatat(int dfd = 0x3, const char *filename = 0x7ffff7f1abd5, struct stat *statbuf = 0x7fffffffd070, int flag = 0x1000) = 0x0
getdents64(unsigned int fd = 0x3, struct linux_dirent64 *dirent = 0x555555580710, unsigned int count = 0x8000) = 0x50
getdents64(unsigned int fd = 0x3, struct linux_dirent64 *dirent = 0x555555580710, unsigned int count = 0x8000) = 0x0
[...]
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "libdebug",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "libdebug, debugger, elf, ptrace, gdb, debug, ctf, reverse-engineering, reverse, rev, scriptable, script",
    "author": "JinBlack, Io_no, MrIndeciso, Frank01001, MarcoDige",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/7b/8d/439519ff892b1a551f144c8a7f3f34a709971e885c8b730a896349d3b685/libdebug-0.4.2.tar.gz",
    "platform": null,
    "description": "# libdebug\nlibdebug is a Python library to automate the debugging of a binary executable.\n\n## Installation\n```bash\npython3 -m pip install git+https://github.com/libdebug/libdebug.git\n```\nPyPy3 is supported but not recommended, as it performs worse on most of our tests.\n\n### Installation Requirements:\nUbuntu: `sudo apt install -y python3 python3-dev libdwarf-dev libelf-dev libiberty-dev linux-headers-generic libc6-dbg`\nDebian: `sudo apt install -y python3 python3-dev libdwarf-dev libelf-dev libiberty-dev linux-headers-generic libc6-dbg`\nFedora: `sudo dnf install -y python3 python3-devel kernel-devel binutils-devel libdwarf-devel`\nArch Linux: `sudo pacman -S python libelf libdwarf gcc make debuginfod`\n\n## Run and Attach\nThe first step of a libdebug script is creating a debugger object. This can be done with the function `debugger(argv,...)`. You can either provide a path or an array of arguments. Once your debugger object has been created, you can use the method `run` to start it\n```python\nfrom libdebug import debugger\n\nd = debugger(\"./test\")\n\nd.run()\n```\n\nAlternatively, you can attach to an already running process using `attach` and specifying the PID.\n```python\nd = debugger()\n\nd.attach(1234)\n```\n\nThe debugger has many more options that can be configured by the user:\n```python\nd = debugger(argv=<\"./test\" | [\"./test\", ...]>,\n    [enable_aslr=<True | False>], # defaults to False\n    [env={...}], # defaults to the same environment in which the debugging script is run\n    [continue_to_binary_entrypoint=<True | False>], # defaults to True\n    [auto_interrupt_on_command=<True | False>], #defaults to False\n)\n```\nBy setting `continue_to_binary_entrypoint` to False, the `run()` command will stop at the first instruction executed by the loader instead of reaching the entrypoint of the binary.\n\n---\n\nThe flag `auto_interrupt_on_command` fundamentally changes the way you use libdebug. By default it is set to False. In this setting, issued commands will not be performed until a breakpoint is hit or any other tracing signal stops the process (e.g, SIGSEGV).\nThis is an example extract of code in the default mode:\n\n```python\nd = debugger(\"./binary\")\n\nbp = d.breakpoint(\"function\")\n\nd.run()\nd.cont()\n\n# Here the register access is performed after the breakpoint is hit\nprint(hex(d.rip))\n\nd.kill()\n```\n\nInstead, when set to True, every debugging command transparenty stops the execution of the program to perform the requested action as soon as possible.\n\n```python\nd = debugger(\"./binary\", auto_interrupt_on_command=True)\n\nbp = d.breakpoint(\"function\")\n\nd.run()\nd.cont()\n\n# If you do not call d.wait() here, the register access will be performed\n# shortly after the process is allowed to continue\nd.wait()\nprint(hex(d.rip))\n\nd.kill()\n```\n\n## Register Access\nRegisters are provided as properties of the debugger object. You can perform read and write operations on them, which by default are handled when the process is stopped by a breakpoint or another tracing signal.\n```python\nd = debugger(\"./test\")\n\nd.run()\n\nprint(d.rax)\nd.rax = 0\n```\n\n## Memory Access\nThe debugger property `memory` is used read to and write from a memory address or range in the virtual memory of the debugged program.\nWe provide multiple elegant ways of accessing it, such as:\n\n```python\nd = debugger(\"./test\")\n\nd.run()\n\nprint(\"[rsp]: \", d.memory[d.rsp])\nprint(\"[rsp]: \", d.memory[d.rsp:d.rsp+0x10])\nprint(\"[rsp]: \", d.memory[d.rsp, 0x10])\n\nprint(\"[main_arena]: \", d.memory[\"main_arena\"])\nprint(\"[main_arena+8:main_arena+18]: \", d.memory[\"main_arena+8\", 0x10])\n\nd.memory[d.rsp, 0x10] = b\"AAAAAAABC\"\nd.memory[\"main_arena\"] = b\"12345678\"\n```\n\n## Control Flow\n`step()` will execute a single instruction stepping into function calls\n\n`cont()` will continue the execution, without blocking the main Python script.\n\n`wait()` will block the execution of the Python script until the debugging process interrupts.\n\n`step_until(<address | symbol>, [max_steps=-1])` will step until the desired address is reached or for `max_steps` steps, whichever comes first.\n\n`breakpoint(<address | symbol>, [hardware=False])` will set a breakpoint, which can be hardware-assisted.\n\n`watchpoint(<address | symbol>, [condition='w'])`\nwill set a watchpoint for the requested stopping condition: on write (`w`), on read and write (`rw`) or on execution (`x`, which basically corresponds to a hardware breakpoint).\n```python\nbp = d.breakpoint(0x1234)\n\nd.cont()\n\nassert d.rip == bp.address\n```\n\n## Asynchronous Callbacks\nBreakpoints can be asynchronous: instead of interrupting the main Python script, they can register a small callback function that is run upon hitting the breakpoint. Execution of the debugged process is continued automatically.\n```python\nd = debugger(\"./test\")\nd.run()\n\ndef callback(d, bp):\n    print(hex(d.rip))\n    assert d.rip == bp.address\n    print(hex(d.memory[d.rax, 0x10]))\n\nd.breakpoint(0x1234, callback=callback)\n\nd.cont()\n```\n\n## Multithreading Support\nLibdebug supports multithreaded applications: each time the process clones itself, the new thread is automatically traced and registered in the `threads` property of the `debugger`.\n\nEach thread exposes its own set of register access properties. Control flow is synchronous between threads: they either are all stopped or all running, and every time a thread stops, all the others are stopped. This is done to avoid concurrency issues.\n\n```python\nd = debugger(\"./threaded_test\")\nd.run()\nd.cont()\n\nfor _ in range(15):\n    for thread in d.threads:\n        print(thread.thread_id, hex(thread.rip))\n\n    d.cont()\n\nd.kill()\n```\n\n## Breakpoints\nThe `Breakpoint` class represents a breakpoint for the traced process. It can be created with a function of the debugger object in the following way:\n```python\nbp = d.breakpoint(position=0x1234, hardware=False, condition=None, length=1, callback=None)\n```\n`position` represents a memory address or a symbol of the ELF. The `hardware` flag trivially controls whether or not the breakpoint is hardware assisted (a maximum of 4 hardware breakpoints are allowed). `condition` and `length` are used to specify properties of hardware watchpoints (see next section). For any type of breakpoint, a `callback` function can be specified. When set, a breakpoint hit will trigger the callback and automatically resume the execution of the program.\n\nFor your convenience, a Breakpoint object counts the number of times the breakpoint has been hit. The current count can be accessed though the `hit_count` property:\n```python\nbp = d.breakpoint(0x1234)\nd.cont()\n\nfor i in range(15):\n    d.wait()\n\n    assert d.rip == bp.address\n    assert bp.hit_count == i\n\n    d.cont()\n```\n\nSince breakpoints in the program text are shared between threads, you can check if a breakpoint was hit on a specific thread with the `hit_on` function:\n```python\n# Assuming to have thread n.3\nchosen_thread = d.threads[3]\n\nbp = d.breakpoint(0x1234)\nd.cont()\n\nfor i in range(15):\n    \n    assert d.rip == bp.address\n    assert bp.hit_on(chosen_thread)\n\n    if bp.hit_on(chosen_thread):\n        ...\n\n    d.cont()\n```\n\n## Watchpoints\nThe suggested way to insert a watchpoint is the following:\n\n```python\nwp = d.watchpoint(position=0x1234, condition='rw', length=8, callback=None)\n```\nThe function returns a `Breakpoint` object, which can be interacted with in the same manner as traditional breakpoints. Valid conditions for the breakpoint are `w`, `rw` and `x` (default is `w`). It is also possible to specify the length of the word being watched (default is 1 byte).\n\n## Syscall Hooking\nlibdebug supports hooking system calls in the debugged binary in the following way:\n```python\ndef on_enter_open(d: ThreadContext, syscall_number: int):\n    print(\"entering open\")\n    d.syscall_arg0 = 0x1\n\ndef on_exit_open(d: ThreadContext, syscall_number: int):\n    print(\"exiting open\")\n    d.syscall_return = 0x0\n\nsys_hook = d.hook_syscall(syscall=\"open\", on_enter=on_enter_open, on_exit=on_exit_open)\n```\n`hook_syscall` accepts either a number or a string.\nIf the user provides a string, a syscall definition list is downloaded from [syscalls.mebeim.net](https://syscalls.mebeim.net/?table=x86/64/x64/latest) and cached internally in order to convert it into the corresponding syscall number.\n`on_enter` and `on_exit` are optional: they are called only if present. At least one callback is required between `on_enter` and `on_exit` to make the hook meaningful.\n\nSyscall hooks, just like breakpoints, can be enabled and disabled, and automatically count the number of invocations:\n```py\nsys_hook.disable()\nsys_hook.enable()\n\nprint(sys_hook.hit_count)\n```\nNote: there can be at most one hook for each syscall.\n\n## Builtin Hooks\nlibdebug provides some easy-to-use builtin hooks for syscalls:\n- antidebug_escaping\nAutomatically patches binaries which use the return value of `ptrace(PTRACE_TRACEME, 0, 0, 0)` to verify that no external debugger is present.\nUsage:\n```py\nfrom libdebug import debugger\nfrom libdebug.builtin import antidebug_escaping\n\nd = debugger(...)\nd.run()\n\nantidebug_escaping(d)\n\nd.cont()\n[...]\n```\n\n- pretty_print_syscall\nInstalls a hook on any syscall that automatically prints the input arguments and the corresponding return values, just like strace does.\nBy default, it hooks every syscall. The user can specify either a list of syscalls to hook onto, or a list of syscalls to exclude from hooking.\nUsage:\n```py\nfrom libdebug import debugger\nfrom libdebug.builtin import pretty_print_syscall\n\nd = debugger(\"/usr/bin/ls\")\nd.run()\n\npretty_print_syscall(d,\n    # syscalls = [\"execve\", \"open\", \"getcwd\"],\n    # exclude = [\"fork\", \"vfork\", \"exit_group\"]\n)\n\nd.cont()\n[...]\n```\nThis results in an output similar to:\n```\nopenat(int dfd = 0xffffff9c, const char *filename = 0x7ffff7f241b0, int flags = 0x80000, umode_t mode = 0x0) = 0x3\nnewfstatat(int dfd = 0x3, const char *filename = 0x7ffff7f1abd5, struct stat *statbuf = 0x7ffff7f53840, int flag = 0x1000) = 0x0\nmmap(unsigned long addr = 0x0, unsigned long len = 0xd5f8ef0, unsigned long prot = 0x1, unsigned long flags = 0x2, unsigned long fd = 0x3, unsigned long off = 0x0) = 0x7fffea600000\nclose(unsigned int fd = 0x3) = 0x0\nioctl(unsigned int fd = 0x1, unsigned int cmd = 0x5401, unsigned long arg = 0x7fffffffd3a0) = 0x0\nioctl(unsigned int fd = 0x1, unsigned int cmd = 0x5413, unsigned long arg = 0x7fffffffd4c0) = 0x0\nopenat(int dfd = 0xffffff9c, const char *filename = 0x5555555806c0, int flags = 0x90800, umode_t mode = 0x0) = 0x3\nnewfstatat(int dfd = 0x3, const char *filename = 0x7ffff7f1abd5, struct stat *statbuf = 0x7fffffffd070, int flag = 0x1000) = 0x0\ngetdents64(unsigned int fd = 0x3, struct linux_dirent64 *dirent = 0x555555580710, unsigned int count = 0x8000) = 0x50\ngetdents64(unsigned int fd = 0x3, struct linux_dirent64 *dirent = 0x555555580710, unsigned int count = 0x8000) = 0x0\n[...]\n```\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2023 - 2024 Mario Polino, Gabriele Digregorio, Roberto Bertolini, Francesco Panebianco.  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
    "summary": "A Python library for the debugging of binary executables.",
    "version": "0.4.2",
    "project_urls": {
        "homepage": "https://pypi.org/project/libdebug/",
        "issues": "https://github.com/libdebug/libdebug/issues",
        "repository": "https://github.com/libdebug/libdebug/"
    },
    "split_keywords": [
        "libdebug",
        " debugger",
        " elf",
        " ptrace",
        " gdb",
        " debug",
        " ctf",
        " reverse-engineering",
        " reverse",
        " rev",
        " scriptable",
        " script"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7b8d439519ff892b1a551f144c8a7f3f34a709971e885c8b730a896349d3b685",
                "md5": "651af677d02cfd3f530151d37326c89a",
                "sha256": "0990657157907076042ffa66cdc83d98bbdfc4e9177c464f52d78e0d1f589b48"
            },
            "downloads": -1,
            "filename": "libdebug-0.4.2.tar.gz",
            "has_sig": false,
            "md5_digest": "651af677d02cfd3f530151d37326c89a",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 53675,
            "upload_time": "2024-04-30T22:01:35",
            "upload_time_iso_8601": "2024-04-30T22:01:35.799559Z",
            "url": "https://files.pythonhosted.org/packages/7b/8d/439519ff892b1a551f144c8a7f3f34a709971e885c8b730a896349d3b685/libdebug-0.4.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-30 22:01:35",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "libdebug",
    "github_project": "libdebug",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "libdebug"
}
        
Elapsed time: 0.24258s