免费精品资源-爱收集资源网-小刀娱乐

【游戏封包】封包辅Z“初体验

灵动 自学技术
前些日子的几篇笔记,分享了如何快乐Ctrl+F9定位游戏功能Call,和处理游戏在这方面设置的障碍,有没有学废了呢?
 
 
 
 
 
 
 
 
 
 
假设...啊 我们假设 张三学废了,然后....
然后....
张三就开始快乐的测试功能Call了,嗯 正当张三同学最快乐的时候,客户端弹出了一个小窗口...第三方检测了..张三同学被封号了。
PS:初次了解封包,最好先看完之前的三篇笔记)
 
上面的故事真是闻者伤心听者落泪啊,不管是重写发包函数还是线程发包,虽然技法巧妙 但是只要清楚原理 细细分析,总会有漏网之鱼。
 
所以龙门前不会只有这些困难,后面也是布防甚多.. 比如,功能Call的检测有什么变量检测 堆栈检测,恐怕是层层套娃 MV众多,解决起来更是烦躁无比啊!(欲扬先抑,先夸张一下)
那我们学废之前的技巧有啥子用罗?和上面那些花里胡哨的东西 中路对线?
 
当然不是,所谓黄河之水宜疏不宜堵,之前的三篇笔记都是铺垫 我们一开始的根本目的完全不是为了快乐的Ctrl+F9。
在揭晓谜底之前我们来观察一个珍贵无比的示例:
捕捉用户键盘消息——》技能相关的几个函数——》告诉服务器我要释放某某技能
 
一款网络游戏角色释放一个技能,大概就是上面这种流程。那比如我们找到了游戏的技能Call 调用它释放技能,会是什么样子?
 
会变成这样:
技能相关的几个函数——》告诉服务器我要释放某某技能
 
同学们,有没有看出来什么?哦,张三同学看出来了,同时它还有一个大胆的想法。
 
其实归根结底就是告诉服务器我干了啥,那我们能不能中间砍一刀 直接告诉服务我要释放啥啥技能,然后达到释放技能的目的?
 
当然可以,游戏实际上就是这样释放技能嘛,我们自然也可以这样做,以至于80%的功能 我们基本都能直接自己发送封包实现它。
 
哇哦!那这样做有什么具体的好处?
 
通过自己发包,试探变态功能这里就不讲了,我想辅助通常大家有一个很关心的点(小声:嘿~~兄弟gua稳吗?)
 
如果把游戏检测比作地雷,那么游戏代码游戏领空就是雷区,想调用游戏功能call免不了遇到有埋雷的情况,而就算是排雷老手也免不了会踩上几颗。所谓究极的排雷方法,就是不走雷区到目的地,你那有雷我绕着走不就是了么。
 
这里也是同样的道理,我们走自己的代码通过send()系统发包函数发送封包实现功能,这样就避开了游戏领空的地雷,所以只要是完美的封包辅助90%稳稳的很安心。
 
既然说到了“完美”二字,当然就有不那么完美的。毕竟自己发包实习功能,可不是说说就完成了。
 
让我们来观察一个珍贵无比的示例:
组包--》加密--》发包
 
游戏要发送一个封包,通常都要走这么一个流程。组包:把你做的坏事打包成小报告;加密:这是你和服务器的小秘密不能让别人知道;发包:把小报告邮寄给服务器。
 
明显的我们要想完美的达到目的主要有三大难点:
1. 分析封包
2. 寻找明文封包
3. 寻找加密函数
 
分析封包:客户端告诉服务端我释放了某某技能,总不可能写得汉字吧,所以我们需要分析封包,知道释放技能应该以什么形式描述。
找明文包:系统发包函数上下断确实可以获得封包内容,但是多半是加密的,而且加密的密钥可能随时会发生变化,加密后的内容基本就是一团乱码无从下手没法分析,所以我们得找到内容还未被加密的地方。
加密函数:我们找到明文包了,也分析了放什么技能该发什么包。但是服务器可是要被加密后的封包啊,直接发送一个明文过去 服务器不认事小,第三方就不好了。我们要学客户端,把明文包加密一下,当然 得按游戏的加密方式,那么我们就得找到游戏的加密函数。
 
 
找明文包
(一)理论
 
明文包的寻找是我们的首要任务,以此为跳板才能分析封包 定位 加密函数,如何寻找?不如再来欣赏一下这个珍贵无比的示例:
组包--》加密--》发包
 
我们可以观察到,从组包完成到加密的这段过程,封包内容是明文的未加密的。通过面对对象的编程原则,我们可以猜测这几个步骤在代码中的表现形式,就是几个对应的函数。
 
或者说组包完成后,封包数据会通过函数一层一层传递,函数怎么传递数据到另外一个函数?没错,当然是通过参数。那么我们是不是能够在发包处下断,使用传统手艺Ctrl+F9逐层返回所有经过的Call(函数),通过分析他们的参数内容,来找到明文的封包内容?
正确!不过在准备撸起袖子加油干之前,我们还有一个问题需要解决,我们怎么分辨出明文封包内容?要知道这些东西可不会写着I is 明文包,它们在内存之中的形式就是一串数字,而堆栈中所有函数的任何参数也都是一堆数字,怎么办?
 
想象一下你在茫茫人海中,光凭一个模糊的照片去寻找一个人,这该有多么困难~但是如果那个人的长相特立独行...比如头上长个角?再顶两只猫耳朵..是不是就简单多了?我相信眼尖的你肯定一眼就认出他了。
 
道理是一样的,那封包里面会不会有这种引起围观的奇异种?我想大多数游戏都是有的,也就是喊话功能 或者 说聊天功能,你想想看在游戏中你和队友聊天,客户端是不是一定需要发送一个装有聊天内容的封包给服务器,这样服务器才能更新到其他玩家的客户端上。我想大家应该都知道吧,实际上电脑只能储存数字...而字符是通过一张表把一些数字定义为字符,如图:
 
通过表图我们可以看到,字符 1 的对应数字是49,以十六进制表示 0x31。那么我们喊话一串“11111111111111”断下,它在封包内容里面就是一串 31 31 31 31 31 31....
 
Nice~ 纸上谈兵不如真刀真枪的干一架,请出我们的老伙计私服版的幻想神域2 带来一段珍贵无比的简单实例。

 

(二)实战
 
先在上面笔记说到的跳出线程发包代码段下断,可以当发包下断可以断到功能函数,当然我们并是要Ctrl+F9去找Call,而是返回上一层寻找未加密的封包内容。
 
喊话“1111111111”断下.
 

管理员提示:
官方微信公众号:XuePoJie
本站备用域名 SharkHeng.com(当前该网址页面记录着一些信息)
RMB付费求助 https://www.xuepojie.com/forum-87-1.html

如遇“原创视频教程”下载链接失效(软件区除外),可以到“补链专区”发帖申请补链 https://www.xuepojie.com/forum-54-1.html
如果你可以帮助其他坛友补链,版主会为你增加HB与THX积分^^

如遇主题内容存在违规行为,请点击主题右下角“举报”,版主将为你的正义行为增加HB。例如:广告,推广,QQ,QQ群,病毒,木马等。


主题隐藏部分:
Ctrl+F9返回上一层调用。
 
 
这个call看起来好像只有一个参数,具体还是需要观察堆栈变化才能确定。Call指令下断,断下后F8单步步过 观察堆栈esp增加了多少。
增加了0x4 看来确实只有一个参数,Call指令下断 喊话“11111”断下,分析一下这个参数。
 
