ToB企服应用市场:ToB评测及商务社交产业平台

标题: 游戏安全入门-扫雷分析&长途线程注入 [打印本页]

作者: 天津储鑫盛钢材现货供应商    时间: 2024-8-13 15:19
标题: 游戏安全入门-扫雷分析&长途线程注入
媒介

无论学习什么,首先,我们应该有个目的,那么入门windows游戏安全,脑海中浮现出来的一个游戏 -- 扫雷,一款家喻户晓的游戏,虽然已经被大家分析的不能再透了,但是我觉得本身去分析一下还是极好的,把它作为一个小目的再好不过了。
我们编写一个妙妙小工具,工具要求实现以下功能:时间停息、修改心情、透视、一键扫雷等等。
本文所用工具:
Cheat Engine、x32dbg(ollydbg)、Visual Studio 2019
扫雷游戏分析

游戏数据在内存中是地址,那么第一个使命,找内存地址
打开CE修改器
修改时间->时间停息

计数器的时间是一个精确的值,所以我们通过精确数值扫描出来,游戏开始之前计数器上的数是0,所以我们扫描0。
[img=720,432.26277372262774]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506350.webp[/img]

时间在变革,选择介于什么数值之间再次扫描
[img=720,462.575]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506410.png[/img]

可得 0x100579c --- winmine.exe+579C
[img=720,480.4339963833635]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506518.png[/img]

我们发现这个数据都是直接通过基址 + 固定偏移能直接得到的。
然后我们对数据去找 是什么改写了这个地址,得到一个指令和指针:
[img=720,535.9245283018868]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506371.png[/img]

时间:0x100579c
修改心情 - 没啥用

修改心情这个功能怎么搞我觉得还是很容易想到的,这个按钮的作用是重新开始游戏,开始游戏,游戏胜利,游戏失败。
(心情的状态被分成了两个变量(4byte)来控制)
所以它是一种状态,所以我们通过0和1进行扫描,游戏进行状态输入1进行扫描,还原游戏之后输入0进行扫描。
首先是游戏进行状态,输入1进行扫描
[img=720,536.5605095541401]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506442.png[/img]

再点击心情,将游戏还原,输入0开始扫描
[img=720,550.9859154929577]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506533.png[/img]

如此反复进行扫描,得到心情的内存地址
0x1005164 -- winmine.exe+5164
[img=720,513.4372135655362]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506185.png[/img]

但是嘞,修改成2或者3,心情没有心得反应,所以控制游戏胜利和游戏失败的是其他的地址,我们知道,一般来说,一个功能的代码在内存中基本上都是连续的,(就像你修改一个游戏的血量,浏览血量内存块,你可以发现怒气,蓝量等内存地址)
所以,我们浏览内存
[img=720,647.6595744680851]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506226.png[/img]

[img=720,450.21209740769837]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506066.png[/img]

0x1005164-4 = 0x1005160
修改为3,发现出现了戴墨镜的心情(游戏胜利)
但是这个胜利知识一个状态,并不能说明扫雷完成.
[img=720,447.98722044728436]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506184.png[/img]

心情:0x1005160与0x1005164
【----资助网安学习,以下所有学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】
 ① 网安学习成长路径头脑导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析陈诉
 ④ 150+网安攻防实战技能电子书
 ⑤ 最权威CISSP 认证测验指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂口试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
透视 - 显示雷区

思考游戏结束的时候会自动显示所有的雷,因此我们动态调试,看看在哪个函数调用之后会显示所有的雷
[img=720,307.52542372881356]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506364.png[/img]

[img=720,309.2420537897311]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506268.png[/img]

经过频频的动态调试之后发现:0x2F80函数是我们要找的结果。
[img=720,295.28795811518324]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506427.png[/img]

一键扫雷

通过透视,我们玩一把游戏,使得游戏胜利(点完最后一个)
[img=720,433.66795366795367]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506994.png[/img]

[img=720,481.2834224598931]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506228.png[/img]

然后后两个函数,是破记载跟英雄榜的函数
[img=720,213.5036496350365]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506618.png[/img]

[img=720,187.1336405529954]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506812.png[/img]

ret来到了这儿,游戏通关了,来到了这儿,可以知道,这个0x347c就是判断输赢的函数
并且通过调试发现由一个参数 0 1 来控制,所以跟透视差不多,带个参数线程回调就完了
[img=720,391.35]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506270.png[/img]

编写妙妙小工具

怎么实现这个工具呢,当然是选择DLL注入
那么dll 怎么注入进去呢,这里选择长途线程注入
这里先简朴介绍下什么是长途线程注入
前置知识-动态调用dll

主要就是这几个个 API:
LoadLibraryA

加载指定 DLL 并返回模块句柄,参数为字符串,就是 dll 的路径。
GetProcAddress

获取指定 dll 的导出函数的地址。
第一个参数是模块句柄,第二个参数是模块函数,返回值为函数的地址。
通过这两个函数,我们可以拿到所有函数的地址,然后就能进行调用。
CreateThread - 长途线程注入

