---
# System prepended metadata

title: sum seccon ctf 2019

---

# 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 獲取控制權**