😃
项目1 主要是复现 GEGL 开源库的漏洞挖掘过程
项目版本: a566b738331757cf25118af5bdc65218ae5eb3b2
bash$ git clone https://gitlab.gnome.org/GNOME/gegl.git
$ git checkout a566b
$ sudo pip3 install meson
$ CC=afl-gcc CXX=afl-g++ meson _build
$ ASAN_OPTIONS=detect_leaks=0 AFL_USE_ASAN=1 ninja -C _build
$ ninja -C _build install
虽然GEGL 提供了一个二进制文件gegl
但是如果直接对二进制文件进行 Fuzz 可能会出现效率低下的问题 所以这里采用编写harness 的方式来进行Fuzz
cgint main(gint argc, gchar **argv) {
GeglNode *gegl = NULL;
gchar *script = NULL;
GError *err = NULL;
gchar *path_root = NULL;
// [1]
gegl_init(NULL, NULL);
path_root = g_get_current_dir ();
// [2]
g_file_get_contents (argv[1], &script, NULL, &err);
if (err != NULL) {
return 1;
}
// [3]
gegl = gegl_node_new_from_xml (script, path_root);
if (!gegl) {
return 1;
}
// [4]
GeglNode *output = gegl_node_new_child (gegl,
"operation", "gegl:save",
"path", "out.png",
NULL);
gegl_node_connect_from (output, "input", gegl, "output");
gegl_node_process (output);
// [5]
g_object_unref (output);
g_object_unref (gegl);
g_free (script);
g_clear_error (&err);
g_free (path_root);
gegl_exit ();
return 0;
}
使用afl-clang-fast
进行编译即可
bash$ afl-clang-fast -o test test.c `pkg-config --cflags --libs gegl-0.4 glib-2.0`
语料库使用的是项目内的tests/compositions
内的文件
bash$ afl-fuzz -i ./compositions -o ./out -m none -t 20000 -- ./test @@
一会就能跑出 crash (虽然 harness 也快不到哪里去就对了..)
这个时候可以编译出来一版包含 ASAN 的 harness 看看到底是哪里 crash 了
bash$ AFL_USE_ASAN=1 afl-clang-fast -o test1 test.c `pkg-config --cflags --libs gegl-0.4 glib-2.0`
可以看到与预估漏洞一致 是 crash 在gegl_path_parse_string
中
可以根据 ASAN 爆出的信息直接定位漏洞所在地 根据GDB 动态调试复现 漏洞实际触发于../gegl/property-type/gegl-path.c:817
cvoid
gegl_path_parse_string (GeglPath *vector,
const gchar *path)
{
GeglPathPrivate *priv = GEGL_PATH_GET_PRIVATE (vector);
const gchar *p = path;
InstructionInfo *previnfo = NULL;
gdouble x0, y0, x1, y1, x2, y2;
while (*p)
{
gchar type = *p;
InstructionInfo *info = lookup_instruction_info(type);
if (!info && ((type>= '0' && type <= '9') || type == '-')) // [1]
{
if (previnfo->type == 'M') // here
{
info = lookup_instruction_info(type = 'L');
}
else if (previnfo->type == 'm')
{
info = lookup_instruction_info(type = 'l');
}
else if (previnfo->type == ' ')
g_warning ("EEEK");
}
...
if (*p)
p++;
}
gegl_path_dirty (vector);
}
漏洞成因也很简单gegl_path_parse_string
主要的作用是针对 xml 中传入的路径 对传入的路径进行上色 例如<gegl:fill-path path='M100,100 L200,100 L200,200 Z' color=ff0000>
就是针对路径 "M100,100 L200,100 L200,200 Z" 定义的区域上红色
如果我们传入<gegl:fill-path path="0">
可以发现*p = path = type
那么此时type = 0
可以顺利通过[1]
处的 if 语句 然后到达 crash 处
至于为什么 crash 可以发现在函数刚开始InstructionInfo *previnfo = NULL;
且后面没有对previnfo
进行赋值导致访问0x0
地址 导致SEGV
经过继续的Fuzz
发现一个资源耗尽漏洞 可以申请0xfffffffffffd9d7c
大小的堆块 很显然这是一个地址 肯定是在申请堆块之前存在问题
可以看到是g_malloc
之前的问题 直接上GDB
然后查栈帧
很明显问题的调用存在于datauri_parse_header -> g_strndup -> g_malloc
重点就是这个datauri_parse_header
cstatic gchar **
datauri_parse_header(const gchar *uri,
gchar **raw_data_out,
gint *header_items_no_out)
{
// Determine data format
const gchar * header_end = g_strstr_len (uri, -1, ",");
const gint data_prefix_len = strlen("data:");
const gint header_len = header_end - uri - data_prefix_len;
gchar *header = g_strndup(uri+data_prefix_len, header_len); // here
gchar **header_items = g_strsplit(header, ";", 3);
gint header_items_no = -1;
while (header_items[++header_items_no]) {
}
g_free(header);
if (header_items_no_out)
{
*header_items_no_out = header_items_no;
}
if (raw_data_out)
{
*raw_data_out = (gchar *)uri+data_prefix_len+header_len;
}
return header_items;
}
经过动态调试很明显问题是出在header_end
这个值上
其中g_strstr_len(const gchar* haystack, gssize haystack_len, const gchar* needle)
的作用是从haystack
字符串中搜索needle
其中最大搜索长度为haystack_len
很显然传入的参数uri
并没有问题 问题主要出现在g_strstr_len
的调用上
因为匹配长度是-1
也就是0xffffffffffffffff
可以简单理解成无穷大 因为 uri 是一个存在栈上的参数 如果 uri 中没有匹配到,
那近乎就是实现了全栈的搜索 很显然这是不合理的 返回回来的地址自然也是预料之外的 那么当后面调用str_dup
时 因为长度过大 会申请一个堆块用于存放输出的字符串 很显然这时候申请的堆块长度是不合理的 自然也就造成了资源耗尽
本文作者:Du4t
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!