NSSCTF刷题日记
2025.11.18
刚开始使用这个网站,感觉像。。。付费制洛谷?(会员制) 先白嫖做几道看看吧。
[SWPUCTF 2021 新生赛]简简单单的逻辑
好水的题,题目给出了一个 python 文件,打开就能看到源代码。
点击查看代码
flag = 'xxxxxxxxxxxxxxxxxx'
list = [47, 138, 127, 57, 117, 188, 51, 143, 17, 84, 42, 135, 76, 105, 28, 169, 25]
result = ''
for i in range(len(list)):
key = (list[i]>>4)+((list[i] & 0xf)<<4)
result += str(hex(ord(flag[i])^key))[2:].zfill(2)
print(result)
# result=bcfba4d0038d48bd4b00f82796d393dfec
key = (list[i]>>4)+((list[i] & 0xf)<<4)
实际上是将 list 中每个数字的二进制高4位和低4位进行互换
如十六进制的 0xAB 会变成 0xBA
加密是通过异或,再异或一边就能还原回去。
解密脚本如下:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
string result_hex="bcfba4d0038d48bd4b00f82796d393dfec";
int List[17]={47, 138, 127, 57, 117, 188, 51, 143, 17, 84, 42, 135, 76, 105, 28, 169, 25};
string flag;
int hex_change(char c) {
if (c >= '0' && c <= '9') {
return c - '0'; // '0'->0, '9'->9
}
if (c >= 'a' && c <= 'f') {
return c - 'a' + 10; // 'a'->10, 'f'->15
}
if (c >= 'A' && c <= 'F') {
return c - 'A' + 10; // 支持大写字母
}
return 0;
}
signed main(){
int len=17;
for (int i=0;i<len;++i) {
int key=(List[i]>>4) + ((List[i]&0xf)<<4);
char h1=result_hex[i*2];
char l1=result_hex[i*2+1];
int h2=hex_change(h1);
int l2=hex_change(l1);
int ans=h2*16+l2;
flag+=char(ans^key);
}
cout<<flag;
return 0;
}
NSSCTF{EZEZ_RERE}
[SWPUCTF 2025 秋季新生赛]Ultimate Packer for eXecutables
2025.11.19
一眼带壳,先脱壳。
直接扔 UPX 脱壳工具,发现脱不了壳,但 ida 里的样子明显是 UPX 壳的形式。
盲猜魔改 UPX 壳。
扔进 hex 编辑器里(我用的 010editor )
果然 UPX 的标志位(UPX0、UPX1、UPX2、UPX!)被改了。

手动改回来即可正常脱壳。
脱壳后再扔进 ida 里即可见到加密函数。

主要的加密函数为
\(y = 122 \times x - 23\)
解密方程为:
\(122 \times x \equiv (y + 23) \pmod{256}\)
考虑到 CTF 题的 flag 一般只用常用字符
解这个方程最简单的办法就是在常用字符范围内枚举爆破(32 到 126)。
解密脚本如下:
#include<bits/stdc++.h>
using namespace std;
unsigned char s[] = {
0x15, 0x77, 0x77, 0xD7, 0xF1, 0x45, 0x87, 0x6B,
0x49, 0x19, 0xEF, 0x93, 0xB7, 0x93, 0x93, 0xA3,
0xB7, 0x23, 0xAB, 0x93, 0xA3, 0x17, 0xB1, 0x3D,
0x49, 0xA3, 0x7B
};
signed main(){
int len=sizeof(s);
for (int i=0;i<len;++i) {
for (int j=32;j<=126;++j) {
int k=(j*122-23)%256;
if (k==s[i]) {
cout<<char(j); break;
}
}
}
return 0;
}
求得 flag 为
NSSCTF{Upx?ysyy!sauy!c4rp!}
注:
- 解释一下解密方程这个 模上 \(256\) 怎么来的。
我们输入的是个 char 数组,char 类型的变量只能存最后 \(8\) 位。
即:砍掉高位、只留低 \(8\) 位
相当于对 \(256\) 取模。
- 还有一个地方需要注意,
处理密文(尤其是 Hex 数据)永远建议使用 unsigned char 或者 uint8_t,否则只要出现大于 0x7F 的字节,就会因为符号位问题导致比较失败。
我一开始定义密文数组用的是 char ,结果输出结果一直不对。
因为密文数组里存在 0xD7 这种值。
常用编译器(如 GCC ),定义 char 时默认是 signed char (有符号)。
虽然 unsigned char (无符号) 和 signed char 范围都是 256 ,但也略有不同。
-
unsigned char: 0 ~ 255
-
signed char: -128 ~ 127
0xD7 (二进制 11010111)
作为 unsigned char (无符号) 是 \(215\) 。
作为 char (有符号) 则是 \(-41\) (最高位是 \(1\) 被当成负数了 ) 。
在比较 \(215 == -41\) 时,结果显然是 false,脚本根本找不到匹配的字符,自然会出错。
[LitCTF 2025]easy_tea
2025.11.21
下载下来的程序名叫 ”花“ ,可以想到花指令 (其实是题目标签里有)
[青海民族大学 2025 新生赛]你的flag被加密啦!
2025.11.24
这题评分这么低不是没理由的。
纯粹的分析 python 代码逆向,有点无聊了。
解题脚本(python):
点击查看代码
def custom_decrypt(ciphertext):
decrypted = ""
key = [3, 5, 2]
key_index = 0
for char in ciphertext:
if 'a' <= char <= 'z':
shift = key[key_index]
# 逆向操作:减去 shift
# Python 的 % 运算对负数处理很方便: -3 % 26 = 23,符合凯撒密码逻辑
new_char = chr((ord(char) - ord('a') - shift) % 26 + ord('a'))
key_index = (key_index + 1) % len(key)
elif 'A' <= char <= 'Z':
shift = key[key_index]
new_char = chr((ord(char) - ord('A') - shift) % 26 + ord('A'))
key_index = (key_index + 1) % len(key)
elif '0' <= char <= '9':
num = int(char)
# 逆向操作:减去 7
new_num = (num - 7) % 10
new_char = str(new_num)
# 注意:数字不改变 key_index
else:
new_char = char
# 注意:符号不改变 key_index
decrypted += new_char
return decrypted
cipher_text = "iqcj{qafmgh89991}"
flag = custom_decrypt(cipher_text)
print(f"Flag: {flag}")
[GHCTF 2025]LockedSecret
2025.11.24
先是 UPX 魔改壳。

