fontfuzzer/fuzzers_downloaded/native.py
#!/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()