0012AC5E:指南针数量不变
0012A748:时间不变
0012AC6E:重列道具不变
有了基址,就可以构造不变化的参数,就可以手动调用程序内部的参数
编写注入程序:Injector.exe
编写被注入dll:MFCGamePlugin.dll(win10虚拟机
Injector.cpp源码
MFCGamePlugin.cpp关键代码
要想消除,就需要获得可以消除的两个点,由上知,指南针call中最里面的那个0041E76C,就可以提供两个点(且可以消除
在手动找两个可消除的两个点,消除过程中,必然会访问连连看数组(将相应位置置为0),在数组处下内存写入断点,会断下来0040FF5F ,然后删除内存断点,F2下断,通过栈回溯,不断找“消除”时会调用的call
(注意,写入哪个会在哪个断下,以每个字节为单位,并非写入数组中任意一个位置,都会断下,所以,根据点击的那两个将要消除的点,在数组内存所在处相应位置下断)
从外到里,找到如下call:0041B4B7 -0041AB34 -0041C6C3,
就像指南针那个一样,必然不止一个,由内而外/由外而内依次检查每一个call,看其做了什么工作,检查其参数都是干嘛的(看push了谁,代码或堆栈中看),看看哪个传入了点的坐标(要想消除,就需要这两个点)
0041B4B7:内部retn 0x1c=28=7个参数,从栈顶依次找7个参数,就参数2靠谱点,是一个地址(其他都是数,一看就不是点坐标),数据窗口中跟随,确实是两个点坐标,再看游戏窗口中点击的那两个待消除的点,确实也符合,但是是一个地址中保存了两个点,而非理想中的一个参数对应一个点,先记下,继续往后找(尽量往里找,找更满足条件的)
0041AB34 :enter进入call的内部,在最后retn 0x18=24=6个参数,参数1-0、参数2-连连看数组地址、参数3-点1坐标、参数4-点2坐标、参数5-同上一样,有那两个点的坐标,暂记做坐标点数组、参数6-数值2,这么一看,这个call相当靠谱
最里层那个call4个参数,不太靠谱,故从里往外,倒数第二个即为目标call,0041AB34,通过其来构造汇编代码,实现程序外调用
难点及重点:如何构造call这个函数相应的6个参数?通过程序中汇编代码来构造,在call之前,第一个push处下断,看每一个参数的值都是怎么来的(追本溯源)
参数2/5比较难找:参数2=12BB50,参数5=1A5DE18,看这俩值怎么构造,是这么x+y=的
注意一点:call单次消除用到获取两点坐标功能,而后者又是在call指南针功能中调用的,就像注释中所说的
lea ecx, DWORD PTR DS : [ecx + 0x494]// 要加上此,原程序中,此函数是在call指南针内部call的
mov ecx, DWORD PTR DS : [ecx + 0x19F0]// 即在前面的基础上调用的,因此ecx...
注意:不仅要构造模拟参数,还有注意各个寄存器的值(用不到的就不管),如ecx=0012A1F4,在基址中45DEBC存储,它是好找的
就是单次消除功能的循环,设置一个停止条件即可,点坐标的x/y==0
关键代码
不断测试,当点击两个炸弹成功消除后,会出现炸弹道具
同理,找到相应的call,观察参数,发现同指南针相比,就是F0换成了F4,二者就是一样的思路来的
炸弹一次就相当于单次消除,加上循环便是秒杀(此时循环没有加停止条件,仅限制了循环次数,无伤大雅,有那个意思就行
版本1的单次消除和秒杀:获取两个点+手动消除,本思路借用炸弹道具,明显简单多了,也提了个醒,逆向时,要举一反三,通过指南针道具的调用,联想其他工具,程序员一般都是按照同一个思路来做的,无非换个参数(时间有限,其他道具也是这个道理,学到思路即可
关键代码
PS:流水账的写法,图片说明较少,简单记录为日后翻看方便
文件: qqllk.exe
大小: 1796608 bytes
修改时间: 2011年11月15日, 19:37:34
MD5: 814DE98DC72E4AB0001BA7F287239D2D
SHA1: 0DF1596821188E2333B8F5B8AE94DB775C796B3F
CRC32: 8437D6BE
文件: qqllk.ocx
大小: 903168 bytes
修改时间: 2008年8月8日, 0:00:00
MD5: 58293D9765FD06BCDFFA638CB596E76C
SHA1: 64489B5B2A38A64004D63064761D46350052976C
CRC32: 3C82EFC8
文件: kyodai.exe
大小: 417857 bytes
文件版本:0, 10, 4, 10
修改时间: 2008年8月8日, 0:00:00
MD5: AFE5BAEB6F29A197BC935286F55D9366
SHA1: DBB8866904F9621010B428CE12777B392C3F06CE
CRC32: 0C1469BB
//Injector.exe
#include <iostream>
#include <windows.h>
using namespace std;
//要加载的dll路径
// 最好改为相对路径(相对于连连看程序的
// WCHAR szDllPath[] = L"C:\\Users\\15pb-win7\\Desktop\\MFCGamePlugin.dll";
WCHAR szDllPath[] = L"../../MFCGamePlugin.dll";
int main()
{
//1.要注入,需要dll文件
//2.找到要注入的进程PID
DWORD dwPid=0;
//HWND hwnd = FindWindow(NULL, L"new 1 - Notepad++");
//GetWindowThreadProcessId(hwnd, &dwPid);
printf("please input PID>> ");
scanf_s("%d", &dwPid);
//3.打开进程,获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
//4.在目标进程中申请空间
LPVOID pBuff = VirtualAllocEx(
hProcess,
0,
sizeof(szDllPath),
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);
//5.将路径写入到目标进程中
DWORD dwSize;
WriteProcessMemory(
hProcess,
pBuff, //在指申请的地址上
szDllPath, //写入的内容
sizeof(szDllPath),//写入大小
&dwSize
);
//6.使用关键函数加载目标dll
// 利用远程创建线程函数,实现目标进程加载dll
// 远程线程执行函数直接指向LoadLibaray函数,同时参数指向dll路径,完美实现加载dll
HANDLE hThread = CreateRemoteThread(
hProcess,
NULL,
NULL,
(LPTHREAD_START_ROUTINE)LoadLibrary, //线程执行地址指向LoadLibrary
pBuff, //线程的附加参数dll路径
NULL, NULL
);
//7 释放句柄
CloseHandle(hProcess);
CloseHandle(hThread);
}
if (Msg == WM_DATA1)
{
OutputDebugString(L"无限指南针");
//0041DE4D | . 8B86 9404000 > MOV EAX, DWORD PTR DS : [ESI + 0x494]
//0041DE53 | . 8D8E 9404000 > 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]; 使用指南针道具
_asm
{
mov ecx, 0x45DEBC
mov ecx, [ecx]
LEA ECX, DWORD PTR DS : [ecx + 0x494]
PUSH 0xF0// 若炸弹,则F4
PUSH 0
PUSH 0
mov eax, 0x0041E691
call eax
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
// MFCGamePlugin.cpp
// 循环消除中,判断是否停止
if (pt1.x == 0 && pt1.y == 0)
{
return -1;
}
//CMyDlg.cpp
void CMyDlg::OnBnClickedButton3()
{
// TODO: 在此添加控件通知处理程序代码
CMFCGamePluginApp* pApp = (CMFCGamePluginApp*)AfxGetApp();
// 循环消除
for (int i = 0; i < 100; i++)
{
int nRet = ::SendMessage(pApp->m_hWnd, WM_DATA2, 0, 0);
if (nRet == -1)
break;
}
}
else if (Msg == WM_DATA2)
{
// 1 获取两个点坐标
POINT pt1 = { 0 };
POINT pt2={ 0 };
// 小技巧,用于调试,当注入成功时,ctrl+s 搜索指令找到此dll地址
//_asm
//{
// mov eax,eax
// mov eax,eax
//}
//0041E75E > \8B8E F0190000 MOV ECX, DWORD PTR DS : [ESI + 0x19F0]; Case F0(BM_GETCHECK) of switch 0041E749
//0041E764 . 8D45 D8 LEA EAX, DWORD PTR SS : [EBP - 0x28]
//0041E767 . 50 PUSH EAX
//0041E768 . 8D45 E0 LEA EAX, DWORD PTR SS : [EBP - 0x20]
//0041E76B . 50 PUSH EAX
//0041E76C.E8 CEAA0000 CALL kyodai2.0042923F; 提示待连接的两个坐标
_asm
{
mov ecx, 0x45DEBC
mov ecx, [ecx]
lea ecx, DWORD PTR DS : [ecx + 0x494]// 要加上此,原程序中,此函数是在call指南针内部call的
mov ecx, DWORD PTR DS : [ecx + 0x19F0]// 即在前面的基础上调用的,因此ecx...
lea eax, pt1.x
push eax// 原程序,push的是栈地址
lea eax, pt2.x
push eax
mov eax,0x0042923F
call eax
}
CString strCode;
strCode.Format(L"单次消除: 点1 x=%d,y=%d,点2 x=%d,y=%d", pt1.x, pt1.y, pt2.x, pt2.y);
OutputDebugString(strCode.GetBuffer());
// 循环消除中,判断是否停止
if (pt1.x == 0 && pt1.y == 0)
{
return -1;
}
// 2 调用消除call
//0041AB13 | > \57 PUSH EDI; 参数6:2(当前edi = 2
//0041AB14 | . 8D45 F4 LEA EAX, [LOCAL.3]
//0041AB17 | . 53 PUSH EBX; 参数5:坐标数组( = 1A5DE18 = ? + ?
//0041AB18 | . 50 PUSH EAX; 参数4:点2坐标(eax来自local3,就是点坐标
//0041AB19 | . 8D45 EC LEA EAX, [LOCAL.5]
//0041AB1C | . 8BCE MOV ECX, ESI
//0041AB1E | . 50 PUSH EAX; 参数3:点1坐标(eax来自local5,就是点坐标
//0041AB1F | . 0FB645 08 MOVZX EAX, BYTE PTR SS : [EBP + 0x8]; eax = 0
//0041AB23 | . 69C0 DC000000 IMUL EAX, EAX, 0xDC; eax = 0
//0041AB29 | . 8D8430 5C1900 > LEA EAX, DWORD PTR DS : [EAX + ESI + 0x195C]
//0041AB30 | . 50 PUSH EAX; 参数2:连连看数组地址( = 12BB50 = ? + ?
//0041AB31 | .FF75 08 PUSH[ARG.1]; 参数1:0(栈中可得,arg1为0
//0041AB34 | .E8 551B0000 CALL kyodai2.0041C68E; 6个参数,相当靠谱,就是他了
_asm
{
// 传递ecx,尤其重要,基地址!!
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
// 第三个参数 坐标1
lea eax, pt1.x
push eax
// 第四个参数 坐标2
lea eax, pt2.x
push eax
// 第五个参数 数组地址
lea eax, DWORD PTR DS : [ecx + 0x494]
mov eax, DWORD PTR DS : [eax + 0x19F0]
mov eax, DWORD PTR DS : [eax + 4]
push eax
// 第六个参数 0
push 0
// 调用函数
mov eax,0x0041C68E
call eax
}
return DefWindowProc(hWnd, Msg, wParam, lParam);// 要加此,否则运行完自动结束
}
else if (Msg == WM_DATA3)
{
OutputDebugString(L"无限炸弹");
//0041DE4D | . 8B86 9404000 > MOV EAX, DWORD PTR DS : [ESI + 0x494]
//0041DE53 | . 8D8E 9404000 > 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]; 使用指南针道具
_asm
{
mov ecx, 0x45DEBC
mov ecx, [ecx]
LEA ECX, DWORD PTR DS : [ecx + 0x494]
PUSH 0xF4// 若指南针,则F0
PUSH 0
PUSH 0
mov eax, 0x0041E691
call eax
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return CallWindowProc(g_oldProc,hWnd,Msg,wParam,lParam);
- 系统环境: Windows10-64位、Windows7-32位<!--more-->
- 工具: 010Editor、OllyDebug、DbgView、Cheat Engine、PCHunter32、VS 2017
- 找到原程序exe、去广告
- 实现连连看外挂:无限指南针、单次消除、秒杀
-
0012AC5E:指南针数量不变
-
0012A748:时间不变
-
0012AC6E:重列道具不变
- 外挂地址:https://github.com/Ry1ynn/QQllkGamePlugin
- kyodai双击不可运行
- qqllk可运行,打开就是一个广告,点击“开始游戏”进入下一窗口
- 再点击“OK我知道了”再到下个窗口,时刻关注进程列表(火绒剑),发现此时创建了一个新进程qqllk.ocx,虽然看起来后缀不是exe,但是既然出现在了进程列表,其本质就是一个exe
- 点击“继续”,kyodai进程创建,qqllk.ocx进程关闭,但qqllk.exe进程依然在运行
- 由此可判定,kyodai是真正的游戏程序,而qqllk是在其基础上,打包了许多广告的程序,去广告,也就是将k从q中分离出来
- OD附加那个ocx(而非qqllk.exe),因为是ocx创建出kyodai的
- 分析:q创建k进程,必然用到创建进程API,ctrl+g搜索CreateProcessA/W并下断
- 运行,停在762E2082 上,看一下堆栈中的参数,CreationFlags = CREATE_SUSPENDED,创建进程后,是暂停状态
- 再开一个OD来附加k,用来测试
- 分析:直接运行k失败,但是通过q就能使其运行,推测q创建进程后,一定是修改了q进程中某些东西,才使其可以运行的
- 因此,搜索WriteProcessMemory-下断-运行,发现断在759246C7 ,观察堆栈中参数,得出:往目标进程43817a处、写入一个字节、00
- 修改完之后,再resumeThread来恢复线程,因此流程就是:创建进程-修改进程-恢复线程
- 根据修改进程时的参数,即往目标进程43817a处、写入一个字节、00,手动修改k程序即可达到目的
- 打开LordPE,拖入Kyodai,位置计算,43817a-40000=3817a得到RVA(OD附加了那个新创建的进程,E-看到其模块基址为400000),将RVA填入得到文件偏移,也是43817a
- 010editor打开K程序,ctrl+g跳到文件偏移出,手动将值修改为0(注:远程序只读不可写入,可file-save a copy复制一份再修改
- 修改后,得到新的K程序,命名为Kyodai-noAd,双击可直接运行,至此,提取成功,也就去除了广告
- ctrl+g:找rand函数,得7623C070 (rand实现处
- 栈回溯:得0041CAF8 (rand调用处
- rand调用后,在0041CB10 ,有一个memcpy,dst为0012BB50,数据窗口跟随
- 运行完memcpy后,内存窗口有明显变化,且较有规律,推测为连连看数组
- 不断点击“练习”,并对比内存窗口,空白处为00,不断测试,证实上述猜想
- 数组处下内存访问断点(硬件断点无效),点击“指南针”,断在此处
- K-调用堆栈处,一层一层下断点,逐个测试(先随便下5、6个断点
- 一次测试,多次循环断下的位置先排除,效果应该是一点“指南针”就断,满足此条件的有:0040CACA -0041AF11 -0041DE5C -0041E76C ,故最外层的为0040CACA(当前测试的几个断点中最外层),最里层的为 0041E76C
- 由内而外,依次来看每个call的具体作用
- 最里层的0041E76C:两个参数,将局部变量赋值为eax再push(dword ptr,4个字节),分别为:00129D8C 、00129D94 ,将两个地址数据窗口跟随(分别M1、M2便于观察),call完成之后,返回值通过push进去的地址体现(指针间接修改),94前4个字节为20,8c前4字节为49,对比游戏界面,为两个坐标,也就是用了指南针用于提示的两个位置,故此处call,获得待连接的两个位置
- 再往外一层0041DE5C:虽连续三个push,但都是些没意义的参数,故推测,其仅仅是一个“使用道具”的函数, (因为参数无意义,故推测其并没有完成什么实质性功能,其调用仅仅为了进一步调用那些有用的函数),call了0041E691,推测这就是“指南针道具”的函数(因为有多个工具,call后面跟具体的哪个道具
- 由上,只要主动调用0041DE5C,倒数第二的那个函数即可
- 要调用就要手动传入正确的参数,以假装它是在正常的程序内部调用的;
- 参数除了通过push进去那三个(栈传递的),还可能是通过ecx寄存器传递的(thiscall)即那个lea ecx,[esi+xx],事实上,也就ecx那个参数看起来靠谱,像一个地址,但是简单将ecx作为参数是不可的,其地址esi+xx=12xx,一看就是栈空间的局部变量,具有不确定性,可能每次运行都不一样,因此,追本溯源,网上找ecx究竟是哪来的(要找到一个基地址
- 经测试,一直向上找是找不尽的,故换思路
- esi是0012A1F4,CE中搜索,发现有几个绿色的地址(即基址),有了基址就能在程序外手动的调用函数(不应该用变值作为参数),几个基址:45DCF8、45DEBC(用这个)、47FDEO、777FEDE8(7开头,暂不考虑
-
有了基址,就可以构造不变化的参数,就可以手动调用程序内部的参数
-
编写注入程序:Injector.exe
-
编写被注入dll:MFCGamePlugin.dll(win10虚拟机
-
Injector.cpp源码
//Injector.exe
#include <iostream>
#include <windows.h>
using namespace std;
//要加载的dll路径
// 最好改为相对路径(相对于连连看程序的
// WCHAR szDllPath[] = L"C:\\Users\\15pb-win7\\Desktop\\MFCGamePlugin.dll";
WCHAR szDllPath[] = L"../../MFCGamePlugin.dll";
int main()
{
//1.要注入,需要dll文件
//2.找到要注入的进程PID
DWORD dwPid=0;
//HWND hwnd = FindWindow(NULL, L"new 1 - Notepad++");
//GetWindowThreadProcessId(hwnd, &dwPid);
printf("please input PID>> ");
scanf_s("%d", &dwPid);
//3.打开进程,获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
//4.在目标进程中申请空间
LPVOID pBuff = VirtualAllocEx(
hProcess,
0,
sizeof(szDllPath),
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);
//5.将路径写入到目标进程中
DWORD dwSize;
WriteProcessMemory(
hProcess,
pBuff, //在指申请的地址上
szDllPath, //写入的内容
sizeof(szDllPath),//写入大小
&dwSize
);
//6.使用关键函数加载目标dll
// 利用远程创建线程函数,实现目标进程加载dll
// 远程线程执行函数直接指向LoadLibaray函数,同时参数指向dll路径,完美实现加载dll
HANDLE hThread = CreateRemoteThread(
hProcess,
NULL,
NULL,
(LPTHREAD_START_ROUTINE)LoadLibrary, //线程执行地址指向LoadLibrary
pBuff, //线程的附加参数dll路径
NULL, NULL
);
//7 释放句柄
CloseHandle(hProcess);
CloseHandle(hThread);
}
-
MFCGamePlugin.cpp关键代码
if (Msg == WM_DATA1)
{
OutputDebugString(L"无限指南针");
//0041DE4D | . 8B86 9404000 > MOV EAX, DWORD PTR DS : [ESI + 0x494]
//0041DE53 | . 8D8E 9404000 > 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]; 使用指南针道具
_asm
{
mov ecx, 0x45DEBC
mov ecx, [ecx]
LEA ECX, DWORD PTR DS : [ecx + 0x494]
PUSH 0xF0// 若炸弹,则F4
PUSH 0
PUSH 0
mov eax, 0x0041E691
call eax
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
-
要想消除,就需要获得可以消除的两个点,由上知,指南针call中最里面的那个0041E76C,就可以提供两个点(且可以消除
-
在手动找两个可消除的两个点,消除过程中,必然会访问连连看数组(将相应位置置为0),在数组处下内存写入断点,会断下来0040FF5F ,然后删除内存断点,F2下断,通过栈回溯,不断找“消除”时会调用的call
-
(注意,写入哪个会在哪个断下,以每个字节为单位,并非写入数组中任意一个位置,都会断下,所以,根据点击的那两个将要消除的点,在数组内存所在处相应位置下断)
-
从外到里,找到如下call:0041B4B7 -0041AB34 -0041C6C3,
-
就像指南针那个一样,必然不止一个,由内而外/由外而内依次检查每一个call,看其做了什么工作,检查其参数都是干嘛的(看push了谁,代码或堆栈中看),看看哪个传入了点的坐标(要想消除,就需要这两个点)
-
0041B4B7:内部retn 0x1c=28=7个参数,从栈顶依次找7个参数,就参数2靠谱点,是一个地址(其他都是数,一看就不是点坐标),数据窗口中跟随,确实是两个点坐标,再看游戏窗口中点击的那两个待消除的点,确实也符合,但是是一个地址中保存了两个点,而非理想中的一个参数对应一个点,先记下,继续往后找(尽量往里找,找更满足条件的)
-
0041AB34 :enter进入call的内部,在最后retn 0x18=24=6个参数,参数1-0、参数2-连连看数组地址、参数3-点1坐标、参数4-点2坐标、参数5-同上一样,有那两个点的坐标,暂记做坐标点数组、参数6-数值2,这么一看,这个call相当靠谱
-
最里层那个call4个参数,不太靠谱,故从里往外,倒数第二个即为目标call,0041AB34,通过其来构造汇编代码,实现程序外调用
-
难点及重点:如何构造call这个函数相应的6个参数?通过程序中汇编代码来构造,在call之前,第一个push处下断,看每一个参数的值都是怎么来的(追本溯源)
-
参数2/5比较难找:参数2=12BB50,参数5=1A5DE18,看这俩值怎么构造,是这么x+y=的
-
注意一点:call单次消除用到获取两点坐标功能,而后者又是在call指南针功能中调用的,就像注释中所说的
lea ecx, DWORD PTR DS : [ecx + 0x494]// 要加上此,原程序中,此函数是在call指南针内部call的
mov ecx, DWORD PTR DS : [ecx + 0x19F0]// 即在前面的基础上调用的,因此ecx...
-
注意:不仅要构造模拟参数,还有注意各个寄存器的值(用不到的就不管),如ecx=0012A1F4,在基址中45DEBC存储,它是好找的
- 因此,对于参数2和参数5,二者的值,可以在ecx的基础上+某个数得到,参数2+40,参数5+4,要特别注意此思路,不管他为什么要加上此数的,只要构造出这个值就行
- (这样就看出,这个ecx的值特别重要,作为一个基础,而那个基址中存储的这个ecx,可见,找到合适的基址尤其重要,是构造汇编代码的重中之重,特别注意基址的寻找,有了基址,一切都好办(哪怕同参数2/5一样,强行+x构造出某个值,只要我能构造出程序当时运行的环境就行
-
就是单次消除功能的循环,设置一个停止条件即可,点坐标的x/y==0
-
关键代码
// MFCGamePlugin.cpp
// 循环消除中,判断是否停止
if (pt1.x == 0 && pt1.y == 0)
{
return -1;
}
//CMyDlg.cpp
void CMyDlg::OnBnClickedButton3()
{
// TODO: 在此添加控件通知处理程序代码
CMFCGamePluginApp* pApp = (CMFCGamePluginApp*)AfxGetApp();
// 循环消除
for (int i = 0; i < 100; i++)
{
int nRet = ::SendMessage(pApp->m_hWnd, WM_DATA2, 0, 0);
if (nRet == -1)
break;
}
}
-
不断测试,当点击两个炸弹成功消除后,会出现炸弹道具
-
同理,找到相应的call,观察参数,发现同指南针相比,就是F0换成了F4,二者就是一样的思路来的
-
炸弹一次就相当于单次消除,加上循环便是秒杀(此时循环没有加停止条件,仅限制了循环次数,无伤大雅,有那个意思就行
-
版本1的单次消除和秒杀:获取两个点+手动消除,本思路借用炸弹道具,明显简单多了,也提了个醒,逆向时,要举一反三,通过指南针道具的调用,联想其他工具,程序员一般都是按照同一个思路来做的,无非换个参数(时间有限,其他道具也是这个道理,学到思路即可
-
关键代码
else if (Msg == WM_DATA2)
{
// 1 获取两个点坐标
POINT pt1 = { 0 };
POINT pt2={ 0 };
// 小技巧,用于调试,当注入成功时,ctrl+s 搜索指令找到此dll地址
//_asm
//{
// mov eax,eax
// mov eax,eax
//}
//0041E75E > \8B8E F0190000 MOV ECX, DWORD PTR DS : [ESI + 0x19F0]; Case F0(BM_GETCHECK) of switch 0041E749
//0041E764 . 8D45 D8 LEA EAX, DWORD PTR SS : [EBP - 0x28]
//0041E767 . 50 PUSH EAX
//0041E768 . 8D45 E0 LEA EAX, DWORD PTR SS : [EBP - 0x20]
//0041E76B . 50 PUSH EAX
//0041E76C.E8 CEAA0000 CALL kyodai2.0042923F; 提示待连接的两个坐标
_asm
{
mov ecx, 0x45DEBC
mov ecx, [ecx]
lea ecx, DWORD PTR DS : [ecx + 0x494]// 要加上此,原程序中,此函数是在call指南针内部call的
mov ecx, DWORD PTR DS : [ecx + 0x19F0]// 即在前面的基础上调用的,因此ecx...
lea eax, pt1.x
push eax// 原程序,push的是栈地址
lea eax, pt2.x
push eax
mov eax,0x0042923F
call eax
}
CString strCode;
strCode.Format(L"单次消除: 点1 x=%d,y=%d,点2 x=%d,y=%d", pt1.x, pt1.y, pt2.x, pt2.y);
OutputDebugString(strCode.GetBuffer());
// 循环消除中,判断是否停止
if (pt1.x == 0 && pt1.y == 0)
{
return -1;
}
// 2 调用消除call
//0041AB13 | > \57 PUSH EDI; 参数6:2(当前edi = 2
//0041AB14 | . 8D45 F4 LEA EAX, [LOCAL.3]
//0041AB17 | . 53 PUSH EBX; 参数5:坐标数组( = 1A5DE18 = ? + ?
//0041AB18 | . 50 PUSH EAX; 参数4:点2坐标(eax来自local3,就是点坐标
//0041AB19 | . 8D45 EC LEA EAX, [LOCAL.5]
//0041AB1C | . 8BCE MOV ECX, ESI
//0041AB1E | . 50 PUSH EAX; 参数3:点1坐标(eax来自local5,就是点坐标
//0041AB1F | . 0FB645 08 MOVZX EAX, BYTE PTR SS : [EBP + 0x8]; eax = 0
//0041AB23 | . 69C0 DC000000 IMUL EAX, EAX, 0xDC; eax = 0
//0041AB29 | . 8D8430 5C1900 > LEA EAX, DWORD PTR DS : [EAX + ESI + 0x195C]
//0041AB30 | . 50 PUSH EAX; 参数2:连连看数组地址( = 12BB50 = ? + ?
//0041AB31 | .FF75 08 PUSH[ARG.1]; 参数1:0(栈中可得,arg1为0
//0041AB34 | .E8 551B0000 CALL kyodai2.0041C68E; 6个参数,相当靠谱,就是他了
_asm
{
// 传递ecx,尤其重要,基地址!!
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
// 第三个参数 坐标1
lea eax, pt1.x
push eax
// 第四个参数 坐标2
lea eax, pt2.x
push eax
// 第五个参数 数组地址
lea eax, DWORD PTR DS : [ecx + 0x494]
mov eax, DWORD PTR DS : [eax + 0x19F0]
mov eax, DWORD PTR DS : [eax + 4]
push eax
// 第六个参数 0
push 0
// 调用函数
mov eax,0x0041C68E
call eax
}
return DefWindowProc(hWnd, Msg, wParam, lParam);// 要加此,否则运行完自动结束
}
else if (Msg == WM_DATA3)
{
OutputDebugString(L"无限炸弹");
//0041DE4D | . 8B86 9404000 > MOV EAX, DWORD PTR DS : [ESI + 0x494]
//0041DE53 | . 8D8E 9404000 > 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]; 使用指南针道具
_asm
{
mov ecx, 0x45DEBC
mov ecx, [ecx]
LEA ECX, DWORD PTR DS : [ecx + 0x494]
PUSH 0xF4// 若指南针,则F0
PUSH 0
PUSH 0
mov eax, 0x0041E691
call eax
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return CallWindowProc(g_oldProc,hWnd,Msg,wParam,lParam);
- 15PB视频课程-逆向工程实战之连连看分析
01-样本概况
1.1-应用程序信息
1.2-分析环境及工具
1.3-分析目标
02-具体分析过程
2.1-去广告
2.2-CE控制游戏以便测试
2.3-实现无限指南针
2.3.1-找数组
2.3.2-找call指南针的地方
2.3.3-找基址
2.3.4-编写注入工具exe及外挂dll
2.4-实现单次消除
2.5-实现秒杀
2.6-另一个思路来单消/秒杀
2.7-最终效果
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-2-16 14:38
被21Gun5编辑
,原因: