学习C++PWN

C++ PWN 学习笔记

Posted by X1ng on December 19, 2020

只会做几个烂大街的堆题目,,比赛堆题签个到走人

这好吗?这不好,,所以赶紧学学C++ pwn,记个笔记

本来想学学在ida里神奇的c++程序要怎么逆,但是查找到的资料大多数都是对虚函数进行利用,就学一学吧

虚函数的实现

由于上C++课时划水太多、平时学习摸鱼太多,先学习一下虚函数的知识

C++拥有面向对象的特性,class则是C++在面向对象这个方面与C语言最大的不同

而class本身只是struct的一种衍生方式,而类中的成员函数会被直接编译到text段,对一般成员函数进行调用的时候,会直接跳转到函数在.text中的地址,而对虚拟成员函数调用,则略微有些复杂

  1. 定义虚函数是为了允许用基类的指针来调用子类的这个函数
  2. 每个包含了虚函数的类都包含一个虚表(对于继承了包含虚函数的基类的派生类来说,派生类可以调用基类的虚函数,故派生类也会有自己的虚表,其虚表中的指针会指向其继承的最近的一个类的虚函数),这个类的所有对象都使用同一个虚表,虚表一般保存在.rodata中,没有写权限
  3. 虚表是一个指针数组,其元素是虚函数的指针,它的地址保存在类的开始处,也就是用于保存类的内存中偏移为0的地址
  4. 虚函数的调用过程:程序调用某类的虚函数->通过该类的虚表地址找到该类对应的虚表->从虚表中找到函数地址进行调用
  5. 虚表中元素的赋值,在编译的时候完成

贴一张图来表达

图片来自Clang裁缝店

再举个栗子

#include <iostream>
#include <string>
using namespace std;
class A {
public:
    virtual void vfunc1(){cout << "A::vfunc1()\n";};
    virtual void vfunc2(){cout << "A::vfunc2()\n";};
    void func1(){cout << "A::func1()\n";};
    void func2(){cout << "A::func2()\n";};
private:
    int m_data1, m_data2;
};

class B : public A {
public:
    virtual void vfunc1(){cout << "B::vfunc1()\n";};
    void func1(){cout << "B::func1()\n";};
private:
    int m_data3;
};

class C: public B {
public:
    virtual void vfunc2(){cout << "C::vfunc2()\n";};
    void func2(){cout << "C::func2()\n";};
private:
    int m_data1, m_data4;
};
int main(){
    A a, *p;
    B b;
    C c;

    p = &b;
    p->func1();
    p->func2();
    p->vfunc1();
    p->vfunc2();
    cout << endl;

    p = &c;
    p->func1();
    p->func2();
    p->vfunc1();
    p->vfunc2();
    cout << endl;
    return 0;
}

这里C继承B、B继承A,输出结果为

A::func1()
A::func2()
B::vfunc1()
A::vfunc2()

A::func1()
A::func2()
B::vfunc1()
C::vfunc2()

三个类对应的虚表分别为

  1. 由于都有虚函数,所以三个类都有自己的虚表
  2. B对虚函数vfunc1进行了重写,并从A继承了vfunc2,所以B虚表里的函数指针为B::vfunc1()A::vfunc2()
  3. C对虚函数vfunc2进行了重写,并从B继承了vfunc1,所以C虚表里的函数指针为B::vfunc1()C::vfunc2()
  4. 在使用A类型的指针p来调用成员函数的时候,调用没有被定义为虚函数的func1func2都直接调用了A::func1A::func2

_IO_FILE的vtable

这个虚表存在的原因就是因为对于linux中各种不同的IO对象(块设备上的文件,驱动设备,伪文件系统中的文件)虽然都是调用的统一的fopen()fread()fclose()函数,但是其实对于不同的对象,这些函数的实现方法肯定是不一样的,也就是为什么存在虚表的原因了


所以除了可以用_IO_FILE来泄露libc地址以外,还可以伪造其vtable来进行利用,但是libc2.24之后加入了vtable check机制,无法再构造vtable

参考资料

C++虚函数的攻与防

C++虚函数表剖析

C++逆向分析