6.1 KMP算法搜索机器码

这篇具有很好参考价值的文章主要介绍了6.1 KMP算法搜索机器码。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

KMP算法是一种高效的字符串匹配算法,它的核心思想是利用已经匹配成功的子串前缀的信息,避免重复匹配,从而达到提高匹配效率的目的。KMP算法的核心是构建模式串的前缀数组Next,Next数组的意义是:当模式串中的某个字符与主串中的某个字符失配时,Next数组记录了模式串中应该回退到哪个位置,以便继续匹配。Next数组的计算方法是找出模式串每一个前缀中最长的相等前缀和后缀,并记录下来它们的长度,作为Next数组中的对应值。

在字符串匹配时,KMP算法从主串和模式串的开头开始逐个字符比较,若发现匹配失败,则根据Next数组中的值进行回退,从失配位置的下一位重新开始比较。这样回退的次数比暴力匹配方式要少得多,因此匹配效率得到了大幅提升。

6.1.1 遍历输出进程内存

首先需要实现取进程PID的功能,当用户传入一个进程名称时则输出该进程的PID号,通过封装GetPidByName函数,该函数用于根据指定的进程名称,获取该进程的进程PID,以便于后续针对进程进行操作。函数参数name为指定的进程名称字符串。该函数通过调用CreateToolhelp32Snapshot函数创建一个系统快照,返回系统中所有进程的快照句柄。然后使用该快照句柄,通过进程快照函数Process32FirstProcess32Next函数逐个对比进程的名称,找到进程名称匹配的PID,返回该PID。若无法找到匹配的进程名称,则返回0。读者需要注意,当使用进程遍历功能时通常需要引入<tlhelp32.h>库作为支持;

// 根据进程名得到进程PID
DWORD GetPidByName(const char* name)
{
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
    DWORD pid = 0;

    if (Process32First(snapshot, &pe32))
    {
        do
        {
            if (_stricmp(pe32.szExeFile, name) == 0)
            {
                pid = pe32.th32ProcessID;
                break;
            }
        } while (Process32Next(snapshot, &pe32));
    }
    CloseHandle(snapshot);
    return pid;
}

在开始使用KMP枚举特征码之前我们需要实现简单的内存读写功能,通过封装一个MemoryTraversal函数,该函数接收三个参数分别是,进程PID,进程开始内存地址,以及进程结束内存地址,该函数输出当前进程内存机器码,每次读入4096字节,然后每16个字符换一次行,遍历内存0x00401000 - 0x7FFFFFFF这篇内存区域,这段代码实现如下所示;

// 遍历并输出进程内存
VOID MemoryTraversal(DWORD PID, const DWORD beginAddr, const DWORD endAddr)
{
    const DWORD pageSize = 4096;

    // 打开并获取进程句柄
    HANDLE process = ::OpenProcess(PROCESS_ALL_ACCESS, false, PID);

    BOOL _break = FALSE;
    BYTE page[pageSize];
    DWORD tmpAddr = beginAddr;

    // 循环枚举进程
    while (tmpAddr <= endAddr)
    {
        // 每次读入内存
        ReadProcessMemory(process, (LPCVOID)tmpAddr, &page, pageSize, 0);

        // 依次循环每一个字节
        for (int x = 0; x < 4096; x++)
        {
            // 每16个字符换一行
            if (x % 15 != 0)
            {
                DWORD ch = page[x];

                if (ch >= 0 && ch <= 15)
                {
                    printf("0%x ", ch);
                }
                else
                {
                    printf("%x ", ch);
                }
            }
            else
            {
                printf(" | %x \n", tmpAddr+x);
            }
        }
        tmpAddr += pageSize;
    }
}

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

    // 输出内存遍历0x401000-0x7FFFFFFF
    MemoryTraversal(Pid, 0x401000, 0x7FFFFFFF);

    system("pause");
    return 0;
}

读者可自行编译这段代码片段,并运行特定进程,当程序运行后即可输出PlantsVsZombies.exe进程内的机器码,并以16个字符为一个单位进行输出,其效果图如下所示;

