coverxit / EasyDrcom

3rd Party Dr.COM Client for HITwh

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

新发现:心跳包7-10字节的含义及“发送心跳包失败”的解决方法

SwimmingTiger opened this issue · comments

v1.7 版本的 EasyDrcomGUI 在我校无法正常运行,在修改心跳服务器地址为我校的172.16.192.111之后,程序会发送无数个心跳包(send_alive_pkt1)导致栈溢出而崩溃。

我抓包对比了程序发送的心跳包(send_alive_pkt1)和原生客户端的区别,发现了第7第8字节是不同的。程序将其设置为 { 0x1F, 0x00 },而原生客户端发送的是 { 0xdc, 0x02 },并且从服务器端返回的 7-8 字节也是 { 0xdc, 0x02 }。

我将这两个字节改为 { 0xdc, 0x02 },然后原本会发送无数次心跳包的客户端就顺利的连上了。所以我想,这两个字节应该类似于协议的版本号,客户端必须和服务器一致才能使服务器接受该心跳包。

我仔细的观察原生客户端的心跳包,发现它是这么处理的:首先用自己的两个字节填充心跳包的 7-8 字节,然后发送给服务器,从服务器接收到一个拒绝包(就是“Received 'Misc, File'”)。然后从拒绝包中读出服务器的版本号,填充到 7-8 字节,再发一个相同的心跳包,然后服务器就接受了。

顺便我还发现了 9-10 字节的作用,就是一个校验串,客户端填充了什么服务器就原样返回。

我模仿了填充心跳包版本号的过程,程序工作的很好,估计能够适应不同学校的网络环境了。我顺便还把心跳服务器的地址端口等信息放进了配置文件里。版本库在这里:https://github.com/SwimmingTiger/EasyDrcomGUI/

贴出改进后的 drcom_dealer_u62.hpp (原谅我使用tab导致的不对齐,它们在VS中看起来是一样的╮(╯▽╰)╭)

class drcom_dealer_u62 : public drcom_dealer_base {
private:
    unsigned char version_id[2] = { 0x00, 0x00 }; //协议版本号(不需要与服务器端一致,程序会自动侦测服务器端版本号)
public:
    ......
    int send_alive_pkt1(int retry_times = 0)
    {
        U62_LOG_INFO("Send Alive Packet 1." << std::endl);

        std::vector<uint8_t> pkt_data;
        pkt_data.push_back(0x07); // Code
        pkt_data.push_back(pkt_id);
        pkt_data.insert(pkt_data.end(), { 0x28, 0x00 }); // Type
        pkt_data.insert(pkt_data.end(), { 0x0B, 0x01 }); // Step
        pkt_data.insert(pkt_data.end(), { version_id[0], version_id[1] }); // 可认为是协议版本号,若和服务器端的不一致则认证失败
        pkt_data.insert(pkt_data.end(), { 0x12, 0x34 }); // 随机码,服务器的响应中会包含同样的内容
        pkt_data.insert(pkt_data.end(), { 0x00, 0x00, 0x00, 0x00 }); // some time
        pkt_data.insert(pkt_data.end(), { 0x00, 0x00 }); // Fixed Unknown

        // some flux
        pkt_data.insert(pkt_data.end(), 4, 0x00);
        memcpy(&pkt_data[16], &misc1_flux, 4);

        pkt_data.insert(pkt_data.end(), 8, 0x00); // Fixed Unknown, 0x00 *8
        pkt_data.insert(pkt_data.end(), { 0x00, 0x00, 0x00, 0x00 }); // Client IP (Fixed: 0.0.0.0)
        pkt_data.insert(pkt_data.end(), 8, 0x00); // Fixed Unknown, 0x00 *8

        U62_LOG_SEND_DUMP

        auto handler_success = [&](std::vector<uint8_t> recv) -> int {
            U62_LOG_RECV_DUMP("Alive Packet 1");

            if (recv[0] != 0x07) // Misc
                return -1;

            if (recv[5] == 0x06) // File
            {
                U62_LOG_INFO("Received 'Misc, File', Send Keep Alive Packet 1 again." << std::endl);

                //复制服务器的协议版本号
                version_id[0] = recv[6];
                version_id[1] = recv[7];

                //递归调用太多次会导致程序崩溃,因此加了限制
                if (retry_times < 10) {
                    return send_alive_pkt1(retry_times + 1);
                }
                else {
                    U62_LOG_INFO("Send Too Many Keep Alive Packets!" << std::endl);
                    return -1;
                }

            }
            else
            {
                U62_LOG_INFO("Gateway return: Response for Alive Packet 1." << std::endl);

                pkt_id++;
                U62_LOG_DBG("next packet id = " << (int) pkt_id << std::endl);

                memcpy(&misc3_flux, &recv[16], 4);
                return 0;
            }
        };

        U62_HANDLE_ERROR("Send Alive Packet 1");
        U62_PERFORM("Send Alive Packet 1");
    }