好大的值,像这种值一般是一个地址,这说明这个参数实际上是一个结构体之类的东西,我们数据窗口中查看分析一下。
这个结构体里面的成员也像是地址,我们挨个进去看一下,第一个:
 
 
...显然是虚表函数之类的,反正没有31 31 31  不管他...再看看第二个:
 
 
哇,哦~ 一串的31 31 31 31看来我们运气不错只返回了一层我们就找到了明文的封包,如果要确认的话也简单。我们再次再次喊话断下,把这个明文内容修改一下会怎样呢?
放开断点。
我们输入的是1111111111111111”,居然喊出了 “1111155555111111”这就是逆向的魅力,这也就说明我们确实找到了明文封包的位置了。
 
我们不如发散一下思维,如果我们调用这个Call是不是就可以直接发送明文内容实现功能了,而不必再费力寻找和分析加密函数了?
 
的确,从实现功能的角度讲,只要分析透了这个函数是能够实现所有的功能,但是这样还是过了游戏代码 并非“完美”的,我们从学习角度出发接着分析。
 
 
找加密Call:
(一)理论
加密call在哪?想想我们跳出线程发包代码段那,封包内容应该已经被加密了,因为下面的代码再走就是把封包内容复制到全局变量里面,然后被发包线程发送出去了。游戏不可能让发包线程里面有明文内容,不然我们是不是就不用幸苦跳出发包线程了,直接分析明文的封包内容 岂不美哉?
 
然后我们返回一层,在传入参数里面发现了明文包,哪说明加密函数就应该在 上层之下,跳出线程发包代码段之上。哪我们的范围就小太多了,我们可以在函数头部跟踪明文内容,加密call必然会需要这个参数!
 
(二)实战
我们来到函数头部追踪一下明文内容。
在头部发现一个堆栈的值,直接赋值给寄存器。一般来说这种[esp+0xXX]要么是局部变量,要么是函数调用者传入的参数。而这里是函数头部,上面没有对[esp+0x14]初始化的代码,这说明它不是一个局部变量而是一个被传入的参数。
 
原因很简单,因为局部变量的空间都是在堆栈划分的,而堆栈被反复使用里面有非常多的垃圾数据,所有如果局部变量不初始化谁也不知道它的值是啥。
 
当然如果看见[esp+0xXX]而自己没有啥把握的话,最好去堆栈中观察确认一下,这是最稳妥的。
 
那么现在edi是参数首地址了,我们再往下看。[edi+0x4]赋值给了ebp,还记得我们上面寻找明文封包的分析么,这个参数是一个结构体,他的第二个成员就是封包内容首地址。
[edi]是第一个成员,[edi+0x4]是第二个成员,那么现在ebp是明文封包内容首地址了。
 
再往下看,ebp[edi+0x8]做比较,[edi+0x8]是第三个成员,这时候我想到了数组,存放数组首地址的地址+0x4通常是数组的尾地址,edi+0x8edi+0x4  应该是这种情况。那么这个操作就很好理解了,应该是判断数组的长度..或者说里面有没有东西。
 
接着分析,顶住ebp这个封包内容,看有没有那个Call把它作为参数。
最后发现在范围内只有这个call把封包内容当作参数传入了,基本上可以断定它就是加密Call了。
 
很明显它有4push的参数,前两个都是来源[edi+0x4] 至于为什么传2个一模一样的我们不管他,而第3push 往前面翻一翻可以发现来自于 [edi+0x8]-[edi+0x4] 前面说过了这是尾地址减去首地址,应该是封包长度。
 
我相信你也看见了,它先把封包首地址+0x2 封包长度-0x2, 也就是说封包内容前2个字节是不加密的,来看看之前的封包截图。
我想前2个字节应该是代表这个封包前2字节之后的有效长度,之所以不加密应该是游戏服务器可以更好的读取和区分。
 
4push下断一看它是不断变化的,也不难猜 估计是密钥 加密就像是用密钥上锁,服务器拿到之前再用密钥打开。密钥应该是服务器隔一段时间更新下发一次,我们要想获得它得往上回溯得到它的偏移表达式和基地址,从里面读取就没问题了,基本功内容就不写出来了 这里没几个偏移就找到基地址了。
 
摸清楚了函数的参数,我们已经可以直接调用它来加密我们自己组装的明文包了,这样就可以用send()发给服务器了。同样的,这么做也并不“完美”,我们先进加密call里面去看看代码。
 
