开局一个pcap数据包,flag在哪全靠猜~
两天只做了一道题,不过还是挺有收获的。顺便纪念一下第一次在大型CTF比赛中拿到带血的flag~
第一部分的flag的获取非常简单,只需要打开数据包追踪tcp流,就可以看到。
因为做到这里的时候还没有提示,开始在数据包内进行自由探索。感觉题目中的DNS数据包和ICMP数据包都有明显的构造痕迹,所以思路开始发散。走了不少的弯路。
第一天下午的时候题目突然发了hint,题目提供了一个github地址 https://github.com/dreamstalker/rehlds,是一个游戏引擎,和flag3有关。
我们开始重新收束思路,根据题目描述,后两部分的flag应该还是跟这个游戏服务器有关。
继续查看数据包中http传输的数据。发现有传输一个flag.wad文件还有一个flag.bsp文件。
我们首先尝试解析那个wad文件,google一下可知,这两个都是游戏数据文件。bsp是游戏地图。
找到了一个在线https://www.coolutils.com/online/WAD-to-PNG,但是转成图片之后只得到了一张没太大用处的图片。
bsp折腾了半天,也没找到一个可以打开的方式,所以…
那我们直接安装 CS 1.6!!
加载 flag.bsp 对应的地图,进入游戏可以看到 FLAG 的相关提示。通过穿墙,可以找到 flag 的 part2。(想哥tql!
根据这里的提示,我们可以明确flag有三部分。
进入游戏 flag 地图后,并没有 hint2 的提示,但是对比连接服务器之后的欢迎界面,我们大概可以明确flag3应该就在服务器和客户端通信的udp数据包中。
但是查看udp流,这些数据明显经过了特殊的加密处理,我们没有办法直接获得通信的数据。
根据题目给出的hint,游戏服务器很可能是采用了上面的github代码搭建的。那为了弄懂这些udp乱码,我们就只有去看代码一条路了。(喂,说好是道misc呢?!
因为我们要找的数据一定是从服务器发到客户端的,所以我们要去看服务器发包部分的代码。
最开始我们开的却是是服务器发包部分的代码,但后来才意识到,我们需要解包,其实我们直接看收包部分的代码就好了。
又经过一番摸索后,我们找到了关于服务器收包部分代码的入口——SV_ReadPacket
函数。这里为了逻辑清晰,删去了一些对我们意义不大的代码。
1 | void SV_ReadPackets(void) |
但是似乎看不懂… g_psvs、netchan都是些什么QAQ
让我们耐心一些,一点点抽丝剥茧。
1 | server_static_t g_psvs; |
由client_t *cl = &g_psvs.clients[i];
这句代码我们可以知道cl
是一个连接服务器的客户端。
让我们再来看看client的结构体长什么样子吧。
1 | typedef struct client_s |
netchan_t netchan;
又是在client里面定义的另一个结构体。结构体真是一个比一个长啊,不过我们好像接近真相了。
1 | // Network Connection Channel |
从上面的变量名还有注释中,我们可以看出,Netchan
这个结构体主要是用来处理网络传输的数据的。
大致理解了这些变量的含义后,我们就可以尝试阅读后面关于数据包处理的代码。从上面的 我们可以看到Netchan_Process
这个函数,结合我们对于Netchan
结构体的判断,可以确认这个函数是比较重要的。 因为这个函数比较大,所以我们一段一段来看。(代码有删减)
1 | int MSG_ReadLong(void) |
函数开始定义了一堆sequence, sequence_ack什么的变量,如果学过计算机网络,对此应该都不会陌生,核心意义是为了在udp中模拟tcp实现可靠数据传输,保证一些关键数据包的到达。
虽然函数整体看上去很复杂,但是其实我们主要关注的是代码如何处理输入的数据包数据,所以我们主要关注MSG_ReadLong
和MSG_ReadShort
这类的函数就可以了。因为只有这类函数才会直接从输入数据中读取信息。
继续阅读Netchan_Process
函数,可以知道数据流的前四个字节是seq num,再之后四个字节是seq ack。
Reliable_message和reliable_ack是基于seq和seq_ack生成的标记位,主要与实现可靠传输有关,我们这里可以不太关注。
message_contains_fragments
是一个比较关键的变量,他是seq num的第二位,它标记着这段数据有没有被分片。
再之后调用了COM_UnMunge2
函数,传递的第一个参数是收到网络数据包第9个字节及以后的内容,第二个参数是这个数据包的大小,第三个参数是序列号的后16位。
1 | void COM_UnMunge2(unsigned char *data, int len, int seq) |
并不是一段太复杂的代码,甚至我们没必要都读懂,只需要能够大致清楚这段代码是用来对数据做加密的就可以了,似乎是个开发者自己实现的对称加密。而且因为它把代码已经写好了,我们解密的时候直接调用就可以了~
1 | if (message_contains_fragments) |
看!我们终于看到了服务器传输的明文!也拿到了第三部分flag~
part2: _@@w4d_
part3: h4ppyY*!uNmUng3}
L3HCTF{v41v3#_@@w4d_h4ppy!uNmUng3}
END!🎉
Worked by wchhlbt & idealeer