osu!gaming CTF 2025 个人Writeup / Personal Writeup

· · 休闲·娱乐

https://osugaming.sekai.team/

Solve puzzles, decrypt ciphers, and uncover hidden secrets in osu!gaming CTF 2025, a beginner-friendly cybersecurity "capture-the-flag" competition where every challenge is themed around the rhythm games you know and love.

While traditional CTFs feature standard cybersecurity challenges, every challenge in this event is themed around osu! and/or popular rhythm games! If technical puzzle-solving sounds interesting, but you've never participated in one of these yourself, no worries! The challenges are designed to accommodate both beginners and experienced players alike.

For more information, read our official news post on the osu! website. Form a team with friends and join the Discord server for announcements, support, and community discussion.

那么大家好啊我也是诈尸了,第一次打 CTF,被队友带飞了……
一个队几乎全是打 osu 的所以就当娱乐了,最终排名 14

This is the first time I participated in CTF, thx for teammates carrying :(
Our team only have one member that touched CTF before, the rest are pure players and just started preparing right before the event, so I'm not expected to receive this place!

我做出来的题 / Challenges done by me

osu/welcome

Welcome to osu!gaming CTF 2025 (Rhythm Game Edition). Please join our Discord for challenge updates and admin support.

Have fun!

flag: osu{Rhythm+G4m3_4_L1f3!}

osu/lobby

Multi is a game mode in osu! that allows players to play together in a multiplayer lobby. PvP is fun, isn't it?

Join the official CTF lobby osu!gaming CTF 2025 to claim the flag! You are more than welcomed to stay in the lobby for a bit longer and have fun with others. There WILL be a secret prize for a random game winner :wysi:

Please behave nicely and follow the rules. Do not spoil the fun for others! Report any misbehaviour to admins.

Note: Please copy the flag string from website/chat log to avoid misspell.

在赛时有开一个 mp 房,进去之后有一个机器人,发一个指令就会私信发 flag. 注意 osu! 客户端的字体分不清 o0, 所以需要拷网页端的 flag.

We have a osu! multiplayer lobby during the event. A bot is there and you can send a command to get the flag.

flag: osu{Welc0me_And_G00d_Luck_Gett1ng_Th3_Secr3t_Prize<3}

osint/dmca

Peppy once received a DMCA takedown request for osu! due to alleged copyright infringement. The client, however, spelled "osu!" wrong.

Can you find out the email address of the client who sent the DMCA request? Wrap in osu{} and submit.

Github 搜索 osu dmca, 找到 seedcrack/osu-DMCA-maps-finder,里面有一个 notices.txt,内容是所有 DMCA Takedown 的链接,其中第一个就是我们要找的。

Search osu dmca in Github, we can find seedcrack/osu-DMCA-maps-finder, notices.txt in it contains links to all the DMCA takedown requests. And the first one have the information we need.

flag: osu{[email protected]}

crypto/xnor-xnor-xnor

https://osu.ppy.sh/beatmapsets/1236927#osu/2573164

import os
flag = open("flag.txt", "rb").read()

def xnor_gate(a, b):
    if a == 0 and b == 0:
        return 1
    elif a == 0 and b == 1:
        return 0
    elif a == 1 and b == 0:
        return 0
    else:
        return 1

def str_to_bits(s):
    bits = []
    for x in s:
        bits += [(x >> i) & 1 for i in range(8)][::-1]
    return bits

def bits_to_str(bits):
    return bytes([sum(x * 2 ** j for j, x in enumerate(bits[i:i+8][::-1])) for i in range(0, len(bits), 8)])

def xnor(pt_bits, key_bits):
    return [xnor_gate(pt_bit, key_bit) for pt_bit, key_bit in zip(pt_bits, key_bits)]

key = os.urandom(4) * (1 + len(flag) // 4)
key_bits = str_to_bits(key)
flag_bits = str_to_bits(flag)
enc_flag = xnor(xnor(xnor(flag_bits, key_bits), key_bits), key_bits)

print(bits_to_str(enc_flag).hex())
# 7e5fa0f2731fb9b9671fb1d62254b6e5645fe4ff2273b8f04e4ee6e5215ae6ed6c

加密方式是生成一个长度为 4 的 key 逐位和明文做同或得到密文。我们猜测明文的前四位是 osu{,据此可以解出 key 是 eed32a76,然后就解密得到明文。

Encryption method is xnor every bit of key and message. The key is 4 bytes long and repeats afterwards.

We can guess the first characters in enc_flag is osu{ so we can construct the key, then we can decrypt it and get out desired flag.

flag: osu{b3l0v3d_3xclus1v3_my_b3l0v3d}

kaijuu/sanity-check

Find my latest mapset on osu! and start from there :)

Note: Flag is case-sensitive and it can be properly figured out by opening the map in-game.

首先找到出题人 sahuang 最新最热的图:

明显 Mapper Tags 里面有一个长得像 flag 的东西。
注意 osu 网页上 Mapper Tags 会自动转成小写,所以需要下载下来进游戏看或者直接用记事本打开谱面文件获得。

We can find the most recent beatmap of author sahuang.

Obviously there is something that looks like flag in Mapper Tags. Then we download the beatmap and edit it so we can get the proper upper/lower-case.

flag: osu{APPEND_38_Goes_Crazy}

crypto/beyond-wood

spinning white floating glowing man in forest

from PIL import Image
import random

FLAG = Image.open("flag.png")
width, height = FLAG.size

key = [random.randrange(0, 256) for _ in range(width+height+3)]

out = FLAG.copy()
for i in range(width):
    for j in range(height):
        pixel = FLAG.getpixel((i, j))
        pixel = tuple(x ^ k for x, k in zip(pixel, key))
        newi, newj = (2134266 + i * 727) % width, (4501511 + j * 727) % height 
        out.putpixel((newi, newj), pixel)

out.save("output.png")

output.png

加密方式是线性打乱图片的像素,然后每个像素的 rgb 都异或一个值。

图片大小是 591 \times 415727 是个素数所以有乘法逆元,可以把各个像素还原到本来的位置上。rgb 异或的值应该解不开我们也不去管它。最后还原位置的图片是

The program replaces positions of the pixels and xor rgb value with a key.

727 is a prime, and the given image dimensions is 591 \times 415, so we can reverse the permutating process. It's enough to get the flag.

flag: osu{h1_05u_d351gn_t34m}

kaijuu/ss-me

Can you SS APPEND? Do not use Auto to cheat...

Note: No Mod only. Do not add any modes.

expected difficulty: 2/5

nc ss-me.challs.sekai.team 1337

题目要求我们提交一个 SS 前面那张图的 APPEND 难度的 replay。

手打 SS 不太像是人能干得出来的事,于是我们开 Auto 导出一个 replay,按照 replay 文件格式把用户名和 Mod 改掉就能通过检测了。

理论上这题真的手打一个 SS 应该也能过。

We need to submit a replay SS-ing the APPEND difficulty of previous mapset.

We have no std players and I personally think it's just impossible for human to achieve it.

So we asked osu!-chan to play it, then modify the header of replay to remove Auto Mod and change the username, according to this page.

This is our modified replay header.

20 8e 01 35 01 0b 20 61 33 37 65 32 36 64 62 30
39 62 39 30 38 64 33 30 39 35 62 31 62 35 32 64
30 30 66 64 35 34 31 0b 04 61 61 61 61 0b 20 32
31 64 30 65 37 63 34 37 64 66 64 33 39 32 39 31
31 38 62 31 65 38 30 39 61 39 36 30 31 38 34 fe
01 20 20 20 20 9c 20 20 20 20 20 0a 87 b5 20 06
04 01 20 20 20 20  

flag: osu{I_th1nk_u_St1LL_ch34t3d_w1th_AU70!}

crypto/linear-feedback

this owc map is so fire btw :steamhappy: https://osu.ppy.sh/beatmapsets/2451798#osu/5355997

from secrets import randbits
from math import floor
from hashlib import sha256

class LFSR:
    def __init__(self, key, taps, format):
        self.key = key
        self.taps = taps
        self.state = list(map(int, list(format.format(key))))

    def _clock(self):
        ob = self.state[0]
        self.state = self.state[1:] + [sum([self.state[t] for t in self.taps]) % 2]
        return ob

def xnor_gate(a, b):
    if a == 0 and b == 0:
        return 1
    elif a == 0 and b == 1:
        return 0
    elif a == 1 and b == 0:
        return 0
    else:
        return 1

key1 = randbits(21)
key2 = randbits(29)
L1 = LFSR(key1, [2, 4, 5, 1, 7, 9, 8], "{:021b}")
L2 = LFSR(key2, [5, 3, 5, 5, 9, 9, 7], "{:029b}")

bits = [xnor_gate(L1._clock(), L2._clock()) for _ in range(floor(72.7))]
print(bits)
# [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0]

FLAG = open("flag.txt", "rb").read()
keystream = sha256((str(key1) + str(key2)).encode()).digest() * 2
print(bytes([b1 ^ b2 for b1, b2 in zip(FLAG, keystream)]).hex())
# 9f7f799ec2fb64e743d8ed06ca6be98e24724c9ca48e21013c8baefe83b5a304af3f7ad6c4cc64fa4380e854e8

显然是要从 bits 逆推 key1 和 key2。

我们知道同或其实就是 \mathbb{F}_2 上的加法再加 1。观察知道这个 LFSR 推进一次状态得到的新状态就是之前状态的线性组合,所以它的每个状态都是初始 key 的线性组合。

那么我们根据 bits 可以列出一个 \mathbb{F}_2 上的线性方程组,高斯消元可以解出这个方程有两个自由未知量,总共有 4 种可能,枚举即可。

We need to use bits to reconstruct key1 and key2.

We know a \ \mathrm{XNOR} \ b is just (a + b + 1) \bmod 2. We can observe each state of LFSR is a linear combination of the bits in the initial key. So we can construct a system of linear equation and solve it using Gaussian.

After doing it we know there are only 2 free variables, so we can just brute force it (4 possibilities) and get the correct key.

flag: osu{th1s_hr1_i5_th3_m0st_fun_m4p_3v3r_1n_0wc}

crypto/ssss

can you ss this secret sharing scheme?

nc ssss.challs.sekai.team 1337

#!/usr/local/bin/python3
from Crypto.Util.number import *
import random

p = 2**255 - 19
k = 15
SECRET = random.randrange(0, p)

def lcg(x, a, b, p):
    return (a * x + b) % p

a = random.randrange(0, p)
b = random.randrange(0, p)
poly = [SECRET]
while len(poly) != k: poly.append(lcg(poly[-1], a, b, p))

def evaluate_poly(f, x):
    return sum(c * pow(x, i, p) for i, c in enumerate(f)) % p

print("welcome to ssss", flush=True)
for _ in range(k - 1):
    x = int(input())
    assert 0 < x < p, "no cheating!"
    print(evaluate_poly(poly, x), flush=True)

if int(input("secret? ")) == SECRET:
    FLAG = open("flag.txt").read()
    print(FLAG, flush=True)

大意:素数 p = 2^{255} - 19,有一个未知的 14 次多项式

f = \sum \limits_{i = 0}^{14} c_i x^i \in \mathbb{F}_p[x]

它的系数满足 c_{i + 1} \equiv a c_i + b \pmod p。你可以询问 f14 个非 0 处的值,求 c_0

首先我们随便问它在任意 14 个点处的值,然后高斯消元可以得到 c_ic_0 之间的线性关系。

因为 c_{i + 1} \equiv a c_i + b \pmod p,所以 (c_{i + 2} - c_{i + 1})^2 \equiv (c_{i + 1} - c_i) (c_{i + 3} - c_{i + 2}) \pmod p,代入线性关系可以得到一个关于 c_0 的二次方程,接下来求解二次剩余即可。

We pick 14 abitrary values and do the query, Then we have 14 linear equations of 15 variables. So we can use c_0 to express every other coefficients linearly.

From c_{i + 1} \equiv a c_i + b \pmod p we know (c_{i + 2} - c_{i + 1})^2 \equiv (c_{i + 1} - c_i) (c_{i + 3} - c_{i + 2}) \pmod p, putting the linear relationships we can get a quadratic equation of c_0 so we can solve it.

flag: osu{0n3_hundr3d_p3rc3nt_4ccur4cy!}

rev/bleh

bleh :p

Charset: [a-f0-9]+. Concatenate all inputs, then decode as hex.

rev_bleh.tar.gz

先对 bleh0 下手,扔进 ida 反编译得到

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  unsigned __int8 n4919_1; // al
  unsigned int n4919; // [rsp+18h] [rbp-98h]
  int i; // [rsp+1Ch] [rbp-94h]
  _QWORD PL4YING_CTFS_ISNTBETTER_THAN_OSU[4]; // [rsp+20h] [rbp-90h] BYREF
  char s[32]; // [rsp+40h] [rbp-70h] BYREF
  char s1[32]; // [rsp+60h] [rbp-50h] BYREF
  char s2[8]; // [rsp+80h] [rbp-30h] BYREF
  __int64 v11; // [rsp+88h] [rbp-28h]
  unsigned __int64 v12; // [rsp+90h] [rbp-20h]
  __int64 v13; // [rsp+98h] [rbp-18h]
  unsigned __int64 v14; // [rsp+A8h] [rbp-8h]

  v14 = __readfsqword(0x28u);
  qmemcpy(
    PL4YING_CTFS_ISNTBETTER_THAN_OSU,
    "PL4YING_CTFS_ISNTBETTER_THAN_OSU",
    sizeof(PL4YING_CTFS_ISNTBETTER_THAN_OSU));
  *(_QWORD *)s2 = 0x34CB9F7D58F19373LL;
  v11 = 0x4FDD7446E17C0BA9LL;
  v12 = 0xC25AF68321B93ABDLL;
  v13 = 0x47E475FC931AA324LL;
  memset(s, 0, sizeof(s));
  memset(s1, 0, sizeof(s1));
  read(0, s, 0x20uLL);
  n4919 = 4919;
  for ( i = 0; i <= 31; ++i )
  {
    n4919_1 = sub_1337((unsigned int)s[i], (unsigned int)*((char *)PL4YING_CTFS_ISNTBETTER_THAN_OSU + i), n4919);
    n4919 = n4919_1;
    s1[i] = n4919_1;
  }
  if ( !strncmp(s1, s2, 0x20uLL) )
    puts("Nicely done");
  else
    puts("Playing ctfs is better than osu");
  return 0LL;
}

__int64 __fastcall sub_1337(unsigned int a1, unsigned int a2, unsigned int a3)
{
  _DWORD v5[4]; // [rsp+14h] [rbp-ECh] BYREF
  unsigned int v6; // [rsp+24h] [rbp-DCh]
  int v7; // [rsp+28h] [rbp-D8h]
  int v8; // [rsp+2Ch] [rbp-D4h]
  int v9; // [rsp+30h] [rbp-D0h]
  unsigned int v10; // [rsp+34h] [rbp-CCh]
  int v11; // [rsp+38h] [rbp-C8h]
  int v12; // [rsp+3Ch] [rbp-C4h]
  int v13; // [rsp+40h] [rbp-C0h]
  unsigned int v14; // [rsp+44h] [rbp-BCh]
  _DWORD *v15; // [rsp+48h] [rbp-B8h]
  __m128i v16; // [rsp+60h] [rbp-A0h] BYREF
  __m128i v17; // [rsp+70h] [rbp-90h] BYREF
  __m128i v18; // [rsp+80h] [rbp-80h] BYREF
  __m128i v19; // [rsp+90h] [rbp-70h] BYREF
  __m128i v20; // [rsp+A0h] [rbp-60h] BYREF
  __m128i v21; // [rsp+B0h] [rbp-50h] BYREF
  __m128i v22; // [rsp+C0h] [rbp-40h] BYREF
  __m128i v23; // [rsp+D0h] [rbp-30h] BYREF
  __m128i v24; // [rsp+E0h] [rbp-20h] BYREF
  unsigned __int64 v25; // [rsp+F8h] [rbp-8h]

  v25 = __readfsqword(0x28u);
  v11 = 0;
  v12 = 0;
  v13 = 0;
  v14 = sub_11C9(a1, 6LL);
  v16 = _mm_unpacklo_epi64(_mm_insert_epi32(_mm_cvtsi32_si128(v14), 0, 1), _mm_insert_epi32(_mm_cvtsi32_si128(0), 0, 1));
  v7 = 0;
  v8 = 0;
  v9 = 0;
  v10 = sub_11C9(a2, 128LL);
  v17 = _mm_unpacklo_epi64(_mm_insert_epi32(_mm_cvtsi32_si128(v10), 0, 1), _mm_insert_epi32(_mm_cvtsi32_si128(0), 0, 1));
  v5[1] = 0;
  v5[2] = 0;
  v5[3] = 0;
  v6 = sub_1280(a3, 128LL);
  v18 = _mm_unpacklo_epi64(_mm_insert_epi32(_mm_cvtsi32_si128(v6), 0, 1), _mm_insert_epi32(_mm_cvtsi32_si128(0), 0, 1));
  v23 = _mm_load_si128(&v16);
  v24 = _mm_load_si128(&v17);
  v19 = _mm_xor_si128(_mm_load_si128(&v24), _mm_load_si128(&v23));
  v19 = _mm_shuffle_epi32(_mm_load_si128(&v19), 180);
  v21 = _mm_load_si128(&v19);
  v22 = _mm_load_si128(&v18);
  v20 = _mm_add_epi32(_mm_load_si128(&v22), _mm_load_si128(&v21));
  v15 = v5;
  return _mm_load_si128(&v20).m128i_u32[0];
}

__int64 __fastcall sub_11C9(unsigned int a1, int a2)
{
  _DWORD v3[4]; // [rsp+14h] [rbp-4Ch] BYREF
  unsigned int v4; // [rsp+24h] [rbp-3Ch]
  _DWORD *v5; // [rsp+28h] [rbp-38h]
  __m128i v6; // [rsp+40h] [rbp-20h] BYREF
  unsigned __int64 v7; // [rsp+58h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  if ( !a2 )
    return a1;
  v3[1] = 0;
  v3[2] = 0;
  v3[3] = 0;
  v4 = sub_11C9(a1, (unsigned int)(a2 - 1)) + 1;
  v6 = _mm_unpacklo_epi64(_mm_insert_epi32(_mm_cvtsi32_si128(v4), 0, 1), _mm_insert_epi32(_mm_cvtsi32_si128(0), 0, 1));
  v5 = v3;
  return _mm_load_si128(&v6).m128i_u32[0];
}

__int64 __fastcall sub_1280(unsigned int a1, int a2)
{
  _DWORD v3[4]; // [rsp+14h] [rbp-4Ch] BYREF
  unsigned int v4; // [rsp+24h] [rbp-3Ch]
  _DWORD *v5; // [rsp+28h] [rbp-38h]
  __m128i v6; // [rsp+40h] [rbp-20h] BYREF
  unsigned __int64 v7; // [rsp+58h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  if ( !a2 )
    return a1;
  v3[1] = 0;
  v3[2] = 0;
  v3[3] = 0;
  v4 = sub_1280(a1, (unsigned int)(a2 - 1)) - 1;
  v6 = _mm_unpacklo_epi64(_mm_insert_epi32(_mm_cvtsi32_si128(v4), 0, 1), _mm_insert_epi32(_mm_cvtsi32_si128(0), 0, 1));
  v5 = v3;
  return _mm_load_si128(&v6).m128i_u32[0];
}

首先我们观察这个 sub_11C9,简化一些乱七八糟的东西发现它就是

unsigned int sub_11C9(unsigned int a, int b) {
    return b == 0 ? a : (sub_11C9(a, b - 1) + 1);
}

所以这个 sub_11C9 就是算加法。类似观察得到 sub_1280 就是算减法。然后观察 sub_1337,整理一通发现它是

unsigned int sub_1337(unsigned int a1, unsigned int a2, unsigned int a3){
    return ((a1 + 6) ^ (a2 + 128) + (a3 - 128)) & 0xff;
}

因为实际上这里 a2 传进去是 ASCII 字符,a1 是数字或字母,所以 (a1 + 6) ^ (a2 + 128) == ((a1 + 6) ^ a2) + 128.

再看 main,核心只有

int n4919 = 4919;
for (int i = 0; i < 32; i++) {
    n4919 = sub_1337(s[i], PL4YING_CTFS_ISNTBETTER_THAN_OSU[i], n4919);
    s2[i] = n4919;
}
if (!strncmp(s1, s2, 0x20uLL))
    puts("Nicely done");
else
    puts("Playing ctfs is better than osu");

因为每次循环中 n4919 的增量完全取决于 s[i]PL4YING_CTFS_ISNTBETTER_THAN_OSU[i],所以我们就可以解出来需要输入的 s.

这里解出来是 ffd8ffe020104a464946200101012060,一看有 4a464946 所以是 jpg 文件头。

打开其他的程序发现只有 s2 是不一样的,而且在代码中的位置也一样。写个脚本把剩下的程序对应的答案解出来,可以拼成一个 jpg:

No English writeup here because I don't want to copy the code :)

flag: osu{bl3h_bleh_b13h_m4n_1_l0v3_aut0_r3vs_e4fb25f}

crypto/ssss+

can you do it again, but with hidden?

nc ssssp.challs.sekai.team 1337

from Crypto.Util.number import *
import random

p = 2**255 - 19
k = 15
SECRET = random.randrange(0, p)

def lcg(x, a, b, p):
    return (a * x + b) % p

pp = getPrime(256)
a = random.randrange(0, pp)
b = random.randrange(0, pp)
poly = [SECRET]
while len(poly) != k: poly.append(lcg(poly[-1], a, b, pp))

def evaluate_poly(f, x):
    return sum(c * pow(x, i, p) for i, c in enumerate(f)) % p

print("welcome to ssss", flush=True)
for _ in range(k - 1):
    x = int(input())
    assert 0 < x < p, "no cheating!"
    print(evaluate_poly(poly, x), flush=True)

if int(input("secret? ")) == SECRET:
    FLAG = open("flag.txt").read()
    print(FLAG, flush=True)

大意:素数 p = 2^{255} - 19,另有一个未知的素数 q,满足 2^{255} < q < 2^{256}。有一个未知的 14 次多项式

f = \sum \limits_{i = 0}^{14} c_i x^i \in \mathbb{F}_p[x]

它的系数满足 c_{i + 1} \equiv a c_i + b \pmod {\color{RED}q\color{BLACK}},且 0 \leq c_i < q。你可以询问 f14 个非 0 处的值 (\bmod \ p),求 c_0

首先 crypto/ssss 的做法不管用了,因为我们询问多项式的值只能得到它们 \bmod \ p 的线性关系,放到 \bmod \ q 下就不对了,所以需要找别的方法。

注意到(嗯)12 \mid (p - 1)2p 的原根。令 \omega = 2^\frac{p - 1}{12},那么它是 \mathbb{F}_p 中的 12 阶元,询问 f(\omega^i) \ (i = 0, 1, \dots, 11) 的值就可以求出 c_3, c_4, \dots, c_{11} \bmod p 的值。

因为这里 \dfrac{q}{p} < 3, 所以 c_3, c_4, \dots, c_{11} 的真实值可以枚举。根据 ssss 的经验,我们知道 q \mid ((c_{i + 2} - c_{i + 1})^2 - (c_{i + 1} - c_i) (c_{i + 3} - c_{i + 2}))。所以计算它们的最大公约数就可以把 q 爆出来,然后就可以算出 c_0 了。

另外一个做法是代入 \pm x \ (x = 1, 2, \dots, 7),令 g(x) = f(x) - f(-x),则 g 共有 7 个未知系数,我们有 g(x)7 个值,所以可以把 c_1, c_3, \dots, c_{13} \bmod p 的值解出来。同理可以爆破出来 q

Let p = 2^{255} - 19, we can observe that 12 \mid (p - 1), and 2 is a primitive root of p.

Let \omega = 2^\frac{p-1}{12}, then \omega^i \ (i = 0, 1, \dots, 11) are distinct modulo p, and \omega^{12} \equiv 1 \pmod p (by Fermat's little theorem). We query the values of f(\omega^i) \ (i = 0, 1, \dots, 11).

Why we choose this set of numbers? We can show some examples with lower divisors of p.

Suggest for any prime p, we have an \omega that satisfies \omega^3 \equiv 1 \pmod p. Then,

\begin{aligned} f(1) & \equiv & c_0 & + & c_1 & + & c_2 & + & c_3 & + & c_4 & + & c_5 & + & \cdots \\ f(\omega) & \equiv & c_0 & + & \omega c_1 & + & \omega^2 c_2 & + & \omega^3 c_3 & + & \omega^4 c_4 & + & \omega^5 c_5 & + & \cdots \\ f(\omega^2) & \equiv & c_0 & + & \omega^2 c_1 & + & \omega^4 c_2 & + & \omega^6 c_3 & + & \omega^8 c_4 & + & \omega^{10} c_5 & + & \cdots \\ \end{aligned} \pmod p.

So

\begin{aligned} f(1) & \equiv & (c_0 + c_3 + \cdots) & + & (c_1 + c_4 + \cdots) & + & (c_2 + c_5 + \cdots) \\ f(\omega) & \equiv & (c_0 + c_3 + \cdots) & + & \omega (c_1 + c_4 + \cdots) & + & \omega^2 (c_2 + c_5 + \cdots) \\ f(\omega^2) & \equiv & (c_0 + c_3 + \cdots) & + & \omega^2 (c_1 + c_4 + \cdots) & + & \omega^4 (c_2 + c_5 + \cdots) \\ \end{aligned} \pmod p.

So if 1, \omega, \omega^2 are distinct modulo p, one can directly solve c_0 + c_3 + \cdots, c_1 + c_4 + \cdots, c_2 + c_5 + \cdots.

If you replace 3 with 2, then it's a well-known trick: We can use f(1) and f(-1) to compute the sum of all even-th terms and odd-th terms.

Now we back to our challenge. Here we can solve the values of c_0 + c_{12}, c_1 + c_{13}, c_2 + c_{14}, c_3, c_4, \dots, c_{11} modulo p. Noticing the constraint on magnitude of c_i, there are only about 2 possible values of c_i, so we can brute force it.

Because c_{i + 1} \equiv a c_i + b \pmod{pp}, let d_i = c_{i + 1} - c_i \pmod{pp} then d_{i + 1} \equiv a d_i \pmod{pp} and pp \mid (d_{i + 1}^2 - d_i d_{i + 2}). Computing the GCD of all of these values we can deduce the prime pp.

Then we know a \equiv d_{i + 1} d_i^{-1} and b \equiv c_{i + 1} - a c_i \pmod {pp}. So we can derive our desired c_0.

~Maybe unintended because it only uses 12 querys~

flag: osu{0r_d1d_y0u_us3_fl45hl1ght_1nst34d?}

crypto/please-nominate

ok this time i'm going to be a bit more nice and personal when sending my message

from Crypto.Util.number import *

FLAG = open("flag.txt").read()

BNS = ["Plus4j", "Mattay", "Dailycore"]
print(len(FLAG))

for BN in BNS:
    print("message for", BN)
    message = bytes_to_long(
        (f"hi there {BN}, " + FLAG).encode()
    )
    n = getPrime(727) * getPrime(727)
    e = 3
    print(n)
    print(pow(message, e, n))

output.txt

147
message for Plus4j
398846569478640111212929905737126219425846611917845064245986310899352455531776606361272505433849914145167344554995030812644189047710542954339906669786929747875597103059283954786345252202913509966200329213618547501451752329008531151228646387182403280019664272348231587940227470159846477386295856419407431569159867135365878913551268186765163877676657618137090681937329865631964114691373454627873900294385351135992352798790940857277941472243
158222951303542921410153264594688628146576794503998427093311713650774531277430572380170172031191979123500854263417781975728851277579707820383487572964731381023292312261571110891911863884482902461410218286288183400964045679561296043109527250114073394295486982996930960424139332989226106162113091535475425207610140495532136130360540296097313129598880764160739736823532823136291009499982471028009894097569348660440830784543668141509385395632
message for Mattay
263921537800979838796221921202623647462415714721726394821753160972868778052085367522658133754602607941536627474441882978361116817475949497489399939969612509386335591643019109294105788234910211931439396509289221190345347268312099449152342020093136914687793357372601654532872673983206837150636881928382445566331068237688851345596537893940860402116702078271640048006152159670916299390559068168682951764623558492401318864545619303656361575039
166535918659821916010028099769670832129351247306732465032191446110439632420210106966178779555368253320514619095691319433325610616798380002564235371548166904170934635615481900225094067835806190805967329346507481446735982286060193425107057243896658750175391024795867108700750688771820192759373880324263838550825384190714299697136118712791588291627904942573970568726142296145821435413605295796704576678263679112272532276173497584694652201371
message for Dailycore
244031565800210621970295548144726813179733488382314342571474949081381534498271587584918252306707810369627816196681952536809552862200098862267362735277533022204353497254323228274491354364582967316701126783750290886096960998524414107270804949357307485711415647006606381700355291677615735646360495158637105102935658791364633128882874894799509049190571860852436246528522948513282608143921733438326627413507376148669722384969604597622237576689
132630164676661516893599967289982601955380588903536428955472887691456873565355730161547637935630009622741758822400797894281114572338413548852823840995609427904180355965179631480101131212344121270312182355342132383562008365671754190667582150820337165104642347684925014217687937383396003151315200366708307846959894632317624125785370063052712288658901699972320248281442796056361927746314341729204052997736037101265782906021307934740092047950

设要解的 flag 部分对应的数字为 x, 那么我们已知 (a_i + x)^3 \equiv m_i \pmod{n_i}, i = 1, 2, 3,要求 x.

用中国剩余定理合并一下可以得到 x^3 + c_2 x^2 + c_1 x^1 + c_0 \equiv 0 \pmod{n_1 n_2 n_3}, 然后用 Coppersmith 方法求小根即可。

Let the number representing flag part is x. Then we have (a_i + x)^3 \equiv m_i \pmod{n_i}, i = 1, 2, 3.

By CRT we can construct a polynomial that satisfies x^3 + c_2 x^2 + c_1 x + c_0 \equiv 0 \pmod{n_1 n_2 n_3}, in which x is sufficiently smaller than n_1 n_2 n_3. Then we can use Coppersmith method to compute x.

flag: osu{0mg_my_m4p_f1n4lly_g0t_r4nk3d_1m_s0_h4ppy!!}

队友们做出来的题 / Challenges done by teammates

crypto/rot727

by 会飞的耗子

crypto/pls-nominate

pls help me get the attention of bns by spamming this message to them pls pls pls

from Crypto.Util.number import *

FLAG = open("flag.txt", "rb").read()
message = bytes_to_long(
    b"hello there can you pls nominate my map https://osu.ppy.sh/beatmapsets/2436259 :steamhappy: i can bribe you with a flag if you do: " + FLAG
)

ns = [getPrime(727) * getPrime(727) for _ in range(5)]
e = 5
print(len(FLAG))
# 51

print(ns)
# [250595396282063209494575040924347535718377683482891600245762578107828862102674761114596255712438613333084596184792828788407383247558654559454089469986479081755983932855108019303831103066796233258382489582930536579371270189763162153515702860564938568045151352777820047894231299381399970139347989102071591624967158663442340607621438098266100701196825065519485094630829410736139969891010792546625692049625954656831699035171996469050264653519, 274193894251894220756361545934981580561716587343502325614560502213231097664469582415896327192024233714369720707686115433619798180404789138894067772483823865845960858748702597162370083356345247341014451005796413454404641015516131648586490172413946109076519615677569833823093216546284937656690546486895353267404868698676199564604463636499575466246860736708986174600028222786416474243294353345680209117811499529853520907091701228114817603341, 250799342406461286072464998395337312450245201655027007410237718631499754452822255347456526289099898965321822671514782488741429427305776533799167428860480805487315744950556177330105418061977431001775317350105402223927656557524000999352457757910475375628290615583082601813818635461940350546377443417651889260364946457646723831198413138381669914264256219982213937975414376723125047495586971424241557485231775101178049132539231505246677535311, 424944038623958440933855665649910939213053530859402286143675513220496021786445337272966523453996205752202713497414819150875478398393046433188515528170856189163826639434032436582059667688688880862276540443913407720932133045261989787061306952765245738545030903458101323357019464537917143233906507656465350475977450213990615572573474700619979502153864626401863294100140176446546365233603532530624614938982882757207592863414287369042546971519, 190902360498923981884467164531021372209737709612751157876047387113070619788864781987406983172298076877277648095765822757629049183301893699559004938030166520411955041284763767367633388446007417241478444104664237424445213634253699407015432110027972438018815169650491766550691229860103594060212173628624522421052861949630723039778975476515078589820738377723637869903050748150998629965678848786824733474968760721078438148119740362699143145661]

print([pow(message, e, n) for n in ns])
# [123451724931477698812069511077458860459906769716993869507604368750013627155203649026878565706523272034474865506508962044958969096479439140090681403538412644329412752922637248582028201974150208943081418296518937380419465322558643434318990712657710219963788075009287648875763482189708828745866942138140694074154628539909488581608681566648945293332282252866416448399029058170554552880389525160322280616124297247776106117085199571903380300602, 25521272212086093871680233376409740841684880441320606653986457586214228433726846827063346040622596803682405351090250464809787622301562945715858274147064371839443966925136930647541454865892780911461753966896009773200289476756676124794087249400266226027218836477923252144492106017794796924100502787481309583584738175138358520327875032255246435556182629496953900460417813322557030951101538854106453327597012091626682332420269075467900021711, 185794248348100154380171644978622218400518238250460812348815837123659842567285820889770059992960889881347901994420631735102166283167843240900863871790624347481898956733174665116495731603736544724548220276282583347110470600528846477853732162784786525839648496719341390265839130761362980916014963941737879183454424512265005367317359376039242988696224018798645014519276397488302687294083964113652155465518932071360150626192713920598188877877, 253586754830185402755129821208401339617813079433078480306550244166018490731187464131167959996920120199131859308803805312785667542198282217092146066559207637833505962392131719788571007971893923218099583927291388588829681723690416962688366275898952085934742953047229390090702589945168823704791405472122768397486450092804662113840124827388352338015003693741233274505595280978468538153880244070082267537407664878646371738748713917087874183934, 179921278884389482689970386454786826216817164768869732618011304299464646110222440114625532548731013316437178194666578920837571688939194292542951671294588526331670811293769249832666220726222224721812635640949602967313922271367769076173808739323285134139353621782784562334885703861549547716097420971226456620806331145247840335617343331077891979324239020388123429770254863290698575175653976557893390825861821917810477967554648397683909903482]

by xxuurruuii

因为给出了 x^5 \equiv m_i \pmod{n_i} \ (i = 1, 2, \dots, 5), 用中国剩余定理合并得到 x^5 \equiv m \pmod{n_1 n_2 n_3 n_4 n_5}, 然后用 Coppersmith 攻击即可。

Weaker version of crypto/please-nominate, we have x^5 \equiv m_i \pmod{n_i} \ (i = 1, 2,\dots, 5), then we combine them using CRT and get x^5 \equiv m \pmod{n_1 n_2 n_3 n_4 n_5}, then use Coppersmith.

flag: osu{pr3tty_pl3453_w1th_4_ch3rry_0n_t0p!?:pleading:}

forensics/map-dealer

We have confiscated a USB drive from sahuang, whom we were informed was trying to sell a beatmap containing some confidential data of the community to the dark web. However, the beatmap was nowhere to be found from the drive when we mounted it to our computer. Can you help recover it?

forensics_map-dealer.tar.gz

by nick-haoran

下载 X-Ways Forensics,然后要的文件就在那里我也不知道为什么总之就是做出来了

Download X-Ways Forensics and the osz file is just there and flag is in it idk

flag: osu{I_hope_my_h4ndwr1ting_is_readable_xd}

kaijuu/怪獣怪獣怪獣怪獣怪獣怪獣怪獣

I can only see 怪獣 in my nightmare...

Wrap flag in osu{...} to submit.

by xxuurruuii

下载谱面发现谱面里有朝左的怪兽和朝右的怪兽,总共有49个,考虑7个一组二进制得到了flag

In KAIJUUKAIJUUKAIJUU difficulty of the mapset, there are sliders representing monsters, either facing left or right.

Take right as 1 and left as 0 then you'll get

1001011011010001100011001010111010110101010100001

There are 49-bits so we group it in 7:

1001011
0110100
0110001
1001010
1110101
1010101
0100001

Then decode as ascii so we get flag.

flag: osu{K41JuU!}

misc/pump-lion

PumpLion, the cheerful lion mascot of osu!gaming CTF, has been deployed as a "super-secure" chatbot for event participants. Rumor has it... the flag is hidden deep in its silicon jungle. But don't expect it to just hand it to you on a silver platter - it's been "prompt-trained" to protect it at all costs.

Note: rate limiting is enforced, do not spam the LLM.

by xxuurruuii

把sys prompt搞出来,看到sys prompt里没有flag而且强调了github,按他说的账号去github上找到了一个仓库里有flag

Ask a for system rules, he will notice a github user called brad-moon-chua, and we can find the flag in his only repository.

flag: osu{Pump_4_Fun_0n_osu}

osint/weebbolt

This challenge is a lot more fun when you're a weeb.

https://weebbolt-web.challs.sekai.team/

by our team

图寻题,很多题可以用图寻 AI 解;henry_in_out 熟悉孤独摇滚所以猜出了 loner 是 STARRY;我 Google 搜图搜出来了 technology 的位置。

都来看孤独摇滚

For most tasks we used a geoguess ai to find the location.

For loner henry_in_out has watched Bocchi the Rock so he knew this is STARRY.

For technology I searched this image on Google then it gave me the exact location.

Go watch Bocchi the Rock

flag: osu{https://i.ibb.co/xTx6Nns/image.png}

osu/files

My filesystem got corrupted somehow... can you find the odd one out? The flag is the file name that doesn't belong, wrapped in osu{}.

osu_files.tar.gz

by our team

这是 lazer 的文件系统。首先寻找里面所有谱面的 BeatmapSetID 然后下载对应的谱面,把谱面里的文件都去除之后还剩一个未知文件,一个 triangles 的谱面,一个 circles! 的谱面和 triangles 的音频。

每个文件名都尝试一遍后发现 circles! 的谱面是答案。

This is lazer filesystem. There are osu beatmap files in it, so we downloaded all related online mapsets. Removing the files from these we have 4 files left, one is unknown, one is a beatmap of song cYsmix - triangles, one is a beatmap of song cYsmix - circles! and remaining is cYsmix - triangles.mp3.

Tried all 4 file names and the beatmap of circles! worked.

flag: osu{80e4c02268d49ca010e3c62fcc2615da2fad4cf0c359eb8fedc0366739b34205}

osu/survey

free points yay

https://forms.gle/ocAr8RSgmUHZ2ArH9

by xxuurruuii

填问卷送 points

Just take the survey

flag: osu{see_you_next_time}

ppc/modulation-master

by xxuurruuii

pwn/username-checker

by 会飞的耗子

rhythm/ksh

This map is pretty cool... but I hear the latest version is better.

rhythm_ksh.tar.gz

by xxuurruuii

下载下来是个 K-shoot mania 谱面,题面里说 the lastest version is better 所以就去找自制社群里上传的谱面,flag 在 description 和谱面里面。

This is a K-shoot mania chart, and the challenge tells the lastest version is better, so search title at Nautica then you'll find the latest version.

Flag is in description and inside the chart.

flag: osu{f4iry_d4nc1ng_in_l4k3_eeeeee}

rhythm/pulsus

Check out Pulsus, a rhythm game for PC in which you hit incoming beats on a 3x3 tile board! There's a flag in my account, can you find it?

Note: no account signup needed. Please do not try to perform malicious actions on the site, no brute-force tools are necessary and will ban you.

by xxuurruuii

题目让你去找作者 (Quintec) 的 Pulsus 账号,进去游戏之后点排名搜 Quintec 就能搜索到,然后发现页面啥也没有。

开控制台重新找一遍发现 flag 在服务器返回的请求里,然后就做完了。

After finding Quintec's Pulsus account, there is nothing on the description.

Open the console and do it again, you'll find the flag is in server response.

flag: osu{pl4y_pul5u5_e6234bd516f}

rhythm/circles

Both osu! and this game consist of circles... And the latest version of the game is also named CiRCLE...

Anyway, we found out an evil group is trying to encode some secret message in their usernames.

The only info we get is the image below, please help us!

P.S. The flag contains non-ascii characters, and you should wrap it in osu{FLAG} format.

P.P.S. You don't have to touch grass in order to solve this challenge :)

by xxuurruuii

右下角的 IGAME(TAI PO) 是打出这个成绩的机厅的名字,搜索这个机厅可以找到一个 Youtube 账号。

根据成绩打出的时间找到那一天的直播回放 1:36:45 处,得到用户名是 SfCfkpZN 和 NGnwn5iL,连起来 base64 解码得到 flag.

The IGAME(TAI PO) at bottom-right is where the player played at. The game center has a Youtube account.

Based on the time on the score, we can find this livestream. At 1:36:45 you can find the play and get their username SfCfkpZN, NGnwn5iL. Concatenate and decode as base64 gives the desired flag.

flag: osu{I💖M4i😋}

rhythm/lunatic-ksm

There's a reason why the term "illegal pattern" exists.

発狂KSM os/us難易度表 (v5.0)

Make sure to submit flag content in uppercase, like this: osu{HELLO_WORLD}

rhythm_lunatic-ksm.tar.gz

by nick-haoran

打开 https://viewksm.dev/ 查看谱面然后就可以发现 flag 在谱面结尾

Go https://viewksm.dev/ to view the chart, and you'll find the flag is at the end of the chart.

flag: osu{KONASUTE_IS_A_SCAM}

rhythm/hidden-signal

Someone showed me a "map" and he claimed to have found a hidden signal in it. Can you decode it?

Turn the answer to uppercase and wrap it in osu{FLAG} format.

Note: A 0 is missing in the string before decoding the flag. It is in the last third of the flag and you could notice it when you get to the part. It should be easily guessable where to add the 0. Sorry for the confusion.

rhythm_hidden-signal.tar.gz

by xxuurruuii

打开谱面看到一堆意味不明的 10 分音,左手当 0 右手当 1 得到的东西培根解码得到 flag.

Open the chart we can find non-sense 10th-notes, take left hand as 0 and right hand as 1, using Bacon then you'll get the flag.

flag: osu{MAIMAICIRCLE}

web/admin-panel

by nick-haoran

web/osu-css

by nick-haoran

web/scorepost-generator

by nick-haoran