• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Gallopsled / pwntools / 5647432354

pending completion
5647432354

push

github-actions

Arusekk
ci: stabilize coverage

4502 of 7155 branches covered (62.92%)

12647 of 17025 relevant lines covered (74.28%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.02
/pwnlib/memleak.py
1
from __future__ import absolute_import
1✔
2
from __future__ import division
1✔
3

4
import ctypes
1✔
5
import functools
1✔
6
import string
1✔
7

8
import six
1✔
9
from six.moves import range
1✔
10

11
from pwnlib.context import context
1✔
12
from pwnlib.log import getLogger
1✔
13
from pwnlib.util.packing import pack, _p8lu
1✔
14
from pwnlib.util.packing import unpack
1✔
15

16
log = getLogger(__name__)
1✔
17

18
__all__ = ['MemLeak', 'RelativeMemLeak']
1✔
19

20
class MemLeak(object):
1✔
21
    """MemLeak is a caching and heuristic tool for exploiting memory leaks.
22

23
    It can be used as a decorator, around functions of the form:
24

25
        def some_leaker(addr):
26
            ...
27
            return data_as_string_or_None
28

29
    It will cache leaked memory (which requires either non-randomized static
30
    data or a continouous session). If required, dynamic or known data can be
31
    set with the set-functions, but this is usually not required. If a byte
32
    cannot be recovered, it will try to leak nearby bytes in the hope that the
33
    byte is recovered as a side-effect.
34

35
    Arguments:
36
        f (function): The leaker function.
37
        search_range (int): How many bytes to search backwards in case an address does not work.
38
        reraise (bool): Whether to reraise call :func:`pwnlib.log.warning` in case the leaker function throws an exception.
39

40
    Example:
41

42
        >>> import pwnlib
43
        >>> binsh = pwnlib.util.misc.read('/bin/sh')
44
        >>> @pwnlib.memleak.MemLeak
45
        ... def leaker(addr):
46
        ...     print("leaking 0x%x" % addr)
47
        ...     return binsh[addr:addr+4]
48
        >>> leaker.s(0)[:4]
49
        leaking 0x0
50
        leaking 0x4
51
        b'\\x7fELF'
52
        >>> leaker[:4]
53
        b'\\x7fELF'
54
        >>> hex(leaker.d(0))
55
        '0x464c457f'
56
        >>> hex(leaker.clearb(1))
57
        '0x45'
58
        >>> hex(leaker.d(0))
59
        leaking 0x1
60
        '0x464c457f'
61
        >>> @pwnlib.memleak.MemLeak
62
        ... def leaker_nonulls(addr):
63
        ...     print("leaking 0x%x" % addr)
64
        ...     if addr & 0xff == 0:
65
        ...         return None
66
        ...     return binsh[addr:addr+4]
67
        >>> leaker_nonulls.d(0) is None
68
        leaking 0x0
69
        True
70
        >>> leaker_nonulls[0x100:0x104] == binsh[0x100:0x104]
71
        leaking 0x100
72
        leaking 0xff
73
        leaking 0x103
74
        True
75

76
        >>> memory = {-4+i: c.encode() for i,c in enumerate('wxyzABCDE')}
77
        >>> def relative_leak(index):
78
        ...     return memory.get(index, None)
79
        >>> leak = pwnlib.memleak.MemLeak(relative_leak, relative = True)
80
        >>> leak[-1:2]
81
        b'zAB'
82
    """
83
    def __init__(self, f, search_range = 20, reraise = True, relative = False):
1✔
84
        self.leak = f
1✔
85
        self.search_range = search_range
1✔
86
        self.reraise = reraise
1✔
87
        self.relative = relative
1✔
88

89
        # Map of address: byte for all bytes received
90
        self.cache = {}
1✔
91

92
        functools.update_wrapper(self, f)
1✔
93

94
    def __repr__(self):
1✔
95
        return "%s.%s(%r, search_range=%i, reraise=%s)" % (
×
96
            self.__class__.__module__,
97
            self.__class__.__name__,
98
            self.leak,
99
            self.search_range,
100
            self.reraise
101
        )
102

103
    def __call__(self, *a, **kw):
1✔
104
        return self.leak(*a, **kw)
×
105

106
    def struct(self, address, struct):
1✔
107
        """struct(address, struct) => structure object
108
        Leak an entire structure.
109
        Arguments:
110
            address(int):  Addess of structure in memory
111
            struct(class): A ctypes structure to be instantiated with leaked data
112
        Return Value:
113
            An instance of the provided struct class, with the leaked data decoded
114

115
        Examples:
116

117
            >>> @pwnlib.memleak.MemLeak
118
            ... def leaker(addr):
119
            ...     return b"A"
120
            >>> e = leaker.struct(0, pwnlib.elf.Elf32_Phdr)
121
            >>> hex(e.p_paddr)
122
            '0x41414141'
123

124
        """
125
        size = ctypes.sizeof(struct)
1✔
126
        data = self.n(address, size)
1✔
127
        obj = struct.from_buffer_copy(data)
1✔
128
        return obj
1✔
129

130
    def field(self, address, obj):
1✔
131
        """field(address, field) => a structure field.
132

133
        Leak a field from a structure.
134

135
        Arguments:
136
            address(int): Base address to calculate offsets from
137
            field(obj):   Instance of a ctypes field
138

139
        Return Value:
140
            The type of the return value will be dictated by
141
            the type of ``field``.
142
        """
143
        size   = obj.size
1✔
144
        offset = obj.offset
1✔
145
        data   = self.n(address + offset, size)
1✔
146
        if not data:
1!
147
            return None
×
148
        return unpack(data, size*8)
1✔
149

150
    def field_compare(self, address, obj, expected):
1✔
151
        """field_compare(address, field, expected) ==> bool
152

153
        Leak a field from a structure, with an expected value.
154
        As soon as any mismatch is found, stop leaking the structure.
155

156
        Arguments:
157
            address(int): Base address to calculate offsets from
158
            field(obj):   Instance of a ctypes field
159
            expected(int,bytes): Expected value
160

161
        Return Value:
162
            The type of the return value will be dictated by
163
            the type of ``field``.
164
        """
165

166
        if isinstance(expected, six.integer_types):
1!
167
            expected = pack(expected, bytes=obj.size)
1✔
168
        elif not isinstance(expected, bytes):
×
169
            raise TypeError("Expected value must be an int or bytes")
×
170

171
        assert obj.size == len(expected)
1✔
172

173
        return self.compare(address + obj.offset, expected)
1✔
174

175
    def _leak(self, addr, n, recurse=True):
1✔
176
        """_leak(addr, n) => str
177

178
        Leak ``n`` consecutive bytes starting at ``addr``.
179

180
        Returns:
181
            A string of length ``n``, or :const:`None`.
182
        """
183
        if not self.relative and addr < 0:
1✔
184
            return None
1✔
185

186
        addresses = [addr+i for i in range(n)]
1✔
187

188
        for address in addresses:
1✔
189
            # Cache hit
190
            if address in self.cache:
1✔
191
                continue
1✔
192

193
            # Cache miss, get the data from the leaker
194
            data = None
1✔
195
            try:
1✔
196
                data = self.leak(address)
1✔
197
            except Exception as e:
×
198
                if self.reraise:
×
199
                    raise
×
200

201
            if data:
1✔
202
                for i,byte in enumerate(bytearray(data)):
1✔
203
                    self.cache[address+i] = _p8lu(byte)
1✔
204

205
            # We could not leak this particular byte, search backwards
206
            # to see if another request will satisfy it
207
            elif recurse:
1✔
208
                for i in range(1, self.search_range):
1✔
209
                    data = self._leak(address-i, i+1, False)
1✔
210
                    if address in self.cache:
1✔
211
                        break
1✔
212
                else:
213
                    return None
1✔
214

215
        # Ensure everything is in the cache
216
        if not all(a in self.cache for a in addresses):
1✔
217
            return None
1✔
218

219
        # Cache is filled, satisfy the request
220
        return b''.join(self.cache[addr+i] for i in range(n))
1✔
221

222
    def raw(self, addr, numb):
1✔
223
        """raw(addr, numb) -> list
224

225
        Leak `numb` bytes at `addr`"""
226
        return [self._leak(a, 1) for a in range(addr, addr+numb)]
×
227

228

229
    def _b(self, addr, ndx, size):
1✔
230
        addr += ndx * size
1✔
231
        data = self._leak(addr, size)
1✔
232

233
        if not data:
1✔
234
            return None
1✔
235

236
        return unpack(data, 8*size)
1✔
237

238
    def b(self, addr, ndx = 0):
1✔
239
        """b(addr, ndx = 0) -> int
240

241
        Leak byte at ``((uint8_t*) addr)[ndx]``
242

243
        Examples:
244

245
            >>> import string
246
            >>> data = string.ascii_lowercase.encode()
247
            >>> l = MemLeak(lambda a: data[a:a+2], reraise=False)
248
            >>> l.b(0) == ord('a')
249
            True
250
            >>> l.b(25) == ord('z')
251
            True
252
            >>> l.b(26) is None
253
            True
254
        """
255
        return self._b(addr, ndx, 1)
1✔
256

257
    def w(self, addr, ndx = 0):
1✔
258
        """w(addr, ndx = 0) -> int
259

260
        Leak word at ``((uint16_t*) addr)[ndx]``
261

262
        Examples:
263

264
            >>> import string
265
            >>> data = string.ascii_lowercase.encode()
266
            >>> l = MemLeak(lambda a: data[a:a+4], reraise=False)
267
            >>> l.w(0) == unpack(b'ab', 16)
268
            True
269
            >>> l.w(24) == unpack(b'yz', 16)
270
            True
271
            >>> l.w(25) is None
272
            True
273
        """
274
        return self._b(addr, ndx, 2)
1✔
275

276
    def d(self, addr, ndx = 0):
1✔
277
        """d(addr, ndx = 0) -> int
278

279
        Leak dword at ``((uint32_t*) addr)[ndx]``
280

281
        Examples:
282

283
            >>> import string
284
            >>> data = string.ascii_lowercase.encode()
285
            >>> l = MemLeak(lambda a: data[a:a+8], reraise=False)
286
            >>> l.d(0) == unpack(b'abcd', 32)
287
            True
288
            >>> l.d(22) == unpack(b'wxyz', 32)
289
            True
290
            >>> l.d(23) is None
291
            True
292
        """
293
        return self._b(addr, ndx, 4)
1✔
294

295
    def q(self, addr, ndx = 0):
1✔
296
        """q(addr, ndx = 0) -> int
297

298
        Leak qword at ``((uint64_t*) addr)[ndx]``
299

300
        Examples:
301

302
            >>> import string
303
            >>> data = string.ascii_lowercase.encode()
304
            >>> l = MemLeak(lambda a: data[a:a+16], reraise=False)
305
            >>> l.q(0) == unpack(b'abcdefgh', 64)
306
            True
307
            >>> l.q(18) == unpack(b'stuvwxyz', 64)
308
            True
309
            >>> l.q(19) is None
310
            True
311
        """
312
        return self._b(addr, ndx, 8)
1✔
313

314
    def p(self, addr, ndx = 0):
1✔
315
        """p(addr, ndx = 0) -> int
316

317
        Leak a pointer-width value at ``((void**) addr)[ndx]``
318
        """
319
        return self._b(addr, ndx, context.bytes)
1✔
320

321
    def s(self, addr):
1✔
322
        r"""s(addr) -> str
323

324
        Leak bytes at `addr` until failure or a nullbyte is found
325

326
        Return:
327
            A string, without a NULL terminator.
328
            The returned string will be empty if the first byte is
329
            a NULL terminator, or if the first byte could not be
330
            retrieved.
331

332
        Examples:
333

334
            >>> data = b"Hello\x00World"
335
            >>> l = MemLeak(lambda a: data[a:a+4], reraise=False)
336
            >>> l.s(0) == b"Hello"
337
            True
338
            >>> l.s(5) == b""
339
            True
340
            >>> l.s(6) == b"World"
341
            True
342
            >>> l.s(999) == b""
343
            True
344
        """
345

346
        # This relies on the behavior of _leak to fill the cache
347
        orig = addr
1✔
348
        while self.b(addr):
1✔
349
            addr += 1
1✔
350
        return self._leak(orig, addr-orig)
1✔
351

352
    def n(self, addr, numb):
1✔
353
        """n(addr, ndx = 0) -> str
354

355
        Leak `numb` bytes at `addr`.
356

357
        Returns:
358
            A string with the leaked bytes, will return `None` if any are missing
359

360
        Examples:
361

362
            >>> import string
363
            >>> data = string.ascii_lowercase.encode()
364
            >>> l = MemLeak(lambda a: data[a:a+4], reraise=False)
365
            >>> l.n(0,1) == b'a'
366
            True
367
            >>> l.n(0,26) == data
368
            True
369
            >>> len(l.n(0,26)) == 26
370
            True
371
            >>> l.n(0,27) is None
372
            True
373
        """
374
        return self._leak(addr, numb) or None
1✔
375

376

377
    def _clear(self, addr, ndx, size):
1✔
378
        addr += ndx * size
1✔
379
        data = [self.cache.pop(x, None) for x in range(addr, addr+size)]
1✔
380

381
        if not all(data):
1✔
382
            return None
1✔
383

384
        return unpack(b''.join(data), size*8)
1✔
385

386
    def clearb(self, addr, ndx = 0):
1✔
387
        """clearb(addr, ndx = 0) -> int
388

389
        Clears byte at ``((uint8_t*)addr)[ndx]`` from the cache and
390
        returns the removed value or `None` if the address was not completely set.
391

392
        Examples:
393

394
            >>> l = MemLeak(lambda a: None)
395
            >>> l.cache = {0:b'a'}
396
            >>> l.n(0,1) == b'a'
397
            True
398
            >>> l.clearb(0) == unpack(b'a', 8)
399
            True
400
            >>> l.cache
401
            {}
402
            >>> l.clearb(0) is None
403
            True
404
        """
405
        return self._clear(addr, ndx, 1)
1✔
406

407
    def clearw(self, addr, ndx = 0):
1✔
408
        """clearw(addr, ndx = 0) -> int
409

410
        Clears word at ``((uint16_t*)addr)[ndx]`` from the cache and
411
        returns the removed value or `None` if the address was not completely set.
412

413
        Examples:
414

415
            >>> l = MemLeak(lambda a: None)
416
            >>> l.cache = {0: b'a', 1: b'b'}
417
            >>> l.n(0, 2) == b'ab'
418
            True
419
            >>> l.clearw(0) == unpack(b'ab', 16)
420
            True
421
            >>> l.cache
422
            {}
423
        """
424
        return self._clear(addr, ndx, 2)
1✔
425

426
    def cleard(self, addr, ndx = 0):
1✔
427
        """cleard(addr, ndx = 0) -> int
428

429
        Clears dword at ``((uint32_t*)addr)[ndx]`` from the cache and
430
        returns the removed value or `None` if the address was not completely set.
431

432
        Examples:
433

434
            >>> l = MemLeak(lambda a: None)
435
            >>> l.cache = {0: b'a', 1: b'b', 2: b'c', 3: b'd'}
436
            >>> l.n(0, 4) == b'abcd'
437
            True
438
            >>> l.cleard(0) == unpack(b'abcd', 32)
439
            True
440
            >>> l.cache
441
            {}
442
        """
443
        return self._clear(addr, ndx, 4)
1✔
444

445
    def clearq(self, addr, ndx = 0):
1✔
446
        """clearq(addr, ndx = 0) -> int
447

448
        Clears qword at ``((uint64_t*)addr)[ndx]`` from the cache and
449
        returns the removed value or `None` if the address was not completely set.
450

451
        Examples:
452

453
            >>> c = MemLeak(lambda addr: b'')
454
            >>> c.cache = {x:b'x' for x in range(0x100, 0x108)}
455
            >>> c.clearq(0x100) == unpack(b'xxxxxxxx', 64)
456
            True
457
            >>> c.cache == {}
458
            True
459
        """
460
        return self._clear(addr, ndx, 8)
1✔
461

462

463
    def _set(self, addr, val, ndx, size):
1✔
464
        addr += ndx * size
1✔
465
        for i,b in enumerate(bytearray(pack(val, size*8))):
1✔
466
            self.cache[addr+i] = _p8lu(b)
1✔
467

468
    def setb(self, addr, val, ndx = 0):
1✔
469
        """Sets byte at ``((uint8_t*)addr)[ndx]`` to `val` in the cache.
470

471
        Examples:
472

473
            >>> l = MemLeak(lambda x: b'')
474
            >>> l.cache == {}
475
            True
476
            >>> l.setb(33, 0x41)
477
            >>> l.cache == {33: b'A'}
478
            True
479
        """
480
        return self._set(addr, val, ndx, 1)
1✔
481

482
    def setw(self, addr, val, ndx = 0):
1✔
483
        r"""Sets word at ``((uint16_t*)addr)[ndx]`` to `val` in the cache.
484

485
        Examples:
486

487
            >>> l = MemLeak(lambda x: b'')
488
            >>> l.cache == {}
489
            True
490
            >>> l.setw(33, 0x41)
491
            >>> l.cache == {33: b'A', 34: b'\x00'}
492
            True
493
        """
494
        return self._set(addr, val, ndx, 2)
1✔
495

496
    def setd(self, addr, val, ndx = 0):
1✔
497
        """Sets dword at ``((uint32_t*)addr)[ndx]`` to `val` in the cache.
498

499
        Examples:
500
            See :meth:`setw`.
501
        """
502
        return self._set(addr, val, ndx, 4)
×
503

504
    def setq(self, addr, val, ndx = 0):
1✔
505
        """Sets qword at ``((uint64_t*)addr)[ndx]`` to `val` in the cache.
506

507
        Examples:
508
            See :meth:`setw`.
509
        """
510
        return self._set(addr, val, ndx, 8)
×
511

512
    def sets(self, addr, val, null_terminate = True):
1✔
513
        r"""Set known string at `addr`, which will be optionally be null-terminated
514

515
        Note that this method is a bit dumb about how it handles the data.
516
        It will null-terminate the data, but it will not stop at the first null.
517

518
        Examples:
519

520
            >>> l = MemLeak(lambda x: b'')
521
            >>> l.cache == {}
522
            True
523
            >>> l.sets(0, b'H\x00ello')
524
            >>> l.cache == {0: b'H', 1: b'\x00', 2: b'e', 3: b'l', 4: b'l', 5: b'o', 6: b'\x00'}
525
            True
526
        """
527
        if null_terminate:
1!
528
            val += b'\x00'
1✔
529

530
        for i,b in enumerate(bytearray(val)):
1✔
531
            self.cache[addr+i] = _p8lu(b)
1✔
532

533
    def __getitem__(self, item):
1✔
534
        if isinstance(item, slice):
1!
535
            start = item.start or 0
1✔
536
            stop  = item.stop
1✔
537
            step  = item.step
1✔
538
        else:
539
            start, stop, step = (item, item+1, 1)
×
540

541
        if None in (stop, start):
1!
542
            log.error("Cannot perform unbounded leaks")
×
543

544
        return self.n(start, stop-start)[::step]
1✔
545

546
    def compare(self, address, bts):
1✔
547
        for i, byte in enumerate(bytearray(bts)):
1✔
548
            if self.n(address + i, 1) != _p8lu(byte):
1✔
549
                return False
1✔
550
        return True
1✔
551

552
    @staticmethod
1✔
553
    def NoNulls(function):
1✔
554
        """Wrapper for leak functions such that addresses which contain NULL
555
        bytes are not leaked.
556

557
        This is useful if the address which is used for the leak is read in via
558
        a string-reading function like ``scanf("%s")`` or smilar.
559
        """
560

561
        @functools.wraps(function, updated=[])
×
562
        def null_wrapper(address, *a, **kw):
×
563
            if b'\x00' in pack(address):
×
564
                log.info('Ignoring leak request for %#x: Contains NULL bytes' % address)
×
565
                return None
×
566
            return function(address, *a, **kw)
×
567

568
        return MemLeak(null_wrapper)
×
569

570
    @staticmethod
1✔
571
    def NoWhitespace(function):
1✔
572
        """Wrapper for leak functions such that addresses which contain whitespace
573
        bytes are not leaked.
574

575
        This is useful if the address which is used for the leak is read in via
576
        e.g. ``scanf()``.
577
        """
578

579
        @functools.wraps(function, updated=[])
×
580
        def whitespace_wrapper(address, *a, **kw):
×
581
            if set(pack(address)) & set(string.whitespace.encode()):
×
582
                log.info('Ignoring leak request for %#x: Contains whitespace' % address)
×
583
                return None
×
584
            return function(address, *a, **kw)
×
585

586
        return MemLeak(whitespace_wrapper)
×
587

588
    @staticmethod
1✔
589
    def NoNewlines(function):
1✔
590
        """Wrapper for leak functions such that addresses which contain newline
591
        bytes are not leaked.
592

593
        This is useful if the address which is used for the leak is provided by
594
        e.g. ``fgets()``.
595
        """
596

597
        @functools.wraps(function, updated=[])
×
598
        def whitespace_wrapper(address, *a, **kw):
×
599
            if b'\n' in pack(address):
×
600
                log.info('Ignoring leak request for %#x: Contains newlines' % address)
×
601
                return None
×
602
            return function(address, *a, **kw)
×
603

604
        return MemLeak(whitespace_wrapper)
×
605

606
    @staticmethod
1✔
607
    def String(function):
1✔
608
        """Wrapper for leak functions which leak strings, such that a NULL
609
        terminator is automaticall added.
610

611
        This is useful if the data leaked is printed out as a NULL-terminated
612
        string, via e.g. ``printf()``.
613
        """
614

615
        @functools.wraps(function, updated=[])
×
616
        def string_wrapper(address, *a, **kw):
×
617
            result = function(address, *a, **kw)
×
618
            if isinstance(result, str) and not isinstance(result, bytes):
×
619
                result = result.encode('latin1')
×
620
            if isinstance(result, bytes):
×
621
                result += b'\x00'
×
622
            return result
×
623

624
        return MemLeak(string_wrapper)
×
625

626
    # Aliases for convenience
627
    u64 = q
1✔
628
    u32 = d
1✔
629
    u16 = w
1✔
630
    u8 = b
1✔
631

632
    p64 = setq
1✔
633
    p32 = setd
1✔
634
    p16 = setw
1✔
635
    p8 = setb
1✔
636

637
class RelativeMemLeak(MemLeak):
1✔
638
    def __init__(self, *a, **kw):
1✔
639
        kw.setdefault('relative', True)
×
640
        super(RelativeMemLeak, self).__init__(*a, **kw)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc