Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decode _IO_* flags in FileStructure member #2542

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ The table below shows which release corresponds to each branch, and what date th
- [#2527][2527] Allow setting debugger path via `context.gdb_binary`
- [#2530][2530] Do NOT error when passing directory arguments in `checksec` commandline tool.
- [#2529][2529] Add LoongArch64 support
- [#2542][2542] Decode `_IO_*` flags in `FileStructure` member

[2519]: https://github.com/Gallopsled/pwntools/pull/2519
[2507]: https://github.com/Gallopsled/pwntools/pull/2507
Expand All @@ -93,6 +94,7 @@ The table below shows which release corresponds to each branch, and what date th
[2527]: https://github.com/Gallopsled/pwntools/pull/2527
[2530]: https://github.com/Gallopsled/pwntools/pull/2530
[2529]: https://github.com/Gallopsled/pwntools/pull/2529
[2542]: https://github.com/Gallopsled/pwntools/pull/2542

## 4.15.0 (`beta`)

Expand Down
1 change: 1 addition & 0 deletions docs/source/filepointer.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.. testsetup:: *

from pwnlib.filepointer import *
from pwnlib.filepointer import _update_var

:mod:`pwnlib.filepointer` --- `FILE*` structure exploitation
============================================================
Expand Down
171 changes: 148 additions & 23 deletions pwnlib/filepointer.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
from __future__ import absolute_import
from __future__ import division

import ctypes

from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util.misc import python_2_bytes_compatible
from pwnlib.util.packing import pack
from pwnlib.util.packing import pack, unpack

log = getLogger(__name__)

Expand Down Expand Up @@ -62,14 +64,18 @@
22:{name:'_offset',size:8},
23:{name:'_codecvt',size:length},
24:{name:'_wide_data',size:length},
25:{name:'unknown2',size:length},
26:{name:'vtable',size:length}
25:{name:'_freeres_list',size:length},
26:{name:'_freeres_buf',size:length},
27:{name:'_pad5',size:length},
28:{name:'_mode',size:4},
29:{name:'_unused2',size:length},
30:{name:'vtable',size:length}
}

del name, size, length


def update_var(l):
def _update_var(l):
r"""
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.

Expand All @@ -82,8 +88,8 @@ def update_var(l):

Examples:

>>> update_var(8)
{'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}
>>> _update_var(8)
{'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}
"""
var={}
for i in variables:
Expand All @@ -92,11 +98,109 @@ def update_var(l):
if var[i]<=0:
var[i]+=l
if l==4:
var['unknown2']=56
var['_unused2']=40
else:
var['unknown2']=48
var['_unused2']=20
return var

class IO_flags:
_IO_MAGIC = 0xFBAD0000 # Magic number
_IO_MAGIC_MASK = 0xFFFF0000
_IO_USER_BUF = 0x0001 # Don't deallocate buffer on close.
_IO_UNBUFFERED = 0x0002
_IO_NO_READS = 0x0004 # Reading not allowed.
_IO_NO_WRITES = 0x0008 # Writing not allowed.
_IO_EOF_SEEN = 0x0010
_IO_ERR_SEEN = 0x0020
_IO_DELETE_DONT_CLOSE = 0x0040 # Don't call close(_fileno) on close.
_IO_LINKED = 0x0080 # In the list of all open files.
_IO_IN_BACKUP = 0x0100
_IO_LINE_BUF = 0x0200
_IO_TIED_PUT_GET = 0x0400 # Put and get pointer move in unison.
_IO_CURRENTLY_PUTTING = 0x0800
_IO_IS_APPENDING = 0x1000
_IO_IS_FILEBUF = 0x2000
_IO_USER_LOCK = 0x8000

class IO_flags2:
_IO_FLAGS2_MMAP = 1
_IO_FLAGS2_NOTCANCEL = 2
_IO_FLAGS2_USER_WBUF = 8
_IO_FLAGS2_NOCLOSE = 32
_IO_FLAGS2_CLOEXEC = 64
_IO_FLAGS2_NEED_LOCK = 128

class _FlagsUnionBase(ctypes.Union):
def __getattr__(self, name):
if any(name == field[0] for field in self._flags_bits._fields_):
return getattr(self._flags_bits, name)
return super().__getattr__(name)

def __setattr__(self, name, value):
if any(name == field[0] for field in self._flags_bits._fields_):
setattr(self._flags_bits, name, value)
return super().__setattr__(name, value)

def __int__(self):
return int(self._flags)

def __str__(self):
return "{:#x} ({})".format(self._flags, self._flags_bits)

# https://elixir.bootlin.com/glibc/glibc-2.41/source/libio/libio.h#L66
class _IOFileFlags_bits(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
("_IO_USER_BUF", ctypes.c_uint8, 1), # Don't deallocate buffer on close.
("_IO_UNBUFFERED", ctypes.c_uint8, 1),
("_IO_NO_READS", ctypes.c_uint8, 1), # Reading not allowed.
("_IO_NO_WRITES", ctypes.c_uint8, 1), # Writing not allowed.
("_IO_EOF_SEEN", ctypes.c_uint8, 1),
("_IO_ERR_SEEN", ctypes.c_uint8, 1),
("_IO_DELETE_DONT_CLOSE", ctypes.c_uint8, 1), # Don't call close(_fileno) on close.
("_IO_LINKED", ctypes.c_uint8, 1), # In the list of all open files.
("_IO_IN_BACKUP", ctypes.c_uint8, 1),
("_IO_LINE_BUF", ctypes.c_uint8, 1),
("_IO_TIED_PUT_GET", ctypes.c_uint8, 1), # Put and get pointer move in unison.
("_IO_CURRENTLY_PUTTING", ctypes.c_uint8, 1),
("_IO_IS_APPENDING", ctypes.c_uint8, 1),
("_IO_IS_FILEBUF", ctypes.c_uint8, 1),
("_IO_BAD_SEEN__UNUSED", ctypes.c_uint8, 1), # No longer used, reserved for compat.
("_IO_USER_LOCK", ctypes.c_uint8, 1),
("_IO_MAGIC", ctypes.c_uint16, 16), # Magic number 0xFBAD0000.
]

def __str__(self):
return " | ".join(name for name, _, _ in self._fields_ if getattr(self, name))

class _IOFileFlags(_FlagsUnionBase):
_fields_ = [
("_flags", ctypes.c_uint64),
("_flags_bits", _IOFileFlags_bits),
]


# https://elixir.bootlin.com/glibc/glibc-2.41/source/libio/libio.h#L85
class _IOFileFlags2_bits(ctypes.LittleEndianStructure):
_pack_ = 1
_fields_ = [
("_IO_FLAGS2_MMAP", ctypes.c_uint8, 1),
("_IO_FLAGS2_NOTCANCEL", ctypes.c_uint8, 1),
("_IO_FLAGS2_USER_WBUF", ctypes.c_uint8, 1),
("_IO_FLAGS2_NOCLOSE", ctypes.c_uint8, 1),
("_IO_FLAGS2_CLOEXEC", ctypes.c_uint8, 1),
("_IO_FLAGS2_NEED_LOCK", ctypes.c_uint8, 1),
]

def __str__(self):
return " | ".join(name for name, _, _ in self._fields_ if getattr(self, name))

class _IOFileFlags2(_FlagsUnionBase):
_fields_ = [
("_flags", ctypes.c_uint64),
("_flags_bits", _IOFileFlags2_bits),
]


@python_2_bytes_compatible
class FileStructure(object):
Expand All @@ -113,7 +217,8 @@ class FileStructure(object):

>>> context.clear(arch='amd64')
>>> fileStr = FileStructure(null=0xdeadbeeef)
>>> fileStr.flags = 0xfbad1807
>>> fileStr.flags = 0xfbad1807 # or use flags by name:
>>> 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
>>> fileStr._IO_buf_base = 0xcafebabe
>>> fileStr._IO_buf_end = 0xfacef00d
>>> payload = bytes(fileStr)
Expand All @@ -128,8 +233,10 @@ class FileStructure(object):
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

>>> q=FileStructure(0xdeadbeef)
>>> q.flags = IO_flags._IO_MAGIC | IO_flags._IO_USER_BUF
>>> q.flags._IO_TIED_PUT_GET = 1
>>> q
{ flags: 0x0
{ flags: 0xfbad0401 (_IO_USER_BUF | _IO_TIED_PUT_GET | _IO_MAGIC)
_IO_read_ptr: 0x0
_IO_read_end: 0x0
_IO_read_base: 0x0
Expand All @@ -144,7 +251,7 @@ class FileStructure(object):
markers: 0x0
chain: 0x0
fileno: 0x0
_flags2: 0x0
_flags2: 0x0 ()
_old_offset: 0xffffffffffffffff
_cur_column: 0x0
_vtable_offset: 0x0
Expand All @@ -154,7 +261,11 @@ class FileStructure(object):
_offset: 0xffffffffffffffff
_codecvt: 0x0
_wide_data: 0xdeadbeef
unknown2: 0x0
_freeres_list: 0x0
_freeres_buf: 0x0
_pad5: 0x0
_mode: 0x0
_unused2: 0x0
vtable: 0x0}
"""

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

def __setattr__(self,item,value):
if item in FileStructure.__dict__ or item in self.vars_:
object.__setattr__(self,item,value)
if hasattr(self, item) and isinstance(getattr(self, item), _FlagsUnionBase):
if isinstance(value, (bytes, bytearray)):
getattr(self, item)._flags = unpack(value.ljust(context.bytes, b'\x00'))
else:
getattr(self, item)._flags = value
else:
object.__setattr__(self,item,value)
else:
log.error("Unknown variable %r" % item)

def __repr__(self):
structure=[]
for i in self.vars_:
structure.append(" %s: %#x" % (i, getattr(self, i)))
val = getattr(self, i)
if isinstance(val, int):
structure.append(" %s: %#x" % (i, val))
else:
structure.append(" %s: %s" % (i, val))
return "{"+ "\n".join(structure)+"}"

def __len__(self):
Expand All @@ -189,7 +310,7 @@ def __bytes__(self):
structure += getattr(self, val).ljust(context.bytes, b'\x00')
else:
if self.length[val] > 0:
structure += pack(getattr(self, val), self.length[val]*8)
structure += pack(int(getattr(self, val)), self.length[val]*8)
return structure

def struntil(self,v):
Expand Down Expand Up @@ -217,13 +338,13 @@ def struntil(self,v):
if isinstance(getattr(self, val), bytes):
structure += getattr(self, val).ljust(context.bytes, b'\x00')
else:
structure += pack(getattr(self, val), self.length[val]*8)
structure += pack(int(getattr(self, val)), self.length[val]*8)
if val == v:
break
return structure[:-1]

def setdefault(self,null):
self.flags=0
self.flags=_IOFileFlags()
self._IO_read_ptr=0
self._IO_read_end=0
self._IO_read_base=0
Expand All @@ -238,7 +359,7 @@ def setdefault(self,null):
self.markers=0
self.chain=0
self.fileno=0
self._flags2=0
self._flags2=_IOFileFlags2()
self._old_offset=0
self._cur_column=0
self._vtable_offset=0
Expand All @@ -248,7 +369,11 @@ def setdefault(self,null):
self._offset=0xffffffffffffffff
self._codecvt=0
self._wide_data=null
self.unknown2=0
self._freeres_list=0
self._freeres_buf=0
self._pad5=0
self._mode=0
self._unused2=0
self.vtable=0

def write(self,addr=0,size=0):
Expand All @@ -271,8 +396,8 @@ def write(self,addr=0,size=0):
>>> payload
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'
"""
self.flags &=~8
self.flags |=0x800
self.flags._IO_NO_WRITES = 0
self.flags._IO_CURRENTLY_PUTTING = 1
self._IO_write_base = addr
self._IO_write_ptr = addr+size
self._IO_read_end = addr
Expand All @@ -299,7 +424,7 @@ def read(self,addr=0,size=0):
>>> payload
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'
"""
self.flags &=~4
self.flags._IO_NO_READS = 0
self._IO_read_base = 0
self._IO_read_ptr = 0
self._IO_buf_base = addr
Expand Down
Loading