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

Gallopsled / pwntools / 18406296292

10 Oct 2025 12:19PM UTC coverage: 73.856% (+0.006%) from 73.85%
18406296292

Pull #2635

github

web-flow
Merge 5395c5157 into 8b6543466
Pull Request #2635: feat: add `overlap()` to merge 2 `FileStructure` objects

3858 of 6500 branches covered (59.35%)

43 of 50 new or added lines in 1 file covered. (86.0%)

5 existing lines in 2 files now uncovered.

13481 of 18253 relevant lines covered (73.86%)

0.74 hits per line

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

91.77
/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
import ctypes
1✔
29

30
from pwnlib.context import context
1✔
31
from pwnlib.log import getLogger
1✔
32
from pwnlib.util.packing import pack, unpack
1✔
33

34
log = getLogger(__name__)
1✔
35

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

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

74
del name, size, length
1✔
75

76

77
def _update_var(l):
1✔
78
    r"""
79
    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.
80

81
    Arguments:
82
        l(int)
83
            l=8 for 'amd64' architecture and l=4 for 'i386' architecture
84

85
    Return Value:
86
        Returns a dictionary in which each field is mapped to its corresponding length according to the architecture set
87

88
    Examples:
89

90
        >>> _update_var(8)
91
        {'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, '_freeres_list': 8, '_freeres_buf': 8, '_pad5': 8, '_mode': 4, '_unused2': 20, 'vtable': 8}
92
    """
93
    var={}
1✔
94
    for i in variables:
1✔
95
        var[variables[i]['name']]=variables[i]['size']
1✔
96
    for i in var:
1✔
97
        if var[i]<=0:
1✔
98
            var[i]+=l
1✔
99
    if l==4:
1!
100
        var['_unused2']=40
×
101
    else:
102
        var['_unused2']=20
1✔
103
    return var
1✔
104

105
class IO_flags:
1✔
106
    _IO_MAGIC =         0xFBAD0000 # Magic number
1✔
107
    _IO_MAGIC_MASK =    0xFFFF0000
1✔
108
    _IO_USER_BUF =          0x0001 # Don't deallocate buffer on close.
1✔
109
    _IO_UNBUFFERED =        0x0002
1✔
110
    _IO_NO_READS =          0x0004 # Reading not allowed.
1✔
111
    _IO_NO_WRITES =         0x0008 # Writing not allowed.
1✔
112
    _IO_EOF_SEEN =          0x0010
1✔
113
    _IO_ERR_SEEN =          0x0020
1✔
114
    _IO_DELETE_DONT_CLOSE = 0x0040 # Don't call close(_fileno) on close.
1✔
115
    _IO_LINKED =            0x0080 # In the list of all open files.
1✔
116
    _IO_IN_BACKUP =         0x0100
1✔
117
    _IO_LINE_BUF =          0x0200
1✔
118
    _IO_TIED_PUT_GET =      0x0400 # Put and get pointer move in unison.
1✔
119
    _IO_CURRENTLY_PUTTING = 0x0800
1✔
120
    _IO_IS_APPENDING =      0x1000
1✔
121
    _IO_IS_FILEBUF =        0x2000
1✔
122
    _IO_USER_LOCK =         0x8000
1✔
123

124
class IO_flags2:
1✔
125
    _IO_FLAGS2_MMAP = 1
1✔
126
    _IO_FLAGS2_NOTCANCEL = 2
1✔
127
    _IO_FLAGS2_USER_WBUF = 8
1✔
128
    _IO_FLAGS2_NOCLOSE = 32
1✔
129
    _IO_FLAGS2_CLOEXEC = 64
1✔
130
    _IO_FLAGS2_NEED_LOCK = 128
1✔
131

132
class _FlagsUnionBase(ctypes.Union):
1✔
133
    def __getattr__(self, name):
1✔
134
        if any(name == field[0] for field in self._flags_bits._fields_):
×
135
            return getattr(self._flags_bits, name)
×
136
        return super().__getattr__(name)
×
137
    
138
    def __setattr__(self, name, value):
1✔
139
        if any(name == field[0] for field in self._flags_bits._fields_):
1✔
140
            setattr(self._flags_bits, name, value)
1✔
141
        return super().__setattr__(name, value)
1✔
142

143
    def __int__(self):
1✔
144
        return int(self._flags)
1✔
145

146
    def __str__(self):
