UTCTF 2025

比赛地点:UTCTF 2025

比赛时间:15 Mar 2025 07:00 CST - 17 Mar 2025 07:00 CST

复现的题目用🔁标注


Misc

Trapped in Plain Sight 1

可以看到 flag.txt 的文件权限为 -r-x------

先运行 find / -perm -4000 -type f 2>/dev/null 查找具有 SUID 权限的文件

1

在这里找到了 xxd

xxd 主要用于将文件内容以十六进制和 ASCII 的形式显示出来。它通常用于调试、分析二进制文件或查看文件的原始数据。

因此这里直接使用 xxd flag.txt 就可以查看这个文档里的内容了

2

🔁 Trapped in Plain Sight 2

4

ls -lah 发现文件的权限是 ----r-----+

这里的 + 表示该文件或目录启用了访问控制列表(ACL, Access Control List) 。这意味着除了标准的文件权限外,还有一些额外的权限设置。

5

getfacl flag.txt 查看文件的 ACL 配置,发现用户 secretuser 对该文件有读权限

6

cat /etc/passwd 发现 GECOS 字段(注释字段)藏有信息 hunter2,这很可能是一个提示信息或某种隐藏线索

因此尝试切换到 secretuser 用户 su secretuser,密码就尝试 hunter2,发现成功了

7

直接 cat 就能拿到 flag 了


Forensics

Streamified

附件如下

1
1111111000011110101111111100000100110101100100000110111010110110111010111011011101010101001101011101101110101001010010101110110000010100101111010000011111111010101010101111111000000001011110100000000010111110001110110011111000111010101100000010100000100011110111100101110111100000100001010100010000011000001000000001011011111100010001010111011100011010100010101001111100110111011100001001100110000011100001100110101011111111100000000110000001000110101111111001111001101010011100000101101001010001000010111010111100011111111011011101011001110011010011101110101010011110010010110000010011011001011100011111111010101010000010111

数了下有 625 个,也就是 25*25 个,不难发现这是一个 25*25 的二维码,写个脚本把它画出来就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bitstring = "1111111000011110101111111100000100110101100100000110111010110110111010111011011101010101001101011101101110101001010010101110110000010100101111010000011111111010101010101111111000000001011110100000000010111110001110110011111000111010101100000010100000100011110111100101110111100000100001010100010000011000001000000001011011111100010001010111011100011010100010101001111100110111011100001001100110000011100001100110101011111111100000000110000001000110101111111001111001101010011100000101101001010001000010111010111100011111111011011101011001110011010011101110101010011110010010110000010011011001011100011111111010101010000010111"
# print(len(bitstring)) # 625

from PIL import Image

def generate_image_from_binary(binary_str, pixel_size=25):
width = pixel_size
height = len(binary_str) // pixel_size

image = Image.new("1", (width, height), color=1)

for i, bit in enumerate(binary_str):
if bit == '1':
x = i % width # 列索引
y = i // width # 行索引
image.putpixel((x, y), 0)

return image

image = generate_image_from_binary(bitstring)
image.show()

得到如下二维码

3

扫码即可得到 flag


Crypto

Crypto 是拿 AI 一把梭的,所以只有 AI 给的过程和 exp

RSA

题目如下

RSA

Idk why people make e so large for rsa… it’s so much easier to just use 3. Why use big number when small number do trick?

by jocelyn (@jocelyn3270 on discord)

附件如下

1
2
3
n: 21507386633439519550169998646896627263990342978145866337442653437291500212804540039826669967421406761783804525632864075787433199834243745244830254423626433057121784913173342863755047712719972310827106310978325541157116399004997956022957497614561358547338887866829687642469922480325337783646738698964794799137629074290136943475809453339879850896418933264952741717996251598299033247598332283374311388548417533241578128405412876297518744631221434811566527970724653020096586968674253730535704100196440896139791213814925799933321426996992353761056678153980682453131865332141631387947508055668987573690117314953760510812159
e: 3
c: 6723702102195566573155033480869753489283107574855029844328060266358539778148984297827300182772738267875181687326892460074882512254133616280539109646843128644207390959955541800567609034853

为了破解这个 RSA 加密,我们利用小指数攻击。由于加密指数 e=3,如果明文 m 的三次方小于模数 n,那么密文 c 即为 m³,直接对 c 开三次方即可得到明文 m。