6.1 KMP算法搜索机器码

6.1.2 使用KMP搜索特征码

为了能让读者更好的理解KMP特征码搜索的实现原理,这里笔者依然在MemoryTraversal函数基础之上进行一定的改进在本次改进中,我们增加了memcmp函数,通过使用该函数我们可以很容易的实现对特定内存区域的相同比较,读者在调用ScanMemorySignatureCode函数时需要传入,开始地址,结束地址,特征码,以及特征码长度,当找到特定内存后则返回该内存的所在位置。

// 内存特征码搜索
ULONG ScanMemorySignatureCode(DWORD Pid, DWORD beginAddr, DWORD endAddr, unsigned char *ShellCode, DWORD ShellCodeLen)
{
    unsigned char *read = new unsigned char[ShellCodeLen];

    // 打开进程
    HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, false, Pid);

    // 开始搜索内存特征
    for (int x = 0; x < endAddr; x++)
    {
        DWORD addr = beginAddr + x;

        // 每次读入ShellCodeLen字节特征
        ReadProcessMemory(process, (LPVOID)addr, read, ShellCodeLen, 0);
        int a = memcmp(read, ShellCode, ShellCodeLen);

        if (a == 0)
        {
            printf("%x :", addr);
            for (int y = 0; y < ShellCodeLen; y++)
            {
                printf("%02x ", read[y]);
            }
            printf(" \n");
            return addr;
        }
    }
    return 0;
}

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

    // 开始搜索特征码
    unsigned char ScanOpCode[3] = { 0x56, 0x57, 0x33 };

    // 依次传入开始地址,结束地址,特征码,以及特征码长度
    ULONG Address = ScanMemorySignatureCode(Pid, 0x401000, 0x7FFFFFFF, ScanOpCode, 3);

    printf("[*] 找到内存地址 = 0x%x \n", Address);

    system("pause");
    return 0;
}

上述程序运行后,将枚举当前进程0x401000-0x7FFFFFFF区域中特征码为0x56, 0x57, 0x33的内存地址,枚举到以后则输出该内存地址的位置,输出效果图如下图所示;

6.1 KMP算法搜索机器码

有了上面的模板我们只需要在此基础之上增加KMP枚举方法即可实现,如下代码则是替换具有KMP功能的搜索模式,在代码中可看出我们仅仅只是将ScanMemorySignatureCode函数内部的memcmp函数替换为了KMPSearchString函数,其他位置并没有任何变化,此处主要增加的函数有GetNextval以及KMPSearchString,这两个函数的核心思想是利用KMP算法,在主字符串中寻找子字符串时,遇到匹配失败的字符时,能够跳过一些已经比较过的字符,重复利用部分匹配的结果,提高字符串匹配的效率。将子串的每个字符失配时应该跳转的位置通过GetNextval函数计算得出,然后在KMPSearchString函数中通过这个数组进行跳转和匹配。该算法的时间复杂度为O(m+n),其中mn分别表示主串和模式串的长度。

#include <iostream>
#include <windows.h>
#include <tlhelp32.h>

using namespace std;

// 根据进程名得到进程PID
DWORD GetPidByName(const char* name)
{
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
    DWORD pid = 0;

    if (Process32First(snapshot, &pe32))
    {
        do
        {
            if (_stricmp(pe32.szExeFile, name) == 0)
            {
                pid = pe32.th32ProcessID;
                break;
            }
        } while (Process32Next(snapshot, &pe32));
    }
    CloseHandle(snapshot);
    return pid;
}

