Skip to content

Commit 447ac96

Browse files
committed
Decode _IO_* flags in FileStructure member
The `_flags` and `_flags2` bitmasks are decoded to a readable string when printing the struct. Next to setting the flags as an integer like before, you can set individual bits now and check set bits. ``` fileStr = FileStructure(null=0xdeadbeeef) fileStr.flags = 0xfbad1807 fileStr.flags = IO_flags._IO_MAGIC | IO_flags._IO_USER_BUF | IO_flags._IO_UNBUFFERED fileStr.flags._IO_USER_BUF = 1 ``` Printing the FileStructure shows the flags in human readable form too ``` { flags: 0xfbad0401 (_IO_USER_BUF | _IO_UNBUFFERED | _IO_MAGIC) _IO_read_ptr: 0x0 ... ```
1 parent 3eb690b commit 447ac96

File tree

2 files changed

+149
-23
lines changed

2 files changed

+149
-23
lines changed

docs/source/filepointer.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.. testsetup:: *
22

33
from pwnlib.filepointer import *
4+
from pwnlib.filepointer import _update_var
45

56
:mod:`pwnlib.filepointer` --- `FILE*` structure exploitation
67
============================================================

pwnlib/filepointer.py

+148-23
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
from __future__ import absolute_import
2626
from __future__ import division
2727

28+
import ctypes
29+
2830
from pwnlib.context import context
2931
from pwnlib.log import getLogger
3032
from pwnlib.util.misc import python_2_bytes_compatible
31-
from pwnlib.util.packing import pack
33+
from pwnlib.util.packing import pack, unpack
3234

3335
log = getLogger(__name__)
3436

@@ -62,14 +64,18 @@
6264
22:{name:'_offset',size:8},
6365
23:{name:'_codecvt',size:length},
6466
24:{name:'_wide_data',size:length},
65-
25:{name:'unknown2',size:length},
66-
26:{name:'vtable',size:length}
67+
25:{name:'_freeres_list',size:length},
68+
26:{name:'_freeres_buf',size:length},
69+
27:{name:'_pad5',size:length},
70+
28:{name:'_mode',size:4},
71+
29:{name:'_unused2',size:length},
72+
30:{name:'vtable',size:length}
6773
}
6874

6975
del name, size, length
7076

7177

72-
def update_var(l):
78+
def _update_var(l):
7379
r"""
7480
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.
7581
@@ -82,8 +88,8 @@ def update_var(l):
8288
8389
Examples:
8490
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}
91+
>>> _update_var(8)
92+
{'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}
8793
"""
8894
var={}
8995
for i in variables:
@@ -92,11 +98,109 @@ def update_var(l):
9298
if var[i]<=0:
9399
var[i]+=l
94100
if l==4:
95-
var['unknown2']=56
101+
var['_unused2']=40
96102
else:
97-
var['unknown2']=48
103+
var['_unused2']=20
98104
return var
99105

