这是内核漏洞挖掘技术系列的第十篇。
第一篇:内核漏洞挖掘技术系列(1)——trinity
第二篇:内核漏洞挖掘技术系列(2)——bochspwn
第三篇:内核漏洞挖掘技术系列(3)——bochspwn-reloaded(1)
第四篇:内核漏洞挖掘技术系列(3)——bochspwn-reloaded(2)
第五篇:内核漏洞挖掘技术系列(4)——syzkaller(1)
第六篇:内核漏洞挖掘技术系列(4)——syzkaller(2)
第七篇:内核漏洞挖掘技术系列(4)——syzkaller(3)
第八篇:内核漏洞挖掘技术系列(4)——syzkaller(4)
第九篇:内核漏洞挖掘技术系列(4)——syzkaller(5)

前言

KernelFuzzer(https://github.com/mwrlabs/KernelFuzzer)是一个mwrlab前几年在defcon24开源的内核fuzz工具,宣称支持Windows 7/10,OS X和QNX系统,PPT的题目就叫Platform Agnostic Kernel Fuzzing(平台无关的内核Fuzzing)。但是我仔细看过代码之后觉得吹nb有点过分了,其实基本上也就只支持windows系统。不过它们github页面上还有一个OSX内核fuzz工具(https://github.com/mwrlabs/OSXFuzz),两者的代码框架倒是有一些相似。

整体架构

我们首先还是来看一下代码的整体目录。

  • crash_processing:处理crash
  • crashes:产生的crash会存放在这个目录
  • library_calls:要fuzz的库调用
  • reproducer:复现crash(这个目录下就几行代码,基本没什么用)
  • worker_setup:设置环境,启动fuzzer
  • bughunt.c:启动fuzz的线程
  • bughunt.h:提供一些返回随机字符或数组的函数
  • bughunt_build_*_*.bat:编译用的bat文件
  • bughunt_loop.py:处理发现的creash
  • bughunt_syscall_x64/bughunt_syscall.asm:进行系统调用的汇编文件
  • bughunt_syscalls.h:要fuzz的系统调用
  • bughunt_thread.h:bughunt.c启动的进行fuzz的线程
  • handles_database.h:生成各种各样的handle
  • helpers.h:几个辅助函数
  • hooking.h:设置和取消hook
  • library_calls.h:library_calls目录下要fuzz的库调用
  • logger.h:日志功能

使用需要先在主机上安装好VS环境,使用提供的bat文件编译出可执行文件,然后将所有文件拷贝到待fuzz的虚拟机系统,在虚拟机系统上安装好python环境,运行worker_setup.py。

代码分析

接下来从worker_setup.py开始分析。在worker_setup目录下除了worker_setup.py还有windbg的安装文件和禁用UAC,禁用自动更新,禁用锁屏,禁用Windows错误报告的注册表脚本文件。

在worker_setup.py中安装了windbg并执行了这些脚本文件,还安装了python的couchdb模块,配置dump文件路径,设置在登录时运行bughunt_loop.py的计划任务,为win32k.sys启用special pool,开启内核调试,最后重启系统。在bughunt_loop.py中如果在C:/Dumps/目录下发现了.dmp文件就说明产生了crash,将其拷贝到新的文件夹,使用kd.exe打开该文件,输出记录到windbg.log,将.log日志文件拷贝到和.dmp文件相同的文件夹,调用couchdb_submit.py将crash信息提交到远程服务器上的数据库。然后运行bughunt.exe,线程设置为1,系统调用设置为350000,seed设置为1,timeout设置为10分钟。超时后执行一些清理工作并重启。
在bughunt.c中,如前所述,主要就是启动fuzz的线程。

在bughunt_thread.h的bughunt_thread函数中首先随机调用要fuzz的库调用。这里也可以看到本意可能是在进行库调用时随机用hooking.h提供的设置和取消hook的功能,但是实际上没有用到。

library_calls.h中有library_calls目录下要fuzz的所有库调用名,这里library_calls目录下只提供了一个GetSysColorBrush。


接下来就是随机进行系统调用了,根据bughunt_syscalls.h提供的参数类型信息随机生成对应的参数,这里也只提供了两个系统调用。

系统调用参数可以是bool,char,int,handle等几种情况,然后调用bughunt_syscall函数。

具体实现分别在bughunt_syscall.asm和bughunt_syscall_x64.asm中,分别处理32位和64位的情况。
32位下参数通过栈传递,提取系统调用号和系统调用参数压栈然后mov edx, 7FFE0300h和call dword ptr [edx]即可。

64位下前四个参数通过RCX,RDX,R8和R9传递,RCX,RDX,R8和R9原来是系统调用号,第一个参数,第二个参数,第三个参数,现在把系统调用号RCX放入RAX,参数依次向前挪,RCX赋值为RDX,RDX赋值为R8,R8赋值为R9,R9赋值为[rbp + 30h]。RCX,RDX,R8和R9现在是第一个参数,第二个参数,第三个参数,第四个参数,最后调用syscall指令。

功能拓展

从前面的分析我们可以看到代码的功能还是比较简陋的,也没有提供多少库调用和系统调用供fuzz。如果我们要增加更加的系统调用呢?系统调用表有现成的,但是参数好像没有现成的。我目前也只想到参考ReactOS的代码生成系统调用参数这个方法。
首先下载j00ru提供的系统调用表(https://github.com/j00ru/windows-syscalls)和ReactOS的代码(https://github.com/reactos/reactos/archive/0.4.11-release.zip),找到ReactOS中含有对应系统调用的头文件(脚本运行需要的时间有点长)。

import os
import csv
import shutil

global allfiles
allfiles = []

def findstring(pathfile, syscallname):
    file = open(pathfile, "r")
    string = file.read()
    if string.find(syscallname) != -1:
        global allfiles
        if pathfile not in allfiles:
            allfiles.append(pathfile)

def readFilename(file_dir, syscallname):
    for root, dirs, files in os.walk(file_dir):
        for file in files:
            if file.endswith(".h"):
                full_path=os.path.join(root, file)
                findstring(full_path, syscallname)

file_path = "F:\\windows-fuzzing\\ReactOS-0.4.11"
fscv = csv.reader(open("F:\\windows-fuzzing\\KernelFuzzer\\nt.csv", 'r'))
for row in fscv:
    syscallname = row[0]
    readFilename(file_path, syscallname)
fscv = csv.reader(open("F:\\windows-fuzzing\\KernelFuzzer\\win32k.csv", 'r'))
for row in fscv:
    syscallname = row[0]
    readFilename(file_path, syscallname)
print allfiles

然后用正则表达式转换成KernelFuzzer用的格式。

import re
import csv

def search_csv(str,fscv,f):
    for row in fscv:
        if str in row:
            if row[26]:
                syscallname = row[0]
                syscallnumber = row[15]
                #win10 1903:27
                #win10 1809:26
                #win10 1803:25
                #win10 1709:24
                #win7 SP1:15
                #XP SP2:2
                arguments = "{"
                while True:
                    str = f.readline()
                    flag = 0

                    matchobj = re.search(r'(\s)+DWORD|int\s', str)
                    if matchobj:
                        flag = 1
                        arguments += " _INT32,"
                    matchobj = re.search(r'(\s)+DWORD_PTR\s', str)
                    if matchobj:
                        flag = 1
                        arguments += " _INT32_PTR,"
                    matchobj = re.search(r'(\s)+UINT\s', str)
                    if matchobj:
                        flag = 1
                        arguments += " _UINT32,"
                    matchobj = re.search(r'(\s)+UINT_PTR\s', str)
                    if matchobj:
                        flag = 1
                        arguments += " _UINT32_PTR,"
                    matchobj = re.search(r'(\s)+LONG\s', str)
                    if matchobj:
                        flag = 1
                        arguments += " _INT64,"
                    matchobj = re.search(r'(\s)+LONG_PTR\s', str)
                    if matchobj:
                        flag = 1
                        arguments += " _INT64_PTR,"
                    matchobj = re.search(r'(\s)+ULONG\s', str)
                    if matchobj:
                        flag = 1
                        arguments += " _UINT64,"
                    matchobj = re.search(r'(\s)+ULONG_PTR\s', str)
                    if matchobj:
                        flag = 1
                        arguments += " _UINT64_PTR,"
                    matchobj = re.search(r'(\s)+H(.*)\s', str)
                    if matchobj:
                        flag = 1
                        arguments += " _HANDLE,"
                    matchobj = re.search(r'(\s)+BOOL\s', str)
                    if matchobj:
                        flag = 1
                        arguments += " _BOOL,"
                    matchobj = re.search(r'(\s)+LPVOID\s|(\s)+PVOID\s', str)
                    if matchobj or flag == 0:
                        flag = 1
                        arguments += " _VOID_PTR,"
                    matchobj = re.search(r'\);', str)
                    if matchobj:
                        arguments += " NIL }, NIL },"
                        break

                finalstr = "    { ((DWORD)" + syscallnumber + "), " + arguments + "     //" + syscallname
                print finalstr
                return True
    return False

def locate_function_to_search(f):
    while True:
        str = f.readline()
        if not str:
            break
        matchobj = re.search(r'Nt(.*)\(',str)
        if matchobj:
            index=str.find("(")
            str = str[:index]
            ntfile = open("F:\\windows-fuzzing\\KernelFuzzer\\nt.csv", 'r')
            fntscv = csv.reader(ntfile)
            if search_csv(str, fntscv, f) == True:
                continue
            win32kfile = open("F:\\windows-fuzzing\\KernelFuzzer\\win32k.csv", 'r')
            fwin32kscv = csv.reader(win32kfile)
            if search_csv(str, fwin32kscv, f) == True:
                continue

if __name__ == '__main__':
    files = (
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\lpcfuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\sefuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\xdk\\ntifs.template.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\exfuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\iofuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\psfuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\mmfuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\xdk\\mmfuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\kefuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ddk\\ntpoapi.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\pofuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\base\\setup\\lib\\utils\\filesup.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\drivers\\filesystems\\udfs\\Include\\nt_native.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\drivers\\filesystems\\udfs\\Include\\zw_2_nt.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\obfuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\psdk\\winternl.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\xdk\\nttmapi.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\cmfuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\xdk\\winnt_old.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\dbgkfuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\dbgktypes.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\iotypes.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\base\\setup\\lib\\utils\\regutil.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\rtlfuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\pstypes.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\mmtypes.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\xdk\\iotypes.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\drivers\\filesystems\\udfs\\Include\\env_spec_w32.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\xdk\\cmtypes.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\drivers\\filesystems\\udfs\\CDRW\\scsi_port.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\drivers\\storage\\ide\\uniata\\ntddscsi.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\drivers\\storage\\ide\\uniata\\atapi.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\dll\\shellext\\shellbtrfs\\shellext.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\cmtypes.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\reactos\\drivers\\ntddrdsk.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\xdk\\psfuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\potypes.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\psdk\\powrprof.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\kdfuncs.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\extypes.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\lpctypes.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\modules\\rostests\\win32\\DriverLoading\\Application\\DriverTester.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\drivers\\filesystems\\udfs\\Include\\CrossNt\\CrossNt.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\drivers\\storage\\ide\\uniata\\ntddk_ex.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\drivers\\storage\\ide\\uniata\\inc\\CrossNt.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\modules\\rostests\\kmtests\\include\\kmt_test.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\reactos\\wine\\winioctl.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\reactos\\subsys\\win\\conmsg.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\psdk\\ntdll.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\drivers\\filesystems\\udfs\\Include\\env_spec_nt.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\reactos\\debug.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\ndk\\ketypes.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\psdk\\ntgdi.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\win32ss\\gdi\\gdi32\\include\\gdi32p.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\win32ss\\user\\ntuser\\painting.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\modules\\rostests\\dxtest\\win32kdxtest\\test.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\win32ss\\include\\ntgdibad.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\win32ss\\gdi\\ntgdi\\intgdi.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\win32ss\\include\\ntgdityp.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\win32ss\\gdi\\gdi32\\wine\\gdi_private.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\win32ss\\user\\ntuser\\winpos.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\win32ss\\include\\ntuser.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\win32ss\\user\\user32\\include\\ntwrapper.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\win32ss\\user\\user32\\include\\user_x.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\sdk\\include\\reactos\\undocuser.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\subsystems\\win32\\csrsrv\\status.h',
'F:\\windows-fuzzing\\ReactOS-0.4.11\\win32ss\\user\\ntuser\\cursoricon.h')

    for file in files:
        f = open(file)
        locate_function_to_search(f)

将转换后的结果粘贴到bughunt_syscalls.h中就可以对更多的系统调用进行fuzz了。当然毕竟没有反馈驱动,很难就这样跑出漏洞。
另外我在github上还找到一个对windows系统调用进行的fuzz的工具NtCall64(https://github.com/hfiref0x/NtCall64)。在NtCall64Main中首先解析命令行参数,之后进入FuzzInitPhase1函数进行一些准备工作,然后调用FuzzInitPhase2函数。根据是否指定-win32k参数fuzz W32pServiceTable(win32k.sys)或者fuzz KiServiceTable(ntoskrnl.exe)。

解析系统调用表要用到三个重要的符号:表的基地址、表的大小和参数在栈上占用的字节数。对于win32k.sys这些符号的名称分别是W32pServiceTable、W32pServiceLimit和W32pArgumentTable;对于ntoskrnl.exe这些符号的名称分别是KiServiceTable、KiServiceLimit和KiArgumentTable。
fuzz W32pServiceTable因为已经导出,所以可以直接调用GetProcAddress取得它们的地址。


fuzz KiServiceTable因为没有导出,所以是通过特征码定位实现的。


FuzzInitPhase2函数继续依次调用FuzzRunThreadWithWait->FuzzThreadProc->DoSystemCall-> ntSyscallGate函数,最后在syscall.asm中完成系统调用。

在DoSystemCall函数中,可以看到系统调用的参数是完全从fuzzdata数组中随机选择的。

总结

这篇文章分析了KernelFuzzer的原理,同时对其进行了简单的扩展,使其对更多的系统调用进行fuzz。同时还以NtCall64为例展示了不同的对windows系统调用进行fuzz的方法。有兴趣可以自行继续对它们进行完善。

参考资料

1.Platform Agnostic Kernel Fuzzing
2.MindshaRE: Walking the Windows Kernel with IDA Python

点击收藏 | 0 关注 | 1
登录 后跟帖