53.网游逆向分析与插件开发-游戏反调试功能的实现-通过内核信息检测调试器

news/2024/7/24 9:25:56 标签: 游戏, 网游逆向

码云地址(master分支):https://gitee.com/dye_your_fingers/sro_-ex.git

码云版本号:b44fddef016fc1587eda40ca7f112f02a8289504

代码下载地址,在 SRO_EX 目录下,文件名为:SRO_Ex-通过内核信息检测调试器.zip

链接:https://pan.baidu.com/s/1W-JpUcGOWbSJmMdmtMzYZg

提取码:q9n5

--来自百度网盘超级会员V4的分享

HOOK引擎,文件名为:黑兔sdk.zip

链接:https://pan.baidu.com/s/1IB-Zs6hi3yU8LC2f-8hIEw

提取码:78h8

--来自百度网盘超级会员V4的分享

以 检测调试器-CSDN博客 它的代码为基础进行修改

BeingDebugged的调试检测基本上用处不大,OD很多插件可以过掉,它核心根本的问题就是数据毕竟是在用户态(软件调试器设计的基本原理-CSDN博客通过它软件调试器设计的基本原理-CSDN博客里面写的peb的位置,然后通过汇编对它BeingDebugged这个字段进行修改成0就可以轻松破解,就一次性把 IsDebuggerPresent函数、CheckRemoteDebuggerPresent函数 这两个函数全部都过掉了),现在为了加强检测要去访问内核的数据,原则上用户态取不到内核的数据,好在微软还是留了口子,一个未公开的API(导出表里有文档里没有这样的函数就叫做未公开的API,ntdll文件里有很多未公开API),如图1。

图1:其中信息类型是一个枚举非常大,在不同操作系统下有不同的变化,我们要用的到它的值是0x07 DebugPort、0x1 EDebugObjectHandle、0x1F DebugFlags(它的值是0表示被调试,1表示未被调试),这样一般的调试器就躲不掉了,NtQueryInformationProcess能取很多内核中的数据,使用详情看下方代码

9a060b31a72f4731a6615cb31acb286b.png

24a825db3b8249d0b8332f7d77426146.jpg

 

GameEx.cpp文件的修改,修改了ExitGame函数

#include "pch.h"
#include "GameEx.h"
#include "htdHook2.h"
#include "GameProtect.h"

extern int client;
extern GameProtect* _protect;
extern unsigned _stdcall GetFunctionAddress(int index);
htd::hook::htdHook2 hooker;

#include <windows.h>
#include<stdio.h>
#include<TlHelp32.h>

/**
  声明要拦截的函数地址
*/
auto h = GetModuleHandle(NULL);
DWORD address = (DWORD)h;
DWORD addRExit = address + 0x88C77E;

size_t 被拦截修改的函数的地址 = (size_t)addRExit;

LONG NTAPI 异常回调(struct _EXCEPTION_POINTERS* Excep)
{
    printf("异常回调1\n");

    /**
      判断出异常的地方是否为 我们修改的地方
    */
    if ((size_t)Excep->ExceptionRecord->ExceptionAddress == 被拦截修改的函数的地址) {

        //const char* szStr = "nei Rong Bei Xiu Gai";
        //*(DWORD*)(Excep->ContextRecord->Esp + 0x8) = (DWORD)szStr;
        //szStr = "biao Ti Bei Xiu Gai";
        //*(DWORD*)(Excep->ContextRecord->Esp + 0xC) = (DWORD)szStr;

        AfxMessageBox(L"游戏退出!");
        DWORD* _esp = (DWORD*)Excep->ContextRecord->Esp;
        DWORD _val = _esp[1];

        if (_val == 0x1035D0C) {
            AfxMessageBox(L"游戏退出2!");
            auto hMuls = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, L"system_seamp");
            if (hMuls) ReleaseSemaphore(hMuls, 1, 0);
            ExitProcess(0);
        }

        Excep->ContextRecord->Eip = *(DWORD *) Excep->ContextRecord->Esp;
        Excep->ContextRecord->Esp += 8;
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    else {
        /**
          防止被其它地方修改了函数地址
        */
        Excep->ContextRecord->Dr0 = 被拦截修改的函数的地址;
        Excep->ContextRecord->Dr7 = 0x405;
        return EXCEPTION_CONTINUE_SEARCH;
    }

}

VOID 设置线程的dr寄存器(HANDLE 线程句柄) {

    printf("设置线程的dr寄存器1\n");
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_ALL;
    GetThreadContext(线程句柄, &ctx);
    ctx.Dr0 = 被拦截修改的函数的地址;
    ctx.Dr7 = 0x1;
    SetThreadContext(线程句柄, &ctx);
    printf("设置线程的dr寄存器2\n");
}

