連連看逆向及輔助工具開發

0. 準備工作

在這裏插入圖片描述

目標:

  1. 找到程序本身的exe
  2. 去掉程序中的廣告
  3. 實現輔助工具

先打開遊戲看一下,開始遊戲前有兩個彈窗,關閉後會打開一個網頁。

0.0 涉及知識

已知:

  • 這是一個vc6 MFC程序
  • 多進程(彈窗廣告)
  • 有動態修改代碼,直接複製exe代碼是不能執行的

涉及技術:

  • MFC dll
  • SetWindowLong修改回調函數
  • CallWindowProc調用指定回調函數
  • 多線程

0.1 分析思路

找到原程序exe:

  1. 加殼
  2. 多進程:需要進程遍歷,對CreateProcessA/W下斷調試(棧回溯)

去廣告:

  1. 對話框,網頁
  2. 涉及API:
    • DialogBoxA/W
    • CreateWindowExA/W
    • WinExec
    • CreateProcessA/W
    • ShellExecuteA/W

輔助:

  1. 分析+算法+模擬點擊:找到數組,一鍵自動連接
  2. 利用遊戲本身功能:分析指南針道具call

比如,一鍵調用指南針, 一鍵消除,秒殺(循環單消),炸彈(前提是有這個道具),

0.2 需要的數據

  • 連連看數組
  • 指南針call
  • 消除call

0.3 分析工具

OD,CE,PEid/exeinfo

VS開發。

0.4 測試框架

需要一個注入程序,一個靜態MFC dll。

unsigned __stdcall threadProc()
{
	CLLKAssistDlg* pAssistDlg = new CLLKAssistDlg;
	pAssistDlg->DoModal();
	return 0;
}

LRESULT CALLBACK wndProc()
{
    switch (msg){}
    return CallWindowProc(g_oldWndProc);
}
BOOL CInjectLLKApp::InitInstance()
{
    SetWindowLong(wndProc);
    _beginthreadex(threadProc);
}

爲了防止MFC的消息機制造成干擾,我們單獨創建一個線程發送自定義消息。

1. 開幹

1.0 分析exe

在這裏插入圖片描述

打開遊戲,關閉開始界面,通過pchunter可以看出真正的遊戲是kyodai.exe,但經過打包後它是不能直接打開的。

在這裏插入圖片描述

在彈出最後一個廣告的時候,OD附加廣告。這個廣告進程的後綴是ocx,其實只要在遍歷的進程中出現,本質還是exe。

由啓動過程來看,這個程序是多進程的,對CreateProcessA/W下斷,開始遊戲,斷下後分析API參數,看到CreationFlags==CREATE_SUSPEND,猜測掛起後會動態修改,然後喚醒。也就是創建掛起進程-修改-喚醒.

這裏的思路是,雙擊不能運行,那麼肯定有動態修改。

所以,我們要對WriteProcessMemory(), ResumeThread()下斷。

1.1 動態修改

斷在WriteProcessMemory後,觀察地址參數爲0x43817A,這就是kyodai進程被修改的地址,並且寫入了一個字節\0

在這裏插入圖片描述

往後分析,這應該是一個Themida虛擬機保護(push xxx; jmp xxx),vmp一般是push xxx; call xxx

可以再開一個OD附加kyodai進程,用dump的方式得到原exe。

也可以用十六進制編輯器修改RVA爲43817A的字節爲00.

在這裏插入圖片描述

用lordPE得到文件偏移。

在這裏插入圖片描述

可以看到這裏本來是0x01,修改爲0x00即可。注意這個文件的tab處有個小鎖,說明是隻讀的,所以要先copy一下再修改保存。

在這裏插入圖片描述

至此已經將廣告去除得到真正的遊戲exe,下面要實現輔助工具。

1.2 分析指南針道具

道具是有數量的,所以可以用CE。但失敗了。。。

1.3 分析數組