可以看到 UPX 壳的开头莫名出现了一串乱码,但经实测并无影响。
问题还是因为 UPX 的标志位被改了, 010editor 里改回来即可正常脱壳。
脱壳后扔入 ida 里查看主函数

先打开 sub_401100() 这个函数。
可以看到是通过随机数函数初始化数组。
点击查看代码
int sub_401100()
{
int i; // [esp+0h] [ebp-4h]
srand(0xC17FF9CC);
for ( i = 0; i < 8; ++i )
{
if ( i )
dword_4043D8[i] = dword_4043D4[i] ^ rand();
else
dword_4043D8[0] = rand();
dword_4043D8[i] %= 256;
}
return 0;
}
再点开 sub_401190(Str) 这个函数,可以看到是 XTEA 加密
直接写逆向脚本有点困难,但其实可以在这个函数结束设断点提取出 key 再写脚本。
不过我选择 AI (真的写不动了)。
点击查看代码
import struct
# 1. 密钥 (来自你的 Dump)
# unsigned int data[5]
# 我们只需要前4个作为 TEA/XTEA 的 Key
KEY = [0x423DF72D, 0x05F59A01, 0x633FCF1D, 0x77D19122]
# 2. 密文数据 (byte_404060)
encrypted_bytes = bytes([
0xDC, 0x45, 0x1E, 0x03, 0x89, 0xE9, 0x76, 0x27, 0x47, 0x48, 0x23, 0x01, 0x70, 0xD2, 0xCE, 0x64,
0xDA, 0x7F, 0x46, 0x33, 0xB1, 0x03, 0x49, 0xA3, 0x27, 0x00, 0xD1, 0x2C, 0x37, 0xB3, 0xBD, 0x75
])
# 3. 常量表
CONSTANTS = [
0x5E2377FF, # C1
-0x43B91002, # C2
0x1A6A67FD, # C3
0x788DDFFC, # C4
-0x294EA805, # C5
0x34D4CFFA, # C6
-0x6D07B807, # C7
-0xEE44008 # C8
]
# 转无符号
C = [c & 0xFFFFFFFF for c in CONSTANTS]
def T(val, k_a, k_b, c):
"""
加密核心变换函数: ((k_a + (val >> 5)) ^ (val + c) ^ (k_b + val * 16))
"""
val &= 0xFFFFFFFF
p1 = (k_a + (val >> 5)) & 0xFFFFFFFF
p2 = (val + c) & 0xFFFFFFFF
p3 = (k_b + (val * 16)) & 0xFFFFFFFF
return p1 ^ p2 ^ p3
def decrypt_block(v_low, v_high):
# 输入对应 Str[0] (L_final) 和 Str[4] (R_final)
# 1. 初始异或还原
# 代码中: *(_DWORD *)&Str[...] = VAL ^ 0xF;
L = v_low ^ 0xF
R = v_high ^ 0xF
# 注意:反编译代码中的变量赋值流向决定了逆向顺序
# 我们将变量命名为 L, R,并逐步回退状态
# === 逆向 Steps 15 & 16 (Using C8) ===
# 正向逻辑:
# L7 = L6 + T(R7, K01, C8)
# R8 = R7 + T(L7, K23, C8)
# 此时 L=L7, R=R8
# 1. 还原 R7
term = T(L, KEY[3], KEY[2], C[7])
R = (R - term) & 0xFFFFFFFF
# 2. 还原 L6
term = T(R, KEY[1], KEY[0], C[7])
L = (L - term) & 0xFFFFFFFF
# === 逆向 Steps 13 & 14 (Using C7) ===
# R7 = R6 + T(L6, K23, C7) -> 还原 R6
term = T(L, KEY[3], KEY[2], C[6])
R = (R - term) & 0xFFFFFFFF
# L6 = L5 + T(R6, K01, C7) -> 还原 L5
term = T(R, KEY[1], KEY[0], C[6])
L = (L - term) & 0xFFFFFFFF
# === 逆向 Steps 11 & 12 (Using C6) ===
# R6 = R5 + T(L5, K23, C6) -> 还原 R5
term = T(L, KEY[3], KEY[2], C[5])
R = (R - term) & 0xFFFFFFFF
# L5 = L4 + T(R5, K01, C6) -> 还原 L4
term = T(R, KEY[1], KEY[0], C[5])
L = (L - term) & 0xFFFFFFFF
# === 逆向 Steps 9 & 10 (Using C5) ===
# R5 = R4 + T(L4, K23, C5) -> 还原 R4
term = T(L, KEY[3], KEY[2], C[4])
R = (R - term) & 0xFFFFFFFF
# L4 = L3 + T(R4, K01, C5) -> 还原 L3
term = T(R, KEY[1], KEY[0], C[4])
L = (L - term) & 0xFFFFFFFF
# === 逆向 Step 8 (Using C4) ===
# R4 = R3 + T(L3, K23, C4) -> 还原 R3
term = T(L, KEY[3], KEY[2], C[3])
R = (R - term) & 0xFFFFFFFF
# === 逆向 "Mixed Round" Steps 5, 6, 7 (Using C3 and C4) ===
# 这是一个特殊的复合步骤,正向逻辑如下:
# L_temp = L2 + T(R2, K01, C3)
# R3 = R2 + T(L_temp, K23, C3) <-- 对应代码 v12
# L3 = L_temp + T(R3, K01, C4) <-- 对应代码 v1
# 当前我们有 L3 (即 L) 和 R3 (即 R)
# 1. 还原 L_temp
# L_temp = L3 - T(R3, K01, C4)
term = T(R, KEY[1], KEY[0], C[3]) # C4 is at index 3
L_temp = (L - term) & 0xFFFFFFFF
# 2. 还原 R2 (此时 R 变为 R2)
# R2 = R3 - T(L_temp, K23, C3)
term = T(L_temp, KEY[3], KEY[2], C[2]) # C3 is at index 2
R = (R - term) & 0xFFFFFFFF
# 3. 还原 L2 (此时 L 变为 L2)
# L2 = L_temp - T(R2, K01, C3)
term = T(R, KEY[1], KEY[0], C[2]) # C3 is at index 2
L = (L_temp - term) & 0xFFFFFFFF
# === 逆向 Steps 3 & 4 (Using C2) ===
# R2 = R1 + T(L2, K23, C2) -> 还原 R1
term = T(L, KEY[3], KEY[2], C[1])
R = (R - term) & 0xFFFFFFFF
# L2 = L1 + T(R1, K01, C2) -> 还原 L1
term = T(R, KEY[1], KEY[0], C[1])
L = (L - term) & 0xFFFFFFFF
# === 逆向 Steps 1 & 2 (Using C1) ===
# R1 = R0 + T(L1, K23, C1) -> 还原 R0 (Initial High)
term = T(L, KEY[3], KEY[2], C[0])
R = (R - term) & 0xFFFFFFFF
# L1 = L0 + T(R0, K01, C1) -> 还原 L0 (Initial Low)
term = T(R, KEY[1], KEY[0], C[0])
L = (L - term) & 0xFFFFFFFF
return L, R
# 4. 执行解密
flag = b""
for i in range(0, 32, 8):
block = encrypted_bytes[i:i+8]
v0, v1 = struct.unpack("<2I", block)
dec_L, dec_R = decrypt_block(v0, v1)
flag += struct.pack("<2I", dec_L, dec_R)
print("Flag:", flag.decode('utf-8', errors='ignore'))
NSSCTF{!!!Y0u_g3t_th3_s3cr3t!!!}
[LitCTF 2025]Robbie Wanna Revenge
11.25
久违的 Unity 游戏题。
这次不是 Mono ,没法套上次的经验。
[LitCTF 2025]灵感菇🍄哩菇哩菇哩哇擦灵感菇灵感菇🍄
2025.11.25
这题目真叫这个名。
上道题目做半天没做出来,做到杂项缓缓。
题目给了一个网站,里头就一个“获取我的灵感菇”,按一下就生成这串抽象东西:

多生成了几次,发现每次生成的是一样的。
自己思考无果,最终逐渐理解一切------这种杂项题就该 bdfs 啊!
😓
直接搜索灵感菇编码,然后在 github 上找到破译脚本
还是探姬的(绷不住了)。
。。。。
直接执行脚本就能出 flag
这题是动态生成 flag ,所以重开题目要重新跑一边脚本

附上一段探姬对这题目的评价

[LitCTF 2025]消失的文字
2025.11.25
再做一道杂项缓缓。
下载下来得到如下:

解压 hidden-word .zip 需要输入密码。

浙公网安备 33010602011771号