编译器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++对象模型的一些有意思的东西,并没有具体分析,因为初窥嘛,以后会讲清楚的,请关注我呦。

请关注下一篇,谢谢。

原文地址:https://www.cnblogs.com/ustckira/p/3893432.html