目录
一、背景介绍
二、去广告实现
2.1 分析广告加载流程
2.2 逆向分析广告加载逻辑
2.3 去广告方案
三、一键消除外挂实现
3.1 分析游戏逻辑
3.2 编写外挂插件
3.3 注入外挂:
四、一键消除效果展示
五、额外扩展
一、背景介绍
QQ 连连看是一款经典的休闲小游戏,但部分版本中嵌入了广告,影响了用户体验。本文将详细介绍如何通过逆向工程和编程技术,去除游戏中的广告,并实现一键消除功能。
二、去广告实现
2.1 分析广告加载流程
1. 运行程序:
双击 `qqllk.exe`,弹出第一个广告窗口。
点击“开始游戏”按钮,弹出第二个广告窗口(百度广告)。
点击“继续”按钮后,真正的游戏窗口 `kyodai.exe` 启动。
通过 PChunter32 查看可知 qqllk.ocx 是 qqllk.exe 的子进程广告程序,qqllk.exe 又是 explorer.exe 的子进程。在百度广告窗口点击继续按钮就打开了真正的游戏窗口 kyodai.exe。
2.程序结构:
使用 `PEid` 扫描程序文件夹,发现以下文件:
`qqllk.exe`:Delphi 编写的程序。
`qqllk.ocx`:是一个加 aps 壳可执行文件,负责广告加载。
`kyodai.exe`:真正的游戏程序。
火绒剑检测 Qqllk.ocx 行为,发现修改了 Keyodai.exe
直接运行 `kyodai.exe` 无法启动游戏,因为 `qqllk.ocx` 修改了 `kyodai.exe` 的行为。
2.2 逆向分析广告加载逻辑
1. 使用 OD(OllyDbg)调试:
将 `qqllk.exe` 拖入 OD,在 `CreateWindowExW` 和 `CreateProcessW` API 下断点。
分析广告窗口的创建和游戏进程的启动过程。
2. 广告窗口分析:
第一个广告窗口通过 `CreateWindowExW` 创建。
第二个广告窗口通过 `CreateProcessW` 启动 `qqllk.ocx`。
下图是第二个程序进程
第二个广告窗口
打开第二个 OD,附加或者直接打开第二个广告程序,但是要先设置 strongOD 才能调试多个
然后重启 OD。CreateprocessA 或 W 下断点
查看到 kyodai.exe 被创建即被挂起
直接用另一个 OD 打开游戏程序找到 winmain 函数
跟进去看看,一看貌似没有恢复
当执行完第二个广告后 Winmain 代码被改变,游戏程序便能正常执行
猜测是第二个广告程序在创建游戏进程挂起的时候修改了 winmian 的地方。
那么我们可以在 Qqllk.ocx 程序中的 resumethread 和 writeprocessmemory 下断点
然后执行唤起操作
总结游戏进程修改:
`qqllk.ocx` 在启动 `kyodai.exe` 时,将其挂起并修改其内存(如 `WinMain` 函数)。
使用 OD 附加 `kyodai.exe`,发现 `WinMain` 函数在广告加载后被修改。
2.3 去广告方案
由此去广告暂时有了两个方案:
1、只要启动 keyoai.exe 到指定的 0x43817A 地址上修改,使用 loadPE 计算出这个地址在文件
中的位置,然后将其值修改成 0 就可以了。
2、通过运行第二个广告程序后,运行到游戏程序的 OEPC 处直接 dump。
直接用第二种方式:
现在第一个 OD 运行 Qqllk.ocx,执行到唤起处,
另外一个 OD 附加 keyoai.exe,然后查看内存,查看 PE 头,找到 OEP 下断,
第一个 OD 继续执行,另外一个 OD 继续执行到 OEP 处直接 dump 出来即可。
到此处 dump
然后可以直接双击打开运行 dump 出来的程序,即可正常运行。
三、一键消除外挂实现
3.1 分析游戏逻辑
思路一 : 指南针 配合清除函数
1.根据 ce 搜索到减少道具数值的地方
2.跟出减少道具数值函数
3.来到 调用减少道具数值函数的地方
4.查找是谁调用的减少道具数值函数
5.即可找到关键点 道具分发函数 f0-fb 间
6.可以通过该函数调用多个道具
思路二 : 查找炸弹函数
可以先从简单的思路二入手,尝试查找炸弹函数,内联汇编调用
经过观察,可以通过炸弹的声效文件 flystar.wav 查找所有参考文本字串
查找所有命令 push 00459250
全部命令下断点,再次运行游戏,玩到点击炸弹的时候,断点在下图
然后查看堆栈调用
回车,在此下断点,重新运行游戏(注意每次运行游戏可以选 A 级别然后点击练习可以简
单点),玩到获得炸弹道具后,点击炸弹道具,运行到此后单步步过执行
执行到此发现 edx 发生变化,多次执行其他道具可以发现
指南针对应 edx = F0
重列对应 edx = F1
炸弹对应 edx = F4
执行完 0041de5c 处后断到 0041ec07,执行完后炸弹减少,两个卡牌消失
在此往上找到 case F4,可知是道具分支调用
因此 0041de5c 处 call 的是道具分发函数
由此内嵌调用以下汇编代码即可调用炸弹做出一个小外挂
0041DE4D |. 8B86 94040000 MOV EAX,DWORD PTR DS:[ESI+0x494] ; dump.0044CE70
0041DE53 |. 8D8E 94040000 LEA ECX,DWORD PTR DS:[ESI+0x494]
0041DE59 |. 52 PUSH EDX
0041DE5A |. 53 PUSH EBX
0041DE5B |. 53 PUSH EBX
0041DE5C |. FF50 28 CALL DWORD PTR DS:[EAX+0x28]
据观察,edx,ebx的赋值容易,但是要找esi的值。
但是由于栈保存的值不是固定的,所以要找到固定的。
可以打开CE软件,打开调试进程查找12A1F4,找到几个静态地址
经过测试上面三个绿色地址都可以赋值给esi。
3.2 编写外挂插件
1. 创建 MFC DLL 工程:
使用 Visual Studio 创建 MFC DLL 项目,命名为 `QQPlugin`。
2. 关键代码实现:
在 `QQPlugin.cpp` 中实现炸弹功能的调用:
#include "stdafx.h"
#include "QQPlugin.h"
#include "QQPlugindll.h"
#ifdef _DEBUG
#define new *DEBUG_NEW*
#endif
// CQQPluginApp
*BEGIN_MESSAGE_MAP*(CQQPluginApp, *CWinApp*)
*END_MESSAGE_MAP*()
// CQQPluginApp 构造
CQQPluginApp::CQQPluginApp()
{
// TODO: 在此处添加构造代码,
// 将所有重要的初始化放置在 InitInstance 中
}
// 唯一的一个 CQQPluginApp 对象
CQQPluginApp theApp;
// CQQPluginApp 初始化
#define WM_MOD_WINNAME *WM_USER*+110
*LRESULT* *CALLBACK* WindowProc(*HWND* hWnd, *UINT* uMsg,*WPARAM* wParam, *LPARAM* lParam)
{
if (uMsg== WM_MOD_WINNAME)
{
*OutputDebugString*(L"ok");
}
else if (uMsg== *WM_KEYDOWN*)
{
if (wParam== *VK_F**1*)
{
int BaseAddr = 0x45DEBC;
int m_Esi = *(int*)BaseAddr;
_asm
{
mov esi, [m_Esi];
mov eax, dword *ptr* ds : [esi + 0x494];
lea ecx, dword *ptr* ds : [esi + 0x494];
*push* 0xf4;
*push* ebx;
*push* ebx;
*call*dword *ptr* ds : [eax + 0x28];
}
}
return *DefWindowProc*(hWnd, uMsg, wParam, lParam);
}
return *CallWindowProc*(theApp.m_pOldProc, hWnd, uMsg, wParam, lParam);
}
*UINT* __stdcall ThreadProc(*PVOID* pVar)
{
// 初始化,防止崩溃
*AFX_MANAGE_STATE*(*AfxGetStaticModuleState*());
QQPlugindll* pDlg= new QQPlugindll;
pDlg->*Create*(IDD_DIALOG1);
pDlg->*ShowWindow*(*SW_SHOW*);
pDlg->*RunModalLoop*();
return 0;
}
*BOOL* CQQPluginApp::InitInstance()
{
//CWinApp::InitInstance();
*OutputDebugString*(L"InitInstance");
m_hMainWnd= *FindWindow*(*NULL*, L"QQ连连看");
m_pOldProc = (*WNDPROC*)*SetWindowLongPtr*(m_hMainWnd, *GWLP_WNDPROC*,
(*LONG_PTR*)WindowProc);
::*SendMessage*(m_hMainWnd, WM_MOD_WINNAME, 0, 0);
// 创建线程,在线程中启动一个对话框
*_beginthreadex*(0, 0, ThreadProc, this, 0, 0);
return *TRUE*;
}
在 `QQPlugindll.cpp` 中实现一键消除功能:
#include "stdafx.h"
#include "QQPlugin.h"
#include "QQPlugindll.h"
#include "afxdialogex.h"
// QQPlugindll 对话框
*IMPLEMENT_DYNAMIC*(QQPlugindll, *CDialogEx*)
QQPlugindll::QQPlugindll(*CWnd** pParent /*=NULL*/)
: *CDialogEx*(IDD_DIALOG1, pParent)
{
}
QQPlugindll::~QQPlugindll()
{
}
void QQPlugindll::DoDataExchange(*CDataExchange** pDX)
{
*CDialogEx*::*DoDataExchange*(pDX);
}
*BEGIN_MESSAGE_MAP*(QQPlugindll, *CDialogEx*)
*ON_BN_CLICKED*(IDC_BUTTON1, &QQPlugindll::OnBnClickedButton1)
*END_MESSAGE_MAP*()
// QQPlugindll 消息处理程序
//循环炸弹
void QQPlugindll::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
for (int i= 0; i< 100; i++)
{
::*SendMessage*(theApp.m_hMainWnd, *WM_KEYDOWN*, *VK_F**1*, 0);
}
}
3.3 注入外挂:
使用注入工具将生成的 DLL 文件注入到游戏进程中。
运行游戏后,点击外挂窗口的“一键消除”按钮,即可实现一键消除功能。
四、一键消除效果展示
一键消除效果:点击外挂窗口的按钮,快速消除所有卡牌。
五、额外扩展
一键消除解决完后,可以再说说思路一的做法:
每次按练习后都会随机生成地图,由此可以给关键api函数下断,bp rand
找到地图数据
483AC8 地图数据地址
483AC8内存拷贝给12BB50
12BB50=[Esi+0x195C] 地图地址
地图数组首地址是12BB50,但是数据开始的地方是12BB58,前面八个字节没用。
在点击游戏中两张相同卡牌或点击指南针之前,找两个相同数字靠在一起的下硬件访问断点
尝试断下来通过栈回溯找指南针函数和消除函数的地方
总结
通过逆向分析和编程技术,我们成功去除了 QQ 连连看中的广告,并实现了一键消除功能。本文详细介绍了去广告和外挂实现的思路与步骤,希望对读者有所帮助。需要注意的是,此类技术仅用于学习和研究,请勿用于非法用途。