当前位置:C++技术网 > 精选软件 > C++数据隐藏技术分析和代码实现

C++数据隐藏技术分析和代码实现

更新时间:2016-09-01 23:46:03浏览次数:1+次

    在提供给别人使用的代码中,我们可以利用数据隐藏技术将提供的头文件的类用到的各种数据成员隐藏起来,让使用者无需关心接口类的数据成员,也防止实现方法被逆向分析出来,在特殊保护代码场合会用到。

    之前我都没有关注过隐藏数据的做法,看到同事代码里用到了,而且看起来很厉害的样子,然后差不多了解一下,就自己来实现一下。其实这个数据隐藏技术并不会很难,只是对于初学者来说,可能有点绕而已。下面我全面分析一下,供部分需要学习的同学看。

    一般的的提供接口的文件结构是这样的,一个main函数所在的测试代码文件,一组接口代码(含头文件和CPP文件),头文件就是提供给别人使用的接口类声明。CPP则是生成Dll后提供给别人使用的,所以CPP里面的代码别人是无法知道的。

    在这种情况下,头文件和生成的dll或者lib一同提供给别人,为了让代码更好被人理解,我们会写一个测试代码,也就是main函数代码,演示接口的使用。cpp文件中的代码被编译成二进制代码后,是很难逆向分析出来的。所以可以很好的保护代码的安全性。然而,头文件必须暴露给别人的,否则你的接口别人是无法使用的。头文件是你dll的一个接口描述文档一样,所以头文件包含了大量的二进制代码的信息。但是一般情况下,我们都将数据成员声明在类里。这是最直接的,很所人会问,不放在类里放在哪里,这不是有点废话了吗。当然不是咯。如果成员变量都声明在类里,使用者是可以看到的。虽然你定义的一些成员变量是私有的,使用者也无法直接操作,却也被暴露给了使用者。一方面,让接口看起来繁杂,这个其实让使用者倒是挺烦的,接口越简单,越清爽。另一方面,不管是私有成员还是公有成员,都毫无遮掩。我们知道,一个程序的实现往往和成员变量的定义密切相关。也就是说,从你定义的成员变量的表示和结构,以及提供的一套接口函数,可以很大程度来推测你代码的实现原理,甚至是过程。在很多专业技术中,基本原理都是差不多的,有时候一个巧妙的实现可以突破瓶颈。但是这很容易从你的数据成员中看到端倪,进而推测实现。这样可以让你很快失去优势,他人不费吹灰之力就可以逆向分析,从而快速从你的接口结构中得到实现方法,进而追赶上你。

    在商业开发中,特别是激烈的竞争行业里,往往都是相互追赶,相互逆向破解,以学习对方的技术,然后保护自己的代码,压制竞争对手。所以,代码的保护是非常有必要讨论的问题。所以,你可以看到,数据隐藏技术确实是有必要探讨一番的。

    我就不一步步的引导实现过程,就将整个结构一次性抛出来吧,反正也不多,相信很好理解。

    代码的结构是这样的:

C++数据隐藏技术分析和代码实现

    因为这是示例代码,所以数据隐藏类就直接在3.h中定义好就行了。测试代码也就一个main,所以就在1.cpp中写。然后就是一对文件,2.h和2.cpp,一个头文件提供接口描述,一个是源文件cpp文件,你可以封装到dll里的。下面分别贴出几个文件的代码:

    1.cpp的代码如下:


#include "stdio.h"
#include "2.h"//接口类头文件
void main()//测试函数
{
    Cjjjs cjjjs;//接口类对象
    cjjjs.ShowData();//调用接口类成员函数
    system("pause");
}
     2.h的代码如下:



#include "iostream"
using namespace std;
//数据隐藏类前向声明
class Data;
//对外提供的类接口
class Cjjjs
{
private:
    //因为只有类的前向声明,只是表面Data是一个类,具体如何定义,却不知道。
    //所以这里不能用类对象作为Cjjjs的成员变量
    Data * m_pData;
public:
    Cjjjs();
    ~Cjjjs();
    void ShowData();
};
     这里就是隐藏了数据成员的类声明,在Cjjjs类中,你看不到实质的数据成员,只有一个m_pData,这是一个指针。通过类的前向声明,也不需要在2.h中提供Data类的头文件,而是在cpp文件中使用头文件来实际操作Data对象。所以,m_pData对使用者来说,你看不到任何关于Cjjjs类的成员变量(数据成员)的信息。你能看到的就是一个对外的接口函数ShowData()。这个函数内部如何实现的,没有成员变量的支持,你很难理解内部的运作机制。可能是人工智能算法得到的一个结果,也可以是直接输出的一个结果,你无从得知。这样也就保护了代码内部的实现逻辑,保护了你的灵感别人窃取。


    2.cpp的代码如下:


#include "2.h"
//在实际使用Data数据隐藏类的时候,必须包含Data声明的头文件,这里是3.h
#include "3.h"
Cjjjs::Cjjjs()
{
    //因为是用指针作为成员,所以务必要创建一个对象
    m_pData = new Data();
}
Cjjjs::~Cjjjs()
{
    //因为构造函数中动态创建了一个对象,所以务必要在析构函数里释放对象内存
    if (m_pData)
        delete m_pData;
}
void Cjjjs::ShowData()
{
    int age = m_pData->GetAge();
    cout<<"你的年龄:永远"<<age<<"岁!"<<endl;
}
     代码中每一处都有详细注释,就不重复解释代码。2.cpp的代码将来是封装在dll或者lib里的,所以对使用者来说是看不见的。


    那么使用的Data数据隐藏类该如何实现呢?看看3.h的结构:


//数据隐藏类
class Data
{
private:
    int m_age;
public:
    Data(){m_age=18;}
    int GetAge(){return m_age;}
};
     这里只是做一个示例,所以尽可能简单,这里也没有使用cpp来写代码定义,而是放在头文件里。我们这里是直接内部初始化了m_age。那么该如何初始化Data里的数据呢?我们可以给Data类提供一些合适的构造函数,在Cjjjs类的构造函数中创建Data对象时,可以进行初始化。初始化之后,Data提供各种成员函数,供Cjjjs来操作数据,而Data和Cjjjs类的交互操作,全都被封装起来了。所以,此时这些数据的操作对使用Cjjjs类的人来说都是不可见的。


    所以,Data和Cjjjs之间的交互,和普通的类之间的交互没有什么两样。这里的数据隐藏技术实现,就是通过增加了一个Data类来实现的。当然,使用Data类的好处不止有隐藏数据的好处,而且还提高了扩展能力。如果后期我们想要增加数据成员或者修改甚至删除某些数据成员,我们都不需要使用我们接口的程序做任何改动,只需要替换一下dll或使用lib重新编译一下即可。不管我们如何修改Data成员变量,对外都是统一的Data对象指针。

    在很多大项目中,都会使用这个技术,一方面基于安全考虑,一方面基于可扩展性考虑。在开源或者不注重代码安全的代码也会大量使用,主要是看中了扩展性。如果有安全需求,那更是需要了。