游戏安全入门-扫雷分析远程线程注入

news/2024/11/18 7:38:47/文章来源:https://www.cnblogs.com/hetianlab/p/18356388

前言

无论学习什么,首先,我们应该有个目标,那么入门windows游戏安全,脑海中浮现出来的一个游戏 -- 扫雷,一款家喻户晓的游戏,虽然已经被大家分析的不能再透了,但是我觉得自己去分析一下还是极好的,把它作为一个小目标再好不过了。

我们编写一个妙妙小工具,工具要求实现以下功能:时间暂停、修改表情、透视、一键扫雷等等。

本文所用工具:

Cheat Engine、x32dbg(ollydbg)、Visual Studio 2019

扫雷游戏分析

游戏数据在内存中是地址,那么第一个任务,找内存地址

打开CE修改器

修改时间->时间暂停

计数器的时间是一个精确的值,所以我们通过精确数值扫描出来,游戏开始之前计数器上的数是0,所以我们扫描0。

image.png

时间在变化,选择介于什么数值之间再次扫描

image.png

可得 0x100579c --- winmine.exe+579C

image.png

我们发现这个数据都是直接通过基址 + 固定偏移能直接得到的。

然后我们对数据去找 是什么改写了这个地址,得到一个指令和指针:

image.png

时间:0x100579c

修改表情 - 没啥用

修改表情这个功能怎么搞我觉得还是很容易想到的,这个按钮的作用是重新开始游戏,开始游戏,游戏胜利,游戏失败。

(表情的状态被分成了两个变量(4byte)来控制)

所以它是一种状态,所以我们通过0和1进行扫描,游戏进行状态输入1进行扫描,还原游戏之后输入0进行扫描。

首先是游戏进行状态,输入1进行扫描

image.png

再点击表情,将游戏还原,输入0开始扫描

image.png

如此反复进行扫描,得到表情的内存地址

0x1005164 -- winmine.exe+5164

image.png

但是嘞,修改成2或者3,表情没有心得反应,所以控制游戏胜利和游戏失败的是其他的地址,我们知道,一般来说,一个功能的代码在内存中基本上都是连续的,(就像你修改一个游戏的血量,浏览血量内存块,你可以发现怒气,蓝量等内存地址)

所以,我们浏览内存

image.png

image.png

0x1005164-4 = 0x1005160

修改为3,发现出现了戴墨镜的表情(游戏胜利)

但是这个胜利知识一个状态,并不能说明扫雷完成.

image.png

表情:0x1005160与0x1005164

【----帮助网安学习,以下所有学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】

 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)

透视 - 显示雷区

思考游戏结束的时候会自动显示所有的雷,因此我们动态调试,看看在哪个函数调用之后会显示所有的雷

image.png

image.png

经过几次的动态调试之后发现:0x2F80函数是我们要找的结果。

image.png

一键扫雷

通过透视,我们玩一把游戏,使得游戏胜利(点完最后一个)

image.png

image.png

然后后两个函数,是破纪录跟英雄榜的函数

image.png

image.png

ret来到了这儿,游戏通关了,来到了这儿,可以知道,这个0x347c就是判断输赢的函数

并且通过调试发现由一个参数 0 1 来控制,所以跟透视差不多,带个参数线程回调就完了

image.png

编写妙妙小工具

怎么实现这个工具呢,当然是选择DLL注入

那么dll 怎么注入进去呢,这里选择远程线程注入

这里先简单介绍下什么是远程线程注入

前置知识-动态调用dll

主要就是这几个个 API:

LoadLibraryA

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

GetProcAddress

获取指定 dll 的导出函数的地址。

第一个参数是模块句柄,第二个参数是模块函数,返回值为函数的地址。

通过这两个函数,我们可以拿到所有函数的地址,然后就能进行调用。

CreateThread - 远程线程注入

里面几乎只有一个参数,那就是线程回调函数,然后当然还有返回地址,返回线程 id 啥的,这里我们都可以不用管,几乎是与 Linux 的创建线程函数一致。