步骤如下:

  1. 验证条件:检查密文 c 的三次方根是否为整数。
  2. 计算三次方根:使用 Python 的 gmpy2 库中的 iroot 函数计算 c 的三次方根。
  3. 转换明文:将得到的整数转换为十六进制,并解码为 ASCII 字符串。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import gmpy2

c = 6723702102195566573155033480869753489283107574855029844328060266358539778148984297827300182772738267875181687326892460074882512254133616280539109646843128644207390959955541800567609034853

# 计算c的三次方根
m, is_exact = gmpy2.iroot(c, 3)

# 确保结果为整数
if is_exact:
# 转换为十六进制并去除前缀'0x'
hex_str = hex(m)[2:]
# 补零确保偶数长度
if len(hex_str) % 2 != 0:
hex_str = '0' + hex_str
# 转换为字节并解码为ASCII
flag = bytes.fromhex(hex_str).decode('utf-8')
print(flag)
else:
print("无法直接开三次方根")

结果:

运行代码后,得到的明文为:

1
utflag{hmm_maybe_bad_idea}

DCΔ

题目如下

DCΔ

Due to a national shortage of primes, the US Department of Agriculture is rationing all citizens to a limit of one generated prime number per CTF challenge.

By Sasha (@kyrili on discord)

附件如下

1
2
3
n = 399956368360808862373914258335185223080849636197711424060797090309268643429064461492550414549161330948819635837600839124910339139212025975705016633767495247163243281423582407941339197895052969960664399531226116807938480610953640675838340969642399505783577667601230289640157854573282615113017817753471366212008719316238931155299741896658264134636523008018510523774126757209492757800553768281613227711738371473830681563493341816035127889532515105148615575695347672918819305383651095344758737833444302556494599778991752161562622963652164008980839152347260377969421014616624263631920322958235478733540894255954351848359580013695597870908080170511403061620632540407634608773118202473287854599776791229532885611074739079107324575619148211269673210431496846247978541032947073060592123529635361112170678347924377962162254827262375685704046691718585952854410058401794022674628779309507437739620598639589987596443373586284136126401843497367142210715014480599609277532331148988390798073713743339823218981940779096432112651466716648010370850152213399051968069102663753404120592506704133217642671853086570223710424683386625314802805217882906873879240914022607713870946351691046929143491841506422542038315876506588525639983398522454145866029283449
e = 65537
c = 22644125297186385803212285721101686380290089858624593588464228942417644877688212364383835956263619653769244324906844180248816686517049952319431524113838480708352331162026595736354019259708442449783760846242702532176456117138374450898213788623580234048867117546091028843127595147910526821835855070663317466469650577618010308109119812464711010326075908158768138773973732088207030977470605554056485614676156104134673446546446752627654287202815354367643042773923258958887865030737447323798382020847653880886311162447594373201951226217556835030816588457674298560260109378271244834215832992407457137601161490484862135147963942227371690835380497920998286827898323068399708168699403459009009580152834747843780155917438758224782364193716322974594031272100820264364860227674838730962348140555980411714722361909800417953974064469599278274083750031569853934963716467881656073359393449142980936480726005445774158733389270553554093627622406166942859792490275434896108377393648278975530519769034633686070931694499857110956537102727286491854314244036392929790997824274724196292688659782806587688964714529943288954314300861531138101192901942534064757877725334672680909389193357725470116673323012331269218651347104807494994267835408427908717684178629

为了破解这个 RSA 加密,利用题目中提到的 “每个 CTF 挑战只能生成一个质数” 的漏洞,推测模数 $ n可能是某个质数可能是某个质数 p的平方。通过验证的平方。通过验证 n是否为平方数,成功分解得到是否为平方数,成功分解得到 p $,进而计算私钥并解密。

步骤如下:

  1. 验证 $ n是否为平方数:使用gmpy2.isqrt计算是否为平方数:使用 `gmpy2.isqrt` 计算 n $ 的平方根,并检查平方是否等于原数。
  2. 计算欧拉函数 $ \phi (n):若:若 n = p^2,则,则 \phi(n) = p(p-1) $。
  3. 求私钥 $ d $ :计算 $ d = e^{-1} \mod \phi (n) $。
  4. 解密密文:计算 $ m = c^d \mod n $。
  5. 转换明文:将整数 $ m $ 转换为十六进制并解码为 ASCII 字符串。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import gmpy2

