|
feng包辅zhu“初体验”(二)
(温馨提示请先观看:封 包辅zhu“初体验”(一))
- bool F发送封包(byte *p, DWORD ndIndex,char *szStr)
- {
- byte *data = p;
- DWORD nd包长 = ndIndex;
- DWORD nd加密地址 = (DWORD)data + 0x2;
- DWORD nd加密长度 = nd包长 - 0x2;
- DWORD nd密钥地址 = NULL;
- __try
- {
- // [[[[[0x0F84BA4]]+0x4]+0x0C+0x8]]+0x54 密钥公 0019FB90 00000B4C |Socket = 0xB4C
- nd密钥地址 = *(DWORD*)0x0F84BA4;
- nd密钥地址 = *(DWORD*)nd密钥地址;
- nd密钥地址 = *(DWORD*)(nd密钥地址 + 0x4);
- nd密钥地址 = *(DWORD*)(nd密钥地址 + 0x0C + 0x8);
- nd密钥地址 = *(DWORD*)nd密钥地址 + 0x54;
- F加密call(nd密钥地址, nd加密长度, nd加密地址, nd加密地址);
- HWND hWnd = (HWND)FindWindowA("Lapis Network Class", NULL);
- DWORD ndData = GetWindowLongW(hWnd, -21);
- DWORD Socket = *(DWORD*)(ndData + 0x38);
- send(Socket, (const char*)data, nd包长, 0);
- }
- __except (1)
- {
- F输出调试信息("幻想神域 %s\n\r", szStr);
- return false;
- }
- return true;
- }
复制代码
HOOK拦截输出明文包内容:
(一)理论
完事具备,只欠东风。我们现在已经封装好了自己明文发包函数,就差内容了。那我们怎么才能知道,什么功能要传什么样的封包呢?比如吃药...难道直接传个“吃药”?
呃...其实说来也简单,我们不是找到明文封包了么,吃药断下我们把内容复制下来,多复制一些案例,细细分析总能猜出来。但是不同的动作会产生不同的封包,比如我们要实习一个自动打怪挂机、跑主线等,那工作量可就大了,如果都OD下断点复制,未免太麻烦了,所以我们需要一个更好的方式——HOOK。
HOOK简单来说其实就是想方设法改变程序原来的执行流程,我们这里的目标很简单,让cpu在运行加密函数之前,先运行我们的函数 我们函数里面输出明文包的内容 然后跳转回原来的地方让程序继续正常执行。
具体操作也很简单,如图:
其实所谓的代码和指令也数字,图上可知最左边是代码在内存中的地址,中间是代码在内存中真正的模样(数字),右边是OD帮我们把数字翻译成人能看懂的汇编代码。
那么既然我们知道了代码的本质(数值)和它的地址,所以我们可以通过地址修改数值来修改代码,从而改变代码流程。
比如把红框中的2句汇编代码修改成:0xE8XXXXXXXX —— E8XXXXXXXX 对应Call指令上图也可以看到,后面的数值是算出来的 等于 要跳转地址 - (当前地址+0x5)。要跳转的地址肯定是我们的函数啊,当前地址上图就有 舒服~
当然我们的函数必须是一个裸函数,而且开头要使用 pushad 指令来保存现场环境,干完坏事后 使用 popad指令还原现场,最后一定要记得一字不差的写上被我们破坏的那2条指令,在使用 ret 指令跳转回去 不然程序肯定崩溃。(篇幅有限这里只是讲个大概,有兴趣具体学习百度一搜就有)
这样在我们的裸函数里面,pushad popad中间就可以写代码输出明文的封包内容。
(二)参考代码
- //HOOK
- void HXSYDialog::OnBnClickedButton3()
- {
- // TODO: 在此添加控件通知处理程序代码
- /*00B92C82 8B46 08 mov eax, dword ptr ds : [esi + 0x8]
- 00B92C85 2BC1 sub eax, ecx
- 00B92C87 83C0 FE add eax, -0x2*/
- DWORD ndHOOKAddress = 0x00B92C82;
- DWORD ndHOOK函数指针 = (DWORD)FHOOK明文发包;
- DWORD ndHOOK跳转值 = ndHOOK函数指针 - ndHOOKAddress - 5;
- DWORD old = 0;
- //改变内存页属性
- VirtualProtect((PVOID)ndHOOKAddress, 0x30, PAGE_EXECUTE_READWRITE, &old);
- //修改代码
- *(byte*)ndHOOKAddress = 0xE8; //Call
- *(DWORD*)(ndHOOKAddress + 1) = ndHOOK跳转值;
- //还原内存页属性
- VirtualProtect((PVOID)ndHOOKAddress, 0x30, old, &old);
- }
- //还原HOOK,一样的道理修改回去
- void HXSYDialog::OnBnClickedButton4()
- {
- // TODO: 在此添加控件通知处理程序代码
- /*00B92C82 8B46 08 mov eax, dword ptr ds : [esi + 0x8]
- 00B92C85 2BC1 sub eax, ecx
- 00B92C87 83C0 FE add eax, -0x2*/
- DWORD ndHOOKAddress = 0x00B92C82;
- DWORD old = 0;
- VirtualProtect((PVOID)ndHOOKAddress, 0x30, PAGE_EXECUTE_READWRITE, &old);
- *(byte*)ndHOOKAddress = 0x8B;
- *(DWORD*)(ndHOOKAddress + 1) = 0xC12B0846;
- VirtualProtect((PVOID)ndHOOKAddress, 0x30, old, &old);
- }
- //我们的函数
- DWORD g_nd包长 = 0;
- DWORD g_nd包Address = 0;
- DWORD g_ndPid = NULL;
- HANDLE g_hProcess = 0;
- byte *g_byP = nullptr;
- char g_szObj[0x1000];
- char g_szStr[0x1000];
- void __declspec(naked) FHOOK明文发包()
- {
- __asm
- {
- //保存寄存器 提升堆栈
- pushad
- //得到包地址 包长
- mov eax, dword ptr[esi + 4]
- mov g_nd包Address, eax
- mov ecx, dword ptr[esi + 8]
- sub ecx, eax
- mov g_nd包长, ecx
- }
- //提升权限
- F提升权限(TRUE);
- //获得进程ID
- GetWindowThreadProcessId(F获取游戏主窗口句柄(), &g_ndPid);//获得进程ID
- //打开进程
- g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, g_ndPid);//打开进程
- g_byP = new byte[g_nd包长];
- //读出包内容
- ReadProcessMemory(g_hProcess, (LPCVOID)g_nd包Address, g_byP, g_nd包长, 0);// p 封包字节集
- *(WORD*)g_byP = g_nd包长 - 2;
- for (int i = 0; i < (int)g_nd包长; i++)
- {
- sprintf_s(g_szObj, "%02X", g_byP[i]);
- strcat_s(g_szStr, g_szObj);
- }
- //自己封装 支持多线程的printf()
- F输出调试信息("幻想神域:明文发包 包长:%x 包内容:%s\r\n", g_nd包长, g_szStr);
- //清空缓冲区
- sprintf_s(g_szStr, "%s", "");
- delete[] g_byP;
- __asm
- {
- popad
- //还原两句 被HOOK的游戏代码
- mov eax, dword ptr ds : [esi + 0x8]
- sub eax, ecx
- retn
- }
- }
- //自己封装 支持多线程的printf()
- void F输出调试信息(char * pszFormat, ...)
- {
- #ifdef _DEBUG
- char szbufFormat[0x1000];
- char szbufFormat_Game[0x1100] = "";
- va_list argList;
- va_start(argList, pszFormat);
- vsprintf_s(szbufFormat, pszFormat, argList);
- strcat_s(szbufFormat_Game, szbufFormat);
- OutputDebugStringA(szbufFormat_Game);
- va_end(argList);
- #endif
- }
复制代码
(三)实战
我们上面的HOOK代码会把封包内容输出出来,但是由于是DLL注入形式的代码,所以我们并没有控制台窗口,故不能直接看到输出。这时候我们就需要借助一个输出捕捉工具了——Dbgview:
只需要输入关键字,就可以捕捉所有进程带关键子的输出,非常好用。
我们试着嗑下如图位置的药品,看看会游戏会发送什么样的封包。
包长:16 包内容:14004F00000000000100000000000000000000000000
包长:16 包内容:14004F00000000000A00000000000000000000000000
很明显,”1400”是封包2字节后的有效长度 我们之前已经分析过了。”4F”应该是代表吃药这个动作,那么”1” 和 “A”根据药品的位置来看 应该就是药品在背包表格中的下标,其他都为0我们可以暂时无视它们。
基本摸清了吃药封包的结构,我们可以尝试封装一个通过药品下标吃药的函数了:
void F吃药Call(int i)
{
byte Data[0x16] = { 0x14,00,0x4F,00,00,00,00,0x00,00,00,00,00,00,00,00,00,00,00,00,00,00,00};
(WORD)(Data + 0x8) = i;
F发送封包(Data, 0x16, "");
}
Nice~! 封包吃药成功~!(使用药水这几个字可不是我P上去的啊,游戏嗑药就这特性)
麻雀虽小,五脏俱全 ; 经历千辛万苦我们终于达成了目标!虽然只有一个简单的嗑药功能,但是这却是一个货真价实的封包辅Z!!!
总结:
封包辅Z也并不多神秘和神奇,只是省去了中间的过程,直接联系服务器。
优点: 不走游戏代码,相当于避开了所有的本地检测,极其稳定。
缺点: 需要做很多前期工作,实现麻烦、分析封包费时费力。
屈尊调用游戏功能函数“内存辅Z”研究速度快,认真逆向分析封包内容“封包辅Z”快乐而稳定!
|
|