首页
社区
课程
招聘
[原创]连连看游戏逆向分析笔记
发表于: 2020-2-15 21:03 9384

[原创]连连看游戏逆向分析笔记

2020-2-15 21:03
9384

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
  1. kyodai双击不可运行
  2. qqllk可运行,打开就是一个广告,点击“开始游戏”进入下一窗口
  3. 再点击“OK我知道了”再到下个窗口,时刻关注进程列表(火绒剑),发现此时创建了一个新进程qqllk.ocx,虽然看起来后缀不是exe,但是既然出现在了进程列表,其本质就是一个exe
  4. 点击“继续”,kyodai进程创建,qqllk.ocx进程关闭,但qqllk.exe进程依然在运行
  5. 由此可判定,kyodai是真正的游戏程序,而qqllk是在其基础上,打包了许多广告的程序,去广告,也就是将k从q中分离出来
  6. OD附加那个ocx(而非qqllk.exe),因为是ocx创建出kyodai的
  7. 分析:q创建k进程,必然用到创建进程API,ctrl+g搜索CreateProcessA/W并下断
  8. 运行,停在762E2082 上,看一下堆栈中的参数,CreationFlags = CREATE_SUSPENDED,创建进程后,是暂停状态
  9. 再开一个OD来附加k,用来测试
  10. 分析:直接运行k失败,但是通过q就能使其运行,推测q创建进程后,一定是修改了q进程中某些东西,才使其可以运行的
  11. 因此,搜索WriteProcessMemory-下断-运行,发现断在759246C7 ,观察堆栈中参数,得出:往目标进程43817a处、写入一个字节、00
  12. 修改完之后,再resumeThread来恢复线程,因此流程就是:创建进程-修改进程-恢复线程
  13. 根据修改进程时的参数,即往目标进程43817a处、写入一个字节、00,手动修改k程序即可达到目的
  14. 打开LordPE,拖入Kyodai,位置计算,43817a-40000=3817a得到RVA(OD附加了那个新创建的进程,E-看到其模块基址为400000),将RVA填入得到文件偏移,也是43817a
  15. 010editor打开K程序,ctrl+g跳到文件偏移出,手动将值修改为0(注:远程序只读不可写入,可file-save a copy复制一份再修改
  16. 修改后,得到新的K程序,命名为Kyodai-noAd,双击可直接运行,至此,提取成功,也就去除了广告
  1. ctrl+g:找rand函数,得7623C070 (rand实现处
  2. 栈回溯:得0041CAF8 (rand调用处
  3. rand调用后,在0041CB10 ,有一个memcpy,dst为0012BB50,数据窗口跟随
  4. 运行完memcpy后,内存窗口有明显变化,且较有规律,推测为连连看数组
  5. 不断点击“练习”,并对比内存窗口,空白处为00,不断测试,证实上述猜想
  1. 数组处下内存访问断点(硬件断点无效),点击“指南针”,断在此处
  2. K-调用堆栈处,一层一层下断点,逐个测试(先随便下5、6个断点
  3. 一次测试,多次循环断下的位置先排除,效果应该是一点“指南针”就断,满足此条件的有:0040CACA -0041AF11 -0041DE5C -0041E76C ,故最外层的为0040CACA(当前测试的几个断点中最外层),最里层的为 0041E76C
  4. 由内而外,依次来看每个call的具体作用
  5. 最里层的0041E76C:两个参数,将局部变量赋值为eax再push(dword ptr,4个字节),分别为:00129D8C 、00129D94 ,将两个地址数据窗口跟随(分别M1、M2便于观察),call完成之后,返回值通过push进去的地址体现(指针间接修改),94前4个字节为20,8c前4字节为49,对比游戏界面,为两个坐标,也就是用了指南针用于提示的两个位置,故此处call,获得待连接的两个位置
  6. 再往外一层0041DE5C:虽连续三个push,但都是些没意义的参数,故推测,其仅仅是一个“使用道具”的函数, (因为参数无意义,故推测其并没有完成什么实质性功能,其调用仅仅为了进一步调用那些有用的函数),call了0041E691,推测这就是“指南针道具”的函数(因为有多个工具,call后面跟具体的哪个道具
  1. 由上,只要主动调用0041DE5C,倒数第二的那个函数即可
  2. 要调用就要手动传入正确的参数,以假装它是在正常的程序内部调用的;
  3. 参数除了通过push进去那三个(栈传递的),还可能是通过ecx寄存器传递的(thiscall)即那个lea ecx,[esi+xx],事实上,也就ecx那个参数看起来靠谱,像一个地址,但是简单将ecx作为参数是不可的,其地址esi+xx=12xx,一看就是栈空间的局部变量,具有不确定性,可能每次运行都不一样,因此,追本溯源,网上找ecx究竟是哪来的(要找到一个基地址
  4. 经测试,一直向上找是找不尽的,故换思路
  5. esi是0012A1F4,CE中搜索,发现有几个绿色的地址(即基址),有了基址就能在程序外手动的调用函数(不应该用变值作为参数),几个基址:45DCF8、45DEBC(用这个)、47FDEO、777FEDE8(7开头,暂不考虑
  1. 有了基址,就可以构造不变化的参数,就可以手动调用程序内部的参数

  2. 编写注入程序:Injector.exe

  3. 编写被注入dll:MFCGamePlugin.dll(win10虚拟机

  4. 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);
    
    }
    
  5. 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);
    }
    
  1. 要想消除,就需要获得可以消除的两个点,由上知,指南针call中最里面的那个0041E76C,就可以提供两个点(且可以消除

  2. 在手动找两个可消除的两个点,消除过程中,必然会访问连连看数组(将相应位置置为0),在数组处下内存写入断点,会断下来0040FF5F ,然后删除内存断点,F2下断,通过栈回溯,不断找“消除”时会调用的call

  3. (注意,写入哪个会在哪个断下,以每个字节为单位,并非写入数组中任意一个位置,都会断下,所以,根据点击的那两个将要消除的点,在数组内存所在处相应位置下断)

  4. 从外到里,找到如下call:0041B4B7 -0041AB34 -0041C6C3,

  5. 就像指南针那个一样,必然不止一个,由内而外/由外而内依次检查每一个call,看其做了什么工作,检查其参数都是干嘛的(看push了谁,代码或堆栈中看),看看哪个传入了点的坐标(要想消除,就需要这两个点)

  6. 0041B4B7:内部retn 0x1c=28=7个参数,从栈顶依次找7个参数,就参数2靠谱点,是一个地址(其他都是数,一看就不是点坐标),数据窗口中跟随,确实是两个点坐标,再看游戏窗口中点击的那两个待消除的点,确实也符合,但是是一个地址中保存了两个点,而非理想中的一个参数对应一个点,先记下,继续往后找(尽量往里找,找更满足条件的)

  7. 0041AB34 :enter进入call的内部,在最后retn 0x18=24=6个参数,参数1-0、参数2-连连看数组地址、参数3-点1坐标、参数4-点2坐标、参数5-同上一样,有那两个点的坐标,暂记做坐标点数组、参数6-数值2,这么一看,这个call相当靠谱

  8. 最里层那个call4个参数,不太靠谱,故从里往外,倒数第二个即为目标call,0041AB34,通过其来构造汇编代码,实现程序外调用

  9. 难点及重点:如何构造call这个函数相应的6个参数?通过程序中汇编代码来构造,在call之前,第一个push处下断,看每一个参数的值都是怎么来的(追本溯源)

  10. 参数2/5比较难找:参数2=12BB50,参数5=1A5DE18,看这俩值怎么构造,是这么x+y=的

  11. 注意一点:call单次消除用到获取两点坐标功能,而后者又是在call指南针功能中调用的,就像注释中所说的

    lea ecx, DWORD PTR DS : [ecx + 0x494]// 要加上此,原程序中,此函数是在call指南针内部call的
    mov ecx, DWORD PTR DS : [ecx + 0x19F0]// 即在前面的基础上调用的,因此ecx...

  12. 注意:不仅要构造模拟参数,还有注意各个寄存器的值(用不到的就不管),如ecx=0012A1F4,在基址中45DEBC存储,它是好找的

  13. 因此,对于参数2和参数5,二者的值,可以在ecx的基础上+某个数得到,参数2+40,参数5+4,要特别注意此思路,不管他为什么要加上此数的,只要构造出这个值就行
  14. (这样就看出,这个ecx的值特别重要,作为一个基础,而那个基址中存储的这个ecx,可见,找到合适的基址尤其重要,是构造汇编代码的重中之重,特别注意基址的寻找,有了基址,一切都好办(哪怕同参数2/5一样,强行+x构造出某个值,只要我能构造出程序当时运行的环境就行
  1. 就是单次消除功能的循环,设置一个停止条件即可,点坐标的x/y==0

  2. 关键代码

    // 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;
        }
    }
    
  1. 不断测试,当点击两个炸弹成功消除后,会出现炸弹道具

  2. 同理,找到相应的call,观察参数,发现同指南针相比,就是F0换成了F4,二者就是一样的思路来的

  3. 炸弹一次就相当于单次消除,加上循环便是秒杀(此时循环没有加停止条件,仅限制了循环次数,无伤大雅,有那个意思就行

  4. 版本1的单次消除和秒杀:获取两个点+手动消除,本思路借用炸弹道具,明显简单多了,也提了个醒,逆向时,要举一反三,通过指南针道具的调用,联想其他工具,程序员一般都是按照同一个思路来做的,无非换个参数(时间有限,其他道具也是这个道理,学到思路即可

  5. 关键代码

    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);
    
  1. 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编辑 ,原因:
    收藏
    免费 8
    支持
    分享
    最新回复 (11)
    雪    币: 729
    活跃值: (383)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    2
    外挂,哈哈哈哈
    2020-2-16 13:35
    0
    雪    币: 2510
    能力值: ( LV1,RANK:0 )
    在线值:
    发帖
    回帖
    粉丝
    3
    感谢分享
    2020-2-16 14:02
    0
    雪    币: 222
    活跃值: (185)
    能力值: ( LV2,RANK:15 )
    在线值:
    发帖
    回帖
    粉丝
    4
    大家好  我系你们郁金香老湿!
    2020-2-16 15:27
    0
    雪    币: 20
    能力值: ( LV1,RANK:0 )
    在线值:
    发帖
    回帖
    粉丝
    5
    感谢分享
    2020-2-16 19:59
    0
    雪    币: 22
    活跃值: (443)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    6
    makr
    2020-2-17 09:37
    0
    雪    币: 2249
    活跃值: (3778)
    能力值: ( LV6,RANK:81 )
    在线值:
    发帖
    回帖
    粉丝
    7
    几年前写过一个图像识别版的
    2020-2-17 11:27
    0
    雪    币: 4540
    活跃值: (1036)
    能力值: ( LV10,RANK:160 )
    在线值:
    发帖
    回帖
    粉丝
    8
    KingSelyF 几年前写过一个图像识别版的[em_13]
    瞬间就高大上了起来
    2020-2-17 13:41
    0
    雪    币: 1028
    活跃值: (226)
    能力值: ( LV2,RANK:15 )
    在线值:
    发帖
    回帖
    粉丝
    9
    学习思路了,谢谢。
    2020-2-17 14:33
    0
    雪    币: 18
    能力值: ( LV1,RANK:0 )
    在线值:
    发帖
    回帖
    粉丝
    10
    大神求助加QQ2285917557
    2020-3-28 02:49
    0
    雪    币: 12324
    活跃值: (5844)
    能力值: ( LV5,RANK:60 )
    在线值:
    发帖
    回帖
    粉丝
    11
    https://bbs.pediy.com/thread-262467.htm
    这帖子的作者是楼主徒弟吗,怎么汇编call的代码一模一样的
    2020-12-24 16:02
    0
    雪    币: 763
    活跃值: (4923)
    能力值: ( LV9,RANK:160 )
    在线值:
    发帖
    回帖
    粉丝
    12
    hhkqqs https://bbs.pediy.com/thread-262467.htm 这帖子的作者是楼主徒弟吗,怎么汇编call的代码一模一样的[em_12]

    部分参考自:15PB薛老师录制视频:15PB视频课程-逆向工程实战之连连看分析。这位应该是15PB的师兄~

    代码是自己写的,思路是老师提供的,汇编代码都长差不多

    最后于 2020-12-24 16:54 被三一米田编辑 ,原因:
    2020-12-24 16:46
    0
    游客
    登录 | 注册 方可回帖
    返回
    //