这是一个函数的虚函数,这个ecx和发送数据&接收数据都有很大关系,这里截取主要就是要拿到ECX,其实更好的方法就是去找它的基址,但基址不好分析,有一大堆函数指针和虚函数,还和Lua有交互,找基址就套浪费时间了。
edx就是这里虚表的地址
可以直接在虚函数这做一个HOOK,通过改虚表来直接调 直接改这 10617c90的数据就能改变eax结果,就可以跳到任何想去的地方
这个类原型名字就叫 GameWinSock ::Connect(char* ip, int port) 很容易看出 返回值是bool
vtable[0x34 / 4] = (unsigned)&GameWinSock::OnConnect;
这句在编译器里是不能直接强转
如果是个普通函数就可以这么直接干了,但这个是类的成员函数。
先试试用类的函数指针能不能得到它来赋值
proc _p = &GameWinSock::OnConnect;
vtable[0x34 / 4] = (unsigned)p;
这样也不行。
只好来个骚操作了:
union
{
unsigned val;
bool(GameWinSock::* _proc)(char*, unsigned);
}vproc;
vproc._proc=&GameWinSock::OnConnect;
vtable[0x34 / 4] = vproc.val;
用联合体直接安排他
修改虚表要注意访问权限
用 VirtualProtect
截取成功
#pragma once
class GameWinSock
{
typedef bool(GameWinSock::* PROC)(char* ip, unsigned port);
public:
static PROC _Connect;
bool OnConnect(char* ip, unsigned port);
};
#include "pch.h"
#include "GameWinSock.h"
#include"extern_all.h"
GameWinSock::PROC GameWinSock::_Connect{};
bool GameWinSock::OnConnect(char* ip, unsigned port)
{
WinSock = this;
MessageBoxA(0, ip, ip, MB_OK);
return (this->*_Connect)(ip,port);
}
#include "pch.h"
#include "GameProc.h"
#include"extern_all.h"
bool _OnConnect(HOOKREFS2)//回调
{
//虚函数表做HOOK
//截取ECX -》 winsock指针
unsigned* vtable =(unsigned*) _EDX;
//vtable[0x34/4]= (unsigned)&GameWinSock::OnConnect;
union
{
unsigned val;
bool(GameWinSock::* _proc)(char*, unsigned);
}vproc;
vproc._proc=&GameWinSock::OnConnect;
InitClassProc(&GameWinSock::_Connect, vtable[0x34 / 4]);
DWORD oldPro,backPro;
VirtualProtect(vtable, 0x100, PAGE_EXECUTE_READWRITE, &oldPro);//修改访问权限
vtable[0x34 / 4] = vproc.val;
VirtualProtect(vtable, 0x100, oldPro,&backPro);//恢复访问权限
return true;
}
GameProc::GameProc()
{
//类的构造
hooker = new htd::hook::htdHook2();
Init();
InitInterface();
}
void GameProc::LoadBase()
{
LoadLibraryA("fxnet2.dll");
}
void GameProc::Init()
{
}
void GameProc::InitInterface()
{
LoadBase();
//10617046
hooker->SetHook((LPVOID)0x10617046, 0x1, _OnConnect);
}
发送数据的函数也是通过ecx,依然可以通过虚函数表做。
进去看看,很容易能看出也是一个bool的返回值
原型:bool GameWinSock::Send(char* buff, int len);
直接还原函数 看看数据包:试着调用
char buf[] = {
0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x02,0x01,0x00,0x00,0x00,0x02,0x01,0x00,0x00,
0x00,0x07,0x0C,0x00,0x00,0x00,0x31,0x00,0x31,0x00,0x31,0x00,0x31,0x00,0x31,0x00,
0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
WinSock->OnSend(buf, sizeof(buf));
成功通过封包来发送消息
VirtualProtect(vtable, 0x100, PAGE_EXECUTE_READWRITE, &oldPro);//修改访问权限
vproc._proc = &GameWinSock::OnConnect;
InitClassProc(&GameWinSock::_Connect, vtable[0x34 / 4]);
vtable[0x34 / 4] = vproc.val;
vproc._proc = &GameWinSock::OnSend;
InitClassProc(&GameWinSock::_OnSend, vtable[0x3C / 4]);
vtable[0x3C / 4] = vproc.val;
VirtualProtect(vtable, 0x100, oldPro,&backPro);//恢复访问权限
GameWinSock::PROC GameWinSock::_OnSend{};
bool GameWinSock::OnSend(char* buf, unsigned len)
{
/*
监控
*/
return (this->*_OnSend)(buf,len);
}
实现其实和Connect没啥区别。
接收数据的函数就没办法用虚表来搞了,因为优化/封装的缘故,用的eax来代替了ecx,就要想别的办法
就要用汇编去传eax或者搞个函数封装一下,不过又涉及到两个参数的传递,可能会把eax用掉
调用的时候就尴尬,用eax做了this指针 用汇编倒是最简单的
也很明显 返回值是个bool
在这个点做HOOK 到时候就能截取到 我们自己模拟的消息了
截一个说话的数据
直接试试
char buf[] = {
0x1E,0x06,0x00,0x06,0x05,0x00,0x00,0x00,0x63,0x68,0x61,0x74,0x00,0x02,0x01,0x00,
0x00,0x00,0x07,0x08,0x00,0x00,0x00,0x1F,0x77,0x1F,0x77,0x31,0x00,0x00,0x00,0x07,
0x6E,0x00,0x00,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,
0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,
0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,
0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,
0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,
0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,
0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,0x33,0x00,
0x00,0x00,0x02,0x01,0x00,0x00,0x00,0x02,0x01,0x00,0x00,0x00
};
WinSock->OnRecv(buf, sizeof(buf));
成功
bool _OnRecv(HOOKREFS2)//回调
{
unsigned* _esp = (unsigned*)_ESP;
_EAX = WinSock->RecvPoint;
return WinSock->OnRecving((char*)_esp[1], _esp[2]);
}
hooker->SetHook((LPVOID)0x10618480, 0x1, _OnRecv,(LPVOID)0x10618502);
InitClassProc(&GameWinSock::_OnRecv, 0x10618480);
bool GameWinSock::OnRecving(char* buf, unsigned len)
{
return true;
}
bool GameWinSock::OnRecv(char* buf, unsigned len)
{
return (this->*_OnRecv)(buf,len);
}
这样就可以了 就是浪费了个ecx..
这个游戏也存在很多问题,比如他用明文字符串直接来打日志,直接就被我们逆向分析的给直接看到了,应该直接用内部编码来规范
而且数据发送阶段 未对数据进行加密,不能有效对抗中间人攻击,别抓个包就寄了,这个游戏直接截取send函数就直接能看到用户的账号密码,基本的加密还是必要的就算只是随便异或了下
关键代码也可以用VM虚拟机加密