Pythonでパケット解析(dpkt) その2~上位レイヤーの内容の取得~
1の続き
Pythonでパケット解析(dpkt) その1~簡単な読み込み~ - ぐるんぐるんの続き。
今回は、http通信の通信を取るところまで頑張りたいです。
実際にやってみた
上位と下位の関係性
その1でも言ったように、あるレイヤーの小包は、
- 小包=宛名(ヘッダー)+中身(ペイロード)
- 中身=上のレイヤーの小包
になっています。
なおHTTPは、Ethernet->IP->TCP->HTTPとなっています。これを頭に入れて、前回のhttp.capでいろいろ遊んでみたいと思います。
上位レイヤーの判別を行う
前回読み込んだethですが、こいつの中身(eth.data)がIPなのかどうかというのは、typeで判別することが出来ます。なおクラスと判別するので、dpkt.(小文字のプロトコル名).(大文字のプロトコル名)と判別します。
eth = dpkt.ethernet.Ethernet(buf) if type(eth.data) == dpkt.ip.IP: print "IPパケットだよ!!"
パケットは入れ子になっているので、どんどん判別していく。
eth = dpkt.ethernet.Ethernet(buf) if type(eth.data) == dpkt.ip.IP: ip = eth.data print "IPパケットだよ!!" if type(ip.data) == dpkt.tcp.TCP: print "TCPパケットだよ"
なお、HTTPはこれで判別できないようです。
ヘッダの情報を取り出す
小包の中身はとりあえずおいておき、宛名の情報をいろいろ抜き出してみましょう。これには、郵便番号やら「割れ物注意」的な感じでいろんな情報があります。たとえば、宛先のIPやらポートやら、いろいろなフラグがあります。
これらは、普通に参照することで取得することが出来ます。わー便利ー。IPパケットは送信元、受信先のIPアドレスを参照することが出来ます。
eth = dpkt.ethernet.Ethernet(buf) if type(eth.data) == dpkt.ip.IP: ip = eth.data print "IPパケットだよ!!" print "IP: %s -> %s" % (socket.inet_ntoa(ip.src), socket.inet_ntoa(ip.dst))
TCPでは、IP同様に宛先のポートを取得することが出来ます。
eth = dpkt.ethernet.Ethernet(buf) if type(eth.data) == dpkt.ip.IP: ip = eth.data print "IPパケットだよ!!" print "IP: %s -> %s" % (socket.inet_ntoa(ip.src), socket.inet_ntoa(ip.dst)) if type(ip.data) == dpkt.tcp.TCP: print "TCPパケットだよ" tcp = ip.data print "","", "Port: %d -> %d" % (tcp.sport, tcp.dport)
HTTPが取れない
HTTPには、リクエストとレスポンスが存在する。リクエストは通常、クライアント→サーバーの通信で、レスポンスはその逆である。
HTTPに関する通信は原則、80番ポートを使う。そのため、TCPパケットで送信または受信ポートが80ポートかどうかで、HTTPかどうか、またリクエストかレスポンスかを判別した。また、headerはheadersで取得できる…ということである。
#HTTP Requestかの判定 if tcp.dport == 80 and len(tcp.data) > 0: httpreq = dpkt.http.Request(tcp.data) print "Rquest:", httpreq.headers #HTTP Responseかの判定 if tcp.sport == 80 and len(tcp.data) > 0: httpres = dpkt.http.Response(tcp.data) print "Response:", httpres.headers
が、見事にエラーをはいてくれた。エラー内容的には、「HTTPパケットは、大きすぎるとTCPレイヤーで分割されて送るんだけれど、それは厳密にはHTTPではないんだよ」的なエラー*1。これをdpktでそのままでは読めない。
暗中模索でやってみた
色々あさってみると、以下の傾向があった。
- IPレイヤーでDon't Fragmentフラグが立ってる
- TCPレイヤーでPUSHフラグが立ってない
#HTTP Requestかの判定 if tcp.dport == 80 and len(tcp.data) > 0: httpreq = dpkt.http.Request(tcp.data) print "Rquest:", httpreq.headers #HTTP Responseかの判定 if tcp.sport == 80 and len(tcp.data) > 0 and (dpkt.ip.IP_DF & ip.off) == 0: httpres = dpkt.http.Response(tcp.data) print "Response:", httpres.headers
しめたーと思ってやってみましたが、無理でした。どうしたらいいんでしょう、これ。
コード(全体)
# -*- coding: utf-8 -*- from __future__ import unicode_literals from __future__ import with_statement import dpkt import socket with open("http.cap", "rb") as f: pcr = dpkt.pcap.Reader(f) count = 0 for ts, buf in pcr: count += 1 print "Packet %d:%d" % (count, ts) try: eth = dpkt.ethernet.Ethernet(buf) except: print "Error: Packet No.%d" % count #IPパケットかの判定 if type(eth.data) == dpkt.ip.IP: ip = eth.data print "", "IP: %s -> %s" % (socket.inet_ntoa(ip.src), socket.inet_ntoa(ip.dst)) #TCPパケットかの判定 if type(ip.data) == dpkt.tcp.TCP: tcp = ip.data print "","", "Port: %d -> %d" % (tcp.sport, tcp.dport) #HTTP Requestかの判定 if tcp.dport == 80 and len(tcp.data) > 0: httpreq = dpkt.http.Request(tcp.data) print "", "", "Rquest:", httpreq.headers #HTTP Responseかの判定 if tcp.sport == 80 and len(tcp.data) > 0 and (dpkt.ip.IP_DF & ip.off) == 0: httpres = dpkt.http.Response(tcp.data) print "", "", "Response:", httpres.headers
*1:厳密には、「TCP segment of a reassembled PDU」