码云地址(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能取很多内核中的数据,使用详情看下方代码
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;
}