**游戏逆向分析笔记
目录
0x01 样本概况
1.1-分析环境及工具
1.2-分析目标
0x02 具体分析过程
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-最终效果
>>>> 1.1 分析环境及工具
1.1 分析环境及工具
>>>> 1.2 分析目标
1.2 分析目标
找到原程序exe、去广告
实现连连看外挂:无限指南针、单次消除、秒杀
>>>> 2.1 去广告
2.1 去广告
>>>> 2.2 CE控制游戏以便测试
2.2 CE控制游戏以便测试
0012AC5E:指南针数量不变
0012A748:时间不变
0012AC6E:重列道具不变
>>>> 2.3 实现无限指南针
2.3 实现无限指南针
找数组
找call指南针的地方
找基址
编写注入工具exe及外挂dll
//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);
}
>>>> 2.4 实现单次消除
2.4 实现单次消除
要想消除,就需要获得可以消除的两个点,由上知,指南针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构造出某个值,只要我能构造出程序当时运行的环境就行。)
>>>> 2.5 实现秒杀
2.5 实现秒杀
// 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;
}
}
>>>> 2.6 另一个思路来单消/秒杀
2.6 另一个思路来单消/秒杀
不断测试,当点击两个炸弹成功消除后,会出现炸弹道具。
同理,找到相应的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);
>>>> 2.7 最终效果
2.7 最终效果
看雪ID:21Gun5
https://bbs.pediy.com/user-868592.htm
推荐文章++++
* 实战栈溢出漏洞