连连看辅助

转载自大神CSDN博主「九阳道人」

版权声明:本文为CSDN博主「九阳道人」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_31507523/article/details/88309060

QQ连连看单机版辅助制作全流程

最近在15PB学习逆向,分析了个小游戏并写出了辅助工具,在这里总结下全流程。

游戏:QQ连连看(单机版1.2)

完成目标:

1.去除广告

2.完成指南针、炸弹消除的功能

3.编写注入程序和游戏辅助的DLL

使用工具:VS2017、OD、CE、PEID、Spy++

分析环境:Win7虚拟机

首先在百度上搜索“QQ连连看单机版”随便下了个V1.2版的,把它解压到桌面然后进入文件夹。

仔细观察下都有些什么文件,这很重要,有可能得到一些有利于破解的信息。

1.在这里观察到有两个可执行的EXE文件、两个音乐文件夹,这都是比较重要的信息。

1565407736276

2.用PEID擦看下这两个EXE程序基本信息,得以得出"kyodai.exe"程序是VC6.0的编译器编写的,游戏一般都是C++语言编写,而"QQ连连看单机版V1.2.exe"是一个加壳的程序,常见压缩壳ASpack这就忽略它,也能分析。

1565407757730

1565407773830

3.双击"kyodai.exe"程序后回崩溃如图。

1565407789849

4.那么就确定了开始游戏的程序"QQ连连看单机版V1.2.exe",打开之后会弹出广告窗口,再之后游戏就运行了。

1565416222982

5.退出游戏,打开”任务管理器”,在运行游戏观察进程的变化,发现在弹出广告窗口时只有一个与游戏相关的进程”QQ连连看单机版V1.2.exe”

1565416238046

6.点击继续进入游戏之后发现此进程消失,而多了另一个很熟悉进程"kyodai.exe"

1565416250432

7.由于最终运行的还是"kyodai.exe"程序,但是点击它又运行不了。而通过其他程序却可运行它, 由此猜想"kyodai.exe"在运行时,”QQ连连看单机版V1.2.exe”打开它并对它的内存进行了修改,使他能正常运行。在这说明下这是老编译器写的游戏不存在随机基址,很多地址都是固定的。

猜想它可能使用了CreateProcessA\W函数打开进程,那么开始调试,使用OD开打”QQ连连看单机版V1.2.exe”,Ctrl+G搜索CreateProcessA\W在2个函数上都按F2设置断点,运行之后发现在CreateProcessA函数上断下,观察堆栈数据,发现"kyodai.exe"程序被它以挂起的方式打开了。

1565416281645

8.很多恶意程序都是这样的套路,那么就容易猜想了,挂起了程序之后就可能修改内存数据,之后恢复程序,那再就来搜索一下修改内存数据函数WriteProcessMemory下断点,运行,在次停下了再观察堆栈数据,发现3个重要的数据,意为在"kyodai.exe"程序中的0043817A 地址处修改1个字节的数据。

1565416294454

9.选中该Buffer右键查看其内容是”0”。

1565416304273

10.然后在搜索一个唤醒线程的函数ResumeThread下断点,运行之后果然走到了这里。

1565416315593

11.那么我们现在就已经分析出了”QQ连连看单机版V1.2.exe”的运行机制,它首先弹出广告,然后再执行CreateProcessA -> WriteProcessMemory -> ResumeThread操作打开"kyodai.exe"程序,那么我们现在就可以模拟它的执行流程编写程序打开它

打开VS2017编写程序如下

#include "pch.h"
#include <iostream>
#include <windows.h>

//kyodai.exe程序的路径
#define CATALOG L"C:\\Users\\15pb-win7\\Desktop\\连连看\\kyodai.exe"
int main()
{
STARTUPINFO si = {};
//操作进程的信息结构体
PROCESS_INFORMATION pi = {};
//打开进程
CreateProcess((LPWSTR)CATALOG, 0, 0, 0, FALSE,
CREATE_SUSPENDED,//此参数为挂起主线程
0, 0, &si, &pi);
DWORD dwWrite = 0;
//修改1字节的数据
WriteProcessMemory(pi.hProcess, (LPVOID)0x43817A, "\x0", 1, &dwWrite);
//恢复线程
ResumeThread(pi.hThread);
return 0;
}

12.本人认为这段程序对咱们新手来说比较有用。在这里要注意的是”kyodai.exe”所在路径每个人的可能都不一样,编译程序后运行,打开游戏成功!

1565416380190