1✔
147
        return "{:#x} ({})".format(self._flags, self._flags_bits)
1✔
148

149
# https://elixir.bootlin.com/glibc/glibc-2.41/source/libio/libio.h#L66
150
class _IOFileFlags_bits(ctypes.LittleEndianStructure):
1✔
151
    _pack_ = 1
1✔
152
    _fields_ = [
1✔
153
        ("_IO_USER_BUF", ctypes.c_uint8, 1), # Don't deallocate buffer on close.
154
        ("_IO_UNBUFFERED", ctypes.c_uint8, 1),
155
        ("_IO_NO_READS", ctypes.c_uint8, 1), # Reading not allowed.
156
        ("_IO_NO_WRITES", ctypes.c_uint8, 1), # Writing not allowed.
157
        ("_IO_EOF_SEEN", ctypes.c_uint8, 1),
158
        ("_IO_ERR_SEEN", ctypes.c_uint8, 1),
159
        ("_IO_DELETE_DONT_CLOSE", ctypes.c_uint8, 1), # Don't call close(_fileno) on close.
160
        ("_IO_LINKED", ctypes.c_uint8, 1), # In the list of all open files.
161
        ("_IO_IN_BACKUP", ctypes.c_uint8, 1),
162
        ("_IO_LINE_BUF", ctypes.c_uint8, 1),
163
        ("_IO_TIED_PUT_GET", ctypes.c_uint8, 1), # Put and get pointer move in unison.
164
        ("_IO_CURRENTLY_PUTTING", ctypes.c_uint8, 1),
165
        ("_IO_IS_APPENDING", ctypes.c_uint8, 1),
166
        ("_IO_IS_FILEBUF", ctypes.c_uint8, 1),
167
        ("_IO_BAD_SEEN__UNUSED", ctypes.c_uint8, 1), # No longer used, reserved for compat.
168
        ("_IO_USER_LOCK", ctypes.c_uint8, 1),
169
        ("_IO_MAGIC", ctypes.c_uint16, 16), # Magic number 0xFBAD0000.
170
    ]
171

172
    def __str__(self):
1✔
173
        return " | ".join(name for name, _, _ in self._fields_ if getattr(self, name))
1✔
174

175
class _IOFileFlags(_FlagsUnionBase):
1✔
176
    _fields_ = [
1✔
177
        ("_flags", ctypes.c_uint64),
178
        ("_flags_bits", _IOFileFlags_bits),
179
    ]
180

181

182
# https://elixir.bootlin.com/glibc/glibc-2.41/source/libio/libio.h#L85
183
class _IOFileFlags2_bits(ctypes.LittleEndianStructure):
1✔
184
    _pack_ = 1
1✔
185
    _fields_ = [
1✔
186
        ("_IO_FLAGS2_MMAP", ctypes.c_uint8, 1),
187
        ("_IO_FLAGS2_NOTCANCEL", ctypes.c_uint8, 1),
188
        ("_IO_FLAGS2_USER_WBUF", ctypes.c_uint8, 1),
189
        ("_IO_FLAGS2_NOCLOSE", ctypes.c_uint8, 1),
190
        ("_IO_FLAGS2_CLOEXEC", ctypes.c_uint8, 1),
191
        ("_IO_FLAGS2_NEED_LOCK", ctypes.c_uint8, 1),
192
    ]
193

194
    def __str__(self):
1✔
195
        return " | ".join(name for name, _, _ in self._fields_ if getattr(self, name))
1✔
196

197
class _IOFileFlags2(_FlagsUnionBase):
1✔
198
    _fields_ = [
1✔
199
        ("_flags", ctypes.c_uint64),
200
        ("_flags_bits", _IOFileFlags2_bits),
201
    ]
202

203

