题目来源:NSSCTF

练习时间:2026年2月4日

练习数量:4

上篇:CTF-SWPUCTF2025秋季新生赛4

📖 SWPUCTF 2025 秋季新生赛合集
1️⃣CTF-SWPUCTF2025秋季新生赛1
2️⃣CTF-SWPUCTF2025秋季新生赛2
3️⃣CTF-SWPUCTF2025秋季新生赛3
4️⃣CTF-SWPUCTF2025秋季新生赛4
5️⃣CTF-SWPUCTF2025秋季新生赛5


⭐️ 14 [SWPUCTF 2025 秋季新生赛]factor?mod?

🚩flag:NSSCTF{Wh@t_4r3_y0u_pO1nt?}

💡hint:CRYPTO RSA 素数分解

🔧tool:python

原题:

from Crypto.Util.number import getPrime, inverse, bytes_to_long, GCD

FLAG = b"NSSCTF{*******}"

p = getPrime(496)
q = getPrime(16)
N = p * q
phi = (p - 1) * (q - 1)

for k_candidate in range(2, 200):
e_candidate = 65537 * k_candidate + 1
if GCD(e_candidate, phi) == 1:
k = k_candidate
e = e_candidate
break

d = inverse(e, phi)
m = bytes_to_long(FLAG)
c = pow(m, e, N)


print(f"N = {N}")
print(f"c = {c}")
print(f"e_mod_65537 = {e % 65537}")
print(f"PHI_mod_65537 = {phi % 65537}")

# N = 7766768698831459057447624753799597048605145237159899787468949300915452509464565099496679099145810975822175634998097262892579678554704760323902554901001931
# c = 3032034114991327495494398063970483186189941361342590220935258419146172566936781482456512156238329348954750763279841189333666924656636066281016492074315344
# e mod 65537 = 1
# PHI_mod_65537 = 43577

q = getPrime(16) 只有 16 bit,也就是一个很小的素数因子,所以 N 可以直接被快速分解,一旦分解出 p,q,RSA 就能正常求私钥解密。

解题脚本:

from Crypto.Util.number import inverse, long_to_bytes, GCD
import sympy as sp

N = 7766768698831459057447624753799597048605145237159899787468949300915452509464565099496679099145810975822175634998097262892579678554704760323902554901001931
c = 3032034114991327495494398063970483186189941361342590220935258419146172566936781482456512156238329348954750763279841189333666924656636066281016492074315344

# 1) factor N: q is 16-bit prime
q = None
for prime in sp.primerange(2, 1 << 16):
if N % prime == 0:
q = prime
break

p = N // q
phi = (p - 1) * (q - 1)

# 2) recover e from e = 65537*k + 1
for k in range(2, 200):
e = 65537 * k + 1
if GCD(e, phi) == 1:
break

# 3) decrypt
d = inverse(e, phi)
m = pow(c, d, N)
print(long_to_bytes(m))

解题结果:
b‘NSSCTF{Wh@t_4r3_y0u_pO1nt?}’


⭐️ 15 [SWPUCTF 2025 秋季新生赛]ezmod

🚩flag:NNSSCTF{Wh4t_3xact1y_d0e5_an_8_a.m._c1a5s_m3an}

💡hint:CRYPTO RSA 素数分解 费马小定理

🔧tool:python

原题:

from Crypto.Util.number import *
from libnum import *
from gmpy2 import *