13.当然也可以直接使用16进制文件编辑器直接找到3817A(因为默认加载基址0x400000需要减去)地址把里面的数据改为“00”,我使用的工具是010Editor。

1565416391301

14.接下来正式分析游戏关键功能了,正常的程序汇编中CALL xxx基本都是函数,运行游戏之后多玩几把测试一下游戏玩法,游戏右上角道具栏有指南针,多刷新几次地图可能会出现炸弹,把炸弹消除掉道具栏里会出现炸弹,而且道具有数量限制。

1565416403574

15.我用了CE工具查找道具基址,很遗憾找了10多分钟没找到,只有换一种方法了,点击道具的时候会发出声音,那么音乐文件的名字是字符串,搜索字符串可以是个切入点。

1565416412285

16.还有点击练习的时候地图会随机刷新,那么肯定会用到rand这个随机函数,亲测两种方法都能达到一样的效果找到关键点,在这里总结下查找API的方法,使用OD打开或者附加”kyodai.exe”程序先运行起来,再在OD中Ctrl+G搜索rand函数下断点,点击游戏中的练习后断了下来。

1565416426499

17.然后点击OD菜单栏上的”K”进行栈回溯分析。

1565416434699

观察发现有2个上层调用(有程序的名字的2个),他们的关系是0x41A080处的函数调用0x41CAF2的函数,0x41CAF2的函数调用rand。这里说的比较啰嗦,反正在”K”中越靠下就越是外层的函数。

1565416445825

18.双击进入第一个地址并在上面设置断点,然后再点K双击进入第2个地址设置断点然后把之前rand的断点删除

1565416463248

1565416472669

19.F9运行后再次在游戏中点击练习会在第一个断点0x41A080地址上停下这里是一个函数(CALL),要重点关注一下它上面行代码MOV ECX, EDI 由于这是C++所写的程序,它都会使用ECX这个寄存器传递this指针也就是传递一个对象,之后看见只要对ECX寄存器操作的代码都要留意一下。按F7进入这个函数,先Ctrl+A让OD帮我们分析一下该模块,发现一开始把ECX的值给了ESI,先不管这继续单步然后没几步就看见了熟悉的字符串”strat.wav”(字符串搜索也能定位到这个函数里来),这已经证明这个函数就是初始化游戏的函数。

1565416483525

20.然后再快速单步很快就能发现刚才所下的第二个断点,发现rand下面有一个 memcpy复制内存的函数,先大致走一遍注意观察OD中右上角寄存器的变化,观察寄存器上的值的内存的变化,走完了好像什么也没发现…然后再重新运行再多单步跟踪几次这个函数总会发现一些什么的。

1565416494177

21.再次跟踪这个函数,实际上刚才说过要注意ECX的值的传递,在这个函数里ECX传递给了ESI,那么我们要对所有操作ESI的代码留意一下,在加上我们下的第二个断点0x41CAF2所在附近,我们就要对它附近ESI重点关注了,在0041CAFC 地址处的这一行代码对ESI进行了访问,走一步之后我们发现EAX的是0012BB50。

1565416504710

22.那么再选中它右键->数据窗口跟随 查看0012BB50地址里面有些什么数据。

1565416514028

23.然后走到0041CB10 地址处,这里会调用memcpy复制内存,走一步观察0012BB50地址里的数据除了第一个DC 00…没变,后面都被填充成某种规律的010101…

1565416524666

24.在继续走两步经过0041CB20 地址出的函数后惊讶的发现0012BB50地址里那一大串数据又被刷新,到此我们大胆猜测0012BB50这就是游戏的地图数组基地址。

1565416536898

25.为了验证是否正确按F9把游戏运行起来观察地图与该内存数据的是否有联系(观察地图和该内存时刷新出一个特点鲜明的地图较好,比如地图右上角有两个靠的近相同的东西等)我多刷新了几次得到一个左上角和右下角都被填满了的地图,观察OD中的内存(记得点击下OD数据才会刷新)也跟着改变了,貌似还找到了数组的边界!0012BB58(第一个点)

1565416544743

26.再刷新几次地图找到第一行有两个连着的相同的东西,发现地图和OD中的内存非常相似。

1565416553410

27.为了确认 我们把游戏中的物品点击消除掉在观察内存发现果然被清0了那么可以确定0012BB50这就是地图数组基地址了,并且我们得到了0012BB58就是地图数据的起始位置。

1565416563802

