6.2 Sunday搜索内存特征

news2024/11/29 8:52:43

Sunday 算法是一种字符串搜索算法,由Daniel M.Sunday于1990年开发,该算法用于在较长的字符串中查找子字符串的位置。算法通过将要搜索的模式的字符与要搜索的字符串的字符进行比较,从模式的最左侧位置开始。如果发现不匹配,则算法将模式向右滑动一定数量的位置。这个数字是由当前文本中当前模式位置的最右侧字符确定的。相比于暴力方法,该算法被认为更加高效。

6.2.1 字符串与特征码转换

GetSignatureCodeArray函数,该函数用于将给定的十六进制串表示的字节码特征码转换为十进制数,存储在一个整型数组中,以便后续进行搜索。同时,特征码中的未知标记符号?会被用256 替代,方便后续搜索对特征码的匹配。

其中,参数SignatureCode为一串十六进制字符串,描述要搜索的字节码特征码,参数BytesetSequence为一个整型数组,用于存储将十六进制数转为十进制后的结果。该函数首先计算给定的十六进制串中包含的字节码个数,因为每个字节对应两个十六进制字符,再加上每两个字符间的空格,故需要将十六进制字符串长度除以三,再加上一。

接下来,函数逐个字符读入特征码串中的每一个十六进制数,如果是有效的十六进制数,则转化为十进制数存入BytesetSequence数组中。如果遇到未知的标记符号?,则在BytesetSequence数组中用256表示该位置的值。最后,返回特征码数组中字节码的个数。

// 定义全局变量
#define BLOCKMAXSIZE 409600  // 每次读取内存的最大大小
BYTE* MemoryData;            // 每次将读取的内存读入这里
SHORT Next[260];             // 搜索下一个内存区域

// 将传入的SignatureCode特征码字符串转换为BytesetSequence特征码字节集
WORD GetSignatureCodeArray(char* SignatureCode, WORD* BytesetSequence)
{
    int len = 0;

    // 用于存储特征码数组长度
    WORD SignatureCodeLength = strlen(SignatureCode) / 3 + 1;

    // 将十六进制特征码转为十进制
    // 依次遍历SignatureCode中的每一个十六进制数
    for (int i = 0; i < strlen(SignatureCode);)
    {
        char num[2];

        // 分别取出第一个和第二个十六进制字符
        num[0] = SignatureCode[i++];
        num[1] = SignatureCode[i++];
        i++;

        // 如果两个字符都是有效的十六进制数,则将它们转换成十进制并存储到 BytesetSequence 中
        if (num[0] != '?' && num[1] != '?')
        {
            int sum = 0;
            WORD a[2];

            // 分别将两个十六进制字符转换成十进制数
            for (int i = 0; i < 2; i++)
            {
                // 如果是数字
                if (num[i] >= '0' && num[i] <= '9')
                {
                    a[i] = num[i] - '0';
                }
                // 如果是小写字母
                else if (num[i] >= 'a' && num[i] <= 'z')
                {
                    a[i] = num[i] - 87;
                }
                // 如果是大写字母
                else if (num[i] >= 'A' && num[i] <= 'Z')
                {
                    a[i] = num[i] - 55;
                }
            }

            // 计算两个十六进制数转换后的十进制数,并将其存储到 BytesetSequence 数组中
            sum = a[0] * 16 + a[1];
            BytesetSequence[len++] = sum;
        }
        else
        {
            BytesetSequence[len++] = 256;
        }
    }
    return SignatureCodeLength;
}

6.2.2 搜索内存区域特征

SearchMemoryBlock函数,该函数用于在指定进程的某一块内存中搜索给定的字节码特征码,查找成功则将匹配地址存入结果数组中。其中,参数hProcess为指向要搜索内存块所在进程的句柄,SignatureCode为给定特征码的数组指针,SignatureCodeLength为特征码长度,StartAddress为搜索的起始地址,size为搜索内存的大小,ResultArray为存储搜索结果的数组引用。

