zoukankan      html  css  js  c++  java
  • 转载:CRC检测的实现与对抗

    本文转载于吾爱破解论坛:https://www.52pojie.cn/forum.php?mod=viewthread&tid=1150032

    一直听说CRC可以校验代码是否被修改,最近研究了一下。
    CRC的优点是代码量小,容易理解,在动态校验上应用比较广泛。
    代码量确实是小,没毛病,但是看网上的资料我理解起来,还真有点费劲,下面详细讲一下。
    1.CRC算法原理
    数据发送过程:
    多项式转化为二进制数,这个2进制数作为除数。
    CRC校验码的位数=上面计算除数的位数-1
    校验码的位数是多少,就把需要校验的数据左移多少位,得到的就是被除数
    被除数 模二除 除数 = 商+余数
    余数就是我们需要的CRC校验码
    数据接收过程:0
    多项式转化为二进制数,这个2进制数作为除数。
    接收到的数据和CRC码拼接起来,作为被除数
    除数确定了,被除数也确定了,接下来再次使用“模2除法”校验
    结果为0,则接受的数据正确,结果不为0接收的数据不正确
    还是不懂,很好,再翻译一遍
    多项式就是一个指定的数值,用我们需要校验的数据模二除这个多项式的数值,得到的余数就是CRC校验码。
    这样好理解很多了吧,模二除法想了解的话可以网上搜一下,大家动动手,我就懒一下了。2.CRC算法实现

    首先生成CRC校验码,我们这里按字节计算CRC,不考虑网上的按位计算(都64位系统了,不差一张表的内存)
    先写个函数生成一张字节CRC校验码的表,因为每个字节从00-FF有256个组合,所以每个字节有256种不同的校验码。

    VOID GenerateByteCrc()
    {
        unsigned int crc = 0;
        int i=0,j=0;
        for (j = 0; j < 256; j++)                       //一个byte有256种不同的值,计算所有可能值的crc码
        {                     
            crc = j;
            for (i = 0; i < 8; i = i++)                 //这个for循环生成crc码
            {   
                if (crc & 1)
                    crc = (crc >> 1) ^ 0xEDB88320;             //根据多项式生成的除数
                else
                    crc >>= 1;
            }
            crc_byte[j] = crc;                          //对应整型数值的crc码保存在该数组中
        }
        crc_byte_being = 1;        //通过这个值判断是否生成了这个表
    }

    有了校验表,就可以对数据进行校验,再写一个校验的函数:

    DWORD GenerateDataCrc(char* data, int len)    //data是校验数据的起始地址,len是校验数据的长度
    {
        unsigned int crc = 0xFFFFFFFF;
        unsigned int i;
        for (i = 0; i < len; i++)
        {
            crc = crc_byte[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
        }
        crc = ~crc;
        printf("当前代码段数据CRC校验码为0x%x ", crc);
        return crc;
    }

    起始看懂原理,再看这份实现的代码也有点晦涩,代码是借鉴加密与解密书中简化的代码,如果纯按照原理来实现的话,代码还有有点繁琐的。
    至此功能基本实现,写一个程序使用CRC校验自身是否被修改。

    int main()
    {
        char* data=0x00401000;        //这里是该程序代码段的起始地址
        int len = 0x0e6c;        //这里是该程序代码段的长度
      
        if (!crc_byte_being)
            GenerateByteCrc();
        DWORD OriginalCrcCode = GenerateDataCrc(data, len);
      
        while (1)
        {
            DWORD CurrentCrcCode = GenerateDataCrc(data, len);
            if (OriginalCrcCode != CurrentCrcCode)
            {
                printf("
    ------程序已经被修改,准备退出-----
    ");
                getchar();
                return 0;
            }
            printf("程序正常运行
    ");
            Sleep(2000);
        }
        return 0;
    }

    看一下代码段的虚拟地址偏移和大小,虚拟地址偏移加上映像基址就是data的值,大小就是len的值。

    程序正常执行如下:

    3.过掉CRC检测
    使用OD打开程序,F9跑起来:

    我们可以在代码段随便修改一条指令,看到效果:程序已经检测到代码被修改。

     好,现在我们试着过掉检测,因为CRC会不断的读取要验证的代码,所以我们可以使用CE查看是哪些代码在读取我们的程序。CE附加进程后,先手动添加一条代码段的地址,这里我就添加了代码段的起始地址,然后查看什么访问了该地址。

     在OD中看一下这个地址,CE也可以看,不过用OD更直观。
    这段代码就是我们计算CRC的代码,esi中保存了最后计算出的CRC码,传给eax作为函数的返回值。

     在这个函数中下个软件断点,因为软件断点会修改当前地址的指令为int 3,所以程序正常执行肯定会退出,我们单步跟着程序走,找到判断的代码

    很快遇到一个跳转, 这个就很明显了,nop掉就可以了,当然绕过的方法还有很多,就不一一列举了。
    4.CRC校验改进
    经过上述过程,发现CRC检测程序很容易被发现,被发现就会被干掉。
    如果创建一个线程专门用来CRC检测呢,通过内存访问断点还是会被定位到检测代码。
    如果双层CRC嵌套检测呢,两处代码还是会访问被检测的地址,所以还是会被定位。
    如果通过另一个进程来检测被保护进程呢?果断写份代码试一试。

    int main()
    {
    SIZE_T* Real_len;
    char* process_name = "CRC-verify.exe";
    char* buff;
      
    VirtualAlloc(&buff, 0x0e6c, MEM_RESERVE, PAGE_READWRITE);
    int Pid = ProcesstoPid(process_name);
    printf("%d
    ", Pid);
    HANDLE hprocess = OpenProcess(PROCESS_ALL_ACCESS, NULL, (DWORD)Pid);
    if (!hprocess)        //进程被od打开时,这里OpenProcess会返回0
    {
    printf("进程打开失败");
    return 0;
    }
    ReadProcessMemory(hprocess, 0x00401000, &buff, 0x0e6c, &Real_len);
    if (!crc_byte_being)
    GenerateByteCrc();
    DWORD OriginalCrcCode = GenerateDataCrc(&buff, 0x0e6c);
      
    while (1)
    {
    ReadProcessMemory(hprocess, 0x00401000, &buff, 0x0e6c, &Real_len);
    DWORD CurrentCrcCode = GenerateDataCrc(&buff, 0x0e6c);
    if (OriginalCrcCode != CurrentCrcCode)
    {
    printf("
    ------程序已经被修改,准备退出-----
    ");
    return 0;
    }
    printf("程序正常运行
    ");
    Sleep(2000);
    }
    CloseHandle(hprocess);
    return 0;
    }

    先运行前面校验自身的程序,再运行后面这个跨进程校验的程序,然后使用CE附加被保护的程序,再次查找一下是什么访问了内存地址。

    发现只能看到自身校验的代码,随便修改一处代码段中的内容,再查看跨进程校验的程序。

     这个程序也发现代码段被修改了,说明思路没问题。
    这样我们可以采用自身CRC校验全部代码,通过保护进程来校验CRC校验的代码的方案来达到检测的目的。
    当然这种方式通过遍历进程句柄表查找哪些进程打开了被保护进程,也可以过掉,但是相比于CRC自校验来说,门槛一下就提高了。
    今天就写到这里吧,新人第一次发帖,有不足的地方还望大佬们指正。

    参考资料:
    《加密与解密第四版》   段刚
    网络游戏安全之实战某游戏厂商FPS游戏CRC检测的对抗与防护   https://bbs.pediy.com/thread-253552.htm
    如何通俗的理解CRC校验并用C语言实现   https://zhuanlan.zhihu.com/p/77408094

  • 相关阅读:
    List 集合的常用方法总结
    springboot 整合 web 项目找不到 jsp 文件
    Web 安全
    微服务开放平台接口设计
    SpringCloud Hystrix 参数
    SpringCloud Eureka 配置
    ELK 日志收集系统
    网盘搜索引擎原理
    jsPlumb.jsAPI阅读笔记(官方文档翻译)
    ionic获取ios唯一设备id的解决方案
  • 原文地址:https://www.cnblogs.com/basstorm/p/13512685.html
Copyright © 2011-2022 走看看