安全矩阵

 找回密码
 立即注册
搜索
查看: 2639|回复: 0

Re - 逆向扫雷

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-12-1 16:38:51 | 显示全部楼层 |阅读模式
原文链接:Re - 逆向扫雷

前几天Mz师傅发了一个逆向扫雷写挂的视频,给我看得热血沸腾从床上猛地一下跳起来,然后翻了个身,首先找mz师傅要了一份扫雷的exe文件

主要就三个玩意,扫雷主程序直接点根本点不开,于是尝试开IDA看一看

在Main里面有这么一个IsGamePlayable,如果返回0的话就直接退出了,怀疑可能是这个东西的原因,于是尝试用ollygdb看看是不是在这个地方出的问题
这里就牵扯到一个寻找函数入口的问题,Ollydbg中进入以后是真正的函数入口,而不是winMain函数,但是我们的关键代码在winMain里,所以就需要先找到真正的入口再说
但是这个扫雷是开了PIE的,我们并不方便直接定位代码,所以我们可能需要先找程序基址,但是其实我们真正需要的事情是找一个特征,这些特征可以从代码中找,我们查找调用winMain的函数

注意到这个ImageBase,正好是程序基址,其是winMain的第一个参数,而winMain的第一个参数按道理来讲应该是hInstance,那么是不是说明hInstance正好就是程序基址呢?带着好奇的心情搜了一下

Win32-HINSTANCE和HWND理解 - 白帽安全技术复现 - 博客园 (cnblogs.com)
这位大佬把源码翻了一通,找了一下定义,发现这个玩意是一个指向ImageBase的指针,那么与我们这里调用WinMain的代码就不谋而合了,下面就是看这个玩意的值到底是个啥
熟悉Linux的同学应该知道,因为页表效率的问题,一个程序基址或者so基址的低12位是肯定为0的,那么也就是说基址一定是0xxxxxx000这个样子的,而Windows也类似,那么ImageBase是一个指针,其地址为程序基址,也长0xxxxxx000这样子
我们再看看其汇编代码

这是不是意味着当快要到程序入口的时候栈上就会出现类似于0xxx000这样的数据?类似的,如果是64位的程序的话,rdi寄存器应该就会变为这个数据
带着这个猜想我们打开od试试,刚进来长这样

然后一路步进F8,盯着栈的数据,直到出现下面这个

这是在开启PIE的情况下的做法,在没有开启PIE的时候有个更方便的做法,在Windows PE头中,有一个建议装载地址,Windows会将程序加载到这个建议装载地址上,这种情况下直接计算就行了
到现在就可以F7了
到这个位置,应该就是IsGamePlayable了,因为前面push了一个Shell-InBoxGames-Minesweeper,这玩意就是我们需要的,现在F8一下
发现执行完成以后是0,那么不符合jnz的判断,于是继续执行到了下面xor eax, eax的地方,最后jmp到ret处直接ret,从而执行失败
那么程序不能启动原因也就是这个了,可能因为一些不知道什么的原因导致了这个IsGamePlayeable的返回值为0,那么我们改一改就好,将jnz改为jz,或者这一串全部改为nop,这里可以使用pwntools和十六进制编辑器进行改写
nop的16进制为0x90
找到要修改的代码,从push开始改,下面全改成nop

从IDA里看是这个地方

到这个地方前

在FlexHEX中搜索68 10 2c 00找到这条指令的位置然后给它用nop填充掉

保存
于是程序就变成了这个样子

isPlayable直接就没了
但是修改完以后发现还是打不开游戏=,=,难道真的not playable吗,我们再尝试将jnz改为jz试一试,万一是因为IsPlayable里还做了什么初始化呢
查看jz的十六进制

然后跟之前一样直接改

然后就会神奇地发现跑起来了!OVO
接下来就是写挂了OVO
进入IDA先静态逆一下逻辑,在InitializeEngine函数中可以发现消息循环逻辑,那么根据g_hWnd去寻找打开窗口的地方

找到了CreateGameWindow函数

CreateGameWindow函数调用了CreateWindowEx

在CreateGameWindow以后就有这玩意,注册了一大堆事件,从函数名来看就是事件

一个大胆的猜想,我们扫雷的时候按下去的时候并不会显示这个是不是雷,而在松开鼠标的时候会开始扫雷,那是不是意味着MouseRelease事件可能就涉及到雷区储存的位置
点进去看看是个什么玩意

