46.网游逆向分析与插件开发-代码保护壳的优化-对壳数据进行加密与解密

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!

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

码云版本号:80d8703b18c38ce4aefb2ff9a5c8254747b59833

代码下载地址,在 SRO_EX 目录下,文件名为:SRO_Ex-逆向分析隐藏壳代码解析不正常的BUG.zip

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

提取码:q9n5

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

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

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

提取码:78h8

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

以 https://blog.csdn.net/qq_36301061/article/details/135093433 它的代码为基础进行修改

由于游戏代码原本的代码我们给放到了游戏exe文件的最后,以防止我们的dll文件被剥离做的壳,然后为了避免被人一眼看出游戏原本的代码被我们放到了最后,所以对这段代码进行加密(^运算符),如果把代码加密了别人就看不出游戏exe文件最后的数据是代码了,如果我们把游戏原本的代码放到其它数据库或者其它位置,当使用是通过网络下载的方式就不需要对代码加密了。壳加密解密其实没必要,壳重要的是在于使用方式。

PEProtecter.cpp文件的修改,修改了 Save函数

#include "pch.h"
#include "PEProtecter.h"

bool PEProtecter::LoadFile(wchar_t* _file)
{
    auto hFile = CreateFile(_file, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    
    if (hFile == INVALID_HANDLE_VALUE) {
        return false;
    }

    DWORD dRead;
   filelen = GetFileSize(hFile, &dRead);
    
    if (filelen < 1)return false;
    if (filelen > _datal) {
        if (_data)delete[]_data;
        _data = new char[filelen];
        _datal = filelen;
    }
    if (ReadFile(hFile, _data, filelen, &dRead, NULL)) {

        PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)_data;
        PIMAGE_NT_HEADERS32 ntHeader32 = (PIMAGE_NT_HEADERS32)(_data + dosHeader->e_lfanew);
        SecCount = ntHeader32->FileHeader.NumberOfSections;
        SecArrys = (PIMAGE_SECTION_HEADER)(sizeof(IMAGE_NT_HEADERS32) + (unsigned)ntHeader32);
        Base = ntHeader32->OptionalHeader.ImageBase;
        CloseHandle(hFile);
        if (filelen > _dataEntryl) {
            if (_dataEntry)delete[]_dataEntry;
            _dataEntry = new char[filelen];
            _dataEntryBegin = _dataEntry;
            _dataEntryl = filelen;
        }
        return true;
      
    }  

    delete[] _data;
    _data = nullptr;
    filelen = 0;

    return false;
}

unsigned PEProtecter::VATofoa(unsigned va, unsigned _base)
{
    DWORD uBase;
    _base == 0 ? uBase = Base : uBase = _base;
    DWORD rva = va - uBase;

    for (int i = 0; i < SecCount; i++) {
        IMAGE_SECTION_HEADER tempSecArr = SecArrys[i];
        if ((rva > tempSecArr.VirtualAddress)&&(rva < tempSecArr.VirtualAddress + tempSecArr.SizeOfRawData)) {
            DWORD offset = rva - tempSecArr.VirtualAddress;
            return offset + tempSecArr.PointerToRawData;
        }
    }

    return 0;
}

char* PEProtecter::ReadDataByVa(unsigned va, unsigned _base)
{
    unsigned index = VATofoa(va, _base);

    return _data + index;
}

PEProtecter::~PEProtecter()
{
    if (_data) {
        delete[] _data;
        _data = 0;
    }

    if (_dataEntryBegin) {
        delete[]_dataEntryBegin;
        _dataEntryBegin = 0;
    }

    if (_dataHeadBegin) {
        delete[]_dataHeadBegin;
        _dataHeadBegin = 0;
    }

}

CString PEProtecter::DAsm(unsigned va, unsigned len, unsigned _base)
{
    CStringA _output;
    CStringA _tmp;
    char* buffer = ReadDataByVa(va, _base);
    DISASM disasm;
    memset(&disasm, 0, sizeof(DISASM));
    disasm.EIP = (UIntPtr)buffer;
    disasm.VirtualAddr = (UIntPtr)va; // 根据这个值把 jmp、call这种跳转指令的地址重新计算,如果它的值是0表示不重新计算
    disasm.Archi = IA32;// 指令集类型,1表示32位、0表示64位,AMD64
    disasm.Options = MasmSyntax;// 汇编语法、指令形式,如masm、nasm
    
    int _len;

    while (!disasm.Error)
    {
        disasm.SecurityBlock = (UIntPtr)buffer + len - disasm.EIP;// 设置停止位置
        if (disasm.SecurityBlock <= 0) {
            break;
        }
        _len = Disasm(&disasm); // 反汇编方法,返回值是当前指令的长度
        switch (disasm.Error)
        {
            case OUT_OF_BLOCK:break;// 指令不完整,比如还有两个字节可以解读,但是这两个字节不是一个完整的指令
            case UNKNOWN_OPCODE: { // 有些指令解读不出来
                _tmp.Format("0x%.08X => ????????\r\n", (unsigned)disasm.VirtualAddr);
                _output += _tmp;

                disasm.EIP += 1;
                disasm.VirtualAddr += 1;

                break;
            }
            default:
                _tmp.Format("0x%.08X => [%s]\r\n", (unsigned)disasm.VirtualAddr, &disasm.CompleteInstr);
                _output += _tmp;
                disasm.EIP += _len;
                disasm.VirtualAddr += _len;
        }
    }
    CString result;
    result = _output;
    return result;
}