rand()下斷,重新開始,肯定停在初始化的地方,可以在這裏嘗試找基址。

在這裏插入圖片描述

這裏調用了rand,堆棧查看上一層:

0041CB15    8B8E 841E0000   MOV ECX,DWORD PTR DS:[ESI+0x1E84]        ; this
0041CB1B    83C4 0C         ADD ESP,0xC
0041CB1E    6A 01           PUSH 0x1
0041CB20    E8 E9C30000     CALL kyodai_c.00428F0E                   ; 初始化數組???

這裏有ecx,猜測是對象調用成員方法,可以查看ecx地址0x003CD418

003CD418  0044D07C  kyodai_c.0044D07C
003CD41C  0012BB50
003CD420  00000002
003CD424  00000002
003CD428  00000000

第一個成員應該是虛函數表,第二個地址指向棧,猜測是數組成員。

0012BB50  DC 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00  
0012BB60  00 00 00 00 00 01 00 00 00 00 00 00 00 00 01 01  
0012BB70  01 00 00 00 00 00 00 01 01 01 00 00 00 00 00 00  
...

上面是初始化之前,下面看看初始化之後是否發生變化。

0012BB50  DC 00 00 00 00 00 00 00 00 00 00 00 07 00 00 00  ?.............
0012BB60  00 00 00 00 00 0C 00 00 00 00 00 00 00 00 F6 09  ..............?
0012BB70  04 00 00 00 00 00 00 0E F7 05 00 00 00 00 00 00  ......?......
0012BB80  0A 0A F0 02 04 00 00 00 00 08 09 0C F6 05 00 00  ..?......?..
0012BB90  00 00 0A 02 09 0E 0F 0D 0E 00 00 F7 F6 07 F6 0D  .......黯?
0012BBA0  08 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ..............
0012BBB0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

在這裏插入圖片描述

000000DC可能是大小。後面4字節不確定。後面兩個0D應該就是黃球。

那麼,0x12BB58應該就是數組基址。

分析這個初始化

跳進call 0x00428F0E,一進去就把mov dword ptr [ebp-0xc], ecx,所以把[ebp-0xc]就是this。

下面有段關鍵代碼:

00428FEA    33C9            XOR ECX,ECX                              ; do{}
00428FEC    8B45 F4         MOV EAX,DWORD PTR SS:[EBP-0xC]           ; do{}
;...
00429007  ^ 7C E3           JL SHORT kyodai_c.00428FEC               ; while(ecx < 0x13)
00429009    83C7 13         ADD EDI,0x13
0042900C    81FF D1000000   CMP EDI,0xD1
00429012  ^ 7C D6           JL SHORT kyodai_c.00428FEA               ; while(edi < 0xD1)
00429014    56              PUSH ESI
00429015    E8 36E80000     CALL <JMP.&MFC42.#825>

從遊戲界面上數一下,0x13是每行的個數,0xD1是總數,所以這段代碼應該是按行初始化。

1.4 再次分析指南針道具

既然確定了數組,那麼就設置內存斷點,使用道具,斷下後查看堆棧,裏面肯定有一個調用是使用道具。最終確定了兩個call(其實有3層是內部調用關係,我們只關心最下面兩層):

0041DE4D    8B86 94040000   MOV EAX,DWORD PTR DS:[ESI+0x494]	;esi == 0x12A1F4
0041DE53    8D8E 94040000   LEA ECX,DWORD PTR DS:[ESI+0x494]
0041DE59    52              PUSH EDX	;f0		;這其實是道具ID
0041DE5A    53              PUSH EBX	;00
0041DE5B    53              PUSH EBX	;00
0041DE5C    FF50 28         CALL DWORD PTR DS:[EAX+0x28]             ; kyodai_c.0041E691

這段代碼可以用來輔助調用工具。

第二個call(包含在上一個call裏):

