#問題文
ssh random@pwnable.kr -p2222
にアクセスして、flagを入手する
#解法
##ファイル構成
- -r--r----- 1 random_pwn root flag
- -r-sr-x--- 1 random_pwn random random
- -rw-r--r-- 1 root root random.c
例によってflag はrootでしか読み取れず、random.cも買い替えは不能。よって./random という実行ファイルに対して何らかの攻撃をすればflagはゲットできると予想する。
##random.c
#include <stdio.h>
int main(){
unsigned int random;
random = rand(); // random value!
unsigned int key=0;
scanf("%d", &key);
if( (key ^ random) == 0xdeadbeef ){
printf("Good!\n");
system("/bin/cat flag");
return 0;
}
printf("Wrong, maybe you should try 2^32 cases.\n");
return 0;
}
ユーザーの入力keyとrand()で生成したrandomの排他的論理和が0xdeadbeefであればcatコマンドでflagを表示してくれる。
#方針
間違ったときに出てくるメッセージによると、2^32 のブルートフォースアタックをすればいいようにも見えるが、(unsunged intなので32乗)約42億となってしまうので少しやりたくない。(やったらどれくらい時間がかかるかどうかはわかりません)
というわけでどうにかして、randomを特定してそれに対応したkeyを入力してやることによるflag入手を目指す。
#rand()関数の脆弱性
さて、randomはrand()関数を使用して乱数を生成しているわけだが、この関数は通常これだけでは使ってはいけない。コンピュータが乱数を生成すると言っても、人間のように思いつきで生成しているわけではなく、擬似乱数というものを計算で生成しており、そのために乱数のシード(seed:種)を使う。
今回のrand()関数が使う方法では、シードが同じなら、必ず同じ値が生成されてしまうという特徴があり、コード中にはシードを設定している箇所はない。
つまり、これを実行しても、randomは乱数ではなく、「定数」となってしまうのだ。
以上から、randomを特定してやり、それを入力してやることでflagゲットを目指す。
#デバッガによるrandom特定
random.cは書き換えできないので、何らかの手段で実行中の変数の内容を観測してやる必要がある。
そのためにGDBというデバッガを使う。
main関数のディスアセンブリ結果の抜粋(下)
- rand関数周辺
0x0000000000400601 <+13>: call 0x400500 <rand@plt>
0x0000000000400606 <+18>: mov DWORD PTR [rbp-0x4],eax
- if文周辺
0x0000000000400629 <+53>: mov eax,DWORD PTR [rbp-0x8]
0x000000000040062c <+56>: xor eax,DWORD PTR [rbp-0x4]
0x000000000040062f <+59>: cmp eax,0xdeadbeef
以上から、[rbp-0x8]にkeyが、[rbp-0x4]にrandomがあるということが推測される。
よってmain+18直後の[rbp-0x8]を調べればrandomがわかることになる。
##答え
(私の環境では)randomは0x6b8b4567となり、これと0xdeadbeefの排他的論理和である3039230856を入力してやることでflagが表示された。
#rand()の正しい(?)利用法
rand()関数を使ってやる際には前述のように毎回違うシードを使ってやる必要があり、慣習的に時間をシードとする方法が知られている。
srand()関数でシードを指定する際にtime()関数で現在時間を表示するなどがある。