Skip to content

Instantly share code, notes, and snippets.

@laanwj
Last active October 30, 2015 15:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save laanwj/01dc996c4755d19e134e to your computer and use it in GitHub Desktop.
Save laanwj/01dc996c4755d19e134e to your computer and use it in GitHub Desktop.
Overwrite 4/8-byte heap leak from old mingw binutils
#!/usr/bin/python2
# W.J. 2015 (License: MIT)
'''
Overwrite 4/8-byte heap leak from old mingw binutils.
Input: test.exe test.map
Create linker map with -Wl,-Map=mtest.map
'''
from __future__ import print_function,division
import struct
from binascii import b2a_hex
from collections import namedtuple
import os, subprocess
# external utilities
STRIP = os.getenv('STRIP', 'strip')
class ParseError(Exception):
pass
def parse_dos_header(data):
'''Parse DOS header, return offset of NT header'''
if data[0:2] != 'MZ':
raise ParseError('No MZ magic found, input is likely not windows PE executable')
return struct.unpack('<I', data[60:64])[0]
FieldDesc = namedtuple('FieldDesc', ['name', 'size', 'char', 'offset'])
class Header(object):
'''Generic header structure parser'''
def __init__(self, types, desc):
self.hdesc = {}
offset = 0
for line in desc.strip().split('\n'):
line = line.split()
tchar = types[line[0]]
tsize = struct.calcsize(tchar)
self.hdesc[line[1]] = FieldDesc(line[1], tsize, tchar, offset)
offset += tsize
self.size = offset
def get(self, data, field):
fdesc = self.hdesc[field]
return struct.unpack(fdesc.char, data[fdesc.offset:fdesc.offset+fdesc.size])[0]
# PE header descriptions
ms_types = {'BYTE':'<B','WORD':'<H','DWORD':'<I','ULONGLONG':'<Q'}
# File header
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=vs.85).aspx
FH = Header(ms_types, '''
WORD Machine
WORD NumberOfSections
DWORD TimeDateStamp
DWORD PointerToSymbolTable
DWORD NumberOfSymbols
WORD SizeOfOptionalHeader
WORD Characteristics
''')
# 32 bit 'optional'header
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx
OH32 = Header(ms_types, '''
WORD Magic
BYTE MajorLinkerVersion
BYTE MinorLinkerVersion
DWORD SizeOfCode
DWORD SizeOfInitializedData
DWORD SizeOfUninitializedData
DWORD AddressOfEntryPoint
DWORD BaseOfCode
DWORD BaseOfData
DWORD ImageBase
DWORD SectionAlignment
DWORD FileAlignment
WORD MajorOperatingSystemVersion
WORD MinorOperatingSystemVersion
WORD MajorImageVersion
WORD MinorImageVersion
WORD MajorSubsystemVersion
WORD MinorSubsystemVersion
DWORD Win32VersionValue
DWORD SizeOfImage
DWORD SizeOfHeaders
DWORD CheckSum
WORD Subsystem
WORD DllCharacteristics
DWORD SizeOfStackReserve
DWORD SizeOfStackCommit
DWORD SizeOfHeapReserve
DWORD SizeOfHeapCommit
DWORD LoaderFlags
DWORD NumberOfRvaAndSizes
''')
# 64 bit 'optional'header
OH64 = Header(ms_types, '''
WORD Magic
BYTE MajorLinkerVersion
BYTE MinorLinkerVersion
DWORD SizeOfCode
DWORD SizeOfInitializedData
DWORD SizeOfUninitializedData
DWORD AddressOfEntryPoint
DWORD BaseOfCode
ULONGLONG ImageBase
DWORD SectionAlignment
DWORD FileAlignment
WORD MajorOperatingSystemVersion
WORD MinorOperatingSystemVersion
WORD MajorImageVersion
WORD MinorImageVersion
WORD MajorSubsystemVersion
WORD MinorSubsystemVersion
DWORD Win32VersionValue
DWORD SizeOfImage
DWORD SizeOfHeaders
DWORD CheckSum
WORD Subsystem
WORD DllCharacteristics
ULONGLONG SizeOfStackReserve
ULONGLONG SizeOfStackCommit
ULONGLONG SizeOfHeapReserve
ULONGLONG SizeOfHeapCommit
DWORD LoaderFlags
DWORD NumberOfRvaAndSizes
''')
# section header
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms680341(v=vs.85).aspx
SH = Header(ms_types, '''
ULONGLONG Name
DWORD PhysicalAddressVirtualSize
DWORD VirtualAddress
DWORD SizeOfRawData
DWORD PointerToRawData
DWORD PointerToRelocations
DWORD PointerToLinenumbers
WORD NumberOfRelocations
WORD NumberOfLinenumbers
DWORD Characteristics
''')
SectionDesc = namedtuple('SectionDesc', ['virtual_address','size_of_raw_data','pointer_to_raw_data'])
class Sections(object):
'''PE executable sections'''
def __init__(self, imagebase, data):
ptr = 0
self.sections = []
while ptr < len(data):
section_header = data[ptr:ptr+SH.size]
virtual_address = SH.get(section_header, 'VirtualAddress')
size_of_raw_data = SH.get(section_header, 'SizeOfRawData')
pointer_to_raw_data = SH.get(section_header, 'PointerToRawData')
self.sections.append(SectionDesc(virtual_address+imagebase, size_of_raw_data, pointer_to_raw_data))
ptr += SH.size
def addr_to_offset(self, addr):
for s in self.sections:
if s.virtual_address < addr < (s.virtual_address + s.size_of_raw_data):
return s.pointer_to_raw_data + addr - s.virtual_address
raise KeyError('Address %016x not found' % addr)
class PEExecutable(object):
def __init__(self, f):
# parse MZ header to find PE header
dos_header = f.read(64)
nth_offset = parse_dos_header(dos_header)
f.seek(nth_offset)
magic = f.read(4)
if magic != 'PE\x00\x00':
raise ParseError('No PE magic found, input is not windows PE executable')
# parse PE header
self.file_header = f.read(FH.size)
optional_header_size = FH.get(self.file_header, 'SizeOfOptionalHeader')
self.optional_header_offset = f.tell()
self.optional_header = f.read(optional_header_size)
# parse optional header
if self.optional_header[0:2] == '\x0b\x01': # 32-bit
self.ohdesc = OH32
elif self.optional_header[0:2] == '\x0b\x02': # 64-bit
self.ohdesc = OH64
else:
raise ParseError('Invalid or no optional header magic found - cannot handle this file')
self.imagebase = self.ohdesc.get(self.optional_header, 'ImageBase')
self.sections_offset = f.tell()
number_of_sections = FH.get(self.file_header, 'NumberOfSections')
sections_data = f.read(number_of_sections * SH.size)
self.sections = Sections(self.imagebase, sections_data)
def patch_executable(filename, linker_map):
# First, determine virtual address to patch
# 32 bit:
# .rdata 0x000000000040425c 0x4 ertr000001.o
# 64 bit:
# .rdata 0x0000000000404280 0x8 ertr000001.o
patch_addr = None
patch_size = None
for line in open(linker_map, 'r'):
line = line.strip().split()
if len(line)>=4 and line[0] == '.rdata' and line[3] == 'ertr000001.o':
patch_addr = int(line[1], 16)
patch_size = int(line[2], 16)
if patch_addr is None or patch_size is None:
raise ParseError('Could not find ertr000001.o .rdata entry in linker map')
# Then process the executable
with open(filename, 'r+b') as f:
pex = PEExecutable(f)
# We located all the necessary information - do the patching
patch_ofs = pex.sections.addr_to_offset(patch_addr)
f.seek(patch_ofs)
oldval = f.read(patch_size)
newval = '\x00' * patch_size
print('%s: Patching %s to %s at offset %08x (virtual address %016x)' % (filename, b2a_hex(oldval), b2a_hex(newval), patch_ofs, patch_addr))
f.seek(patch_ofs)
f.write(newval)
# update PE checksum (using strip)
subprocess.check_call([STRIP, filename])
def main(argv):
try:
patch_executable(argv[1], argv[2])
except (ParseError,IOError) as e:
print('%s: %s' % (argv[1], e))
exit(1)
if __name__ == '__main__':
import sys
main(sys.argv)
int main()
{
return 0;
}
#!/bin/bash
# Usage: mtest.py [i686-w64-mingw32-gcc|x86_64-w64-mingw32-gcc]
COMPILER=x86_64-w64-mingw32-gcc
if [ ! -z $1 ]; then
COMPILER=$1
fi
$COMPILER mtest.c -o mtest_1 -Wl,-Map=mtest.map
$COMPILER mtest.c -o mtest_2 -Wl,-Map=mtest.map
$COMPILER mtest.c -o mtest_3 -Wl,-Map=mtest.map
sha256sum mtest_1 mtest_2 mtest_3
./bitstomp.py mtest_1 mtest.map
./bitstomp.py mtest_2 mtest.map
./bitstomp.py mtest_3 mtest.map
sha256sum mtest_1 mtest_2 mtest_3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment