连连看逆向及辅助工具开发

文章目录

  • 0. 准备工作
    • 0.0 涉及知识
    • 0.1 分析思路
    • 0.2 需要的数据
    • 0.3 分析工具
    • 0.4 测试框架
  • 1. 开干
    • 1.0 分析exe
    • 1.1 动态修改
    • 1.2 分析指南针道具
    • 1.3 分析数组
      • 分析这个初始化
    • 1.4 再次分析指南针道具
      • 炸弹道具
    • 1.5 分析消除调用
      • 一键秒杀
    • 消息处理代码
  • 2. BUG

0. 准备工作

连连看逆向及辅助工具开发_第1张图片

目标:

  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

连连看逆向及辅助工具开发_第2张图片

打开游戏,关闭开始界面,通过pchunter可以看出真正的游戏是kyodai.exe,但经过打包后它是不能直接打开的。

在这里插入图片描述

在弹出最后一个广告的时候,OD附加广告。这个广告进程的后缀是ocx,其实只要在遍历的进程中出现,本质还是exe。

由启动过程来看,这个程序是多进程的,对CreateProcessA/W下断,开始游戏,断下后分析API参数,看到CreationFlags==CREATE_SUSPEND,猜测挂起后会动态修改,然后唤醒。也就是创建挂起进程-修改-唤醒.

这里的思路是,双击不能运行,那么肯定有动态修改。

所以,我们要对WriteProcessMemory(), ResumeThread()下断。

1.1 动态修改

断在WriteProcessMemory后,观察地址参数为0x43817A,这就是kyodai进程被修改的地址,并且写入了一个字节\0

连连看逆向及辅助工具开发_第3张图片

往后分析,这应该是一个Themida虚拟机保护(push xxx; jmp xxx),vmp一般是push xxx; call xxx

可以再开一个OD附加kyodai进程,用dump的方式得到原exe。

也可以用十六进制编辑器修改RVA为43817A的字节为00.

连连看逆向及辅助工具开发_第4张图片

用lordPE得到文件偏移。

连连看逆向及辅助工具开发_第5张图片

可以看到这里本来是0x01,修改为0x00即可。注意这个文件的tab处有个小锁,说明是只读的,所以要先copy一下再修改保存。

连连看逆向及辅助工具开发_第6张图片

至此已经将广告去除得到真正的游戏exe,下面要实现辅助工具。

1.2 分析指南针道具

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

1.3 分析数组

rand()下断,重新开始,肯定停在初始化的地方,可以在这里尝试找基址。

连连看逆向及辅助工具开发_第7张图片

这里调用了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  ................

连连看逆向及辅助工具开发_第8张图片

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 

从游戏界面上数一下,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

再看下游戏:

连连看逆向及辅助工具开发_第9张图片

(1,11), (1,6)处的两个人可以相连。


如果想要一键调用这个工具,就需要this指针(ecx),根据上面的代码,由esi+0x494加上偏移0x19f0得到,观察一下esi的值是0x12A1F4,用CE找一下。

连连看逆向及辅助工具开发_第10张图片
连连看逆向及辅助工具开发_第11张图片

OD里查找这3个常量,0x45DEBC多次使用,估计是全局变量。

连连看逆向及辅助工具开发_第12张图片

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

连连看逆向及辅助工具开发_第13张图片

那么,下面就是我们的一键消除消息处理代码。

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

连连看逆向及辅助工具开发_第14张图片

2. BUG

避免消息冲突,自定义消息最好大一些,我是从WM_USER+100开始的。

你可能感兴趣的:(#,re,#,逆向项目)