/*
* P 为模式串,下标从 0 开始。
* nextval 数组是模式串 SubString 中每个字符失配时应该回溯的位置。
*/
void GetNextval(string SubString, int nextval[])
{
    int SubStringLen = SubString.size(); // 计算模式串的长度
    int i = 0;                           // 子串的指针
    int j = -1;                          // 前缀的指针
    nextval[0] = -1;                     // 初始化 nextval 数组,将第一个值设为 -1

    while (i < SubStringLen - 1)
    {
        if (j == -1 || SubString[i] == SubString[j]) // 如果子串和前缀相等,或 j==-1
        {
            i++; j++;                                // 子串指针和前缀指针分别加一
            if (SubString[i] != SubString[j])        // 如果下一个字符不相等
            {
                nextval[i] = j;                      // 将前缀指针 j 的值赋给 nextval 数组中的当前位置 i
            }
            else                                     // 如果下一个字符相等
            {
                nextval[i] = nextval[j];             // 已经有 nextval[j],所以将它赋给 nextval[i]
            }
        }
        else                                        // 如果子串和前缀不相等
        {
            j = nextval[j];                        // 更新前缀指针 j 的值,指向 nextval[j]
        }
    }
}

/* 在 MainString 中找到 SubString 第一次出现的位置 下标从0开始*/
int KMPSearchString(string MainString, string SubString, int next[])
{
    GetNextval(SubString, next);

    int MainStringIndex = 0;                 // 存储主字符串下标
    int SubStringIndex = 0;                  // 存储子字符串下标
    int MainStringLen = MainString.size();   // 主字符串大小
    int SubStringLen = SubString.size();     // 子字符串大小

    // 循环遍历字符串,因为末尾 '\0' 的存在,所以不会越界
    while (MainStringIndex < MainStringLen && SubStringIndex < SubStringLen)
    {
        // MainString 的第一个字符不匹配或 MainString[] == SubString[]
        if (SubStringIndex == -1 || MainString[MainStringIndex] == SubString[SubStringIndex])
        {
            MainStringIndex++; SubStringIndex++;
        }
        else   // 当字符串匹配失败则跳转
        {
            SubStringIndex = next[SubStringIndex];
        }
    }
    // 最后匹配成功直接返回位置
    if (SubStringIndex == SubStringLen)
    {
        return MainStringIndex - SubStringIndex;
    }
    return -1;
}

// 内存特征码搜索
ULONG ScanMemorySignatureCode(DWORD Pid, DWORD beginAddr, DWORD endAddr, char *ShellCode, DWORD ShellCodeLen)
{
    char *read = new char[ShellCodeLen];

    // 打开进程
    HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
    int next[100] = { 0 };

    // 开始搜索内存特征
    for (int x = 0; x < endAddr; x++)
    {
        DWORD addr = beginAddr + x;

        // 每次读入ShellCodeLen字节特征
        ReadProcessMemory(process, (LPVOID)addr, read, ShellCodeLen, 0);

        // 在Str字符串中找Search子串,找到后返回位置
        int ret = KMPSearchString(read, ShellCode, next);

        if (ret != -1)
        {
            return addr;
        }
    }
    return 0;
}

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

    // 开始搜索特征码
    char ScanOpCode[3] = { 0x56, 0x57, 0x33 };

    // 依次传入开始地址,结束地址,特征码,以及特征码长度
    ULONG Address = ScanMemorySignatureCode(Pid, 0x401000, 0x7FFFFFFF, ScanOpCode, 3);

    printf("[*] 找到内存地址 = 0x%x \n", Address);

    system("pause");
    return 0;
}

编译并运行上述代码片段,读者应该能看出与暴力枚举并无任何区别,其输出效果图如下图所示;

6.1 KMP算法搜索机器码文章来源地址https://www.toymoban.com/news/detail-709959.html

