# sum seccon ctf 2019 根據解題人數來看應該算是簡單題,但太久沒打真的忘的好快...... ## 分析 程式很簡單,基本上就是輸入 5 個數字,然後做加法 * 漏洞: ```c while (idx <= cnt) { iVar1 = __isoc99_scanf(&DAT_00400a68,ptr + idx,idx * 8); if (iVar1 != 1) { /* WARNING: Subroutine does not return */ exit(-1); } if (ptr[idx] == 0) break; idx = idx + 1; } ``` cnt 為總數,而 idx 從 0 開始存放,故可以多放一個到 local 空間,有 8 bytes 的 oob ptr 為上一層 function 傳進來的 pointer ,存放地點也在上層的 local array 裡面,根據分析應該是長這樣: ``` long local_48; undefined8 local_40; undefined8 local_38; undefined8 local_30; undefined8 local_28; long *sum_ptr; long sum; long local_10; ``` array 從 0x48 開始存放到 0x28 ,接著是 sum_ptr 然後 sum 最後 canary 因為可以多覆蓋 8 bytes ,所以可以蓋到 sum_ptr , sum_ptr 則會被 sum(long *arr, long *sum_ptr) 拿去存資料,透過 overflow 到 sum_ptr 和前面的 array 會有個任意地址寫入 到此我有一個一次的任意地址寫入的 primitive ## 利用 雖然沒有 PIE 但 ASLR 還是存在的,所以我還是得要一個任意讀取漏洞才能 get shell ,但此時只有一次任意地址寫入,所以第一要務先思考怎麼將一次利用轉為多次利用 程式後半段會判斷 sum() 裡面總共加了多少次,超過 5 則透過 exit() 離開,由於我需要 overflow ,加總次數絕對是超過 5 次,所以 exit() 避不開,既然避不開那就乾脆拿 exit 的 got 來開刀好了 因為想要多次任意地址寫入,所以 exit 的 got 我將其改為 return to main ,這樣基本上就可以多次任意寫入 做到這邊我又卡關了,我想了以下幾個方法: 1. 覆蓋某些 data 來 control flow hijack 去 leak address 然後 get shell 2. 用 got hijack 改掉不重要的程式來 leak 和 write > 基本上該程式很少呼叫 libc function ,所以沒用 > 3. 修改掉 IO function 的 format string data > 基本上這些 format string 都放在 rodata 內,通常改不到 4. 用 sum 加法來修改偏移 > 程式加法前會先 *sum = 0 ,所以無法保存原本的值讓我做偏移,純粹是我眼殘 > 5. 用 fini array 來控制 > 沒有 leak 前能用的東西太少 有些方法我想完都覺得好笑,很多都是可以直覺劃掉的我還浪費時間在上面,真的是生疏好多 最後我迸出了一個想法,既然沒有好用的 libc function ,那有沒有好用的 gadget 來寫掉 got 呢? 寫掉的目標當然就是 exit 的 got ,但是要怎麼把控制權牢牢的握在手中? 我想到因為 exit 是用 call 的,所以 stack 內有 return address ,這個一定要避掉,否則就回歸正常流程(是說 exit() 會 return 嗎?),這個可以用 add rsp, 0x8; ret 來規避 再來要 stack pivot ,這部分一定是控制 rsp ,通常會用 leave 透過 rbp 來設定 rsp ,不然就是 pop rsp 或 xchg 之類的改寫 rsp 值,由於無法用直接輾過的方式寫掉 rbp ,我採用 pop rbp; leave ret 來搞定,將 stack 成功遷移到 bss 上 stack pivot 後要跳到 rop chain 上,但我覺得先前的任意地址寫入好象比較無腦一點,所以在 pivot 前先將可以寫的 rop chain 布置到 bss 中, pivot 後跳過去即可 關於 rop chain 的部分我是依照下面的順序擺放: 1. pop rdi 2. setvbuf@got 3. puts@plt 4. pop rdi 5. "%lld" 6. pop rsi; pop r15 7. buffer 8. garbage 9. scanf@plt 10. pop rdi 11. "sh" 12. ret 13. system 其中 1~3 是 leak , 4~9 是寫 system 地址到 13 的位置, "sh" 一樣是在任意地址寫上時順便寫上去的 基本上我覺得到這邊開 shell 就不是問題......才怪 1. scanf 的 local 空間要夠大,換句話說選定的 pivot 空間要可以讓 scanf 開闢 stack frame 空間,太小的話就會開空間到沒有寫入權限的地方 2. execve 莫明其妙開不起來,到 execve syscall 時直接回傳 -14 後關閉...... 第 2 點我搞不清楚怎麼回事,不過我用 one_gadget 就能順利拿到 shell # 總結 不是很難,但對我這種生疏已久的人來說搞了不少時間......其他高手應該一個鐘頭不到就能搞定了吧......,在此簡單做個紀錄 **用 gadget 覆蓋 GOT 獲取控制權**