通过调用ReadProcessMemory函数读取进程内存中指定地址和大小的数据,将读取的数据存入变量MemoryData中,然后对读取的数据进行匹配,查找特征码。若匹配成功,则将特征码匹配的起始地址存入结果数组中。在匹配时,采用了KMP算法。如果找到与特征码中的字节码不匹配的字节,就根据Next数组记录的回溯位置,重新从失配的位置开始匹配,以降低匹配的时间复杂度,提高搜索效率。在代码中,若特征码中存在问号,则匹配位置从问号处开始重新匹配,如果没有则继续按照Next数组回溯进行匹配。

// 获取GetNextArray数组
void GetNextArray(short* next, WORD* SignatureCode, WORD SignatureCodeLength)
{
    // 特征码字节集的每个字节的范围在0-255(0-FF)之间
    // 256用来表示问号,到260是为了防止越界
    for (int i = 0; i < 260; i++)
    {
        next[i] = -1;
    }
    for (int i = 0; i < SignatureCodeLength; i++)
    {
        next[SignatureCode[i]] = i;
    }
}

// 搜索一块内存区域中的特征
void SearchMemoryBlock(HANDLE hProcess, WORD* SignatureCode, WORD SignatureCodeLength, unsigned __int64 StartAddress, unsigned long size, vector<unsigned __int64>& ResultArray)
{
    // 读取指定进程的内存数据到MemoryData缓冲区中
    if (!ReadProcessMemory(hProcess, (LPCVOID)StartAddress, MemoryData, size, NULL))
    {
        return;
    }

    // 循环遍历内存数据缓冲区
    for (int i = 0, j, k; i < size;)
    {
        j = i; k = 0;

        // 逐个比对内存数据缓冲区中的字节和特征码中的字节
        for (; k < SignatureCodeLength && j < size && (SignatureCode[k] == MemoryData[j] || SignatureCode[k] == 256); k++, j++);

        // 如果特征码完全匹配到内存数据缓冲区中的一段数据
        if (k == SignatureCodeLength)
        {
            // 将该段数据的起始地址保存到结果数组中
            ResultArray.push_back(StartAddress + i);
        }

        // 如果已经处理到缓冲区的末尾
        if ((i + SignatureCodeLength) >= size)
        {
            return;
        }

        int num = Next[MemoryData[i + SignatureCodeLength]];

        // 如果特征码中有问号,从问号处开始匹配
        if (num == -1)
        {
            // 如果特征码有问号,就从问号处开始匹配,如果没有就 i += -1
            i += (SignatureCodeLength - Next[256]);
        }
        else
        {
            // 否则从匹配失败的位置开始
            i += (SignatureCodeLength - num);
        }
    }
}

6.2.3 搜索整块内存区域

SearchMemory函数,该函数用于在指定进程的内存空间中搜索给定特征码的内存块,并把搜索到的内存地址存入结果数组中。函数为一层循环枚举给定的内存块,内部则调用SearchMemoryBlock函数进行内存块搜索。其中,参数hProcess为指向要搜索内存块所在进程的句柄,SignatureCode为给定特征码的字符串指针,StartAddress为搜索的起始地址,EndAddress为搜索的结束地址,InitSize为搜索结果数组初始空间大小,ResultArray为存储搜索结果的数组引用。

该函数首先通过调用VirtualQueryEx函数获取可读可写和可读可写可执行的内存块信息,并遍历每个内存块,对内存块进行搜索。之所以不直接搜索整个内存区域,是因为那样可以减少非必要的搜索,提高效率。

内存块的搜索通过调用SearchMemoryBlock函数实现。搜索采用了KMP算法,先通过GetNextArray函数和GetSignatureCodeArray函数将特征码转换为对应的变量,再对每个内存块逐个匹配,在匹配过程中若找到与特征码中的字节码不匹配的字节,就根据Next数组记录的回溯位置从失配的位置开始重新匹配,以降低匹配的时间复杂度。在内存块搜索过程中,若匹配成功,则将特征码匹配的起始地址存入结果数组中,最终函数返回结果数组大小。