VOID 使用dr寄存器拦截修改函数() {
    printf("使用dr寄存器拦截修改函数1\n");
    HANDLE 线程快照句柄 = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
    if (线程快照句柄 == INVALID_HANDLE_VALUE) {
        printf("线程快照创建失败");
        return;
    }

    THREADENTRY32* 线程结构体 = new THREADENTRY32;
    线程结构体->dwSize = sizeof(THREADENTRY32);

    /**
      Thread32First获取快照中第一个线程
       返回值bool类型
    */
    // Thread32First(线程快照句柄, &线程结构体);

    HANDLE 线程句柄 = NULL;

    printf("使用dr寄存器拦截修改函数2\n");

    /**
      Thread32Next获取线程快照中下一个线程
    */
    while (Thread32Next(线程快照句柄, 线程结构体))
    {
        if (线程结构体->th32OwnerProcessID == GetCurrentProcessId()) {
            printf("使用dr寄存器拦截修改函数3\n");
            线程句柄 = OpenThread(THREAD_ALL_ACCESS, FALSE, 线程结构体->th32ThreadID);
            printf("使用dr寄存器拦截修改函数4\n");
            设置线程的dr寄存器(线程句柄);
            printf("使用dr寄存器拦截修改函数5\n");
            CloseHandle(线程句柄);
        }
    }
}

bool ExitGame(HOOKREFS2) {
    if (_protect->CheckDebugByNT())AfxMessageBox(L"检测到了DEBUG程序的存在");
	// AfxMessageBox(L"游戏退出2222!");
	DWORD* _esp = (DWORD*)_ESP;
	DWORD _val = _esp[1];

	if (_val == 0x1035D0C) {
		// AfxMessageBox(L"游戏退出!");
		auto hMuls = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, L"system_seamp");
		if (hMuls) ReleaseSemaphore(hMuls, 1, 0);
        client--;
		ExitProcess(0);
	}
	return true;
}

GameEx::GameEx()
{
	// AfxMessageBox(L"注册hook!");
	// auto h = GetModuleHandle(NULL);
	// DWORD address = (DWORD)h;
    // DWORD* addRExit = (DWORD*)(address + 0x88C77E);
    /**addRExit = 0;*/
	// CString txt;
    // txt.Format(L"addRExit[0]D:%d,addRExit[0]X:%X,addRExit:%X", addRExit[0], addRExit[0], addRExit);
    // AfxMessageBox(txt);

    // hooker.SetHook((LPVOID)addRExit, 3, ExitGame);
    //AddVectoredExceptionHandler(1, 异常回调);
    //设置线程的dr寄存器(GetCurrentThread());
}

void GameEx::InitInterface()
{
    unsigned addr =  GetFunctionAddress(0);
    hooker.SetHook((LPVOID)(addr + 0x30 - 2), 0x3, ExitGame);
    hooker.SetHook((LPVOID)(addr + 0x51 - 2), 0x3, ExitGame);

}

GameProtect.h文件的修改,新加 ZwSetInformationThread变量、NtQueryInformationProcess变量、hNtdll变量、HProcess变量、InitApiEx函数、CheckDebugByPEB函数、CheckDebugByNT函数,ZwSetInformationThreadPtr函数指针、NtQueryInformationProcessPtr函数指针

#pragma once

/*
	第一个参数是线程的id
	第二个参数是给一个0x11,0x11就代表把线程设置成隐匿的状态

*/
typedef NTSTATUS(NTAPI* ZwSetInformationThreadPtr)(DWORD, DWORD, DWORD, DWORD);
typedef NTSTATUS(NTAPI* NtQueryInformationProcessPtr)(HANDLE, DWORD, PVOID, ULONG, PULONG);

typedef struct CODEContext {
	unsigned start;
	bool hide;
	unsigned short r_count; // 重定位信息,call、jmp这样的需要重定位的数据的个数
	unsigned short len; // 代码的长度
}*PCODEContext;

typedef struct HIDE_CODE {
	unsigned Start;
	unsigned Index;
}*PHIDE_CODE;