204
class FileStructure(object):
1✔
205
    r"""
206
    Crafts a FILE structure, with default values for some fields, like _lock which should point to null ideally, set.
207

208
    Arguments:
209
        null(int)
210
            A pointer to NULL value in memory. This pointer can lie in any segment (stack, heap, bss, libc etc)
211

212
    Examples:
213

214
        FILE structure with flags as 0xfbad1807 and _IO_buf_base and _IO_buf_end pointing to 0xcafebabe and 0xfacef00d
215

216
        >>> context.clear(arch='amd64')
217
        >>> fileStr = FileStructure(null=0xdeadbeeef)
218
        >>> fileStr.flags = 0xfbad1807 # or use flags by name:
219
        >>> fileStr.flags = IO_flags._IO_MAGIC | IO_flags._IO_USER_BUF | IO_flags._IO_UNBUFFERED | IO_flags._IO_NO_READS | IO_flags._IO_CURRENTLY_PUTTING | IO_flags._IO_IS_APPENDING
220
        >>> fileStr._IO_buf_base = 0xcafebabe
221
        >>> fileStr._IO_buf_end = 0xfacef00d
222
        >>> payload = bytes(fileStr)
223
        >>> payload
224
        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'
225

226
        Check the length of the FileStructure
227

228
        >>> len(fileStr)
229
        224
230

231
        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
232

233
        >>> q=FileStructure(0xdeadbeef)
234
        >>> q.flags = IO_flags._IO_MAGIC | IO_flags._IO_USER_BUF
235
        >>> q.flags._IO_TIED_PUT_GET = 1
236
        >>> q
237
        { flags: 0xfbad0401 (_IO_USER_BUF | _IO_TIED_PUT_GET | _IO_MAGIC)
238
         _IO_read_ptr: 0x0
239
         _IO_read_end: 0x0
240
         _IO_read_base: 0x0
241
         _IO_write_base: 0x0
242
         _IO_write_ptr: 0x0
243
         _IO_write_end: 0x0
244
         _IO_buf_base: 0x0
245
         _IO_buf_end: 0x0
246
         _IO_save_base: 0x0
247
         _IO_backup_base: 0x0
248
         _IO_save_end: 0x0
249
         markers: 0x0
250
         chain: 0x0
251
         fileno: 0x0
252
         _flags2: 0x0 ()
253
         _old_offset: 0xffffffffffffffff
254
         _cur_column: 0x0
255
         _vtable_offset: 0x0
256
         _shortbuf: 0x0
257
         unknown1: 0x0
258
         _lock: 0xdeadbeef
259
         _offset: 0xffffffffffffffff
260
         _codecvt: 0x0
261
         _wide_data: 0xdeadbeef
262
         _freeres_list: 0x0
263
         _freeres_buf: 0x0
264
         _pad5: 0x0
265
         _mode: 0x0
266
         _unused2: 0x0
267
         vtable: 0x0}
268
    """
269

270
    vars_=[]
1✔
271
    length={}
1✔
272

273
    def __init__(self, null=0):
1✔
274
            self.vars_ = [variables[i]['name'] for i in sorted(variables.keys())]
1✔
275
            self.setdefault(null)
1✔
276
            self.length = _update_var(context.bytes)
1✔
277
            self._old_offset = (1 << context.bits) - 1
1✔
278

279
    def __setattr__(self,item,value):
1✔
280
        if item in FileStructure.__dict__ or item in self.vars_:
1!
281
            if hasattr(self, item) and isinstance(getattr(self, item), _FlagsUnionBase):
1✔
282
                if isinstance(value, (bytes, bytearray)):
1✔
283
                    getattr(self, item)._flags = unpack(value.ljust(context.bytes, b'\x00'))
1✔
284
                else:
285
                    getattr(self, item)._flags = value
1✔
286
            else:
287
                object.__setattr__(self,item,value)
1✔
288
        else:
289
            log.error("Unknown variable %r" % item)
×
290

291
    def __repr__(self):
1✔
292
        structure=[]
1✔
293
        for i in self.vars_:
1✔
294
            val = getattr(self, i)
1✔
295
            if isinstance(val, int):
1✔
296
                structure.append(" %s: %#x" % (i, val))
1✔
297
            else:
298
                structure.append(" %s: %s" % (i, val))
1✔
299
        return "{"+ "\n".join(structure)+"}"
1✔
300

301
    def __len__(self):
1✔
302
        return len(bytes(self))
1✔
303

304
    def __bytes__(self):
1✔
305
        structure = b''
1✔
306
        for val in self.vars_:
1✔
307
            if isinstance(getattr(self, val), bytes):
1!
308
                structure += getattr(self, val).ljust(context.bytes, b'\x00')
×
309
            else:
310
                if self.length[val] > 0:
1!
311
                    structure += pack(int(getattr(self, val)), self.length[val]*8)
1✔
312
        return structure
1✔
313

314
    @classmethod
1✔
315
    def from_bytes(cls, fileStr):
