コース目次 / 第2章
自分だけの、ミニtcpdumpを書く
AF_PACKETソケットで、loopback上を流れる生のパケットをPythonで捕まえます。Ethernet/IP/TCPのヘッダーをmini-ctf-forensicsと同じ要領でたどり、HTTPのやり取りを生のキャプチャから取り出します。
パケットを、直接捕まえる
sniff.py として保存します(root権限が必要です)。
import socket, struct, sys
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003))
s.bind(('lo', 0))
print("キャプチャ開始", file=sys.stderr)
for _ in range(6):
packet, addr = s.recvfrom(65535)
print(f"=== raw packet, len={len(packet)} ===")
print(packet[:34].hex())
sudo python3 sniff.py &
curl -s http://127.0.0.1:8080/ -o /dev/null
=== raw packet, len=74 ===
00000000000000000000000008004500003ccd91400040066f287f0000017f000001
先頭12バイト(00…00)は、loopback用の疑似的な宛先/送信元MACアドレス(実際のEthernetケーブルは無いので、すべてゼロ)。続く0800がEthertype(IPv4)。その後の45…から、mini-ctf-forensicsの手作りpcapで読んだのとまったく同じIPヘッダーが始まっています。loopback宛の通信も、Ethernet風のヘッダーを持つパケットとして扱われていることが分かります。
構造をたどって、HTTPを取り出す
sniff_http.py として保存します。mini-ctf-forensicsのpcapパーサと、ほぼ同じ構造です。
import socket, struct, sys
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003))
s.bind(('lo', 0))
print("キャプチャ開始。別ターミナルで通信を発生させてください。", file=sys.stderr)
seen = set()
count = 0
while count < 30:
packet, addr = s.recvfrom(65535)
count += 1
eth_type = struct.unpack('>H', packet[12:14])[0]
if eth_type != 0x0800: # IPv4以外は無視
continue
ip_start = 14
ihl = (packet[ip_start] & 0x0f) * 4
proto = packet[ip_start + 9]
if proto != 6: # TCP以外は無視
continue
tcp_start = ip_start + ihl
src_port, dst_port = struct.unpack('>HH', packet[tcp_start:tcp_start+4])
data_offset = (packet[tcp_start + 12] >> 4) * 4
payload = packet[tcp_start + data_offset:]
if payload.startswith(b'GET') or payload.startswith(b'HTTP'):
key = (src_port, dst_port, payload[:20])
if key in seen:
continue
seen.add(key)
print(f"--- {src_port} -> {dst_port} ---")
print(payload.decode(errors='replace').splitlines()[0])
sudo python3 sniff_http.py &
sleep 1
curl -s http://127.0.0.1:8080/ -o /dev/null
--- 46274 -> 8080 ---
GET / HTTP/1.1
--- 8080 -> 46274 ---
HTTP/1.0 200 OK
curl -vで見たのと同じやり取りが、生のパケットのバイト列から復元できました。ポート番号(46274 -> 8080)から、リクエストとレスポンスの向きも読み取れます。tcpdumpやWiresharkは、まさにこれと同じ仕組み(パケットを捕まえて、ヘッダーをたどってペイロードを見せる)を、もっと洗練された形でやっているだけです。
今回HTTPが平文で読めたのは、暗号化されていない(HTTPSではない)通信だからです。もしTLSで暗号化されていれば、ヘッダーの構造は同じように読めても、ペイロード部分は暗号文になり、これでは中身が読めません。sec-csp・sec-authで扱った「通信を暗号化する」ことの意味が、ここでも裏側から見えます。
こうなっていればOK
卒業まであと1章です。
この章はまだ完了していません。
保存できませんでした(プライベートブラウズ中かもしれません)。この端末に進捗は残りませんが、先へは進めます。