class GameProtect
{
public:
	unsigned GetAddress(int index);
	unsigned GetAddressHide(unsigned _eip);
	GameProtect();
private:
	int _HideCount = 0;
	PHIDE_CODE _HideCode;
	LPVOID* _EntryCode;
	int _CodeCount{};
	char _EntryCodeEx[8]{(char)0xE8, (char)0x00, (char)0x00,(char)0x00, (char)0x00, (char)0xFF, (char)0xE0};
	bool MulCheckBySempore();
public:
	void  CheckMult(); // 检测有没有多开
public:
	bool InitEntryCode(); // 释放保护代码数据
	BOOL CheckDebugByPEB(); // true表示存在debug
	BOOL CheckDebugByNT(); // true表示存在debug
private:
	void InitApiEx();
	HMODULE hNtdll;
	ZwSetInformationThreadPtr ZwSetInformationThread;
	NtQueryInformationProcessPtr NtQueryInformationProcess;
	void AntiDebug();
	HANDLE HProcess;
};

GameProtect.cpp文件的修改,修改了AntiDebug函数、GameProtect函数,新加 InitApiEx函数、CheckDebugByPEB函数、CheckDebugByNT函数,删除了 ZwSetInformationThreadPtr函数指针

#include "pch.h"
#include "GameProtect.h"

GameProtect* _protect;
extern int client;

unsigned _stdcall GetFunctionAddress(int index) {
	
	//CString txt;
	//txt.Format(L"接收到:%d", index);
	//AfxMessageBox(txt);
	
	return _protect->GetAddress(index);
}

unsigned GameProtect::GetAddress(int index)
{
	//CString txt;
	unsigned result = (unsigned)this->_EntryCode[index];

	//txt.Format(L"index:%d获取地址:%x", index, result);
	// AfxMessageBox(txt);
	return result;
}

unsigned GameProtect::GetAddressHide(unsigned _eip)
{
	//CString txt;
	for (int i = 0; i < _HideCount; i++){
		if (_HideCode[i].Start == _eip) {
			return (unsigned)_EntryCode[_HideCode[i].Index];
		}
	}

	//txt.Format(L"index:%d获取地址:%x", index, result);
	// AfxMessageBox(txt);
	return 0;
}

GameProtect::GameProtect()
{
	//AfxMessageBox(L"122222");
	InitApiEx();
	_protect = this;
	// 为了后续内容 这里先注释掉
	// AntiDebug();
	if (!InitEntryCode()) {
		AfxMessageBox(L"程序加载失败!");
		ExitProcess(0);
	}
	CString txt;
	txt.Format(L"111");
	AfxMessageBox(txt);
}

bool GameProtect::MulCheckBySempore()
{
	auto hMuls = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, L"system_seamp");
	
	if (!hMuls) {
		hMuls = CreateSemaphore(0, 3, 3, L"system_seamp");
	}

	if (WaitForSingleObject(hMuls, 0) == WAIT_TIMEOUT) return true;

	return false;
}

void GameProtect::CheckMult()
{
	
	if (MulCheckBySempore()) {
		AfxMessageBox(L"当前客户端启动已经超过最大数量");
		ExitProcess(0);
	}
}

LONG _stdcall PVEHandl(PEXCEPTION_POINTERS val) {
	if (val->ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT) {
		unsigned _eip = val->ContextRecord->Eip;
		unsigned _eipReal = _protect->GetAddressHide(_eip);
	/*	CString txt;
		txt.Format(L"PVEHandl当前地址:%X", _eipReal);
		AfxMessageBox(txt);*/
		if (_eipReal) {
			val->ContextRecord->Eip = _eipReal;
			return EXCEPTION_CONTINUE_EXECUTION; // 继续执行
		}
		else return EXCEPTION_CONTINUE_SEARCH;
	}
	return EXCEPTION_CONTINUE_SEARCH;
}

