前言
对图形界面程序的模糊测试无外乎以下这三种方法(以下为仅使用afl/winafl的):
1.针对有源码的可以进行patch使其跳过界面显示阶段直接结束。(可参考:Fuzzing Linux GUI/GTK Programs With American Fuzzy Lop )
2.针对无源码的可以写harness。(可参考:50 CVEs in 50 Days: Fuzzing Adobe Reader)
3.通过监控被fuzz的GUI程序在一定时间内将其kill掉。
本文属于第三种方法,但通过Autoit进行辅助可以拓展出更加丰富的功能而不局限于仅仅将被fuzz程序关闭。
Autoit简介
AutoIt是一个用于Windows的自动化语言。它兼容于Windows 95,98,ME,NT4,2000,XP,2003,Vista以及Windows 7。AutoIt自动化脚本可以编译成压缩、单一的可执行文件,这样的文件可以运行在没有安装AutoIt解释器的计算机上。
部分功能:调用Win32 DLL中的函数、模拟鼠标移动、操作窗口和进程、自动发送用户输入和键击到应用程序以及程序中的单个控件、运行控制台应用程序和访问标准流、包含文件在编译文件中以便在运行时提取、GUI接口,创建消息和输入框、支持COM。
最初的思路
一开始的思路是使用Autoit的Run或者ShellExecute函数执行被fuzz函数通过发送快捷键或者结束进程的方式将其关闭,将脚本打包成exe,这样用winafl去fuzz被打包过的exe。但实际测试过程中发现winafl只会记录打包的exe的代码覆盖率。被fuzz程序实际上是以子进程的形式去运行。翻看winafl的issue发现作者的这样一段话:
DynamoRIO does indeed instrument child processes by default, but this is disabled in WinAFL here: https://github.com/ivanfratric/winafl/blob/master/afl-fuzz.c#L2089
(note the -no_follow_children flag).
The main reason is communication of target process and afl-fuzz: Target process needs to connect to a pipe and shared memory exposed by afl-fuzz. IIRC this led to problems in the past when multiple processes attempted to connect to the same pipe/shared memory and I put -no_follow_children to fix that.
A possible alternative could be to only connect to pipe/shm once the target function is reached (and hope it's only going to get reached in 1 process). Not sure if this would result in some other problems, difficult to say off the top of my head.
由于winafl使用管道与目标进程进行通信,多个进程同时连接同一管道会出现问题。
最后实现的方法
使用Autoit的 WinWait 函数监控被fuzz程序的主窗口类,捕获主窗口类后可以对其发送快捷键或鼠标点击事件,之后再对其关闭。实现脚本如下:
#include <Constants.au3>
While True
Local $hWnd = WinWait("[CLASS:Notepad]")
If WinActive($hWnd) Then
Sleep(1000)
Send("{ESC}")
WinClose($hWnd)
EndIf
WEnd
但这个脚本存在一个问题,若其他程序主窗口类名和其相同也会被捕获从而造成干扰,在查阅autoit文档后改进如下:
#cs ----------------------------------------------------------------------------
AutoIt Version: 3.3.14.5
Author: 1vanChen
Name: fuzzHelper
Script Function:
Get target program window class
Open target program and close it automatically
Usage:
Find target program window class (make sure the program is running): fuzzHelper.exe programName.exe
Open target program and close it automatically: fuzzHelper.exe ProgramName.exe WindowClsName
#ce ----------------------------------------------------------------------------
#include <Constants.au3>
#include <MsgBoxConstants.au3>
#include <WindowsConstants.au3>
#include <WinAPI.au3>
#pragma compile(Console, True)
If $CmdLine[0]==2 Then
While True
Local $hWnd = _WinGetHandleByPnmAndCls($CmdLine[1],$CmdLine[2])
If WinActive($hWnd) Then
Sleep(500)
;此处只是发送"关闭"这个快捷键,
;还可以根据需要添加其他要发送的快捷键
Send("{ESC}")
WinClose($hWnd)
EndIf
WEnd
ElseIf $CmdLine[0]==1 Then
Local $pid = ProcessExists($CmdLine[1])
If $pid Then
_WinGetWindowClsByPid($pid)
Else
MsgBox($MB_SYSTEMMODAL, "", "程序未找到")
EndIf
EndIf
; 根据pid打印该程序所有窗口类
Func _WinGetWindowClsByPid ($pid )
Local $winArr = _WinAPI_EnumWindowsTop()
For $i=1 To $winArr[0][0]
If $pid=WinGetProcess($winArr[$i][0])Then
ConsoleWrite($winArr[$i][1] & @CRLF)
EndIf
Next
EndFunc
; 根据pname和class获取窗口句柄,找不到则返回0
Func _WinGetHandleByPnmAndCls($pname, $class)
Local $pid = ProcessExists($pname)
If $pid Then
return _WinGetHandleByPidAndCls($pid, $class)
Else
Return 0
EndIf
EndFunc
; 根据pid和class获取窗口句柄,找不到则返回0
Func _WinGetHandleByPidAndCls($pid, $class)
Local $winArr = _WinAPI_EnumWindowsTop()
For $i=1 To $winArr[0][0]
If $pid=WinGetProcess($winArr[$i][0]) And $winArr[$i][1]=$class Then
return $winArr[$i][0]
EndIf
Next
Return 0
EndFunc
该脚本可以直接运行也可以打包成exe,有两个功能:
1.在打开被fuzz程序的形况下运行:fuzzHelper.exe programName.exe
列出被fuzz程序窗口类。
2.在启动winafl前运行: fuzzHelper.exe ProgramName.exe WindowClsName
捕获被fuzz程序窗口类并执行相应操作。
总结
这种方式最大的优势在于不需要对程序进行任何前期逆向工作,无脑fuzz。缺点在于由于需要加载图形界面或者执行相应操作,fuzz过程会变得异常缓慢,我在测试的时候一个test case大约需要2秒。目前可以想到的提升速度的方法有:多核fuzz、虚拟机cpu加速、移植论文中改进的变异算法...但终究和写harness这种在速度上不是一个数量级的。
在fuzz中减少人工投入还是有很长的路要走...
参考链接
https://www.autoitscript.com/autoit3/docs/
https://blog.csdn.net/moonshine_1988/article/details/48006043