首页 > 其他分享 >CTF-PWN: 虚表(vtable)

CTF-PWN: 虚表(vtable)

时间:2024-10-30 22:50:45浏览次数:3  
标签:file vtable IO cookie FILE PWN 虚表 my

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 的定义以及其他相关实现细节。

标签:file,vtable,IO,cookie,FILE,PWN,虚表,my
From: https://blog.csdn.net/weixin_59166557/article/details/143377069

相关文章

  • 2024年网鼎杯青龙组 pwn
    pwn2开局泄露栈地址,又是栈溢出,直接栈转移拿下frompwnimport*fromLibcSearcherimportLibcSearcher#fromCrypto.Util.numberimportbytes_to_long,bytes_to_long#--------------------settingcontext---------------------context.clear(arch='amd64',os='linux&#......
  • pwn入门体验
    pwn真的最初让人敬而远之,但现在发现一些刚入门的题目稍微学一下还是能做的,起码了解pwn的答题模式。。。本篇讲一讲我入门遇到的一些困难和我的解决方案,以供借鉴。首先是配环境,配环境一定一定要跟着视频教程走,而且是pwn方向的最新的教程。我最初自己装ubuntu,调这个调那个装了两......
  • 2024强网杯pwn short wp
    这时2024强网杯的pwn部分的short的WP分析以下程序的基本安全措施*]'/home/ysly/solve/tmp/short'Arch:i386-32-littleRELRO:PartialRELROStack:NocanaryfoundNX:NXenabledPIE:NoPIE(0x8048000)Stripp......
  • BUUCTF pwn学习日记
    我是纯新手,零基础的开始学Pwn喽时间:2024年10月29日BUUCTFPwn学习日记1.test_your_nc下载附件,用IDA打开发现直接nc就可以获得flagcatflag得到flag{07c0b69c-dcbf-4991-8cc6-05660b1a2dd2}2.ripIDA打开发现没有看见有用信息,Shift+F12发现了/bin/sh初步怀疑是栈溢......
  • 2024版最新148款CTF工具整理大全(附下载安装包)含基础环境、Web 安全、加密解密、密码爆
    经常会有大学生粉丝朋友私信大白,想通过打CTF比赛镀金,作为进入一线互联网大厂的门票。但是在CTF做题很多的时候都会用到工具,所以在全网苦寻CTF比赛工具安装包!目录:一、基础环境二、常用工具三、Web安全四、加密解密六、文件工具七、隐写图片八、隐写音频九、隐写......
  • ctfshow-pwn-前置基础(20-22)
    pwn20-pwn22是关于.got节跟.got.plt节的。这3道题的问题描述全是一样的,全都是问.got跟.got.plt是否可写以及他们的地址是什么,然后根据这些信息来拼成flag。那么关于.got和.got.plt的内容非常复杂,这里呢我也不解释了,推荐一个牛逼的博主的文章,大家可以自行去了解一下这两个节。聊聊......
  • 二进制菜鸟的杂谈-调试与pwn
    反调试技术NLFlagGlobalPEB的偏移当被调试的时候会有标志位:FLG_HEAP_ENABLE_TAIL-CHECK()FLG_HEAP_ENABLE_FREE_CHECK()FLG_HEAP_VALIDATE_PARAMETERS()一般为:moveax,fs:[30h]moval,[eax+68h]moval,70hcmpal,70h其实是因为isDebugger被检测到了进而影响......
  • pwntool
    基本使用首先需要frompwnimport*把pwntools导入进来,它同时会把一些系统库给导入进来本地打的话p=process('./filename'),远程的话p=remote('192.168.1.103',10001)p.close()关闭发送payloadp.send(payload)发送payloadp.sendline(payload)发送payload,并进行......
  • pwndbg
    dbg内容runrun:跑一遍 start:运行到程序认为的入口点停止ccontinue,执行到断点为止ni单步si步入iir:查看寄存器ib:查看断点disassembledisassemble$rip:反汇编rip附近汇编disassemblemain:反汇编mainbb*0x000055555555527a:在0x000055555555527a处下断点disable......
  • ctfshow-pwn-前置基础
    pwn13按照题目提示的信息,用gcc命令生成可执行文件,再运行即可得到flagpwn14题目提示:阅读以下源码,给定key为”CTFshow”,编译运行即可获得flag,那么我们直接看源代码开始有一个文件是否存在的检查,如果当前目录下不存在名为"key"的文件就会报错接下去就是通过循环将fp的值(也就......