IP数据包中的隐写术

有一次,在捍卫另一项实验室工作之前,他们问我一个问题:IP数据包的哪些字段可用于缝? 我不知道,只是耸了耸肩。 但是很快我仍然决定研究这个问题。

在裁减下,您将找到IP数据包头的研究,Python自己的ping实用程序以及几种不引起注意的数据传输方法。

目录内容


  1. IP包结构
  2. 环境设定
  3. Ping:简单选项
  4. Ping:困难的选择
  5. 有改善吗?

IPv4封包结构




选择字段,其更改不会对包产生很大影响:

国际人道法的范围是5到15。
ToS字段用于确定流量和拥塞通知的优先级,而不丢弃数据包。 通常,该字段为0。从理论上讲,它可以用于传输整个字节的信息。
数据包的长度对于传输20到65535之间的数字是一个极好的领域。
TTL最多可以传输7位信息。 您需要知道到主机的跳数,并将其考虑在内。

环境设定


要重复该实验,您将需要两台装有Python和Scapy框架的机器。

您可以按照文档中说明进行安装。 就我而言,这是在本地网络已打开的情况下在DO上的两个分支。 为了测试stegano的可操作性,选择了两条路由:通过本地网络进行1跳,通过Internet进行2跳。

Ping:简单选项


首先,我们实现sender.py,它将发送没有隐藏消息的ICMP数据包。

from scapy.all import * #    10.0.0.2  icmp-type 8 (echo-request) pkt = IP(src="10.0.0.1", dst="10.0.0.2") / ICMP(type = 8) #      sr1(pkt) 

Scapy将在发送前用默认值填充其余字段,并计算校验和。

在接收方,编写listener.py,它将侦听并显示所有传入的ICMP数据包。

 from scapy.all import * #    # filter --  icmp # timeout --   10  # count --    100  # iface --    eth1 packets = sniff(filter = "icmp", timeout = 10, count = 100, iface = "eth1") #      for pkt in packets: #     echo-request if pkt[ICMP].type != 8: continue #    pkt.show() 

侦听器输出
 ###[ Ethernet ]### dst = hh:hh:hh:hh:hh:hh src = gg:gg:gg:gg:gg:gg type = 0x800 ###[ IP ]### version = 4 ihl = 5 tos = 0x0 len = 28 id = 24923 flags = frag = 0 ttl = 64 proto = icmp chksum = 0x4364 src = 10.0.0.1 dst = 10.0.0.2 \options \ ###[ ICMP ]### type = echo-request code = 0 chksum = 0xf7ff id = 0x0 seq = 0x0 ###[ Padding ]### load = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 


IP数据包头具有一个标识符字段。 用符号“ A”和“ B”填充它:

 payload = ord("A") * 0x100 + ord("B") pkt = IP(src="10.0.0.1", dst="10.0.0.2", id = payload) / ICMP(type = 8) 

此外,在ICMP头中,有一个完全相同的字段,也可以将两个字节装入该字段。

更改侦听器以显示接收到的数据:

 from scapy.all import * import sys packets = sniff(filter="icmp", timeout = 10, count = 100, iface="eth0") for pkt in packets: if pkt[ICMP].type != 8: continue #    a, b = divmod(pkt[IP].id, 0x100) sys.stdout.write(chr(a)) sys.stdout.write(chr(b)) sys.stdout.flush() 

在图像和相似度中,您可以填充几乎以前提到的适合缝的任何字段。

Ping:困难的选择


上一段中的数据传输不是最明显的,但是我们可以使其变得更加不明显。 您可以将数据隐藏在校验和字段中。 根据RFC1071,校验和(突然!)是稍微复杂一点的算术和的按位求逆。

举例说明
假设我们有一个要为其计算校验和的标头。 在计算时,校验和字段被重置。

 4500 003c 000a 0000 8001 [checksum] c0a8 000d c0a8 000d 

1.将所有16位字加起来,记住从高位开始的传输:

 4500 + 003c + 000a + 0000 + 8001 + [checksum=0000] + c0a8 + 000d + c0a8 + 000e = = (2) 46b2 

2.将结果与转移相加:

 46b2 + 2 = 46b4 

3.反转:

 ~(46b4) = b94b 

b94b是我们要查找的校验和。 为了进行验证,您可以替换标题并执行步骤1和2。如果得到FFFF,则发现的数量正确。

验证:

 1. 4500 + 003c + 000a + 0000 + 8001 + [checksum=b94b] + c0a8 + 000d + c0a8 + 000e = = (2) FFFD 2. FFFD + 2 = FFFF 


我们知道,随着TTL的变化,数据包的校验和会随着节点通过网络的变化而变化。 此外,当通过数据包中的NAT时,“源地址”将被替换,这也会影响校验和。 到达我们的听众后,TTL会减少多少……麻烦的是,“标识符”的位数与校验和的位数重合。 这个事实使我们能够影响校验和并将其更改为定义区域中的任何值。 由于仅在通过路由中的最后一个节点时才计算校验和(有效载荷),因此在计算过程中考虑路由中包中可能更改的所有内容非常重要。

查找“标识符”的算法,它将为我们提供所需的校验和:

  1. 我们将数据包配置为通过最后一个节点(IP,TTL等)时
  2. 在“标识符”中写入有效载荷
  3. 我们计算校验和
  4. 结果必须写在已发送数据包的“标识符”中

我们将编写一个函数,该函数将根据希望的数量,位于NAT之后的IP和两个字节的有效负载来形成一个数据包。

 # src -   # src_nat -    NAT # dst -   # dttl -       # a, b --      def send_stegano(src, src_nat, dst, dttl, a, b): #       payload = ord(a)*0x100 + ord(b) #         pkt = IP(dst=dst, src=src_nat, ttl=64-dttl, id = payload) / ICMP(type=8) #  Scapy  chksum pkt = IP(raw(pkt)) #     pkt[IP].src = src pkt[IP].ttl = 64 pkt[IP].id = pkt[IP].chksum #   chksum,  Scapy   del pkt[IP].chksum # Scapy      pkt = IP(raw(pkt)) #      sr1(pkt) 

有改善吗?


  • ICMP协议标头中的字段chksum,seq,id也可用于传输数据
  • ToS可用于“从其自身”识别包,而忽略其他人的回声请求。

Source: https://habr.com/ru/post/zh-CN413851/


All Articles