内容参考于:易道云信息技术研究院VIP课
上一个内容:项目需求与需求拆解-CSDN博客
码云地址(ui显示角色数据 分支):https://gitee.com/dye_your_fingers/sro_-ex.git
码云版本号:453dd83d54140d2e1ee65c915ed2cede543f7400
代码下载地址,在 SRO_EX 目录下,文件名为:SRO_Ex-物品使用的逆向分析与C++代码的封装.zip
链接:https://pan.baidu.com/s/1W-JpUcGOWbSJmMdmtMzYZg
提取码:q9n5
--来自百度网盘超级会员V4的分享
HOOK引擎,文件名为:黑兔sdk.zip
链接:https://pan.baidu.com/s/1IB-Zs6hi3yU8LC2f-8hIEw
提取码:78h8
--来自百度网盘超级会员V4的分享
以 82.网游逆向分析与插件开发-背包的获取-装备栏数据与算法的C++还原-CSDN博客 它的代码为基础进行修改
选择从网络的地方去下手,因为网络这个东西是它肯定避不开的环节,这是一个非常好的下手点,网络游戏整个逆向,或者说但凡有统一,就是所有东西都会访问一个函数,这种反而会分析起来难度比其它的难度要低非常多。
然后梳理一下思路,首先使用物品这时发出ui的消息,这个时候它一定会换算成网络消息,然后网络消息会经过一个组合,然后经过一个加密,然后经过一个发送,这就是它的一个大概的情况,这整个流程里面,只有一个最好切入,就是发送这个地方最好切入,使用ui的地方消息太多了,处理起来肯定会很麻烦,然后组合什么的都是算法,这东西没有一个明确的点没有一个明确的特征所以找不着,然后到了发送的地方,就简单了,因为网络的发送就是围绕着send函数和wsasend函数或sendto等函数,所以可以对这几个函数下断点来回溯它整个过程,然后分析过程中有一个要点,就是分析的时候会发现一个问题,发现它用了send函数或wsasend函数,这个时候一路往回找,但是要在哪停呢?比如第一次找去找到跟ui相关的,跟ui相关的会看到跟消息有关的函数,然后接下来再往后,这个时候要随时观察函数的参数,只要在参数里发现可以辨别的东西,那这个东西就基本找到了,所以有几个东西是需要密切关注的,比如物品的指针,或者说数组它的序号等这些信息比较重要,所以要随时观察,什么时候出现了这些数据就很关键,就知道什么时候停了。
然后打开x96dbg下断点,首先是send函数
然后是sendto函数,sendto一般在聊天软件里用的比较多
然后WSASend函数,它可以实现iocp(iocp例子),它是异步的,在新的游戏里用的比较多
然后可以发现WSASend会触发断点,那基本上就可以确认是它了,然后它会不停的断下来,会有干扰
然后游戏不动它也会停下来,原因是,游戏为了保证安全它有心跳包,这个时候最好找到它的长度通过条件断点筛选掉,或者眼疾手快点,然后使用物品,使用一个药物,然后它的id是应该是5912或者5913,然后因为现在以及知道它背包的物品是一个指针的数组,所以如果服务器响应这个东西,就要告诉服务器要用哪个物品,所以应该会给服务器发送一个可以证明是哪一个物品的数据,如果是数组的话使用序号的可能性要大,然后下图红框位置的物品数组下标是22,然后也可能是5912或者5913,所以看到这几个数据要敏感
然后接下来眼疾手快一点使用物品,然后就断下来了
然后断点住后,把断点取消掉,再往上跟否则它会乱
然后一直ctrl+f9,再按f8,大致的过一遍,看看到什么位置就过不去了,然后就会看到在下图红框位置就结束了,这说明在循环里,然后下图红框位置附近有Sleep函数,这个Sleep函数一般跟消息靠的比较近,现在就大概知道它是一个什么情况了,函数很长,然后接着往下看
这么长的函数该怎样处理?首先要分清楚它的一个结构,就是游戏它有各种操作,比如a操作使用物品,b操作走路,c操作心跳包,d操作使用技能,它们都会走到网络的部分发送数据,刚刚经历的一系列函数,假设a是使用物品,那么它有一部分是属于a使用物品的,它的前面还有ui管理或者消息管理的一部分,如果要精确的找到a的过程,才能去分析它,刚刚的一系列操作该怎样排除ui或者send呢?倒着分析的逻辑的话是不需要排除ui的,只需要排除网络的部分,不可能在WSASend函数分析使用物品的逻辑,所以该怎样排除WSASend函数?然后ABCD这四个操作它基本上是共用或者部分共用这个send函数,就是共用网络发送的部分,网络发送的函数它一定是由N个函数构成,也就是说在ABCD所有的操作它到最后都会跑到send函数里,如果现在处在A的操作里面就会只有A操作能触发,就比如走路它就一定不触发A操作,使用使能也不会触发A操作,只有使用物品触发A操作,所以通过这一点就可以很容易的定位到A操作,所以接下来重新回到WSASend函数里,ctrl+f9再按f8每按一次都在上一行代码(调用函数的位置)打断点,然后走路或者执行一个非使用物品的操作,看看是否还会触发断点,如果触发就接着在断点位置使用一次物品,然后继续往上跟(ctrl+f9再按f8这个操作),然后一路这样打断点做非使用物品的操作,最终来到下图位置0x860A43,它并不会被其它操作影响,只会在使用物品的时候断下来
但是这个函数并不怎样理想,它的参数看不懂,ecx的数据也看不懂,这没关系,因为游戏它组织数据的时候一定也是循序渐进的,所以就继续往前找,一定能找到一个能看懂的地方,这个时候才好切入
然后ctrl+f9再按f8来到上一层,关键点 0x74688F
然后它的参数0x85F640函数的入参,它有三个入参,通过0x85F640函数的返回也就是ret位置看出的它是ret c,所以是三个参数,然后通过下图可以看出其中一个参数是索引,使用的物品正好是22
然后使用第一个物品,eax的值是0,所以它是索引,然后0x85F640函数就是使用物品的函数,它是0x1256E3C类里的成员函数,这个0x1256E3C之前已经用C++封装过了,也就是SRO_Control类的成员函数
效果图:可以正常使用物品了
总结:
在盲分析的情况下,可以发现是非常需要依赖参数的,能看懂参数会省很大的劲,可以尝试在0x74688F位置往上跟一层,看看它的入参,看看它的触发机制,还有下图所示的物品栏,是不是也能通过 0x74688F 位置分析到
新加按钮:
GameBase.cpp文件的修改,修改了 Init函数
#include "pch.h"
#include "GameBase.h"
GameBase* _pgamebase;
void GameBase::Init()
{
unsigned* addrRead = (unsigned*)0x1256E3C;
SRO_Res = (PRes)0x1036518;
SRO_Control = (PControl)addrRead[0];
addrRead = (unsigned*)0x1037D3C;
SRO_Player = (PAIM)addrRead[0];
InitClassProc(&Res::_ReadTitle, 0x9A46C0);
InitClassProc(&Res::_ReadItemTitle, 0x9A4640);
InitClassProc(&Control::_NormalNotice, 0x848580);
InitClassProc(&Control::_NetNotice, 0x844E40);
InitClassProc(&Control::_ChatNotice, 0x844E80);
InitClassProc(&Control::_GetPPack, 0x866140);
InitClassProc(&Control::_UseItem, 0x85F640);
InitClassProc(&ITEM::_GetItemRes, 0x995800);
InitClassProc(&Pack::_GetPackPack, 0x7722C0);
InitClassProc(&Pack::_GetEquipPack, 0x772300);
}
void GameBase::InitClassProc(LPVOID proc_addr, unsigned value)
{
unsigned* uWrite = (unsigned*)proc_addr;
uWrite[0] = value;
}
GameBase::GameBase()
{
_pgamebase = this;
// Init();// 初始化机制,完成游戏与我们dll的对接
}
Control.h文件的修改,新加 PROC_D_D_D函数指针类型、_UseItem函数指针变量、UseItem函数声明
#pragma once
#include "SRO_String.h"
#include "Pack.h"
typedef class Control
{
typedef PPack (Control::* PROC)();
typedef void (Control::* PROC_PSROSTR)(PSROSTRING);
typedef void (Control::* PROC_D_PWSTR_D_D)(int, wchar_t*, int, int);
typedef void (Control::* PROC_D_D_D)(int, int, int);
public:
static PROC _GetPPack;
static PROC_PSROSTR _NormalNotice;
static PROC_PSROSTR _NetNotice;
static PROC_D_PWSTR_D_D _ChatNotice;
static PROC_D_D_D _UseItem;
public:
void NormalNotice(PSROSTRING _txt);
void NetNotice(PSROSTRING _txt);
void ChatNotice(wchar_t* _txt, int color=0xFFFFAEC3, int type1=0x3, int type2=0x1);
void UseItem(int index, int p1 = -1, int p2 = -1);
PPack GetPPack();
}*PControl;
Control.cpp文件的修改,新加 UseItem函数、_UseItem变量初始化
#include "pch.h"
#include "Control.h"
Control::PROC Control::_GetPPack{};
Control::PROC_PSROSTR Control::_NormalNotice{};
Control::PROC_PSROSTR Control::_NetNotice{};
Control::PROC_D_PWSTR_D_D Control::_ChatNotice{};
Control::PROC_D_D_D Control::_UseItem{};
void Control::NormalNotice(PSROSTRING _txt)
{
(this->*_NormalNotice)(_txt);
}
void Control::NetNotice(PSROSTRING _txt)
{
(this->*_NetNotice)(_txt);
}
/**
type1 默认0x3
type2 默认0x1
*/
void Control::ChatNotice(wchar_t* _txt, int color, int type1, int type2)
{
(this->*_ChatNotice)(type1, _txt, color, type2);
}
void Control::UseItem(int index, int p1, int p2)
{
(this->*_UseItem)(index, p1, p2);
}
PPack Control::GetPPack()
{
return (this->*_GetPPack)();
}
CUIWnd_1.cpp文件的修改,新加OnBnClickedButton3函数,OnBnClickedButton3函数是Button3按钮的点击事件处理函数
// CUIWnd_1.cpp: 实现文件
//
#include "pch.h"
#include "htdMfcDll.h"
#include "CUIWnd_1.h"
#include "afxdialogex.h"
#include "extern_all.h"
// CUIWnd_1 对话框
IMPLEMENT_DYNAMIC(CUIWnd_1, CDialogEx)
CUIWnd_1::CUIWnd_1(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_PAGE_1, pParent)
{
}
CUIWnd_1::~CUIWnd_1()
{
}
void CUIWnd_1::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LIST1, lstPack);
}
BEGIN_MESSAGE_MAP(CUIWnd_1, CDialogEx)
ON_BN_CLICKED(IDC_BUTTON1, &CUIWnd_1::OnBnClickedButton1)
ON_BN_CLICKED(IDC_BUTTON2, &CUIWnd_1::OnBnClickedButton2)
ON_BN_CLICKED(IDC_BUTTON3, &CUIWnd_1::OnBnClickedButton3)
END_MESSAGE_MAP()
// CUIWnd_1 消息处理程序
void CUIWnd_1::OnBnClickedButton1()
{
// int count = _pgamebase->SRO_Control->GetPPack()->GetPackBack()->PackCount();
CString tmp;
// tmp.Format(L"%d", count);
// AfxMessageBox(tmp);
PBackPack _PackBack = _pgamebase->SRO_Control->GetPPack()->GetPackBack();
lstPack.ResetContent();
for (int i = 0; i < _PackBack->PackCount(); i++)
{
PITEM item = _PackBack->GetItem(i);
if ((item != NULL) && (item->Type)) {
tmp.Format(L"[%s][数量:%d][耐久:%d/%d]\n", item->GetNameByWide(), item->Count, item->Durabillty, item->MaxDurabillty);
lstPack.AddString(tmp);
}
}
}
void CUIWnd_1::OnBnClickedButton2()
{
// int count = _pgamebase->SRO_Control->GetPPack()->GetPackBack()->PackCount();
CString tmp;
// tmp.Format(L"%d", count);
// AfxMessageBox(tmp);
PEquipPack _PackBack = _pgamebase->SRO_Control->GetPPack()->GetEquipBack();
lstPack.ResetContent();
for (int i = 0; i < 13; i++)
{
PITEM item = _PackBack->GetItem((EquipType)i);
if ((item != NULL) && (item->Type > 0)) {
tmp.Format(L"[%s][数量:%d][耐久:%d/%d]\n", item->GetNameByWide(), item->Count, item->Durabillty, item->MaxDurabillty);
lstPack.AddString(tmp);
}
}
}
void CUIWnd_1::OnBnClickedButton3()
{
_pgamebase->SRO_Control->UseItem(12);
}