106+
class IO_flags:
107+
_IO_MAGIC = 0xFBAD0000 # Magic number
108+
_IO_MAGIC_MASK = 0xFFFF0000
109+
_IO_USER_BUF = 0x0001 # Don't deallocate buffer on close.
110+
_IO_UNBUFFERED = 0x0002
111+
_IO_NO_READS = 0x0004 # Reading not allowed.
112+
_IO_NO_WRITES = 0x0008 # Writing not allowed.
113+
_IO_EOF_SEEN = 0x0010
114+
_IO_ERR_SEEN = 0x0020
115+
_IO_DELETE_DONT_CLOSE = 0x0040 # Don't call close(_fileno) on close.
116+
_IO_LINKED = 0x0080 # In the list of all open files.
117+
_IO_IN_BACKUP = 0x0100
118+
_IO_LINE_BUF = 0x0200
119+
_IO_TIED_PUT_GET = 0x0400 # Put and get pointer move in unison.
120+
_IO_CURRENTLY_PUTTING = 0x0800
121+
_IO_IS_APPENDING = 0x1000
122+
_IO_IS_FILEBUF = 0x2000
123+
_IO_USER_LOCK = 0x8000
124+
125+
class IO_flags2:
126+
_IO_FLAGS2_MMAP = 1
127+
_IO_FLAGS2_NOTCANCEL = 2
128+
_IO_FLAGS2_USER_WBUF = 8
129+
_IO_FLAGS2_NOCLOSE = 32
130+
_IO_FLAGS2_CLOEXEC = 64
131+
_IO_FLAGS2_NEED_LOCK = 128
132+
133+
class _FlagsUnionBase(ctypes.Union):
134+
def __getattr__(self, name):
135+
if any(name == field[0] for field in self._flags_bits._fields_):
136+
return getattr(self._flags_bits, name)
137+
return super().__getattr__(name)
138+
139+
def __setattr__(self, name, value):
140+
if any(name == field[0] for field in self._flags_bits._fields_):
141+
setattr(self._flags_bits, name, value)
142+
return super().__setattr__(name, value)
143+
144+
def __int__(self):
145+
return int(self._flags)
146+
147+
def __str__(self):
148+
return "{:#x} ({})".format(self._flags, self._flags_bits)
149+
150+
# https://elixir.bootlin.com/glibc/glibc-2.41/source/libio/libio.h#L66
151+
class _IOFileFlags_bits(ctypes.LittleEndianStructure):
152+
_pack_ = 1
153+
_fields_ = [
154+
("_IO_USER_BUF", ctypes.c_uint8, 1), # Don't deallocate buffer on close.
155+
("_IO_UNBUFFERED", ctypes.c_uint8, 1),
156+
("_IO_NO_READS", ctypes.c_uint8, 1), # Reading not allowed.
157+
("_IO_NO_WRITES", ctypes.c_uint8, 1), # Writing not allowed.
158+
("_IO_EOF_SEEN", ctypes.c_uint8, 1),
159+
("_IO_ERR_SEEN", ctypes.c_uint8, 1),
160+
("_IO_DELETE_DONT_CLOSE", ctypes.c_uint8, 1), # Don't call close(_fileno) on close.
161+
("_IO_LINKED", ctypes.c_uint8, 1), # In the list of all open files.
162+
("_IO_IN_BACKUP", ctypes.c_uint8, 1),
163+
("_IO_LINE_BUF", ctypes.c_uint8, 1),
164+
("_IO_TIED_PUT_GET", ctypes.c_uint8, 1), # Put and get pointer move in unison.
165+
("_IO_CURRENTLY_PUTTING", ctypes.c_uint8, 1),
166+
("_IO_IS_APPENDING", ctypes.c_uint8, 1),
167+
("_IO_IS_FILEBUF", ctypes.c_uint8, 1),
168+
("_IO_BAD_SEEN__UNUSED", ctypes.c_uint8, 1), # No longer used, reserved for compat.
169+
("_IO_USER_LOCK", ctypes.c_uint8, 1),
170+
("_IO_MAGIC", ctypes.c_uint16, 16), # Magic number 0xFBAD0000.
171+
]
172+
173+
def __str__(self):
174+
return " | ".join(name for name, _, _ in self._fields_ if getattr(self, name))
175+
176+
class _IOFileFlags(_FlagsUnionBase):
177+
_fields_ = [
178+
("_flags", ctypes.c_uint64),
179+
("_flags_bits", _IOFileFlags_bits),
180+
]
181+
182+
183+
# https://elixir.bootlin.com/glibc/glibc-2.41/source/libio/libio.h#L85
184+
class _IOFileFlags2_bits(ctypes.LittleEndianStructure):
185+
_pack_ = 1
186+
_fields_ = [
187+
("_IO_FLAGS2_MMAP", ctypes.c_uint8, 1),
188+
("_IO_FLAGS2_NOTCANCEL", ctypes.c_uint8, 1),
189+
("_IO_FLAGS2_USER_WBUF", ctypes.c_uint8, 1),
190+
("_IO_FLAGS2_NOCLOSE", ctypes.c_uint8, 1),
191+
("_IO_FLAGS2_CLOEXEC", ctypes.c_uint8, 1),
192+
("_IO_FLAGS2_NEED_LOCK", ctypes.c_uint8, 1),
193+
]
194+
195+
def __str__(self):
196+
return " | ".join(name for name, _, _ in self._fields_ if getattr(self, name))
197+
198+
class _IOFileFlags2(_FlagsUnionBase):
199+
_fields_ = [
200+
("_flags", ctypes.c_uint64),
201+
("_flags_bits", _IOFileFlags2_bits),
202+
]
203+
100204

