7.7 实现进程内存读写

实现,进程,内存,读写 · 浏览次数 : 319

小编点评

## 文本摘要生成算法 **步骤 1: 读取内容** * 首先需要读取文本内容,并将其存储在内存中。 * 也可以使用 `ReadMemory` 读取字节数组,并将其存储在内存中。 **步骤 2: 计算特征码** * 根据文本内容,计算特征码。 * 特征码可以是字节数组,也可以是字符串。 **步骤 3: 搜索特征码** * 使用 `FindPattern` 函数搜索特征码。 * `FindPattern` 函数可以接受三个参数: * 文本处理器 * 模式 * 掩码 * 掩码可以是字节数组,也可以是字符串。 **步骤 4: 返回结果** * 在搜索完特征码后,返回结果。 * 结果可以是内存地址,也可以是字符串。 **示例代码:** ```python # 读取文本内容 text = ReadMemory("my_text_file.txt", 1024) # 计算特征码 feature_code = CalculateFeatureCode(text) # 搜索特征码 result = FindPattern(text, feature_code, "?? ?? ??") # 返回结果 print(result) ``` **结果:** ``` param = 0x7ab1f162 ``` **说明:** * 此代码仅展示了搜索特征码的例子,实际应用中可能需要根据具体情况进行修改。 * 代码中使用了 `ReadMemory` 读取文本内容,你可以根据实际情况使用不同的读取方法。 * 代码中使用了 `FindPattern` 函数搜索特征码,你可以根据实际情况使用不同的搜索方法。

正文

内存进程读写可以让我们访问其他进程的内存空间并读取或修改其中的数据。这种技术通常用于各种调试工具、进程监控工具和反作弊系统等场景。在Windows系统中,内存进程读写可以通过一些API函数来实现,如OpenProcessReadProcessMemoryWriteProcessMemory等。这些函数提供了一种通用的方式来访问其他进程的内存,并且可以用来读取或写入不同类型的数据,例如整数、字节集、浮点数等。

在开始编写内存读者功能之前我们先来实现一个获取特定进程内特定模块基址的功能,该功能的实现分为两部分首先我们封装一个GetProcessModuleHandle函数,该函数用户可传入一个进程PID以及需要获取的进程内的模块名,此时会通过循环的方式找到所需返回的模块并返回该模块的moduleEntry.hModule基址,由于使用了进程快照函数所以在使用时需要引入TlHelp32.h库。

// 根据PID模块名(需要写后缀.dll)获取模块入口地址
HMODULE GetProcessModuleHandle(DWORD pid, CONST TCHAR* moduleName)
{
  MODULEENTRY32 moduleEntry;
  HANDLE handle = NULL;

  // 获取进程快照中包含在th32ProcessID中指定的进程的所有的模块
  handle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
  if (!handle)
  {
    CloseHandle(handle);
    return NULL;
  }
  ZeroMemory(&moduleEntry, sizeof(MODULEENTRY32));
  moduleEntry.dwSize = sizeof(MODULEENTRY32);
  if (!Module32First(handle, &moduleEntry))
  {
    CloseHandle(handle);
    return NULL;
  }
  do
  {
    if (_tcscmp(moduleEntry.szModule, moduleName) == 0)
    {
      return moduleEntry.hModule;
    }
  } while (Module32Next(handle, &moduleEntry));

  CloseHandle(handle);
  return 0;
}

有了上述读取模块基址的函数,接着就是要封装实现GetProcessModuleBase函数,该函数接收两个参数,分别是进程名以及模块名,并返回该模块在指定进程中的句柄。如果指定的模块名称不存在于所给进程的模块列表中,函数会返回NULL。

