網絡安全學習第16篇 - CE動態內存遊戲修改器

                                                                                         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();
}


簡單的說一下:

  1. 這個工具的實現原理就是通過 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);

}

 

  1. 然後通過修改進程中的值,再查找修改值在進程的地址(存放的是等於該值的所有地址)逐步過濾到只剩下一個地址。

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行業打下了一定安全基礎。我感覺這門課是大學爲數不多真正學到東西的一門課。老師講課條理清楚,而且結合他生活中的例子,把我們帶進了神祕未知的安全世界,網絡安全是程序員捍衛自己地位的權杖。俗話說師傅領進門,修行在個人,想要真正成爲網絡安全的專家也不僅僅是通過十多節課就可以達到,更需要自己去專研,但姜曄老師把我們從門外漢領進了安全的大門,我們以後要進行網絡安全的深入學習可以是少了很多攔路石,少走了不知道多少彎路。

感謝老師一學期的教導~

致謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章