还有一个远程版本的叫 CreateRemoteThread,它可以给别的进程创建一个线程并可以在本进程创建那个进程调用的回调函数。我们可以在回调函数中加载指定的 dll,在 dllmain 的入口当中,有一个 switch 的四个选项。

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
​
BOOL APIENTRY DllMain( HMODULE hModule,//指向自身的句柄                      DWORD  ul_reason_for_call,//调用原因                      LPVOID lpReserved//隐式加载or显式加载                    )
{   switch (ul_reason_for_call)   {   case DLL_PROCESS_ATTACH://附加到进程上时执行   case DLL_THREAD_ATTACH://附加到线程上时执行   case DLL_THREAD_DETACH://从线程上剥离时执行   case DLL_PROCESS_DETACH://从进程上剥离时执行       break;   }   return TRUE;
}
​
​

我们可以在 DLL_PROCESS_ATTACH 的选项中加入代码,让它在加载的时候调用执行。

那么我们的步骤是:

  1. 打开指定进程获得句柄

  2. 开辟远程进程的空间,分配可读可写段。

  3. 调用 WriteProcessMemory 将 dll 路径写入该内存区域。

  4. 创建远程线程,回调函数使用 LoadLibrary 加载指定 dll。

  5. 等待返回(loadLibrary返回)

  6. 释放空间

  7. 释放句柄

  8. 返回结果

demo:

void Inject(DWORD ProcessId, const char* szPath)
{   //1.打开目标进程获取句柄   HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);   printf("进程句柄:%p\n", hProcess);   //2.在目标进程体内申请空间   LPVOID lpAddress = VirtualAllocEx(hProcess, NULL, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);   //3.写入DLL路径   SIZE_T dwWriteLength = 0;   WriteProcessMemory(hProcess, lpAddress, szPath, strlen(szPath), &dwWriteLength);   //4.创建远程线程,回调函数使用 LoadLibrary 加载指定 dll   HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, lpAddress, NULL, NULL);   //5.等待返回(loadLibrary返回)   WaitForSingleObject(hThread, -1);   //6.释放空间   VirtualFreeEx(hProcess, lpAddress, 0, MEM_RELEASE);   //7.释放句柄   CloseHandle(hProcess);   CloseHandle(hThread);   //返回结果   AfxMessageBox(L"完成");
}

编写DLL注入器

#include<windows.h>
#include<iostream>
#include<time.h>
#include<stdlib.h>
#include<TlHelp32.h>
DWORD FindProcess() {   HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);   PROCESSENTRY32 pe32;   pe32 = { sizeof(pe32) };   BOOL ret = Process32First(hSnap, &pe32);   while (ret)   {       if (!wcsncmp(pe32.szExeFile, L"mine.exe", 11)) {           printf("Find winmine.exe Process %d\n", pe32.th32ProcessID);           return pe32.th32ProcessID;       }       ret = Process32Next(hSnap, &pe32);   }   return 0;
}
void Inject(DWORD ProcessId, const char* szPath)
{   //1.打开目标进程获取句柄   HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);   printf("进程句柄:%p\n", hProcess);   //2.在目标进程体内申请空间   LPVOID lpAddress = VirtualAllocEx(hProcess, NULL, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);   //3.写入DLL路径   SIZE_T dwWriteLength = 0;   WriteProcessMemory(hProcess, lpAddress, szPath, strlen(szPath), &dwWriteLength);   //4.创建远程线程,回调函数使用 LoadLibrary 加载指定 dll   HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, lpAddress, NULL, NULL);   //5.等待返回(loadLibrary返回)   WaitForSingleObject(hThread, -1);   //6.释放空间   VirtualFreeEx(hProcess, lpAddress, 0, MEM_RELEASE);   //7.释放句柄   CloseHandle(hProcess);   CloseHandle(hThread);
}
​
int main() {   DWORD ProcessId = FindProcess();   while (!ProcessId) {       printf("未找到扫雷程序,等待两秒中再试\n");       Sleep(2000);       ProcessId = FindProcess();   }   printf("开始注入进程...\n");   Inject(ProcessId, "E:\\CODE\\wimine\\Mine\\release\\Mine.dll");   printf("注入完毕\n");
​
}

编写DLL

这里我们采用MFC DLL 基于对话框 (dialog)的方式编写(简单),使用静态编译的方式

image.png

image.png

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

image.png

这样我们在加载窗体的时候需要创建一个窗体类对象用它的 DoModal 方法去显示,用线程回调的方式加载并且初始化InitInstance

DWORD WINAPI DlgThreadCallBack(LPVOID lp) {   MineDlg* Dlg;   Dlg = new MineDlg();   Dlg->DoModal();   delete Dlg;   FreeLibraryAndExitThread(theApp.m_hInstance, 1);   return 0;
}
// CMineApp 初始化
BOOL CMineApp::InitInstance()
{   CWinApp::InitInstance();   ::CreateThread(NULL, NULL, DlgThreadCallBack, NULL, NULL, NULL);   return TRUE;
}

时间暂停

上面我们找到了它控制时间增加的指令,我们把它们全部 NOP 掉,就可以实现时间暂停

写两个按钮,创建下面的事件实现时间暂停开关。

image.png

DWORD GetBaseAddr() {   HMODULE hMode = GetModuleHandle(nullptr);   //LPWSTR s = (LPWSTR)malloc(0x100);   //wsprintf(s, L"基址:%p", hMode);   //AfxMessageBox(s);   return (DWORD)hMode;
}
​
void MineDlg::OnBnClickedButton1() // 时间暂停
{   // TODO: 在此添加控件通知处理程序代码   auto BaseAddr=GetBaseAddr();   DWORD TimeOffset = 0x579C;   DWORD TimeInsOffset = 0x2FF5;   DWORD InsLen = 6;   DWORD old;   VirtualProtect((void*)(BaseAddr + TimeInsOffset), InsLen, PAGE_EXECUTE_READWRITE, &old);   BYTE INS[] = { 0x90,0x90,0x90,0x90,0x90,0x90 };   memcpy((void *)(BaseAddr + TimeInsOffset), INS, InsLen);   VirtualProtect((void*)(BaseAddr + TimeInsOffset), InsLen, old, &old);
}
​
​
void MineDlg::OnBnClickedButton2() // 恢复字节即可取消时间暂停
{   // TODO: 在此添加控件通知处理程序代码   auto BaseAddr = GetBaseAddr();   DWORD TimeOffset = 0x579C;   DWORD TimeInsOffset = 0x2FF5;   DWORD InsLen = 6;   DWORD old;   VirtualProtect((void*)(BaseAddr + TimeInsOffset), InsLen, PAGE_EXECUTE_READWRITE, &old);   BYTE INS[] = { 0xFF,0x05,0x9C,0x57,0x00,0x01 };   memcpy((void*)(BaseAddr + TimeInsOffset), INS, 6);   VirtualProtect((void*)(BaseAddr + TimeInsOffset), InsLen, old, &old);
}

测试

image.png

透视

经过上面动态调试我们得出结论:0x2F80函数是踩雷函数。

我们如果调用这个函数,是不是就能够实现透视了呢?

我们依旧采取线程回调的方式

void MineDlg::OnBnClickedButton3()
{   // TODO: 在此添加控件通知处理程序代码   DWORD ESPOffset = 0x2f80;   DWORD FuncAddr = GetBaseAddr() + ESPOffset;   // 创建不带参数的线程   CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)FuncAddr, NULL, 0, NULL);
}

测试

image.png

一键扫雷

跟透视差不多,只不过创建带参数的线程回调

void MineDlg::OnBnClickedButton4()
{   // TODO: 在此添加控件通知处理程序代码   DWORD ESPOffset = 0x347C;   DWORD FuncAddr = GetBaseAddr() + ESPOffset;   //创建带参数的线程   struct { int a; } s = { 0 };   CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)FuncAddr, &s, NULL, NULL);
​
}

测试

image.png

总结

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

更多网安技能的在线实操练习,请点击这里>>

  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/783173.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线段树进阶 Part 1

线段树常见技巧,可持久化线段树,线段树合并,树套树线段树是信息学竞赛最常见的数据结构。本篇笔记总结技巧和应用,不介绍基本线段树算法。 1. 常见技巧 1.1 信息设计 用线段树解决问题,首先得考虑维护哪些信息。若不带修,任何 满足结合律且封闭 的信息(称为半群)都是可…

大气热力学(16)——风矢端图的分析方法(上篇)

注:本篇涉及超级单体的概念,因此在学习本篇教程前,建议先看《雷达气象学(9)——反射率因子图分析(强对流篇)》! 目录16.1 风矢端图的画法16.2 整体风切变(Bulk Shear)16.3 风矢端线的典型形状16.4 平均风切变(Mean Wind Shear)16.5 使用 Bunkers 技术预测风暴的移动…

距离-有这么多类

在做分类时常常需要估算不同样本之间的相似性度量(SimilarityMeasurement),这时通常采用的方法就是计算样本间的“距离”(Distance)。采用什么样的方法计算距离是很讲究,甚至关系到分类的正确与否。本文的目的就是对常用的相似性度量作一个总结。 本文目录: 1.欧氏距离 2.曼…

解决pypi上传轮子unsupported platform tag linux_x86_64问题

对于一个纯Python的项目,从构建到发布是比较容易的。但是如果构建的轮子中含有C代码或者生成的动态链接库,那么构建发布有另外一套规则。我们需要经过manylinux平台的验证,以及动态链接库的固化等过程,还需要当心动态链接库的存放地址等信息。本文主要是提供了一个流程化的…

如何选择合适的在线文档管理工具?

国内外主流的10款在线文档管理软件对比:PingCode、Worktile、腾讯文档、石墨文档、语雀、Egnyte、Zoho Office Suite、Microsoft SharePoint、ONLYOFFICE DocSpace、DocuWare Cloud。在当今的数字化办公环境中,有效管理和分享文档变得至关重要,但找到一个既能满足功能需求又…

【深度分析】关于SPN不正确导致SQL数据库连接失败

连接SQL Server数据库时发生报错“The target principal name is incorrect. Cannot generate SSPI context”,无法连接,可能是由于AD域中记录了错误的SPN,导致无法进行身份验证而连接失败。下文通过简述Kerberos认证过程、SPN的组成,引出由SPN错误引发报错的解决方法。 Ke…

可持久化可反悔贪心

接到上级通知,贪心思路假了,紧急需要调整思路 思路假了?考虑反悔 while(思路==false){cout<<"思路假了"<<endl;思路=true;cout<<"改对了"<<endl; }Sample Output 思路假了 改对了 思路假了 改对了 思路假了 改对了 思路假了 改…

使用 navigateTo 实现灵活的路由导航

title: 使用 navigateTo 实现灵活的路由导航 date: 2024/8/13 updated: 2024/8/13 author: cmdragon excerpt: 摘要:本文详细介绍 Nuxt.js 中的 navigateTo 函数,包括基本用法、在路由中间件中使用、导航到外部 URL 和新标签页打开链接的方法,以及参数详解和注意事项,展示…

神经网络之卷积篇:详解Padding

详解Padding 为了构建深度神经网络,需要学会使用的一个基本的卷积操作就是padding,让来看看它是如何工作的。如果用一个33的过滤器卷积一个66的图像,最后会得到一个44的输出,也就是一个44矩阵。那是因为33过滤器在66矩阵中,只可能有44种可能的位置。这背后的数学解释是,如…

ChatMoneyAI嘴替,高情商回复

本文由 ChatMoney团队出品会说话是一个人的优势,而会接话才是一个人的本事。现实中很多人有这样的困扰:朋友聚会、上门拜访以及和人聊天。是不是完全不知道如何回应,只会说“嗯”、“对”、“好”。这种回应方式,会让人没有和你聊下去的欲望,也容易把天聊死,从而错失大把…