1✔
316
        r"""
317
        Creates a new instance of the class from a byte string representing a file structure.
318

319
        Arguments:
320
            fileStr (bytes): The byte string representing the file structure. It should have the exact length as the class instance.
321

322
        Returns:
323
            FileStructure: A new instance of the class, populated with values from the byte string.
324

325
        Example:
326
            Example of creating a `FileStructure` from a byte string
327

328
            >>> context.clear(arch='amd64')
329
            >>> byte_data = 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\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\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'
330
            >>> fileStr = FileStructure.from_bytes(byte_data)
331
            >>> hex(fileStr.vtable)
332
            '0xcafebabe'
333
        """
334
        if not isinstance(fileStr, bytes):
1!
NEW
335
            log.error('fileStr should be of type bytes')
×
336
        fp = cls()
1✔
337
        expected_len = len(bytes(fp))
1✔
338
        if len(fileStr) != expected_len:
1!
NEW
339
            log.error('fileStr should be of length %d' % expected_len)
×
340
        for member, pos in fp.offsets().items():
1✔
341
            length = fp.length[member]
1✔
342
            setattr(fp, member, unpack(fileStr[pos:pos+length], length * 8))
1✔
343
        return fp
1✔
344

345
    def struntil(self,v):
1✔
346
        r"""
347
        Payload for stuff till 'v' where 'v' is a structure member. This payload includes 'v' as well.
348

349
        Arguments:
350
            v(string)
351
                The name of the field uptil which the payload should be created.
352

353
        Example:
354

355
            Payload for data uptil _IO_buf_end
356

357
            >>> context.clear(arch='amd64')
358
            >>> fileStr = FileStructure(0xdeadbeef)
359
            >>> payload = fileStr.struntil("_IO_buf_end")
360
            >>> payload
361
            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'
362
        """
363
        if v not in self.vars_:
1!
364
            return b''
×
365
        structure = b''
1✔
366
        for val in self.vars_:
1!
367
            if isinstance(getattr(self, val), bytes):
1!
368
                structure += getattr(self, val).ljust(context.bytes, b'\x00')
×
369
            else:
370
                structure += pack(int(getattr(self, val)), self.length[val]*8)
1✔
371
            if val == v:
1✔
372
                break
1✔
373
        return structure[:-1]
1✔
374

375
    def setdefault(self,null):
1✔
376
            self.flags=_IOFileFlags()
1✔
377
            self._IO_read_ptr=0
1✔
378
            self._IO_read_end=0
1✔
379
            self._IO_read_base=0
1✔
380
            self._IO_write_base=0
1✔
381
            self._IO_write_ptr=0
1✔
382
            self._IO_write_end=0
1✔
383
            self._IO_buf_base=0
1✔
384
            self._IO_buf_end=0
1✔
385
            self._IO_save_base=0
1✔
386
            self._IO_backup_base=0
1✔
387
            self._IO_save_end=0
1✔
388
            self.markers=0
1✔
389
            self.chain=0
1✔
390
            self.fileno=0
1✔
391
            self._flags2=_IOFileFlags2()
1✔
392
            self._old_offset=0
1✔
393
            self._cur_column=0
1✔
394
            self._vtable_offset=0
1✔
395
            self._shortbuf=0
1✔
396
            self.unknown1=0
1✔
397
            self._lock=null
1✔
398
            self._offset=0xffffffffffffffff
1✔
399
            self._codecvt=0
1✔
400
            self._wide_data=null
1✔
401
            self._freeres_list=0
1✔
402
            self._freeres_buf=0
1✔
403
            self._pad5=0
1✔
404
            self._mode=0
1✔
405
            self._unused2=0
1✔
406
            self.vtable=0
1✔
407

408
    def write(self,addr=0,size=0):
1✔
409
        r"""
410
        Writing data out from arbitrary memory address.
411

412
        Arguments:
413
            addr(int)
414
                The address from which data is to be printed to stdout
415
            size(int)
416
                The size, in bytes, of the data to be printed
417

418
        Example:
419

420
            Payload for writing 100 bytes to stdout from the address 0xcafebabe
421

422
            >>> context.clear(arch='amd64')
423
            >>> fileStr = FileStructure(0xdeadbeef)
424
            >>> payload = fileStr.write(addr=0xcafebabe, size=100)
425
            >>> payload
426
            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'
427
        """
