zoukankan      html  css  js  c++  java
  • 记一道比较简单的协议栈逆向题目

    记一道协议栈逆向题目

      三星ctf的一道协议相关的逆向题目,出题人自实现了一个简单的协议栈,给了可执行文件的同时,给了一个流量包。

      题目不是特别难,但是考察的点比较多,学到了一些东西,也比较麻烦。比赛的时候没有解出来,赛后根据队友的writeup进行复盘。总结一下自己失误的地方,一方面因为自己对于openssl库函数不是很熟悉,对AES加密算法不够了解,另一方面缺乏逆向协议栈的基础,在IDA反汇编结果不理想的时候,没有仔细理清栈桢的结构,导致了题目走了弯路,这里记录一下这道题目,日后在遇到协议栈分析的时候,希望自己能够有一些基本的思路和方法。

    静态分析

      进入主函数,主函数如下,其中connect2host函数主要是建立套接字返回文件流,AESencrypt函数是对字符串进行AES加密,AESdecrypt函数是对字符串进行解密,这三个函数不是关注的重点,生成AES加密key和iv向量的功能都在getkey这个函数中实现了。

       getkey函数中,senddata函数是向服务器端发送一段数据,receive函数是接收服务器端的数据。

    unsigned __int64 __fastcall senddata(int fd, __int64 a2)
    {
      _QWORD to[31]; // [rsp+20h] [rbp-240h] BYREF
      __int64 v4; // [rsp+11Ah] [rbp-146h]
      char buf; // [rsp+130h] [rbp-130h] BYREF
      _BYTE v6[7]; // [rsp+131h] [rbp-12Fh] BYREF
      _QWORD v7[31]; // [rsp+151h] [rbp-10Fh] BYREF
      __int64 v8; // [rsp+24Bh] [rbp-15h]
      unsigned __int64 v9; // [rsp+258h] [rbp-8h]
    
      v9 = __readfsqword(0x28u);
      memset(to, 0, 0x100uLL);
      HIWORD(v4) = 0;
      generateMD5(randhash);                        
      generateMD5(from);                            
      memset(&buf, 0, 0x120uLL);
      *(_WORD *)((char *)&v8 + 5) = 0;
      HIBYTE(v8) = 0;
      buf = 1;
      memcpy(v6, randhash, 0x20uLL);
      if ( (unsigned int)rsa_encrypt((__int64)from, 0x20u, (__int64)to) == -1 )// rsa(randhash)
      {
        perror("Failed to encrypt");
      }
      else
      {
        v7[0] = to[0];                              // 数据包:标志位 + randhash + rsa(from)
        v8 = v4;
        qmemcpy(
          (char *)v7 + 7,
          (char *)to - ((char *)v7 - ((char *)v7 + 7)),
          8LL * ((((unsigned int)((char *)v7 - ((char *)v7 + 7)) + 258) & 0xFFFFFFF8) >> 3));
        write(fd, &buf, 0x123uLL);                  // 发送到7001端口的密文
      }
      return __readfsqword(0x28u) ^ v9;
    }

      senddata函数中,栈地址中从buf开始的后面的数据都是发送给服务器端的数据,buf = 1定义了数据帧的标志位,randhash是generateMD5函数生成的md5哈希值,被赋值给v6指向的地址。from也是generateMD5函数生成的哈希值,这个哈希值进行了rsa加密,加在了数据帧的末尾。(这个反汇编的结果,确实有些反人类)

      整个数据包的结构就是:标志位(0x1)+ randhash(32 bytes长度) + rsa(from),通过流量包可以看到标志位。

       generateMD5函数值得仔细研究一下,这个函数中有玄机。

    unsigned __int64 __fastcall generateMD5(_QWORD *init_buf)
    {
      unsigned int seek; // eax
      __int64 v2; // rdx
      __int64 v3; // rdx
      int randnum1; // [rsp+18h] [rbp-E8h] BYREF
      int randnum2; // [rsp+1Ch] [rbp-E4h] BYREF
      char c[96]; // [rsp+20h] [rbp-E0h] BYREF
      char v8[96]; // [rsp+80h] [rbp-80h] BYREF
      __int64 v9; // [rsp+E0h] [rbp-20h] BYREF
      __int64 v10; // [rsp+E8h] [rbp-18h]
      unsigned __int64 v11; // [rsp+F8h] [rbp-8h]
    
      v11 = __readfsqword(0x28u);
      seek = time(0LL);                             // 种子固定
      srand(seek);
      randnum1 = rand();
      MD5_Init(c);                                  // 初始化MD5 Contex
      MD5_Update(c, &randnum1, 4LL);
      MD5_Final(&v9, c);                            // 输出md5值
      v2 = v10;
      *init_buf = v9;                               // 指针赋值,v9指向地址保存md5值
      init_buf[1] = v2;
      randnum2 = rand();
      MD5_Init(v8);
      MD5_Update(v8, &randnum2, 4LL);
      MD5_Final(&v9, v8);
      v3 = v10;
      init_buf[2] = v9;                             // md5(rand())+md5(rand())
      init_buf[3] = v3;
      return __readfsqword(0x28u) ^ v11;
    }

      generateMD5函数中,用time(0)生成了一次种子,然后使用srand函数为rand函数提供种子,生成两次md5值,init_buf为两次md5值之和。在generateMD5函数中,第一次生成的MD5值和第二次生成的MD5值自然是不同的,但是generateMD5函数实际上被调用了两次,而且两次间隔时间非常短,这样一来,相当于time(0)固定,生成的种子不变,每次generateMD5函数调用srand函数随机播种了两次。

      那么rand函数生成的随机数会有什么变化呢?其实每次生成的随机数是相等的。

      实验如下:

       所以说数据帧的结构实际上就是:0x1 + randhash + rsa_encryt(randhash)。从流量分析中,我们可以得到randhash的值。

      receive函数主要是接受服务器端返回的数据,并且对数据进行rsa解密,并且将被解密的数据拷贝到bss段,这个salt需要根据题目给出的流量包,来动态调试得出。

     

       之前所有拷贝到bss段的数据的值,实际上都用来在generatehash函数中生成AES加密所需要的key和iv。

       randhash等于from之前已经提到,可以在流量中提取出来,salt可以根据动态调试找出来,然后叠加求md5值,就可以算出key和iv,这道题的flag也就在key里面了,题目中也提示了:

     

    动态调试

      动态调试获取salt,wireshark提取服务器端返回的数据,写个socket脚本挂住:

    import socket
    from requests import *
    import binascii
    
    def server():
        host,port = "127.0.0.1",7001
        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        s.bind((host,port))
        s.listen(2)
        server_data1 = "020f4b82b9d771a2625de1339269ead8599308a5119f3c8a3eb2e266f04210c2ac7e5657072ecd5fb777a99a8d57d94e39fa7001dd926ac42e4e9c944cd086868605d59db718caf0738f9983575119e4ae63f84c7a274eba7b39b9dc19a749a9bca7bead0aa75ea8f2c34a48dda8a4812e933249e945f66858785947d95168154b18e44f0ffa4f3c0a336ee2fc72f6b0aa1deeba5cd4646e68ae591923dc2894597862a753c3f86409cc19b8b5070de08fdab340618e6fb9370d95bf07670d76cdf320d5bd3bf10c26ec89f47956a4e6f850f751d7480c82cb25f7a48ba167d207d7a3836c7dee679a7ac1e004e0399598994e7542d63e65eb24b41158c66728720000"
        server_data2 = "029d1a9edb0d1ec28a3d941bee70e42af795bfec2bbe5a9ccc61a838c037addc6d0506512bd9295af10be912343dfc582bc44c1eff6e9989f3b8a005a92f4b67edc7fae41ada053779c91902801af473510e14401978c35458a599d5711ec411a224598163e4d08ac6dddbd10100064793da6bf2f03a14d33ebdc251d7cb3f149dc995abde49ca04339fd474a118489baedb300055d8a847dda102266dbdcb2cb497706fde541bbb4315f967d105f4a1cd54d6c92ada31aacd65c65e74654e23a7d7ac3174c2247d7f7796fee47e851558ce1d98470ce3ae83a42ff63bf8402d04cf0a48209677b950c401829a85063a1754d7dc25f0ef4cbe753e034081756d170000"
        server_data1 = binascii.a2b_hex(server_data1)
        server_data2 = binascii.a2b_hex(server_data2)
        print(server_data1)
        while True:
            c,addr = s.accept()
            #s.recv(1024)
            #c.send(payload)
            #data = c.recv(1024)
            while True:
                data = c.recv(1024)
                print(data)
                c.send(server_data1)
    
    def main():
        server()
        # print(key)
        
    if __name__ == "__main__":
        main()

      断点下载receive函数调用memcpy前,rsi寄存器保存的地址里的值就是salt。

     总结

      这道题主要记录一下分析的过程:1.学到一些openssl函数;2.学到IDA里面分析协议栈的一点技巧;3.伪随机数生成种子过程中,当seek固定的时候,重置srand生成随机数不变。

      现在逆向的能力实在不敢恭维,吐槽一下自己。

     

      

  • 相关阅读:
    全国省市县三级数据库
    多源教学数据管理系统之团队课设博客
    1.判断字符串中的字符是否Unique
    [转载]linux防火墙基础和管理设置iptables规则
    (转)Sed使用详解
    2.判断回文(Anagrams)
    【转载】关于23 种设计模式的有趣见解
    macbook M1芯片在centos8下安装k8s笔记
    Winform 学习初级 从WebForm到WinForm
    如何建立数据模型
  • 原文地址:https://www.cnblogs.com/L0g4n-blog/p/15159637.html
Copyright © 2011-2022 走看看