bool GameProtect::InitEntryCode()
{

	TCHAR FileModule[0x100];
	GetModuleFileName(NULL, FileModule, 0x100);
	auto hFile = CreateFile(FileModule, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if (hFile == INVALID_HANDLE_VALUE) {
		return false;
	}

	DWORD dRead;
	DWORD filelen = GetFileSize(hFile, &dRead);

	char* _data = new char[filelen];

	if (ReadFile(hFile, _data, filelen, &dRead, NULL)) {
		char* _dataBegin = _data;
		unsigned* _uRead = (unsigned*)(_data + filelen - 4);

		for (int i = 0; i < filelen - _uRead[0]-4; i++) // 解密数据
		{
			_data[_uRead[0] + i] = _data[_uRead[0] + i] ^ 0x23;
		}

		//unsigned* _uRead = (unsigned*)_data[filelen - 4];
		filelen = _uRead[0];// 真实的文件大小
		_uRead = (unsigned*)(_data + filelen);
		unsigned code_count = _uRead[0];
		_data = _data + filelen + sizeof(code_count);

		PCODEContext _ContextArrys = (PCODEContext)_data;
	
		for (int i = 0; i < code_count; i++)
		{
			if (_ContextArrys[i].hide) {
				_HideCount++;
			}
		}

		if (_HideCount > 0) {
			AddVectoredExceptionHandler(1, PVEHandl);
			_HideCode = new HIDE_CODE[_HideCount];
			_HideCount = 0;
		}

		_data = _data + sizeof(CODEContext) * code_count;
		_EntryCode = new LPVOID[code_count];
		for (int i = 0; i < code_count; i++)
		{
			char* _tmpByte = new char[_ContextArrys[i].len + 2];
			_EntryCode[i] = _tmpByte;
			_tmpByte[0] = 0x9D;
			_tmpByte[1] = 0x61;

			/*CString txt;
			txt.Format(L"当前地址:%X", _EntryCode[i]);
			AfxMessageBox(txt);*/
			

			unsigned offset = sizeof(_ContextArrys[i].r_count) * _ContextArrys[i].r_count;
			memcpy((char*)_EntryCode[i] + 2, _data + offset, _ContextArrys[i].len);
			unsigned short* rel = (unsigned short*)_data;
			for (int x = 0; x < _ContextArrys[i].r_count; x++)
			{
				unsigned* _callAddr = (unsigned*)((char*)_EntryCode[i] + rel[x] + 1 + 2);
				_callAddr[0] = _callAddr[0] - (unsigned)_callAddr - 4;
				// AfxMessageBox(L"这里代码存在问题,后面改");
			}
			_data = _data + offset + _ContextArrys[i].len;
			DWORD dOld;
			VirtualProtect(_EntryCode[i], _ContextArrys[i].len, PAGE_EXECUTE_READWRITE, &dOld);
			if (_ContextArrys[i].hide) {
				_EntryCode[i] = (LPVOID)((unsigned)_EntryCode[i] + 2);
				_HideCode[_HideCount].Index = i;
				_HideCode[_HideCount].Start = _ContextArrys[i].start;
				_HideCount++;
			}
		}

		delete[]_dataBegin;
	}
	else return false;


	auto hMod = GetModuleHandle(NULL);
	unsigned addMod = (unsigned)hMod;
	unsigned addReset = addMod + 0xC2EFFC;
	DWORD dOld = GetFunctionAddress(0);
	// ::VirtualProtect((LPVOID)addReset, 4, PAGE_EXECUTE_READWRITE, &dOld);
	// ::VirtualProtect(this->_GameCode, 0x1000, PAGE_EXECUTE_READWRITE, &dOld);
	unsigned* read = (unsigned*)addReset;
	read[0] = (unsigned)this->_EntryCodeEx;
	//_EntryCode[1] = GetFunctionAddress;

	read = (unsigned*)(this->_EntryCodeEx + 1);
	read[0] = (unsigned)GetFunctionAddress - 5 - (unsigned)(this->_EntryCodeEx);
	return true;
}

/**
	下方的代码不猥琐
	   因为用了 ZwSetInformationThread、NtQueryInformationProcess 这样的常量字符串
	   然后可以在 ZwSetInformationThread、NtQueryInformationProcess 做HOOK,拦截我们
	   这样我们可以把 ZwSetInformationThread、NtQueryInformationProcess 函数拷贝到我们的内存空间里去调用
	   不再通过 GetProcAddress 方式去获取函数地址,直接通过我们复制到的地址调用它,最终这俩函数会去内核,我们也可以
	   把进内核的逻辑自己实现,这样就让别人完全搞不定了
*/
void GameProtect::InitApiEx()
{

	hNtdll = LoadLibrary(L"ntdll.dll");
	if (hNtdll) {
		/**
			GetProcAddress通过导出表获取函数
		*/
		ZwSetInformationThread = (ZwSetInformationThreadPtr)GetProcAddress(hNtdll, "ZwSetInformationThread");
		NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hNtdll, "NtQueryInformationProcess");
	}
	HProcess = GetCurrentProcess();
}

void GameProtect::AntiDebug()
{
	/**
	  这个函数在ntdll..dll里
		这个位置真正使用时不能这样写,因为这样写了之后,会被人看出来
		最好的方式是通过启动器计算好传递过来,传递过来就是悄无声息了
		相当于调用了一个函数,但破解者不知道,调用函数还可以猥琐点
		把它拷贝到我们的内存空间里调用。
	*/
	if (ZwSetInformationThread) {
		ZwSetInformationThread((DWORD)GetCurrentThread(), 0x11, 0x0, 0x0);
	}

}

