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!)被改了。

屏幕截图 2025-11-20 215101

手动改回来即可正常脱壳。

脱壳后再扔进 ida 里即可见到加密函数。

屏幕截图 2025-11-21 080709

主要的加密函数为

\(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 魔改壳。

屏幕截图 2025-11-24 215936

可以看到 UPX 壳的开头莫名出现了一串乱码,但经实测并无影响。

问题还是因为 UPX 的标志位被改了, 010editor 里改回来即可正常脱壳。

脱壳后扔入 ida 里查看主函数

屏幕截图 2025-11-24 220245

先打开 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

传送门

这题目真叫这个名。

上道题目做半天没做出来,做到杂项缓缓。

题目给了一个网站,里头就一个“获取我的灵感菇”,按一下就生成这串抽象东西:

屏幕截图 2025-11-25 172402

多生成了几次,发现每次生成的是一样的。

自己思考无果,最终逐渐理解一切------这种杂项题就该 bdfs 啊!

😓

直接搜索灵感菇编码,然后在 github 上找到破译脚本

还是探姬的(绷不住了)。

。。。。

直接执行脚本就能出 flag

这题是动态生成 flag ,所以重开题目要重新跑一边脚本

屏幕截图 2025-11-25 213111

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

屏幕截图 2025-11-25 213238

[LitCTF 2025]消失的文字

2025.11.25

再做一道杂项缓缓。

下载下来得到如下:

屏幕截图 2025-11-25 215512

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

posted @ 2025-11-18 21:40  int_Hello_world  阅读(11)  评论(0)    收藏  举报