つけじょにーのすぱげていコード

主に、競技プログラミング、セキュリティのお勉強の際に書いたすぱげていコードを書き込みます

セキュキャンの応募用紙

この間、セキュリティキャンプに応募したのですが、なんとか選考を通過することができました!
私は今年22歳になったばかりで、これがラストチャンスだったのですが、なんとか拾ってもらえて本当に良かった・・・
今年のキャンプに参加される方々の中でも知識・技術力ともに一番無いので、必死に食らいついて、キャンプでしか得られない宝物持ち帰ってやる!!という意気込みで望みます。

参考になるのか、大分怪しいのですが、私が書いた応募用紙を晒そうと思います。
共通問題には軽く触れて、選択問題を全部晒そうかと思います。

念のため、先に言いますが、選択問題の解答が間違っている可能性があります。
その点だけ、お気をつけください
あと、温かい目で見ていただけると嬉しいです・・・

選択問題3について、UNIX V6カーネルの知識も少し使っているので、Linuxカーネルでの挙動とは異なるかと思います。

                          • -

共通問題1-1

私が作ったSlackのボットと、Jonny言語について書きました。
また、製作の際に工夫した点も書きました。
あと、私は作った作品が少ないため、普段読んでいる技術書の中でも、私のお気に入りについての熱い思いを書きました。

共通問題1-2

私が作った作品で用いられている言語やライブラリを書きました。

共通問題1-3

ブログ(ここ)やQiita、GithubなどのURLを書きました

共通問題2-1

競技プログラミングで苦悩していたお話を書きました。

共通問題2-2

自分が、情報セキュリティをきっかけに幅広い分野を学び、楽しく勉強できるようになったこと、それによってそれまで以上に成長できたことなどを書きました。

共通問題2-3

無理せず、好きなことをやっていくのが良いということ
広い分野に目を向けてみること
IT技術は結局、どれも、どこかで関連してくること
つらいのに、それでも諦めきれない自分の気持ちに気づくこと、そしてそこにはちゃんとした理由があること
などを書きました。

共通問題3-1

あとから見て気づいたのですが、講義単位で話さなくてはならないところを、トラック単位で話してしまっていました(何やってんだ俺・・・)
私は、アプリケーショントラック、IoTトラック、検知トラック、集中講義の講義を受けたいと考えていたため、これらのトラックについて書きました。
また、身に付けたいものについて書きました。

                                      • -

選択問題3

1.パソコンに電源を入れる。
2.CPUが、マザーボード上のROMに格納されているシステムBIOSを実行する。
3.システムBIOSは、サーバに搭載されたCPUやメモリ状態、外部デバイスの状態などを確認する。接続方法に問題があった際は、構成エラーとみなし、コンソールにエラー出力する。おそらく、各ハードウェアコンポーネントに組み込まれているファームウェアとやり取りが正常にできるかどうかをチェックし、構成エラーが生じているかどうかを判断しているものと思われる。
4.BIOSは、設定に従い、HDD、CDなどの先頭ブロックからブートストラップローダを実行する。ちなみに、設定ファイルの実態が/boot/grub/grub.confであり、grubの設定をいじる際には、
5.ブートストラップローダは、GRUBなどのブートローダを起動
6.ブートストラップは、初期ルートファイルシステムである初期RAMディスク(initrd, initramfs)とカーネル(vmlinuz)をメモリに読み込む。どうやら、Linux Kernelのバージョンが2.6.xxからinitrdではなくinitramfsが採用されるようになったらしい。initrdでは、ddコマンドを使って必要なサイズのファイルを/dev/zeroを入力に、initrdを出力に作成している。initrdでは、これによって出来たファイルをloopbackマウント(ddコマンドで作成された”ファイル”を、フォーマットし、あたかもブロックデバイスペシャルファイルかのように扱い、任意ディレクトリにマウントすること)する操作が必要だが、initramfsでは適当なディレクトリを作り、その中に必要なファイル群を詰め込んでcpioとgzipで圧縮するため、loopbackファイルを作る必要がなく、作成されたloopbackファイルに制限されることがないため、自由度がある。
7.ブートローダによってカーネルが起動される
8.カーネルは、初期RAMディスクを展開する

試しに、CentOS5でinitrd(Initial Ram Disk)を展開してみた。初めてまともにcpioコマンドを使ったので、「ファイルをアーカイブにコピーしたり、アーカイブからファイルをコピーしたりするコマンドだったんだ・・・」と驚きました。-iでアーカイブからファイルを抽出するようですが、いくつかのディレクトリが含まれるため、-dをつけて必要であればディレクトリを作成するようにしました。

$gzip -d initrd-2.6.18-406.el5.img
$cat initrd-2.6.18-406.el5 | cpio -id
$ tree .
.
|-- bin
|   |-- dmraid
|   |-- insmod
|   |-- kpartx
|   |-- modprobe -> /sbin/nash
|   `-- nash
|-- dev
|   |-- mapper
|   `-- ram -> ram1
|-- etc
|-- init
|-- initrd-2.6.18-406.el5
|-- lib
|   |-- ahci.ko
|   |-- ata_piix.ko
|   |-- crc16.ko
…
|   |-- sd_mod.ko
|   `-- uhci-hcd.ko
|-- proc
|-- sbin -> bin
|-- sys
`-- sysroot

10 directories, 25 files

bin/にはnashやmodprobe(カーネルに対し、モジュールの追加や削除ができるコマンド)、insmod(カーネルモジュールのロードができるコマンド)、kpartx(パーティション毎のブロックデバイスファイルを/dev/mapper/に生成できるコマンド)、dmraid(RAIDバイスの発見、設定、有効化ができるコマンド)がある。
lib/にカーネルモジュール群(デバイスドライバなど)があり、おそらく、modprobeあるいはinsmodを用いてこれらのモジュールをロードするのだろうなと予測できる。
initファイル(実行可能スクリプト)を覗いてみると、insmodコマンドを用いてモジュールをロードしている部分もあれば、procやsys、devなどのファイルシステムをマウントしている部分などが見られる。
ついでに、CentOS7でinitramfsも展開してみたが、何故かearly_cpioとGenuineIntel.binだけであり、これではとても起動できるように思えない。
binwalkによって解析すると、gzipが始まるオフセットがわかったため、以下のように進め、解凍を行った。

