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

Gallopsled / pwntools / 24009977420

05 Apr 2026 08:37PM UTC coverage: 73.563% (-0.04%) from 73.602%
24009977420

push

github

web-flow
fix: pad bytes fields to correct field size in FileStructure (#2694)

* fix: pad bytes fields to correct field size in FileStructure

When setting bytes fields (e.g. _IO_save_base) on FileStructure, the
value was padded to context.bytes instead of the actual field size from
self.length[val]. This caused struct.pack errors for fields whose size
differs from the pointer size.

Fixes #2693

* Cleanup test and add CHANGELOG

---------

Co-authored-by: nightcityblade <nightcityblade@gmail.com>
Co-authored-by: Peace-Maker <peacemakerctf@gmail.com>

3810 of 6422 branches covered (59.33%)

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

8 existing lines in 2 files now uncovered.

13326 of 18115 relevant lines covered (73.56%)

0.74 hits per line

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

94.12
/pwnlib/filepointer.py
1
# -*- coding: utf-8 -*-
2

3
r"""
4
File Structure Exploitation
5

6
struct FILE (_IO_FILE) is the structure for File Streams.
7
This offers various targets for exploitation on an existing bug in the code.
8
Examples - ``_IO_buf_base`` and ``_IO_buf_end`` for reading data to arbitrary location.
9

10
Remembering the offsets of various structure members while faking a FILE structure can be difficult,
11
so this python class helps you with that. Example-
12

13
>>> context.clear(arch='amd64')
14
>>> fileStr = FileStructure(null=0xdeadbeef)
15
>>> fileStr.vtable = 0xcafebabe
16
>>> payload = bytes(fileStr)
17
>>> payload
18
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\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\xef\xbe\xad\xde\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xef\xbe\xad\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbe\xba\xfe\xca\x00\x00\x00\x00'
19

20
Now payload contains the FILE structure with its vtable pointer pointing to 0xcafebabe
21

22
Currently only 'amd64' and 'i386' architectures are supported
23
"""
24

25
from __future__ import absolute_import
1✔
26
from __future__ import division
1✔
27

28
from pwnlib.context import context
1✔
29
from pwnlib.log import getLogger
1✔
30
from pwnlib.util.misc import python_2_bytes_compatible
1✔
31
from pwnlib.util.packing import pack
1✔
32

33
log = getLogger(__name__)
1✔
34

35
length=0
1✔
36
size='size'
1✔
37
name='name'
1✔
38

39
variables={
1✔
40
    0:{name:'flags',size:length},
41
    1:{name:'_IO_read_ptr',size:length},
42
    2:{name:'_IO_read_end',size:length},
43
    3:{name:'_IO_read_base',size:length},
44
    4:{name:'_IO_write_base',size:length},
45
    5:{name:'_IO_write_ptr',size:length},
46
    6:{name:'_IO_write_end',size:length},
47
    7:{name:'_IO_buf_base',size:length},
48
    8:{name:'_IO_buf_end',size:length},
49
    9:{name:'_IO_save_base',size:length},
50
    10:{name:'_IO_backup_base',size:length},
51
    11:{name:'_IO_save_end',size:length},
52
    12:{name:'markers',size:length},
53
    13:{name:'chain',size:length},
54
    14:{name:'fileno',size:4},
55
    15:{name:'_flags2',size:4},
56
    16:{name:'_old_offset',size:length},
57
    17:{name:'_cur_column',size:2},
58
    18:{name:'_vtable_offset',size:1},
59
    19:{name:'_shortbuf',size:1},
60
    20:{name:'unknown1',size:-4},
61
    21:{name:'_lock',size:length},
62
    22:{name:'_offset',size:8},
63
    23:{name:'_codecvt',size:length},
64
    24:{name:'_wide_data',size:length},
65
    25:{name:'unknown2',size:length},
66
    26:{name:'vtable',size:length}
67
}
68

69
del name, size, length
1✔
70

71

72
def update_var(l):
1✔
73
    r"""
74
    Since different members of the file structure have different sizes, we need to keep track of the sizes. The following function is used by the FileStructure class to initialise the lengths of the various fields.
75

76
    Arguments:
77
        l(int)
78
            l=8 for 'amd64' architecture and l=4 for 'i386' architecture
79

80
    Return Value:
81
        Returns a dictionary in which each field is mapped to its corresponding length according to the architecture set
82

83
    Examples:
84

85
        >>> update_var(8)
86
        {'flags': 8, '_IO_read_ptr': 8, '_IO_read_end': 8, '_IO_read_base': 8, '_IO_write_base': 8, '_IO_write_ptr': 8, '_IO_write_end': 8, '_IO_buf_base': 8, '_IO_buf_end': 8, '_IO_save_base': 8, '_IO_backup_base': 8, '_IO_save_end': 8, 'markers': 8, 'chain': 8, 'fileno': 4, '_flags2': 4, '_old_offset': 8, '_cur_column': 2, '_vtable_offset': 1, '_shortbuf': 1, 'unknown1': 4, '_lock': 8, '_offset': 8, '_codecvt': 8, '_wide_data': 8, 'unknown2': 48, 'vtable': 8}
87
    """
88
    var={}
1✔
89
    for i in variables:
1✔
90
        var[variables[i]['name']]=variables[i]['size']
1✔
91
    for i in var:
1✔
92
        if var[i]<=0:
1✔
93
            var[i]+=l
1✔
94
    if l==4:
1✔
95
        var['unknown2']=56
1✔
96
    else:
97
        var['unknown2']=48
1✔
98
    return var
1✔
99

100

101
@python_2_bytes_compatible
1✔
102
class FileStructure(object):
1✔
103
    r"""
104
    Crafts a FILE structure, with default values for some fields, like _lock which should point to null ideally, set.
105

106
    Arguments:
107
        null(int)
108
            A pointer to NULL value in memory. This pointer can lie in any segment (stack, heap, bss, libc etc)
109

110
    Examples:
111

112
        FILE structure with flags as 0xfbad1807 and _IO_buf_base and _IO_buf_end pointing to 0xcafebabe and 0xfacef00d
113

114
        >>> context.clear(arch='amd64')
115
        >>> fileStr = FileStructure(null=0xdeadbeeef)
116
        >>> fileStr.flags = 0xfbad1807
117
        >>> fileStr._IO_buf_base = 0xcafebabe
118
        >>> fileStr._IO_buf_end = 0xfacef00d
119
        >>> payload = bytes(fileStr)
120
        >>> payload
121
        b'\x07\x18\xad\xfb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbe\xba\xfe\xca\x00\x00\x00\x00\r\xf0\xce\xfa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\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\xef\xee\xdb\xea\r\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xef\xee\xdb\xea\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
122

123
        Check the length of the FileStructure
124

125
        >>> len(fileStr)
126
        224
127

128
        The definition for __repr__ orders the structure members and displays then in a dictionary format. It's useful when viewing a structure objet in python/IPython shell
129

130
        >>> q=FileStructure(0xdeadbeef)
131
        >>> q
132
        { flags: 0x0
133
         _IO_read_ptr: 0x0
134
         _IO_read_end: 0x0
135
         _IO_read_base: 0x0
136
         _IO_write_base: 0x0
137
         _IO_write_ptr: 0x0
138
         _IO_write_end: 0x0
139
         _IO_buf_base: 0x0
140
         _IO_buf_end: 0x0
141
         _IO_save_base: 0x0
142
         _IO_backup_base: 0x0
143
         _IO_save_end: 0x0
144
         markers: 0x0
145
         chain: 0x0
146
         fileno: 0x0
147
         _flags2: 0x0
148
         _old_offset: 0xffffffffffffffff
149
         _cur_column: 0x0
150
         _vtable_offset: 0x0
151
         _shortbuf: 0x0
152
         unknown1: 0x0
153
         _lock: 0xdeadbeef
154
         _offset: 0xffffffffffffffff
155
         _codecvt: 0x0
156
         _wide_data: 0xdeadbeef
157
         unknown2: 0x0
158
         vtable: 0x0}
159

160
        Bytes fields are padded to the correct field size.
161
        For example, unknown2 is 56 bytes on i386, so a short value gets
162
        zero-padded to 56, not to context.bytes (4):
163

164
        >>> context.clear(arch='i386')
165
        >>> fileStr2 = FileStructure(null=0)
166
        >>> fileStr2.vtable = 0x561859f0
167
        >>> old_len = len(bytes(fileStr2))
168
        >>> fileStr2.unknown2 = b'AB'
169
        >>> len(bytes(fileStr2)) == old_len
170
        True
171
    """
172

173
    vars_=[]
1✔
174
    length={}
1✔
175

176
    def __init__(self, null=0):
1✔
177
            self.vars_ = [variables[i]['name'] for i in sorted(variables.keys())]
1✔
178
            self.setdefault(null)
1✔
179
            self.length = update_var(context.bytes)
1✔
180
            self._old_offset = (1 << context.bits) - 1
1✔
181

182
    def __setattr__(self,item,value):
1✔
183
        if item in FileStructure.__dict__ or item in self.vars_:
1!
184
            object.__setattr__(self,item,value)
1✔
185
        else:
186
            log.error("Unknown variable %r" % item)
×
187

188
    def __repr__(self):
1✔
189
        structure=[]
1✔
190
        for i in self.vars_:
1✔
191
            structure.append(" %s: %#x" % (i, getattr(self, i)))
1✔
192
        return "{"+ "\n".join(structure)+"}"
1✔
193

194
    def __len__(self):
1✔
195
        return len(bytes(self))
1✔
196

197
    def __bytes__(self):
1✔
198
        structure = b''
1✔
199
        for val in self.vars_:
1✔
200
            if isinstance(getattr(self, val), bytes):
1✔
201
                structure += getattr(self, val).ljust(self.length[val], b'\x00')
1✔
202
            else:
203
                if self.length[val] > 0:
1✔
204
                    structure += pack(getattr(self, val), self.length[val]*8)
1✔
205
        return structure
1✔
206

207
    def struntil(self,v):
1✔
208
        r"""
209
        Payload for stuff till 'v' where 'v' is a structure member. This payload includes 'v' as well.
210

211
        Arguments:
212
            v(string)
213
                The name of the field uptil which the payload should be created.
214

215
        Example:
216

217
            Payload for data uptil _IO_buf_end
218

219
            >>> context.clear(arch='amd64')
220
            >>> fileStr = FileStructure(0xdeadbeef)
221
            >>> payload = fileStr.struntil("_IO_buf_end")
222
            >>> payload
223
            b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
224
        """
225
        if v not in self.vars_:
1!
226
            return b''
×
227
        structure = b''
1✔
228
        for val in self.vars_:
1!
229
            if isinstance(getattr(self, val), bytes):
1!
NEW
230
                structure += getattr(self, val).ljust(self.length[val], b'\x00')
×
231
            else:
232
                structure += pack(getattr(self, val), self.length[val]*8)
1✔
233
            if val == v:
1✔
234
                break
1✔
235
        return structure[:-1]
1✔
236

237
    def setdefault(self,null):
1✔
238
            self.flags=0
1✔
239
            self._IO_read_ptr=0
1✔
240
            self._IO_read_end=0
1✔
241
            self._IO_read_base=0
1✔
242
            self._IO_write_base=0
1✔
243
            self._IO_write_ptr=0
1✔
244
            self._IO_write_end=0
1✔
245
            self._IO_buf_base=0
1✔
246
            self._IO_buf_end=0
1✔
247
            self._IO_save_base=0
1✔
248
            self._IO_backup_base=0
1✔
249
            self._IO_save_end=0
1✔
250
            self.markers=0
1✔
251
            self.chain=0
1✔
252
            self.fileno=0
1✔
253
            self._flags2=0
1✔
254
            self._old_offset=0
1✔
255
            self._cur_column=0
1✔
256
            self._vtable_offset=0
1✔
257
            self._shortbuf=0
1✔
258
            self.unknown1=0
1✔
259
            self._lock=null
1✔
260
            self._offset=0xffffffffffffffff
1✔
261
            self._codecvt=0
1✔
262
            self._wide_data=null
1✔
263
            self.unknown2=0
1✔
264
            self.vtable=0
1✔
265

266
    def write(self,addr=0,size=0):
1✔
267
        r"""
268
        Writing data out from arbitrary memory address.
269

270
        Arguments:
271
            addr(int)
272
                The address from which data is to be printed to stdout
273
            size(int)
274
                The size, in bytes, of the data to be printed
275

276
        Example:
277

278
            Payload for writing 100 bytes to stdout from the address 0xcafebabe
279

280
            >>> context.clear(arch='amd64')
281
            >>> fileStr = FileStructure(0xdeadbeef)
282
            >>> payload = fileStr.write(addr=0xcafebabe, size=100)
283
            >>> payload
284
            b'\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbe\xba\xfe\xca\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbe\xba\xfe\xca\x00\x00\x00\x00"\xbb\xfe\xca\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00'
285
        """
286
        self.flags &=~8
1✔
287
        self.flags |=0x800
1✔
288
        self._IO_write_base = addr
1✔
289
        self._IO_write_ptr = addr+size
1✔
290
        self._IO_read_end = addr
1✔
291
        self.fileno = 1
1✔
292
        return self.struntil('fileno')
1✔
293

294
    def read(self,addr=0,size=0):
1✔
295
        r"""
296
        Reading data into arbitrary memory location.
297

298
        Arguments:
299
            addr(int)
300
                The address into which data is to be written from stdin
301
            size(int)
302
                The size, in bytes, of the data to be written
303

304
        Example:
305

306
            Payload for reading 100 bytes from stdin into the address 0xcafebabe
307

308
            >>> context.clear(arch='amd64')
309
            >>> fileStr = FileStructure(0xdeadbeef)
310
            >>> payload = fileStr.read(addr=0xcafebabe, size=100)
311
            >>> payload
312
            b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbe\xba\xfe\xca\x00\x00\x00\x00"\xbb\xfe\xca\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
313
        """
314
        self.flags &=~4
1✔
315
        self._IO_read_base = 0
1✔
316
        self._IO_read_ptr = 0
1✔
317
        self._IO_buf_base = addr
1✔
318
        self._IO_buf_end = addr+size
1✔
319
        self.fileno = 0
1✔
320
        return self.struntil('fileno')
1✔
321

322
    def orange(self,io_list_all,vtable):
1✔
323
        r"""
324
        Perform a House of Orange (https://github.com/shellphish/how2heap/blob/master/glibc_2.23/house_of_orange.c), provided you have libc leaks.
325

326
        Arguments:
327
            io_list_all(int)
328
                Address of _IO_list_all in libc.
329
            vtable(int)
330
                Address of the fake vtable in memory
331

332
        Example:
333

334
            Example payload if address of _IO_list_all is 0xfacef00d and fake vtable is at 0xcafebabe -
335

336
            >>> context.clear(arch='amd64')
337
            >>> fileStr = FileStructure(0xdeadbeef)
338
            >>> payload = fileStr.orange(io_list_all=0xfacef00d, vtable=0xcafebabe)
339
            >>> payload
340
            b'/bin/sh\x00a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfd\xef\xce\xfa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\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\xef\xbe\xad\xde\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xef\xbe\xad\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbe\xba\xfe\xca\x00\x00\x00\x00'
341
        """
342
        if context.bits == 64:
1!
343
            self.flags = b'/bin/sh\x00'
1✔
344
            self._IO_read_ptr = 0x61
1✔
345
            self._IO_read_base = io_list_all-0x10
1✔
346
        elif context.bits == 32:
×
347
            self.flags = b'sh\x00'
×
348
            self._IO_read_ptr = 0x121
×
349
            self._IO_read_base = io_list_all-0x8
×
350
        self._IO_write_base = 0
1✔
351
        self._IO_write_ptr = 1
1✔
352
        self.vtable = vtable
1✔
353
        return self.__bytes__()
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

© 2026 Coveralls, Inc