28.接下来分析指南针和炸弹,指南针功能是可以帮助玩家找到2个相同物品,炸弹是找到2个相同的物品并消除,这2个道具不管是谁都会遍历地图数组,才能实现他们的功能,所以在0012BB58地址处右键下一个内存访问断点。

1565416574502

29.先在OD中按ALT+B把其他断点禁止或者删掉,然后运行游戏点击指南针,程序会停下来。点击”K”进行栈回溯分析观察他的上几层调用函数发现有5个那么每个都点进去设置上断点,然后右键选中先把0012BB58的内存断点给删除掉。

1565416584279

30.再F9运行OD再运行几次发现都停在004292A5 地址上那么这肯定是不需要的函数把它断点去掉,再运行2次,点不动了需要点击游戏界面,一点就在OD中断下来了 因此判断0040CACA断点也不是我们想要的,同样的继续运行几次还是都停在了0041AF11 处,这也是无用地址去掉断点,再F9游戏就运行起来了,这个过程多调试几次就会明白的。

因此0041DE5C和0041E76C 处的函数就可能是我们能利用关键函数,现在想要写代码完成指南针的功能,只要找到这两处地址的函数调用时所需要的参数,那就可以模拟出指南针的功能。

重新运行起来,点击游戏中的指南针,会在0041DE5C 处的代码断下,选中这一行按Enter建进入这个函数一直往下拉找到末尾RETN观察返回值发现RETN 0xC 也就是说0041DE5C 地址处的函数调用需要3个参数,在观察堆栈得出参数是 0,0,F0。

1565416599545

31.为了确认这3个参数是不是可变的,再次运行游戏,再点击指南针,再次在0041DE5C 停下发现堆栈里的参数还是这么多没有改变,那我就决定调用了这个函数模拟指南针功能了,写好注释。 其实在第2个断点0041E76C 处的参数更少,只有2个,但是在那里断下来之后查看堆栈里的参数也不知道是些什么,所以放弃它把它的断点去掉。

相同的原理我再找到炸弹的调用地址,多刷新几次游戏,找到有可以消除炸弹地图,把炸弹消除了道具栏中就出现了炸弹。

1565416611362

32.同样运行游戏之后在0012BB58下内存访问断点,再点击游戏道具栏里的炸弹,断下之后再次按”K”进行栈回溯分析,发现这几个地址很眼熟,和之前找指南针的栈回溯基本一致 只有一个不一样,那么无用几个地址不用管他,我们在0041DE5C 处下断点,刷新游戏地图找到一个有炸弹的地图,再使用炸弹之后停下观察堆栈参数的变化,发现参数是0,0,F4。

1565416620756

由此我们可以对比下两次调用这个函数的参数

调用指南针是0,0,F0

调用炸弹是0,0,F4

得出结论第3个参数就是游戏的道具类型,0041DE5C 地址处的函数功能应该就是使用道具。

33.谈一下我对辅助中的的DLL,注入程序,原程序(这里就是游戏程序)他们关系的理解:我们想要DLL里面的功能在原程序种实现,直接来是不行的,需要通过注入程序把DLL注入到原程序中,这时DLL功能才会对原程序产生影响。 这就像热发烧了打针一样,针筒里的药水就是DLL,针筒就是注入程序,人体就是原程序。

34.接下里就是编写辅助了,在这需要实现指南针和炸弹的功能,之需要模拟调用0041DE5C 地址处的函数就OK了。

我用的是VS2017首先创建一个MFC的DLL程序,选择在静态库中使用MFC,这个选项也可以在属性->常规里面更改。

1565416634318

35.使用到的关键API如下

FindWindow获取窗口句柄

SetWindowLong设置窗口回调

_beginthreadex创建线程便于弹窗

因为要在游戏中弹出一个窗口,所以要添加一个资源Dialog

窗口回掉原型为

LRESULT

CALLBACK

DefWindowProc(

In HWND hWnd,

In UINT Msg,

In WPARAM wParam,

In LPARAM lParam);

要实现的辅助功能主要就是在这个窗口回掉函数里面实现。

36.现在先使用VS中的自带工具Spy++先查找游戏的,窗口名,和类名,用于的FindWindow函数获取窗口句柄。

把Spy++中的望远镜拖到游戏窗口上可以可到,窗口名。

1565416650983

1565416665152

37.再次分析下0041DE5C 地址的函数这里的参数是3个但之前也说过C++使用ECX传递对象,所以我们必须得到ECX的值,经过多次运行发现右上角的ECX值一直是0012A688所以我们直接使用这个固定值。

