1. 样本概况
1.1 应用程序信息
应用程序名称:连连看单机版
MD5值:
SHA1值:
简单功能介绍:
说明:如果是分析网页则记录网页的相关信息即可
1.2 分析环境及工具
Win7 32位操作系统,使用工具:OD,CE,Visual Studio 2019
2. 具体分析过程
2.1 找到程序本身的EXE
先打开游戏,会先进入广告界面,需要跳过这个界面。在任务管理器中关闭游戏进程,可以发现关闭kyodai.exe后游戏关闭,所以程序本身的exe应该是 kyodai.exe。
在文件夹中找到kyodai.exe,双击打开,发现不能打开,可以知道游戏的打开被广告程序限制了,我们需要分析广告程序找到打开游戏的部分。
2.2 去除程序的广告
由于不能直接打开程序本身的exe,程序是通过另一个广告程序打开的,所以我们可以分析一下广告程序是怎么打开游戏的,然后我们直接利用这个功能打开游戏。
来到广告程序中打开游戏的界面,发现点击继续之后会弹出游戏界面。
猜测这个程序使用了创建窗口或者创建进程的 API,所以我们用OD打开广告程序,然后在这些API中下断点,然后点击继续,看程序在OD中有没有断下来。
经过尝试发现程序在CreateProcess处断了下来。
查看堆栈,发现创建进程的地址为0x43817A,跟随地址,观察函数调用之后,将01修改成了00。
用LoadPE找到偏移地址,然后在010Ediotr中将对应的EXE中的值修改成00
修改之后保存,然后双击修改后的程序,进入了游戏,并且跳过了广告。
2.3 编写DLL完成外挂
我们要完成的外挂功能是可以自动匹配相同的方块,在游戏中有自动匹配方块的道具,我们的思路是利用游戏自带的道具,完成外挂功能。
首先我们要找到游戏中调用道具的地方。
找出道具的思路是,先在内存中找到存储方块的数组,然在这里下内存访问断点,再使用道具,因为道具会根据数组判断方块是否可以消,所以在断点处查看堆栈,里面就会有道具函数的调用。
要找出存储数组,我们考虑在可能调用的API处下断点,然后去查看哪个地方存储了数组。
因为地图的初始化是没有规律的,所以猜测可能调用了RAND函数来生成随机数。
我们在Rand函数处下一个断点,让程序运行起来,点击练习,会生成新的地图,并且程序也断了下来,我们按F8向下查看。
我们来到第二个CALL时,进入函数,发现只传入了一个参数,我们查看ECX,在数据窗口跟随。
再跟随第二个地址,找到数组。
在数组里下一个内存访问断点,然后使用指南针道具,当程序断下来时查看堆栈。
从里面可以找出调用指南针的函数。
给栈里可能的函数下断点,然后使用指南针道具看程序在哪里断下来。
找到之后我们创建一个MFC DLL来调用这个函数,来验证这个函数的功能。
首先创建一个MFC DLL项目,在初始化部分获取连连看窗口句柄,然后设置窗口回调函数,并且还需要弹出有一个对话框,在对话框里添加按钮,通过按按钮实现外挂功能。
通过调试之后写出相应部分的代码
①处的代码为:
CWinApp::InitInstance();
//1.查找扫雷窗口,获得句柄
g_Wnd = ::FindWindow(NULL, L"QQ连连看");
if (g_Wnd == NULL)
{
OutputDebugString(L"获取窗口句柄失败");
return FALSE;
}
//2.设置窗口回调函数
g_OldProc = (WNDPROC)SetWindowLong(g_Wnd, GWL_WNDPROC, (LONG)WindowProc);
if (g_OldProc == NULL)
{
OutputDebugString(L"设置窗口回调函数失败");
return FALSE;
}
②处的代码为:
//创建线程
_beginthreadex(0, 0, (_beginthreadex_proc_type)ThreadProc, 0, 0, 0);
③处的代码为:
//线程回调函数
unsigned __stdcall ThreadProc()
{
CMyDlg dlg;
dlg.DoModal();
return 0;
}
④处的窗口按钮:处的窗口按钮:
⑤处的代码为:
//使用指南针
void CMyDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
CLianLianKanPlugApp* pApp = (CLianLianKanPlugApp*)AfxGetApp();
::SendMessage(pApp->g_Wnd, WM_DATA1, 0, 0);
}
⑥处的代码为:
//窗口回调函数
LRESULT CALLBACK WindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
if (Msg == WM_DATA1)
{
//调用指南针道具
_asm
{
mov ECX, 0x45DEBC
mov ECX, [ECX]
LEA ECX, DWORD PTR DS : [ECX + 0x494]
push 0xF0
push 0
push 0
mov EAX,0x0041E691
call EAX
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
}
写好程序后生成DLL,将DLL注入练练看进程,点击使用指南针按钮。
接下来再实现消除功能。
先找到消除两个相同块的函数,采用之前找指南针函数的方法,先找到内存数组,然后下内存访问断点,手动消除两个相同方块,直到程序断下来,程序断下来后按F8继续走,直到内存中对应的区域的方块被消除,然后点击K查看堆栈,
经过排除和观察参数,找到一个很有可能是消除的CALL
我们通过分析参数,再VS中模拟参数的传递来调用这个CALL,来验证它的功能
DLL的代码如下:
POINT P1 = {0};
POINT P2 = {0};
//获得坐标
_asm
{
mov ecx, 0x045DEBC
mov ecx, [ecx]
lea ecx,DWORD PTR DS:[ecx+0x494]
mov ecx, DWORD PTR DS : [ecx + 0x19F0]
lea eax, P1.x;
push eax
lea eax, P2.x;
push eax
mov eax,0x0042923F
call eax
}
if (P1.x == 0 && P1.x == P1.y)
{
return -1;
}
//调用消除函数
_asm
{
mov ecx, 0x45DEBC
mov ecx, [ecx]
push 0x4
lea eax, DWORD PTR DS : [ecx + 0x494]
mov eax, DWORD PTR DS : [eax + 0x19F0]
add eax, 0x40
push eax
lea eax, P1.x;
push eax
lea eax, P2.x;
push eax
lea eax,DWORD PTR DS:[ecx+0x494]
mov eax,DWORD PTR DS:[eax+0x19F0]
mov eax,DWORD PTR DS:[eax+0x4]
push eax
push 0
mov eax,0x0041C68E
call eax
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
生成DLL,然后注入到游戏中。
经过测试,消除功能可以使用
再实现一个一键秒杀功能
我们只需要循环发送消息,让程序不断使用消除功能直道消除所有方块为止。
实现的代码如下:
//一键秒杀
void CMyDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
CLianLianKanPlugApp* pApp = (CLianLianKanPlugApp*)AfxGetApp();
for (int i = 0; i < 100; i++)
{
::SendMessage(pApp->g_Wnd, WM_DATA2, 0, 0);
if (i == -1)
{
break;
}
}
}
生成之后再注入连连看测试一下。
经过测试,一键秒杀功能也实现了。
3. 总结
经过几天的学习,终于实现了练练看的外挂,在完成练练看项目的过程中,我学到了新的逆向思路和方法。
可以通过搜索函数或API对它们下断点,然后在它们附近找需要的部分,并且通过堆栈查看在断点前都调用了哪些函数,然后在这些函数处下断点来找出需要分析的函数。
如果在内存中找到了要分析的数据,可以对这个数据下内存访问断点,看哪些地方对它进行了访问。
这些方法都是帮助我们快速定位到需要分析的函数或者数据,通过下断点和栈回溯的结合,定位函数非常方便。
另外还学习了在DLL中使用汇编来调用程序的函数,以及通过自定义消息的回调函数来使用这些函数。