// 返回ExeName进程中指定Dll的基址
DWORD GetProcessModuleBase(string ExeName, string DllName)
{
  HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);  // 进程快照句柄
  PROCESSENTRY32 process = { sizeof(PROCESSENTRY32) };                    // 存放进程快照的结构体

  //  遍历进程
  while (Process32Next(hProcessSnap, &process))
  {
    // 寻找ExeName指定进程 char* 转 string
    string s_szExeFile = process.szExeFile;
    if (s_szExeFile == ExeName)
    {
      HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process.th32ProcessID);
      if (hProcess != NULL)
      {
        // 寻找DllName并返回DWORD类型基址
        return (DWORD)GetProcessModuleHandle(process.th32ProcessID, DllName.c_str());
      }
    }
  }
  return 0;
}

参数说明:

  • hProcess:指定进程的句柄,通常可以通过OpenProcess函数获取。
  • lpModuleName:要获取的模块名称,可以是一个字符串形式的模块名称或者指向模块名称字符串的指针。

当有了上述两个模块的支持那么实现进程模块基址的读取将变得非常容易实现,如下是一段读取模块句柄的代码示例,在代码中我们分别读取了Tutorial-i386.exe自身模块基地址,以及该进程内user32.dll模块基址,需要注意的是运行该程序需要使用管理员身份。

int main(int argc, char *argv[])
{
  // "Tutorial-i386.exe"+256650
  DWORD DllBase = GetProcessModuleBase("Tutorial-i386.exe", "Tutorial-i386.exe");
  if (DllBase != 0)
  {
    std::cout << "模块基地址: " << hex << DllBase + 0x256650 << std::endl;
  }

  DWORD User32 = GetProcessModuleBase("Tutorial-i386.exe", "user32.dll");
  if (User32 != 0)
  {
    std::cout << "模块基地址: " << hex << User32 << std::endl;
  }

  system("pause");
  return 0;
}

运行上述代码片段,读者可看到如下输出结果,代码中分别读取了一个进程基址,与系统模块基址。

接着我们讲解一下内存读写的实现方法,此处的读写分为32位与64位实现,在32位进程读写时可以使用微软提供的ReadProcessMemory读及WriteProcessMemory写入,这两个函数在参数传递上并没有太大的差异。

ReadProcessMemory 函数用于从指定进程中读取指定内存地址的数据,写入一个缓冲区中。函数接受的参数包括要读取的进程句柄,要读取的内存地址,要读取的数据大小等。如果读取成功,函数会返回非零值。

BOOL WINAPI ReadProcessMemory(
  HANDLE  hProcess,
  LPCVOID lpBaseAddress,
  LPVOID  lpBuffer,
  SIZE_T  nSize,
  SIZE_T  *lpNumberOfBytesRead
);

WriteProcessMemory 函数用于向指定进程中写入数据,写入一个缓冲区中的数据到另一个进程指定的内存地址中。函数接受的参数包括要写入的进程句柄,要写入的内存地址,要写入的数据大小等。如果写入成功,函数会返回非零值。

BOOL WINAPI WriteProcessMemory(
  HANDLE  hProcess,
  LPVOID  lpBaseAddress,
  LPCVOID lpBuffer,
  SIZE_T  nSize,
  SIZE_T  *lpNumberOfBytesWritten
);

在使用 ReadProcessMemory()WriteProcessMemory() 函数时,需要以管理员身份运行程序。此外,为了访问其他进程的内存,还需要指定合适的访问权限,并且需要根据具体情况指定正确的内存地址和数据类型。

BOOL ReadMemory(DWORD dwID, LPCVOID lpAddress, DWORD* pRead)
{
  HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwID);
  if (INVALID_HANDLE_VALUE == hProcess)
  {
    return FALSE;
  }
  DWORD dwTemp = 0;
  ReadProcessMemory(hProcess, lpAddress, (LPVOID)(pRead), 4, &dwTemp);
  if (4 != dwTemp)
  {
    return FALSE;
  }
  return TRUE;
}

