Fuzzing是一种高效的漏洞挖掘方法,它通过不断地让被测程序处理各种畸形测试数据来挖掘软件漏洞。一个Fuzz工具由三个基础模块组成,分别是测试用例生成模块、程序执行模块以及异常检测模块。
-
测试用例生成模块负责不断的生成测试用例,然后会把测试用例交给程序执行模块。 -
程序执行模块根据被测程序接收数据的方式启动程序并把测试用例交给目标程序处理。 -
异常检测模块负责监控程序在处理测试用例时是否发生异常,如果发生了异常就保存异常信息。
struct{
int ID;
int Size; // data 的长度
data[Size]; // data数组
}
<DataModel name="Chunk">
<Number name="ID" size="32" value="0x12345678" />
<Number name="Size" size="32" >
<Relation type="size" of="Data" />
</Number>
<Blob name="Data" valueType="hex" value="aa bb cc dd ee ff 11 22"/>
</DataModel>
ID: 78 56 34 12 // 0x12345678的小端表示
Size: 08 00 00 00 // 4字节,表示Data的长度
Data: aa bb cc dd ee ff 11 22 // 8 个字节的Data数据
-
从一些提供样本集的在线站点获取 -
通过搜索引擎的语法爬取大量的样本文件 -
一些开源项目会带一些测试用例来测试程序 -
Fuzz其他类似软件时生成的样本文件 -
目标程序或者类似程序的bug提交页面 -
用格式转换工具生成
https://files.fuzzing-project.org/
http://samples.ffmpeg.org/
http://lcamtuf.coredump.cx/afl/demo/
https://github.com/MozillaSecurity/fuzzdata
https://github.com/strongcourage/fuzzing-corpus
https://github.com/Cisco-Talos/clamav-fuzz-corpus
https://github.com/mozilla/pdf.js/tree/master/test/pdfs
https://github.com/codelibs/fess-testdata
https://github.com/google/honggfuzz/tree/master/examples/apache-httpd
文件Fuzz
Generation Based Fuzzing
http://community.peachfuzzer.com/v3/ref.html
89 50 4E 47 0D 0A 1A 0A
struct{
int length; // chunk 的长度
type[4]; // chunk 的类型
data[length]; // chunk 的数据
int crc; // data 的 crc校验和
}
http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
<DataModel name="Chunk">
<Number name="Length" size="32" endian="big" mutable="true">
<Relation type="size" of="Data" />
</Number>
<Block name="TypeData">
<Blob name="Type" length="4" />
<Blob name="Data" />
</Block>
<Number name="crc" size="32" endian="big" mutable="true">
<Fixup class="Crc32Fixup">
<Param name="ref" value="TypeData"/>
</Fixup>
</Number>
</DataModel>
Number: 表示数字类型, size指定该字段的大小,单位是 bit(位),这里就是32bit,也就是4个字节
Blob: 表示一段二进制数据,length指定二进制数据的长度,单位是自己
Block: 用于组合表示一些字段
Relation: 表示字段之间的关系,比如上面表示Length是Data的长度
Fixup: 用于基于其他的字段计算一个值比如hash、crc等,文件中crc的值就是是TypeData的crc校验和
<DataModel name="Chunk_IHDR" ref="Chunk">
<Block name="TypeData">
<String name="Type" value="IHDR" length="4" token="true"/>
<Block name="Data">
<Number name="width" size="32" mutable="true"/>
<Number name="height" size="32" mutable="true"/>
<Number name="bits" size="8" mutable="false"/>
<Number name="color_type" size="8" />
<Number name="compression" size="8" />
<Number name="filter" size="8" />
<Number name="interlace" size="8" />
</Block>
</Block>
</DataModel>
<DataModel name="Chunk_IEND" ref="Chunk">
<Block name="TypeData">
<String name="Type" value="IEND" length="4" token="true" />
<Blob name="Data" length="0" />
</Block>
</DataModel>
<DataModel name="Png">
<Number name="Signature" valueType="hex" value="89504e470d0a1a0a" size="64" token="true" />
<Block name="IHDR" ref="Chunk_IHDR"/>
<Choice name="DataChunks" minOccurs="2" maxOccurs="30000">
<Block name="PLTE" ref="Chunk"/>
</Choice>
<Block name="IEND" ref="Chunk_IEND"/>
</DataModel>
-
首先8个字节的Signature开头 -
然后以一个IHDR的chunk开始 -
然后使用Choice随机生成多个chunk -
最后生成一个type为IEND的chunk
<StateModel name="TheState" initialState="Initial">
<State name="Initial">
<Action type="output">
<DataModel ref="Png"/>
</Action>
<Action type="close"/>
# 输出样本后 call LaunchViewer 通知 Agent
<Action type="call" method="LaunchViewer" publisher="Peach.Agent"/>
</State>
</StateModel>
<Agent name="WinAgent">
<Monitor class="WindowsDebugger">
# 被测程序执行的命令行
<Param name="CommandLine" value='"C:Program FilesHoneyviewHoneyview.exe" "C:\Users\XinSai\Desktop\honeyview.png"' />
# 当接收到 call LaunchViewer 后才启动被测程序
<Param name="StartOnCall" value="LaunchViewer" />
<Param name="CpuKill" value="true"/>
</Monitor>
</Agent>
<Test name="Default">
<Agent ref="WinAgent" platform="windows"/>
<StateModel ref="TheState"/>
# 定义输出样本数据到 C:UsersXinSaiDesktophoneyview.png
<Publisher class="File">
<Param name="FileName" value="C:\Users\XinSai\Desktop\honeyview.png"/>
</Publisher>
<Logger class="Filesystem">
<Param name="Path" value="logs" />
</Logger>
</Test>
Peach.exe samplesdemo.xml
https://github.com/aflsmart/aflsmart/blob/master/input_models/png.xml
Mutation Based Fuzzing
MANGLE_FUNCS = [
lambda data: mangle_bytes(data),
lambda data: mangle_magic(data),
lambda data: mangle_add_sub(data),
lambda data: mangle_mem_copy(data),
lambda data: mangle_mem_insert(data),
lambda data: mangle_memset_max(data),
lambda data: mangle_random(data),
lambda data: mangle_clonebyte(data),
lambda data: mangle_expand(data),
lambda data: mangle_shrink(data),
lambda data: mangle_insert_rnd(data),
lambda data: mangle_copy_token(data),
lambda data: mangle_insert_token(data)
]
def mangle_bytes(data):
"""
在随机位置覆盖写2~4字节数据
"""
length = len(data)
if length < 4:
return data
# 获取要填充的数据的长度
size = random.randint(2, 4)
# 获取插入位置, length - size 确保不会越界
idx = random.randint(0, length - size)
# 获取 size 长的随机字符串, 然后复写到指定位置
return replace_string(data, get_random_string(size), idx)
def mutate(self, data, maxlen=0xffffffff, fuzz_rate=1):
"""
对 data 进行变异
:param data: 待变异的数据
:param callback: 对变异后的数据进行修正的callback 函数,比如 crc, header等
:param fuzz_rate: 数据变异的比率, 用于决定变异的数据长度 , len(data) * fuzz_rate
:return:
"""
length = len(data)
fuzz_len = int(length * fuzz_rate)
# 选取数据中的某个区域传给数据变异函数去进行变异操作
off = random.randint(0, length - fuzz_len)
pre = data[:off]
post = data[off + fuzz_len:]
data = data[off:off + fuzz_len]
count = random.randint(1, self.mutate_max_count)
for i in xrange(count):
# 随机选取一个变异函数
func = self.mutate_funcs[random.randint(0, self.mutate_func_count - 1)]
data = func(data)
if len(data) >= maxlen:
data = data[:maxlen - 1]
data = pre + data + post
# 对数据进行后处理,比如计算校验和等
if self.callback:
data = self.callback(data)
return data
def fuzz(self):
count = 1
start = time.time()
while True:
seed = self.read_file_data(random.choice(self.seeds))
for i in range(10):
print "test: {}".format(count)
# 从 seed 变异出测试用例
self.testcase = self.mutater.mutate(seed, fuzz_rate=0.3)
# 把测试用例写入文件
self.write_file_data(self.target_read_from, self.testcase)
# 用调试器加载程序让它去处理数据
debugger = winappdbg.Debug(self.exception_handler, bKillOnExit=True)
proc = debugger.execv(self.cmdline)
# 开一个线程超时就 kill 掉进程
thread.start_new_thread(self.timeout_killer, (proc,))
debugger.loop()
if count % 10 == 0:
delta = time.time() - float(start)
# print delta
print "test rate: {}/s".format(delta / count)
count += 1
1. 随机从初始样本集中选取一个样本进行变异。
2. 把生成的变异数据写入文件。
3. 然后用winappdbg启动目标程序处理数据。
4. 然后新建一个线程 timeout_killer ,这个线程在指定的超时时间后杀掉目标进程。
5. debugger.loop()用于等待被测进程结束。
这样一来我们的 Fuzzer 基本功能就完成了,下面组合一下就可以开始fuzzing了。
if __name__ == "__main__":
cmd = '"C:\Program Files\HoneyviewHoneyview.exe" "C:\Users\XinSai\Desktop\honeyview.gif"'
print shlex.split(cmd)
fuzzer = FileFuzzer("C:\fuzz\afl_testcases\gif\full\", shlex.split(cmd),
"C:\Users\XinSai\Desktop\honeyview.gif")
fuzzer.fuzz()
cmd: 执行目标程序处理数据的命令行
C:fuzzafl_testcasesgiffull: 初始样本集所在的目录
C:UsersXinSaiDesktophoneyview.gif: 执行cmd后目标程序读取文件的路径,后面Fuzzer会把生成的测试数据写入这里
Fuzzing优化
基于二进制patch的方案
-
首先系统会从dynamorio的代码开始执行,dynamorio会把目标程序加载起来并获取到程序的入口点。 -
dynamorio从被插桩程序的代码区域读取代码并解析指令,然后对解析得到的指令进行增加和修改,构建基本块。 -
然后把构建好的基本块的代码存放到 dynamorio的code cache里面。 -
dynamorio转移cpu的控制权,被插桩程序去code cache里面执行基本块。 -
如果发现要接下来要执行的基本块不在code cache里面,被插桩程序会转移cpu控制权给dynamorio,进入第2步不断翻译基本块。
DR_EXPORT void
dr_client_main(client_id_t id, int argc, const char *argv[])
{
// 一些初始化操作
dr_set_client_name("BBLOG", "");
drmgr_init();
drx_init();
drwrap_init();
dr_register_exit_event(event_exit);
// 注册模块加载回调函数,当有新模块加载时会调用 event_module_load
drmgr_register_module_load_event(event_module_load);
// 打开 log.txt, 用于存放bbl信息
LOG_FD = dr_open_file("log.txt", DR_FILE_WRITE_APPEND);
//注册基本块构建函数,每当有新的基本块构建好后会调用 event_bb_analysis 函数
drmgr_register_bb_instrumentation_event(event_bb_analysis, NULL, NULL);
}
static dr_emit_flags_t
event_bb_analysis(void *drcontext, void *tag, instrlist_t *bb, bool for_trace,
bool translating, void **user_data)
{
app_pc pc = dr_fragment_app_pc(tag);
if (pc > target.start && pc < target.end) {
dr_fprintf(LOG_FD, "%pn", pc - target.start);
}
return DR_EMIT_DEFAULT;
}
static void
event_module_load(void *drcontext, const module_data_t *info, bool loaded)
{
const char *module_name = info->names.exe_name;
if (module_name == NULL) {
module_name = dr_module_preferred_name(info);
}
dr_fprintf(LOG_FD, "Module loaded, %sn", module_name);
if (strstr(module_name, "Honeyview.exe")) {
target.start = info->start;
target.end = info->end;
dr_fprintf(LOG_FD, "Honeyview.exe:%p-----%pn", target.start, target.end);
}
}
cmake -G"Visual Studio 14 2015" .. -DDynamoRIO_DIR=C:UsersXinSaiDesktopwinafl-masterDynamoRIO-Windows-7.91.18187-0cmake
cmake -G"Visual Studio 14 2015 Win64" .. -DDynamoRIO_DIR=C:UsersXinSaiDesktopwinafl-masterDynamoRIO-Windows-7.91.18187-0cmake
DynamoRIO-Windows-7.91.18187-0bin64drrun.exe -c C:UsersXinSaiDesktophoneyviewbblogbuild64Debugbblog.dll -- "C:Program FilesHoneyviewHoneyview.exe" C:UsersXinSaiDesktophoneyviewpnggrad16rgb.png
0x00000000001eed32
0x00000000001e7008
0x00000000001e7028
0x00000000001e7066
0x00000000001ec1af
0x00000000001eed3c
0x0000000000283744
0x0000000000290950
0x0000000000290965
while True:
if 有用户请求:
处理
else:
休息一段时间
.text:00000001401E885E mov rcx, rdi
.text:00000001401E8861 mov [rdi+393Ch], ebp
.text:00000001401E8867 call sub_1401EBE70 //会执行到 1eed3c
.text:00000001401E886C test eax, eax
.text:00000001401E886E jnz short loc_1401E8878
.text:00000001401E8870 mov rcx, rdi
.text:00000001401E8861 xor rcx, rcx
.text:00000001401E8864 nop
.text:00000001401E8865 nop
.text:00000001401E8866 nop
.text:00000001401E8867 call ExitProcess
基于进程cpu监控的方案
def get_cpu_usage_by_pid_no(self, pid):
p = psutil.Process(pid)
p.cpu_percent(None)
sleep(0.5)
usage = p.cpu_percent(None)
return usage
def kill_when_cpu_free(self, proc):
"""
当cpu使用率为0时 kill 进程
:param proc:
:return:
"""
count = 0
while count < 1:
usage = self.get_cpu_usage_by_pid_no(proc.get_pid())
print usage
if usage == 0:
count += 1
try:
proc.kill()
except:
pass
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论