1565416676992

38.使用_asm内嵌汇编调用那个关键函数,0041DE5C 地址处汇编是CALL [EAX+0x28] ,在内存窗口中Ctrl+G搜索EAX+0x28 里面的值为0041E691(以小端方式读) 这就是需要调用函数的地址。

LRESULT
CALLBACK
MyDefWindowProc(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
{
//指南针消息
if (Msg == WM_SIGN1)
{
_asm
{
MOV ECX, 0X12A688;    //this指针
PUSH 0XF0;            //参数3是道具类型
PUSH 0;               //参数2
PUSH 0;               //参数1
MOV EAX, 0X0041E691;
CALL EAX;             //指南针函数
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
if (Msg == WM_SIGN2)
{
_asm
{
MOV ECX, 0X12A688;
PUSH 0XF4;
PUSH 0;
PUSH 0;
MOV EAX, 0X0041E691;
CALL EAX;//炸弹函数
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return CallWindowProc(g_OldProc, hWnd, Msg, wParam, lParam);
}

39.说明一下,为了在辅助程序的窗口发送消息,我在头文件stdafx.h中自定义了两个消息,然后编译生成DLL就OK。

1565416728499

1565416737795

1565416746284

40.最后编写远程注入程序,就创建一个控制台程序就行了,要注意的是在这里要在属性里选择C+±>代码生成把运行库改为多线程调试(\MTd) 就跟上面的DLL静态编译一样,如果不这样,我们写的程序在别人的电脑上可能就运行不了。

1565416757512

41.远程线程注入的代码基本都固定,死记硬背就行了,在此贴上。

#include "pch.h"
#include <windows.h>
#include <string.h>

//注入DLL函数
void InjectDll(HWND hWnd, const char* DllPath)
{
//获取要注入的进程的PID并打开它
DWORD dwPid = 0;
GetWindowThreadProcessId(hWnd, &dwPid);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (!hProcess)
{
printf("打开进程失败\n");
getchar();
return;
}
//在注入的进程中分配一块虚拟内存
LPVOID lpAddr = VirtualAllocEx(hProcess,
NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE);
if (!lpAddr)
{
printf("分配内存失败\n");
CloseHandle(hProcess);
getchar();
return;
}

//把dll路径写入到目标进程空间中
DWORD dwWrite = 0;
WriteProcessMemory(hProcess, lpAddr, DllPath,
strlen(DllPath) + 1, &dwWrite);
if (strlen(DllPath) + 1 != dwWrite)
{
printf("dll路径写入失败\n");
VirtualFreeEx(hProcess, lpAddr, 0, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return;
}

//创建远程线程
HANDLE hThread = CreateRemoteThread(hProcess, 0, 0,
(LPTHREAD_START_ROUTINE)LoadLibraryA,
lpAddr, 0, 0);
if (!hThread)
{
printf("远程线程创建失败\n");
VirtualFreeEx(hProcess, lpAddr, 0, MEM_RELEASE);
CloseHandle(hProcess);
getchar();
return;
}

//等待线程结束(无穷大毫秒)
WaitForSingleObject(hThread, INFINITE);

//释放
VirtualFreeEx(hProcess, lpAddr, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
}

//动态库DLL的名字
#define DLLNAME "\\01连连看辅助.dll"

int main()
{
//dll的全路径名
char dllNamePath[256] = {};
//获取DLL的全路径
GetCurrentDirectoryA(sizeof(dllNamePath), dllNamePath);
strcat_s(dllNamePath, DLLNAME);
//获取游戏窗口句柄
HWND Handle = FindWindow(NULL, L"QQ连连看");
//调用远程注入函数
InjectDll(Handle, dllNamePath);
return 0;
}

42.这里说下游戏辅助常用的API函数GetCurrentDirectoryA这个API可以获取到本程序的当前目录的字符串,只要把要DLL放在与注入程序同一个目录了,直接运行注入程序就注入DLL了,这个非常方便。

1565416812292

43.把DLL文件和注入程序生成放在同一个目录下,先打开游戏,然后点击注入程序,单机版连连看的辅助就顺利完成了,运行秒杀效果如图。

1565416820759

最后感谢15PB的栽培

游戏和源码网盘链接: 链接:https://pan.baidu.com/s/1LyAn27Y__beBpCewMIr-WA 提取码:oq08

posted @ 2019-08-10 14:27  ltyandy  阅读(2065)  评论(0编辑  收藏  举报