// 实现搜索整个程序
int SearchMemory(HANDLE hProcess, char* SignatureCode, unsigned __int64 StartAddress, unsigned __int64 EndAddress, int InitSize, vector<unsigned __int64>& ResultArray)
{
    int i = 0;
    unsigned long BlockSize;
    MEMORY_BASIC_INFORMATION mbi;

    WORD SignatureCodeLength = strlen(SignatureCode) / 3 + 1;
    WORD* SignatureCodeArray = new WORD[SignatureCodeLength];

    // 实现特征码字符串与数组转换
    GetSignatureCodeArray(SignatureCode, SignatureCodeArray);
    GetNextArray(Next, SignatureCodeArray, SignatureCodeLength);

    // 初始化结果数组
    ResultArray.clear();
    ResultArray.reserve(InitSize);

    // 查询内存属性并循环
    while (VirtualQueryEx(hProcess, (LPCVOID)StartAddress, &mbi, sizeof(mbi)) != 0)
    {
        // 判断并获取具有PAGE_READWRITE读写,或者PAGE_EXECUTE_READWRITE读写执行权限的内存
        if (mbi.Protect == PAGE_READWRITE || mbi.Protect == PAGE_EXECUTE_READWRITE)
        {
            i = 0;

            // 得到当前块长度
            BlockSize = mbi.RegionSize;
            
            // 搜索这块内存
            while (BlockSize >= BLOCKMAXSIZE)
            {
                // 调用内存块搜索功能依次搜索内存
                SearchMemoryBlock(hProcess, SignatureCodeArray, SignatureCodeLength, StartAddress + (BLOCKMAXSIZE * i), BLOCKMAXSIZE, ResultArray);
                BlockSize -= BLOCKMAXSIZE;
                i++;
            }
            SearchMemoryBlock(hProcess, SignatureCodeArray, SignatureCodeLength, StartAddress + (BLOCKMAXSIZE * i), BlockSize, ResultArray);
        }

        // 开始地址增加下一块长度继续搜索
        StartAddress += mbi.RegionSize;
        if (EndAddress != 0 && StartAddress > EndAddress)
        {
            return ResultArray.size();
        }
    }

    // 释放特征码数组并返回搜索计数器
    free(SignatureCodeArray);
    return ResultArray.size();
}

将上述代码理解后读者可以自行使用

int main(int argc, char *argv[])
{
    // 通过进程名获取进程PID号
    DWORD Pid = GetPidByName("PlantsVsZombies.exe");
    printf("[*] 获取进程PID = %d \n", Pid);

    // 初始化MemoryData大小
    MemoryData = new BYTE[BLOCKMAXSIZE];

    // 存储搜索返回值
    vector<unsigned __int64> ResultArray;

    // 通过进程ID获取进程句柄
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, Pid);

    // 开始搜索
    // 搜索特征码 FF 25 ?? 从0x0000000到0xFFFFFFF 初始长度为3 返回值放入ResultArray
    SearchMemory(hProcess, "FF 25 ??", 0x0000000, 0xFFFFFFF, 3, ResultArray);

    // 输出结果
    for (vector<unsigned __int64>::iterator it = ResultArray.begin(); it != ResultArray.end(); it++)
    {
        printf("0x%08X \n", *it);
    }

    system("pause");
    return 0;
}

编译并运行上述程序片段,则会枚举hProcess进程内特征码时FF 25 ??的片段,枚举位置为0x0000000-0xFFFFFFF枚举长度为3个特征,最终将枚举结果输出到ResultArray数组内,输出效果图如下所示;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/ae682eb.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1028184.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是IoT数字孪生?