0041E6AC    8BF1            MOV ESI,ECX                              ; esi changed
;...
0041E75E    8B8E F0190000   MOV ECX,DWORD PTR DS:[ESI+0x19F0]	;this==[0x12A1F4+0x494+0x19F0]==[0x12A1F4+0x1E84]
0041E764    8D45 D8         LEA EAX,DWORD PTR SS:[EBP-0x28]		;0x00129D8C
0041E767    50              PUSH EAX
0041E768    8D45 E0         LEA EAX,DWORD PTR SS:[EBP-0x20]		;0x00129D94
0041E76B    50              PUSH EAX
0041E76C    E8 CEAA0000     CALL kyodai_c.0042923F

這裏傳入兩個地址,應該是傳出參數,傳出可以連接的兩個點,所以在內存裏監視一下。

00129D8C  00129DA4
00129D90  0012A254
00129D94  0012A254
00129D98  0040CA97  kyodai_c.0040CA97

調用之後:

00129D8C  0000000B
00129D90  00000001
00129D94  00000006
00129D98  00000001

再看下游戲:

在這裏插入圖片描述

(1,11), (1,6)處的兩個人可以相連。


如果想要一鍵調用這個工具,就需要this指針(ecx),根據上面的代碼,由esi+0x494加上偏移0x19f0得到,觀察一下esi的值是0x12A1F4,用CE找一下。

在這裏插入圖片描述
在這裏插入圖片描述

OD裏查找這3個常量,0x45DEBC多次使用,估計是全局變量。

在這裏插入圖片描述

mov ecx, 0x45DEBC;
mov ecx, [ecx];
mov eax, DWORD PTR DS : [ecx + 0x494];
LEA ECX, DWORD PTR DS : [ecx + 0x494];
PUSH 0xF0;
PUSH 00;
PUSH 00;
CALL DWORD PTR DS : [EAX + 0x28];

這段彙編可以用來一鍵調用指南針。

炸彈道具

同樣地思路分析一下炸彈道具會發現,第一個call的F0參數是指南針ID,換成F4就變成了調用炸彈。

1.5 分析消除調用

剛剛已經分析了獲取兩個點的call,還需要找到消除的call。

消除的時候會將數組對應位置清零,所以可以下內存寫入斷點。斷下後堆棧窗口給幾個call下斷,繼續遊戲,再次消除後分析這些call,根據參數個數找到消除call,參數裏應該有之前獲得的兩個點,最終鎖定了這個call:

0041AA90         MOV EBX,DWORD PTR SS:[EBP+0xC]           ; ebx=座標數組
;...
0041AB10         MOV EDI,DWORD PTR SS:[EBP+0x10]
0041AB13         PUSH EDI                                 ; num???
0041AB14         LEA EAX,DWORD PTR SS:[EBP-0xC]
0041AB17         PUSH EBX                                 ; addr 0x56D448
0041AB18         PUSH EAX                                 ; point 1
0041AB19         LEA EAX,DWORD PTR SS:[EBP-0x14]
0041AB1C         MOV ECX,ESI
0041AB1E         PUSH EAX                                 ; point 2
0041AB1F         MOVZX EAX,BYTE PTR SS:[EBP+0x8]
0041AB23         IMUL EAX,EAX,0xDC                        ; eax=0
0041AB29         LEA EAX,DWORD PTR DS:[EAX+ESI+0x195C]    ; esi==0x12A1F4
0041AB30         PUSH EAX                                 ; array
0041AB31         PUSH DWORD PTR SS:[EBP+0x8]              ; 0
0041AB34         CALL kyodai_c.0041C68E                   ; 6個參數

先看簡單的,最後一個參數是個變量,我們偷點懶,先不管他,push一個固定值4; 第0個參數是0,第1個參數是數組,這裏沒有用[0x12A1F4+0x1E84]獲取指針,而是另一種偏移。

兩個點也很好解決。問題是那個push ebx.

