【干货】如何开发一款游戏修改器?13:多线程发包与主线程发包

文摘   游戏   2025-01-06 00:00   广西  
本期我们的主要任务是分析明文发包。明文发包是从技能库这里开始的分析。首先,让我们解释一下什么是明文发包。
所谓的明文发包,就是指像我们在喊话时发送的内容。如果消息经过了明文发包,那么我们就能够看到真实的数据。比如说,当我们喊话时输入的是123,那么在我们内存中看到的也会是123。同样地,当我们发送一个指令“1”时,我们能够看到的就是真实的数字。
在游戏中,明文发包涵盖了诸如技能释放、背包管理、NPC对话、在商店购买和出售物品、使用物品、使用技能、交接任务和接受任务等功能。这些操作都会通过明文发包与服务器进行通信。当然,这并不是绝对的,但至少在大多数情况下是如此。因为在一些游戏设计中,可能会采用加密等方式保护铭文包的数据,但通常情况下,所有功能性操作,无论是移动、使用物品还是技能,都会与服务器进行交互通信。
这样的交互具有重要性,因为它能够将玩家的状态同步到服务器,并由服务器将这些信息分发给其他客户端,使其他玩家能够看到玩家的动作。如果没有与服务器的通信,其他玩家将无法看到我们的动作,因为这些动作仅存在于本地。
因此,对于游戏功能的分析来说,当我们分析到了这个明文发包的库时,至少五六成的功能已经覆盖了。其他一小部分功能可能不在其中,比如本地选怪功能,因为这些功能不需要与服务器同步。铭文包的发送通常可以通过套接字函数来实现,我们之前也提到过。
这几个都是套接字。向服务器发送数据的功能一般都可以通过这些套接字函数来实现。但在细节上,它们可能会有一些不同,比如同步和异步等。但这些细节我们不需要过多关心,因为它们都是由底层实现的。我们只需要知道这几个函数能够向服务器发送数据即可。
在一些复杂的游戏中,可能会同时使用其中一个或多个函数来发送不同的信息。但有些游戏可能会采用多线程操作,即发包不是在主线程中进行。这种情况下,发包会有一定的同步机制。我们可以通过断线的情况来区分出是否是多线程发包。如果在断线之后,我们看到的线程不是主线程,那么就很可能是多线程的发包了。
当发现是多线程发包时,我们就不能再以这几个函数作为突破口了。只有当是主线程发包时,我们才能直接使用这几个套接字的API作为突破口。
如果是多线程发包,我们就需要从技能对象、喊话对象、背包物品对象或者NPC对象等比较容易分析的地方来做突破口。无论是使用技能、使用物品还是打开NPC等操作,都会向服务器发送一个包。但这个包可能是由主线程发出的,也可能是由其他线程发出的。
需要注意的是,多线程发包指的是不由主线程发包的情况。我们需要对游戏进行分析,确定喊话是否是一个好的追踪点。但我认为,使用技能可能是一个更好的突破口。
此外,我们还需要确定是主线程发包还是多线程发包。只有确定了这一点,我们才能有针对性地进行应对。
如果是主线程发包,那么处理起来就比较简单了,可以直接从技能这里下手。现在我们已经附加到游戏中了,接下来就可以对这些套接字函数进行进一步的分析了。
这是第一个断点,我们发现四个地方都跟踪到了一个段。SN已经断了,说明那里是被使用的。接下来,我们看一下send to这几个API函数有没有被使用到。主线程发包的话,肯定是异步的,因为同步的话会卡顿。我们先运行起来看一下。
在这个地方,我们已经确定了线程。现在的线程是3928非主线程。而主线程是2034,因此这个SD是一个多线程的发包方法。从这里能够看出来它是一个线程发包。当然,我们还有其他的几个套接字没有分析,我们看看有没有主线发包的接口。
我们先F9运行起来。移动一下,看看这个地方的断点。其他的断点全部删除,重新跟踪。喊话、打坐,断点都没有被触发。在使用技能的时候,其他三个断点也没有被触发,说明它们没有被使用。唯一被使用的是剩下的一个地方。
但是这个地方被使用时,提示的线程不是主线程,所以从现在的分析来看,它是一个多线程的发包。虽然是多线程发包,但数据肯定是从主线程传出去的。假设我们的技能库或物品使用的伪代码可能是这样的:
int UseSkill(BCX* skillObject) {    // 其他代码...    // 假设主包含有技能ID和角色ID等信息    int skillID = skillObject->GetSkillID();    int characterID = skillObject->GetCharacterID();    int serverCommand = skillObject->GetServerCommand();    int socketID = skillObject->GetSocketID();
    // 发送数据到服务器    // 发送 skillID、characterID、serverCommand 等信息...}
这段伪代码中,我们假设了一个主包,包含了技能ID、角色ID等信息,还有可能包含连接的IP和端口等。这个角色ID不是绝对的,因为它可以通过连接的IP和端口来确定。这个IP和端口实际上相当于是套接字,它在连接服务器时就绑定在一起,相当于一把钥匙。
让我们来简单了解一下套接字。当我们在发包时,其中一个参数就是套接字。无论是我们提到的那四个API函数,它们的第一个参数都是套接字,比如你看这个socket,它就是套接字。
套接字实际上是一个整数变量,类型与int相同,只是另外取了个名字而已。它就是一个套接字,与我们的IP和端口有关系。当我们像服务器发送数据时,套接字会自动找到对应的IP和端口。因此,当服务器接收到信息时,就知道是哪个客户端的IP和端口发送过来的。这个IP和端口与角色是绑定的。
因此,在发包时,角色的id可能是没有的,因为可以通过IP和端口推断出角色的id。关键是技能的id应该有,如果没有技能的id,就会有另外一个服务器发送的指令,这两者必定有一个。主包的目的就是告诉服务器我们发送了什么指令。这个指令可能是一个整数,也可能是一个复杂的结构,具体要分析才知道。可能有约定俗成的指令,也可能有复杂的分类。无论哪种形式,它的目的都是告诉服务器我们这边做了什么。
因此,技能库里肯定有这样的一个结构,至少包含两个参数,一个是缓冲区地址,一个是整数。
除了这两个常见的参数外,还可能有其他更多的参数。但通常这两个参数是比较常见的。在设计时,我们要找的就是这个铭文包的库。除了技能外,其他地方也会使用,比如物品使用的扩展。在这些地方,也会有一个缓冲区地址和一个包大小,尽管包的大小可能不同。
这就导致了明文包的使用非常普遍。如果我们通过常量来查找,调用它的地方可能至少有几百个,甚至上千个。像《魔兽世界》这样的大型游戏,调用铭文包的地方可能有四五千个。但是对于今天要分析的这个游戏,目前还不是很清楚。
回到我们的分析,要找到铭文包,要么以这四个函数作为突破口,但是这需要在主线程发包的情况下才行。但根据我们刚刚的分析,它是一个非主线程发包。所以现在只有这个地方会断下来,这表明它是经过非主线程发包的。
如果我们提示的线程是主线程,那么肯定就是主线程发包。在这种情况下,我们可以直接以这个为突破口,然后按下 Ctrl+F9 返回几层,就能找到铭文包的库。但是如果是非主线程发包,那么分析起来就相对复杂。
本期我们尽量能够将其分析出来。说了这么多理论,现在让我们实践一下。我们不需要再使用顺德向上跟踪的方法,因为这只适用于线程发包的情况,而不是主线程发包。所以我们需要从技能库或者喊话库这些地方跟踪。最好的方法是从伪代码来看,技能库和物品使用扩展都会调用铭文库,它们有共同的特点。
另外,明文包的特征之一是第一个参数或第二个参数可能是缓冲区地址,另一个可能是包的大小。当然,还可能有第三个或第四个参数。
我们可以先尝试从缓冲区地址和包大小这两个参数入手查找。如果找不到的话,可以先分析物品使用的库或者喊话库。喊话库的构造可能与其他库不同,因为它可能不需要加密等功能,所以它使用的接口可能不包含铭文包的接口,但也有可能包含。
在这种情况下,物品使用的库和技能使用的库的相似程度可能更大。从结构上看,它们都涉及到背包里的对象或者角色的行动,所以它们的使用方式可能相似。
但是喊话库可能就不同了,因为它的功能与技能和物品使用的库不同,它可能有自己的特点。因此,在分析时,我们不能假设喊话库中一定包含铭文包。尽管我们已经讲了很多理论,但在实际分析中,我们可能需要更多的时间。如果分析不出来,我们可以休息一下,或者在下期教程继续分析。
现在,让我们先尝试以缓冲区地址为线索来查找。我们可以参考之前的一些教程,我们可能会发现在使用技能栏对象时调用了一个虚函数。我们可以跟踪这个函数,看看是否能够找到铭文包的库。

如果在这里没有找到,我们可以转到上期教程中涉及技能扩展的地方。然后,在这些地方设置断点,例如在使用打坐功能时。然后,我们可以通过按数字键等方式触发这些功能,看看是否能够在这些地方找到调用铭文包的线索。

我们来进行一些实际操作。首先,我们在代码中找到一个适合下断点的地方。然后,我们运行程序,用鼠标双击该功能,看看是否会触发断点。即使断点被触发,我们也要注意,该功能可能是从其他地方调用的,而不是从我们之前预期的地方。
例如,在前面的教程中,我们讨论了使用技能栏对象时调用的虚函数。但是,我们也可以通过数字键触发类似的功能。虽然这些功能是从不同的地方调用的,但它们都涉及到相同的对象的虚函数表加偏移地址。因此,我们可以先备注这两个功能的区别,一个是鼠标使用对象,另一个是数字键使用对象。
进一步来说,我们可以看到无论是鼠标使用对象还是数字键使用对象,它们都是调用相同的对象虚函数表加偏移地址,只是指向的地方不同。这一特征相对一致,无论是在第14课还是第15课中,我们都可以看到这一点。
接下来,我们进入代码中相应的部分,看看能否找到我们预期的缓冲区地址和包大小的特征。我们再次在适当的地方下断点,运行程序,并使用特定的按键触发功能。然后,我们可以进一步查看代码,看看是否存在类似缓冲区地址和包大小的特征。
在我们的尝试中,我们可能会发现一些地址值或者指针,但是可能需要更进一步的跟踪才能确定它们是否与我们寻找的特征相关。如果我们在当前的地方找不到所需的特征,我们可以继续跟踪程序的执行,或者在下期教程中尝试分析其他部分。


暮色的狐
这是一只高强度上网冲浪、高质量输出内容的狐狸。主打ACGN杂谈、技术干货分享、第九艺术鉴赏、网梗百科解析、情感树洞鸡汤、正能量价值观~
 最新文章