BOOL WriteMemory(DWORD dwID, LPVOID lpAddress, DWORD dwWrite)
{
  HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwID);
  if (INVALID_HANDLE_VALUE == hProcess)
  {
    return FALSE;
  }
  DWORD dwTemp = 0;
  WriteProcessMemory(hProcess, lpAddress, (LPVOID)(&dwWrite), 4, &dwTemp);
  if (4 != dwTemp)
  {
    return FALSE;
  }
  return TRUE;
}

// 读取指定位置内存数据
DWORD Read(DWORD dwPID, std::string strAddress)
{
  // 字符串转为16进制整数指针
  LPCVOID lpAddress = (LPCVOID)::strtol(strAddress.c_str(), NULL, 16);
  DWORD dwRead = 0;

  // 调用内存读取
  BOOL ref = ReadMemory(dwPID, lpAddress, &dwRead);
  if (ref == TRUE)
  {
    return dwRead;
  }
  return FALSE;
}

// 写入指定位置内存数据
BOOL Write(DWORD dwPID, std::string strAddress, std::string write_value)
{
  LPVOID lpAddress = (LPVOID)::strtol(strAddress.c_str(), NULL, 16);
  DWORD dwWrite = ::strtol(write_value.c_str(), NULL, 16);
  BOOL bRet = WriteMemory(dwPID, lpAddress, dwWrite);
  if (bRet == TRUE)
  {
    return TRUE;
  }
  return FALSE;
}

而如果读者使用的是64位环境,那么内存读写则需要借助于NtWow64ReadVirtualMemory64以及NtWow64WriteVirtualMemory64这两个未公开函数实现。

NtWow64ReadVirtualMemory64NtWow64WriteVirtualMemory64 函数用于在 64Windows 系统中的 32 位进程中读写虚拟内存。这两个函数通常不直接由应用程序调用,而是由系统的函数库和其他底层代码使用。

NtWow64ReadVirtualMemory64 函数的原型为:

NTSTATUS NTAPI NtWow64ReadVirtualMemory64(HANDLE ProcessHandle,
    PVOID64 BaseAddress,
    PVOID Buffer,
    ULONG64 Size,
    PULONG64 NumberOfBytesRead);

该函数接受五个参数:

  • ProcessHandle: 进程的句柄。用于指定要读取内存的进程。
  • BaseAddress: 要读取的起始地址。
  • Buffer: 读取的数据存储在这个缓冲区中。
  • Size: 要读取的字节数量。
  • NumberOfBytesRead: 实际读取的字节数量。

NtWow64WriteVirtualMemory64 函数的原型为:

NTSTATUS NTAPI NtWow64WriteVirtualMemory64(HANDLE ProcessHandle,
    PVOID64 BaseAddress,
    PVOID Buffer,
    ULONG64 Size,
    PULONG64 NumberOfBytesWritten);

该函数同样接受五个参数:

  • ProcessHandle: 进程的句柄。指定要写入内存的进程。
  • BaseAddress: 要写入的起始地址。
  • Buffer: 要写入的数据存储在这个缓冲区中。
  • Size: 要写入的字节数量。
  • NumberOfBytesWritten: 实际写入的字节数量。

上述这两个函数都位于ntdll.dll库中,在使用时需要通过LoadLibrary函数获取到该动态链接库的模块句柄,并在该内存中使用GetProcAddress函数动态得到上述两个函数的基地址,有了基址就可以使用函数指针的方式动态的引用内存读写功能,如下则是一个完整的读写使用案例。

