6.3 应用动态内存补丁

2023-09-21 09:12:47

动态内存补丁可以理解为在程序运行时动态地修改程序的内存,在某些时候某些应用程序会带壳运行,而此类程序的机器码只有在内存中被展开时才可以被修改,而想要修改此类应用程序动态补丁将是一个不错的选择,动态补丁的原理是通过CreateProcess函数传递CREATE_SUSPENDED将程序运行起来并暂停,此时程序会在内存中被解码,当程序被解码后我们则可以通过内存读写实现对特定区域的动态补丁。

当读者需要手动拉起一个进程时则可以使用OpenExeFile函数实现,该函数调用后会拉起一个进程,并默认暂停在程序入口处,返回一个PROCESS_INFORMATION结构信息;

// 打开进程并暂停运行
PROCESS_INFORMATION OpenExeFile(char *szFileName)
{
    STARTUPINFO si = { 0 };
    PROCESS_INFORMATION pi = { 0 };

    si.cb = sizeof(STARTUPINFO);
    si.wShowWindow = SW_SHOW;
    si.dwFlags = STARTF_USESHOWWINDOW;

    // 创建子线程并默认暂停
    BOOL bRet = CreateProcess(szFileName, 0, 0, 0, 0, CREATE_SUSPENDED, 0, 0, &si, &pi);
    if (bRet == FALSE)
    {
        exit(0);
    }
    ResumeThread(pi.hThread);
    return pi;
}

其中CreateProcess函数的一般格式:

BOOL WINAPI CreateProcess(
  LPCWSTR lpApplicationName,
  LPWSTR lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL bInheritHandles,
  DWORD dwCreationFlags,
  LPVOID lpEnvironment,
  LPCWSTR lpCurrentDirectory,
  LPSTARTUPINFOW lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

下面是函数的参数说明:

  • lpApplicationName:指向一个空字符结束的字符串,指定将要执行的可执行文件的名称。如果lpApplicationNameNULL,那么应该将可执行文件的名称包含在lpCommandLine所指向的字符串中。
  • lpCommandLine:指向一个空字符结束的字符串,该字符串包含了要执行的命令行和参数,用于指定要运行的可执行文件和要传递给该进程的命令行参数。
  • lpProcessAttributes:指向PROCESS_ATTRIBUTES结构体,用于指定新进程的安全描述符。
  • lpThreadAttributes:指向THREAD_ATTRIBUTES结构体,用于指定新进程的主线程的安全描述符。
  • bInheritHandles:一个布尔值,指定新进程是否继承了它的父进程的句柄。
  • dwCreationFlags:指定新进程的创建标志。一般情况下会指定为 0。
  • lpEnvironment:指向一个环境块,用于指定新进程的环境块。如果为NULL,则新进程将继承调用进程的环境块。
  • lpCurrentDirectory:指向一个空字符结束的字符串,该字符串指定新进程的当前工作目录。如果为NULL,则新进程将继承父进程的当前工作目录。
  • lpStartupInfo:指向STARTUPINFO结构体,该结构体指定了新进程的主窗口外观。
  • lpProcessInformation:指向PROCESS_INFORMATION结构体,该结构体返回了新进程的信息,例如新进程的进程标识符和主线程标识符等。

CreateProcess 函数返回一个布尔值,表示函数的调用是否成功。如果成功,则返回值为非零,否则返回值为零,并通过调用GetLastError函数获取错误代码。为了使得新进程与父进程独立运行,一般需要用到独立的进程空间和线程,这通常需要在创建新进程之前调用一些Windows系统API函数,如VirtualAlloc、CreateThread等。

接着来看封装过的三个内存读写函数,其中ReadMemory()用于读取进程内存数据,WriteMemory()用于写入内存数据,CheckMemory()则用于验证两个内存空间内的字节是否匹配。

// 读取指定的内存地址
BYTE * ReadMemory(PROCESS_INFORMATION pi, DWORD dwVAddress, int Size)
{
    BYTE bCode = 0;
    BYTE *buffer = new BYTE[Size];

    for (int x = 0; x < Size; x++)
    {
        ReadProcessMemory(pi.hProcess, (LPVOID)dwVAddress, (LPVOID)&bCode, sizeof(BYTE), 0);
        buffer[x] = bCode;
        dwVAddress++;
    }
    return buffer;
}

// 写入内存特征
BOOL WriteMemory(PROCESS_INFORMATION pi, DWORD dwVAddress, unsigned char *ShellCode, int Size)
{
    BYTE *Buff = new BYTE[Size];

    SuspendThread(pi.hThread);
    memset(Buff, *ShellCode, Size);
    VirtualProtectEx(pi.hProcess, (LPVOID)dwVAddress, Size, 0x40, 0);
    BOOL Ret = WriteProcessMemory(pi.hProcess, (LPVOID)dwVAddress, Buff, Size, 0);
    if (Ret != 0)
    {
        ResumeThread(pi.hThread);
        return TRUE;
    }
    return FALSE;
}

// 比较内存中前几个字节是否一致
BOOL CheckMemory(PROCESS_INFORMATION pi, DWORD dwVAddress, BYTE OldCode[], int Size)
{
    BYTE *Buff = new BYTE[Size];
    ReadProcessMemory(pi.hProcess, (LPVOID)dwVAddress, Buff, Size, 0);

    if (!memcmp(Buff, OldCode, Size))
    {
        /*
        for (int x = 0; x < Size; x++)
        {
            printf("内存地址: %x --> 对比地址: %x \n", Buff[x], OldCode[x]);
        }
        */
        return TRUE;
    }
    return FALSE;
}

接下来我们将通过使用特征码定位技术来实现对特定内存区域的定位并实现特征替换,首先我们搜索0x85, 0xed, 0x57, 0x74, 0x07这段特征值,并定位到0x0402507内存区域,如下图所示;

当定位到内存区域后,我们首先通过ReadMemory读取前五个字节的内存,并调用CheckMemory函数用于验证此片内存区域是否时我们需要修改的,如果验证一致则通过调用WriteMemory函数向该内存中写出替换一段0x90, 0x90, 0x90, 0x90, 0x90的指令,最后通过调用ResumeThread恢复线程运行,并以此实现动态内存补丁;

int main(int argc, char *argv[])
{
    // 动态加载进程
    PROCESS_INFORMATION pi = OpenExeFile("d://lyshark.exe");

    // 开始搜索特征码
    char ScanOpCode[5] = { 0x85, 0xed, 0x57, 0x74, 0x07 };

    // 依次传入开始地址,结束地址,特征码,以及特征码长度
    ULONG Address = ScanMemorySignatureCode(pi.dwProcessId, 0x401000, 0x47FFFF, ScanOpCode, 5);
    printf("[*] 找到内存地址 = 0x%x \n", Address);

    // 读取位于Address地址处的5条机器指令
    BYTE *recv_buffer = ReadMemory(pi, Address, 5);
    for (int x = 0; x < 5; x++)
    {
        printf("%x ", recv_buffer[x]);
    }
    printf("\n");

    // 比较Address内存中前5个字节是否一致
    BYTE cmp_code[] = { 0x85, 0xed, 0x57, 0x74, 0x07 };
    BOOL ret = CheckMemory(pi, Address, cmp_code, 5);
    if (ret == TRUE)
    {
        printf("[*] 内存一致,可以进行打补丁 \n");
    }
    else
    {
        printf("[-] 不一致 \n");
    }

    // 写入修补文件
    unsigned char set_buffer[] = { 0x90, 0x90, 0x90, 0x90, 0x90 };
    WriteMemory(pi, Address, set_buffer, 5);

    // 运行修补后的程序
    ResumeThread(pi.hThread);
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);

    system("pause");
    return 0;
}

当调用成功后,读者可自行跳转到0x0402507处的内存区域,观察替换效果,当替换成功后,其内存输出效果如下图所示;

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

更多推荐

Java 基础学习总结(207)—— 具有革命性、未来性、开创新纪元的 JDK 21, 它来了

具有革命性、未来性、开创新纪元的JDK21按照官方的时间计划表,今天终于是要正式GA了:https://openjdk.org/projects/jdk/21/GA,就是我上面框起来的“GeneralAvailability”的缩写,直译成中文,虽然是“普通可用”的意思,但是在软件行业,它就代表正式版。如果对外发布一个

