Steganographie in IP-Paketen

Bevor sie eine andere Laborarbeit verteidigten, stellten sie mir einmal eine Frage: Welche Felder eines IP-Pakets können zum Quilten verwendet werden? Ich wusste es nicht und zuckte nur die Achseln. Aber bald entschied ich mich immer noch, dieses Thema zu studieren.

Unter dem Schnitt finden Sie die Studie über IP-Paket-Header, Pythons eigenes Ping-Dienstprogramm und verschiedene Möglichkeiten, Daten zu übertragen, ohne Aufmerksamkeit zu erregen.

Inhalt


  1. IP-Paketstruktur
  2. Umgebungseinstellung
  3. Ping: Einfache Option
  4. Ping: Schwierige Option
  5. Verbesserungen?

IPv4-Paketstruktur




Wählen Sie die Felder aus, deren Änderung das Paket nicht wesentlich beeinflusst:

IHL kann von 5 bis 15 variieren.
Das ToS- Feld wird verwendet, um Verkehrs- und Überlastungsbenachrichtigungen zu priorisieren, ohne Pakete zu verwerfen. Meistens ist dieses Feld 0. Theoretisch kann es verwendet werden, um ein ganzes Byte an Informationen zu übertragen.
Die Länge des Pakets ist ein ausgezeichnetes Feld für die Übertragung von Nummern von 20 bis 65535.
TTL kann bis zu 7 Informationsbits übertragen. Sie müssen die Anzahl der Hops zum Host kennen und dies berücksichtigen.

Umgebungseinstellung


Um das Experiment zu wiederholen, benötigen Sie zwei Maschinen mit Python und dem Scapy-Framework.

Sie können es installieren, indem Sie den Anweisungen in der Dokumentation folgen. In meinem Fall waren dies zwei Tröpfchen auf DO mit eingeschaltetem lokalen Netzwerk. Um die Funktionsfähigkeit des Steganos zu testen, wurden zwei Routen ausgewählt: über das lokale Netzwerk für 1 Hop und über das Internet für 2 Hop.

Ping: Einfache Option


Zuerst implementieren wir sender.py, das ICMP-Pakete ohne versteckte Nachrichten sendet.

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 füllt die verbleibenden Felder vor dem Senden mit Standardwerten aus und berechnet die Prüfsumme.

Schreiben Sie auf der Empfangsseite listener.py, das alle eingehenden ICMP-Pakete abhört und anzeigt.

 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() 

Listener-Ausgabe
 ###[ 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' 


Der IP-Paket-Header hat ein Identifizierungsfeld. Füllen Sie es mit den Symbolen "A" und "B":

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

Darüber hinaus gibt es im ICMP-Header genau das gleiche Feld, in das auch zwei Bytes geladen werden können.

Ändern Sie den Listener, um die empfangenen Daten anzuzeigen:

 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() 

Im Bild und in der Abbildung können Sie fast jedes Feld ausfüllen, das zuvor als zum Quilten geeignet eingestuft wurde.

Ping: Schwierige Option


Die Übertragung von Daten aus dem vorherigen Absatz war nicht die offensichtlichste, aber wir können sie noch offensichtlicher machen. Sie können die Daten im Feld für die Prüfsumme ausblenden. Nach RFC1071 ist eine Prüfsumme (plötzlich!) Eine bitweise Inversion einer etwas komplexeren arithmetischen Summe.

Erklärung mit einem Beispiel
Angenommen, wir haben einen Header, für den wir die Prüfsumme berechnen möchten. Zum Zeitpunkt der Berechnung wird das Prüfsummenfeld zurückgesetzt.

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

1. Addieren Sie alle 16-Bit-Wörter und merken Sie sich die Übertragung von der höheren Ordnung:

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

2. Addieren Sie das Ergebnis mit Übertragungen:

 46b2 + 2 = 46b4 

3. Invertieren:

 ~(46b4) = b94b 

b94b ist die Prüfsumme, nach der wir suchen . Zur Überprüfung können Sie in der Kopfzeile ersetzen und die Schritte 1 und 2 ausführen. Wenn Sie FFFF erhalten, ist der gefundene Betrag korrekt.

Überprüfung:

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


Wir wissen, dass sich die Prüfsumme eines Pakets ändert, wenn Knoten das Netzwerk passieren, wenn sich die TTL ändert. Außerdem wird beim Durchlaufen von NAT im Paket die „Quelladresse“ ersetzt, was sich auch auf die Prüfsumme auswirkt. Und wie viel TTL wird abnehmen, wenn wir unseren Hörer erreichen ... Die Kirsche auf dem Kuchen ist, dass die Bitterkeit des "Bezeichners" mit der Bitterkeit der Prüfsumme übereinstimmt. Diese Tatsache ermöglicht es uns, die Prüfsumme zu beeinflussen und in einen beliebigen Wert aus dem Definitionsbereich zu ändern. Da die Prüfsumme (Nutzlast) nur beim Passieren des letzten Knotens in der Route berechnet wird, ist es wichtig, bei der Berechnung alles zu berücksichtigen, was während der Route im Paket geändert werden kann.

Der Algorithmus zum Finden des "Bezeichners", der uns die gewünschte Prüfsumme gibt:

  1. Wir konfigurieren das Paket so, als ob es den letzten Knoten passiert (IP, TTL usw.)
  2. Schreiben Sie in die "Kennung" die Nutzdaten
  3. Wir berechnen die Prüfsumme
  4. Das Ergebnis muss in die "Kennung" des gesendeten Pakets geschrieben werden

Wir werden eine Funktion schreiben, die ein Paket aus der Anzahl der Hoffnungen, IPs hinter NAT und zwei Bytes Nutzlast bildet.

 # 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) 

Verbesserungen?


  • Die Felder chksum, seq, id im Header des ICMP-Protokolls können auch zur Datenübertragung verwendet werden
  • ToS kann verwendet werden, um Pakete "von ihren eigenen" zu identifizieren und die Echoanforderung anderer Leute zu ignorieren.

Source: https://habr.com/ru/post/de413851/


All Articles