BOOL ReadMemory(DWORD dwID, PVOID64 lpAddress, DWORD* pRead)
{
  // 打开进程并返回句柄
  HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwID);
  if (INVALID_HANDLE_VALUE == hProcess)
  {
    return FALSE;
  }

  typedef void* POINTER_64 PVOID64;
  // 定义函数指针
  typedef NTSTATUS(__stdcall* NTWOW64READVIRTUALMEMORY64)(HANDLE ProcessHandle, PVOID64  BaseAddress, PVOID Buffer, DWORD64 BufferSize, PDWORD64 NumberOfBytesRead);
  NTWOW64READVIRTUALMEMORY64 pNtWow64ReadVirtualMemory64 = NULL;

  // 加载模块基址
  HMODULE hModule = ::LoadLibrary("ntdll.dll");
  if (NULL == hModule)
  {
    return FALSE;
  }

  // 通过GetProcAddress函数获取NtWow64ReadVirtualMemory64入口地址
  pNtWow64ReadVirtualMemory64 = (NTWOW64READVIRTUALMEMORY64)::GetProcAddress(hModule, "NtWow64ReadVirtualMemory64");
  if (NULL == pNtWow64ReadVirtualMemory64)
  {
    return FALSE;
  }

  DWORD64 dwTemp = (DWORD64)0;

  // 调用64位内存读取函数
  pNtWow64ReadVirtualMemory64(hProcess, (PVOID64)lpAddress, (LPVOID)(pRead), 4, &dwTemp);
  FreeLibrary(hModule);
  if (4 != dwTemp)
  {
    return FALSE;
  }
  return TRUE;
}

BOOL WriteMemory(DWORD dwID, PVOID64 lpAddress, DWORD* pWrite)
{
  // 打开进程并返回句柄
  HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwID);
  if (INVALID_HANDLE_VALUE == hProcess)
  {
    return FALSE;
  }

  typedef void* POINTER_64 PVOID64;
  // 定义函数指针
  typedef NTSTATUS(__stdcall* NTWOW64WRITEVIRTUALMEMORY64)(HANDLE ProcessHandle, PVOID64  BaseAddress, PVOID Buffer, DWORD64 BufferSize, PDWORD64 NumberOfBytesRead);
  NTWOW64WRITEVIRTUALMEMORY64 pNtWow64WriteVirtualMemory64 = NULL;

  // 加载模块基址
  HMODULE hModule = ::LoadLibrary("ntdll.dll");
  if (NULL == hModule)
  {
    return FALSE;
  }

  // 通过GetProcAddress函数获取NtWow64WriteVirtualMemory64入口地址
  pNtWow64WriteVirtualMemory64 = (NTWOW64WRITEVIRTUALMEMORY64)::GetProcAddress(hModule, "NtWow64WriteVirtualMemory64");
  if (NULL == pNtWow64WriteVirtualMemory64)
  {
    return FALSE;
  }

  DWORD64 dwTemp = (DWORD64)0;

  // 调用内存写入
  pNtWow64WriteVirtualMemory64(hProcess, (PVOID64)lpAddress, (pWrite), 4, &dwTemp);
  ::FreeLibrary(hModule);
  if (4 != dwTemp)
  {
    return FALSE;
  }
  return TRUE;
}

// 执行内存读取
DWORD Read(DWORD dwPID, std::string strAddress)
{
  // LPCVOID lpAddress = (LPCVOID)::strtol(m_strAddress, NULL, 16);
  typedef void* POINTER_64 PVOID64;
  PVOID64 lpAddress = (PVOID64)::_strtoi64_l(strAddress.c_str(), NULL, 16, NULL);

  DWORD dwRead = 0;
  
  BOOL ref = ReadMemory(dwPID, lpAddress, &dwRead);

  if (ref == TRUE)
  {
    return dwRead;
  }
  return FALSE;
}

// 执行写入命令
BOOL Write(DWORD dwPID, std::string strAddress, DWORD write_value)
{
  typedef void* POINTER_64 PVOID64;
  PVOID64 lpAddress = (PVOID64)::_strtoi64_l(strAddress.c_str(), NULL, 16, NULL);
  BOOL bRet = WriteMemory(dwPID, lpAddress, &write_value);
  if (bRet)
  {
    return TRUE;
  }
  return FALSE;
}

上述代码的使用非常容易,调用内存写入时只需要传入进程PID,写入的内存地址,和写入的数据长度即可,如下所示则是实现调用的案例。