$ binwalk initramfs-3.10.0-327.4.5.el7.x86_64.img
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
…
18432         0x4800          gzip compressed data, maximum compression, from Unix, last modified: 2016-02-08 19:18:12
$ dd if=initramfs-3.10.0-327.4.5.el7.x86_64.img of=initramfs-3.10.0-327.4.5.el7.x86_64.gz bs=18432 skip=1
$ gunzip initramfs-3.10.0-327.4.5.el7.x86_64.gz
$ cat initramfs-3.10.0-327.4.5.el7.x86_64 | cpio -id

モノシリックカーネルというだけあって、ものすごいファイルの量・・・。initrdとは比べ物にならない。CentOS5からCentOS7までで、これだけの量が増えるくらい変化があったんだなぁと感じる。binやsbinの中身もかなり豪華で、systemdが採用されているからsystemctlやjournalctlが入っていたりしており、initファイルはsystemdへのシンボリックリンクとなっていた。起動手順はおそらくそこまで変わっておらず、initrdにてinitが行っていたことは、systemdが行っているものと思われる。

9.カーネルが、initを起動する。これにより、前述の通りモジュールがロードされたり、procやsysファイルシステムがマウントされたりする。この時、ブロックデバイスのスーパーブロック(inodeのブロック数やフリーリストなどの情報が含まれている)の情報をもとに、ブロックデバイス上のinode領域からinode情報を取得、メモリ用のinodeに変換(ブロックデバイス上のinode情報の形式と異なる)、メモリに載せるということも行っている。また、init.dのデーモン群もランレベルに応じて実行される

・プログラムがメモリに載る流れ

1.ユーザがプログラム名を指定して実行やコンパイルなどの操作をしようとすると、そのファイルのinode情報のうち、デバイス番号(inode.i_dev)と使用しているストレージ領域のブロック(inode.i_addr[])を参照し、ファイルのコンテンツをブロックデバイスから取得、プロセスの仮想アドレス空間内のdataセグメントに一時的に載せる。
2.載せられたファイルコンテンツに対応するinode内の参照カウンタが0の場合、dataセグメントから一旦スワップアウトを行う。
3.schedなどのスケジューラによって、プロセスの仮想アドレス空間のtextセグメントにスワップインされる。これによってプログラムが実行できるようになった。

textセグメントの共有について、textセグメントに関する情報は、text構造体型の配列によって管理されており、プロセスのproc構造体内にこのtext構造体型へのポインタがある。このため、物理メモリを見てみると、単一のtextセグメントに対し、複数のプロセスのproc構造体からの参照がある。

ここまででコンピュータ起動の流れとファイルを参照しようとした時にプロセスのtextセグメントに載るまでの流れがわかった。
以上を踏まえて、プログラムが実行される流れを見ていく。

1.ソースコードファイルを作成

$ cat > hello.c << EOF
#include <stdio.h>

int main(void) {
   printf("Hello, World!\n");
   return 0;
}
EOF

一定時間が経ったりメモリの空きページが少なくなってくると、スレッドが起きて、ブロックデバイスへの反映を行う。
それまでは、メモリ上に今書いたプログラムが存在することとなる。メモリ上のinodeにはファイル名などメタ情報が、プログラム自体はおそらくtextセグメントに存在するものと思われる。

2.プリプロセス
マクロ展開、#includeや#ifdefなどの処理が行われる。

$ gcc -E hello.c | cat -s
…

__extension__ typedef unsigned long int __fsblkcnt_t;

プリプロセッサによって#includeであればその箇所に指定したヘッダのプログラムを置いたり、#defineマクロであれば、展開して、プログラム中で使用されているところに埋め込んだりする。

3.コンパイル
/usr/libexec/gcc/i386-redhat-linux/4.1.2/cc1がコンパイラの実体となる。
普段はgccがこれを呼び出して処理してくれているが、今回は何が行われているか知りたいので、実体を利用する。
これによって、Cプログラムからアセンブラが出力される。

$ /usr/libexec/gcc/i386-redhat-linux/4.1.2/cc1 -quiet -v hello.c -quiet -dumpbase hello.c -mtune=generic -march=x86-64 -auxbase hello -Wall -version -o hello.s
...
/usr/local/include
/usr/lib/gcc/i386-redhat-linux/4.1.2/include
/usr/include
探索リストの終わり
GNU C version 4.1.2 20080704 (Red Hat 4.1.2-55) (i386-redhat-linux)
compiled by GNU C version 4.1.2 20080704 (Red Hat 4.1.2-55).
GGC heuristics: --param ggc-min-expand=64 --param ggc-min-heapsize=64358
Compiler executable checksum: 391ea76ef17128f7cf6721bdef1a3431

4.アセンブル
これによって、リンクされる前のオブジェクトファイルが出来上がる。

$ as -v --32 -o hello.o hello.s
GNU assembler version 2.17.50.0.6-26.el5 (i386-redhat-linux) using BFD version 2.17.50.0.6-26.el5 20061020

5.リンク
この例では一つしかファイルがないが、本来なら複数のオブジェクトファイルをリンクすることが想定される

$ /usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 --eh-frame-hdr -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 /usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crt1.o /usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crti.o /usr/lib/gcc/i386-redhat-linux/4.1.2/crtbegin.o -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2/../../.. hello.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i386-redhat-linux/4.1.2/crtend.o /usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crtn.o

これで実行ファイルを作成できた。
上の手順で作成した実行ファイルを、straceシステムコールの引数に渡して実行してみた
アプリケーションから実行したり、シェルから実行したりと様々な場合があるとおもうが、ここではシェルでの実行を考える。

