逆向太难了 我太菜了
MISC who moved my flag 给出一个流量包,看了下,可分为两段。
第一段是172.17.0.25访问172.17.0.1上使用python开启的httpSimple服务,下载了一个名为tshd的文件。tshd是tiny shell tsd的服务端,可用于加密流量的传输。
第二段就是tshd与tsh通信的过程。172.17.0.24和172.17.0.25
项目开源在creaktive /tsh 。
项目基本框架如下:
tsh.c和tshd.c分别是客户端和服务端,调用pel.c的pel_send_msg和pel_recv_msg来收发数据。
pel.c是封包加密层,但应该不是作者写的,而是Christophe Devine devine@cr0.net 。搜了下,这老哥写过很多密码学的轮子。但是没有社工到具体的关于pel.c这个框架的解释,没办法,只能自己去读源码了。
然后归纳下通信流程:
客户端tsh主动生成两个20Byte的IV,并向服务端tshd发送长为40Byte的Packet.
tshd接收两个IV之后,双方建立加密通信(密钥是secret,提前写死在tsh和tshd中)
此后的第一对packet,是挑战与应答。tsh发送一个使用IV1加密后的数据,tshd必须响应使用IV2加密后的数据。如果tsh解密出原有信息,则通过challenge。双方正式进行通信。
通信时,packet结构为:
头部的两个字节是message的长度,其后是message,二者组装后填充0向上取整到16的倍数字节,aes-128加密得到密文。然后计算密文的hmac,共20Byte,追加在packet的后面。
收发时,要动态更新上一个密文块的值,并与当前解密结果异或(CBC模式)
tsh和tshd支持三种后门,get_file, put_file, run_shell.
非空通信流量共7条,前三条是传输iv和应答挑战。
计算aes密钥的相关代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void  pel_setup_context ( struct pel_context *pel_ctx,                         char  *key, unsigned  char  IV[20 ] )     int  i;     struct  sha1_context  sha1_ctx ;     sha1_starts( &sha1_ctx );     sha1_update( &sha1_ctx, (uint8 *) key, strlen ( key ) );     sha1_update( &sha1_ctx, IV, 20  );     sha1_finish( &sha1_ctx, buffer );			     aes_set_key( &pel_ctx->SK, buffer, 128  );       memcpy ( pel_ctx->LCT, IV, 16  );     memset ( pel_ctx->k_ipad, 0x36 , 64  );		     memset ( pel_ctx->k_opad, 0x5C , 64  );     for ( i = 0 ; i < 20 ; i++ )     {         pel_ctx->k_ipad[i] ^= buffer[i];         pel_ctx->k_opad[i] ^= buffer[i];     }     pel_ctx->p_cntr = 0 ;        				 } 
secret可从流量包中提取出来的tshd中找到(可以自己编译一份带符号的tshd,对比着可以很快找到secret.
解密脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 from  scapy.all import  rdpcapimport  socketimport  timeimport  structfrom  Crypto.Cipher import  AESdef  get_sha1 (b ):    import  hashlib     return  hashlib.sha1(b).digest() def  get_session_key (secret, iv ):    return  get_sha1(secret + iv)[:16 ] class  PrpCrypt (object ):    def  __init__ (self, key, iv ):         assert  len(key) == 16          assert  len(iv) == 16          self.key = key         self.iv = iv         self.mode = AES.MODE_CBC     def  encrypt (self, data ):         cryptor = AES.new(self.key, self.mode, self.iv)         if  len(data) % 16  != 0 :             data += b'\x00'  * (16  - len(data) % 16 )         cipher = cryptor.encrypt(data)         return  cipher     def  decrypt (self, data ):                  data = data[:len(data)//16 *16 ]         cryptor = AES.new(self.key, self.mode, self.iv)         plain = cryptor.decrypt(data)         return  plain packets = rdpcap("flow.pcap" ) from_24 = [] sport=8888  src="172.17.0.24"  for  packet in  packets:    if  "TCP"  in  packet and  packet['TCP' ].payload:         if  packet["IP" ].src==src and  packet["TCP" ].sport==sport:             from_24.append(packet['TCP' ].payload.load) to_24 = [] sport=55794  src="172.17.0.25"  for  packet in  packets:    if  "TCP"  in  packet and  packet['TCP' ].payload:         if  packet["IP" ].src==src and  packet["TCP" ].sport==sport:             to_24.append(packet['TCP' ].payload.load)              send_iv, recv_iv = from_24[0 ][20 :], from_24[0 ][:20 ] secret = b'6.3.0-18+deb9u1'  send_aes_key = get_session_key(secret, send_iv) recv_aes_key = get_session_key(secret, recv_iv) cryp = PrpCrypt(key = send_aes_key, iv = send_iv[:16 ]) plain = cryp.decrypt(to_24[2 ][:16 *5 ] + to_24[2 ][16 *5 +20 :16 *5 +20 +16 ] + to_24[2 ][16 *5 +20 +16 +20 :]) print(plain) 
需要注意的是,最后一条流量是个粘包。
当时我iv没找对,密钥搞对了。但是由于是cbc模式,所以只有第一个密文块会求不对,后面的理论上还是可以求的。但是第一个密文块求不对,就不知道第一个包的长度(前两字节),就没法自动剔除20Byte的hmac。但是由于是比赛,所以怎么省事怎么来,所以我直接尝试多次,手动分包,还好,分到第三个包的时候,就看到flag了。
2021.1.5的补充
 
刚考完网安,闲得没事干,所以又把这题翻出来分析了下,写出了下面的脚本,正确处理了iv和分包。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 from  scapy.all import  rdpcapimport  socketimport  timeimport  structfrom  Crypto.Cipher import  AESdef  aes_ecb_decrypt (data, key ):    assert  len(data) == 16      assert  len(key) == 16      cryptor = AES.new(key, AES.MODE_ECB)     plain = cryptor.decrypt(data)     return  plain def  aes_cbc_decrypt (data, key, iv ):    assert  len(data) == 16      assert  len(key) == 16      assert  len(iv) == 16      LCT = iv     plain = aes_ecb_decrypt(data, key)     new_plain = b''      for  i in  range(len(plain)):         new_plain += struct.pack('B' , plain[i] ^ LCT[i])     return  new_plain, data def  get_sha1 (b ):    import  hashlib     return  hashlib.sha1(b).digest() def  get_session_key (secret, iv ):    return  get_sha1(secret + iv)[:16 ] packets = rdpcap(r"D:\桌面\flow.pcap" ) from_24 = [] sport=8888  src="172.17.0.24"  for  packet in  packets:    if  "TCP"  in  packet and  packet['TCP' ].payload:         if  packet["IP" ].src==src and  packet["TCP" ].sport==sport:             from_24.append(packet['TCP' ].payload.load) to_24 = [] sport=55794  src="172.17.0.25"  for  packet in  packets:    if  "TCP"  in  packet and  packet['TCP' ].payload:         if  packet["IP" ].src==src and  packet["TCP" ].sport==sport:             to_24.append(packet['TCP' ].payload.load)              send_iv, recv_iv = from_24[0 ][20 :], from_24[0 ][:20 ] secret = b'6.3.0-18+deb9u1'  send_aes_key = get_session_key(secret, send_iv) recv_aes_key = get_session_key(secret, recv_iv) print('客户端tsh send:' ) data = b''  for  i in  from_24[1 :]:    data += i key = recv_aes_key LCT = recv_iv[:16 ] index = 0  while  index < len(data):    first_block, _ = aes_cbc_decrypt(data[index : index + 16 ], key, LCT)     msg_len = (first_block[0 ]<<8 ) + first_block[1 ]     blk_len = ((msg_len + 2  + 16 ) // 16  if  (msg_len + 2 ) % 16  != 0  else  (msg_len + 2 ) // 16 ) * 16      msg_data = data[index : index + blk_len]     msg = b''      for  i in  range(blk_len//16 ):         _msg, LCT = aes_cbc_decrypt(msg_data[i*16  : i*16 +16 ], key, LCT)         msg += _msg     msg = msg[2 :msg_len+2 ]     print('msg_len = %d\tblk_len = %d\tmsg = %s'  % (msg_len, blk_len, msg))     index += blk_len + 20  print('\n服务端tshd send:' ) data = b''  for  i in  to_24:    data += i key = send_aes_key LCT = send_iv[:16 ] index = 0  while  index < len(data):    first_block, _ = aes_cbc_decrypt(data[index : index + 16 ], key, LCT)     msg_len = (first_block[0 ]<<8 ) + first_block[1 ]     blk_len = ((msg_len + 2  + 16 ) // 16  if  (msg_len + 2 ) % 16  != 0  else  (msg_len + 2 ) // 16 ) * 16      msg_data = data[index : index + blk_len]     msg = b''      for  i in  range(blk_len//16 ):         _msg, LCT = aes_cbc_decrypt(msg_data[i*16  : i*16 +16 ], key, LCT)         msg += _msg     msg = msg[2 :msg_len+2 ]     print('msg_len = %d\tblk_len = %d\tmsg = %s'  % (msg_len, blk_len, msg))     index += blk_len + 20  
输出为:
第一条封包看起来是乱码,其实它是挑战响应机制的那十六字节,详见源项目中的:
over~