int main(int argc, char *argv[])
{
  DWORD Pid = 13564;

  // 执行内存写入
  BOOL write_flag = Write(Pid, "0x00151F38", "9999");
  printf("[内存写入] 状态 = %d \n", write_flag);

  // 执行内存读取
  DWORD read_byte = Read(Pid, "0x00151F38");
  printf("[内存读取] 数据 = %d \n", read_byte);

  system("pause");
  return 0;
}

内存读写输出效果如下图所示;

我们以32位为例对上述函数进行整合封装,实现一个通用的内存读写,通过使用template模板机制封装ReadMemory内存读取,WriteMemory内存写入,这些函数在调用时支持读写,内存整数型,短整数,浮点数,字节,字节集等,同时还封装实现FindPattern函数用于实现对特定内存的特征匹配,读者在编写进程读写时可以直接使用这些函数案例,完整代码如下所示;

#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>

// 打开进程
HANDLE GetProcessHandle(DWORD pid)
{
  return OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
}

// 获取进程PID
DWORD GetProcessIdByHwnd(DWORD hWnd)
{
  DWORD pid;
  GetWindowThreadProcessId((HWND)hWnd, &pid);
  return pid;
}

// 取进程PID
DWORD GetProcessIdByName(LPCTSTR name)
{
  HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (INVALID_HANDLE_VALUE == hSnapshot)
  {
    return NULL;
  }
  PROCESSENTRY32 pe = { sizeof(pe) };

  for (BOOL ret = Process32First(hSnapshot, &pe); ret; ret = Process32Next(hSnapshot, &pe))
  {
    if (lstrcmpi(pe.szExeFile, name) == 0)
    {
      CloseHandle(hSnapshot);
      return pe.th32ProcessID;
    }
  }
  CloseHandle(hSnapshot);
  return 0;
}

// 取指定模块句柄
DWORD GetProcessModuleHandle(DWORD pid, CONST TCHAR* moduleName)
{
  MODULEENTRY32 moduleEntry;
  HANDLE handle = NULL;
  handle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
  if (!handle)
  {
    CloseHandle(handle);
    return NULL;
  }

  ZeroMemory(&moduleEntry, sizeof(MODULEENTRY32));
  moduleEntry.dwSize = sizeof(MODULEENTRY32);
  if (!Module32First(handle, &moduleEntry))
  {
    CloseHandle(handle);
    return NULL;
  }

  do
  {
    if (lstrcmpi(moduleEntry.szModule, moduleName) == 0)
    {
      return (DWORD)moduleEntry.hModule;
    }
  } while (Module32Next(handle, &moduleEntry));
  CloseHandle(handle);
  return 0;
}

// 读取类型: 整数型 短整数型 浮点型 字节型
template<typename T1>
T1 ReadMemory(HANDLE processHandle, DWORD pAddr)
{
  T1 result;
  ReadProcessMemory(processHandle, (LPCVOID)(pAddr), &result, sizeof(T1), NULL);
  return result;
}

// 读取类型: 自定义大小内存
template<typename T1>
T1* ReadMemory(HANDLE processHandle, DWORD pAddr, DWORD length)
{
  T1* result = new T1[length];
  ReadProcessMemory(processHandle, (LPCVOID)(pAddr), result, length, NULL);
  return result;
}

// 写类型: 整数型 短整数型 浮点型 字节型
template<typename T1>
void WriteMemory(HANDLE processHandle, DWORD pAddr, T1 vaule)
{
  WriteProcessMemory(processHandle, (LPVOID)(pAddr), &vaule, sizeof(T1), NULL);
}

// 写类型: 字节集
template<typename T1>
void WriteMemory(HANDLE processHandle, DWORD pAddr, T1 vaule, DWORD length)
{
  WriteProcessMemory(processHandle, (LPVOID)(pAddr), vaule, length, NULL);
}

