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

CTF-PWN: 虚表(vtable)

vtable

vtable(虚表,virtual table)是面向对象编程中的一个关键概念,主要用于实现多态性(polymorphism)。它是一种数据结构,通常是一个指针数组,包含了类的虚函数(virtual functions)的地址。每个类都有自己的 vtable,并且每个对象实例都有一个指向该 vtable 的指针,称为 vptr(虚表指针)。

主要功能和工作原理

  1. 虚函数调用机制

    • 当一个类定义了虚函数时,编译器会为这个类生成一个 vtable。虚函数表中记录了当前类及其基类的虚函数地址。
    • 每个对象实例在内存中都有一个 vptr,指向这个对象所属类的 vtable
    • 当通过基类指针或引用调用虚函数时,程序会通过 vptr 查找 vtable 中对应函数的地址,从而实现动态绑定(dynamic binding)和多态性。
  2. 继承与覆盖

    • 子类可以覆盖基类的虚函数。编译器会在子类的 vtable 中用子类的函数地址替换基类的函数地址。
    • 这样,当通过基类指针或引用调用虚函数时,程序会使用子类的实现,而不是基类的实现。

示例

以下是一个简单的例子,说明 vtable 的工作原理:

#include <iostream>

class Base {
public:
    virtual void foo() {
        std::cout << "Base::foo()" << std::endl;
    }
    virtual void bar() {
        std::cout << "Base::bar()" << std::endl;
    }
};

class Derived : public Base {
public:
    void foo() override {
        std::cout << "Derived::foo()" << std::endl;
    }
    void bar() override {
        std::cout << "Derived::bar()" << std::endl;
    }
};

int main() {
    Base* b = new Derived();
    b->foo(); // 输出 "Derived::foo()"
    b->bar(); // 输出 "Derived::bar()"
    delete b;
    return 0;
}

在这个例子中:

  • Base 类有两个虚函数 foobar
  • Derived 类继承自 Base 并覆盖了这两个虚函数。
  • main 函数中,通过基类指针 b 调用了虚函数 foobar,由于动态绑定的机制,实际调用的是 Derived 类中的实现。

vtable 和 vptr 的示意图

假设 Base 类和 Derived 类的 vtable 如下:

Base::vtable
+------------+
| &Base::foo |
| &Base::bar |
+------------+

Derived::vtable
+---------------+
| &Derived::foo |
| &Derived::bar |
+---------------+

当创建一个 Derived 类对象时,内存布局可能如下:

Derived object
+--------+
| vptr   | ----> Derived::vtable
+--------+

小结

vtable 是实现 C++ 等面向对象编程语言中多态性的重要机制。它通过维护一个虚函数指针数组和对象实例中的虚表指针,实现了动态绑定和函数调用的多态性。在编译器的支持下,vtable 机制在运行时动态选择合适的函数实现,从而使得面向对象编程中的继承和多态特性能够顺利工作。

__IO_FILEvtable

__IO_FILE 结构体的虚表(vtable)指向了各种文件操作函数,例如 openreadwriteclose 等。这些函数指针赋予了不同类型的文件流(如普通文件、内存流、网络流等)特定的行为,从而实现了多态性。

具体来说,__IO_FILE 结构体中的虚表指向了一个包含这些函数指针的结构体(通常称为 jump tablevtable),这些函数指针对应于文件操作函数。这些函数指针在运行时会被调用,以执行具体的文件操作。

示例代码解释

以下是一个简化的示例,展示了 __IO_FILE 结构体及其虚表的概念:

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

// 定义虚表结构体,包含文件操作函数指针
struct _IO_jump_t {
    ssize_t (*read)(void *cookie, char *buf, size_t nbytes);
    ssize_t (*write)(void *cookie, const char *buf, size_t nbytes);
    int (*close)(void *cookie);
};

// 定义文件结构体,包含一个指向虚表的指针
typedef struct {
    struct _IO_jump_t *vtable;
    void *cookie; // 自定义数据,可以用来存储文件句柄或其他状态信息
} _IO_FILE;

// 虚表的具体实现
ssize_t my_read(void *cookie, char *buf, size_t nbytes) {
    // 自定义读函数的实现
    // 这里假设cookie是一个文件指针
    FILE *fp = (FILE *)cookie;
    return fread(buf, 1, nbytes, fp);
}

ssize_t my_write(void *cookie, const char *buf, size_t nbytes) {
    // 自定义写函数的实现
    // 这里假设cookie是一个文件指针
    FILE *fp = (FILE *)cookie;
    return fwrite(buf, 1, nbytes, fp);
}

int my_close(void *cookie) {
    // 自定义关闭函数的实现
    // 这里假设cookie是一个文件指针
    FILE *fp = (FILE *)cookie;
    return fclose(fp);
}

// 定义虚表实例并赋值
struct _IO_jump_t my_vtable = {
    .read = my_read,
    .write = my_write,
    .close = my_close,
};

// 打开文件并初始化自定义文件结构体
_IO_FILE *my_fopen(const char *filename, const char *mode) {
    FILE *fp = fopen(filename, mode);
    if (!fp) return NULL;

    _IO_FILE *file = (_IO_FILE *)malloc(sizeof(_IO_FILE));
    file->vtable = &my_vtable;
    file->cookie = fp;
    return file;
}

// 关闭文件并释放自定义文件结构体
int my_fclose(_IO_FILE *file) {
    int result = file->vtable->close(file->cookie);
    free(file);
    return result;
}

// 读取文件
ssize_t my_fread(_IO_FILE *file, char *buf, size_t nbytes) {
    return file->vtable->read(file->cookie, buf, nbytes);
}

// 写入文件
ssize_t my_fwrite(_IO_FILE *file, const char *buf, size_t nbytes) {
    return file->vtable->write(file->cookie, buf, nbytes);
}

