Last active
October 30, 2015 15:02
-
-
Save laanwj/01dc996c4755d19e134e to your computer and use it in GitHub Desktop.
Overwrite 4/8-byte heap leak from old mingw binutils
This file contains 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/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) |
This file contains 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
int main() | |
{ | |
return 0; | |
} |
This file contains 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
#!/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