428
        self.flags._IO_NO_WRITES = 0
1✔
429
        self.flags._IO_CURRENTLY_PUTTING = 1
1✔
430
        self._IO_write_base = addr
1✔
431
        self._IO_write_ptr = addr+size
1✔
432
        self._IO_read_end = addr
1✔
433
        self.fileno = 1
1✔
434
        return self.struntil('fileno')
1✔
435

436
    def read(self,addr=0,size=0):
1✔
437
        r"""
438
        Reading data into arbitrary memory location.
439

440
        Arguments:
441
            addr(int)
442
                The address into which data is to be written from stdin
443
            size(int)
444
                The size, in bytes, of the data to be written
445

446
        Example:
447

448
            Payload for reading 100 bytes from stdin into the address 0xcafebabe
449

450
            >>> context.clear(arch='amd64')
451
            >>> fileStr = FileStructure(0xdeadbeef)
452
            >>> payload = fileStr.read(addr=0xcafebabe, size=100)
453
            >>> payload
454
            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'
455
        """
456
        self.flags._IO_NO_READS = 0
1✔
457
        self._IO_read_base = 0
1✔
458
        self._IO_read_ptr = 0
1✔
459
        self._IO_buf_base = addr
1✔
460
        self._IO_buf_end = addr+size
1✔
461
        self.fileno = 0
1✔
462
        return self.struntil('fileno')
1✔
463

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

468
        Arguments:
469
            io_list_all(int)
470
                Address of _IO_list_all in libc.
471
            vtable(int)
472
                Address of the fake vtable in memory
473

474
        Example:
475

476
            Example payload if address of _IO_list_all is 0xfacef00d and fake vtable is at 0xcafebabe -
477

478
            >>> context.clear(arch='amd64')
479
            >>> fileStr = FileStructure(0xdeadbeef)
480
            >>> payload = fileStr.orange(io_list_all=0xfacef00d, vtable=0xcafebabe)
481
            >>> payload
482
            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'
483
        """
484
        if context.bits == 64:
1!
485
            self.flags = b'/bin/sh\x00'
1✔
486
            self._IO_read_ptr = 0x61
1✔
487
            self._IO_read_base = io_list_all-0x10
1✔
488
        elif context.bits == 32:
×
489
            self.flags = b'sh\x00'
×
490
            self._IO_read_ptr = 0x121
×
491
            self._IO_read_base = io_list_all-0x8
×
492
        self._IO_write_base = 0
1✔
493
        self._IO_write_ptr = 1
1✔
494
        self.vtable = vtable
1✔
495
        return self.__bytes__()
1✔
496

497
    def offsets(self):
1✔
498
        r"""
499
        Exports the offsets of all the members of the file pointer struct.
500

501
        Arguments:
502
            None
503

504
        Returns:
505
            dict: A dictionary where keys are the member names and values are their offsets (in bytes) within the struct.
506

507
        Example:
508

509
            Example offset of vtable on amd64 arch
510

511
            >>> context.clear(arch='amd64')
512
            >>> offsets = FileStructure().offsets()
513
            >>> offsets['vtable']
514
            216
515
        """
516
        res = {}
1✔
517
        curr_offset = 0
1✔
518
        for member, size in self.length.items():
1✔
519
            res[member] = curr_offset
1✔
520
            curr_offset += size
1✔
521
        return res
1✔
522

523
    def overlap(self, fake_fp, offset=0):
1✔
524
        r"""
525
        Constructs a new file pointer by overlapping the original file pointer (`self`) with a fake file pointer (`fake_fp`), using the given offset.
526

527
        The fields of `fake_fp` are shifted by the specified `offset` relative to the fields in the original structure. If a non-zero field in the original struct overlaps with a corresponding field in `fake_fp`, priority is given to the value from the original struct, meaning the original value will overwrite the fake one.
528

529
        Arguments:
530
            fake_fp (FileStructure): The fake file pointer to overlap with the original.
531
            offset (int, optional): The byte offset to apply when shifting the fake file pointer's members. Default is 0.
532

533
        Returns:
534
            FileStructure: A new `FileStructure` instance created by overlapping the original and fake file pointers.
535

536
        Example:
537

538
            Example of overlapped struct by offsetting the fake_fp by -8
539

540
            >>> context.clear(arch='amd64')
541
            >>> fileStr = FileStructure(0xdeadbeef)
542
            >>> fileStr.chain = 0xcafebabe
543
            >>> fake = FileStructure(0xdeadbeef)
544
            >>> fake._IO_write_base = 1
545
            >>> fake._IO_write_ptr = 2
546
            >>> fake._wide_data = 0xd00df00d
547
            >>> fake.vtable = 0xfacef00d
548
            >>> overlap = fileStr.overlap(fake, offset=-8)
549
            >>> overlap
550
            { flags: 0x0 ()
551
             _IO_read_ptr: 0x0
552
             _IO_read_end: 0x0
553
             _IO_read_base: 0x1
554
             _IO_write_base: 0x2
555
             _IO_write_ptr: 0x0
556
             _IO_write_end: 0x0
557
             _IO_buf_base: 0x0
558
             _IO_buf_end: 0x0
559
             _IO_save_base: 0x0
560
             _IO_backup_base: 0x0
561
             _IO_save_end: 0x0
562
             markers: 0x0
563
             chain: 0xcafebabe
564
             fileno: 0xffffffff
565
             _flags2: 0xffffffff (_IO_FLAGS2_MMAP | _IO_FLAGS2_NOTCANCEL | _IO_FLAGS2_USER_WBUF | _IO_FLAGS2_NOCLOSE | _IO_FLAGS2_CLOEXEC | _IO_FLAGS2_NEED_LOCK)
566
             _old_offset: 0xffffffffffffffff
567
             _cur_column: 0xbeef
568
             _vtable_offset: 0xad
569
             _shortbuf: 0xde
570
             unknown1: 0x0
571
             _lock: 0xdeadbeef
572
             _offset: 0xffffffffffffffff
573
             _codecvt: 0xd00df00d
574
             _wide_data: 0xdeadbeef
575
             _freeres_list: 0x0
576
             _freeres_buf: 0x0
577
             _pad5: 0x0
578
             _mode: 0x0
579
             _unused2: 0xfacef00d000000000000000000000000
580
             vtable: 0x0}
581
        """
582
        if not isinstance(fake_fp, FileStructure):
1!
NEW
583
            log.error('fake_fp must be of type FileStructure')
×
584
        if len(bytes(self)) != len(bytes(fake_fp)):
1!
NEW
585
            log.error('both file structures should be of same length')
×
586
        
587
        new_fp_bytes = bytearray(len(bytes(self)))
1✔
588
        total_len = len(new_fp_bytes)
1✔
589
        
590
        # For fake file struct
591
        for member, pos in fake_fp.offsets().items():
1✔
592
            effective_pos = pos + offset
1✔
593
            if effective_pos < 0 or total_len <= effective_pos:
1✔
594
                continue
1✔
595
            new_bytes = b''
1✔
596
            if isinstance(getattr(fake_fp, member), bytes):
1!
NEW
597
                new_bytes = getattr(fake_fp, member).ljust(context.bytes, b'\x00')
×
598
            else:
599
                if fake_fp.length[member] > 0:
1!
600
                    new_bytes = pack(int(getattr(fake_fp, member)), fake_fp.length[member]*8)
1✔
601
            new_fp_bytes[effective_pos:effective_pos+len(new_bytes)] = new_bytes
1✔
602
        
603
        # For original file struct
604
        for member, pos in self.offsets().items():
1✔
605
            effective_pos = pos + 0
1✔
606
            if effective_pos < 0 or total_len <= effective_pos:
1!
NEW
607
                continue
×
608
            new_bytes = b''
1✔
609
            if isinstance(getattr(self, member), bytes):
1!
NEW
610
                new_bytes = getattr(self, member).ljust(context.bytes, b'\x00')
×
611
            else:
612
                if self.length[member] > 0:
1!
613
                    new_bytes = pack(int(getattr(self, member)), self.length[member]*8)
1✔
614
            if unpack(new_bytes, len(new_bytes) * 8) != 0:
1✔
615
                # If this field is not `0` meaning that is is initialised in
616
                # the original struct then give priority to it by replacing its
617
                # byte in the `new_fp_bytes` array
618
                new_fp_bytes[effective_pos:effective_pos+len(new_bytes)] = new_bytes
1✔
619

620
        new_fp_bytes = new_fp_bytes[:total_len] # Remove any added new bytes
1✔
621
        new_fp = FileStructure.from_bytes(bytes(new_fp_bytes))
1✔
622
        return new_fp
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