// 分配内存
DWORD AllocMemory(HANDLE processHandle, DWORD size)
{
  return (DWORD)VirtualAllocEx(processHandle, NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
}

// 释放内存
BOOL FreeMemory(HANDLE processHandle, DWORD addr, DWORD size)
{
  return VirtualFreeEx(processHandle, (LPVOID)addr, size, MEM_RELEASE);
}

// 特征查找
uintptr_t FindPattern(HANDLE processHandle, uintptr_t start, uintptr_t length, const unsigned char* pattern, const char* mask)
{
  size_t pos = 0;
  auto maskLength = strlen(mask) - 1;

  auto startAdress = start;
  for (auto it = startAdress; it < startAdress + length; ++it)
  {
    if (ReadMemory<unsigned char>(processHandle, (DWORD)it) == pattern[pos] || mask[pos] == '?')
    {
      if (mask[pos + 1] == '\0')
      {
        return it - maskLength;
      }
      pos++;
    }
    else
    {
      pos = 0;
    }
  }
  return 0;
}

当我们需要读写整数或浮点数时只需要在调用特定函数时传入模板即可,我们以读取浮点数为例,在调用ReadMemory函数时传入<FLOAT>则代表参数传递采用浮点数模式,同理读取整数时同样可以使用<DWORD>模板,如下代码则是实现读写整数与浮点数的案例演示。

int main(int argc, char *argv[])
{
  DWORD Pid = GetProcessIdByName(L"Tutorial-i386.exe");
  printf("[+] 进程PID = %d \n", Pid);

  HANDLE handle = GetProcessHandle(Pid);
  printf("[+] 进程句柄 = %X \n", handle);

  // -----------------------------------------------------------
  // 进程写内存
  // -----------------------------------------------------------

  // 写整数
  WriteMemory<DWORD>(handle, 0x019C7A18, (DWORD)1000);

  // 写浮点数
  WriteMemory<FLOAT>(handle, 0x019CD0E8, (FLOAT)100.234);

  // -----------------------------------------------------------
  // 进程读内存
  // -----------------------------------------------------------

  // 读整数
  DWORD read_dword = ReadMemory<DWORD>(handle, 0x019C7A18);
  printf("[*] 读内存整数型 = %d \n", read_dword);

  // 读浮点数
  FLOAT read_float = ReadMemory<FLOAT>(handle, 0x019CD0E8);
  printf("[*] 读内存浮点数 = %f \n", read_float);

  system("pause");
  return 0;
}

上述代码运行后,首先会调用写入函数对内存0x19C7A18写入1000的整数,并对0x19CD0E8写入100.234的浮点数,接着会再调用ReadMemory将这两个数读取并输出到屏幕,如下图所示;

接着我们继续实现读写内存字节集的功能,字节集的读写其原理是通过循环的方式读写字节,每次循环时内存地址递增1,并循环将列表内的参数一次性写出到进程中,在写入字节集之前需要确保该内存空间具有PAGE_EXECUTE_READWRITE读写执行属性,如果不存在则还需要调用VirtualProtectEx设置属性,如下所示是读写字节集的完整代码;

int main(int argc, char *argv[])
{
  DWORD Pid = GetProcessIdByName(L"Tutorial-i386.exe");
  printf("[+] 进程PID = %d \n", Pid);

  HANDLE handle = GetProcessHandle(Pid);
  printf("[+] 进程句柄 = %X \n", handle);

  // -----------------------------------------------------------
  // 进程写字节集
  // -----------------------------------------------------------

  DWORD addr = 0x57E000;
  DWORD length = 10;

  BYTE code[10] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };

  DWORD old_protect;
  if (VirtualProtectEx(handle, (LPVOID)addr, length, PAGE_EXECUTE_READWRITE, &old_protect))
  {
    BYTE* temp = (BYTE*)addr;
    for (int i = 0; i < length; i++)
    {
      WriteMemory<BYTE>(handle, DWORD(temp + i), code[i]);

    }
  }
  VirtualProtectEx(handle, (LPVOID)addr, length, old_protect, NULL);

  // -----------------------------------------------------------
  // 进程读字节与字节集
  // -----------------------------------------------------------

  // 测试读字节
  BYTE read_byte = ReadMemory<BYTE>(handle, 0x57E000);
  printf("[byte] 读内存字节 = %02X \n", read_byte);

  // 测试读字节集
  BYTE** read_byte_ptr = ReadMemory<BYTE *>(handle, 0x57E000, 10);

  for (int x = 0; x < 10; x++)
  {
    printf("[bytes] 读[%d]字节集 = %02X \n", x, read_byte_ptr[x]);
  }

  system("pause");
  return 0;
}

