期末考考得差不多了辆毡,雖然還在實(shí)訓(xùn),但還是能抽出點(diǎn)時(shí)間來更新下博客怪得。
前一段時(shí)間看到有人推薦一個(gè)網(wǎng)站咱枉,pwnable.kr,我做了一下里面的題徒恋,感覺蠻有趣的蚕断,主要是跟二進(jìn)制有關(guān),涉及操作系統(tǒng)底層的東西多一些入挣。雖然網(wǎng)上有很多的 write up亿乳,在沒思路的時(shí)候我也會(huì)去看看人家寫好的 write up,但是總是覺得他們寫的不夠詳細(xì)径筏,我就想把詳細(xì)的解題思路記錄下來葛假,方便自己以后回來看并希望對(duì)這方面有興趣的朋友們提供一些些幫助。
這篇文章寫的是 pwnable.kr 里的第五題匠璧,別的題我也做了一些桐款,以后慢慢補(bǔ),這題剛做完夷恍,就先寫這題。
題目描述是這樣的:
用 ssh 連接一下媳维,看看目錄下有什么
可以看到有三個(gè)文件酿雪,其中 flag 只對(duì)創(chuàng)建者 passcode_pwn 和 root 可讀,而我們登錄的用戶是 passcode(從連接時(shí)的用戶名或使用 whoami 命令可以得知)侄刽,因此是沒有權(quán)限讀這個(gè)文件的指黎。passcode 對(duì)有所有用戶都開放讀和執(zhí)行權(quán)限,而且留意到權(quán)限里面有 s州丹,因此普通用戶在執(zhí)行這個(gè)文件時(shí)會(huì)被賦予 root 權(quán)限醋安。最后一個(gè)文件是 passcode 的源代碼,這是分析程序執(zhí)行邏輯的關(guān)鍵墓毒。
首先先運(yùn)行一下 passcode
可以看到需要輸入一個(gè)用戶名和兩個(gè)密碼吓揪。
再來看看源碼
通過源碼我們知道 main 函數(shù)先后調(diào)用了 welcome 和 login 這兩個(gè)函數(shù),其中在 welcome 函數(shù)中輸入用戶名所计,在 login 中輸入兩個(gè)密碼柠辞。
細(xì)心一點(diǎn)可以發(fā)現(xiàn),在 login 函數(shù)調(diào)用 scanf 的時(shí)候主胧,對(duì)參數(shù)沒有取址的操作叭首!那我們輸入的數(shù)據(jù)保存到哪里去了呢习勤?誰也不知道,因?yàn)?passcode1 和 passcode2 沒有被初始化焙格,我們只知道 scanf 把數(shù)據(jù)保存到 passcode1 和 passcode2 中的值指向的那個(gè)地址里去了图毕。
分析到這里,就想到一個(gè)思路眷唉,如果能控制 passcode1 或者 passcode2 的值予颤,把它們的值變成一個(gè)地址,那在輸入的時(shí)候不就相當(dāng)于往這個(gè)地址寫東西了嗎厢破!
gdb 調(diào)試一下荣瑟,看看各個(gè)變量的地址
首先在各個(gè)函數(shù)起始的地方下個(gè)斷點(diǎn)
讓程序跑起來,看看每個(gè)函數(shù)執(zhí)行的匯編指令
指令有點(diǎn)多摩泪,挑重點(diǎn)笆焰。main 函數(shù)不管,先看 welcome 函數(shù)见坑,對(duì)照著源代碼嚷掠,可以知道 name 的地址是 -0x70(%ebp),即 %ebp - 0x70荞驴,再看 login 函數(shù)不皆,同樣對(duì)照源代碼,知道 passcode1 的地址是 -0x10(%ebp)熊楼,即 %ebp - 0x10霹娄,passcode2 的地址是 -0xc(%ebp),即 %ebp - 0xc鲫骗。注意這兩個(gè)函數(shù)的 %ebp 是不一樣的犬耻,但是由于這兩個(gè)函數(shù)是由 main 函數(shù)同步調(diào)用的而且它們的參數(shù)個(gè)數(shù)一樣多(都是 0 個(gè)),所以在數(shù)值上兩個(gè)函數(shù)的 %ebp 是相等的执泰。關(guān)于函數(shù)堆棧枕磁,參見我的另一篇文章
通過計(jì)算可以知道,name 的地址比 passcode1 的地址低 96 個(gè)字節(jié)(0x70 - 0x10 = 96)术吝,name 的地址比 password2 的地址低 100 個(gè)字節(jié)(0x70 - 0xc = 100)计济,而我們可以控制的 name 剛好能夠到 100 個(gè)字節(jié),也就是說排苍,我們剛好能夠控制 passcode1 的值而剛好不能控制 password2 的值沦寂。
既然能夠控制 passcode1 的值,那我們就可以把它改寫成某個(gè)地址纪岁,然后在輸入 passcode1 的時(shí)候輸入我們想要執(zhí)行的指令地址凑队,那在調(diào)用完 scanf 之后,passcode1 指向的地址就是我們的想要執(zhí)行的指令了。舉個(gè)例子漩氨,如果通過將 name 的最后四個(gè)字節(jié)寫成 plt 中 printf 函數(shù)的地址西壮,再在輸入 passcode1 的時(shí)候輸入 login 函數(shù)的地址,那再這之后再次調(diào)用 printf 函數(shù)時(shí)實(shí)際上執(zhí)行的就是 login 函數(shù)了叫惊。
這里我們要輸出的是 flag 的內(nèi)容款青,看到 login 函數(shù)中調(diào)用了 system 函數(shù)來輸出 flag 的內(nèi)容,前面提到霍狰,普通用戶在運(yùn)行這個(gè)程序的時(shí)候會(huì)被暫時(shí)賦予 root 權(quán)限抡草,所以直接調(diào)用這個(gè) system 函數(shù)是可以輸出 flag 中的內(nèi)容的。因此蔗坯,我們可以把 name 的最后四個(gè)字節(jié)寫成 plt 中 printf 函數(shù)的地址康震,即 0x08048420,然后再輸入 passcode1 時(shí)輸入調(diào)用 system 函數(shù)的地址宾濒,即 0x080485e3(注意函數(shù)調(diào)用前還有給參數(shù)賦值等初始化操作腿短,因此這個(gè)地址在 call system 語句的前面一點(diǎn)點(diǎn)),這樣實(shí)際上相當(dāng)于在執(zhí)行 printf("enter passcode2 : ");
語句時(shí)執(zhí)行的是 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
得到結(jié)果