hackedteam/fuzzer-windows

View on GitHub
fontfuzzer/fuzzers_downloaded/native.py

Summary

Maintainability
F
6 days
Test Coverage
#!/usr/bin/env python

# awful
import sys
sys.path.append('../')
import parsers.TTF as TTF

import os
import time
import random
import shutil
import string
import struct
import hashlib
import win32api
import win32gui
import threading

from ctypes import *
from struct import *
from win32con import *
from fontTools import ttLib



FONT_SPECIFIER_NAME_ID = 4
FONT_SPECIFIER_FAMILY_ID = 1
FR_PRIVATE=0x10


def shortName(font):
    name = ""
    family = ""

    for record in font['name'].names:
        if record.nameID == FONT_SPECIFIER_NAME_ID and not name:
            if '\000' in record.string:
                name = unicode(record.string, 'utf-16-be').encode('utf-8')
            else:
                name = record.string
        elif record.nameID == FONT_SPECIFIER_FAMILY_ID and not family:
            if '\000' in record.string:
                family = unicode(record.string, 'utf-16-be').encode('utf-8')
            else:
                family = record.string
        if name and family:
            break
    return name, family

# for deployment
class mainWindow():

    def __init__(self):
        win32gui.InitCommonControls()
        self.hinst = windll.kernel32.GetModuleHandleW(None)
        self.classNameRandom = ''.join( random.choice(string.ascii_lowercase + string.digits) for x in range(6) )

    def CreateWindow(self):
        reg = self.RegisterClass()
        hwnd = self.BuildWindow(reg)
        return hwnd


    def RegisterClass(self):
        WndProc = { WM_DESTROY: self.OnDestroy }
        wc = win32gui.WNDCLASS()
        wc.hInstance = self.hinst
        wc.hbrBackground = COLOR_BTNFACE + 1
        wc.hCursor = win32gui.LoadCursor(0, IDC_ARROW)
        wc.hIcon = win32gui.LoadIcon(0, IDI_APPLICATION)
        wc.lpszClassName = 'FontFuzzer{}'.format(self.classNameRandom)
        wc.lpfnWndProc = WndProc
        reg = win32gui.RegisterClass(wc)
        return reg

    def BuildWindow(self, reg):
        hwnd = windll.user32.CreateWindowExW (
            WS_EX_TOPMOST | WS_EX_NOACTIVATE,
            reg, # atom returned by RegisterClass
            'FontFuzzer{}'.format(self.classNameRandom),
            WS_POPUP,
            10,
            10,
            2000,
            400,
            0,
            0,
            self.hinst,
            0 )

        windll.user32.ShowWindow(hwnd, SW_SHOW)
        windll.user32.UpdateWindow(hwnd)
        return hwnd


    def OnDestroy(self, hwnd, message, wparam, lparam):
        win32gui.PostQuitMessage(0)
        return True


def draw(handleDeviceContext, char_map):

        chars_not_rendered = 0
        chars_rendered = 0

        array_types = c_wchar * len(char_map)
        var1 = array_types()

        for y in range(0, len(char_map) ):

            var1[y] = char_map[y]

            ETO_GLYPH_INDEX = 16
            
            result = windll.gdi32.ExtTextOutW(  handleDeviceContext,
                                                5,
                                                5,
                                                ETO_GLYPH_INDEX,
                                                None,
                                                var1,
                                                len(var1),
                                                None )

            #try:
            #    print '[D]\t{} : {}'.format(var1[y], result)
            #except:
            #    continue

            if result == 0:
                chars_not_rendered +=1
            elif result == 1:
                chars_rendered += 1

        return chars_rendered, chars_not_rendered
    

def deploy(fontPath, ttfInstance=None):


    # setup
    fontName = ''.join( random.choice(string.digits) for i in range(0,6) ) #shortName(ttfInstance)[1]
    lf = win32gui.LOGFONT()
    number_of_font_added = windll.gdi32.AddFontResourceExA(fontPath, FR_PRIVATE, None)
    assert number_of_font_added == 1,  'Font not added' # TODO: what happens when a font is not added?

    win = mainWindow()
    hwnd = win.CreateWindow()
    hdc = windll.user32.GetDC(hwnd)

   

    # draw some crap
    chars_not_rendered = 0
    chars_rendered = 0
    
    for j in range(10, 100, 10):
        time.sleep(.1)
        lf.lfHeight     = j #int(random.choice(string.digits))
        lf.lfFaceName   = fontName
        lf.lfWidth      = j #int(random.choice(string.digits))
        lf.lfEscapement = 0
        lf.lfOrientation= 0
        lf.lfWeight     = FW_NORMAL
        lf.lfItalic     = False
        lf.lfUnderline  = False
        lf.lfStrikeOut  = False
        lf.lfCharSet    = DEFAULT_CHARSET
        lf.lfOutPrecision = OUT_DEFAULT_PRECIS
        lf.lfClipPrecision = CLIP_DEFAULT_PRECIS
        lf.lfPitchAndFamily = DEFAULT_PITCH|FF_DONTCARE

        hFont = win32gui.CreateFontIndirect(lf)
        oldFt = win32gui.SelectObject(hdc, hFont) # replace font

        #print '[*]', lf.lfFaceName, ':', lf.lfWidth, ' - ', lf.lfHeight

        # chars to draw
        char_map = [ chr(i) for i in range(1,255) ]


        chars_rendered_current, chars_not_rendered_current = draw(hdc, char_map)
        chars_rendered += chars_rendered_current
        chars_not_rendered += chars_not_rendered_current
            
        windll.gdi32.DeleteObject( win32gui.SelectObject(hdc, oldFt) )
        windll.gdi32.GdiFlush()
        windll.gdi32.RemoveFontResourceExW( fontPath, FR_PRIVATE, None)
        
    print '[*]\t{} chars not rendered'.format(chars_not_rendered)
    print '[*]\t{} chars rendered'.format(chars_rendered)

    # bail    
    windll.user32.ReleaseDC(hwnd, hdc)
    windll.user32.DestroyWindow(hwnd)
    print '[*] Destroy window'
    time.sleep(1)