当读者运行上述代码后,会调用WriteMemory<BYTE>向内存0x57E000写出一段code字节集,接着再次调用ReadMemory<BYTE *>读取字节集并打印输出,如下图所示;

特征码搜索功能可以使用FindPattern函数,该函数接收匹配进程的句柄,以及内存开始位置及结束位置,变量find_code则是所需搜索的字节集列表,mask代表字节集掩码,此处的掩码必须要与字节集列表保持一致,当搜索到特征码之后会返回当前的内存地址,放入param变量内,如下代码;

int main(int argc, char *argv[])
{
  DWORD Pid = GetProcessIdByName(L"Tutorial-i386.exe");
  printf("[+] 进程PID = %d \n", Pid);

  HANDLE handle = GetProcessHandle(Pid);
  printf("[+] 进程句柄 = %X \n", handle);

  DWORD module_base = GetProcessModuleHandle(Pid, L"Tutorial-i386.exe");
  printf("[*] 模块句柄 = %X \n", module_base);

  // -----------------------------------------------------------
  // 内存特征匹配
  // -----------------------------------------------------------

  // 读取DOS头
  IMAGE_DOS_HEADER DOSHeader = ReadMemory<IMAGE_DOS_HEADER>(handle, (DWORD)module_base);
  printf("[+] DOS Header = %X \n", DOSHeader);

  // 得到NT头
  IMAGE_NT_HEADERS NTHeaders = ReadMemory<IMAGE_NT_HEADERS>(handle, DWORD(uintptr_t(module_base) + DOSHeader.e_lfanew));
  printf("[+] NT Header = %X \n", NTHeaders);

  // 搜索字节集
  BYTE find_code[10] = { 0xF0, 0x8B, 0x46, 0x08, 0xE8, 0xCF, 0xD8, 0xFF, 0xFF, 0x8B };

  // 搜索掩码
  const char* mask = "?? ?? ?? ?? ?? ?? ?? ?? ?? ??";

  // 搜索特征码并返回指针
  uintptr_t param = FindPattern(handle,
    reinterpret_cast<uintptr_t>(handle)+NTHeaders.OptionalHeader.BaseOfCode,
    reinterpret_cast<uintptr_t>(handle)+NTHeaders.OptionalHeader.SizeOfCode,
    find_code,
    mask
    );

  printf("param = 0x%x \n", param);

  system("pause");
  return 0;
}

运行后即可搜索到PE文件内,符合条件的内存机器码,输出效果如下图所示;

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

与7.7 实现进程内存读写相似的内容:

7.7 实现进程内存读写

内存进程读写可以让我们访问其他进程的内存空间并读取或修改其中的数据。这种技术通常用于各种调试工具、进程监控工具和反作弊系统等场景。在`Windows`系统中,内存进程读写可以通过一些`API`函数来实现,如`OpenProcess`、`ReadProcessMemory`和`WriteProcessMemory`等。这些函数提供了一种通用的方式来访问其他进程的内存,并且可以用来读取或写入不同类型的

7.1 实现进程内存块枚举