void PEProtecter::Init(unsigned count)
{
    CodeCount = count;
    unsigned _size = 4 + count * sizeof(CODEContext);
    if (_size > _dataHeadl) {
        if (_dataHead)delete[]_dataHead;
        _dataHead = new char[_size];
        _dataHeadBegin = _dataHead;
        _dataHeadl = _size;
    }
    unsigned* _count = (unsigned*)_dataHead;
    _count[0] = count;
    _dataHead += sizeof(_count);

}

void PEProtecter::PushCode(unsigned va, unsigned len, unsigned _base, bool hide)
{
    char* buffer = ReadDataByVa(va, _base); // 硬盘中的代码位置(FOA)
    DISASM disasm;
    memset(&disasm, 0, sizeof(DISASM));
    disasm.EIP = (UIntPtr)buffer;
    disasm.VirtualAddr = (UIntPtr)va; // 根据这个值把 jmp、call这种跳转指令的地址重新计算,如果它的值是0表示不重新计算
    disasm.Archi = IA32;// 指令集类型,1表示32位、0表示64位,AMD64
    disasm.Options = MasmSyntax;// 汇编语法、指令形式,如masm、nasm


    int _len;
    Int32 opcode;

    PCODEContext _pcontext = (PCODEContext)_dataHead;
    _pcontext->start = va;
    _pcontext->r_couent = 0;
    _pcontext->len = len;
    _pcontext->hide = hide;
    while (!disasm.Error)
    {
        disasm.SecurityBlock = (UIntPtr)buffer + len - disasm.EIP;// 设置停止位置
        if (disasm.SecurityBlock <= 0) {
            break;
        }
        _len = Disasm(&disasm); // 反汇编方法,返回值是当前指令的长度
        switch (disasm.Error)
        {
        case OUT_OF_BLOCK:break;// 指令不完整,比如还有两个字节可以解读,但是这两个字节不是一个完整的指令
        case UNKNOWN_OPCODE: { // 有些指令解读不出来
            /**
                只要执行到这里就说明有不可解读的指令
                可以进行 停止 或 报错
            */
            disasm.EIP += 1;
            disasm.VirtualAddr += 1;
            break;
        }
        default:
            opcode = disasm.Instruction.Opcode;
            if (opcode == 0xE8 || (opcode == 0xE9)) {
                _pcontext->r_couent++;
                unsigned short offset = (unsigned)disasm.EIP - (unsigned)buffer;
                unsigned* e8 = (unsigned*)(disasm.EIP + 1);
                unsigned callAddr = va + (unsigned)disasm.EIP - (unsigned)buffer + e8[0]+5;
                e8[0] = callAddr;
                unsigned short* _offset = (unsigned short*)_dataEntry;
                _offset[0] = offset;
                _dataEntry = _dataEntry + sizeof(offset);
            }
          
            disasm.EIP += _len;
            disasm.VirtualAddr += _len;
        }
    }

    memcpy( _dataEntry, buffer, len); // 复制客户端原本代码到我们的加密空间,这个空间可以上服务器
    _dataEntry += len; // 加密空间递增指向下一块空间
    memset(buffer, 0xCC, len); // 删除客户端代码
    if (!hide) {
        memcpy(buffer, _AsmCode, 11); // 给客户端写入跳转原本客户段代码,原本客户端的代码被我们获取了目的是让我们的插件与客户端形成耦合,从而防止我们的插件被移除
    }
    char* _pushIndex = _AsmCode + 3; // 修改序号
    _pushIndex[0]++; // 修改序号
    _dataHead += sizeof(CODEContext);
}

bool PEProtecter::Save(wchar_t* _file)
{
    unsigned* fcount = (unsigned*)_dataEntry;
    fcount[0] = filelen;
    _dataEntry += sizeof(fcount);
    auto hFile = CreateFile(_file, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        return false;
    }
    DWORD dRead;
    WriteFile(hFile, _data, filelen, &dRead, NULL); // 写入客户端数据
    unsigned _dataHeadlenth = (unsigned)_dataHead - (unsigned)_dataHeadBegin;
    unsigned _dataEntrylenth = (unsigned)_dataEntry - (unsigned)_dataEntryBegin;
    
    // 加密数据
    for (int i = 0; i < _dataHeadlenth; i++)
    {
        _dataHeadBegin[i] = _dataHeadBegin[i] ^ 0x23;
    }

    for (int i = 0; i < _dataEntrylenth - 4; i++)
    {
        _dataEntryBegin[i] = _dataEntryBegin[i] ^ 0x23;

    }
    // SetFilePointer(hFile, 0, 0,FILE_END);
    WriteFile(hFile, _dataHeadBegin, _dataHeadlenth, &dRead, NULL);
    // SetFilePointer(hFile, 0, 0, FILE_END);
    WriteFile(hFile, _dataEntryBegin, _dataEntrylenth, &dRead, NULL);
    CloseHandle(hFile);

    _dataHead = _dataHeadBegin;
    _dataEntry = _dataEntryBegin;
    return true;
}

 GameProtect.cpp文件的修改,修改了 InitEntryCode函数

#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");
	_protect = this;
	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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值