• C++ 虚指针、成员变量与类对象的偏移地址


    先给出一段代码实现

    #include <iostream>
    using namespace std;
    class animal
    {
    protected:
        int age;
    public:
        virtual void print_age(void) = 0;
    };
    class dog : public animal
    {
    public:
           dog() {this -> age = 2;}
           ~dog() { }
           virtual void print_age(void) {cout<<"Wang, my age = "<<this -> age<<endl;}
    };
    class cat: public animal
    {
    public:
        cat() {this -> age = 1;}
        ~cat() { }
        virtual void print_age(void) {cout<<"Miao, my age = "<<this -> age<<endl;}
    };
    int main(void)
    {
         cat kitty;
         dog jd;
         animal * pa;
         int * p = (int *)(&kitty);
         int * q = (int *)(&jd);
        p[0] = q[0];
        pa = &kitty;
        pa -> print_age();
        return 0;
    }
    【源代码输出】
    代码输出是 Wang, my age = 1;
    假设将 p[0] = q[0]; 换为 p[1] = q[1]; 则输出为 Miao, my age = 2。

    【源代码分析】

    首先,这是一个取巧的改变虚表指针的办法,它利用了C++的对象模型的特点。我们知道,一个类有了虚函数后。它会有一个虚表来维护虚函数和一个虚表指针__vptr来指向它。而这个程序利用的即是改变虚指针的指向。它首先&kitty,而且转换为int*,获得cat类的虚表首地址,相同&jd获得dog类的虚表地址,而p[0] = q[0]令指向cat的虚表首地址。一下就变成了指向dog类的虚表首地址,然后基类获取到了这个指向dog类的kitty,调用虚方法则自然调用到了dog的print_age,然后这里的age则依旧保留的是cat的。由于你仅仅是改变了虚指针指向的虚表地址,不影响member data。
    重中之重,记住一个点:类对象的首地址是虚函数指针地址,其次是变量地址。改变对象指针类型,将改变实函数,改变对象指针变量,将改变虚函数与成员变量。


    其次,这代码不但依赖某些C++编译器的行为,还依赖平台的指针宽度是32位。


    int * p = (int *)(&kitty);
    int * q = (int *)(&jd);
    p[0] = q[0];
    这几句不应该用int*,而应该用intptr_t*才对。这样才干保证拷贝的是一个指针宽度的数据。而不是一个int宽度的数据。
    在32位平台上。int一般是32位,而指针是32位,所以正好匹配了,程序能正常执行;
    在64位平台上,假设是流行的LP64模型。int是32位而指针是64位,这里实际上仅仅拷贝了指针的一半。程序是否能正常执行就看运气了。
    假设是在一个64位且小端(little endian)的平台上,那这代码拷贝的是指针的低32位。非常可能会运气好能正常执行,由于dog类与cat类的vtable可能正好在内存里处于非常近的位置,它们的地址的高32位可能正好同样,地址不同的地方都在低32位。这样这个程序就运气好能正常执行。


    假设是在一个64位且大端(big endian)的平台上。那这段代码拷贝的是指针的高32位,那就全然达不到效果了。


    最后。这样的题还有非常多玩法。比如说一种简单的玩法是像这样:

    #include <iostream>
    using namespace std;
    class animal
    {
    protected:
        int age;
    public:
        virtual void print_age(void) = 0;
    };
    class dog : public animal
    {
    public:
           dog() {this -> age = 2;}
           ~dog() { }
           virtual void print_age(void) {cout<<"Wang, my age = "<<this -> age<<endl;}
    };
    class cat: public animal
    {
    public:
        cat() {this -> age = 1;}
        ~cat() { }
        virtual void print_age(void) {cout<<"Miao, my age = "<<this -> age<<endl;}
    };
    int main(void)
    {
         cat kitty;
         dog jd;
         animal * pa;
         int * p = (int *)(&kitty);
         int * q = (int *)(&jd);
        p[0] = q[0];
        pa = &kitty;
        pa -> print_age();
        return 0;
    }
    直接整个vtable伪造出来然后想往里面填啥就填啥。

    
    

  • 相关阅读:
    史上最强内网渗透知识点总结
    最全的网站渗透测试详细检测方法
    java代码审计
    信息收集
    Java Web安全之代码审计
    Bell-Lapadula和Biba的改进安全模型与应用
    浅谈Forrester零信任架构评估的7个技术维度
    CMD命令混淆高级对抗
    一篇文章带你领悟Frida的精髓(基于安卓8.1)
    svg作图
  • 原文地址:https://www.cnblogs.com/zsychanpin/p/6927947.html
Copyright © 2020-2023  润新知