flag = b"NSSCTF{***************}"
m1 = s2n(flag[0:len(flag)//2])
m2 = s2n(flag[len(flag)//2:])

p1 = getPrime(512)
q1 = getPrime(512)
n1 =p1*q1
e1 = getPrime(15)
e2 = getPrime(15)
hint1 = pow(e2*p1+e1*q1,2,n1)
hint2 = pow((p1+e2)*q1,e1,n1)
c1=pow(m1,65537,n1)


p2 = getPrime(512)
q2 = getPrime(512)
r = getPrime(512)
n2 = p2*q2*r
hint3 = pow(p2*q2**2+r,p2,n2)
hint4 = pow(p2*q2*r+q2+r,p2,n2)
hint5 = pow(r**2+1,m2,r**3)

print('n1 = ',n1)
print('e1 = ',e1)
print('e2 = ',e2)
print('c1 = ',c1)
print('hint2 = ',hint2)
print('hint1 = ',hint1)
print('n2 = ',n2)
print('hint3 = ',hint3)
print('hint4 = ',hint4)
print('hint5 = ',hint5)


# n1 = 69237412819939639452602923767390021440483462182682879972053689337566305146360670141885719490681273734760061512519320363972997519120208982200937497939609239656895817642113833504695009955103513083736827796959971881574254357515577554120238786887083984552104374617519795066378544484239723381870277406815344816333
# e1 = 29527
# e2 = 20117
# c1 = 29091310695342856566138644401065835896832250861184448854997851711346752666744213880628516094732261931894315036306410329745248525044513718362465300436701334890744982477672840197643709318354899748726146475571529311179946813181381358178861493626190582931509706101754396232759316452157394231162762993017545388413
# hint2 = 35064290617684889168326866883116063909046848851566428690678819117544793799571693709457524424684621704495903073634509282445170100417712329053124327037228710541383897618165318789436757925443415160782651229020549677843858532532771577582051147943146243696815139130166282984736042542125453264858041896972533226829
# hint1 = 6684343928626259115166556536967237351704873870967672531851104012737638516939912489158888115396930384822560698673563498898490639775761096112411751814558903510006095703737660799311396164641693857972158521286705142411677136118464043578480075934828150802349974263484801212589479718679655427434597007764470156881
# n2 = 1345452346323458846685078201357739237930456697726463214841840378972748510374939760683716312429693260865118966731854460154556882413775350647221842518311385613574199367419368259373562227767966719390051368563739831674520939480848992603419586010579222762497163771709730786459075520090201718825633880843179147482733505895506557434742546890561936473748675630866436648001676410604303149849018119601033995027851824674505929142399828357204316834997808360126991477224901719
# hint3 = 847188571113943173287332469527967344011334943746227619399621256260877662268276902469817095373874888480020071468062585786037818342625399891363280962564316441064449832009670275586211975663877027865035973739632012813329247804225301535242488377690663593897904931237203520895777439199763949284015075990326550163802234666394658195172638253704737050298013115065181864025540632934830257887421985531190093051146583215774752000629314051518227632851605979279986676394796845
# hint4 = 806668794330377497579889477399120279541227586456038436879781285391665348755137976786329245671139948859359946788143532968544043837955414909826507013343756603308599647221325686544191625843232340416160397119858073908352503811559979566876363266612113621634034075141694073726351946703452197479179194460281502325911084357841305170261155379651873650770166579405942671807649621165262056859860734822943050236603538147024706785308699380519584964634415545709968346683403784
# hint5 = 824023351148258203438869300498693582257573642933589256075919307337418863180477757714828211190934112905395542087377464543208715687979548609801730334216105292953134985687665222287478128775799341477741181300324116951794054625966987353659492980465028187867334398317297053360048397682372264656563428873630084268151903278298458772453240199263614327092856105929207255270

解题脚本:

# -*- coding: utf-8 -*-
# solve.py (Python 3)
from Crypto.Util.number import long_to_bytes, inverse, GCD

# ====== given ======
n1 = 69237412819939639452602923767390021440483462182682879972053689337566305146360670141885719490681273734760061512519320363972997519120208982200937497939609239656895817642113833504695009955103513083736827796959971881574254357515577554120238786887083984552104374617519795066378544484239723381870277406815344816333
e1 = 29527
e2 = 20117
c1 = 29091310695342856566138644401065835896832250861184448854997851711346752666744213880628516094732261931894315036306410329745248525044513718362465300436701334890744982477672840197643709318354899748726146475571529311179946813181381358178861493626190582931509706101754396232759316452157394231162762993017545388413
hint2 = 35064290617684889168326866883116063909046848851566428690678819117544793799571693709457524424684621704495903073634509282445170100417712329053124327037228710541383897618165318789436757925443415160782651229020549677843858532532771577582051147943146243696815139130166282984736042542125453264858041896972533226829
hint1 = 6684343928626259115166556536967237351704873870967672531851104012737638516939912489158888115396930384822560698673563498898490639775761096112411751814558903510006095703737660799311396164641693857972158521286705142411677136118464043578480075934828150802349974263484801212589479718679655427434597007764470156881

n2 = 1345452346323458846685078201357739237930456697726463214841840378972748510374939760683716312429693260865118966731854460154556882413775350647221842518311385613574199367419368259373562227767966719390051368563739831674520939480848992603419586010579222762497163771709730786459075520090201718825633880843179147482733505895506557434742546890561936473748675630866436648001676410604303149849018119601033995027851824674505929142399828357204316834997808360126991477224901719
hint3 = 847188571113943173287332469527967344011334943746227619399621256260877662268276902469817095373874888480020071468062585786037818342625399891363280962564316441064449832009670275586211975663877027865035973739632012813329247804225301535242488377690663593897904931237203520895777439199763949284015075990326550163802234666394658195172638253704737050298013115065181864025540632934830257887421985531190093051146583215774752000629314051518227632851605979279986676394796845
hint4 = 806668794330377497579889477399120279541227586456038436879781285391665348755137976786329245671139948859359946788143532968544043837955414909826507013343756603308599647221325686544191625843232340416160397119858073908352503811559979566876363266612113621634034075141694073726351946703452197479179194460281502325911084357841305170261155379651873650770166579405942671807649621165262056859860734822943050236603538147024706785308699380519584964634415545709968346683403784
hint5 = 824023351148258203438869300498693582257573642933589256075919307337418863180477757714828211190934112905395542087377464543208715687979548609801730334216105292953134985687665222287478128775799341477741181300324116951794054625966987353659492980465028187867334398317297053360048397682372264656563428873630084268151903278298458772453240199263614327092856105929207255270

# ====== Part 1: recover (p1, q1) and decrypt m1 ======
q1 = GCD(hint2, n1)
assert q1 != 1 and n1 % q1 == 0, "Failed to recover q1"

p1 = n1 // q1
phi1 = (p1 - 1) * (q1 - 1)

E = 65537
d1 = inverse(E, phi1)
m1_int = pow(c1, d1, n1)
m1 = long_to_bytes(m1_int)

print("[+] q1 =", q1)
print("[+] p1 =", p1)
print("[+] m1 =", m1)

# (Optional) quick sanity: hint2 should be multiple of q1
assert hint2 % q1 == 0

# ====== Part 2: recover r from gcd(hint5-1, n2), then get m2 from (hint5-1)/r^2 ======
r = GCD(hint5 - 1, n2)
assert r != 1 and n2 % r == 0, "Failed to recover r"

# Because hint5 < r^3, we can extract m2 exactly from binomial expansion:
# hint5 ≡ 1 + m2*r^2 (mod r^3) and 0 <= hint5 < r^3 => hint5-1 = m2*r^2
m2_int = (hint5 - 1) // (r * r)
m2 = long_to_bytes(m2_int)

print("[+] r =", r)
print("[+] m2 =", m2)

# (Optional) recover q2 from gcd(hint3-hint4, n2)
q2 = GCD(hint3 - hint4, n2)
# gcd may return q2 or q2*r (rare if (hint3-hint4) also multiple of r)
if q2 % r == 0:
q2 //= r
assert q2 != 1 and n2 % q2 == 0, "Failed to recover q2"

p2 = n2 // (q2 * r)

print("[+] q2 =", q2)
print("[+] p2 =", p2)

# ====== assemble flag ======
flag = m1 + m2
print("\n[+] FLAG =", flag.decode(errors="replace"))

解题思路:
这道题本质是利用特殊构造的模幂表达式,通过取模与 gcd 泄露 RSA 素因子或明文。第一部分中,hint2 = ((p1+e2)·q1)^{e1} mod n1,其底数在模 n1=p1q1 下等价于 e2·q1,因此整个 hint2 必然含有因子 q1,直接计算 gcd(hint2, n1) 就能恢复 q1,从而分解 n1 并用标准 RSA 解出第一段明文 m1;第二部分中,hint5 = (r²+1)^{m2} mod r³ 利用二项式展开可得 hint5 ≡ 1 + m2·r² (mod r³),而由于结果本身小于 r³,所以 m2 = (hint5−1)/r² 可以被直接还原,同时 gcd(hint5−1, n2) 会泄露素数 r;至于 hint3 和 hint4,它们在模 q2 下的底数同余,从而使 hint3−hint4 成为 q2 的倍数,用 gcd(hint3−hint4, n2) 可分解出 q2 作为验证。整题的核心就是:精心设计的幂模形式 + 模数结构,使得 gcd 和低阶二项式展开直接泄露关键信息。


⭐️ 16 [SWPUCTF 2025 秋季新生赛]哈基米南北露躲

🚩flag:NSSCTF{welcome_to_2025_SWPU!}

💡hint:MISC

🔧tool:python

题目:
哈吉米题目
扫描二维码得到key:hajimi
然后再看txt

米米米哈基哈基哈哈哈哈哈哈基哈哈基米哈哈哈哈基哈基哈哈哈基基米哈哈米米米哈基基米哈基哈基基基米米哈米米米基哈米米米哈基基米米米哈米米基基哈基基米基基基基哈哈基哈基基哈哈哈米哈哈基基基基基基哈基哈哈基米米米哈基基基米哈米哈哈哈基基基哈基基哈哈哈米米哈基基米哈哈哈基哈米米哈基哈哈基基米哈基基基基基基基米哈哈基基米米基哈米基米哈基米米哈米哈基基哈基基基米哈基米哈哈基哈哈基哈米哈哈米基基基米基基基哈哈基基基哈米基基哈基哈米米米基哈基哈哈基哈哈米哈哈哈基哈哈米米基米哈基哈哈哈米米哈哈哈基哈基米米米哈哈基米基基哈基基基哈基基米基

解题脚本:

# -*- coding: utf-8 -*-
# Python 3

def decode_hajiami(encoded_str: str, private_key: str = "hajimi") -> str:
# 三符号到三进制 digit 的映射(WP 给定)
mapping = {'哈': 0, '基': 1, '米': 2}

# 固定凯撒基准偏移
base_shift = 3

# 生成私钥偏移数组:把 key 每个字符的 Unicode 十进制展开成数字序列
shift_array = []
if private_key:
for ch in private_key:
shift_array.extend(int(d) for d in str(ord(ch)))

# 三种长度类型对应的数据位数(trits 数)
len_table = [6, 9, 12]

out = []
pos = 0
char_index = 0

def need(n: int):
if pos + n > len(encoded_str):
raise ValueError(f"密文不足:当前位置 {pos} 需要 {n} 个符号,但总长 {len(encoded_str)}")

while pos < len(encoded_str):
# 1) 读取 2 位随机偏移编码
need(2)
a = encoded_str[pos]
b = encoded_str[pos + 1]
pos += 2
if a not in mapping or b not in mapping:
raise ValueError(f"出现非法符号:{a}{b} (pos={pos-2})")

random_shift = mapping[a] * 3 + mapping[b] # 0..8

# 2) 读取 1 位长度标识
need(1)
lc = encoded_str[pos]
pos += 1
if lc not in mapping:
raise ValueError(f"出现非法符号:{lc} (pos={pos-1})")

# 还原长度类型: (length_digit - random_shift) mod 3
length_type = (mapping[lc] - random_shift) % 3
data_length = len_table[length_type]

# 3) 读取数据部分
need(data_length)
data_code = encoded_str[pos:pos + data_length]
pos += data_length
for ch in data_code:
if ch not in mapping:
raise ValueError(f"出现非法符号:{ch} (data block)")

# 4) 数据部分转三进制串
trits = ''.join(str(mapping[ch]) for ch in data_code)

# 5) 复原循环移位
# 加密:original -> 左移 random_shift 得到 trits
# 解密:trits -> 右移 random_shift 复原
# 等价写法:original = trits[random_shift:] + trits[:random_shift] (WP 版本)
# 这里保持与 WP 一致:
original_trits = trits[random_shift:] + trits[:random_shift]

# 6) 三进制 -> 十进制(得到“加密态 unicode”)
enc_unicode = int(original_trits, 3)

# 7) 私钥偏移(循环使用)
private_shift = shift_array[char_index % len(shift_array)] if shift_array else 0

# 总偏移
total_shift = base_shift + random_shift + private_shift

# 8) 还原原始 Unicode 并转字符
orig_unicode = enc_unicode - total_shift
out.append(chr(orig_unicode))

char_index += 1

return ''.join(out)


if __name__ == "__main__":
encoded_text = (
"米米米哈基哈基哈哈哈哈哈哈基哈哈基米哈哈哈哈基哈基哈哈哈基基米哈哈米米米哈基基米哈基哈基基基米米哈米米米基哈米米米哈基基米米米哈米米基基哈基基米基基基基哈哈基哈基基哈哈哈米哈哈基基基基基基哈基哈哈基米米米哈基基基米哈米哈哈哈基基基哈基基哈哈哈米米哈基基米哈哈哈基哈米米哈基哈哈基基米哈基基基基基基基米哈哈基基米米基哈米基米哈基米米哈米哈基基哈基基基米哈基米哈哈基哈哈基哈米哈哈米基基基米基基基哈哈基基基哈米基基哈基哈米米米基哈基哈哈基哈哈米哈哈哈基哈哈米米基米哈基哈哈哈米米哈哈哈基哈基米米米哈哈基米基基哈基基基哈基基米基"
)
key = "hajimi"
plain = decode_hajiami(encoded_text, key)
print(plain)

先把“哈/基/米”映射成三进制数 哈=0, 基=1, 米=2;密文按每个明文字符一组解析:先读 2 个符号得到 random_shift(把两位三进制转十进制 0~8),再读 1 个符号作为长度标识并用 (标识−random_shift) mod 3 还原出数据长度是 6/9/12 位之一,然后读对应数量的符号转成三进制串;由于加密时三进制串被循环左移了 random_shift,解密时把它反向复位得到原始三进制,再转成十进制得到一个“被加密过的 Unicode”;最后再减去固定偏移 base_shift=3、随机偏移 random_shift,以及由 key “hajimi” 生成的循环私钥偏移 private_shift,即可还原每个字符并拼出最终明文/flag。


⭐️ 17 [SWPUCTF 2025 秋季新生赛]哈基米南北露躲

🚩flag:NSSCTF{Y3s_7uRe_BaseHJM}

💡hint:REVERSE

🔧tool:python DiE pyinstxtractor uncompyle6

python打包成的exe,工具pyinstxtractor + uncompyle6 / decompyle3

解题脚本:

import random
import string
CHARS = "哈基米南北绿豆阿西噶压库那鲁曼波欧马自立悠嗒步诺斯哇嗷冰踩背叮咚鸡大狗叫袋鼠兴奋剂出示健康码楼上下来带一段小白手套胖宝牛魔呵嘿喔"
random.seed(777)
a = random.sample(range(64), 64)
CHARS1 = "".join((CHARS[a.index(i)] for i in range(64)))
CHARS = CHARS1
CHAR_TO_INDEX = {char: idx for idx, char in enumerate(CHARS)}

if __name__ == "__main__":
encoded = "叮鼠背叮康豆绿步叫豆北哇基豆喔袋哈阿牛鼠南健牛出基来阿鸡马北豆库"
result=""
for char in encoded:
targetIndex = CHAR_TO_INDEX[char]
for i in string.printable:
i=str(i)
index = -1
if "A" <= i <= "Z":
index = ord(i) - ord("A")
else:
if "a" <= i <= "z":
index = ord(i) - ord("a") + 26
else:
if "0" <= i <= "9":
index = ord(i) - ord("0") + 52
else:
if i == "+":
index = 62
else:
if i == "/":
index = 63
if index == targetIndex:
result+=i
print(result)
break

运行完成后用base64解码。


以我推的美图草草收尾这个系列的题目,做了大概一半的。今天再修修前面5篇的目录。