    int send_alive_pkt2()
    {
        U62_LOG_INFO("Send Alive Packet 2." << std::endl);

        std::vector<uint8_t> pkt_data;
        pkt_data.push_back(0x07); // Code
        pkt_data.push_back(pkt_id);
        pkt_data.insert(pkt_data.end(), { 0x28, 0x00 }); // Type
        pkt_data.insert(pkt_data.end(), { 0x0B, 0x03 }); // Step
        pkt_data.insert(pkt_data.end(), { version_id[0], version_id[1] }); // 可认为是协议版本号,若和服务器端的不一致则认证失败
        pkt_data.insert(pkt_data.end(), { 0x43, 0x21 }); // 随机码,服务器的响应中会包含同样的内容
        pkt_data.insert(pkt_data.end(), { 0x00, 0x00, 0x00, 0x00 }); // some time
        pkt_data.insert(pkt_data.end(), { 0x00, 0x00 }); // Fixed Unknown

        // some flux
        pkt_data.insert(pkt_data.end(), 4, 0x00);
        memcpy(&pkt_data[16], &misc3_flux, 4);

        pkt_data.insert(pkt_data.end(), 8, 0x00); // Fixed Unknown, 0x00 *8
        pkt_data.insert(pkt_data.end(), local_ip.begin(), local_ip.end()); // Client IP
        pkt_data.insert(pkt_data.end(), 8, 0x00); // Fixed Unknown, 0x00 *8

        U62_LOG_SEND_DUMP

        auto handler_success = [&](std::vector<uint8_t> recv) -> int {
            U62_LOG_RECV_DUMP("Alive Packet 2");

            if (recv[0] != 0x07 && recv[5] != 0x04) // Misc 4
                return -1;

            U62_LOG_INFO("Gateway return: Response for Alive Packet 2." << std::endl);

            pkt_id++;
            U62_LOG_DBG("next packet id = " << (int) pkt_id << std::endl);

            memcpy(&misc1_flux, &recv[16], 4);
            return 0;
        };

        U62_HANDLE_ERROR("Send Alive Packet 2");
        U62_PERFORM("Send Alive Packet 2");
}
......

drcom_dealer_u31.hpp 做了同样的改进后也工作良好。

此外,吐槽一下 Drcom 的心跳服务器:

2016-10-14 11:18:27 [U62 Debug] Received after Alive Packet 1, dump:
00000000: 07 00 10 01 0b 06 dc 02  12 34 00 00 00 00 00 00  .........4......
00000010: a8 a8 00 00 e6 59 f1 67  00 00 00 00 dc 02 00 00  .....Y.g........
00000020: 4d 5a 90 00 03 00 00 00  04 00 00 00 ff ff 00 00  MZ..............
00000030: b8 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00  ........@.......
00000040: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
00000050: 00 00 00 00 00 00 00 00  00 00 00 00 08 01 00 00  ................
00000060: 0e 1f ba 0e 00 b4 09 cd  21 b8 01 4c cd 21 54 68  ........!..L.!Th
00000070: 69 73 20 70 72 6f 67 72  61 6d 20 63 61 6e 6e 6f  is program canno
00000080: 74 20 62 65 20 72 75 6e  20 69 6e 20 44 4f 53 20  t be run in DOS 
00000090: 6d 6f 64 65 2e 0d 0d 0a  24 00 00 00 00 00 00 00  mode....$.......
000000a0: 7c c3 83 5a 38 a2 ed 09  38 a2 ed 09 38 a2 ed 09  |..Z8...8...8...
000000b0: 61 81 fe 09 3a a2 ed 09  43 be e1 09 39 a2 ed 09  a...:...C...9...
000000c0: bb aa b0 09 32 a2 ed 09  bb be e3 09 3a a2 ed 09  ....2.......:...
000000d0: 57 bd e6 09 39 a2 ed 09  57 bd e7 09 3d a2 ed 09  W...9...W...=...
000000e0: 57 bd e9 09 3a a2 ed 09  38 a2 ec 09 81 a2 ed 09  W...:...8.......
000000f0: 0e 84 e9 09 3b a2 ed 09  0e 84 e6 09 30 a2 ed 09  ....;.......0...
00000100: ff a4 eb 09 39 a2 ed 09  c7 82 e9 09 3b a2 ed 09  ....9.......;...

MZ ... This program cannot be run in DOS mode ...
这么大大的一个PE结构体就这么发过来了。分明是发生了内存泄漏。
还有,操作系统无疑是 Windows。

感谢新发现~顺带后面那堆目测不是内存泄露,应该是来跑本地客户端校验的(用于自动升级吧大概)。
建议 unsigned char version_id[2] = { 0x1F, 0x00 };
顺带如果方便的话请clone一个EasyDrcom到本地,修改对应文件然后发起pull request,方便把你计入本项目的contributor :)

pull request已发送。此外我很好奇心跳包里的那个PE结构到底是啥,我试试把它dump下来运行一下:satisfied:

已merge,再次感谢。

记录一下我的新想法。我们已知心跳包的第6、7字节是服务器端给出的,客户端需要复制。而第8、9字节是客户端给出的,服务器端需要复制。我抓包显示客户端经常改变其第8、9字节,而服务器端没有改变过第6、7字节。也许之所以不改变,是为了实现方便:satisfied:

所以我们可以这样认为,第6、7字节和第8、9字节很可能是随机产生的,是客户端和服务器互相确认用的字节。第6、7字节是服务器给出,客户端在下一个包中需要复制。第8、9字节是客户端给出,服务器在下一个包中需要复制。如果收到和自己给出的随机字节不一致的包,就丢掉。(不过由于某种原因,服务器总是给出毫无变化的“随机字节”:satisfied:)