里面险些只有一个参数,那就是线程回调函数,然后当然还有返回地址,返回线程 id 啥的,这里我们都可以不消管,险些是与 Linux 的创建线程函数一致。
还有一个长途版本的叫 CreateRemoteThread,它可以给别的进程创建一个线程并可以在本进程创建谁人进程调用的回调函数。我们可以在回调函数中加载指定的 dll,在 dllmain 的入口当中,有一个 switch 的四个选项。
  1. // dllmain.cpp : 定义 DLL 应用程序的入口点。
  2. #include "pch.h"
  3. BOOL APIENTRY DllMain( HMODULE hModule,//指向自身的句柄
  4.                       DWORD  ul_reason_for_call,//调用原因
  5.                       LPVOID lpReserved//隐式加载or显式加载
  6.                     )
  7. {
  8.    switch (ul_reason_for_call)
  9.    {
  10.    case DLL_PROCESS_ATTACH://附加到进程上时执行
  11.    case DLL_THREAD_ATTACH://附加到线程上时执行
  12.    case DLL_THREAD_DETACH://从线程上剥离时执行
  13.    case DLL_PROCESS_DETACH://从进程上剥离时执行
  14.        break;
  15.    }
  16.    return TRUE;
  17. }
复制代码
我们可以在 DLL_PROCESS_ATTACH 的选项中加入代码,让它在加载的时候调用实验。
那么我们的步骤是:
demo:
  1. void Inject(DWORD ProcessId, const char* szPath)
  2. {
  3.    //1.打开目标进程获取句柄
  4.    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
  5.    printf("进程句柄:%p\n", hProcess);
  6.    //2.在目标进程体内申请空间
  7.    LPVOID lpAddress = VirtualAllocEx(hProcess, NULL, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
  8.    //3.写入DLL路径
  9.    SIZE_T dwWriteLength = 0;
  10.    WriteProcessMemory(hProcess, lpAddress, szPath, strlen(szPath), &dwWriteLength);
  11.    //4.创建远程线程,回调函数使用 LoadLibrary 加载指定 dll
  12.    HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, lpAddress, NULL, NULL);
  13.    //5.等待返回(loadLibrary返回)
  14.    WaitForSingleObject(hThread, -1);
  15.    //6.释放空间
  16.    VirtualFreeEx(hProcess, lpAddress, 0, MEM_RELEASE);
  17.    //7.释放句柄
  18.    CloseHandle(hProcess);
  19.    CloseHandle(hThread);
  20.    //返回结果
  21.    AfxMessageBox(L"完成");
  22. }
复制代码
编写DLL注入器
  1. #include<windows.h>
  2. #include<iostream>
  3. #include<time.h>
  4. #include<stdlib.h>
  5. #include<TlHelp32.h>
  6. DWORD FindProcess() {
  7.    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  8.    PROCESSENTRY32 pe32;
  9.    pe32 = { sizeof(pe32) };
  10.    BOOL ret = Process32First(hSnap, &pe32);
  11.    while (ret)
  12.    {
  13.        if (!wcsncmp(pe32.szExeFile, L"mine.exe", 11)) {
  14.            printf("Find winmine.exe Process %d\n", pe32.th32ProcessID);
  15.            return pe32.th32ProcessID;
  16.        }
  17.        ret = Process32Next(hSnap, &pe32);
  18.    }
  19.    return 0;
  20. }
  21. void Inject(DWORD ProcessId, const char* szPath)
  22. {
  23.    //1.打开目标进程获取句柄
  24.    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
  25.    printf("进程句柄:%p\n", hProcess);
  26.    //2.在目标进程体内申请空间
  27.    LPVOID lpAddress = VirtualAllocEx(hProcess, NULL, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
  28.    //3.写入DLL路径
  29.    SIZE_T dwWriteLength = 0;
  30.    WriteProcessMemory(hProcess, lpAddress, szPath, strlen(szPath), &dwWriteLength);
  31.    //4.创建远程线程,回调函数使用 LoadLibrary 加载指定 dll
  32.    HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, lpAddress, NULL, NULL);
  33.    //5.等待返回(loadLibrary返回)
  34.    WaitForSingleObject(hThread, -1);
  35.    //6.释放空间
  36.    VirtualFreeEx(hProcess, lpAddress, 0, MEM_RELEASE);
  37.    //7.释放句柄
  38.    CloseHandle(hProcess);
  39.    CloseHandle(hThread);
  40. }
  41. int main() {
  42.    DWORD ProcessId = FindProcess();
  43.    while (!ProcessId) {
  44.        printf("未找到扫雷程序,等待两秒中再试\n");
  45.        Sleep(2000);
  46.        ProcessId = FindProcess();
  47.    }
  48.    printf("开始注入进程...\n");
  49.    Inject(ProcessId, "E:\\CODE\\wimine\\Mine\\release\\Mine.dll");
  50.    printf("注入完毕\n");
  51. }
复制代码
编写DLL

这里我们采用MFC DLL 基于对话框 (dialog)的方式编写(简朴),使用静态编译的方式
[img=720,478.125]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506488.png[/img]

[img=720,457.2371134020619]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506581.png[/img]

然后我们需要在资源窗体,新建一个 Dialog ,简朴包装一个界面