n = 399956368360808862373914258335185223080849636197711424060797090309268643429064461492550414549161330948819635837600839124910339139212025975705016633767495247163243281423582407941339197895052969960664399531226116807938480610953640675838340969642399505783577667601230289640157854573282615113017817753471366212008719316238931155299741896658264134636523008018510523774126757209492757800553768281613227711738371473830681563493341816035127889532515105148615575695347672918819305383651095344758737833444302556494599778991752161562622963652164008980839152347260377969421014616624263631920322958235478733540894255954351848359580013695597870908080170511403061620632540407634608773118202473287854599776791229532885611074739079107324575619148211269673210431496846247978541032947073060592123529635361112170678347924377962162254827262375685704046691718585952854410058401794022674628779309507437739620598639589987596443373586284136126401843497367142210715014480599609277532331148988390798073713743339823218981940779096432112651466716648010370850152213399051968069102663753404120592506704133217642671853086570223710424683386625314802805217882906873879240914022607713870946351691046929143491841506422542038315876506588525639983398522454145866029283449
e = 65537
c = 22644125297186385803212285721101686380290089858624593588464228942417644877688212364383835956263619653769244324906844180248816686517049952319431524113838480708352331162026595736354019259708442449783760846242702532176456117138374450898213788623580234048867117546091028843127595147910526821835855070663317466469650577618010308109119812464711010326075908158768138773973732088207030977470605554056485614676156104134673446546446752627654287202815354367643042773923258958887865030737447323798382020847653880886311162447594373201951226217556835030816588457674298560260109378271244834215832992407457137601161490484862135147963942227371690835380497920998286827898323068399708168699403459009009580152834747843780155917438758224782364193716322974594031272100820264364860227674838730962348140555980411714722361909800417953974064469599278274083750031569853934963716467881656073359393449142980936480726005445774158733389270553554093627622406166942859792490275434896108377393648278975530519769034633686070931694499857110956537102727286491854314244036392929790997824274724196292688659782806587688964714529943288954314300861531138101192901942534064757877725334672680909389193357725470116673323012331269218651347104807494994267835408427908717684178629

p = gmpy2.isqrt(n)
assert p * p == n, "n is not a perfect square"

phi = p * (p - 1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)

# Convert to flag
hex_str = hex(m)[2:]
if len(hex_str) % 2 != 0:
hex_str = '0' + hex_str
flag = bytes.fromhex(hex_str).decode('utf-8')
print(flag)

结果:

运行代码后,得到的明文为:

1
utflag{th3_t0t13nt_funct10n_uns1mpl1f13d}

Autokey Cipher

题目如下

Autokey Cipher

I know people say you can do a frequency analysis on autokey ciphers over long text, but the flag is short so it’ll be fine.

lpqwma

by jocelyn (@jocelyn3270 on discord)

忘记是怎么解的了,好像是维吉尼亚密码的变种,下面是 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import string

# 定义字母表
ALPHABET = string.ascii_lowercase

# 解密函数:自动密钥解密
def decrypt_autokey(ciphertext, key):
plaintext = []
key = list(key) # 将密钥转换为列表以便动态扩展
for i, c in enumerate(ciphertext):
if c.lower() not in ALPHABET:
# 如果字符不在字母表中(如空格或标点符号),直接保留
plaintext.append(c)
continue

# 获取当前密钥字符
current_key = key[i] if i < len(key) else plaintext[i - len(key)]

# 计算解密字符:(密文 - 密钥) mod 26
c_idx = ALPHABET.index(c.lower())
k_idx = ALPHABET.index(current_key)
decrypted_char = ALPHABET[(c_idx - k_idx) % 26]
plaintext.append(decrypted_char)

# 动态扩展密钥:将解密后的字符添加到密钥中
if i >= len(key):
key.append(decrypted_char)

return ''.join(plaintext)

# 主程序
ciphertext = "lpqwmarwsywpqaauadrrqfcfkqwueyifwoxlkvxawjhpkbgrzf"
initial_key = "rwllmuvputflagwhyfrequencyanalysiswhenknowbeginning"

# 解密密文
decrypted_text = decrypt_autokey(ciphertext, initial_key)

# 输出结果
print(f"Initial Key: {initial_key}")
print(f"Decrypted Text: {decrypted_text}")

结果:

运行代码后,得到的明文为:

1
utflag{why_frequency_analysis_when_know_beginning_letters}