• 编译器perspective oo 对象模型(1) 之 初窥c++对象模型


    ==============================================

    copyright: KIRA-lzn

    ==============================================

    转载请注明出处,这篇是我原创,翻版必究!

    ==============================================

    第二篇,如果写的好,请点个赞呦。当然有不对之处,非常欢迎拍砖!!!!

    第二篇来了,这次篇幅不能那么长了,得简短一点了,xixi。说好的oo对象模型,我一定能讲清楚,很久以前自己就已经写过至少5篇关于object model的文章,只是自己写着玩的,自己保存用的,怕以后忘记了,这次既然要发文章,就好好把以前的东西有条理的整理下。


    1.本人读过inside the c++ object model , effective c++ , more effective c++ , c++沉思录,c++ primer 等书

    2.编译器写过 minijava -> c 的代码生成,用prefix算法设计过java->c的对象模型存储,包括最难的minijava->x86的直接生成,意味用最低级的x86去直接模拟高度封装的java

    看过轮子哥专栏的朋友都知道,http://zhuanlan.zhihu.com/vczh-nichijou/19796639

    轮子哥的理念我也是很推崇的,他其中写道"譬如说我大四尝试并最终成功的一个问题——怎么把C语言编译成x86"

    其实我看到这里,我偷偷的小happy了一把,虽然我离轮子哥的水平还相差十万八千里,但是我觉得他还是识货的,他尽然提到这个东西。

    1.不过他是大四完成的,而我是研一上半学期完成的。

    2.不过他做的是c -> x86 ,我做的是minijava -> x86,当然他肯定不能包含全部c的语法,我当然也是。

    3.直接把minijava->x86,我个人认为难度系数应该是c->x86的10倍。因为java是面向对象的语言。

    我几乎2个多星期不眠不休,连吃饭,睡觉都在想问题,我设计了几种架构,后来连续推翻重写,最后才想到一种方法很适合,当然期间遇到很多问题,我都一一解决,包括编译器还支持自我递归,要知道一个函数调用自己,怎么样保持堆栈平衡呢?

    至于我是怎么设计的,有很多心得。我还想专门开个专栏去解释我到底是怎么想的。

    3.研究过信息安全相关

    所以深知c++对象模型的机理,才敢操刀 oo对象模型的相关文章。

     

    首先还是用我自己的话给 object model 下一个定义:

    当你用 Base* b = new Base(); 的时候,你应该考虑,此时发生了什么?考虑内存怎么样分配的? 此时是单一继承呢,还是多重继承呢? 虚函数表应该长成什么样? 多重继承又因为比如(微软专利:虚基类虚函数表技术)菱形多重继承导致的数据二义性问题呢?最最关键的,b指向内存中的哪里??

    放心吧,这些我都会讲清楚的。但我还是喜欢一步一步深入,带我一步一步引导,不是隐刀,和dota没关系,要不写篇dota攻略吧,xixi

    先看一段我认为入门最好的,最有趣的小程序,可能有点毁三观(第一反应:这tm也行?怎么做到的?,高手飘过,谢谢),不过没关系,这可以充分展示对象模型。

    请欣赏:深入理解oo对象模型4部曲


    1.第一部

        #include <iostream>
        using namespace std;
    
        class Base{
        public:
            virtual void f(){cout<<"Base"<<endl;}                        
        };
    
        class Derived:public Base
        {
        private:
            void f(){cout<<"Derived"<<endl;}
        };
    
        int main()
        {
            Base* bp = new Derived();
            bp->f();
            return 0;
        }
    

    答案是什么?

    毫不犹豫的回答,输出 Derived

    ok,答案是没问题了,这个不就是多态嘛? 没错,就是多态,但是注意,

    注意,我飘红的关键字,private修饰的fuction f(),这里好像违反了c++语法?

    因为对象是不能直接访问类的private成员函数的,对不对?

    这tm也行?

    如果不深入探讨,仅仅认为就是多态而已,当然也行。。。不过打破砂锅问到底的精神还是值得赞扬的!!!!!


    note: 注意,我举这个例子的更深层次的意图:这就是为什么说 多态 ,是运行期绑定?


    提前给出答案:后面有详细解释,不懂没关系。

    是吧,运行期绑定,我可以负责任(写过编译器)的告诉你,编译期的时候编译器根本不知道,只有当你程序跑起来的时候,去相应位置调用了相应位置的函数指针,才知道你到底运行的是什么函数,so,运行期(动态)绑定就是这个原因!

    好吧,第一个程序如果还是不过瘾,请欣赏第二个程序。


    2.第二部

    #include <iostream>
    using namespace std;
    
    class Base{
    
    public:
        virtual void f(){cout<<"Base"<<endl;}
    };
    
    class Derived:public Base {
    
    private:
         void f(){cout<<"Derived"<<endl;}
    };
    
     typedef void (*Func)();
    
    int main()
    {
    
        Base* bp = new Derived();
    
        Func fp = (Func)*(int*)*(int*)bp;
    
        fp(); 
    
        return 0;
    
    }
    

    这tm是什么?为什么fp尽然能调用bp对象模型里面的函数?这里面有什么猫腻吗? 此时应该引发思考,bp指向的对象模型到底长成神马模样?能让 人 如此用指针摧残?


    ok,如果还不过瘾,我再敲一个,更毁三观的程序,可以叫它 -> hook虚函数表 , 不行,这样叫不准确,就叫他 object hook 吧,其实就hook了一个函数指针而已。


    3.第三部

    #include <iostream>
    using namespace std;
    
    class Base{
    
    public: virtual void f(){cout<<"Base"<<endl;}
    };
    
    class Derived:public Base {
    
    private: void f(){cout<<"Derived"<<endl;}
    
    };
    
    void hookf() { cout<<"you are being hooked"<<endl; }
    
    typedef void (*Func)();
    
    int main()
    {
    
    Base* bp = new Derived();
    
    Func fp = (Func)*(int*)*(int*)bp;
    
    *fp = hookf;
    
    bp->f();
    
    return 0;
    }
    

    注意,这里*fp = hookf; 是编译不通过的,因为 vtable是被编译到 .data段 ,是只读的,g++下:

    a.cpp:29:11: error: assignment of read-only location ‘* fp’

    但是,在windows下,没有神马是不能修改的,比如(VirtualProtect,VirtualProtectex,修改CR0的写保护位,MDL等等),这里不详细讨论。

    所以说,如果能改,则hook一个函数指针有多么容易???

    如果改了之后,bp->f();输出什么,应该很明了了。


    4.第四部,来了,不像3一样,4是可以直接跑的,所以说,c++对象模型中。。vptr的设计。。。 -> hook 整张虚函数表

    windows下cl.exe编译 , linux 下 g++ 编译均可以。

    这个是我写的 监控虚函数表差异定位 时候的程序,我简化到最简。

    #include <iostream>
    #include <string.h>
    #include <stdlib.h>
    using namespace std;
    
    void Hook_all_Vtable(int* pObjectBase);
    
    void hookfunc();
    
    class Game {
    int a;
    int b;
    string s;
    public:
    virtual void add(){cout<<"virtual function add() being called:0x"<<endl;}
    virtual void sub(){cout<<"virtual function sub() being called:0x"<<endl;}
    virtual void mul(){cout<<"virtual function mul() being called:0x"<<endl;}
    virtual void divv(){cout<<"virtual function div() being called:0x"<<endl;}
    };
    
    void hookfunc() { cout<<"you are being hooked."<<endl; }
    
    typedef void (*Func)();
    
    int main() {
    
    Game* gp = new Game();
    
    Hook_all_Vtable((int*)gp);
    
    gp->add();
    
    return 0;
    
    }
    
    void Hook_all_Vtable(int* pObjectBase)
    {
    int* allocp = (int*)calloc(1,100*sizeof(long));
    memcpy(allocp,(void*)(*pObjectBase),100*sizeof(long));
    *pObjectBase = (int)allocp;
    *allocp = (int)hookfunc;
    }
    

    程序的细节,不做任何过多的解释,初窥嘛~,细节请听下回分解。

    此时,一段可以直接跑的程序出炉了。

    输出什么,相信大家很明白。


    这tm就随意的就hook整张虚函数表了??????

    c++对象模型到底长成神马模样??

    是不是有点意思了?????????

    通过这段最最简单的hook虚函数表程序,大家可以发现,其实玩转c++对象模型其实不是难事,希望大家跟着我一点一点学习,我保证能解释清楚。


    最后还是要总结一下下:


    note: 注意,所以这里说,c++有指针,所以可以肆意访问c++的对象模型,去hook里面的对象模型里的函数指针,比如堆溢出,对喷射等技术,所以c++对象模型在所有人都是好人的情况下,模型是完美的,是B.lippman在cfront的时候设计出来的完美模型,但是。 所以说,java做的比较彻底,没有指针,就不让你用指针,这样你怎么去遍历java对象模型的函数指针呢?是不是变得更安全了???而且java还有反射机制,也是存在于java对象模型中,所以,就是java,就是不让你用指针。就用reference吧。 but,我要说明一下: 不要认为c++对象模型不安全就不用c++,hook函数指针,不是因为c++对象模型的设计而造成的,说白了, 就是结构体里面有函数指针,然后函数指针被hook了,就这么简单!我们亲切的称之为 object hook 只要你用oo,注意,不是c++能oo,但是c不能oo,其实c也能oo,很easy(自己搞vptr和vtable) 所以,基本上,只要你用oo,就会存在函数指针,就有可能被hook,所以错不在c++,请大家明示!!! 但是c++允许指针去遍历虚函数表,这就不对了,呼呼。 java么有指针,看,多彻底??


    注意:c++的虚函数表,是被编译器编译到 .rodata 段,即只读段,你是不能直接修改虚函数表里面的函数指针,当然在windows下,没有神马是不能修改的,比如(VirtualProtect,VirtualProtectex,修改CR0的写保护位,MDL等等),但是可以修改vptr的指向,即可以hook整张虚函数表,因为vptr是在结构体里,当然看看你是分配在 栈上,还是堆里,还是.data段咯,这些都是可以直接修改(参考第4个程序)的, .text 和 .rodata是只读的。

    note:

    这里插播一条我写的小程序,和对象模型没关系,仅供娱乐:一定要运行一下,看下输出,嘻嘻。

    #include <stdio.h>
    int main()
    {
    
        int i = 0x00752065;
        int j = 0x00766f6c;
        short k = 0x0049;
        printf("%s %s%s",&k,&j,&i);
    
        return 0;
    }
    

    note:

    由以上语言,当年一起开发c编译器和unix的男银最后得了图灵奖,但是c++这么庞大编译器的男银却没有得图灵奖,这是又说明了神马??

    (又想到了特洛伊木马,有兴趣的童鞋可以自行去了解,蛋生鸡鸡生蛋的问题,得想清楚呦)

    各位看官有自己的观点,是吧。

    当然第一篇,只是随便谈谈c++对象模型的一些有意思的东西,并没有具体分析,因为初窥嘛,以后会讲清楚的,请关注我呦。

    请关注下一篇,谢谢。

  • 相关阅读:
    linq获取最大ID值
    asp:MultiView选项卡控件,可以用来选择性的显示需要的部门
    TFS修改工作区映射区
    怎么解决javascript小数相减会出现一长串的小数位数?
    (转)向页面动态载入用户控件和自定义控件的方法(谨记)
    (转)工作经验到底是个什么东东?工作经验从哪里来?
    hdu 5441 travel 离线+带权并查集
    hdu 5438 Ponds dfs
    hdu 5437 Alisha’s Party 模拟 优先队列
    CF 500 B. New Year Permutation 并查集
  • 原文地址:https://www.cnblogs.com/ustckira/p/3893432.html
Copyright © 2020-2023  润新知