CE程序設計
注:參考網上程序,和實驗15大同小異
相關知識:
CE(Cheat Engine )是衆多遊戲修改工具的其中之一,也是一款開源軟件。運行之後的界面,估計大多數人看見了都不知所謂,一對對數字描述了遊戲的內存地址和對應的數值。不過對於普通用戶來說,這些都不需要去了解和理解,只需要把遊戲中想要修改的數值找到對應的地址,並修改成想要的數值就可以了。
尋找數值對應地址的方法一般是:運行1.CE->2.運行遊戲->3.打開遊戲進程->4.首次搜索一個數值->5.迴游戲中讓這個數值增加或減少 ->6.回CE按數值增減的情況再次搜索->7.重複5和6直到得到一個或很少的幾個結果->8.在這幾個結果中判斷哪一個是真正的結果(一般來說,搜索幾次之後,基本就剩下一個修改地址了)。
參考:https://baike.baidu.com/item/CE%E5%86%85%E5%AD%98%E4%BF%AE%E6%94%B9%E5%99%A8/9225930?fr=aladdin - CE內存修改器
代碼實現:
內存查找類
#ifndef __MEMFINDER_H__
#define __MEMFINDER_H__
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
class CMemFinder
{
public:
CMemFinder(DWORD dwProcessId);
virtual ~CMemFinder();
// 屬性
public:
BOOL IsFirst() const { return m_bFirst; }
BOOL IsValid() const { return m_hProcess != NULL; }
int GetListCount() const { return m_nListCnt; }
DWORD operator [](int nIndex) { return m_arList[nIndex]; }
// 操作
virtual BOOL FindFirst(DWORD dwValue);
virtual BOOL FindNext(DWORD dwValue);
virtual BOOL WriteMemory(DWORD dwAddr, DWORD dwValue);
// 實現
protected:
virtual BOOL CompareAPage(DWORD dwBaseAddr, DWORD dwValue);
DWORD m_arList[1024]; // 地址列表
int m_nListCnt; // 有效地址的個數
HANDLE m_hProcess; // 目標進程句柄
BOOL m_bFirst; // 是不是第一次搜索
};
->OpenProcess 函數用來打開一個已存在的進程對象,並返回進程的句柄。
CMemFinder::CMemFinder(DWORD dwProcessId)
{
m_nListCnt = 0;
m_bFirst = TRUE;
m_hProcess = ::OpenProcess(PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION, FALSE, dwProcessId);
}
CMemFinder::~CMemFinder()
{
if(m_hProcess != NULL)
::CloseHandle(m_hProcess);
}
->從打開的進程開始查找查找的數據的地址
BOOL CMemFinder::FindFirst(DWORD dwValue)
{
const DWORD dwOneGB = 1024*1024*1024; // 1GB
const DWORD dwOnePage = 4*1024; // 4KB
if(m_hProcess == NULL)
return FALSE;
// 查看操作系統類型,以決定開始地址
DWORD dwBase;
OSVERSIONINFO vi = { sizeof(vi) };
::GetVersionEx(&vi);
if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
dwBase = 4*1024*1024; // Windows 98系列,4MB
else
dwBase = 640*1024; // Windows NT系列,64KB
// 在開始地址到2GB的地址空間進行查找
for(; dwBase < 2*dwOneGB; dwBase += dwOnePage)
{
// 比較1頁大小的內存
CompareAPage(dwBase, dwValue);
}
m_bFirst = FALSE;
return TRUE;
}
->比較一頁內存(指定範圍),查看值是否相等,相等保存到全局數組中
BOOL CMemFinder::CompareAPage(DWORD dwBaseAddr, DWORD dwValue)
{
// 讀取1頁內存
BYTE arBytes[4096];
if(!::ReadProcessMemory(m_hProcess, (LPVOID)dwBaseAddr, arBytes, 4096, NULL))
return FALSE; // 此頁不可讀
// 在這1頁內存中查找
DWORD* pdw;
for(int i=0; i<(int)4*1024-3; i++)
{
pdw = (DWORD*)&arBytes[i];
if(pdw[0] == dwValue) // 等於要查找的值?
{
if(m_nListCnt >= 1024)
return FALSE;
// 添加到全局變量中
m_arList[m_nListCnt++] = dwBaseAddr + i;
}
}
return TRUE;
}
->查找全局數組中下一個地址的數據
BOOL CMemFinder::FindNext(DWORD dwValue)
{
// 保存m_arList數組中有效地址的個數,初始化新的m_nListCnt值
int nOrgCnt = m_nListCnt;
m_nListCnt = 0;
// 在m_arList數組記錄的地址處查找
BOOL bRet = FALSE; // 假設失敗
DWORD dwReadValue;
for(int i=0; i<nOrgCnt; i++)
{
if(::ReadProcessMemory(m_hProcess, (LPVOID)m_arList[i], &dwReadValue, sizeof(DWORD), NULL))
{
if(dwReadValue == dwValue)
{
m_arList[m_nListCnt++] = m_arList[i];
bRet = TRUE;
}
}
}
return bRet;
}
->修改內存中的數據
BOOL CMemFinder::WriteMemory(DWORD dwAddr, DWORD dwValue)
{
return ::WriteProcessMemory(m_hProcess, (LPVOID)dwAddr, &dwValue, sizeof(DWORD), NULL);
}
////////////////////////////////////////////////////////////
// 下面的CMsgMemFinder類在搜索內存時還會不斷處理消息
// 在等待期間處理消息
void WaitForIdle()
{
MSG msg;
while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
class CMsgMemFinder : public CMemFinder
{
public:
CMsgMemFinder(DWORD dwProcessId) : CMemFinder(dwProcessId)
{
m_bIsWorking = FALSE;
}
// 是否在工作
BOOL IsWorking() const { return m_bIsWorking; }
// 開始查找
virtual BOOL FindFirst(DWORD dwValue)
{
m_bIsWorking = TRUE;
BOOL b = CMemFinder::FindFirst(dwValue);
m_bIsWorking = FALSE;
return b;
}
protected:
// 比較1頁內存前,先處理消息
virtual BOOL CompareAPage(DWORD dwBaseAddr, DWORD dwValue)
{
WaitForIdle();
return CMemFinder::CompareAPage(dwBaseAddr, dwValue);
}
BOOL m_bIsWorking;
};
#endif // __MEMFINDER_H__
-----------------------------------------------各控件的作用---------------------------------------
BOOL CMainDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// 設置圖標
SetIcon(theApp.LoadIcon(IDI_MAIN), FALSE);
m_comboList.SubclassWindow(::GetDlgItem(m_hWnd, IDC_PROLIST));
m_btnUpdate.SubclassWindow(*GetDlgItem(IDC_UPDATE));
m_listAddr.SubclassWindow(*GetDlgItem(IDC_ADDRLIST));
m_pFinder = NULL;
// 創建狀態欄,設置它的屬性(CStatusBarCtrl類封裝了對狀態欄控件的操作)
m_bar.Create(WS_CHILD|WS_VISIBLE|SBS_SIZEGRIP, CRect(0, 0, 0, 0), this, 10);
m_bar.SetBkColor(RGB(0xa6, 0xca, 0xf0)); // 背景色
int arWidth[] = { 145, -1 };
// 更新進程列表
OnUpdate();
// 更新界面
UIControl();
return FALSE;
}
void CMainDialog::OnSearch()
{
// 創建查找對象
if(m_pFinder == NULL)
{
DWORD dwId = m_comboList.GetItemData(m_comboList.GetCurSel());
m_pFinder = new CMsgMemFinder(dwId);
UIControl();
}
if(m_pFinder->IsWorking())
return;
CString sText;
GetDlgItem(IDC_EDITSEARCH)->GetWindowText(sText);
if(!sText.IsEmpty())
{
DWORD dwSearch = atoi(sText);
if(m_pFinder->IsFirst())
{
m_bar.SetText("正在搜索,請耐心等待...", 0, 0);
// 長時間操作
m_pFinder->FindFirst(dwSearch);
}
else
{
m_pFinder->FindNext(dwSearch);
}
sText.Format("搜索到%d個結果", m_pFinder->GetListCount());
m_bar.SetText(sText, 0, 0);
}
else
{
if(m_pFinder->IsValid())
{
m_bar.SetText("打開目標進程成功!", 0, 0);
}
else
{
m_bar.SetText("打開目標進程失敗!", 0, 0);
}
}
UIControl();
}
void CMainDialog::OnRestart()
{
if(m_pFinder != NULL)
{
if(m_pFinder->IsWorking())
return;
delete m_pFinder;
m_pFinder = NULL;
}
m_bar.SetText("空閒", 0, 0);
UIControl();
}
void CMainDialog::UIControl()
{
m_comboList.EnableWindow(m_pFinder == NULL);
m_btnUpdate.EnableWindow(m_pFinder == NULL);
GetDlgItem(IDC_MODIFY)->EnableWindow(m_pFinder != NULL);
// 列出搜索結果
if(m_pFinder != NULL)
{
CString s;
m_listAddr.ResetContent();
for(int i=0; i<m_pFinder->GetListCount(); i++)
{
s.Format("%08lX", (*m_pFinder)[i]);
m_listAddr.AddString(s);
}
}
}
void CMainDialog::OnUpdate()
{
// 刪除所有的項
m_comboList.ResetContent();
int nItem = 0; // 項計數
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(hProcessSnap == INVALID_HANDLE_VALUE)
return;
if(Process32First(hProcessSnap, &pe32))
{
do
{
CString szText;
szText.Format("%s (%d)", pe32.szExeFile, pe32.th32ProcessID);
m_comboList.InsertString(nItem, szText);
m_comboList.SetItemData(nItem, pe32.th32ProcessID);
nItem++;
}
while(Process32Next(hProcessSnap, &pe32));
}
::CloseHandle(hProcessSnap);
m_comboList.SetCurSel(nItem-1);
}
void CMainDialog::OnModify()
{
CString sText;
DWORD dwValue;
GetDlgItem(IDC_EDITMODIFY)->GetWindowText(sText);
if(sText.IsEmpty())
{
MessageBox("請輸入一個你想要的值!");
return;
}
dwValue = (DWORD)atoi(sText);
GetDlgItem(IDC_SELADDR)->GetWindowText(sText);
if(sText.IsEmpty())
{
MessageBox("請輸入你要將哪個地址的值改爲:%s", sText);
return;
}
DWORD dwAddr;
sscanf(sText, "%x", &dwAddr);
if(m_pFinder != NULL)
{
if(m_pFinder->WriteMemory(dwAddr, dwValue))
m_bar.SetText("修改成功!", 0, 0);
else
m_bar.SetText("修改失敗!", 0, 0);
}
}
void CMainDialog::OnSelectChange()
{
CString sText;
int nSel = m_listAddr.GetCurSel();
m_listAddr.GetText(nSel, sText);
GetDlgItem(IDC_SELADDR)->SetWindowText(sText);
}
void CMainDialog::OnCancel()
{
OnRestart();
CDialog::OnCancel();
}
簡單的說一下:
- 這個工具的實現原理就是通過 CreateToolhelp32Snapshot 獲取系統中所有進程中的句柄。
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
注:CreateToolhelp32Snapshot可以通過獲取進程信息爲指定的進程、進程使用的堆[HEAP]、模塊[MODULE]、線程建立一個快照。說到底,可以獲取系統中正在運行的進程信息,線程信息,等。
2.通過選擇進程,打開指定進程的句柄:使用
CMemFinder::CMemFinder(DWORD dwProcessId)
{
m_nListCnt = 0;
m_bFirst = TRUE;
m_hProcess = ::OpenProcess(PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION, FALSE, dwProcessId);
}
- 然後通過修改進程中的值,再查找修改值在進程的地址(存放的是等於該值的所有地址)逐步過濾到只剩下一個地址。
BOOL CMemFinder::CompareAPage(DWORD dwBaseAddr, DWORD dwValue)
{
// 讀取1頁內存
BYTE arBytes[4096];
if(!::ReadProcessMemory(m_hProcess, (LPVOID)dwBaseAddr, arBytes, 4096, NULL))
return FALSE; // 此頁不可讀
// 在這1頁內存中查找
DWORD* pdw;
for(int i=0; i<(int)4*1024-3; i++)
{
pdw = (DWORD*)&arBytes[i];
if(pdw[0] == dwValue) // 等於要查找的值?
{
if(m_nListCnt >= 1024)
return FALSE;
// 添加到全局變量中
m_arList[m_nListCnt++] = dwBaseAddr + i;
}
}
return TRUE;
}
4.然後寫入值修改該地址存放的數據。
BOOL CMemFinder::WriteMemory(DWORD dwAddr, DWORD dwValue)
{
return ::WriteProcessMemory(m_hProcess, (LPVOID)dwAddr, &dwValue, sizeof(DWORD), NULL);
}
內存修改成功!!!
此次試驗與實驗15遊戲內存修改原理基本相同,實驗15的內存修改步驟大致如下:
獲得窗口句柄->獲得該窗口的進程(在內存中)->打開進程->修改內存
使用的函數分別有:
FindWindow(NULL, L"shooting")->GetSafeHwnd();
函數功能:該函數獲得一個頂層窗口的句柄,該窗口的類名和窗口名與給定的字符串相匹配。這個函數不查找子窗口。在查找時不區分大小寫。
GetWindowThreadProcessId(hWnd, &Pid);
函數功能:GetWindowThreadProcessId是一種計算機函數,功能是找出某個窗口的創建者(線程或進程),返回創建者的標誌符,函數原型是DWORD GetWindowThreadProcessId。
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
函數功能:OpenProcess 函數用來打開一個已存在的進程對象,並返回進程的句柄。
result = WriteProcessMemory(hProcess, (LPVOID)Address, &Score, 4, 0);
函數功能:WriteProcessMemory是計算機語言中的一種函數。此函數能寫入某一進程的內存區域(直接寫入會出Access Violation錯誤),故需此函數入口區必須可以訪問,否則操作將失敗。
而此次試驗是在實驗15基礎上進行改進,它不是使用獲取窗口句柄打開進程,而是通過獲取系統中的所有進程句柄,找到運行的遊戲,這個其實改進了一下,但使用窗口句柄代開進程也未嘗不可。
而最大的不同,且最重要的點是:它通過比較進程中的所有地址中的數據,通過遊戲的數值變化,使用該數值對內存中的進行過濾(保存地址,逐步減少選中的地址數量),最後找到想要修改的地址,方法高明瞭不止一籌。
實驗步驟爲:
獲取系統所有進程句柄 -> 打開指定進程 -> 觀察遊戲值的變化輸入值比較內存中所有地址的數據,篩選出相等數據的地址,重複操作過濾到只剩一個地址 -> 修改內存
通過參考網上資料,進行此次實驗,學會了CE動態內存修改器的製作,也算是對這個學期網絡安全課程的一個總結。通過這學期網絡安全的學習,對網絡有了更深一步的瞭解,也對自己以後從事IT行業打下了一定安全基礎。我感覺這門課是大學爲數不多真正學到東西的一門課。老師講課條理清楚,而且結合他生活中的例子,把我們帶進了神祕未知的安全世界,網絡安全是程序員捍衛自己地位的權杖。俗話說師傅領進門,修行在個人,想要真正成爲網絡安全的專家也不僅僅是通過十多節課就可以達到,更需要自己去專研,但姜曄老師把我們從門外漢領進了安全的大門,我們以後要進行網絡安全的深入學習可以是少了很多攔路石,少走了不知道多少彎路。
感謝老師一學期的教導~
致謝!