1月下旬に行われたSECCON CTFのオンライン予選のネットワーク・Webの200点問題、 "Find the key!" をdpktを使って解くデモをしてきました。
SECCON CTF自体については公式サイト http://2013.seccon.jp/ をご覧ください。
また、問題や解き方については、既に多くの方がWrite upを書いているので、そちらもご覧ください。
この問題は、pcapファイルが渡されて、その中からキーを見つけると言う問題でした。
pcapファイルの中身はPingの通信で、Pingのデータ部分にHTTPの通信がトンネルされていて、その中で kagi.png という画像がGETされていました。要はその画像を復元してやればよいという問題でした。
まずは問題のファイルを開きます。
>>> import dpkt
>>> p=dpkt.pcap.Reader(open("seccon_q1_pcap.pcap","rb"))
Wiresharkでパケットを眺めていると、大体100バイトより大きいパケットに目当てのデータが含まれているコトが分かります。そこで、100バイトより大きいパケットのデータ部分をリストに抜き出します。
>>> pl=[]
>>> for t,b in p.readpkts():
... ip=dpkt.ethernet.Ethernet(b).data
... if ip.len > 100:
... pl.append(ip.data.data.data)
リストの中身を見ていくと、 pl[2]
から pl[6]
に画像が入っていることが分かります。
まずは pl[2]
を見ます。これにはHTTPのレスポンスヘッダも含まれているので、それを取り除いてやります。PNGファイルは先頭1バイトが 0x89 ではじまり、続く3バイトがASCIIでPNGなので、PNGを探してその1バイト前からを抜き出すようにします。
>>> pl[2].index("PNG")
285
>>> PNG=pl[2][284:]
次に pl[3]
から pl[6]
ですが、これをすべて PNG
につなげるとうまくいきません。
PNG画像(の一部)以外の何かが入っています。
kagi.png の中身はレスポンスヘッダの Content-Lengthの値から2795バイトであることが分かっています。
>>> pl[2][216:238]
'Content-Length: 2795\r\n'
それに対し、 pl[3]
から pl[6]
を全部つなげると、上記の値より大きくなってしまいます。
>>> len(PNG)
768
>>> l=len(PNG)
>>> for n in range(3,7):
... l+=len(pl[n])
...
>>> l
2908
というわけで、 pl[3]
から pl[6]
の各パケットにどれだけ余計なのがついてるんだろうか、としらべてみると、28バイト取り除いてあげればよさそうということが分かります。
>>> 2908 - 2795
113
>>> 113 / 4
28
余りが1あるのと、取り除くのは先頭か最後か、は pl[6]
の最後を見ると分かります。
>>> pl[6][-10:]
'\x00IEND\xaeB`\x82\x00'
というわけで、 pl[3-6][28:]
をつなげて、最後の1バイトを取り除いてあげればよいことが分かりました。
285
>>> PNG=pl[2][284:]
>>> for n in range(3,7):
... PNG=PNG+pl[n][28:]
...
>>> len(PNG)
2796
>>> PNG=PNG[:-1]
>>> open("kagi.png","w").write(PNG)
あとは、書き込んだ kagi.png を適当な画像ビューワで開いてあげるとフラグが見えます。
後で気づいたのですが、 pl[2]
で HTTPのレスポンスが始まる位置を調べても、先頭28バイトを取り除いてあげればよいことが分かります。
>>> pl[2][:50]
'\xd5 \x08\x80\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x02\x00\x00\x00\x01\x00\x00\x04\x00\x00\x01\x028HTTP/1.1 200 OK\r\nDate:'
>>> pl[2].index("HTTP")
28