代码太多就不放老长的图了,假装一下它是那种长图~)
 
我们发现这是一Call到底,里面没有调用任何其他的call,下断F8执行一遍之后也没有发现什么花里胡哨的跳转。那么我们另一条路就好走多了------偷功能! 顾名思义,我们要偷游戏加密call的功能。
 
怎么偷?其实也很简单,C++ 是支持内联汇编的,正好OD工具直接帮我们把汇编显示了出来,直接复制到我们的代码里面,只不过需要稍稍修改一下——JCC指令。
 
OD反汇编显示出来的JCC 跳转指令后面都是直接跟得立即数,内联汇编中并不支持这种写法,所以我们全部改成标签跳转就可以了,没什么技巧和技术 就是需要一点汇编基础和细心耐心。
 
如图:建议写一个裸函数,__asm 内联汇编把汇编复制进去就得勒。
假装...一下..放个N长得图或者代码也没意思..
 
这样就可以调用我们偷出来的函数加密了,由于是直接复制出来的,所以效果和原版一模一样。不过在我们用参数最少最简单的发包函数send()夹带私货之前,我们还需要打通最后一公里,先看看send()函数的定义。
 
2 3 个参数也就是封包内容和长度我们有,第4个参数 常量0 我们不用管他,但是第一个参数需要解决一下,这代表东西要送到那里去。
 
好在三大发包函数第一个参数都是SOCKET套接字描述符,游戏里面使用的WSASend 函数当然是的,所以咱们可以直接Ctrl+GWSASend里面下断点Ctrl+F9到调用处回溯第一个参数。
 
ECX看来就是我们要找的SOCKET了,都帮我们标注出来了,暖暖的很贴心~
随便往上翻一个偏移,来到了这,如图:

 
游戏为了获取SOCKET调用了这个函数,不过OD已经帮我们标注出来了,那我们肯定没必要进Call追数据了,OD认识的肯定是系统的API函数,我们直接上网查查这个函数是咋回事,然后学着游戏的样子调用它就ok了。
 
第二个参数游戏直接传入了常量0x15 所以我们不用关心它,第一个参数是一个窗口句柄...我们怎么才能方便的获取它呢?
 
既然它是一个窗口句柄那么可以使用 FindWindow() 系统API函数获得,但是这个函数需要一个窗口类名,或者窗口标题....嗯,这就不得不借用Visual Studio自带的工具Spy++了。
 
Spy++根据窗口句柄的数值查询到窗口的类名或者标题名,而窗口句柄的数值我们可以在游戏调用GetWindowLongW的地方下断复制出来(可惜下次打开游戏会变化,不然也不用这么麻烦了),然后扔到Spy++查询一下就OK了,如图:
 
 
这下就得到类名拉,只需要通过FindWindow()传入这个类名就可以获得我们想要的那个窗口句柄了~!
接下来只需要把我们上面讲的内容有机的结合一下,就可以封装成我们自己的明文发包函数拉~!
 
(三)代码示例
 
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);
                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上去的啊,游戏嗑药就这特性)
 
麻雀虽小,五脏俱全 ; 经历千辛万苦我们终于达成了目标!虽然只有一个简单的嗑药功能,但是这却是一个货真价实的封包辅助!!!
总结:
封包辅助也并不多神秘和神奇,只是省去了中间的过程,直接联系服务器。
 
优点: 不走游戏代码,相当于避开了所有的本地检测,极其稳定。
缺点: 需要做很多前期工作,实现麻烦、分析封包费时费力。
 
屈尊调用游戏功能函数“内存辅助”研究速度快,认真逆向分析封包内容“封包辅助”快乐而稳定!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

 

图片18.png (38.14 KB, 下载次数: 23)

 

图片18.png
转载 https://www.xuepojie.com/thread-47704-1-6.html
标签: 暂无标签
资源分享
评论列表

允许邮件通知

Sitemap