免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
网游逆向分析与插件开发系列之前的内容:
单击游戏逆向:
码云地址(master分支):SRO_Ex: 网游逆向分析与插件开发https://gitee.com/dye_your_fingers/sro_-ex.git
码云版本号:86e72e3570b896d23bc5701aa0b3d7474c8b1a63
代码下载地址,在 SRO_EX 目录下,文件名为:SRO_Ex-逆向分析隐藏壳代码解析不正常的BUG.zip
提取码:q9n5
--来自百度网盘超级会员V4的分享
HOOK引擎,文件名为:黑兔sdk.zip
提取码:78h8
--来自百度网盘超级会员V4的分享
以 网游逆向分析与插件开发-代码保护壳的优化-游戏扩展插件实现对隐藏壳代码的支持 它的代码为基础进行修改
PEProtecter.cpp文件的修改,之前游戏会卡主的原因是,我们一共修改了两个函数,但是代码的序号没有自增,导致第二个不隐藏的代码调用了第一个函数,修改了 PushCode函数
#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); // 写入客户端数据
// SetFilePointer(hFile, 0, 0,FILE_END);
WriteFile(hFile, _dataHeadBegin, (unsigned)_dataHead - (unsigned)_dataHeadBegin, &dRead, NULL);
// SetFilePointer(hFile, 0, 0, FILE_END);
WriteFile(hFile, _dataEntryBegin, (unsigned)_dataEntry - (unsigned)_dataEntryBegin, &dRead, NULL);
CloseHandle(hFile);
_dataHead = _dataHeadBegin;
_dataEntry = _dataEntryBegin;
return true;
}