到了这里,关于6.1 KMP算法搜索机器码的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • KMP超高效匹配算法

            江河入海,知识涌动,这是我参与江海计划的第1篇。         KMP算法是一种改进的字符串匹配算法,其中,KMP算法的运用核心是利用匹配失败后的信息,最大进度的减少模式串与目标串的匹配次数以达到快速匹配的效果。算法与暴力求解的改进在于每当一趟匹配过

    2024年02月10日
    浏览(47)
  • C#,字符串匹配(模式搜索)KMP算法的源代码与数据可视化

      D.E.Knuth   J.H.Morris KMP 算法(Knuth-Morris-Pratt 算法)是其中一个著名的、传统的字符串匹配算法,效率比较高。 KMP算法由 D.E.Knuth , J.H.Morris 和 V.R.Pratt 在 Brute-Force 算法的基础上提出的模式匹配的改进算法。因此人们称它为“克努特—莫里斯—普拉特算法”,简称KMP算法。K

    2024年01月25日
    浏览(51)
  • 【算法 & 高级数据结构】树状数组:一种高效的数据结构(一)

    🚀 个人主页 :为梦而生~ 关注我一起学习吧! 💡 专栏 :算法题、 基础算法~赶紧来学算法吧 💡 往期推荐 : 【算法基础 数学】快速幂求逆元(逆元、扩展欧几里得定理、小费马定理) 【算法基础】深搜 树状数组 (Binary Indexed Tree,BIT)是一种数据结构,用于高效地处理

    2024年03月11日
    浏览(64)
  • 【算法 & 高级数据结构】树状数组:一种高效的数据结构(二)

    🚀 个人主页 :为梦而生~ 关注我一起学习吧! 💡 专栏 :算法题、 基础算法、数据结构~赶紧来学算法吧 💡 往期推荐 : 【算法基础 数学】快速幂求逆元(逆元、扩展欧几里得定理、小费马定理) 【算法基础】深搜 数据结构各内部排序算法总结对比及动图演示(插入排序

    2024年03月26日
    浏览(82)
  • 【STL切片算法文献笔记】一种使用 STL 文件进行高效轮廓构造的改进切片算法

    STL 模型是通过将实体模型曲面细分为三角形而获得的。 镶嵌的精度由弦误差控制,弦误差是镶嵌模型与原始实体模型之间的最大形状差异。 细分实体模型所需的三角形数量是可接受的弦误差和零件几何复杂性的函数。 在 STL 文件创建过程中生成的大量三角形可能会影响对模

    2023年04月09日
    浏览(35)
  • 算法与人生 揭秘C语言中高效搜索的秘诀——二分查找算法详解

    引言,少年们,大家好。在这里祝大家元旦快乐,我是博主 那一脸阳光 ,今天来介绍二分查找 在计算机科学领域,搜索算法是数据处理和问题解决的重要工具之一。其中,**二分查找算法(Binary Search)**以其卓越的时间复杂度和简洁高效的实现,在众多搜索算法中脱颖而出

    2024年01月17日
    浏览(57)
  • 机器学习笔记 - YOLO-NAS 最高效的目标检测算法之一

            YOLO(You Only Look Once)是一种对象检测算法,它使用深度神经网络模型,特别是卷积神经网络,来实时检测和分类对象。该算法首次在 2016 年由 Joseph Redmon、Santosh Divvala、Ross Girshick 和 Ali Farhadi 发表的论文《You Only Look Once: Unified, Real-Time ObjectDetection》中被引入。  

    2024年02月14日
    浏览(45)
  • 机器学习笔记之优化算法(七)线搜索方法(步长角度;非精确搜索;Wolfe Condition)

    上一节介绍了 Glodstein text{Glodstein} Glodstein 准则 ( Glodstein Condition ) (text{Glodstein Condition}) ( Glodstein Condition ) 及其弊端。本节将针对该弊端,介绍 Wolfe text{Wolfe} Wolfe 准则 ( Wolfe Condition ) (text{Wolfe Condition}) ( Wolfe Condition ) 。 Armijo text{Armijo} Armijo 准则及其弊端 在当前迭代步骤

    2024年02月14日
    浏览(45)
  • 集成多元算法,打造高效字面文本相似度计算与匹配搜索解决方案,助力文本匹配冷启动[BM25、词向量、SimHash、Tfidf、SequenceMatcher]

    搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目

    2024年02月05日
    浏览(68)
  • 图解KMP算法,带你彻底吃透KMP

    KMP算法 一直是一个比较难以理解的算法,本篇文章主要根据《大话数据结构》中关于KMP算法的讲解,结合自己的思考,对于KMP算法进行一个比较详细的解释。 由于博主本人水平有限,难免会出现一些错误。如果发现文章中存在错误敬请批评指正,感谢您的阅读。 字符串模式

    2024年02月08日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包