$ strace ./hello
execve("./hello", ["./hello"], [/* 22 vars */]) = 0 //hello実行ファイルのパスを引数に、execveを呼び出し
brk(0)                                  = 0x87f4000 //データセグメントのサイズを変更する
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory) //ld.so.preloadにアクセス可能か調べる
open("/etc/ld.so.cache", O_RDONLY)      = 3  //ld.so.cacheのファイルディスクリプタが3だった
fstat64(3, {st_mode=S_IFREG|0644, st_size=18680, ...}) = 0 //ld.so.cacheの情報(inode番号やデバイス番号含む)が詰め込まれたstat構造体を取得
mmap2(NULL, 18680, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fbb000 //ファイルディスクリプタ3番、つまりld.so.cacheのオフセット0から、18680バイトだけマッピングする。その際、読み込み権限のみ付加する
close(3)                                = 0 //ld.so.cacheを閉じる
open("/lib/libc.so.6", O_RDONLY)        = 3 // /lib/libc.so.6を開く。ファイルディスクリプタは3番。
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0  W\0004\0\0\0"..., 512) = 512 //ファイルディスクリプタ3番、つまりlibc.so.6のELFヘッダを読み込む。
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fba000 //ファイルディスクリプタ-1番(どうも、ゼロクリアを意味するらしい)4096バイトだけマッピングする。その際、読み書きの権限をつけてあげる。また、マッピングに対する変更が他のプロセスから見えず、マッピングはどのファイルとも関連付けされないオプションをつけている。
fstat64(3, {st_mode=S_IFREG|0755, st_size=1706204, ...}) = 0 //libc.so.6のstat構造体を得る。
mmap2(0x55c000, 1422788, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x55c000 //libc.so.6のオフセット0からを、なるべくメモリ番地0x55c000から、1422788バイトだけマッピングする。読み込み、実行の権限が付いている。また、マッピングに対する変更が他のプロセスには見えない上、このマッピング領域に対する書き込みを拒否する。
mmap2(0x6b2000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x156) = 0x6b2000 //libc.so.6のオフセット0x156からを、なるべくメモリ番地0x6b2000から12288バイトだけマッピングする。読み込み、実行の権限が付いている。また、マッピングに対する変更が他のプロセスから見えず、指定アドレス以外のアドレスにマッピングを行わない。これは非常に強力で、既存のマッピングと重なる場合は既存のマッピングを破棄する。それに加え、このマッピング領域に対する書き込みを拒否する。
mmap2(0x6b5000, 9668, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x6b5000 //ファイルディスクリプタ-1(ゼロクリア)のオフセット0からを、なるべくメモリ番地0x6b5000から9668バイトだけマッピングする。読み込み、実行の権限が付いている。また、マッピングに対する変更が他のプロセスから見えず、指定アドレス以外のアドレスにマッピングを行わない。それに加え、このマッピングはどのファイルとも関連付けられない。
close(3)                                = 0 //libc.so.6を閉じる
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fb9000 //ファイルディスクリプタ-1(ゼロクリア)のオフセット0からを、4096バイトだけマッピングする。読み書き権限を付け、マッピング変更が他のプロセスから見えず、マッピングが他のプロセスに関連付けられない。
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7fb96c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 //entry_numberが-1なので、未使用のスレッド局所記憶、TLS(Thread-Local Storage)エントリを探し、適切なentry_number(6)を得る。
mprotect(0x6b2000, 8192, PROT_READ)     = 0 //0x6b2000から8192バイトを読み込み専用でメモリ保護
mprotect(0x558000, 4096, PROT_READ)     = 0 //0x558000から4096バイトを読み込み専用でメモリ保護
munmap(0xb7fbb000, 18680)               = 0 //0xb7fbb000から18680バイトだけのマッピングを消去
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 //標準出力のstat構造体を得る。
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fbf000 //4096バイトだけ、ゼロクリア。そのマッピング領域は、読み書き権限が付いており、マッピングの変更は他のプロセスからはわからず、このマッピングが他のプロセスに関連付けられることはない。
write(1, "Hello, World!\n", 14Hello, World! //Hello, Worldを出力
)         = 14
exit_group(0)                           = ? //プロセス中のすべてのスレッドをexitさせる。

ライブラリ等を、実行ファイルプロセスの仮想アドレス空間マッピングしたり、権限を調整したりしているのが分かる。
まず、実行ファイルを実行するようにシェルに命令すると、シェルはfork()システムコールを呼び出し、子プロセスを生成する。
試しに、while文をぶん回すだけのCプログラムを実行し、Ctrl-Zでサスペンドし、pstreeで確認してみると

init
 …
 ├─sshd
 │   └─sshd
 │       └─sshd
 │           └─bash
 │               ├─pstree -a
 │               └─while_prog

bash上でwhile_progを実行するように命令すると、bashは子プロセスを生成し、生成された子プロセスにてexecve()が実行される。この時、bashはwait()システムコールを発行して、子プロセスの実行終了を待つ。
execve()が、実行ファイルのパスが渡されて呼び出される。execveは、ファイルのパスにあるファイルを実行し、自身のプロセスとすり替える動作を行う。ソースコードを試しに読んでみた。リンカが、execveがどこにも定義されていない場合、__execveを参照するように、weak_alias関数を呼び出して別名をつけている。おそらく、この部分ですり替えることを行っているのだとおもう。
これにより、実行ファイルが参照され、補助記憶装置から主記憶装置への読み込みが行われ、実行が始まる。

選択問題4

以下のようなC言語のプログラムを書いた。
DEBUGマクロを定義すると、DEBUG情報が出力されるようになっている。
CPUサイクルはインラインアセンブラを用いて、メモリ使用量はpsコマンドを用いて得た。
filepathグローバル変数は、適宜pyonpyon.rhへのパスに書き換える必要がある。

valid_order_brand、invalid_order_brandはそのまま定義しようとしても、コンパイラの警告が出力されてしまった。
おそらく、char **というスカラー型に対して配列型で初期化しようとしたためだと思う。
そのままC言語のコードに埋め込めと言われているわけではないので、正しく解釈する必要があると考えた。
「厳密に」と書かれているため、文字列のどこにも含まれてはいけないと考えると、文字列の配列として定義し、それぞれの文字列が含まれているかどうかチェックすれば良いと考えた。
そのため、以下のソースコードでは、valid_order_brandとinvalid_order_brandのどちらも文字列へのポインタの配列として定義している。

ソースコード

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
//#define DEBUG
typedef unsigned long long ull;

const char *filepath = "/vagrant/pyonpyon.rh"; //ファイルへのパス
unsigned int pid = -1; //Process ID。psコマンドを実行するのに用いる

//Rabbit House Packet
struct rh_packet {
   char Magic[2];
   char Source[20];
   char Destination[20];
   uint32_t DataLength;
   char *Data;
};
typedef struct rh_packet rh_packet;


struct stat_info {
   ull cpu_clock;
   double mem;
};
typedef struct stat_info stat_info;

int read_stat = 1; //読み込み可能かどうか

const int valid_order_brand_num = 3;
char *valid_order_brand[] ={
   "BlueMountain",
   "Columbia",
   "OriginalBlend",
};
const int invalid_order_brand_num = 2;
char *invalid_order_brand[] ={
   "DandySoda",
   "FrozenEvergreen",
};

//CPUタイムスタンプ(最後にリセットされてからのCPUクロック数)を取得する関数
//Intel x86系のCPUではクロック毎加算される64bitタイムスタンプカウンタがある。
//これをrdtsc(Read Time Stamp Counter)命令によって呼び出すことが可能だ。
//ここでは、rdtscをインラインアセンブラで実装している。
ull rdtsc() { 
   ull ret;
   //インラインアセンブラ
   //volatileキーワードによって、コンパイラの最適化を抑制する
   //=Aは制約子であり、eax(32bit), edx(32bit)レジスタを表す制約子(64bit int向け)。ちなみに、出力先を指定する制約文字列には必ず制約修飾子文字「=」を書かなければならないというルールがある
   //Intel x86のrdtsc命令はCPUのタイムスタンプカウンタの現在値をedx:eaxにロードする(上位32bitがedx、下位32bitがeax)
   //インラインアセンブラの構文は、asm(アセンブラテンプレート : 出力オペランド : 入力オペランド : ワークレジスタ);となっている
   //ここでは、rdtscによってrdtsc命令を実行し、edx:eaxにタイムスタンプカウンタを格納した後、出力先変数として指定されているretに、edx:eaxに格納された値を出力する。
   __asm__ volatile ("rdtsc" : "=A" (ret));
   return(ret);
}

//現在のCPUサイクル及びメモリ使用量を取得する関数
stat_info get_stat(int pid) {
   stat_info ret;
   char command[256] = "";
   //このプロセスのRSS(このプロセスが使用しているスワップされていない物理メモリサイズ)を取得
   snprintf(command, sizeof(command), "ps l -p %d | awk '{print $8}'",pid);

   FILE *fp;
   char result[256];
   if((fp = popen(command, "r")) == NULL) {
       printf("cannot open");
       exit(EXIT_FAILURE);
   }
   while(fgets(result, sizeof(result), fp) != NULL) {
       int res = sscanf(result, "%lf", &ret.mem);
       if(res == EOF) {
           pclose(fp);
           return(ret);
       }
   }

   pclose(fp);
   ret.cpu_clock = rdtsc();
   return(ret);
}

//文字列の文字を全て小文字に変える
char *str2lower(char *s) {
   int length = (int)strlen(s);
   int i;
   for(i = 0; i < length; i++) {
       s[i] = tolower(s[i]);
   }
   return s;
}

//条件1~6のチェック
int check(rh_packet rhpkt) {
   int i;
   //Condition 1 Rabbit House?
   if(rhpkt.Magic[0] != 'R' || rhpkt.Magic[1] != 'H') {
       return(0);
   }
#ifdef DEBUG
   printf("[*] Condition 1 OK\n");
#endif
   //Condition 2
   char *src = str2lower(rhpkt.Source);
   if(!strncmp(src, "rise-san", 8) && !strncmp(src, "cocoa-san", 9)) {
       return(0);
   }
#ifdef DEBUG
   printf("[*] Condition 2 OK\n");
#endif
   //Condition 3
   char *dst = str2lower(rhpkt.Destination);
   if(!strncmp(dst, "chino-chan", 10) && !strncmp(dst, "chino", 5)) {
       return(0);
   }
#ifdef DEBUG
   printf("[*] Condition 3 OK\n");
#endif

   //Condition 4
   if(!strcmp(src, "cocoa-san") && !strcmp(dst, "chino")) {
       return(0);
   }
#ifdef DEBUG
   printf("[*] Condition 4 OK\n");
#endif

   //Condition 6
   for(i = 0; i < invalid_order_brand_num; i++) {
       if(strstr(rhpkt.Data, invalid_order_brand[i]) != NULL) {
#ifdef DEBUG
           printf("[-] %s found.\n", invalid_order_brand[i]);
#endif
           return(0);
       }
   }
#ifdef DEBUG
   printf("[*] Condition 6 OK\n");
#endif

   //Condition 5
   for(i = 0; i < valid_order_brand_num; i++) {
       if(strstr(rhpkt.Data, valid_order_brand[i]) == NULL) {
#ifdef DEBUG
           printf("[-] %s not found.\n", valid_order_brand[i]);
#endif
           return(0);
       }
   }
#ifdef DEBUG
   printf("[*] Condition 5 OK\n");
#endif

       return(1);
}

rh_packet read_packet(FILE *fp) {
   rh_packet ret;

   //Magic Byte
   int fsize = (int)fread(ret.Magic, sizeof(ret.Magic), 1, fp);
#ifdef DEBUG
   printf("[+] Magic Byte = %s\n", ret.Magic);
#endif
   if(fsize == 0) { //もうこれ以上読み込めない
       read_stat = 0;
#ifdef DEBUG
       printf("Finish.\n");
#endif
       return(ret);
   }

   //Source Addr
   fread(ret.Source, sizeof(ret.Source), 1, fp);
#ifdef DEBUG
   printf("[+] Source Addr = %s\n", ret.Source);
#endif

   //Destination Addr
   fread(ret.Destination, sizeof(ret.Destination), 1, fp);
#ifdef DEBUG
   printf("[+] Destination Addr = %s\n", ret.Destination);
#endif

   //Data Length
   fread(&ret.DataLength, sizeof(uint32_t), 1, fp);
   ret.DataLength = ntohl(ret.DataLength);
#ifdef DEBUG
   printf("[+] DataLength = %d\n", ret.DataLength);
#endif

   ret.Data = (char *)malloc(sizeof(char)*ret.DataLength);
   fread(ret.Data, sizeof(char)*ret.DataLength, 1, fp);
#ifdef DEBUG
   printf("[+] Data = %s\n", ret.Data);
#endif
   return(ret);
}

int main(void) {
   pid = (int)getpid();
   //pyonpyon.rhを開く
   FILE *fp;
   fp = fopen(filepath, "rb");
   if(fp == NULL) {
       printf("OPEN ERROR");
       exit(EXIT_FAILURE);
   }

   rh_packet pkt;
   while(1) {
       pkt = read_packet(fp);
       if(!read_stat) break;
       //CPUサイクルやメモリ使用量を取得(before)
       stat_info before = get_stat(pid);
#ifdef DEBUG
       printf("Before CPU Cycle=%llu, Memory=%lf(kB)\n", before.cpu_clock, before.mem);
#endif
       //チェックをかけて
       int result = check(pkt)
       //再度CPUサイクルやメモリ使用量を取得
       stat_info after = get_stat(pid);

       //最後にチェックに応じた出力を行い
       if(result) {
           printf("PASS\n");
       } else {
           printf("REJECT\n");
       }

#ifdef DEBUG
       printf("After CPU Cycle=%llu, Memory=%lf(kB)\n", after.cpu_clock, after.mem);
#endif
       //マッチングにかかったCPUサイクル及びメモリ使用量を出力する。
       printf("Matching used CPU Cycle %llu, Memory %lf(kB).\n", after.cpu_clock-before.cpu_clock, after.mem-before.mem);
       putchar('\n');
       free(pkt.Data); //Dataは動的に確保しているため、解放
   }

   fclose(fp);
   return 0;
}
・実行結果
$ ./rhpkt_match
REJECT
Matching used CPU Cycle 31619646, Memory 80.000000(kB).

REJECT
Matching used CPU Cycle 17624155, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 26430615, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 30070283, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 22568636, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 24127622, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 15864860, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 24185634, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 22638197, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 28097601, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 20553726, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 34922973, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 23925269, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 20784937, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 23470467, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 23961367, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 23308672, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 29050089, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 16536914, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 21359561, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13795710, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13543117, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 17725989, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 17355279, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 23561724, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13990891, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13264643, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13755037, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13287532, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 21081993, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 20273016, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 19157997, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 14535584, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13464240, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 23214606, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 14579642, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 16930423, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 22910441, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 73592721, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 19578587, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 19594448, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 19079588, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 15535166, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13411838, Memory 0.000000(kB).

REJECT
Matching used CPU Cycle 13420062, Memory 0.000000(kB).

選択問題6

実際に攻撃を行いながら調べたいと思い、isoが配布されているBadStoreというwebアプリケーションに対して攻撃を行った。
前提
Apache+Perl(CGI)+MySQLの構成を想定する。
ECサイトであり、プロトコルにhttpを用いている。
ページ内検索、ホーム画面、最新の商品、掲示板入力フォーム、注文履歴、カートの中身確認、About Us、パスワードリセット、アカウントログイン及び登録、関係者専用ページなどがある。


テスト
1.ID「a」、パスワード「a」のような非常に脆弱なパスワードで登録できるか試してみる
1桁が無理でも、7桁未満くらいなら、総当たり攻撃が可能であるため、最低何桁であれば登録できるか試す。
BadStoreではID「a」、パスワード「a」の組み合わせで登録できてしまった・・・。

2.ID「admin」、パスワード「’ OR 1=1; #」でログインを試みる
認証時、簡単な例だと以下のようなSQL文が実行されていることが予測できる

SELECT * FROM users WHERE id = ‘<param_id>’ AND password = ‘<param_password>’;

ここで、上記の通りにログインしようとすると

SELECT * FROM users WHERE id = ‘admin’ AND password = ‘’ OR 1=1; #

となる。
「#」以降はコメントアウトとして扱われ(1行コメントアウト)、無視される。
テーブル名などを知りたいという場合は

登録されていないID’ OR lower(substring((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES LIMIT 1), 1, 1)) >= ‘u’ #

のように打ち込み、ログインが成功するようであれば、あるテーブル名の1文字目がアルファベット順で’u’より後ろにあることがわかる。
ちょっと長いため、Firebugなどでインプットタグのmaxlengthをいじる必要がある

3.ID「admin’ OR 1=1; #」、パスワード「xxx」でログインを試みる
パスワードでダメだったので、IDで試してみる。
こうすると、SQL文が以下のようになると思われる

SELECT * FROM users WHERE id = ‘admin’ OR 1=1; # AND password = ‘xxx’;

Bad Storeでは、Software errorが発生した。
エラー画面から、SQL文は以下のようなものが実行されているとわかった

SELECT * FROM users WHERE id = ‘admin’ AND password = ‘f56…’;

2で失敗していたのは、パスワードを用いてハッシュ値を算出するため、攻撃が意図通りにいかないためであると分かる。

4.IDに「’」でログインを試みる
シングルクオートのみにし、徐々にいじることで攻撃を図る。
3の時と似たようなエラー画面が表示された。

5.ID「admin’ #」、パスワード「xxx」でログインを試みる
OR 1=1がない場合、SQL文は以下のとおり

SELECT * FROM users WHERE id = ‘admin’ # AND password=‘f56…’;

これでも、idがadminでさえあれば一致するので試してみた。
通ってしまった・・・。見事にAdministratorになった。

6.ID「admin’ OR ‘x’=‘x」、パスワード「xxx」でログインを試みる
SQL文が以下のとおりになる

SELECT * FROM users WHERE id=‘admin’ OR ‘x’=‘x’ AND password=‘f56…’;

やっていることは5と同じ。結局、idがadminであるものが偶々見つかって認証が通る。

7.ページ内検索フォームに

「<s>hello」

と打ち込む
適切にエスケープされていないと、helloに打ち消し線が引かれる。
もし適切にエスケープされていないのであれば、他のHTMLタグを用いることが可能となる
Bad Storeでは打ち消し線が引かれた。


8.ページ内検索フォームに

「<script>alert(‘XSS’)</script>」

と打ち込む
単純なXSSを試す。
BadStoreではSQLのエラーが出た。おそらくシングルクオートで引っかかったものと思われる。

9.ページ内検索フォームに

「<script>alert(1)</script>」

と打ち込む
シングルクオートがあったことがエラーの原因だったため、数値で試してみる。
Bad Storeではalertが発火した。

10.ページ内検索フォームに

「<script>alert(“XSS”)</script>」

と打ち込む
では、ダブルクオートではどうだろうか。
Bad Storeではalertが発火した。

11.最新の商品一覧画面にて、通常はあまりないと思うが、チェックボックスなどでチェックしている場合、value値に適当な値を入れることで一覧に無い商品を参照できないか
例えば、Bad Storeでは以下のようになっていた

<input name=“cart item” value=“1000“ type=“checkbox”></input>

value値に該当するアイテムが無い旨のエラーメッセージが表示された。


12.ゲストブックにて、

name「<s>name」、email「<s>email」、comments「<s>comment」を打ち込む

適切にエスケープされてい無い箇所が無いか調べる。
Bad Storeでは全て打ち消し線が引かれた・・・。


13.ゲストブックにて、

name「<img src="/images/BadStore.jpg”/>」、email「<h1 onclick="alert(1)">click me!</h1>」、comments「<script>alert("XSS")</script>」

を打ち込んでみる。
適切にエスケープされていないことがわかったため、画像を名前に埋め込んでみたり、持続型XSSを埋め込んだりといったことを実験してみる。
期待通り、名前にはBad Storeの画像が埋め込まれ、alertが発火した。

14.パスワードリセットにて、メールアドレスフォームに「admin@example.com」と打ち込んでみる。
どのように動作するか確認する。
メールアドレスに送信するのではなく、直接webページに仮パスワードを記載するようだ。

15.パスワードリセットにて、メールアドレスフォームに

「<script>alert(1)</script>」

と打ち込んでみる。
POST後、メールアドレスがwebページに表示されていたため、適切にエスケープされていなければalertが発火する。
Bad Storeでは発火した。

16.パスワードリセットにて、CSRFトークンが無いか確認し、なさそうであれば試しにPOSTしてみる

$curl -H "Content-Type: application/x-www-form-urlencoded"  -X POST -d '{"email":"<script>alert(1)</script>", "pwdhint":"green", "DoMods": "Reset User Password"}' http://192.168.100.131/cgi-bin/badstore.cgi\?action\=moduser
<HR><P><BR><Center><FONT SIZE=2, FACE='Times'>BadStore v1.2.3s - Copyright &#169; 2004-2005</Center></BODY></HTML>

実際に攻撃を行う際は、以下のようなHTMLを用意する方法もある

<html>
<head></head>
<body onload="document.csrf.submit();">
<form name="csrf" method="POST" action="http://192.168.100.131/cgi-bin/badstore.cgi?action=moduser" enctype="application/x-www-form-urlencoded">
<input name="email" size="15" type="text"> 
<select name="pwdhint">
<option value="green">green</option>
<option value="blue">blue</option>
<option value="red">red</option>
<option value="orange">orange</option>
<option value="purple">purple</option>
<option value="yellow">yellow</option>
</select>
<input name="DoMods" value="Reset User Password" type="submit"></p></form>
</body>
</html>

SSOidが、「YTowY2MxNzViOWMwZjFiNmE4MzFjMzk5ZTI2OTc3MjY2MTphOlU==」というようなbase64で、解読すると「a:0cc175b9c0f1b6a831c399e269772661:a:U」となる。これは、ID「a」、パスワード「a」で試した結果で、a::a:Uという形式(md5はhashkillerのようなレインボーテーブルで調べた)になっており、トークンが生成しやすいものとなっている。
商品をカートに追加する際も、CSRFトークンが用いられていないため、以下のようにPOSTすることで、SSOidに指定したユーザのカートに任意の商品が追加可能であると考えられる。
curl -H "Content-Type: application/x-www-form-urlencoded" -H "Cookie: SSOid=<攻撃対象のSSOid>” -d '{"cartitem":1000, "Add Items to Card": "Add Items to Cart"}' http://192.168.100.131/cgi-bin/badstore.cgi\?action\=cartadd
このほかにも、商品注文画面やログイン画面にもトークンが用いられていない。

17.関係者用ログインページにて、email「’」、パスワード「xxx」を打ち込んでみる
SQLのエラー画面をみたいため、エラーが起きるように、余計なシングルクオートを追加する。
Bad Storeでは何も起きなかった。

18.関係者用ログインページにて、email「”」、パスワード「xxx」を打ち込んでみる。
シングルクオートでダメだったので、ダブルクオートを用いている可能性を疑う。
IDとパスワードに一致するアカウントが見つからなかった旨のページが表示される。

19.cgi-bin/test.cgiに強制アクセスしてみる
testといった名前は使われやすく、意外と重要な情報があったりすることもある。
Bad Storeでは見ることができた。

20.cgi-bin/に強制アクセスしてみることで、ディレクトリリスティングを試みる
test.cgi以外にも、名前の予測が難しいが重要な情報が含まれるファイルを閲覧したい。
Bad StoreではさすがにForbiddenが返された。

21.robots.txtに強制アクセスしてみる
robots.txtには、検索エンジンにインデクシングしたくないページの情報が含まれていることが多く、重要な情報を得られることが期待できる。
Bad Storeでは、見ることができ、アカウント情報を格納したページ(supplier/accounts)を閲覧できた
そのページにはbase64エンコーディングされた以下の文字列のような重要情報が記載されていた
joeuser/password/platnum/192.168.100.56
そのほかには、scanbotというディレクトリの下に、ウィンドウを開いてリダイレクトさせることを繰り返すような嫌がらせhtmlファイル(?)があった。

22.Bad Storeであれば「Doing Business」、「Procedures」などのディレクトリリスティングできそうなディレクトリに強制アクセスを仕掛けてみる
ほかにも有用な情報を得たいため。
Bad Storeでは、ディレクトリリスティングできるものの、有用なファイルが見当たらなかった。

23.getパラメータにlogin=Trueなどというものがあれば、logout=Trueなどとして試してみる。
これにより、ログアウトを強制することができる。
BadStoreではlogin=Trueというクエリパラメータを認証時に利用していたため、logout=Trueとしたらログアウトできた。

24.ファイルを開いていると推測できる箇所、メール送信を行っている箇所では、「;ls+-l」や、「|ls; #」などOSコマンドインジェクションを試みる
OSコマンドが実行できると、webアプリケーションを起動しているユーザの権限で任意コマンド実行ができるので、サーバー側に対してほとんどなんでもできてしまう場合もある(例えば、root権限でwebアプリケーションを立ち上げている場合)
Bad Storeでは、従業員としてログイン(通常のログインと同じ手法でいけた)したのち、ファイルアップロードができるようだ。
しかし、なぜかrobots.txtに書いてあったuploadsディレクトリを参照しても「そんなディレクトリはない」と怒られた。
試しに以下のようなPerlプログラムを書いて、名前を「../../cgi-bin/exec.pl」としてアップロードしてみた
Permission Deniedが返された
だが、Bad StoreのProceduresディレクトリもみれることがわかっているので、試しにそこにも置こうと試みる。
が、同様にPermission Deniedが返された。

25.Perl CGIでopen関数が使われていそうな場所が見つかれば、「|ls -la」などを打ち込んでみる
sysopen関数を使われていると難しくなるが、open関数は、「|」があるとOSコマンドを実行してしまう。
Bad Storeで試してみたが、どうやら「|ls -la」というファイル名でファイルが置かれただけらしい・・・。

26.メール問い合わせ部分があった際、メールヘッダインジェクションができないか試す。
メールの文章入力フォームに
sample
Bcc: example@example.com
などと入力すると、BCCが追加され、example@example.comにも送信されてしまう。


27.GETパラメータにurlリダイレクタがある場合など、urlに「http://example.com/%0D%0ASet-Cookie:+SSOid=abc」と打ち込んでみる
リダイレクタではLocationヘッダが用いられると推測できるため、
Location: http://example.com/
Set-Cookie: SSOid=abc
となることが期待される。
これによってセッション固定化攻撃が可能となる。


28.ユーザ認証画面にて、ID「xxx' OR 1=1; #」でログインを試みる
Bad Storeで任意のIDでログインしたかったので、諦めきれず試す。
やはりエラーがでる

SELECT * FROM users WHERE id = ‘xxx’ OR 1=1; # ‘ AND password=…

コメントアウトされた部分についてもsyntaxがチェックされるのだろうか?

選択問題11

CVE-2015-7547(glibc)

攻撃概要:
この脆弱性攻撃は、悪意のあるDNSサーバに対して、DNSクライアントがglibcのgetaddrinfo()関数を呼び出し、ホスト名からIPアドレスを解決しようとした際に、2048バイトを超えるようなレスポンスが悪意あるDNSサーバから返ってくると、getaddrinfo()関数呼び出しにより呼び出された_nss_dns_gethostbyname4_rにて、BOFが発生(2048の固定サイズでスタックに領域を確保している)してしまうことで成功する。
細かく説明すると、getaddrinfo()では、レスポンスを受け取ると、UDPのクエリ処理を受け持つsend_dg関数内にて2048バイトまではスタック上に確保するが、それ以上のサイズのレスポンスについて、確保しきれない分をヒープ上に、最大65536バイトまで確保する。ここでサイズが最大65536と認識されるわけだが、send_dg関数から戻った時、スタック上には2048バイトしか確保していないのに実際のデータサイズは65536バイトもある、ということで不整合が起きる。次に__libc_res_nsendがsend_dg(UDP)やsend_vc(TCP)を呼び出す際、ちゃんと不整合を修正(ポインタの更新)しているため、1回の__libc_res_nsend呼び出しで攻撃を完結させる必要がある。
攻撃を1回で完結させるため、公開されているPoCでは、
TC(TrunCated)フラグを立てて2048バイトを超えるUDPレスポンスを返す。
ここでクライアント側が、レスポンスサイズが最大で65536だと認識してしまい、バッファ(2048バイト分しか確保されてない)サイズが最大で35536バイトに更新される
TCビットが立っているため、TCPフォールバックが起きる(クライアントが、今度はTCPでリクエストを送る)。このTCPフォールバックの活用こそが、__libc_res_nsend関数の呼び出しが1回目のうちに攻撃を完了させるための工夫である。
クライアントは、バッファのサイズが最大で65536バイトだと勘違いしたまま、バッファにレスポンスを書き込もうとする。ここでは、バッファのサイズを超えるレスポンスにならないため、ヒープ領域が確保されず、そのままスタックに書き込まれる。
これによりバッファオーバーフロー攻撃が成功する。

攻撃を検知する方法:
1.信頼できるDNSキャッシュサーバを経由して名前解決を行う
UDPクエリでは、resolve.confのoptionsでedns0を指定しない場合は512バイトを越えるレスポンスが送出されず、TCビットの付いたレスポンスはデータを含まない(その後のTCP通信からデータが送られ始める)ことが、仕様にあり、DNSキャッシュサーバがこれに従うため、クライアント側が2048バイトを越えるレスポンスを受けることがないと分かる。よって、攻撃の防御が容易。キャッシュサーバで2048バイトを超えるようなレスポンスを受け取っていることをチェックすれば、検知することが可能である。具体的には、iptablesのlengthで2048を指定し、--log-level=[info|debug]などでログに記録しておくような方法がある。また、logwatchなど他のツールを用いることで、システム管理者に通知することも可能と考えられる。なお、iptablesコマンド実行時に、状態がESTABLISHEDの時にチェックをかけるように指定しないとダメである。なぜならば、TCPならフラグメントが生じて、パケットを再構築した際に2048以上になりうるからだ。

$ sudo iptables -A INPUT -p udp - -sport 53 -m state - -state ESTABLISHED -m length - -length 2048: -j LOG - -log-level=info - -log-prefix “ipv4 udp glibc detect”
$ sudo iptables -A INPUT -p tcp - -sport 53 -m state - -state ESTABLISHED -m length - -length 2048: -j LOG - -log-level=info - -log-prefix “ipv4 tcp glibc detect”
$ sudo ip6tables -A INPUT -p up - -sport 53 -m state - -state ESTABLISHED -m length - -length 2048: -j LOG - -log-level=info - -log-prefix “ipv6 udp glibc detect”
$ sudo ip6tables -A INPUT -p tcp - -sport 53 -m state - -state ESTABLISHED -m length - -length 2048: -j LOG - -log-level=info - -log-prefix “ipv6 tcp glibc detect”

2.SSPを用いる
getaddrinfo()を呼び出すプログラムのコンパイル時に、Stack Smashing Protectorを指定しておくと、stack canaryを用いてBOFが検知される。例えば以下のように実行すれば、SSP有効となる。

$ cat > propolice.c << EOF
#include <string.h>

void check() {
  char buf[2];
  memset(buf, 114514, 32);
  return;
}

int main(void) {
  check();
  return 0;
}
$ gcc -fstack-protector-all -o propolice propolice.c #-fstack-protectorでもSSP有効となるが、例えばBOFされるバッファが8バイト未満だとスタック破壊検出コードを生成しないため、必ずスタック破壊検出コードを生成する-fstack-protector-allを使用したほうがいい。
$ ./propolice
*** stack smashing detected ***: ./propolice terminated
アボートしました

内部的にはmain()の始めで、スタックにランダムな値を仕込んでおく(canary)。最後に、最初のcanaryと同じ値(ハードコードされているので、この値はテキストセグメントにある)と、スタック上のcanaryがあるであろう位置にある値とを比較し、一致しなければプログラムを強制終了する。
これにより、バッファオーバーフローが起きてしまい、DoS攻撃が成功してしまうものの、その後のROPなどの攻撃は防げるため、任意コード実行ほど深刻にはならない。

  • fstack-protector-allがきちんと動作することが確認できた。では、-fstack-protectorでは本当にスタック破壊検出コードが生成されないのだろうか。試してみた。
$ cat > sample_nogen_stackdestroycode.c << EOF
#include <string.h>

int main(void) {
   char buf[1];
   const char *sample = "AAAA";
   strcpy(buf, sample);
   return 0;
}
EOF
$ gcc -fstack-protector -o sample_nogen_stackdestroycode sample_nogen_stackdestroycode.c
$ ./sample_nogen_stackdestroycode
セグメンテーション違反です

Segmentation Faultが起きたことはわかるがスタック破壊検出されていないように見える。
しかし、ももいろテクノロジーにてここで紹介したSSPのBypass手法のエントリを見つけた。
どうやら、ROP前になんとかcanaryの値を得て、ROP実行し、プログラム終了前にスタック上の破壊されたcanaryの位置に復元してやるというものだった。
そのため、この手法で検知しようとしても、うまくBypassされる可能性がある。
後述するmudflapでも、似たような手法で検知を行っているため、同様にBypassされる可能性が否定できない。

3.mudflapを用いる
2で取り上げたSSPと同様、バッファオーバーフローを検知してくれる。他にも、メモリリーク、ポインタ誤使用なども検出してくれる。

$ cp propolice.c mudflap.c
$ gcc -fmudflap -lmudflap -o mudflap mudflap.c
$ ./mudflap
*******
mudflap violation 1 (check/write): time=1464535861.664713 ptr=0xbfe333fa size=23
pc=0xdc5ecd location=`(strcpy dest)'
     /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xdc5ecd]
     /usr/lib/libmudflap.so.0(__mfwrap_strcpy+0x158) [0xdd2298]
     ./mudflap(main+0x4e) [0x8048672]
Nearby object 1: checked region begins 0B into and ends 21B after
mudflap object 0x83d7e38: name=`mudflap.c:5 (main) buf'
bounds=[0xbfe333fa,0xbfe333fb] size=2 area=stack check=0r/3w liveness=3
alloc time=1464535861.664706 pc=0xdc590d
number of nearby objects: 1
セグメンテーション違反です

ついでにメモリリークやポインタ誤使用もやってみた

$ cat >  memory_leak.c << EOF
#include <stdlib.h>

int main(void) {
   char *ptr = (char *)malloc(sizeof(char *)*10);
   for(i=0;i<=3;i++){
       printf("Hello\n");
   }
   return 0;
}
EOF
$ export MUDFLAP_OPTIONS='-print-leaks'
$ gcc -fmudflap -lmudflap -o memory_leak memory_leak.c
$ ./memory_leak
Hello
Hello
Hello
Hello
Leaked object 1:
mudflap object 0x8910f10: name=`malloc region'
bounds=[0x8910ea0,0x8910ec7] size=40 area=heap check=0r/0w liveness=0
alloc time=1464537305.697616 pc=0x1ca90d
     /usr/lib/libmudflap.so.0(__mf_register+0x3d) [0x1ca90d]
     /usr/lib/libmudflap.so.0(__wrap_malloc+0xcf) [0x1cbe6f]
     ./memory_leak(main+0x1d) [0x8048631]
     /usr/lib/libmudflap.so.0(__wrap_main+0x49) [0x1ca969]
number of leaked objects: 1

間違ったポインタの扱いというのが、いまいちピンとこなかったが、「これはダメでしょ」というものを試してみた

$ cat > wrong_pointer.c << EOF
#include <stdio.h>

int main(void) [
   int *ptr; //初期化されていない
   *ptr = 5;
   printf("%d\n", *ptr);
   return 0;
}
EOF
$ gcc -fmudflap -lmudflap -o wrong_pointer wrong_pointer.c
$ ./wrong_pointer
number of leaked objects: 0

初期化されていない状態のポインタを使って、参照外しで値を入れ込んでいる。これも、メモリリークにつながる。
無事検知されたようで良かった。

興味を持った理由:
この脆弱性を知るまで、時折Twitterで脆弱性情報を探していたが、とびきり目立つ脆弱性しか目につかず、おそらく、同年代で自分と同じく情報セキュリティに興味がある方々よりも脆弱性に関する知識が少なかった。
この脆弱性はTwitterでもおおきく話題として取り上げられ、時々しか確認しない私でも知ることができた。
DNSに関して知識が浅かったため、勉強も兼ねて・・・と最新情報(英語)をアバウトに翻訳し、友人に伝えた。
これは、私にとって初めてアグレッシブに脆弱性情報と向き合った瞬間かもしれない。
これがきっかけで、脆弱性情報を効率的に収集するにはどうしたらいいか考えるようになり、現在では、研究室のSlackで、毎朝7:00にJVNiPediaから脆弱性情報を取得し、知らせてくれるボットを動かしている。
こうした経緯があり、この脆弱性にはおおきく関心があった。
だが、脆弱性が発覚した当日に英語の翻訳をした程度で、日本語の記事をチェックしていなかったため、これを機会に調べてみようと思い、この問題で提示した。

                                              • -

以上になります。
プロの方々がアップしていくなか、こうしてビクビクしながらアップしております・・・´д` ;