BOOL GameProtect::CheckDebugByPEB()
{
	/**

		IsDebuggerPresent 检测当前进程
		CheckRemoteDebuggerPresent 检测指定进程
		它俩检测的都是peb结构
		有的调试器会把它们给处理掉,比如OD,它有插件会把这个东西修复掉
		修复掉之后就检测不到了,它修复的原理就是把 BeingDebugged 这个字段给处理了
		就是说有调试器 BeingDebugged它的值是1,然后我再给BeingDebugged写成0就行了

	*/
	BOOL bResult = IsDebuggerPresent();
	if (bResult) {
		return bResult;
	}
	CheckRemoteDebuggerPresent(GetCurrentProcess(), &bResult);
	if (bResult) {
		AfxMessageBox(L"CheckRemoteDebuggerPresent 检测到调试器");
		bResult = TRUE;
	}
	return bResult;
}

BOOL GameProtect::CheckDebugByNT()
{
	// 
	DWORD debug_port = 0;
	//NtQueryInformationProcess(HProcess, 0x07, &debug_port, sizeof(debug_port), 0x0);
	//if (debug_port ) {
	//	return TRUE;
	//}
	// 在64位下这个Object是一个HANDLE结构
	HANDLE debug_object = 0;
	//NtQueryInformationProcess(HProcess, 0x1E, &debug_object, sizeof(debug_object), 0x0);
	//if (debug_object) {
	//	return TRUE;
	//}
	BOOL debug_flags = 0;
	NtQueryInformationProcess(HProcess, 0x1F, &debug_flags, sizeof(debug_flags), 0x0);
	if (!debug_flags) {
		return TRUE;
	}
	return FALSE;
}

 

 

 

 


http://www.niftyadmin.cn/n/5290170.html

相关文章

Kali Linux如何启动SSH并在Windows系统远程连接

文章目录 1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 简单几步通过[cpolar 内网穿透](cpolar官网-安全的内网穿透工具 | 无需公网ip | 远程访问 | 搭建网站)软件实现ssh 远程连接kali! …

Flask 日志

flask 日志 代码源码源自编程浪子flask点餐小程序代码 记录用户访问日志 和 错误日志 这段代码是一个基于Flask框架的日志服务类&#xff0c;用于 记录用户访问日志 和 错误日志。代码中定义了一个名为LogService的类&#xff0c;其中包含了两个静态方法&#xff1a;addAcc…

【VUE】Flask+vue-element-admin前后端分离项目发布到linux服务器操作指南

目录 一、Flask后端发布环境搭建1.1 python环境第一步&#xff1a;安装python环境第二步&#xff1a;配置python虚拟环境 1.2 uwsgi环境1.3 nginx配置1.4 测试 二、VUE前端发布环境搭建2.1 配置修改2.2 打包上传服务器2.3 nginx配置2.3 测试 三、联合调试 一、Flask后端发布环境…

Feature Prediction Diffusion Model for Video Anomaly Detection 论文阅读

Feature Prediction Diffusion Model for Video Anomaly Detection论文阅读 Abstract1. Introduction2. Related work3. Method3.1. Problem Formulation3.2. Feature prediction diffusion module 3.3. Feature refinement diffusion module4. Experiments and discussions4.1…

节假日计算器

节假日计算器 代码结果 代码 import cn.hutool.core.text.StrFormatter; import com.google.common.collect.Lists; import lombok.Data;import java.time.LocalDate; import java.time.format.TextStyle; import java.util.ArrayList; import java.util.Collections; import …

c语言用四种方式求解成绩之中最高分和最低分的差值

文章目录 一&#xff0c;题目二&#xff0c;方法1&#xff0c;方法一2&#xff0c;方法二3&#xff0c;方法三4&#xff0c;方法四 三&#xff0c;示例结果 一&#xff0c;题目 最高分最低分之差 输入n个成绩&#xff0c;换行输出n个成绩中最高分数和最低分数的差 输入 : 两行…

Docker 概念介绍

1、Docker 简介 Docker一个快速交付应用、运行应用的技术: 可以将程序及其依赖、运行环境一起打包为一个镜像&#xff0c;可以迁移到任意Linux操作系统运行时利用沙箱机制形成隔离容器&#xff0c;各个应用互不干扰启动、移除都可以通过一行命令完成&#xff0c;方便快捷 Doc…

Django Cookie和Session使用(十一)

一、Cookie Cookie具体指一小段信息&#xff0c;它是服务器发送出来存储在浏览器上的一组键值对&#xff0c;下次访问服务器时浏览器会自动携带这些键值对&#xff0c;以便服务器提取有用信息。 Cookie的特性 1、服务器让浏览器进行设置的 2、保存在浏览器本地&#xff0c;…