トリコロールな猫/セキュリティトリコロールな猫/セキュリティ読者になるトリコロールな猫/セキュリティ思いついたことをぼちぼち書いてます。「デジタル・フォレンジック研究会」追加】2015年に公開した以下の記事、そろそろ全体的に更新したくなったので新たに記事を作成しました。security.nekotricolor.comリンク切れの修正が主ですが、多少追加・削除しています。一覧セキュリティベンダーセキュリティベンダーのうち、注意喚起やセキュリティ関連情報を公開しているところをピックアップ。ブログ(脅威調査)その他団体セキュリティを銘打っているわけではないけど、チェックしておいた方がいいサイト。News脆弱性情報(全分野)網羅的に公開しているもの。Notes(RSS)脆弱性情報(ベンダ・開発者)ベンダーのセキュリティアドバイザリやパッチの情報など。英語版が先に出て日本語版が後から公開されるベンダは、英語日本語両方のページを載せています。PHP国内のニュースサイトスラドのセキュリティはてブの「セキュリティ」タグ日経クロステック(「セキュリティ」で検索)ScanNetSecurity(RSS)海外のニュースサイト数が多いのでRSSはご勘弁を。読者になる2020-02-20pwnable.twのorwのwrite-up。アセンブラ前回の「start」のwrite-upに思いの外アクセスがあって嬉しかったので、調子に乗って早々に次の問題もやってみました。スコア100の「orw」です。pwnable.tw前回のwrite-upは以下。security.nekotricolor.com以降、MBAのコマンドプロンプトは「%」、DockerのUbuntuは「#」です。方針challenges/のorwのページにアクセスすると、以下のようなことが書いてあります。use.ということで、/home/orw/flagをopenし、readして標準出力かなにかにwriteすればフラグが分かると思われます。問題に書かれているものをとりあえずやってみる問題のページに書かれているncコマンドを実行してみます。"me"では?」という気がしますが気にせずリターンキーを押してみるとプロンプトに戻ります。startと同じく、標準入力になんらかの文字列を入れるパターンのようです。fileコマンド問題のページからorwをダウンロードして、fileコマンドでなんのファイルなのかを見てみます。stripped実行ファイルのようです。stringsコマンドファイルサイズが前回より大きく、たくさん出ちゃったので途中は略。有益な情報はなかったです。今回も実行ファイルですので、実行してみます。orwはlibc6-i386がインストールされていない環境だと動かないようです。shellcode:最初にncコマンドで接続したときと同じ文字列が出て待ち状態になります。chall.pwnable.twの10001番ポートに接続するとこのorwが実行されるのでしょう。文字列が入力できるときは、とりあえず大量に「a」を入力してみます。fault異常終了しました。「たくさん文字を入れると異常終了する」ということが分かりました。gdb上で実行する前回と同じく、せっかく異常終了したのでgdb上で実行して大量の文字列を入力してみます。なんとびっくり入力した文字列がそのまま実行されています。*1文字列の長さは関係ないですね。文字列を入力、というかただリターンするだけでも異常終了すると。するシェルコードを標準入力に与えればいいわけですね。シェルコードを書く脆弱性をつくわけじゃないので「シェルコード」ではないのかな?特定のファイルをopen/read/writeするアセンブリコードです。onlyで)openするには、以下のようなシステムコールを実行します。0x80でシステムコールを呼び出す場合、システムコールの番号をeaxに、引数をebxから順番に設定する必要があります。blog.ishikawa.techとなると、以下のようなシェルコードを書く必要があります。0x80でシステムコールを呼び出すただし、「\x00」が途中に入るとプログラムが終了してしまうためそこは一工夫。pwntoolsのshellcraftを参考にしました。documentationこれの「pwnlib.shellcraft.i386.linux.syscall」の項に、以下のような記述があります。0x800x1010101と0x1016660のxorは「0x00006761」、リトルエンディアンを考慮しつつ文字列に変換すると「ag\x00\x00」。/home/pwn/flag\x00の最後の4バイトになります。文字数がちょうど一緒なので、上記の/home/pwn/flagの「pwn」の部分を「orw」にすればこのまま流用できます。実際のアセンブリコードは以下の通りです。0x80ちなみに、前回参考にしたシェルコードの書き方の記事では、「db」という、数値を1バイトのデータとしてメモリに格納する疑似命令を使っていました。文字列が長い場合にはこっちの方が楽ですねたぶん。book.mynavi.jpopenシステムコールの戻り値はファイルディスクリプタで、eaxにセットされます。ファイルディスクリプタは、0が標準入力、1が標準出力、2が標準エラー出力で、openシステムコールによるファイルディスクリプタは3から順に割り当てられるようです。qiita.comreadするファイルディスクリプタを指定し、64バイト分readしてスタック領域に書き込むには、以下のようなシステムコールを実行します。64)となると、以下のようなアセンブリコードを書く必要があります。0x80でシステムコールを呼び出す実際のアセンブリコードは以下の通りです。0x80writeする標準出力にespの指す先を64バイト分writeするには、以下のようなシステムコールを実行します。64)となると、以下のようなアセンブリコードを書く必要があります。0x80でシステムコールを呼び出す実際のアセンブリコードは以下の通りです。0x80書いたアセンブリコードを実行してみる先ほどのアセンブリコードをまとめてshellcode.sとします。分かりやすくするよう、ラベル(_start:とかopen:とか)をつけ、_exitとして終了処理を実行していますが、なくても大丈夫なはず。終了処理は、exitシステムコール(番号は「1」)を実行するだけです。0x80これのバイナリコードをターゲットホストに送信すればフラグが取れるのですが、せっかくなのでローカルホスト上に/home/orw/flagを作成し、ファイルが読めるかやってみます。生のアセンブリコードを実行できるようにする方法は前回参考にさせていただいたブログに答えが書かれています。いうかこの記事って書籍の一部なんですよね。何度も引用させていただいているので書籍が何かも載せておきます。単行本(ソフトカバー)アセンブリコードを実行ファイルに変換するには、以下のコマンドを実行します。shellcode.oこれでa.outという実行ファイルができたので、実行してみます。flagの中身は「a」の羅列にしました。./a.outaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?o???w???????できました。後ろに変な文字列が入っているのは、スタック領域を(flagのサイズを超えて)64バイト分読み込んでいるからです。(おまけ)シェルコードを短くするレジスタに数値をセットするのにmovではなくpushとpopを使っているのは、その方がコードが短くなるからです。0x1pushとpopだと3バイト。eaxシェルコードはものによっては書き込める領域がかなり小さかったりするので、短いに越したことはありません。今回はあまり考えなくても良さそうですが。Flag実行ファイルからバイナリコードを抽出するこれもどーすれば?と思いますよね。実行ファイルはできているので、gdbやnasmで逆アセンブルし、該当部分をコピペして地道に「\x」をつけてもいいんですが、それをやってくれる素晴らしいワンライナーを見つけたのでご紹介。objdumpでできるそうです。qiita.com実行した結果が以下。's/(\w{2})\s+/\\x\1/g'\xeb\x00\x68\x01\x01\x01\x01\x81\x34\x24\x60\x66\x01\x01(略)バイナリコードをターゲットホストに送信するスクリプトを書いちゃうと答えになっちゃうので書きませんが、送信する部分は前回のスクリプトと同じなのでそちらを参照ください。実行すると答えが出ちゃうのでそれも載せませんが、せっかくなので、/etc/passwdを読んでみた結果をどうぞ。64バイトしか読んでないので途中で切れてます。root:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/uなお、実際のスクリプトはpwnable.twのwrite-upのページに載せています。クリアした人のみ閲覧可能です。脆弱性をどうこうするのではなく、制限されたシステムコールによってフラグのファイルを閲覧する、という、製品のexploitコードを書くときにはありえないシチュエーションが面白かったです。pwntoolsのシェルコード生成のライブラリにこのへんのシステムコールを実行する機能があるということは、割とポピュラーなやり方なんでしょうか?この問題は脆弱性だけでなく、ASLRとかDEPとかも考える必要がなく、純粋にシェルコードを書くことに特化しているのがとても良かったです。シェルコードの書き方やそれを実行する方法、バイナリコードへの変換など、これまたいい勉強になりました。関連記事security.nekotricolor.comsecurity.nekotricolor.com*1:正確には、実行しようとして異常終了しています。*2:64バイトじゃなくても構わないですが、長すぎるとうまく動きません。読者になる2020-02-19pwnable.tw攻略のためのDockerイメージ作り〜orw編pwnablepwnable.twの「orw」のDockerイメージについて。write-upは以下。security.nekotricolor.comDockerfile前回はいろんなことをやったのでruby環境をrbenvから作りましたが、今回は難しいことはしていないのでごくごくシンプルにしました。orwはlibc6-i386が入っていないと動かないようなのでそこだけ注意。シェルコード作成に使うnasmも入れています。Dockerfile.orwという名前で以下を作成します。libc6-i386イメージを作成するpwnabletwという名前で、タグを「orw」としました。.orwを実行するシェルスクリプトを作成するホストOS上のどこか(ここでは/Users/nekotricolor/share/orw)に、start.shとして以下のシェルスクリプトを作成します。/root/share/orw/orwは問題のバイナリファイルです。/root/share/orw/orw;doneDockerコンテナを実行するプロセスにアタッチできるようにするオプションをつけ、/Users/nekotricolor/shareを/root/shareにマウントしつつ、コンテナを実行します。コンテナ上でorwを実行するシェルスクリプトを実行する/Users/nekotricolor/shareを/root/shareにマウントしたので、先ほど作ったstart.shはコンテナ上でも使えるようになっています。これを実行すると、10001番ポートがListen状態になり、接続するとorwが実行されます。/root/share/orw/start.shコンテナに入り、10001番ポートに接続してみるホストOSの別のターミナルからコンテナ内に入ります。上記のroot@以下がコンテナのIDです。bashncコマンドで10001番ポートに接続してみます。これで、問題を解く環境が整いました。startのwrite-up。アセンブラpwnable.twのスコア100の問題、「start」のwrite-upです。Girlsのバイナリ解析の講師をやったときに、講義後に実習ということで簡単な問題をやってもらったのですが、まず何をしていいのか分からないという人が結構いて、その辺を説明しなかったことをずっと後悔していたので、だいぶ詳細に解説してみました。問題自体は基本をおさえたシンプルなもので、バイナリ解析の勉強にはとてもいいのではないかと思います。諸々思い出すためにも、pwn用ツールは使わずゆっくりじっくりやっています。検証環境はVirtualBoxでも実機でもなんでも構いません。私は母艦のMBAが不安定なのでこれを機にDockerイメージを作ってみました。security.nekotricolor.com以降、MBAのコマンドプロンプトは「%」、Ubuntuは「#」です。Flagのことで、与えられた問題を解いてフラグを取得する競技です」といわれますが、そもそもフラグってなんなのか、私は最初見当がつきませんでした。結論から言うと、基本的にフラグは文字列です。willian」という文字列です。/home/xxx/flag」と書かれていますので、通常はログインしたユーザのホームディレクトリにある、「flag」というファイルに書かれている文字列がフラグということになります。問題に書かれているものをとりあえずやってみるchallenges/のstartをクリックすると、以下のような画面が出ます。書いてあるコマンドを実行してみます。CTF:」という文字列が出て待ち状態になり、リターンを押すと接続が切れます。ここになにか文字列を入れればいいらしい、と予想がつきます。startファイルをダウンロード上記の画面から「start」がダウンロードできます。ホストとDockerコンテナで共有しているフォルダに置いて、このファイルをDocker上で調べてみます。fileコマンドfileコマンドで、このファイルがなんなのかを確認。stripped実行ファイルのようです。stringsコマンドフラグの文字列がまんま入ってる可能性も無きにしも非ずなので、stringsコマンドで、ファイル内の表示可能な文字列を表示してみます。ファイルサイズが大きいとブワッと出てしまいますが、このファイルは564バイトしかないので安心。sthLet'start.s_exit__bss_start_edata_end.symtab.strtab.shstrtab.text残念ながらフラグはありませんでしたが、__bss_startとか.textとかでてるので、実行ファイルで確定のようですね。提供されたファイルが何か、で次に何するかを決めます。実行ファイルなら実行する画像ファイルならブラウザ等で表示するtcpdumpの結果ならWireshark等で開くという感じです。今回は実行ファイルですので、実行してみます。CTF:最初にncコマンドで接続したときと同じ文字列が出て待ち状態になります。chall.pwnable.twの10000番ポートに接続するとこのstartが実行されるのでしょう。文字列が入力できるときは、とりあえず大量に「a」を入力してみます。dumped)異常終了しました。「たくさん文字を入れると異常終了する」ということが分かりました。Exploitここから難しくなってきます。デバッグの方法やレジスタ・スタック等の基本については、手前味噌な上に未完ですが、こちらを参照していただければ幸いです。gdbではなくOllyDbgというWindowsのGUIのツールを使って説明しています。こちらの方がアセンブリコードやレジスタなどの仕組みそのものを知るには分かりやすいと思います。security.nekotricolor.comgdb上で実行するすぐにファイルをdumpしてアセンブリコードを読んでもいいのですが、せっかく異常終了したので先にgdb上で実行して大量の文字列を入力してみます。()あらやだいきなりeipが書き換わってる。バッファオーバーフローですね。どこの部分がeipになっているか、古典的方法で確認します。()0x6e6d6c6bということは、「nmlk」の部分、21文字目からの4バイトですね。(リトルエンディアンなので、表示が逆になっています)objdumpによる逆アセンブルstartが何をやっているかは、objdumpコマンドで見ることができます。長いとここで挫折しますが、startはとても短いので読む気になります。読みやすくするよう行番号を付けました。$0x80逆アセンブルした結果を読む_start関数を読んでいきます。_exit関数は単にexitするだけみたいなので割愛。01行目:この時点でのespの値をスタックに格納。02行目:_exitの先頭アドレスを格納。03〜06行目:eax、ebx、ecx、edxを0x00に。CTF:」という文字列をスタックに格納12行目:この時点でのespの値をecxに格納13行目:edxに0x14を格納14行目:ebxに0x01を格納15行目:eaxに0x04を格納16行目:call命令を実行。何をどう実行するかはレジスタの値で決まる。ここでは、標準出力に(ebxが0x01)、ecx(12行目によりespと同値)を先頭アドレスとした文字列を、20バイト(edxが0x14=20)write(eaxが0x04)する。CTF:」をwriteする。17行目:ebxを0x00に。18行目:edxに0x3cを格納。19行目:eaxに0x03を格納。20行目:call命令を実行。ここでは、標準入力から(ebxが0x00)、60バイト(edxが0x3c=60)read(eaxが0x03)するという意味。21行目:espに0x14を加える。22行目:retする。CTF:」と表示し、標準入力から60バイト読み込み、終了している、ということが分かります。たいしたことはやっていません。バッファオーバーフローはどこで起こるのかスタックの状態を図にすると以下のようになります。20バイト分しか書き込める場所がないのに、read()で60バイト読み込んでいるところが脆弱性です。read()した瞬間、緑色の部分が標準入力された文字列に書き換わります。入力が20バイト以内なら何も起きませんが、20バイト以上になると22行目でretしたときにジャンプする先のアドレス(0x0804809d、つまり_exit関数の先頭アドレス)が書き換わってしまいます。faultしてしまうというわけです。以下、eipが書き換わる瞬間を捉えたgdbです。0x00シェルコードを書くシェルコードと一言で言ってもいろいろなものがあるわけですが、今回は最もシンプルな、/bin/shを起動するものを使います。/bin/shを起動することで、以降任意のコマンドが実行できます。/bin/shを起動するには、以下のシステムコールを実行します。引数の"/bin/sh"はスタックに入れてそこのアドレスをebxに・・・書くのが面倒なので以下のサイトを参考にさせていただきました。コードを短くするためにecxとedxに0x00をmovしているところはxorに変えました。CTF:」と表示された後に書き込めば良い、ということになります。図にするとこういう感じです。シェルコードの先頭アドレスとはどこなのか?ここは結構考えさせられました。Ubuntuでは、アドレス空間配置のランダム化、ASLRがデフォルトで有効になっています。スタック領域の配置が、startが実行されるごとに大きく変わってしまうため、スタック領域に書き込んだシェルコードの先頭アドレスがどこなのか、事前には分かりません。いうことで、_start関数を実行中に、スタック領域のアドレス、espなりebpなりを取ってこなければなりません。そこで注目したのは、_start関数の01行目、なぜかespをpushしているんですよね。普通、関数が始まる時というのはebpをpushするものなのになんでだろう?と思ったのですが、これとwrite()を組み合わせることで、標準出力にespを表示させることができるのです!どういうことかというと:たとえば、20個の「a」を入力した場合、retが実行された時点でのスタックの状態は以下のようになります。retはpopしてスタックから値を取り出し、その値にjmpする命令です。retが実行されると(popにより)espが4バイト下がって、start開始時のespが入っているところを指します。ここで思い出すのはアセンブリコードの16行目。16行目:call命令を実行。何をどう実行するかはレジスタの値で決まる。ここでは、標準出力に(ebxが0x01)、ecx(12行目によりespと同値)を先頭アドレスとした文字列を、20バイト分(edxが0x14=20)write(eaxが0x04)する。分かりづらいんですが、上図のように、espが、start開始時のespが入っているところを指しているときに12行目〜16行目を実行すれば、start開始時のespがwrite()されることになります。シェルコードを与えれば、シェルコードが実行されるはずです。Flagさて、いよいよリモートホスト上で実行されるstartに前述の文字列を与えていくわけですが、ここからはexploitとはまた少し違う問題があります。標準入力の特定の部分に、アドレスやシェルコードをどうやって書くか。これも初めてやるとき戸惑いませんでした?人の手では書くことはできませんよね。「b80b0000・・」とか手で入力しても文字として認識されるだけですので。そこで、バイナリを含む文字列を送信するようなスクリプトを書くことになります。CTF:」という文字列が表示された後に、前述したアドレスやシェルコードを含む文字列を送信する必要があります。いうスクリプトをRubyで書きます。Pythonの方が書きやすいと思いますしCでもPerlでもなんでもOKです。Docker環境の中で、自身の10000番ポートに接続して文字列を入力するRubyスクリプトを書いてみました。"\n")これを実行すると、ローカルホストの10000番ポートに接続し、返答を受け取り、「aaaaaaaaaaaa」という文字列を送信し、返答を受け取る、ことができます。リモートホストからの返信には「>>」を、こちらからの送信には「<<」を先頭につけています。このRubyスクリプトを実行すると以下のような結果になります。12行目のアドレス(0x08048087)を送信します。受信した最初の4バイトがstart開始時のespです。packとかunpackとかほんとハマったんですけど、そこは本筋と関係ないので割愛します。"\n")これを実行してみると、今回のstart開始時のespは0xffb94ca0であることがわかります。シェルコードという文字列を作って送信するスクリプトを追加します。スクリプトはずばり答えになっちゃうので載せませんが(pwnable.twのwrite-upにあります。クリアした人のみ読めるページです)、これで、任意のコマンドが実行できるようになりました。-aコマンドを実行した結果を書いておきます。GNU/Linux(おまけ)プロセスにアタッチしてみるスクリプトの途中にsleep()を入れ、別のターミナルでgdbでプロセスにアタッチすると、start実行途中のレジスタやスタックの状態がどうなっているか見られます。自分の想定している通りの文字列が送信されているか、シェルコードの先頭アドレスは正しいか、など確認することができます。-aefstartのプロセス番号は「1217」ですね。[プロセス番号]をつけます。()フラグをゲット接続先をlocalhostからターゲットホストに変更して実行すれば、ターゲットホスト上で任意のコマンドが実行可能になります。フラグのファイルを探し、catコマンドなり何なりで中を見ればフラグが書いてあります。「あれ・・ステップ実行って"s"じゃなかったっけ・・あああ"si"いいいいいい」「こういうときってどーすんだっけ・・・はっアタッチ・・sleepしてアタッチや!」「あれっなんか想定とシェルコードが違う・・リトルエンディアンでしたあああああ」とか大変楽しくリハビリできました。私がexploitコードを本気で書いていたのは20年近く前ですが、今回の問題はその頃の知識をそのまま使えた上に、ASLRというちょっとしたエッセンスが追加されていて(当時はなかった)、リハビリにはぴったりなものでした。eipが0x61616161とか0x90909090(パディングによく使われるNOP)とかに書き換わったときにYES!ってなる感じ、久々に味わったなー。Studio+VCで頑張ってたんですよねー。SPARCは固定長命令とビッグエンディアンで分かりやすくて美しかったなぁ・・。懐古ついでにこちらも紹介。今回のスタックベースのバッファオーバーフローの仕組みについて丁寧に解説されています。素晴らしい日本語訳が、「趣味と実益のスタック破壊」として公開されています。(残念ながらもうアーカイブしか残っていないようです)にしても、当時はリモートでもローカルでも自社内に実機で環境を作ってやっていたので、シェルコードを含むパケットをインターネット越しに投げたのはたぶん生まれて初めてなんじゃないかと・・。いやもちろんそのためのサイトなのでなんの問題もないのですが、ちょっと緊張しました。今回は心赴くままにガッツリと検証してあれこれ試しましたが、次回以降はpwn用のツールもいろいろと使ってみようと思います。調子に乗って次の問題「orw」のwrite-upも書きました。security.nekotricolor.com*1:でもさすがにgdb-pedaくらいは使った方がいいかも*2:いきなりpwnable.twのターゲットホストに接続してもいいのですが、インターネット上にmalっぽいパケットを投げまくるのに躊躇いがあり。読者になる2020-02-13pwnable.tw攻略のためのDockerイメージ作りpwnablepwnable.twの「start」をやってみたんですよ。security.nekotricolor.com母艦であるMBAが不安定でいつまたmacOSを再インストールすることになるか分からないので、以下のサイトを参考にDockerでイメージを作っておくことにしました。github.comDockerfilegdbやfileコマンド、vimなどの基本的なツールのほか、exploitコードを書くための環境もインストール。セキュリティといえばPythonですが、私はRubyistなのでRuby環境です。こちらを参考にさせていただきました。qiita.comDockerfile.startという名前で以下を作成します。2.0イメージを作成するpwnabletwという名前で、タグを「start」としました。.startを実行するシェルスクリプトを作成するホストOS上のどこか(ここでは/Users/nekotricolor/share)に、start.shとして以下のシェルスクリプトを作成します。/root/share/startは問題のバイナリファイルです。/root/share/start;doneDockerコンテナを実行するプロセスにアタッチできるようにするオプションをつけ、/Users/nekotricolor/shareを/root/shareにマウントしつつ、コンテナを実行します。コンテナ上でstartを実行するシェルスクリプトを実行する/Users/nekotricolor/shareを/root/shareにマウントしたので、先ほど作ったstart.shはコンテナ上でも使えるようになっています。これを実行すると、10000番ポートがListen状態になり、接続するとstartが実行されます。/root/share/start.shコンテナに入り、10000番ポートに接続してみるホストOSの別のターミナルからコンテナ内に入ります。上記のroot@以下がコンテナのIDです。bashncコマンドで10000番ポートに接続してみます。CTF:これで、問題を解く環境が整いました。読者になる2020-01-072019年に公開されたセキュリティ関連文書まとめリアルタイムには情報を追っておらず、お知らせ一覧等から調べているため抜けがあるかもしれません。公共性の高いものを載せていますWGや研究会の純粋な活動報告書、個別のインシデント・脆弱性は載せていません情報源はこの辺。ブログを報告する引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。