7.7 實現程式記憶體讀寫

lyshark發表於2023-09-25

記憶體程式讀寫可以讓我們訪問其他程式的記憶體空間並讀取或修改其中的資料。這種技術通常用於各種除錯工具、程式監控工具和反作弊系統等場景。在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 許可協議。轉載請註明出處!

相關文章