这是内核漏洞挖掘技术系列的第二篇。第一篇:内核漏洞挖掘技术系列(1)——trinity
2013年Project Zero的j00ru开源了用bochs的插桩API实现的挖掘内核double fetch漏洞的工具bochspwn(171K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Y4L8$3!0Y4L8r3g2H3M7X3!0B7k6h3y4@1P5X3g2J5L8#2)9J5c8X3u0G2j5$3S2K6M7s2N6F1),2018年j00ru更新了bochspwn,还开源了同样用bochs的插桩API实现的挖掘未初始化导致的内核信息泄露漏洞的工具bochspwn-reloaded(69bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Y4L8$3!0Y4L8r3g2H3M7X3!0B7k6h3y4@1P5X3g2J5L8#2)9J5c8X3u0G2j5$3S2K6M7s2N6F1i4K6u0V1M7X3g2D9L8$3q4V1k6h3b7`.)。这两个工具发现了windows/linux等操作系统中的多个漏洞。因为bochspwn搭建环境有一些坑,所以这篇文章先讲解double fetch漏洞原理,再搭建bochspwn环境,最后讲解bochspwn原理。
double fetch原理说起来很简单,如图所示,用户通常会通过调用内核函数完成特定功能,当内核函数两次从同一用户内存地址读取同一数据时,通常第一次读取用来验证数据或建立联系,第二次则用来使用该数据。与此同时,用户空间并发运行的恶意线程可以在两次内核读取操作之间利用竞争条件对该数据进行篡改,从而造成内核使用数据的不一致。double fetch漏洞可造成包括缓冲区溢出、信息泄露、空指针引用等后果,最终造成内核崩溃或者恶意提权。在bochspwn论文中,对double fetch是这样定义的:
在windows系统上比较典型的double fetch长下面这样。
如果要使用bochs提供的插桩API,需要把自己写的代码放在源代码中instrument目录下一个单独的目录(比如bochspwn),在编译时启用--enable-instrumentation选项并指定该目录。
所以需要先在linux上搭建交叉编译环境编译出bochs.exe再在windows上使用。sudo apt-get install g++-mingw-w64并按提示安装其它需要的包。 bochspwn使用protobuf记录日志,接下来到3b8K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6H3M7X3!0@1L8$3y4G2L8r3u0#2k6X3k6W2M7Y4y4Q4x3V1k6H3M7X3!0@1L8$3u0#2k6R3`.`.下载2.5.0版的protobuf,执行下面的命令编译protobuf并在mingw目录中安装头文件和库。
make时会出现下面这样的错误。 注释掉src目录下Makefile中下图所示第3114行即可。 之后sudo apt-get install g++并按提示安装其它需要的包,执行下面的命令编译linux上用的。
之后运行protoc –version检查安装是否成功,此时会出现下面这样的错误。这是因为protobuf的默认安装路径是/usr/local/lib,而/usr/local/lib不在ubuntu体系默认的LD_LIBRARY_PATH里。创建文件/etc/ld.so.conf.d/libprotobuf.conf并写入/usr/local/lib,sudo ldconfig再运行protoc --version就可以正常看到版本号了。 到027K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6L8%4g2J5j5$3g2X3L8%4u0Y4k6g2)9J5k6h3&6W2N6q4)9J5c8Y4m8J5L8$3A6W2j5%4c8K6i4K6u0r3j5X3!0U0K9s2y4Q4x3V1k6X3K9h3I4W2M7#2)9J5c8X3u0G2j5$3S2K6i4K6u0r3下载bochs2.6.9的源代码,183K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Y4L8$3!0Y4L8r3g2H3M7X3!0B7k6h3y4@1P5X3g2J5L8#2)9J5c8X3u0G2j5$3S2K6M7s2N6F1下载bochspwn的源代码。在bochs-2.6.9/instrument/目录下创建bochspwn目录,将bochspwn/instrumentation和bochspwn/third_party/instrumentation中的文件拷贝到我们创建的bochs-2.6.9/instrument/bochspwn中。在windows操作系统中找到32位的dbghelp.lib或者dbghelp.dll(原来的文档中这里说法有点问题,我在github上提了个issue之后改过来了),也拷贝到我们创建的bochs-2.6.9/instrument/bochspwn中。如果安装了windbg可以在C:\Program Files (x86)\Debugging Tools for Windows (x86)\中找到这些文件(我认为这些文件的版本应该对应于待fuzz的windows操作系统的版本,原来的文档中并没有说明这一点)。之后在bochs-2.6.9目录下执行下面的命令。
切换到bochs-2.6.9/instrument/bochspwn,protoc --cpp_out=. logging.proto生成logging.pb.cc和logging.pb.h,之后make编译。第一次报错找不到DbgHelp.h,将dbghelp.h重命名为DbgHelp.h。 第二次报错有关__in和__out,这是因为微软风格的代码使用__in和__out来注释函数参数,执行下面的命令把/usr/lib/gcc/i686-w64-mingw32/7.3-win32/include目录下所有文件中的__in和__out替换成___in和___out。
编译成功之后应该会有一个libinstrument.a文件。 然后切换回bochs-2.6.9,之后make编译。第一次报错narrow conversion,CXXFLAGS加上-Wno-narrowing忽略此错误。 第二次报错没有找到windres,把Makefile中所有的windres替换成i686-w64-mingw32-windres。 第三次报错是因为链接器没有指定-lws2_32,在Makefile相关命令后加上-lws2_32。 之后就应该不会再有什么错误了。如果我们想编译64位版本的bochs从第一步编译protobuf开始把编译器换成x86_64-w64-mingw32,把dbghelp.lib/dll换成64位的版本,其余步骤应该一样。接下来以fuzz linux内核为例。在windows操作系统上安装好bochs-2.6.9,用VirtualBox安装好ubuntu sever 18.04.2。这里用server也是因为没有图形化界面,bochs运行起来相对流畅一些。升级到最新的内核并安装好符号包(98fK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3c8V1k6h3u0K6i4K6u0W2N6h3u0#2L8Y4c8#2i4K6u0W2j5$3!0E0i4K6u0r3M7r3!0G2L8q4)9J5c8X3#2S2K9h3&6Q4x3V1k6D9i4K6u0r3L8r3W2F1N6i4S2Q4x3V1j5`.)。然后下载并安装好这个系列第一篇介绍的trinity(c20K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6C8k6i4u0F1k6h3I4K6L8r3q4U0K9$3g2J5i4K6u0r3N6s2u0A6L8X3W2@1P5b7`.`.)或者其它能帮助提高代码覆盖率的工具。bochspwn在生成日志文件的时候需要一些特定的结构体信息,使用gdb加载内核符号文件之后打印出这些信息。在原来的文档中提供了打印出这些信息需要的gdb脚本。
不过内核的数据结构已经有了一些变化,我不太确定我下面的脚本是否正确(我不是很清楚struct module中core_layout和init_layout有什么区别)。修改bochspwn中的config.txt,增加一项ubuntu_server_64_4.15.0-47-generic。 通过查看源码可以发现task_comm_len和module_name_len都没有变。 使用virtualbox自带的工具将硬盘格式由VDI格式转换成RAW格式。 根据原来的文档bochspwn应该包含一个bochsrc.txt,但是我却并没有找到。从bochs-2.6.9的安装目录C:\Program Files (x86)\Bochs-2.6.9中找到bochsrc-sample.txt复制过来,重命名为bochsrc.txt。在这个文件中指定我们转换的raw文件的路径。 我们还可以根据情况调整使用的内存。 现在终于可以用bochs运行我们的虚拟机了,把我们在linux上交叉编译出来的bochs.exe拷贝到bochspwn目录下,如下图所示设置并运行。 出现了一个小错误,注释掉bochsrc.txt这一行即可。 成功启动之后运行trinity,我们应该能够得到memlog.bin和modules.bin。之后可以使用类似下面的命令编译tools目录下的工具进行进一步分析。
在bochspwn下有三个含有代码的目录:instrumentation,third_party和tools。因为在泉哥的一篇博客中有较为详细的分析[1],这里只简单分析tools目录下的代码。
1.Bochspwn漏洞挖掘技术深究(1):Double Fetches 检测2.Bochspwn: Identifying 0-days via system-wide memory access pattern analysis3.Windows Kernel Local Denial-of-Service #1: win32k!NtUserThunkedMenuItemInfo (Windows 7-10)
./configure [...] --enable-instrumentation="instrument/bochspwn"
./configure --host=i686-w64-mingw32 --prefix=/usr/i686-w64-mingw32/ make sudo make install
./configure make sudo make install
export MINGW=i686-w64-mingw32 CXXFLAGS="-O2 -I/usr/${MINGW}/include/ -D_WIN32 -L/usr/${MINGW}/lib -static-libgcc -static-libstdc++" CFLAGS="-O2 -I/usr/${MINGW}/include/ -D_WIN32 -L/usr/${MINGW}/lib" LIBS="/usr/${MINGW}/lib/libprotobuf.a instrument/bochspwn/dbghelp.lib" ./configure \ --host=i686-w64-mingw32 \ --enable-instrumentation="instrument/bochspwn" \ --enable-x86-64 \ --enable-e1000 \ --with-win32 \ --without-x \ --without-x11 \ --enable-cpu-level=6 \ --enable-pci \ --enable-pnic \ --enable-fast-function-calls \ --enable-fpu \ --enable-cdrom \ --disable-all-optimizations
sudo sed -ri 's/\b(__in|__out)\b/_&/g' $(egrep -rl '\b(__in|__out)\b' include)
[培训]科锐逆向工程师培训第53期2025年7月8日开班!
syser double fetch 的利用实现率很低吧?