慢SQL治理经验总结

在过去两年的工作中,我们团队曾负责大淘宝技术的慢SQL治理工作,作为横向的数据安全治理平台,如何快速准确地发现部门内所有应用的慢SQL,并进行高效的推动治理,同时覆盖多个开发、生产环境,是一个很大的挑战。以下是一些经验分享,我们通过持续的慢SQL推动治理,有效降低了DB相关的线上问题,极大提高了系统稳定性。关于慢SQL

MVVM 模式、Vue 双向绑定原理

MVVM模式是什么传统的MVC指的是,用户操作会请求服务端路由,路由会调用对应的控制器来处理,控制器会获取数据。将结果返回给前端,页面重新渲染MVVM(Model-View-ViewModel)是一种软件架构模式,用于实现界面(UI)和业务逻辑的分离。他的设计目标是将界面的开发与后端的业务逻辑分离,使得代码易于理解、维

为什么要选择Spring cloud Sentinel

为什么要选择SpringcloudSentinel🍎对比Hystrix🍂雪崩问题及解决方案🍂雪崩问题🍂.超时处理🍂仓壁模式🍂断路器🍂限流🍂总结🍎对比Hystrix在SpringCloud当中支持多种服务保护技术:NetfixHystrixSentinelResilience4J早期比较流行的是Hyst

线性回归网络

李沐大神的《动手学深度学习》,是我入门机器学习的首课,因此在这里记录一下学习的过程。线性回归的从零开始实现线性回归是理解机器学习的基础,它经常用来表示输入和输出之间的关系。线性回归基于几个简单的假设:首先,假设自变量X和因变量y之间的关系是线性的,即y可以表示为X中元素的加权和,这里通常允许包含观测值的一些噪声。下面基

行业报告:视频直播美颜sdk对互联网直播产业的影响与前景

随着互联网直播产业的不断崛起,直播内容的质量和用户体验已成为成功的关键因素之一。本篇报告将深入研究视频直播美颜sdk对互联网直播产业的影响,并探讨其未来的前景。第一章:视频直播美颜sdk的基本概念1.1什么是视频直播美颜SDK?视频直播美颜sdk是一种软件工具包,旨在为互联网直播平台和应用程序提供实时的美颜和图像增强功

笔试面试相关记录(4)

(1)实现防火墙的主流技术有哪些?实施防火墙主要采用哪些技术-服务器-亿速云(yisu.com)(2)chararr[][2]={'a','b','c','d'};printf("%d",*(arr+1));输出的是谁的地址?字符c测试代码如下chararr[][2]={'a','b','c','d'};printf(

java 工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发

Java版工程项目管理系统SpringCloud+SpringBoot+Mybatis+Vue+ElementUI+前后端分离功能清单如下:首页工作台:待办工作、消息通知、预警信息,点击可进入相应的列表项目进度图表:选择(总体或单个)项目显示1、项目进度图表2、项目信息施工地图:1、展示当前角色权限下能看到的施工地图(

pom的配置策略

dependencyManagement和dependencies区别和联系参考:https://blog.csdn.net/Sunshineoe/article/details/121083505<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://mav

剔除数据中的异常值(python实现)

目录一、3σ原则二、箱线图发现异常值三、boxcox数据变换一、3σ原则该准则仅局限于对正态或近似正态分布的样本数据处理,此外,当测量次数少的情形用准则剔除粗大误差是不够可靠的。异常值是指样本中的个别值,其数值明显偏离其余的观测值。异常值也称离群点,异常值的分析也称为离群点的分析。在进行机器学习过程中,需要对数据集进行

Python进阶学习----一闭三器

目录​编辑前言一.三器1.迭代器(Iterator)1.1什么是可迭代对象1.2什么是迭代器1.3案例演示:以下是一个简单的迭代器示例,遍历一个列表并打印每个元素:1.4迭代器总结2.生成器(Generator)3.装饰器(Decorator)二.一闭4.闭包(Closure)总结:前言Python是一种功能强大而灵活

热文推荐