这样我们在加载窗体的时候需要创建一个窗体类对象用它的 DoModal 方法去显示,用线程回调的方式加载并且初始化InitInstance
  1. DWORD WINAPI DlgThreadCallBack(LPVOID lp) {
  2.    MineDlg* Dlg;
  3.    Dlg = new MineDlg();
  4.    Dlg->DoModal();
  5.    delete Dlg;
  6.    FreeLibraryAndExitThread(theApp.m_hInstance, 1);
  7.    return 0;
  8. }
  9. // CMineApp 初始化
  10. BOOL CMineApp::InitInstance()
  11. {
  12.    CWinApp::InitInstance();
  13.    ::CreateThread(NULL, NULL, DlgThreadCallBack, NULL, NULL, NULL);
  14.    return TRUE;
  15. }
复制代码
时间停息

上面我们找到了它控制时间增加的指令,我们把它们全部 NOP 掉,就可以实现时间停息
写两个按钮,创建下面的事故实现时间停息开关。
[img=720,316.7088607594937]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506901.png[/img]
  1. DWORD GetBaseAddr() {
  2.    HMODULE hMode = GetModuleHandle(nullptr);
  3.    //LPWSTR s = (LPWSTR)malloc(0x100);
  4.    //wsprintf(s, L"基址:%p", hMode);
  5.    //AfxMessageBox(s);
  6.    return (DWORD)hMode;
  7. }
  8. void MineDlg::OnBnClickedButton1() // 时间暂停
  9. {
  10.    // TODO: 在此添加控件通知处理程序代码
  11.    auto BaseAddr=GetBaseAddr();
  12.    DWORD TimeOffset = 0x579C;
  13.    DWORD TimeInsOffset = 0x2FF5;
  14.    DWORD InsLen = 6;
  15.    DWORD old;
  16.    VirtualProtect((void*)(BaseAddr + TimeInsOffset), InsLen, PAGE_EXECUTE_READWRITE, &old);
  17.    BYTE INS[] = { 0x90,0x90,0x90,0x90,0x90,0x90 };
  18.    memcpy((void *)(BaseAddr + TimeInsOffset), INS, InsLen);
  19.    VirtualProtect((void*)(BaseAddr + TimeInsOffset), InsLen, old, &old);
  20. }
  21. void MineDlg::OnBnClickedButton2() // 恢复字节即可取消时间暂停
  22. {
  23.    // TODO: 在此添加控件通知处理程序代码
  24.    auto BaseAddr = GetBaseAddr();
  25.    DWORD TimeOffset = 0x579C;
  26.    DWORD TimeInsOffset = 0x2FF5;
  27.    DWORD InsLen = 6;
  28.    DWORD old;
  29.    VirtualProtect((void*)(BaseAddr + TimeInsOffset), InsLen, PAGE_EXECUTE_READWRITE, &old);
  30.    BYTE INS[] = { 0xFF,0x05,0x9C,0x57,0x00,0x01 };
  31.    memcpy((void*)(BaseAddr + TimeInsOffset), INS, 6);
  32.    VirtualProtect((void*)(BaseAddr + TimeInsOffset), InsLen, old, &old);
  33. }
复制代码
测试
[img=720,398.4119106699752]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506998.png[/img]

透视

经过上面动态调试我们得出结论:0x2F80函数是踩雷函数。
我们如果调用这个函数,是不是就能够实现透视了呢?
我们仍旧采取线程回调的方式
  1. void MineDlg::OnBnClickedButton3()
  2. {
  3.    // TODO: 在此添加控件通知处理程序代码
  4.    DWORD ESPOffset = 0x2f80;
  5.    DWORD FuncAddr = GetBaseAddr() + ESPOffset;
  6.    // 创建不带参数的线程
  7.    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)FuncAddr, NULL, 0, NULL);
  8. }
复制代码
测试
[img=720,347.21205597416576]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506680.png[/img]

一键扫雷

跟透视差不多,只不过创建带参数的线程回调
  1. void MineDlg::OnBnClickedButton4()
  2. {
  3.    // TODO: 在此添加控件通知处理程序代码
  4.    DWORD ESPOffset = 0x347C;
  5.    DWORD FuncAddr = GetBaseAddr() + ESPOffset;
  6.    //创建带参数的线程
  7.    struct { int a; } s = { 0 };
  8.    CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)FuncAddr, &s, NULL, NULL);
  9. }
复制代码
测试
[img=720,350.7096774193548]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202408121506441.png[/img]

总结

通过这个小项目,对WIN游戏安全有初步的认识,并且加强对软件的逆向头脑,加强动态调试的能力,找到软件关键的基地址,通过CE修改器,初步pojie软件,了解软件的状态,修改时间(时间停息等等),理解几个紧张的API,FindWindow获取句柄,WriteProcessMemory写入内存信息,LoadLibraryA加载指定 DLL 并返回模块句柄,GetProcAddress,获取指定 dll 的导出函数的地址,CreateThread 线程回调函数等等。多写,多做,多调,多实验,加油,互勉。
更多网安技能的在线实练习习,请点击这里>>
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4