參數ebx(0x0064D448)是一個地址,觀察一下是兩個點的座標:

0064D448  00000009
0064D44C  00000003
0064D450  0000000A
0064D454  00000003

這個地址該怎麼獲取呢。堆棧回溯分析一下,上一層是一個有7個參數的調用:

0041B440                 MOV ECX,DWORD PTR DS:[ESI+0x1E84]        ; 0x494+0x19F0 == 0x1E84, esi == 0x12A1F4
0041B446                 LEA EAX,DWORD PTR SS:[EBP-0x18]
0041B449                 PUSH EAX                                 ; 傳出參數 ebp-0x18
0041B44A                 MOV DWORD PTR SS:[EBP-0x18],EBX
0041B44D                 CALL kyodai_c.00429025                   ; [ebp-0x18] == 下標數組
;....
0041B4AF                 PUSH EAX
0041B4B0                 PUSH 0x1
0041B4B2                 PUSH EDI
0041B4B3                 PUSH DWORD PTR SS:[EBP-0x18]             ; 座標數組
0041B4B6                 PUSH EBX
0041B4B7                 CALL kyodai_c.0041AA1D                   ; 7個參數

也就是說這個地址是上一層函數的一個局部指針變量[ebp-0x18]ebp-0x18作爲傳出參數在CALL kyodai_c.00429025處獲得下標數組,而且這個函數的this是我們熟悉的[ESI+0x1E84]。進去看看。

00429025    8B5424 04                 MOV EDX,DWORD PTR SS:[ESP+0x4]
00429029    8D41 30                   LEA EAX,DWORD PTR DS:[ECX+0x30]
0042902C    8902                      MOV DWORD PTR DS:[EDX],EAX
0042902E    8B41 50                   MOV EAX,DWORD PTR DS:[ECX+0x50]
00429031    C2 0400                   RETN 0x4

很短(極度舒適),這個地址是this+0x30

在這裏插入圖片描述

那麼,下面就是我們的一鍵消除消息處理代碼。

case WM_KILL:
	{
		OutputDebugStringW(L"recv WM_KILL");

		POINT point1 = { 0 };
		POINT point2 = { 0 };

		/******************
		*	Get 2 points
		******************/
		__asm {
			/*
			*	Help locate
			*/
			mov eax, eax;
			mov eax, eax;
			mov eax, eax;
		}
		__asm {
			mov ecx, 0x45DEBC;
			mov ecx, [ecx]; //ecx = 0x12A1F4;
			LEA ECX, DWORD PTR DS : [ecx + 0x494];
			mov ecx, dword ptr[ecx + 0x19f0];	//ecx = this
			LEA EAX, point1;
			PUSH EAX;
			LEA EAX, point2;
			PUSH EAX;
			mov eax, 0x0042923F;
			CALL eax;
		}

		CString str;
		str.Format(L"point1(%d, %d), point2(%d, %d)", point1.x, point1.y, point2.x, point2.y);
		OutputDebugStringW(str.GetBuffer());

		/******************
		*	消除
		********************/
		__asm {
			push 0x4;	//arg5

			mov ecx, 0x45DEBC;
			mov ecx, [ecx]; //ecx = 0x12A1F4;
			lea eax, DWORD PTR DS : [ecx + 0x494];
			mov eax, dword ptr[eax + 0x19f0];	//ecx = this
			add eax, 0x30;
			push eax; //arg4

			lea eax, point1;
			push eax;	//arg3
			lea eax, point2;
			push eax;	//arg2

			mov ecx, 0x45DEBC;
			mov ecx, [ecx];	//ecx = 0x12A1F4;
			lea eax, DWORD PTR DS : [ecx + 0x195c];//獲取數組的另一種方法
			push eax;	//arg1, array
			push 0;		//arg0

			mov eax, 0x41C68E;
			call eax;
		}

		break;
		//return DefWindowProc(hWnd, msg, wParam, lParam);
	}

