windows运算符和数据类型重载反CE查询搜索

  1、windows下搞逆向,一般都是从数据搜索开始的。数据搜索最常见的工具就是CE了;由于CE自己重写了关键的系统函数,想从驱动层完全杜绝CE读内存数据很难,怎么才能在应用层防止或干扰CE读取自己的内存了?

       

   要反CE搜索,就要先学习CE搜索的原理和数据在内存存放的原理;这里以32位CPU为例:总所周知,理论上讲,内存的读写速度只有cpu寄存器的1/100左右,所以cpu的厂家设计了各种缓存来存储cpu从内存读取的数据,同时也会尽量减少从内存读数据;为了高效从内存读数据,cpu一般都是按照32/8=4字节对齐来写内存数据的,char、byte、short等不满4字节的也占用4自己的空间,比如:

  struct{
       char a;
       short b;
        int  c;
  }

  char占用1字节,short占用2字节,int占用4字节,整个结构体占用7字节???  哈哈,哪有这么简单啊! 为了4字节对齐,char和short一共占用4字节,int占用4字节,那么这个结构体一共占用8字节。cpu读内存的时候按照4字节的颗粒度读取(地址线是32位的),也就是每次读连续的4字节,这也刚好是int的长度 CE搜索的类型展示如下: 

      

    CE搜索也要通过CPU去读内存的数据,所以也是按照4字节的宽度从内存读数据的!然后根据用户搜索不同的数据长度做拼接或裁剪;这里以int为例,CE会按照4字节的对齐方式和颗粒度在内存搜索,所以这里反CE的方式也就很明显了:

  •  对数据加密:在屏幕显示的数据并不直接存放在内存,而是简单加密,比如异或某个数字。需要在屏幕显示的时候再异或解密
  •    或则不加密,把int整型的4个字节分别存放在4个不同的、不连续的内存空间,然后自己重载所需的算术运算;
  •    或则把int的4个字节存在4个连续的byte空间,但顺序打乱,读取的时候自己恢复

  这里顺便扩展一下:我们用椭圆曲线做非对称加密的时候,密钥一般不低于128位,远超了普通int 32位的长度。为了表达128位的私钥,专门定制了bigNum类型来保存私钥!其原理和这里是一样的,没有本质区别!

  下面代码演示通过重载int类型、算术运算来反CE搜索!先写段简单的代码模拟被攻击少血的场景:角色有1500滴血,每被攻击一次就减少50滴血;

#include <iostream>

int main()
{
    int hp = 1500;
    while (true) 
    {
        hp = hp - 50;
        printf("剩余血量:%d\n",hp); 
        system("pause");
    }
    
}

  由于是int类型,在CE里面选择4字节的精准搜索;这里很容易就搜到了,然后改成任意数字;

    

      通过CE的”find out what access this address“还能进一步定位到读写这个内存地址的代码,如下:

      

  这里甚至可以把sub改成add,导致血量越来越多:

   

  血量越来越多:

   

   2、先在怎么通过重载反CE搜索了?

   (1)重写int类型

    上面说了,CE是从4字节对齐的地址一次性读取4字节来搜索和比对的。这里有两种思路:4字节还是挨着,但是顺序打乱;或4字节顺序不打乱,但是地址随机分配,隔开了!这里为了节约内存、便于管理,我们采用第一种思路,即4字节还是挨着,但是人为把字节存储的顺序打乱!核心代码如下:

myInt::myInt(int val) 
{
    mem[0] = new char;
    mem[1] = new char;
    mem[2] = new char;
    mem[3] = new char;

    *this = val;//只有一个成员变量,所以*this就是取这个成员变量的值
}

void myInt::operator=(int val)
{
    /*
    1、把原int顺序打散
    2、mem这4个byte还是顺序相接的
    */
    char* read = (char*)&val;
    mem[0][0] = read[3];
    mem[1][0] = read[1];
    mem[2][0] = read[0]^0xa5;
    mem[3][0] = read[2];
}

  这里重载了赋值号,方便给成员变量赋值;同时也打乱了原有int中4字节的顺序来存储;为了显示打印,还有做四则运算,还需要重载int类型,核心代码如下:

myInt::operator int()
{
    int val;
    char* read = (char*)&val;
    read[3] = mem[0][0];
    read[1] = mem[1][0];
    /*255以内的数,1个字节和4个字节的效果是一样的,所以这里用个随机数加密一下,避免被CE搜索到*/
    read[0] = mem[2][0]^0xa5;
    read[2] = mem[3][0];
    return val;
}

  有一点需要注意:如果数字在255以内,也就是只用了1字节,那么打乱顺序也是没用的,所以这里随机选一个数给最后一个自己的数加密;

       完整的代码如下:

  (1)自定义myInt类型来重载int类型的数据变量。头文件如下:

#pragma once
class myInt
{
    char* mem[4];
public:
    myInt(int val = 0);
    ~myInt();
    operator int();
    void operator=(int val);
};

  myInt.cpp实现如下:

#include "myInt.h"

//内存分配io效率低,建议一次性分配足够空间,每次按需取用就行
myInt::myInt(int val) 
{
    mem[0] = new char;
    mem[1] = new char;
    mem[2] = new char;
    mem[3] = new char;

    *this = val;//只有一个成员变量,所以*this就是取这个成员变量的值
}

myInt::~myInt() 
{
    delete mem[0];
    delete mem[1];
    delete mem[2];
    delete mem[3];
}

myInt::operator int()
{
    int val;
    char* read = (char*)&val;
    read[3] = mem[0][0];
    read[1] = mem[1][0];
    /*255以内的数,1个字节和4个字节的效果是一样的,所以这里用个随机数加密一下,避免被CE搜索到*/
    read[0] = mem[2][0]^0xa5;
    read[2] = mem[3][0];
    return val;
}

void myInt::operator=(int val)
{
    /*
    1、把原int顺序打散
    2、mem这4个byte还是顺序相接的
    */
    char* read = (char*)&val;
    mem[0][0] = read[3];
    mem[1][0] = read[1];
    mem[2][0] = read[0]^0xa5;
    mem[3][0] = read[2];
}

  (2)使用myInt类型的代码如下:

#include <iostream>
#include "myInt.h"

myInt hp = 1500;
int main()
{
    //myInt hp = 1500;
    while (true) 
    {
        hp = hp - 50;
        //printf("剩余血量:%d\n",hp); 
        std::cout << "剩余血量:" << hp;
        system("pause");
    }
    
}

  效果:这里用CE已经搜不到了!

     

   这种方式可以有效反CE搜索,但是由于每次调用生成myInt类型数据都要分配、“逆序”读写内存,大量的IO操作会导致效率很低,所以只适用于关键数据的保护,比如key之类的!

 

扩展:这里使用了随机数组来操控内存地址。数组是一种非常简单的数据存储结构或方式,最大的特点就是数据是连续存储的;举个例子:char  a[5],内存中的存储方式如下:

       

   这个很简单,大家都能理解。因为本文重载int类型用了二维数组,所里这里有必要探究二维数组的存储方式,比如char a[5][5],大部分同学理解的存储方式是如下的:

     

   可能是为了方便开发人员理解,上图这里实际上是抽象出来的逻辑存储方式。在实际的物理内存中也是这样存储的么?我们做个小测试打印出来看看:

  

   看到了么,实际上是挨着存储的,并不是我们想象的在物理内存中也是“二维”存储的!数组元素的下标仅仅是索引,不代表实际的物理存储方式!所以代码中的mem[0][0]、mem[1][0]、mem[2][0]、mem[3][0]就是通过这种方式隔开存储的!

 

       

posted @ 2021-04-23 18:40  第七子007  阅读(506)  评论(0编辑  收藏  举报