owned this note
owned this note
Published
Linked with GitHub
## AI/PixelPingu :drop_of_blood:
### Description
---
> Piplup is hosting a penguin drawing contest! But, feeling a bit lazy, she has decided to use two AI models to judge the entries for her. A small gift awaits every creative drawing, so come and show your skills!
The challenge provide a toolset to draw and submit artwork. An artwork score will be returned along with a part of the flag content.
[image](https://hackmd.io/_uploads/SkQ8xtqUll.png)
When user use the given canvas, `canvas.js` file process client-side drawing and convert them into RGB value. Then
The RGB data then pass through the backend by the `/submit_artwork` route and be judged for score.
```py
@app.route("/submit_artwork", methods=["POST"])
def submit_artwork():
data = request.json
canvas_data = data.get("canvas_data")
if canvas_data:
judge_results = score_penguin_submission(canvas_data)
judge_score = judge_results.get("score", 0)
return jsonify(
{
"success": True,
"judge_score": round(judge_score, 2),
"flag_part": judge_results.get("flag_part", ""),
}
)
return jsonify({"success": False, "error": "No canvas data provided"})
```
The "judge" here is actually two models what will evaluate the class of the provided image, with number 145 as "king penguin".
```py
class PenguinJudge:
def __init__(self):
self.judge_one_model = shufflenet_v2_x2_0(weights=None)
self.judge_two_model = regnet_x_1_6gf(weights=None)
self.load_custom_weights()
self.judge_one_model.eval()
self.judge_two_model.eval()
self.judge_one_transform = ShuffleNet_V2_X2_0_Weights.IMAGENET1K_V1.transforms()
self.judge_two_transform = RegNet_X_1_6GF_Weights.IMAGENET1K_V2.transforms()
self.penguin_class = 145
self.flag = os.getenv(
"FLAG", "HCMUS-CTF{FAKEEEEEE_FLAGGGGG_FAKEEEEEE_FLAGGGGG}"
)
self.flag_parts = self.split_flag_into_parts(self.flag)
```
The flag is splited into four part and they are return to user under certain condition
```py
def get_flag_part(self, judge_one_is_penguin, judge_two_is_penguin):
if not judge_one_is_penguin and not judge_two_is_penguin:
return self.flag_parts[0]
elif judge_one_is_penguin and judge_two_is_penguin:
return self.flag_parts[1]
elif judge_one_is_penguin and not judge_two_is_penguin:
return self.flag_parts[2]
elif not judge_one_is_penguin and judge_two_is_penguin:
return self.flag_parts[3]
```
The goal here is to find input (or images) that satisfies all of the condition to retrive the full flag.
My idea is to just download a king penguin image dataset online and feed them into the model.
Anyway here is the `sol.py`
```py
from PIL import Image
import requests
from judge import score_penguin_submission
# Needed to find the needed photos (definitely not AI generated)
# PHOTO_DIR = "./photos"
# required_flags = {
# (False, False): None,
# (True, True): None,
# (True, False): None,
# (False, True): None,
# }
# image_extensions = (".jpg")
# for filename in os.listdir(PHOTO_DIR):
# if not filename.lower().endswith(image_extensions):
# continue
# path = os.path.join(PHOTO_DIR, filename)
# try:
# canvas = get_canvas_array_from_png(path)
# result = score_penguin_submission(canvas)
# key = (
# result["judge_one"]["is_penguin"],
# result["judge_two"]["is_penguin"]
# )
# if key in required_flags and required_flags[key] is None:
# required_flags[key] = {
# "filename": filename,
# "score": result["score"],
# "flag_part": result["flag_part"]
# }
# if all(required_flags.values()):
# break
image_paths = [
'./public/src/photos/04HNGRM58JN2.jpg', # (False, False)
'./public/src/photos/000R7L59NT8J.jpg', # (True, True)
'./public/src/photos/06T013BWP9BX.jpg', # (True, False)
'./public/src/photos/17J1T2KGXV3V.jpg', # (False, True)
]
def get_canvas_array_from_png(path):
img = Image.open(path).convert("RGBA")
white_bg = Image.new("RGBA", img.size, (255, 255, 255, 255))
img = Image.alpha_composite(white_bg, img)
img = img.resize((128, 128), Image.Resampling.NEAREST)
flat_array = [channel for pixel in img.getdata() for channel in pixel]
return flat_array
url = 'http://103.199.17.56:25001/submit_artwork'
for path in image_paths:
canvas_data = get_canvas_array_from_png(path)
payload = {
'canvas_data': canvas_data
}
resp = requests.post(url=url, json=payload)
resp_json = resp.json()
print(resp_json)
```
---
## AI/gsql1
### Description
>I have a database containing user information, but I'm too lazy to write queries for each specific case, so I've handed everything over to a GPT model. Everything should be fine 🤗.
Private Message @gsql <@1392549979272249494> for flag
This challenge initialize a database in `database.go` file with a `user` and a `flag` table. The goal is to get the flag from `flag` table.
```javascript=
func InitializeDatabase() error {
db, err := sql.Open("sqlite", "./data/database.db")
if err != nil {
return fmt.Errorf("error opening database: %w", err)
}
defer db.Close()
statement, err := db.Prepare("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE)")
if err != nil {
return fmt.Errorf("error creating users table: %w", err)
}
_, err = statement.Exec()
if err != nil {
return fmt.Errorf("error executing create users table: %w", err)
}
statement, err = db.Prepare("CREATE TABLE IF NOT EXISTS flag (data TEXT UNIQUE)")
if err != nil {
return fmt.Errorf("error creating flag table: %w", err)
}
_, err = statement.Exec()
if err != nil {
return fmt.Errorf("error executing create flag table: %w", err)
}
statement, err = db.Prepare("INSERT OR IGNORE INTO flag (data) VALUES (?)")
if err != nil {
return fmt.Errorf("error preparing insert into flag statement: %w", err)
}
_, err = statement.Exec("[redacted]")
if err != nil {
return fmt.Errorf("error executing insert into flag: %w", err)
}
statement, err = db.Prepare("INSERT OR IGNORE INTO users (name) VALUES (?)")
if err != nil {
return fmt.Errorf("error preparing insert into users statement: %w", err)
}
_, err = statement.Exec("Jane Smith")
if err != nil {
return fmt.Errorf("error executing insert into users: %w", err)
}
_, err = statement.Exec("John Doe")
if err != nil {
return fmt.Errorf("error executing insert into users: %w", err)
}
return nil
}
```
The server get user input from a Discord bot and feed it to a `gemini-2.0-flash-lite` model with the following sanitation and rules
``` python
prompt := "You are a strict SQL query generator.\n" +
"Only output a single-line raw SQL SELECT query using only the users table and its columns: id and name.\n" +
"Do not generate any INSERT, UPDATE, DELETE, DROP, ALTER, or any non-SELECT operations.\n" +
"Do not include any semicolons, comments, or additional statements.\n" +
"Do not include markdown or any formatting (e.g., no ```sql).\n" +
"Sanitize the input to prevent any kind of command injection.\n" +
"Always output a safe, valid SQL query that only reads from users.\n\n" +
"Input: " + userQuery
```
The return from the AI model is then used to query directly with the database and the Discord bot will return the result.
This is a prompt injection problems that requires user to bypass all the setup rules and construct the query to the `flag` table.
Here are some commands that I tried:



---
### Misc/Is this Bad Apple
## Description
> An easy misc challenge to warm you up!
The challenge is really similar to a video that I found about exploiting Youtube to store data for free
https://www.youtube.com/watch?v=8I4fd_Sap-g
I used the link in the description to install a tool that helps me decode the video challenge.
The decoded data has PNG header so change the extension and submit the flag.

---
## Misc/Is this Bad Apple - Sequel
### Description
> There's another flag hidden somewhere in the first challenge, can you find it?
Note: Not a stego challenge
The flag is in the thumbnail when I try to download the video.
---
## Misc/PJSK :drop_of_blood:
### Description
> Do you know Project Sekai? It's that rhythm game that has a lot of cute characters and songs.
One day, I was vibing to one of my favorite songs in the game, missing every note as usual, and I thought, "Hey, this would make a great CTF challenge!" Naturally, I did what any CTFer would do, I hid a flag in the song.
It’s in there somewhere, probably chilling behind a high note or hiding in your wifi.
Flag format: HCMUS-CTF{...}
Note: Some OSINT skill may be required
The challenge is a `chal.sus` file, which stands for Sliding Universal Score, a music score map that can be used in many popular rhythm game such as Osu and Project Sekai.
```
This file was generated by MikuMikuWorld 3.1.0
#TITLE ""
#ARTIST ""
#DESIGNER ""
#WAVE "./an_0098_01.flac"
#WAVEOFFSET -9
#JACKET "./jacket_s_098.png"
#BACKGROUND "./jacket_s_098.png"
#REQUEST "ticks_per_beat 480"
#00002: 4
#BPM01: 162
#00008: 01
#TIL00: ""
#HISPEED 00
#MEASUREHS 00
...
```
One of the first thing that I did is download MikuMikuWorld, an app that allow me to visualize and edit the score

I pressed play and come to a conclusion that this is a modified version of some available map since the rhythm sounds good and not some gibberish stuff.
So I take a look at the file in text and saw
```
#JACKET "./jacket_s_098.png"
#BACKGROUND "./jacket_s_098.png"
```
which made me wonder if this is from the original script.
After some researching I found this website https://sekai.best/asset_viewer that contains data about the game itself and most of its default map.
Then I found the original background https://storage.sekai.best/sekai-jp-assets/music/jacket/jacket_s_098/jacket_s_098.png
After a reverse image search I found the name of the song and also the original map, it's the hardest difficulty of them all.
```
This file was generated by Ched 2.6.4.0.
#TITLE ""
#ARTIST ""
#DESIGNER ""
#DIFFICULTY 0
#PLAYLEVEL
#SONGID ""
#WAVE ""
#WAVEOFFSET 0
#JACKET ""
#REQUEST "ticks_per_beat 480"
#00002: 4
#BPM01: 162
#00008: 01
#TIL00: ""
#HISPEED 00
#MEASUREHS 00
#00212:0013130013000013
#0021b:2300001300131300
#00215:00000000000013000000000000130000
#00217:0013
#00218:00000000001300000000000000001300
#00216:23
#00210:41
```
On MikuMikuWorld, the two map looks almost identical with some extra note added in the `chal.sus` file

I then filtered out the added one by my own hand because there are only a few differences (not because I can't find a way to generate a script that works).
The filter out vesion `diff.sus` look really like Morse code so I quickly (absolutely no struggling here) put them together and the flag is found `HCMUS-CTFOMG_IT_MIGU_:D`
