最近上的一門安全課,完成一個作業,需要把一個簡單遊戲的血量鎖定,使其“永生”所以學習了一下相關的知識,現在也記錄下來吧。
大致思路如下:
1) 通過搜索血量的值找出所有保存這個值的地址,
2) 改變血量,在上次結果中搜索改變後的血量,此時結果數會少些,如果不爲1,重複2),如果爲1,此時的地址就是儲存該變量的地址。
3) 將要改變的量寫入該地址
實現細節:
FindFirst();
該函數實現遍歷指定的某個進程的整個內存空間
FindNext();
該函數在FindFirst()的基礎之上,對其或者上一次FindNext()函數執行後的結果列表進行搜索
CompareAPage(DWORD dwBaseAddr, DWORD dwValue)
該函數通過基於頁,比較兩個頁是否一樣。(因爲進程的內存採用分頁機制,所以採用頁爲單位比較兩個值)
程序的思路要跟大家說清楚,就是如果你要修改某個進程裏面某個變量值val(比如遊戲中的血量),首先你在進程中搜索所有保存該值的地址,會得到一個地址列表,這就是FindFirst函數做的事情,然後更改該值,再搜索一遍,這次搜索就是在第一次中的結果列表中搜索了,如果得到的地址不唯一,那麼繼續改變值,再搜索,知道結果唯一,該地址就是儲存該值的,下面就直接把自己想要的值寫入就行了。
下面是console的代碼:(ps.網上可能有類似的,我也不標註了,我看了一下,這是王豔平的windows程序設計上的代碼,權當大家一起學習吧。)
附贈界面化程序代碼
#include <windows.h>
#include <stdio.h>
BOOL CompareAPage(DWORD dwBaseAddr,DWORD dwValue);
BOOL FindFirst(DWORD dwValue);//目標進程空間進行第一次查找
BOOL FindNext(DWORD dwValue);//在目標進程地址空間第2、3、4 ...次查找
DWORD g_arList[1024];//地址列表
int g_nListCnt;//有效地址的個數
HANDLE g_hProcess;//目標進程句柄
BOOL CompareAPage(DWORD dwBaseAddr,DWORD dwValue)
{
BYTE arBytes[4096];
if (!::ReadProcessMemory(g_hProcess,(LPVOID)dwBaseAddr,arBytes,4096,NULL))
{
return false;
}
DWORD * pdw;
for (int i=0;i<(int)4*1024-3;i++)
{
pdw =(DWORD*)&arBytes[i];
if (pdw[0]==dwValue)//等於要查找的值
{
if(g_nListCnt>=1024)
{
return FALSE;
}
else
{
g_arList[g_nListCnt++]=dwBaseAddr+i;
}
}
}
return true;
}
BOOL FindFirst(DWORD dwValue)
{
const DWORD dwOneGB=1024*1024*1024;
const DWORD dwOnePage=4*1024;
if (g_hProcess==NULL)
{
return FALSE;
}
//查看操作系統類型,以決定開始地址
DWORD dwBase;
OSVERSIONINFO vi={sizeof(vi)
};
::GetVersionEx(&vi);
if (vi.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS)
{
dwBase=4*1024*1024;//windows98系列,4mb
}
else
{
dwBase=640*1024;//windowsNT系列,64kb
}
//在開始地址到2GB的地址空間進行查找
for (;dwBase<2*dwOneGB;dwBase+=dwOnePage)
{
CompareAPage(dwBase,dwValue);
}
return TRUE;
}
void ShowList()
{
for (int i=0;i<g_nListCnt;i++)
{
printf("%08lX\n",g_arList[i]);
}
}
BOOL FindNext(DWORD dwValue)
{
//保存m_arList數組中有效地址的個數,初始化新的m_nListCnt值
int nOrgCnt=g_nListCnt;
g_nListCnt=0;
//在m_arList數組記錄的地址處查找
BOOL bRet=FALSE;//假設失敗
DWORD dwReadValue;
for (int i=0;i<nOrgCnt;i++)
{
if (::ReadProcessMemory(g_hProcess,(LPVOID)g_arList[i],&dwReadValue,sizeof(DWORD),NULL))
{
if (dwValue==dwReadValue)
{
g_arList[g_nListCnt++]=g_arList[i];
bRet=TRUE;
}
}
}
return bRet;
}
BOOL WriteMemory(DWORD dwAddr,DWORD dwValue)
{
return ::WriteProcessMemory(g_hProcess,(LPVOID)dwAddr,&dwValue,sizeof(DWORD),NULL);
}
int main()
{
char szFileName[]="1.exe";
STARTUPINFO si={sizeof(si)};
PROCESS_INFORMATION pi;
::CreateProcess(NULL,szFileName,NULL,NULL,FALSE,CREATE_NEW_CONSOLE,NULL,NULL,&si,&pi);
::CloseHandle(pi.hThread);
g_hProcess=pi.hProcess;
//輸入要修改的值
int iVal;
printf("input val=");
scanf("%d",&iVal);
FindFirst(iVal);
//打印出搜索結果
ShowList();
while(g_nListCnt>1)
{
printf("Input val=");
scanf("%d",&iVal);
FindNext(iVal);
ShowList();
}
printf("New value=");
scanf("%d",&iVal);
//寫入新值
if (WriteMemory(g_arList[0],iVal))
{
printf("Write data success\n");
}
::CloseHandle(g_hProcess);
return 0;
}
優點:
1. 通過遍歷內存和更改變量的方式,使得實現起來較爲簡單,邏輯清晰。
缺點:
1. 不能固定住血量對應的變量不變,需要動態的修改血量
2. 對於通過修改變量不能得出唯一地址的程序,該工具失靈
可能的改進:
1. 動態監控血量的變化,如果值低於某個值得時候,自動更改血量
2. 通過反編譯找出相應的指令,修改指令。