在`Windows`操作系统中,每个进程的虚拟地址空间都被划分为若干内存块,每个内存块都具有一些属性,如内存大小、保护模式、类型等。这些属性可以通过`VirtualQueryEx`函数查询得到。该函数可用于查询进程虚拟地址空间中的内存信息的函数。它的作用类似于`Windows`操作系统中的`Task...

10.3 调试事件转存进程内存

我们继续延申调试事件的话题,实现进程转存功能,进程转储功能是指通过调试API使获得了目标进程控制权的进程,将目标进程的内存中的数据完整地转存到本地磁盘上,对于加壳软件,通常会通过加密、压缩等手段来保护其代码和数据,使其不易被分析。在这种情况下,通过进程转储功能,可以将加壳程序的内存镜像完整地保存到本...

7.3 通过API枚举进程

首先实现枚举当前系统中所有进程信息,枚举该进程的核心点在于使用`CreateToolhelp32Snapshot()`函数,该函数用于创建系统进程和线程快照,它可以捕获当前系统中进程和线程相关的信息(如PID、线程数量、线程ID等),在对这些信息进行处理后,可以获得很多有用的数据,如当前系统中所有正在执行的进程的信息列表,以及每个进程各自的详细信息(如CPU、内存占用量等)。

7.5 通过API判断进程状态

进程状态的判断包括验证进程是否存在,实现方法是通过枚举系统内的所有进程信息,并将该进程名通过`CharLowerBuff`转换为小写,当转换为小写模式后则就可以通过使用`strcmp`函数对比,如果发现继承存在则返回该进程的PID信息,否则返回-1。

7.1 C/C++ 实现动态数组

动态数组相比于静态数组具有更大的灵活性,因为其大小可以在运行时根据程序的需要动态地进行分配和调整,而不需要在编译时就确定数组的大小。这使得动态数组非常适合于需要动态添加或删除元素的情况,因为它们可以在不浪费空间的情况下根据需要动态增加或减少存储空间。动态数组的内存空间是从堆(heap)上分配的,动态数组需要程序员手动管理内存,因为它们的内存空间是在程序运行时动态分配的。程序员需要在使用完动态数组后

FreeRTOS简单内核实现7 阻塞链表

0、思考与回答 0.1、思考一 如何处理进入阻塞状态的任务? 为了让 RTOS 支持多优先级,我们创建了多个就绪链表(数组形式),用每一个就绪链表表示一个优先级,对于阻塞状态的任务显然要从就绪链表中移除,但是阻塞状态的任务并不是永久阻塞了,等待一段时间后应该从阻塞状态恢复,所以我们需要创建一个阻塞链

7.1 C++ STL 非变易查找算法

C++ STL 中的非变易算法(Non-modifying Algorithms)是指那些不会修改容器内容的算法,是C++提供的一组模板函数,该系列函数不会修改原序列中的数据,而是对数据进行处理、查找、计算等操作,并通过迭代器实现了对序列元素的遍历与访问。由于迭代器与算法是解耦的,因此非变易算法可以广泛地应用于各种容器上,提供了极高的通用性和灵活性。

Java第二次Blog

7-4~6题目集 前言 这些题目主要用到对象与类的处理继承与多态的使用: 继承和多态是面向对象编程中相互关联的两个概念。继承为多态提供了基础,而多态则通过继承实现了代码的灵活性和可扩展性。 1.字符串处理:需要对输入的题目信息和答题信息进行字符串分割、提取和处理,以获取题目编号、题目内容、标准答案和

使用vCenter对ESXi主机进行补丁升级

使用vCenter 对ESXi 主机进行补丁升级 背景说明:公司内部有许多ESXi主机需要进行补丁升级,记录一下通过vCenter对ESXi主机进行补丁升级的过程,也可以使用esxcli命令行方式。 vsphere版本:vCenter 6.7 和 ESXi 6.7 实操过程 1、查看ESXi主机版本