数字孪生是资产或系统的实时虚拟模型&#xff0c;它使用来自连接的物联网传感器的数据来创建数字表示。数字孪生允许您从任何地方实时监控设备、资产或流程。数字孪生用于多种目的&#xff0c;例如分析性能、监控问题或在实施之前运行测试。从物联网数字孪生中获得的见解使用户…

软件测试 —— 答疑篇

什么是软件测试&#xff1a; 软件测试是不是就是找 bug &#xff1f; 软件测试就是证明软件不存在错误的过程 软件测试就是为了证明程序能够正确运行 刚新买来一部手机&#xff0c;我们要干什么&#xff1f; 一场考试 , 做完一遍题目之后 , 进行一遍检查 , 就是在 "…

迅为RK3399开发板创建android工程

打开 AndroidStudio 软件&#xff0c;点击“Start a new Android Studio project”新建一个 Native C工程&#xff08;因为我们要调用本地库&#xff0c;所以要创建这个 C实例工程&#xff09;&#xff0c;点击“Next” 2.填写创建信息&#xff0c;如下图所示。最后点击“finis…

台灯应该买什么样的才能护眼?盘点好用的护眼台灯

现在我们很多家长对自己孩子的视力十分关心&#xff0c;生怕自己的孩子是近视、远视、弱视等等。对于父母而言&#xff0c;在孩子读书压力大课业重的关键时期&#xff0c;为孩子选择合适的桌椅&#xff0c;保护灯具从而保护孩子的眼睛是非常重要的事情!那么买给孩子读书做功课的…

全网最全!保姆级教程!XRD数据分析工具HighScore3.0.5+数据库2021下载、安装及使用教程...

编辑&#xff1a;研路科研人Sci “研路漫漫&#xff0c;伴你同行” 01 目录 1.目录 2.软件下载 3.介绍 3.1 概含介绍 3.2 特点介绍 4.下载、安装教程 4.1 软件安装教程 4.2 数据库导入教程 5.使用教程 5.1导入文件 5,2 寻峰 5.3 背底扣除 5.4 检索 5.5 导出报告 02 软件下载 压…

yolov5自动训练/预测-小白教程

文章目录 引言一、配置参数设置1、数据参数配置2、模型训练参数配置3、模型预测参数配置 二、一键训练/预测的sh介绍1、训练sh文件(train.sh)介绍2、预测sh文件(detect.sh)介绍 三、本文训练main代码解读1、训练main函数解读2、数据加工与参数替换 四、本文预测main代码解读1、…

MFC扩展库BCGControlBar Pro v33.6亮点 - 流程图、Ribbon Bar功能升级

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版 v33.6已正式发布了&#xff0c;此版本包含了对图表组件的改进、带隐藏标签的单类功能区栏…

数据结构--7.1散列表(哈希表)查找

散列表查找 我们要在a[ ] 中查找key关键字的记录&#xff1a; ——顺序表查找&#xff1a;挨个儿查找 ——有序表查找&#xff1a;二分法查找 ——散列表查找 记录的存储位置 f&#xff08;关键字&#xff09; 散列技术是在记录的存储位置和它的关键字之间建立一个确定的…

shell脚本命令

Shell命令是在类Unix操作系统中使用的命令行解释器&#xff08;shell&#xff09;中执行的命令。Shell命令可以用于执行系统命令、操作文件、进行文本处理、管理进程等。以下是一些常见的Shell命令&#xff1a; 1. ls&#xff1a;列出当前目录下的文件和文件夹。 2. cd&#x…

泡泡玛特加速海外布局,泰国首店开业吸引超千名粉丝排队

自2022年起&#xff0c;泡泡玛特全球门店布局加速&#xff0c;在包括英国、美国、新西兰欧美国家均开设新店面&#xff0c;2022年7月&#xff0c;泡泡玛特全球首家旗舰店落地首尔。泡泡玛特自2018年年底开始规划出海&#xff0c;截至目前&#xff0c;在全球已经拥有50多家门店&…

解锁前端Vue3宝藏级资料 第五章 Vue 组件应用 1( Props )