def generateTestCases(fontSourceDir):
    fonts = {}

    testcasesFolder = 'testcases'
    
    for i in os.listdir(sys.argv[1]):
            
        print '----------- Start -------------------'
        print '[*] Loading {}'.format(i)
        abspath =  os.path.abspath(sys.argv[1] + '\\' + i)

        try:
            tt = TTF.TTFont(abspath)
            fonts[abspath] = tt
            
            print '[*] Fuzzing {}'.format(abspath)
            
            fileInMemory, isFontFuzzed = fonts[abspath].fuzzFontBytecode()
            #fonts[abspath].fuzzDirectoriesBitFlipping()
            #fileInMemory = fonts[abspath].fuzzCffTableBitFlipping()


            if isFontFuzzed:
                testcaseName = os.path.abspath( os.path.join( testcasesFolder,
                                                              os.path.basename(abspath).split('.otf')[0] + '_' +
                                                              ''.join( random.choice( string.ascii_lowercase + string.digits) for x in range(8) ) + '.otf')
                                                )

                open( testcaseName, 'wb').write(fileInMemory)
                self.fontsPath.append( '/static' + testcaseName.split('/static')[1] )

            else:
                print '[*] Font {} not fuzzed'.format(abspath)
           

            print '------------ End -------------------'
        except Exception as e:
            print e
            continue
    
    return len( os.listdir(testcasesFolder) )


def viewerDeploy():

    print '[*] Deploy fonts found in testcases folder'
    for i in os.listdir('testcases'):
        try:
            print '[*] Testing {}'.format(i)
            abspath = os.path.abspath('testcases' + '\\' + i)
            deploy(abspath, None) #ttLib.TTFont(abspath) )

        except Exception as e:
            print '[E] Issues parsing font {}: {}'.format(i, e)
            continue



def validateInputFonts(fontSourceDir):
    fonts = {}
    for i in os.listdir(sys.argv[1]):
        try:
            abspath =  os.path.abspath(sys.argv[1] + '\\' + i)
            tt = ttLib.TTFont(abspath)
            fonts[abspath] = tt
            print '[*] Validated {} TTF'.format(shortName(tt)[1])
        except Exception as e:
            print '[E] Issues parsing font {}: {}'.format(i, e)
            continue


def cleanUp():
    
    print '[*] Cleaning up fuzzed fonts'
    for i in os.listdir('testcases'):
        try:
            #shutil.move('testcases\\' + i, 'archive\\' + i)
            os.remove('testcases\\' + i)
        except:
            continue

'''
For fuzzing framework
'''
class NativeFuzzer(threading.Thread):
    
    def __init__(self, folder):
        self.fontsFolder = folder
        self.stopMe = False

   
    def run(self):
    
        while not self.stopMe:
        
            # 2] generate test cases
            numberOfTestCases = generateTestCases(self.fontsFolder)
        
            print '[*] Generated {} test cases'.format(numberOfTestCases)
        

            for i in os.listdir('testcases'):
                m = hashlib.md5()
                m.update(open('testcases' + '\\' + i, "rb").read())
                print '[*]\t {} : {}'.format(i, m.hexdigest())
                            
            # 3] deploy
            viewerDeploy()

    def stop(self):
        self.stopMe = True        
        
    
    def getDescription(self):
        return 'Native fuzzer'


def getFuzzerInstance(folder):
    return NativeFuzzer(folder)


if __name__ == '__main__':
 
 
    if( len(sys.argv[1:]) < 1 ):
        print 'Usage: {} font_directory'.format(sys.argv[0])

    elif( len(sys.argv[1:]) == 2 and sys.argv[1] == 'd'):
        try:
            print '[*] Testing {}'.format(sys.argv[2])
            abspath = os.path.abspath(sys.argv[2])
            deploy(abspath, ttLib.TTFont(abspath) )

        except Exception as e:
            print '[E] Issues parsing font {}: {}'.format(sys.argv[2], e)
            
        
    else:

        # display font
        if len(sys.argv[1:]) == 2 and sys.argv[1] == 'd':
            abspath = os.path.abspath(sys.argv[2])
            deploy(abspath, ttLib.TTFont(abspath))

        # fuzz
        else:
            for i in range(0, 2):
                #break
            
                # 2] generate test cases
                numberOfTestCases = generateTestCases(sys.argv[1])
                
                print '[*] Generated {} test cases'.format(numberOfTestCases)
                

                for i in os.listdir('testcases'):
                    m = hashlib.md5()
                    m.update(open('testcases' + '\\' + i, "rb").read())
                    print '[*]\t {} : {}'.format(i, m.hexdigest())
            
                                
            # 3] deploy
            viewerDeploy()