int main() {
    _IO_FILE *file = my_fopen("example.txt", "w+");
    if (!file) {
        perror("Failed to open file");
        return 1;
    }

    const char *text = "Hello, world!";
    my_fwrite(file, text, strlen(text));

    char buf[512];
    fseek((FILE *)file->cookie, 0, SEEK_SET); // 重置文件指针位置
    my_fread(file, buf, sizeof(buf));
    printf("Read from file: %s\n", buf);

    my_fclose(file);
    return 0;
}

解释

  1. 虚表结构体 _IO_jump_t

    • 这个结构体包含了各种文件操作函数的指针,例如 readwriteclose
  2. 文件结构体 _IO_FILE

    • 这个结构体包含一个指向虚表的指针 vtable 和一个自定义数据指针 cookie,用于存储具体的文件信息(例如文件指针)。
  3. 自定义文件操作函数

    • 这些函数实现了特定的文件操作,例如 my_readmy_writemy_close,并通过虚表中的函数指针调用。
  4. 文件操作函数的调用

    • my_fopen 函数打开文件并初始化 _IO_FILE 结构体。
    • my_freadmy_fwrite 函数通过虚表指针调用具体的读写函数。
    • my_fclose 函数通过虚表指针调用关闭函数并释放结构体内存。

通过这种方式,你可以看到 __IO_FILE 结构体的虚表指向了各种文件操作函数,从而实现了不同类型文件流的多态行为。

寻找不同libc版本的定义

要查找特定版本的 GNU libc(glibc)中 _IO_jump_t 的定义和其他相关实现细节,你可以采取以下几种方法:

1. 查看源码仓库

glibc 的源码是公开的,你可以在其源码仓库中查找特定版本的实现:

  • GNU libc 官方网站:你可以从 GNU libc 网站上获取源码。
  • GNU libc Git Repository:glibc 的代码仓库托管在 sourceware.org。你可以使用以下命令克隆仓库:
    git clone git://sourceware.org/git/glibc.git
    
    然后你可以切换到特定的版本标签或提交记录来查看源代码。例如:
    cd glibc
    git checkout tags/glibc-2.31
    

2. 在线代码浏览器

你也可以使用在线代码浏览器查看特定版本的 glibc 源代码。这些浏览器通常提供了方便的搜索和导航功能。例如:

  • Sourceware Git Web:这是 sourceware.org 提供的在线代码浏览器,直接访问 glibc Git web.

3. 下载和解压发行版源码

特定版本的 glibc 源代码可以通过下载对应的源码压缩包来获取:

  • 从 GNU FTP 站点 下载特定版本的源码压缩包。
  • 解压下载的源码包:
    tar -xvf glibc-2.31.tar.gz
    cd glibc-2.31
    

4. 查看系统安装的源码包

如果你使用的是基于 Debian 或 Fedora 的 Linux 发行版,通常可以安装特定版本的 glibc 源代码包:

  • Debian/Ubuntu
    sudo apt-get source libc6
    
  • Fedora
    sudo dnf download --source glibc
    

查找 _IO_jump_t 的定义

在源码树中,你可以使用 grep 或其他文本搜索工具查找 _IO_jump_t 的定义。通常,相关定义会出现在 libio 目录下的头文件中,例如 libio.h

grep -r "_IO_jump_t" .

以上命令会在当前目录及其子目录中递归搜索包含 _IO_jump_t 的文件。

示例

让我们以 glibc 2.31 版本为例:

  1. 克隆并检出特定版本:

    git clone git://sourceware.org/git/glibc.git
    cd glibc
    git checkout tags/glibc-2.31
    
  2. 查找 _IO_jump_t 的定义:

    grep -r "_IO_jump_t" .
    

这样,你应该能够找到 _IO_jump_t 以及其他相关数据结构和函数的定义。通常,这些定义会出现在 libio/libio.h 或类似的头文件中。

通过这些步骤,你可以查找到特定版本的 glibc 中 _IO_jump_t 的定义以及其他相关实现细节。


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

相关文章:

  • Flink概念知识讲解之:Restart重启策略配置
  • 刚体变换矩阵的逆
  • ffmpeg7.0 合并2个 aac 文件
  • 学习threejs,导入assimp assimp2json格式的模型
  • 1/7距离放假一周加1
  • 旷视科技C++面试题及参考答案
  • Vue学习记录之二十二 Vue3+vite+electron 构建项目实例
  • 别被忽悠了 Lua 数组真的也可以从 0 开始索引?
  • 10 最长回文子串、买卖股票的最好时机(一)、[NOIP2002 普及组] 过河卒24_10_30
  • CodeQL学习笔记(3)-QL语法(模块、变量、表达式、公式和注解)
  • @tarojs/components 和 taro-ui 中的组件之间的区别
  • HarmonyOS NEXT API12最新版 端云一体化开发-创建端云一体化项目流程
  • docker部署SQL审核平台Archery
  • C++类和对象 (中)
  • ubuntu内核更新导致显卡驱动掉的解决办法
  • 软考中级计算题笔记
  • C++朝花夕拾
  • Golang Agent 可观测性的全面升级与新特性介绍
  • 记MySQL下一次DEPENDENT SUBQUERY的优化
  • Github 2024-10-29Python开源项目日报 Top10
  • 算法刷题-小猫爬山
  • 从0开始搭建一个生产级SpringBoot2.0.X项目(二)SpringBoot应用连接数据库集成mybatis-plus
  • ElasticSearch - Bucket Script 使用指南
  • 三菱FX5U PLC使用SD存储卡固件更新的方法
  • python实现excel数据导入数据库
  • 频率限制:WAF保护网站免受恶意攻击的关键功能