本章带领大家理解组件、props、emits、slots、providers/injects&#xff0c;Vue 插件 等Vue组件使用的基础知识。 5.1 组件注册5.2 Props5.2.1 组件之间如何传值5.2.2 参数绑定 v-bind5.2.3 参数类型5.2.4 props 默认与必填5.2.5 验证设置5.2.6 useAttrs 属性设置 第一章 Vue3…

Centos7安装wps无法打开及字体缺失的问题解决

在centos7上安装了最新的wps2019版本的wps-office-11.1.0.11704-1.x86_64.rpm&#xff0c;生成了桌面图标并信任&#xff0c;可以新建文件&#xff0c;但是软件无法打开。在终端执行如下命令&#xff0c;用命令行启动wps&#xff1a; cd /opt/kingsoft/wps-office/office6/ ./…

深度学习修炼(二)全连接神经网络 | Softmax,交叉熵损失函数 优化AdaGrad,RMSProp等 对抗过拟合 全攻略

文章目录 1 多层感知机&#xff08;全连接神经网络&#xff09;1.1 表示1.2 基本概念1.3 必要组成—激活函数1.4 网络结构设计 2 损失函数2.1 SOFTMAX操作2.2 交叉熵损失函数 3 优化3.1 求导计算过于复杂&#xff1f;3.2 链式法则导致的问题&#xff1f;3.3 梯度下降算法的改进…

自定义协议、序列化与反序列化

在编写TCP和UDP程序的时候&#xff0c;我们很自然的就使用了读取的函数对数据进行获取&#xff0c;对于UDP来说提供的是无连接的以数据报的形式进行传输&#xff0c;对于TCP来说是面向数据流的&#xff0c;在之前的程序中我们只是进行了读取的操作&#xff0c;但是并没有对读取…

通讯网关软件008——利用CommGate X2Mysql实现OPC数据转储Mysql

本文介绍利用CommGate X2MYSQL实现从OPC Server读取数据并转储至MYSQL数据库。CommGate X2MYSQL是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;实现从OPC Server读取数据并转储至MYSQL数据…

Go语言进化之路:泛型的崛起与复用的新篇章

一、引言 泛型编程在许多编程语言中都是一项非常强大的特性&#xff0c;它可以使程序更加通用、具有更高的重用性。然而&#xff0c;Go语言在很长一段时间内一直没有提供泛型功能。在过去的一些版本中&#xff0c;Go语言开发者试图引入泛型&#xff0c;但最终都因为各种原因被…

分布式系统——分布式系统知识脑图

摘要 本博文主要介绍分布式系统知识脑图&#xff0c;帮助大家更好的快速的了解分布式系统相关知识。同时也是为大家在工作中应对分布式系统设计提供相关参考。 一、分布式系统知识脑图 博文参考

origin自定义颜色

点击图例&#xff0c;然后点击菜单栏的颜色右边的三角形。选择自定义颜色 在红绿蓝里输入自己想要的颜色配比。点击确定 如图&#xff0c;定义好五组颜色。我这里用的是比较经典的五色配图 蓝&#xff1a;1,86,153 黄&#xff1a;250,192,15 橙&#xff1a;243,118,74 浅蓝…

mysql workbench常见问题

1、No database selected Select the default DB to be used by double-clicking its name in the SCHEMAS list in the sidebar 方法一&#xff1a;双击你要使用的库 方法二&#xff1a;USE 数据库名 2、复制表名&#xff0c;字段名 3、保存链接

【PostgreSQL内核学习(十一)—— (CreatePortal)】

CreatePortal 概述CreatePortal 函数GetPortalByName 函数PortalHashTableLookup 函数 MemoryContextAllocZero 函数 AllocSetContextCreate 函数ResourceOwnerCreatePortalHashTableInsert总结 声明&#xff1a;本文的部分内容参考了他人的文章。在编写过程中&#xff0c;我们…