一鍵秒殺

用一鍵消除全部消除後繼續消除,發現座標(0,0)出還有消除特效,所以這時尋找兩點會返回(0,0)

加個循環發送消息,條件判斷全部消除時返回-1就可以了。

消息處理代碼

LRESULT CALLBACK wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_POINT:
	{
		OutputDebugStringW(L"recv WM_POINT");
		__asm {
			/*
			*	Help locate
			*/
			mov eax, eax;
			mov eax, eax;
			mov eax, eax;
		}
		__asm {
			mov ecx, 0x45DEBC;
			mov ecx, [ecx];
			mov eax, DWORD PTR DS : [ecx + 0x494];
			LEA ECX, DWORD PTR DS : [ecx + 0x494];
			PUSH 0xF0;
			PUSH 00;
			PUSH 00;
			CALL DWORD PTR DS : [EAX + 0x28];
		}

		break;
		//return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	case WM_KILL:
	{
		OutputDebugStringW(L"recv WM_KILL");

		POINT point1 = { 0 };
		POINT point2 = { 0 };

		/******************
		*	Get 2 points
		******************/
		__asm {
			/*
			*	Help locate
			*/
			mov eax, eax;
			mov eax, eax;
			mov eax, eax;
		}
		__asm {
			mov ecx, 0x45DEBC;
			mov ecx, [ecx]; //ecx = 0x12A1F4;
			LEA ECX, DWORD PTR DS : [ecx + 0x494];
			mov ecx, dword ptr[ecx + 0x19f0];	//ecx = this
			LEA EAX, point1;
			PUSH EAX;
			LEA EAX, point2;
			PUSH EAX;
			mov eax, 0x0042923F;
			CALL eax;
		}



		CString str;
		str.Format(L"point1(%d, %d), point2(%d, %d)", point1.x, point1.y, point2.x, point2.y);
		OutputDebugStringW(str.GetBuffer());

		if (point1.x == 0 && point1.y == 0 && point2.x == 0 && point2.y == 0)
		{
			return -1;
		}

		/******************
		*	消除
		********************/
		__asm {
			push 0x4;	//arg5

			mov ecx, 0x45DEBC;
			mov ecx, [ecx]; //ecx = 0x12A1F4;
			lea eax, DWORD PTR DS : [ecx + 0x494];
			mov eax, dword ptr[eax + 0x19f0];	//ecx = this
			add eax, 0x30;
			push eax; //arg4

			lea eax, point1;
			push eax;	//arg3
			lea eax, point2;
			push eax;	//arg2

			mov ecx, 0x45DEBC;
			mov ecx, [ecx];	//ecx = 0x12A1F4;
			lea eax, DWORD PTR DS : [ecx + 0x195c];//獲取數組的另一種方法
			push eax;	//arg1, array
			push 0;		//arg0

			mov eax, 0x41C68E;
			call eax;
		}

		break;
		//return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	case WM_BOMB:
	{
		OutputDebugStringW(L"recv WM_BOMB");
		__asm {
			/*
			*	Help locate
			*/
			mov eax, eax;
			mov eax, eax;
			mov eax, eax;
		}
		__asm {
			mov ecx, 0x45DEBC;
			mov ecx, [ecx];
			mov eax, DWORD PTR DS : [ecx + 0x494];
			LEA ECX, DWORD PTR DS : [ecx + 0x494];
			PUSH 0xF4;
			PUSH 00;
			PUSH 00;
			CALL DWORD PTR DS : [EAX + 0x28];
		}
	}
	}
	//return DefWindowProc(hWnd, msg, wParam, lParam);
	return CallWindowProc(g_oldWndProc, hWnd, msg, wParam, lParam);
}

在這裏插入圖片描述

2. BUG

避免消息衝突,自定義消息最好大一些,我是從WM_USER+100開始的。

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