简单地很,但是后面逆了一段时间发现这玩意没啥用,它唯一的用处就是返回的result+16偏移处有事件编号,所以猜测这里只是简单地注册一个事件,::`vftable`是虚函数表
​​
但总之在这里我们没有发现什么线索,后来找了很久也没什么线索,所以转换思路,我们直接猜函数名(主要这个玩意它没去掉调试符号),试一下Game、Player等关键词,然后就发现了下面这个牛马

在这个函数里面我们又能看到这个调用

这什么玩意啊,这看起来像重置游戏啊,但是进去看了以后发现什么线索都没有,就是delete掉一堆内存,但是从这个点我们想到,是不是这个Board类可能是储存雷区或者旗区的,那么我们找它的初始化部分是不是就可以找到雷区的地址了??

然后就跟到了上面这个地方,调用了Board的构造函数,且传入的v50是一个this指针,继续跟

果然找到了placeMines函数,这个玩意肯定就是放雷的了,继续跟

我把我写的注释打了个马,先不看注释,大家能猜出这段代码是干什么的吗,反正一开始我是没看出来,向上面的width等变量名还是我改好了的,没改之前根本就不能看,现在我们还不清楚这里每个循环都有什么用,但是我们可以注意一下GetRandom函数,这很像是随机雷的代码,那么我们就尝试在这个while里下断点看一下,我们但是在这之前我们猜这个while的条件是雷的个数

上面是这个循环的汇编,我们用x32dbg在这里下硬件断点
首先找到ImageBase 0x590000

然后计算这个循环的VA 0x590000 + 0x2019d = 0x5b019d,然后下断点
mov dr0, 0x5b019d
mov dr7, 0
随后摁F9让程序跑起来,由于第一次点击之前并不会生成雷区,所以现在断点是不会生效的,所以我们需要再点一下,生成雷区
于是现在断点就生效卡在这了
我们仔细观察
这里是将eax与 【esi + 4】做对别,现在是第一轮循环,eax为0,我们去看看这个【esi + 4】
0a即10,10个雷,正好达到我们的预期,且在后面的循环中eax会逐渐增加到10,但是这个过程并不是完全的累加,这个循环的Add和Remove函数是用来处理雷重复的情况的,而没有重复的话类的位置,即v9就会被储存到v8数组中,那么这个v9长啥样呢,我们猜测它的范围是 0 ~ 长 * 宽 - 1,在实际调试的过程中也证实了我们的猜想
接着我们看一看下面那个循环的代码

这个v11是宽度,而v8是雷区数组,那么v12应该就是雷的y轴坐标,v13是x轴坐标,而在上面的那一长串指针操作应该就是给雷区赋值了

稍微解释一下这个地方

首先这里是获取到雷区的数组指针,它储存在this[68] + 12中

获取到目标y轴数组指针储存的位置

获取到目标y轴数组指针

获取到目标坐标的指针
我们上dbg看一看


会发现这里储存的雀食是一个指针数组,用来储存雷区数据
好了,现在我们基本上能确定雷区的位置是怎么来的了,下面就是把这个过程自动化
在Board类中,雷区位置是这么来的,*(Board指针 + 68)+ 12 = 雷区双重指针,现在我们去跟Board的初始化

Board指针被储存在了Game指针 + 4(DWORD)的位置,所以Game指针 + 16 = Board,继续跟Game指针储存在哪

在全局变量Game::G中(或者应该说是静态变量),那么看一看这个地方的地址