101205
@python_2_bytes_compatible
102206
class FileStructure(object):
@@ -113,7 +217,8 @@ class FileStructure(object):
113217
114218
>>> context.clear(arch='amd64')
115219
>>> fileStr = FileStructure(null=0xdeadbeeef)
116-
>>> fileStr.flags = 0xfbad1807
220+
>>> fileStr.flags = 0xfbad1807 # or use flags by name:
221+
>>> 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
117222
>>> fileStr._IO_buf_base = 0xcafebabe
118223
>>> fileStr._IO_buf_end = 0xfacef00d
119224
>>> payload = bytes(fileStr)
@@ -128,8 +233,10 @@ class FileStructure(object):
128233
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
129234
130235
>>> q=FileStructure(0xdeadbeef)
236+
>>> q.flags = IO_flags._IO_MAGIC | IO_flags._IO_USER_BUF
237+
>>> q.flags._IO_TIED_PUT_GET = 1
131238
>>> q
132-
{ flags: 0x0
239+
{ flags: 0xfbad0401 (_IO_USER_BUF | _IO_TIED_PUT_GET | _IO_MAGIC)
133240
_IO_read_ptr: 0x0
134241
_IO_read_end: 0x0
135242
_IO_read_base: 0x0
@@ -144,7 +251,7 @@ class FileStructure(object):
144251
markers: 0x0
145252
chain: 0x0
146253
fileno: 0x0
147-
_flags2: 0x0
254+
_flags2: 0x0 ()
148255
_old_offset: 0xffffffffffffffff
149256
_cur_column: 0x0
150257
_vtable_offset: 0x0
@@ -154,7 +261,11 @@ class FileStructure(object):
154261
_offset: 0xffffffffffffffff
155262
_codecvt: 0x0
156263
_wide_data: 0xdeadbeef
157-
unknown2: 0x0
264+
_freeres_list: 0x0
265+
_freeres_buf: 0x0
266+
_pad5: 0x0
267+
_mode: 0x0
268+
_unused2: 0x0
158269
vtable: 0x0}
159270
"""
160271

@@ -164,19 +275,29 @@ class FileStructure(object):
164275
def __init__(self, null=0):
165276
self.vars_ = [variables[i]['name'] for i in sorted(variables.keys())]
166277
self.setdefault(null)
167-
self.length = update_var(context.bytes)
278+
self.length = _update_var(context.bytes)
168279
self._old_offset = (1 << context.bits) - 1
169280

170281
def __setattr__(self,item,value):
171282
if item in FileStructure.__dict__ or item in self.vars_:
172-
object.__setattr__(self,item,value)
283+
if hasattr(self, item) and isinstance(getattr(self, item), _FlagsUnionBase):
284+
if isinstance(value, (bytes, bytearray)):
285+
getattr(self, item)._flags = unpack(value.ljust(context.bytes, b'\x00'))
286+
else:
287+
getattr(self, item)._flags = value
288+
else:
289+
object.__setattr__(self,item,value)
173290
else:
174291
log.error("Unknown variable %r" % item)
175292

176293
def __repr__(self):
177294
structure=[]
178295
for i in self.vars_:
179-
structure.append(" %s: %#x" % (i, getattr(self, i)))
296+
val = getattr(self, i)
297+
if isinstance(val, int):
298+
structure.append(" %s: %#x" % (i, val))
299+
else:
300+
structure.append(" %s: %s" % (i, val))
180301
return "{"+ "\n".join(structure)+"}"
181302

182303
def __len__(self):
@@ -189,7 +310,7 @@ def __bytes__(self):
189310
structure += getattr(self, val).ljust(context.bytes, b'\x00')
190311
else:
191312
if self.length[val] > 0:
192-
structure += pack(getattr(self, val), self.length[val]*8)
313+
structure += pack(int(getattr(self, val)), self.length[val]*8)
193314
return structure
194315

195316
def struntil(self,v):
@@ -217,13 +338,13 @@ def struntil(self,v):
217338
if isinstance(getattr(self, val), bytes):
218339
structure += getattr(self, val).ljust(context.bytes, b'\x00')
219340
else:
220-
structure += pack(getattr(self, val), self.length[val]*8)
341+
structure += pack(int(getattr(self, val)), self.length[val]*8)
221342
if val == v:
222343
break
223344
return structure[:-1]
224345

225346
def setdefault(self,null):
226-
self.flags=0
347+
self.flags=_IOFileFlags()
227348
self._IO_read_ptr=0
228349
self._IO_read_end=0
229350
self._IO_read_base=0
@@ -238,7 +359,7 @@ def setdefault(self,null):
238359
self.markers=0
239360
self.chain=0
240361
self.fileno=0
241-
self._flags2=0
362+
self._flags2=_IOFileFlags2()
242363
self._old_offset=0
243364
self._cur_column=0
244365
self._vtable_offset=0
@@ -248,7 +369,11 @@ def setdefault(self,null):
248369
self._offset=0xffffffffffffffff
249370
self._codecvt=0
250371
self._wide_data=null
251-
self.unknown2=0
372+
self._freeres_list=0
373+
self._freeres_buf=0
374+
self._pad5=0
375+
self._mode=0
376+
self._unused2=0
252377
self.vtable=0
253378

254379
def write(self,addr=0,size=0):
@@ -271,8 +396,8 @@ def write(self,addr=0,size=0):
271396
>>> payload
272397
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'
273398
"""
274-
self.flags &=~8
275-
self.flags |=0x800
399+
self.flags._IO_NO_WRITES = 0
400+
self.flags._IO_CURRENTLY_PUTTING = 1
276401
self._IO_write_base = addr
277402
self._IO_write_ptr = addr+size
278403
self._IO_read_end = addr
@@ -299,7 +424,7 @@ def read(self,addr=0,size=0):
299424
>>> payload
300425
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'
301426
"""
302-
self.flags &=~4
427+
self.flags._IO_NO_READS = 0
303428
self._IO_read_base = 0
304429
self._IO_read_ptr = 0
305430
self._IO_buf_base = addr

0 commit comments

Comments
 (0)