练习四 - LibTIFF 版本:libtiff 4.0.4
漏洞编号:CVE-2016-9297
CVE-2016-9297 是一个越界读取漏洞,可以通过精心设计的 TIFF_SETGET_C16ASCII 或 TIFF_SETGET_C32_ASCII 标签值触发。
越界读取是指程序读取数据超过预期缓冲区的结束点或开始时发生的漏洞。
因此,它允许远程攻击者实施拒绝服务,或可能从进程内存中获取潜在敏感信息
下载LibTIFF 1 2 3 4 mkdir $HOME /fuzz_main/cd $HOME /fuzz_main/ wget https://github.com/libsdl-org/libtiff/archive/refs/tags/v4.0.4.tar.gz tar -xzvf v4.0.4.tar.gz
测试覆盖范围 一般命令行类工具都提供了额外参数来实现不同额外功能,对于我们 fuzz 来说,能通过参数组合启用程序越多的功能越好,意味着这会覆盖更多的代码。
我们将采用工具 LCOV 来测试覆盖率
首先我们得安装 LCOV:
使用--coverage标志编译 libTIFF:
1 2 3 4 5 cd $HOME /fuzz_main/cd $HOME /fuzz_main/libtiff-4.0.4/ CFLAGS="--coverage" LDFLAGS="--coverage" ./configure --prefix="$HOME /fuzz_main/cov_install/" --disable-shared make make install
通过以下输入收集代码的覆盖率:
1 2 3 4 5 6 7 mkdir $HOME /fuzz_main/fuzz/cd $HOME /fuzz_main/fuzz/ lcov --zerocounters --directory $HOME /fuzz_main/libtiff-4.0.4 lcov --capture --initial --directory $HOME /fuzz_main/libtiff-4.0.4 --output-file app.info --ignore-errors source $HOME /fuzz_main/install/bin/tiffinfo -D -j -c -s -r -w $HOME /fuzz_main/libtiff-4.0.4/test/images/logluv-3c-16b.tiff lcov --no-checksum --directory $HOME /fuzz_main/libtiff-4.0.4 --capture --output-file app2.info genhtml --highlight --legend -output-directory ./app2/ ./app2.info
如果操作正确,你可以点开 app2 下的 index.htm :
我们可以尝试另外一条命令:
1 2 3 4 5 lcov --zerocounters --directory $HOME /fuzz_main/libtiff-4.0.4 lcov --capture --initial --directory $HOME /fuzz_main/libtiff-4.0.4 --output-file app.info --ignore-errors source $HOME /fuzz_main/install/bin/tiffinfo -D -d -j -c -s -w $HOME /fuzz_main/libtiff-4.0.4/test/images/logluv-3c-16b.tiff lcov --no-checksum --directory $HOME /fuzz_main/libtiff-4.0.4 --capture --output-file app1.info genhtml --highlight --legend -output-directory ./app1/ ./app1.info
此时点开 app1 下的 index.html :
明显第二次覆盖率达到了 Functions:13.0% ; Lines: 8.0%
所以我们 fuzz 时就应该使用第二次测试用的参数。
准备fuzz 1 2 3 4 5 6 cd $HOME /fuzz_main/libtiff-4.0.4/ make cleanexport LLVM_CONFIG="llvm-config-11" CC=afl-clang-lto ./configure --prefix="$HOME /fuzz_main/install/" --disable-shared AFL_USE_ASAN=1 make -j4 AFL_USE_ASAN=1 make install
使用以下命令 fuzz:
1 afl-fuzz -m none -i $HOME /fuzz_main/libtiff-4.0.4/test/images/ -o ./out -s 123 -- $HOME /fuzz_main/install/bin/tiffinfo -D -d -j -c -s -w @@
这次 fuzz 应该非常快,不出10分钟应该会有十几个独特崩溃。
源码分析 根据错误输出我们可以看到这又是一个堆溢出漏洞。
我们根据代码调用追踪到 tif_print.c:126 :
1 2 3 4 5 6 7 8 9 10 11 12 else if (fip->field_type == TIFF_DOUBLE) fprintf (fd, "%f" , ((double *) raw_data)[j]);else if (fip->field_type == TIFF_ASCII) { fprintf (fd, "%s" , (char *) raw_data); break ; }else { fprintf (fd, "<unsupported data type in TIFFPrint>" ); break ; }
这里raw_data指向的是 0x5555555d8ee0 ,该位置的值:
1 2 3 4 5 6 7 8 9 10 11 -exec x/20gx 0x5555555d8ee0 0x5555555d8ee0: 0x00005555555d424d 0x00005555555d42b0 0x5555555d8ef0: 0x00005555555d42e0 0x0000000000000021 0x5555555d8f00: 0x0000000000000008 0x00005555555d4370 0x5555555d8f10: 0x00005555555d43a0 0x0000000000000021 0x5555555d8f20: 0x0000000000000008 0x00005555555d4430 0x5555555d8f30: 0x00005555555d4460 0x0000000000000411 0x5555555d8f40: 0x3536206761542020 0x756769203a303330 0x5555555d8f50: 0x203a6e6f69746172 0x6920656c676e6973 0x5555555d8f60: 0x616c70206567616d 0x00005555550a656e 0x5555555d8f70: 0x00005555555d45e0 0x00005555555d4610
我们发现一旦输出这里的值0x00005555555d424d就会包含一个 PIE 地址,有泄漏 PIE 基地址的风险。
那么为什么要输出这里的值呢?
经过调试断点发现当断点在setByteArray(第二段崩溃链发现)时继续运行就会去到上面那个输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 static void setByteArray (void ** vpp, void * vp, size_t nmemb, size_t elem_size) { if (*vpp) _TIFFfree(*vpp), *vpp = 0 ; if (vp) { tmsize_t bytes = (tmsize_t )(nmemb * elem_size); if (elem_size && bytes / elem_size == nmemb) *vpp = (void *) _TIFFmalloc(bytes); if (*vpp) _TIFFmemcpy(*vpp, vp, bytes); } }
经过调用链我们可以发现_TIFFmalloc(bytes)最终会调用malloc申请bytes大小的空间。
在我调试这里的时候bytes = 1,这意味着经过程序只需要一个字节空间,但是 Linux 系统会分配 0x20 大小的空间。
并且后面可以直接看到如果申请成功,vp会复制到申请到的内存中,而这个vp = 0x4d ,这也就是上面泄漏的0x00005555555d424d的最后一个字节。
修复 我觉得可以在复制到目标之前清空目标地址的空间内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static void setByteArray (void ** vpp, void * vp, size_t nmemb, size_t elem_size) { if (*vpp) _TIFFfree(*vpp), *vpp = 0 ; if (vp) { tmsize_t bytes = (tmsize_t )(nmemb * elem_size); if (elem_size && bytes / elem_size == nmemb) *vpp = (void *) _TIFFmalloc(bytes+1 ); if (*vpp){ _TIFFmemcpy(*vpp, vp, bytes); *((char *)(*vpp) + bytes) = '\x00' ; } } }
实际上官方补丁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 static void setByteArray (void ** vpp, void * vp, size_t nmemb, size_t elem_size) { if (*vpp) _TIFFfree(*vpp), *vpp = 0 ; if (vp) { if (nmemb == 0 || elem_size == 0 ) return ; if (nmemb > SIZE_MAX / elem_size) return ; tmsize_t bytes = (tmsize_t )(nmemb * elem_size); if (bytes / elem_size != nmemb) return ; *vpp = (void *) _TIFFmalloc(bytes+1 ); if (*vpp) { _TIFFmemcpy(*vpp, vp, bytes); ((char *)(*vpp))[bytes] = '\0' ; } } }
好吧我确实没有想到nmemb * elem_size可能会溢出。
不过我的补丁至少暂时有效 QvQ。