好那接下来就是写注入了,但是我这里嫌麻烦就没写了,就暴力读内存了
下面是代码,因为偷了懒,没怎么注意代码格式,所以这里的代码很丑(,默认是16 * 16的棋盘,在初始化雷区以后将扫雷保持在桌面置顶,再启动脚本,就会自动去扫了,没有采用强制写入内存的方式,是移动鼠标的方式,视觉效果更好一点
  1. #include
  2. #include
  3. #include


  4. using namespace std;


  5. DWORD GetProcessIdEx(char windowName[]) {
  6.        HWND hWnd = FindWindow(NULL, windowName);
  7.        DWORD pid;
  8.        GetWindowThreadProcessId(hWnd, &pid);
  9.        return pid;
  10. }


  11. DWORD GetModuleInfo(
  12.     DWORD pid,
  13.     MODULEENTRY32* info,
  14.     const char processName[]
  15.     ) {
  16.        HANDLE handle;
  17.        handle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
  18.        //尝试获取进程信息
  19.        Module32First(handle, info);
  20.        do{
  21.               if (strcmp(processName, info->szModule) == 0) {
  22.                      return TRUE;
  23.               }


  24.        } while (Module32Next(handle, info) != FALSE);
  25.        return FALSE;
  26. }


  27. VOID ClickMineTile(HWND hWnd, RECT rect, int x, int y) {
  28.        x = rect.left + 45 + x * 18;
  29.        y = rect.top + 87 + y * 18;
  30.        //::PostMessage(hWnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x, y));
  31.        //::PostMessage(hWnd, WM_LBUTTONUP, MK_LBUTTON, MAKELONG(x, y));
  32.        SetCursorPos(x, y);
  33.        mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, x, y, 0, 0);
  34.        mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, x, y, 0, 0);
  35. }


  36. BOOL GetWindowPosition(char windowName[], RECT *rect) {
  37.        HWND hWnd = FindWindow(NULL, windowName);
  38.        return GetWindowRect(hWnd, rect);
  39. }


  40. BOOL InjectDLL(const wchar_t* DllPullPath, const DWORD dwRemoteProcessId) {
  41.        int pathSize = (wcslen(DllPullPath) + 1) * sizeof(wchar_t);


  42.        //尝试获取远程进程句柄
  43.        HANDLE rtProcessHandle = OpenProcess(
  44.          PROCESS_ALL_ACCESS, FALSE, dwRemoteProcessId);


  45.        if (rtProcessHandle == NULL) {
  46.               cout << "获取远程句柄失败" << endl;
  47.               return FALSE;
  48.        }


  49.        HANDLE hToken;
  50.        if (OpenProcessToken(GetCurrentProcess(),
  51.          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) == FALSE) {
  52.               cout << "权限提升失败" << endl;
  53.               return FALSE;
  54.        }


  55.        LUID luid;
  56.        if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid) == FALSE) {
  57.               cout << "权限信息查询失败" << endl;
  58.               return FALSE;
  59.        }


  60.        TOKEN_PRIVILEGES tkp;
  61.        tkp.PrivilegeCount = 1;
  62.        tkp.Privileges[0].Luid = luid;
  63.        tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; //允许特权


  64.        if (AdjustTokenPrivileges(
  65.      hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL) == FALSE) {
  66.               cout << "特权提升失败" << endl;
  67.               return FALSE;
  68.        }


  69.        //在远程线程中申请空间
  70.        LPVOID lpAddr = VirtualAllocEx(
  71.      rtProcessHandle, NULL, pathSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  72.        if (lpAddr == NULL) {
  73.               cout << "远程内存申请失败" << endl;
  74.               return FALSE;
  75.        }


  76.        if (WriteProcessMemory(
  77.      rtProcessHandle, lpAddr, DllPullPath, pathSize, NULL) == FALSE) {
  78.               cout << "远程内存写入失败" << endl;
  79.               return FALSE;
  80.        }


  81.        PTHREAD_START_ROUTINE pfnStartAssr =
  82.          (PTHREAD_START_ROUTINE)GetProcAddress(
  83.              GetModuleHandle("Kernel32.dll"), "LoadLibraryW");


  84.        HANDLE hRemoteThread = CreateRemoteThread(
  85.          rtProcessHandle, NULL, 0, pfnStartAssr, lpAddr, 0, NULL);


  86.        if (hRemoteThread == NULL) {
  87.               cout << "创建远程线程失败" << endl << GetLastError() << endl;
  88.               VirtualFreeEx(rtProcessHandle, lpAddr, 0, MEM_FREE);
  89.               return FALSE;
  90.        }


  91.        cout << "注入成功" << endl;


  92.        WaitForSingleObject(hRemoteThread, -1);
  93.        CloseHandle(hRemoteThread);
  94.        VirtualFreeEx(rtProcessHandle, lpAddr, 0, MEM_FREE);
  95.        return TRUE;
  96. }


  97. BOOL PeekMines(const DWORD dwRemoteProcessId,
  98.     const DWORD dwRemoteImageBase) {
  99.        //尝试获取远程进程句柄
  100.        HANDLE rtProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
  101.      dwRemoteProcessId);


  102.        if (rtProcessHandle == NULL) {
  103.               cout << "获取远程句柄失败" << endl;
  104.               return FALSE;
  105.        }
  106.        cout << "获取远程句柄成功" << endl;


  107.        HANDLE hToken;
  108.        if (OpenProcessToken(GetCurrentProcess(),
  109.      TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) == FALSE) {
  110.               cout << "权限提升失败" << endl;
  111.               return FALSE;
  112.        }
  113.        cout << "权限提升成功" << endl;


  114.        LUID luid;
  115.        if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid) == FALSE) {
  116.               cout << "权限信息查询失败" << endl;
  117.               return FALSE;
  118.        }
  119.        cout << "权限信息查询成功" << endl;




  120.        TOKEN_PRIVILEGES tkp;
  121.        tkp.PrivilegeCount = 1;
  122.        tkp.Privileges[0].Luid = luid;
  123.        tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; //允许特权


  124.        if (AdjustTokenPrivileges(
  125.      hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL) == FALSE) {
  126.               cout << "特权提升失败" << endl;
  127.               return FALSE;
  128.        }
  129.        cout << "特权提升成功" << endl;


  130.        cout << "开始读取雷区数据" << endl;


  131.        LPVOID GameAddr = (LPVOID)(dwRemoteImageBase + 0x868b4);
  132.        cout << "Game指针地址:\t0x" << hex << GameAddr << endl;
  133.       
  134.        LPVOID GamePtr;
  135.        ReadProcessMemory(rtProcessHandle, GameAddr, &GamePtr, 4, 0);
  136.        cout << "Game指针:\t0x" << hex << GamePtr << endl;


  137.        LPVOID BoardPtr;
  138.        ReadProcessMemory(rtProcessHandle,
  139.          (LPVOID)((DWORD)GamePtr + 16), &BoardPtr, 4, 0);
  140.        cout << "Board指针:\t0x" << hex << BoardPtr << endl;


  141.        LPVOID MinesTmpPtr;
  142.        ReadProcessMemory(rtProcessHandle,
  143.          (LPVOID)((DWORD)BoardPtr + 68), &MinesTmpPtr, 4, 0);
  144.        cout << "雷区缓冲指针:\t0x" << hex << MinesTmpPtr << endl;


  145.        LPVOID MinesPtr;
  146.        ReadProcessMemory(rtProcessHandle,
  147.          (LPVOID)((DWORD)MinesTmpPtr + 12), &MinesPtr, 4, 0);
  148.        cout << "雷区指针:\t0x" << hex << MinesPtr << endl;


  149.        BYTE map[16][32] = { 0 };


  150.        for (int i = 0; i < 30; i++) {
  151.               //获取雷区二级指针
  152.               LPVOID MinesColPtr;
  153.               ReadProcessMemory(rtProcessHandle,
  154.               (LPVOID)((DWORD)MinesPtr + 4 * i), &MinesColPtr, 4, 0);
  155.               ReadProcessMemory(rtProcessHandle,
  156.               (LPVOID)((DWORD)MinesColPtr + 12), &MinesColPtr, 4, 0);


  157.               for (int j = 0; j < 16; j++) {
  158.                      BYTE tmp;
  159.                      ReadProcessMemory(
  160.        rtProcessHandle, (LPVOID)((DWORD)MinesColPtr + j), &tmp, 1, 0);
  161.                      map[j][i] = tmp;
  162.               }
  163.        }


  164.        for (int i = 0; i < 16; i++) {
  165.               for (int j = 0; j < 30; j++) {
  166.                      cout << (int)map[i][j] << " ";
  167.               }
  168.               cout << endl;
  169.        }


  170.        cout << "开始模拟点击" << endl;
  171.        RECT windowRect;
  172.        GetWindowPosition((char *)"扫雷", &windowRect);
  173.        HWND hWnd = FindWindow(NULL, "扫雷");
  174.        for (int i = 0; i < 16; i++) {
  175.               for (int j = 0; j < 30; j++) {
  176.                      if (map[i][j] != 1) {
  177.                             ClickMineTile(hWnd, windowRect, j, i);
  178.                             Sleep(2);
  179.                      }
  180.               }
  181.        }
  182. }
  183. /*
  184. 0x868b4 = Game
  185. Game指针 + 16 = Board
  186. (*(Board指针 + 68)+ 12) == 雷区指针
  187. */
  188. int main() {
  189.        MODULEENTRY32 modentry;
  190.        DWORD pid = GetProcessIdEx((char *)"扫雷");
  191.        cout << "目标进程ID:\t" << pid << endl;


  192.        Sleep(2000);


  193.        if (GetModuleInfo(pid, &modentry, "MineSweeper.exe")) {
  194.               cout << "目标进程:\t" << modentry.th32ProcessID << endl;
  195.               cout << "目标程序基址:\t0x" << hex << modentry.hModule << endl;
  196.               PeekMines(modentry.th32ProcessID, (DWORD)modentry.hModule);


  197.        }
  198.        else {
  199.               cout << "不存在的进程" << endl;
  200.        }
  201.        return 0;
  202. }
复制代码




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-5-8 12:07 , Processed in 0.024385 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表