所謂動態庫注入是指,將自己編寫的動態庫,通過自己的程序來注入到別的進程中去,然後運行。
原理:
在目標進程,開闢一段內存,然後寫入要注入的動態庫.dll 。然後讓目標運行加載動態庫函數,將該動態庫載入到到進程中,然後運行指定函數,通常在進程創建一個線程,然後運行指定的代碼。
涉及到的函數如下如果需要更詳細的解釋,請前往MSDN查閱:
/*
\ brief 檢索頂級窗口的句柄,該窗口的類名和窗口名與指定的字符串匹配。
\ param lpClassName 窗口或控件類名稱
\ param lpWindowName 窗口名稱(窗口標題)
\ return 返回窗口的句柄,失敗返回NULL
*/
HWND FindWindowW(
LPCSTR lpClassName,
LPCSTR lpWindowName
);
/*
\ brief 檢索指定窗口的線程標識符(ID)與進程標識符(ID)
\ param hWnd 窗口的句柄
\ param lpdwProcessId 接收進程標識符的指針
\ return 線程標識符
*/
DWORD GetWindowThreadProcessId(
HWND hWnd,
LPDWORD lpdwProcessId
);
/*
\ brief 打開一個現有的本地過程對象
\ param dwDesiredAccess 進程訪問權限
\ param bInheritHandle 進程是否繼承句柄
\ param dwProcessId 進程標識符
\ return 進程的打開句柄,失敗返回NULL
*/
HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId
);
/*
\ brief 指定進程分配虛擬內存,提交或更改內存區域狀態,並初始化爲NULL
\ param hProcess 進程的句柄
\ param lpAddress 分配的頁面區域指定所需的起始地址,NULL則由系統默認
\ param dwSize 要分配的內存區域的大小,以字節爲單位,NULL則由系統默認認
\ param flAllocationType 內存分配的類型
\ param flProtect 內存保護常量
\ return 頁面分配的基地址(起始地址),失敗返回NULL
*/
LPVOID VirtualAllocEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
/*
\ brief 在指定的進程中將數據寫入內存區域
\ param hProcess 進程句柄,必須具有PROCESS_VM_WRITE和PROCESS_VM_OPERATION訪問權限
\ param lpBaseAddress 指向要寫入數據的指定進程中的基地址的指針
\ param lpBuffer 要寫入數據的緩衝區的指針
\ param nSize 緩衝區大小
\ param lpNumberOfBytesWritten 該變量接收傳輸到指定進程中的字節數
\ return 失敗返回,fALSE
*/
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesWritten
);
/*
\ brief 創建一個在另一個進程中運行的線程
\ param hProcess 創建線程的進程的句柄
\ param lpThreadAttributes 該結構爲新線程指定安全描述符,NULL爲系統默認
\ param dwStackSize 堆棧的初始大小,以字節爲單位,0 則使用系統默認大小
\ param lpStartAddress 類型爲LPTHREAD_START_ROUTINE函數的指針
\ param lpParameter 函數的參數指針
\ param dwCreationFlags 控制線程創建的標誌
\ param lpThreadId 指向接收線程標識符的變量的指針,NULL則不返回
\ return 新線程句柄,失敗返回NULL
*/
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
/*
\ brief 關閉打開的句柄
\ param 句柄
\ return 失敗返回FALSE
*/
BOOL CloseHandle(
HANDLE hObject
);
準備好要注入的,動態庫。
我個人是喜歡將動態庫,直接放置早要注入的程序目錄下。
注入程序的代碼,項目的字符集使用的Unicode字符
#include<iostream>
#include<Windows.h> // 引入Windows庫
using namespace std;
int main(int argc, char* argv[])
{
// 獲取窗口句柄 , 結尾是 W 表示寬字節, A 表示多字節
HWND hwnd = ::FindWindowW(NULL, TEXT("植物大戰殭屍中文版"));
if (hwnd == NULL) {
cout << __FILE__ << " " << __LINE__ << endl;
system("pause");
return 0;
}
// 定義一個變量接收 進程標識符
DWORD pId;
// 獲取進程與線程 標識符
DWORD tId = GetWindowThreadProcessId(hwnd, &pId);
// 獲取進程句柄
HANDLE pHandle = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pId);
if (pHandle == NULL) {
cout << __FILE__ << " " << __LINE__ << endl;
system("pause");
return 0;
}
// 定義寬字節數組,接收寬字節字符串
TCHAR buf[] = TEXT("StaticMfcDll.dll");
// 在目標進程分配一塊內存區域,使用 baseAddress變量接收,內存地址
LPVOID baseAddress = ::VirtualAllocEx(pHandle,
NULL,
sizeof(buf),
MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
// 在目標進程寫入buf數組,也就是 StaticMfcDll.dll 這一串字符串,注意這是寬字節
if (!::WriteProcessMemory(pHandle,
baseAddress,
buf,
sizeof(buf),
NULL)) {
cout << __FILE__ << " " << __LINE__ << endl;
system("pause");
return 0;
}
// 在目標進程調用 LoadLibrary()函數加載動態庫函數,參數是 baseAddress 地址的字符串。
if (!::CreateRemoteThread(pHandle,
NULL,
0,
(LPTHREAD_START_ROUTINE)LoadLibrary,
baseAddress,
0,
NULL)) {
cout << __FILE__ << " " << __LINE__ << endl;
system("pause");
return 0;
}
// 關閉句柄
::CloseHandle(pHandle);
}
這個動態庫是 MFC庫,重點配置是,可以用vs直接創建出來
動態庫的核心代碼
// CStaticMfcDllApp 初始化
// .h 頭文件定義的一個 線程指針
private:
std::unique_ptr<std::thread> tPtr;
/*
\ breif 線程運行的函數
*/
void CStaticMfcDllApp::ThreadFunciton()
{
// 定義 植物變量
Plant p;
// 模態顯示 植物對話框
p.DoModal();
}
/*
\brief 當加載該庫的時候,會調用到這個重載函數。
*/
BOOL CStaticMfcDllApp::InitInstance()
{
CWinApp::InitInstance();
// 開啓一個線程,運行 成員函數
tPtr = std::make_unique<std::thread>(&CStaticMfcDllApp::ThreadFunciton, this);
return TRUE;
}
/*
\ brief 析構函數
*/
CStaticMfcDllApp::~CStaticMfcDllApp()
{
// 銷燬時,阻塞等待線程退出
tPtr->join();
}
/*
\ brief 這個是修改陽光
*/
void Plant::OnBnClickedBtnSum()
{
// TODO: 在此添加控件通知處理程序代碼
UpdateData(TRUE);
int sum = _wtoi(editSum.GetBuffer());
// 陽光基址 [[006A9EC0]+768]+5560
size_t * pointer = (size_t*)((*(size_t*)((*(size_t*)0x006A9EC0) + 0x768)) + 0x5560);
*pointer = sum;
}
/*
\ brief 這個是修改金幣
*/
void Plant::OnBnClickedBtnMenory()
{
// TODO: 在此添加控件通知處理程序代碼
UpdateData(TRUE);
int menory = _wtoi(editMenory.GetBuffer());
// 金幣基址 [[006A9EC0] + 82C] + 28
size_t* pointer = (size_t*)((*(size_t*)((*(size_t*)0x006A9EC0) + 0x82C)) + 0x28);
*pointer = menory;
}
/*
\ brief 這個是修改植物的CD冷卻邏輯代碼,直接修改程序運行代碼
*/
void Plant::OnBnClickedPlant()
{
// TODO: 在此添加控件通知處理程序代碼
// 將00487296 | 7E 14 | jle plantsvszombies.4872AC 處代碼 修改成 9090
UpdateData(TRUE);
UCHAR buf[2] = {};
if (plantCD) {
buf[0] = 0x90;
buf[1] = 0x90;
}
else {
buf[0] = 0x7E;
buf[1] = 0x14;
}
// 只能使用這個 函數來進行代碼段的修改,直接 使用指針會使程序崩潰,哪怕是在同一個進程
::WriteProcessMemory(::GetCurrentProcess(),
LPVOID(0x00487296),
buf,
sizeof(buf),
NULL);
}
/*
\ brief 這個是修改大嘴花的吞噬冷卻邏輯
*/
void Plant::OnBnClickedFlower()
{
// TODO: 在此添加控件通知處理程序代碼
// 將 0046324C 處代碼 0046324C | 83C0 FF | add eax,FFFFFFFF
// 修改成 0046324C | 33C0 90 | xor eax,eax nop
UpdateData(TRUE);
UCHAR buf[3] = {};
if (flowerCd) {
buf[0] = 0x33;
buf[1] = 0xC0;
buf[2] = 0x90;
}
else {
buf[0] = 0x83;
buf[1] = 0xC0;
buf[2] = 0xFF;
}
// 只能使用這個 函數來進行代碼段的修改,直接 使用指針會使程序崩潰,哪怕是在同一個進程
::WriteProcessMemory(::GetCurrentProcess(),
LPVOID(0x0046324C),
buf,
sizeof(buf),
NULL);
}
/*
\ brief 按放櫻花炸彈
\ param x 軸座標 0-8
\ param y 軸座標 0-8
*/
void Plant::Bom(int x, int y)
{
// 這個是彙編代碼,調用 遊戲的植物按放call
_asm
{
pushad
push 0xFFFFFFFF
push 2
mov eax, y
push x
mov ebx, ds: [0x6A9EC0]
mov ebx, ds : [ebx + 0x768]
push ebx
mov edx, 0x40D120;
call edx
popad
}
}
/*
\ brief 滿屏櫻花炸彈
*/
void Plant::OnBnClickedBtnBom()
{
// TODO: 在此添加控件通知處理程序代碼
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
Bom(i, j);
}
}
}
看一下實際運行測試:
先啓動,植物大戰殭屍遊戲程序(PlantsVsZombies.exe),然後啓動注入程序(Grammar.exe)。
運行效果
我們可以使用工具查看一下,是否成功注入到植物大戰殭屍進程中
已經成功注入到,植物大戰殭屍進程中了。
接下來就是測試下,動態庫效果。
可以看到,陽關的數量,和金幣的數量已經修改了,植物的冷卻已已經完畢,是沒有問題的。
接下來測試,櫻花炸彈 全屏轟炸效果
沒有問題。
文章時間2019年12月20日15:41:54