QQ連連看單機版輔助制作全流程
最近在15PB學習逆向,分析了個小遊戲并寫出了輔助工具,在這裡總結下全流程。
遊戲:QQ連連看(單機版1.2)
完成目标:
1.去除廣告
2.完成指南針、炸彈消除的功能
3.編寫注入程式和遊戲輔助的DLL
使用工具:VS2017、OD、CE、PEID、Spy++
分析環境:Win7虛拟機
首先在百度上搜尋“QQ連連看單機版”随便下了個V1.2版的,把它解壓到桌面然後進入檔案夾。
仔細觀察下都有些什麼檔案,這很重要,有可能得到一些有利于破解的資訊。
**1.**在這裡觀察到有兩個可執行的EXE檔案、兩個音樂檔案夾,這都是比較重要的資訊。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL6lEVONTQU5EeNpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5MjN5MzN0QTM3AzMwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
**2.**用PEID擦看下這兩個EXE程式基本資訊,得以得出"kyodai.exe"程式是VC6.0的編譯器編寫的,遊戲一般都是C++語言編寫,而"QQ連連看單機版V1.2.exe"是一個加殼的程式,常見壓縮殼ASpack這就忽略它,也能分析。
**3.**輕按兩下"kyodai.exe"程式後回崩潰如圖。
**4.**那麼就确定了開始遊戲的程式"QQ連連看單機版V1.2.exe",打開之後會彈出廣告視窗,再之後遊戲就運作了。
**5.**退出遊戲,打開”任務管理器”,在運作遊戲觀察程序的變化,發現在彈出廣告視窗時隻有一個與遊戲相關的程序”QQ連連看單機版V1.2.exe”
**6.**點選繼續進入遊戲之後發現此程序消失,而多了另一個很熟悉程序"kyodai.exe"
**7.**由于最終運作的還是"kyodai.exe"程式,但是點選它又運作不了。而通過其他程式卻可運作它, 由此猜想"kyodai.exe"在運作時,”QQ連連看單機版V1.2.exe”打開它并對它的記憶體進行了修改,使他能正常運作。在這說明下這是老編譯器寫的遊戲不存在随機基址,很多位址都是固定的。
猜想它可能使用了CreateProcessA\W函數打開程序,那麼開始調試,使用OD開打”QQ連連看單機版V1.2.exe”,Ctrl+G搜尋CreateProcessA\W在2個函數上都按F2設定斷點,運作之後發現在CreateProcessA函數上斷下,觀察堆棧資料,發現"kyodai.exe"程式被它以挂起的方式打開了。
**8.**很多惡意程式都是這樣的套路,那麼就容易猜想了,挂起了程式之後就可能修改記憶體資料,之後恢複程式,那再就來搜尋一下修改記憶體資料函數WriteProcessMemory下斷點,運作,在次停下了再觀察堆棧資料,發現3個重要的資料,意為在"kyodai.exe"程式中的0043817A 位址處修改1個位元組的資料。
**9.**選中該Buffer右鍵檢視其内容是”0”。
**10.**然後在搜尋一個喚醒線程的函數ResumeThread下斷點,運作之後果然走到了這裡。
**11.**那麼我們現在就已經分析出了”QQ連連看單機版V1.2.exe”的運作機制,它首先彈出廣告,然後再執行CreateProcessA -> WriteProcessMemory -> ResumeThread操作打開"kyodai.exe"程式,那麼我們現在就可以模拟它的執行流程編寫程式打開它
打開VS2017編寫程式如下
#include "pch.h"
#include <iostream>
#include <windows.h>
//kyodai.exe程式的路徑
#define CATALOG L"C:\\Users\\15pb-win7\\Desktop\\連連看\\kyodai.exe"
int main()
{
STARTUPINFO si = {};
//操作程序的資訊結構體
PROCESS_INFORMATION pi = {};
//打開程序
CreateProcess((LPWSTR)CATALOG, 0, 0, 0, FALSE,
CREATE_SUSPENDED,//此參數為挂起主線程
0, 0, &si, &pi);
DWORD dwWrite = 0;
//修改1位元組的資料
WriteProcessMemory(pi.hProcess, (LPVOID)0x43817A, "\x0", 1, &dwWrite);
//恢複線程
ResumeThread(pi.hThread);
return 0;
}
**12.**本人認為這段程式對咱們新手來說比較有用。在這裡要注意的是”kyodai.exe”所在路徑每個人的可能都不一樣,編譯程式後運作,打開遊戲成功!
**13.**當然也可以直接使用16進制檔案編輯器直接找到3817A(因為預設加載基址0x400000需要減去)位址把裡面的資料改為“00”,我使用的工具是010Editor。
**14.**接下來正式分析遊戲關鍵功能了,正常的程式彙編中CALL xxx基本都是函數,運作遊戲之後多玩幾把測試一下遊戲玩法,遊戲右上角道具欄有指南針,多重新整理幾次地圖可能會出現炸彈,把炸彈消除掉道具欄裡會出現炸彈,而且道具有數量限制。
**15.**我用了CE工具查找道具基址,很遺憾找了10多分鐘沒找到,隻有換一種方法了,點選道具的時候會發出聲音,那麼音樂檔案的名字是字元串,搜尋字元串可以是個切入點。
**16.**還有點選練習的時候地圖會随機重新整理,那麼肯定會用到rand這個随機函數,親測兩種方法都能達到一樣的效果找到關鍵點,在這裡總結下查找API的方法,使用OD打開或者附加”kyodai.exe”程式先運作起來,再在OD中Ctrl+G搜尋rand函數下斷點,點選遊戲中的練習後斷了下來。
**17.**然後點選OD菜單欄上的”K”進行棧回溯分析。
觀察發現有2個上層調用(有程式的名字的2個),他們的關系是0x41A080處的函數調用0x41CAF2的函數,0x41CAF2的函數調用rand。這裡說的比較啰嗦,反正在”K”中越靠下就越是外層的函數。
**18.**輕按兩下進入第一個位址并在上面設定斷點,然後再點K輕按兩下進入第2個位址設定斷點然後把之前rand的斷點删除
**19.**F9運作後再次在遊戲中點選練習會在第一個斷點0x41A080位址上停下這裡是一個函數(CALL),要重點關注一下它上面行代碼MOV ECX, EDI 由于這是C++所寫的程式,它都會使用ECX這個寄存器傳遞this指針也就是傳遞一個對象,之後看見隻要對ECX寄存器操作的代碼都要留意一下。按F7進入這個函數,先Ctrl+A讓OD幫我們分析一下該子產品,發現一開始把ECX的值給了ESI,先不管這繼續單步然後沒幾步就看見了熟悉的字元串”strat.wav”(字元串搜尋也能定位到這個函數裡來),這已經證明這個函數就是初始化遊戲的函數。
**20.**然後再快速單步很快就能發現剛才所下的第二個斷點,發現rand下面有一個 memcpy複制記憶體的函數,先大緻走一遍注意觀察OD中右上角寄存器的變化,觀察寄存器上的值的記憶體的變化,走完了好像什麼也沒發現…然後再重新運作再多單步跟蹤幾次這個函數總會發現一些什麼的。
**21.**再次跟蹤這個函數,實際上剛才說過要注意ECX的值的傳遞,在這個函數裡ECX傳遞給了ESI,那麼我們要對所有操作ESI的代碼留意一下,在加上我們下的第二個斷點0x41CAF2所在附近,我們就要對它附近ESI重點關注了,在0041CAFC 位址處的這一行代碼對ESI進行了通路,走一步之後我們發現EAX的是0012BB50。
**22.**那麼再選中它右鍵->資料視窗跟随 檢視0012BB50位址裡面有些什麼資料。
**23.**然後走到0041CB10 位址處,這裡會調用memcpy複制記憶體,走一步觀察0012BB50位址裡的資料除了第一個DC 00…沒變,後面都被填充成某種規律的010101…
**24.**在繼續走兩步經過0041CB20 位址出的函數後驚訝的發現0012BB50位址裡那一大串資料又被重新整理,到此我們大膽猜測0012BB50這就是遊戲的地圖數組基位址。
**25.**為了驗證是否正确按F9把遊戲運作起來觀察地圖與該記憶體資料的是否有聯系(觀察地圖和該記憶體時重新整理出一個特點鮮明的地圖較好,比如地圖右上角有兩個靠的近相同的東西等)我多重新整理了幾次得到一個左上角和右下角都被填滿了的地圖,觀察OD中的記憶體(記得點選下OD資料才會重新整理)也跟着改變了,貌似還找到了數組的邊界!0012BB58(第一個點)
**26.**再重新整理幾次地圖找到第一行有兩個連着的相同的東西,發現地圖和OD中的記憶體非常相似。
**27.**為了确認 我們把遊戲中的物品點選消除掉在觀察記憶體發現果然被清0了那麼可以确定0012BB50這就是地圖數組基位址了,并且我們得到了0012BB58就是地圖資料的起始位置。
**28.**接下來分析指南針和炸彈,指南針功能是可以幫助玩家找到2個相同物品,炸彈是找到2個相同的物品并消除,這2個道具不管是誰都會周遊地圖數組,才能實作他們的功能,是以在0012BB58位址處右鍵下一個記憶體通路斷點。
**29.**先在OD中按ALT+B把其他斷點禁止或者删掉,然後運作遊戲點選指南針,程式會停下來。點選”K”進行棧回溯分析觀察他的上幾層調用函數發現有5個那麼每個都點進去設定上斷點,然後右鍵選中先把0012BB58的記憶體斷點給删除掉。
**30.**再F9運作OD再運作幾次發現都停在004292A5 位址上那麼這肯定是不需要的函數把它斷點去掉,再運作2次,點不動了需要點選遊戲界面,一點就在OD中斷下來了 是以判斷0040CACA斷點也不是我們想要的,同樣的繼續運作幾次還是都停在了0041AF11 處,這也是無用位址去掉斷點,再F9遊戲就運作起來了,這個過程多調試幾次就會明白的。
是以0041DE5C和0041E76C 處的函數就可能是我們能利用關鍵函數,現在想要寫代碼完成指南針的功能,隻要找到這兩處位址的函數調用時所需要的參數,那就可以模拟出指南針的功能。
重新運作起來,點選遊戲中的指南針,會在0041DE5C 處的代碼斷下,選中這一行按Enter建進入這個函數一直往下拉找到末尾RETN觀察傳回值發現RETN 0xC 也就是說0041DE5C 位址處的函數調用需要3個參數,在觀察堆棧得出參數是 0,0,F0。
**31.**為了确認這3個參數是不是可變的,再次運作遊戲,再點選指南針,再次在0041DE5C 停下發現堆棧裡的參數還是這麼多沒有改變,那我就決定調用了這個函數模拟指南針功能了,寫好注釋。 其實在第2個斷點0041E76C 處的參數更少,隻有2個,但是在那裡斷下來之後檢視堆棧裡的參數也不知道是些什麼,是以放棄它把它的斷點去掉。
相同的原理我再找到炸彈的調用位址,多重新整理幾次遊戲,找到有可以消除炸彈地圖,把炸彈消除了道具欄中就出現了炸彈。
**32.**同樣運作遊戲之後在0012BB58下記憶體通路斷點,再點選遊戲道具欄裡的炸彈,斷下之後再次按”K”進行棧回溯分析,發現這幾個位址很眼熟,和之前找指南針的棧回溯基本一緻 隻有一個不一樣,那麼無用幾個位址不用管他,我們在0041DE5C 處下斷點,重新整理遊戲地圖找到一個有炸彈的地圖,再使用炸彈之後停下觀察堆棧參數的變化,發現參數是0,0,F4。
由此我們可以對比下兩次調用這個函數的參數
調用指南針是0,0,F0
調用炸彈是0,0,F4
得出結論第3個參數就是遊戲的道具類型,0041DE5C 位址處的函數功能應該就是使用道具。
**33.**談一下我對輔助中的的DLL,注入程式,原程式(這裡就是遊戲程式)他們關系的了解:我們想要DLL裡面的功能在原程式種實作,直接來是不行的,需要通過注入程式把DLL注入到原程式中,這時DLL功能才會對原程式産生影響。 這就像熱發燒了打針一樣,針筒裡的藥水就是DLL,針筒就是注入程式,人體就是原程式。
**34.**接下裡就是編寫輔助了,在這需要實作指南針和炸彈的功能,之需要模拟調用0041DE5C 位址處的函數就OK了。
我用的是VS2017首先建立一個MFC的DLL程式,選擇在靜态庫中使用MFC,這個選項也可以在屬性->正常裡面更改。
**35.**使用到的關鍵API如下
FindWindow擷取視窗句柄
SetWindowLong設定視窗回調
_beginthreadex建立線程便于彈窗
因為要在遊戲中彈出一個視窗,是以要添加一個資源Dialog
視窗回掉原型為
LRESULT
CALLBACK
DefWindowProc(
In HWND hWnd,
In UINT Msg,
In WPARAM wParam,
In LPARAM lParam);
要實作的輔助功能主要就是在這個視窗回掉函數裡面實作。
**36.**現在先使用VS中的自帶工具Spy++先查找遊戲的,視窗名,和類名,用于的FindWindow函數擷取視窗句柄。
把Spy++中的望遠鏡拖到遊戲視窗上可以可到,視窗名。
**37.**再次分析下0041DE5C 位址的函數這裡的參數是3個但之前也說過C++使用ECX傳遞對象,是以我們必須得到ECX的值,經過多次運作發現右上角的ECX值一直是0012A688是以我們直接使用這個固定值。
**38.**使用_asm内嵌彙編調用那個關鍵函數,0041DE5C 位址處彙編是CALL [EAX+0x28] ,在記憶體視窗中Ctrl+G搜尋EAX+0x28 裡面的值為0041E691(以小端方式讀) 這就是需要調用函數的位址。
LRESULT
CALLBACK
MyDefWindowProc(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
{
//指南針消息
if (Msg == WM_SIGN1)
{
_asm
{
MOV ECX, 0X12A688; //this指針
PUSH 0XF0; //參數3是道具類型
PUSH 0; //參數2
PUSH 0; //參數1
MOV EAX, 0X0041E691;
CALL EAX; //指南針函數
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
if (Msg == WM_SIGN2)
{
_asm
{
MOV ECX, 0X12A688;
PUSH 0XF4;
PUSH 0;
PUSH 0;
MOV EAX, 0X0041E691;
CALL EAX;//炸彈函數
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return CallWindowProc(g_OldProc, hWnd, Msg, wParam, lParam);
}
**39.**說明一下,為了在輔助程式的視窗發送消息,我在頭檔案stdafx.h中自定義了兩個消息,然後編譯生成DLL就OK。
40.最後編寫遠端注入程式,就建立一個控制台程式就行了,要注意的是在這裡要在屬性裡選擇C+±>代碼生成把運作庫改為多線程調試(\MTd) 就跟上面的DLL靜态編譯一樣,如果不這樣,我們寫的程式在别人的電腦上可能就運作不了。
**41.**遠端線程注入的代碼基本都固定,死記硬背就行了,在此貼上。
#include "pch.h"
#include <windows.h>
#include <string.h>
//注入DLL函數
void InjectDll(HWND hWnd, const char* DllPath)
{
//擷取要注入的程序的PID并打開它
DWORD dwPid = 0;
GetWindowThreadProcessId(hWnd, &dwPid);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (!hProcess)
{
printf("打開程序失敗\n");
getchar();
return;
}
//在注入的程序中配置設定一塊虛拟記憶體
LPVOID lpAddr = VirtualAllocEx(hProcess,
NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE);
if (!lpAddr)
{
printf("配置設定記憶體失敗\n");
CloseHandle(hProcess);
getchar();
return;
}
//把dll路徑寫入到目标程序空間中
DWORD dwWrite = 0;
WriteProcessMemory(hProcess, lpAddr, DllPath,
strlen(DllPath) + 1, &dwWrite);
if (strlen(DllPath) + 1 != dwWrite)
{
printf("dll路徑寫入失敗\n");
VirtualFreeEx(hProcess, lpAddr, 0, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return;
}
//建立遠端線程
HANDLE hThread = CreateRemoteThread(hProcess, 0, 0,
(LPTHREAD_START_ROUTINE)LoadLibraryA,
lpAddr, 0, 0);
if (!hThread)
{
printf("遠端線程建立失敗\n");
VirtualFreeEx(hProcess, lpAddr, 0, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return;
}
//等待線程結束(無窮大毫秒)
WaitForSingleObject(hThread, INFINITE);
//釋放
VirtualFreeEx(hProcess, lpAddr, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
}
//動态庫DLL的名字
#define DLLNAME "\\01連連看輔助.dll"
int main()
{
//dll的全路徑名
char dllNamePath[256] = {};
//擷取DLL的全路徑
GetCurrentDirectoryA(sizeof(dllNamePath), dllNamePath);
strcat_s(dllNamePath, DLLNAME);
//擷取遊戲視窗句柄
HWND Handle = FindWindow(NULL, L"QQ連連看");
//調用遠端注入函數
InjectDll(Handle, dllNamePath);
return 0;
}
**42.**這裡說下遊戲輔助常用的API函數GetCurrentDirectoryA這個API可以擷取到本程式的目前目錄的字元串,隻要把要DLL放在與注入程式同一個目錄了,直接運作注入程式就注入DLL了,這個非常友善。
**43.**把DLL檔案和注入程式生成放在同一個目錄下,先打開遊戲,然後點選注入程式,單機版連連看的輔助就順利完成了,運作秒殺效果如圖。
最後感謝15PB的栽培
遊戲和源碼網盤連結:
連結:https://pan.baidu.com/s/1LyAn27Y__beBpCewMIr-WA
提取碼:oq08