期末考考得差不多了,雖然還在實訓,但還是能抽出點時間來更新下博客。
前一段時間看到有人推薦一個網站,pwnable.kr,我做了一下里面的題,感覺蠻有趣的,主要是跟二進制有關,涉及操作系統底層的東西多一些。雖然網上有很多的 write up,在沒思路的時候我也會去看看人家寫好的 write up,但是總是覺得他們寫的不夠詳細,我就想把詳細的解題思路記錄下來,方便自己以后回來看并希望對這方面有興趣的朋友們提供一些些幫助。
這篇文章寫的是 pwnable.kr 里的第五題,別的題我也做了一些,以后慢慢補,這題剛做完,就先寫這題。
題目描述是這樣的:
用 ssh 連接一下,看看目錄下有什么
可以看到有三個文件,其中 flag 只對創建者 passcode_pwn 和 root 可讀,而我們登錄的用戶是 passcode(從連接時的用戶名或使用 whoami 命令可以得知),因此是沒有權限讀這個文件的。passcode 對有所有用戶都開放讀和執行權限,而且留意到權限里面有 s,因此普通用戶在執行這個文件時會被賦予 root 權限。最后一個文件是 passcode 的源代碼,這是分析程序執行邏輯的關鍵。
首先先運行一下 passcode
可以看到需要輸入一個用戶名和兩個密碼。
再來看看源碼
通過源碼我們知道 main 函數先后調用了 welcome 和 login 這兩個函數,其中在 welcome 函數中輸入用戶名,在 login 中輸入兩個密碼。
細心一點可以發現,在 login 函數調用 scanf 的時候,對參數沒有取址的操作!那我們輸入的數據保存到哪里去了呢?誰也不知道,因為 passcode1 和 passcode2 沒有被初始化,我們只知道 scanf 把數據保存到 passcode1 和 passcode2 中的值指向的那個地址里去了。
分析到這里,就想到一個思路,如果能控制 passcode1 或者 passcode2 的值,把它們的值變成一個地址,那在輸入的時候不就相當于往這個地址寫東西了嗎!
gdb 調試一下,看看各個變量的地址
首先在各個函數起始的地方下個斷點
讓程序跑起來,看看每個函數執行的匯編指令
指令有點多,挑重點。main 函數不管,先看 welcome 函數,對照著源代碼,可以知道 name 的地址是 -0x70(%ebp),即 %ebp - 0x70,再看 login 函數,同樣對照源代碼,知道 passcode1 的地址是 -0x10(%ebp),即 %ebp - 0x10,passcode2 的地址是 -0xc(%ebp),即 %ebp - 0xc。注意這兩個函數的 %ebp 是不一樣的,但是由于這兩個函數是由 main 函數同步調用的而且它們的參數個數一樣多(都是 0 個),所以在數值上兩個函數的 %ebp 是相等的。關于函數堆棧,參見我的另一篇文章
通過計算可以知道,name 的地址比 passcode1 的地址低 96 個字節(0x70 - 0x10 = 96),name 的地址比 password2 的地址低 100 個字節(0x70 - 0xc = 100),而我們可以控制的 name 剛好能夠到 100 個字節,也就是說,我們剛好能夠控制 passcode1 的值而剛好不能控制 password2 的值。
既然能夠控制 passcode1 的值,那我們就可以把它改寫成某個地址,然后在輸入 passcode1 的時候輸入我們想要執行的指令地址,那在調用完 scanf 之后,passcode1 指向的地址就是我們的想要執行的指令了。舉個例子,如果通過將 name 的最后四個字節寫成 plt 中 printf 函數的地址,再在輸入 passcode1 的時候輸入 login 函數的地址,那再這之后再次調用 printf 函數時實際上執行的就是 login 函數了。
這里我們要輸出的是 flag 的內容,看到 login 函數中調用了 system 函數來輸出 flag 的內容,前面提到,普通用戶在運行這個程序的時候會被暫時賦予 root 權限,所以直接調用這個 system 函數是可以輸出 flag 中的內容的。因此,我們可以把 name 的最后四個字節寫成 plt 中 printf 函數的地址,即 0x08048420,然后再輸入 passcode1 時輸入調用 system 函數的地址,即 0x080485e3(注意函數調用前還有給參數賦值等初始化操作,因此這個地址在 call system 語句的前面一點點),這樣實際上相當于在執行 printf("enter passcode2 : ");
語句時執行的是 if 中的 system("/bin/cat flag");
語句了。
這里用 readelf -r ./passcode
查看程序的 plt
可以看到 printf 的偏移是 0x0804a000
用 python 生成 payload 并作為程序的輸入
python -c "print 'A' * 96 + '\x00\xa0\x04\x08' + '134514147\n'" | ./passcode
得到結果