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 需要输入密码。
把 .pcapng 文件扔随波逐流里,USB鼠标流量分析,得到压缩包密码。


密码是:868F-83BD-FF
解压下来后得到一个 hidden-word.txt
这个文件名就是提示

[央企杯 2025]big_e_rsa
12.16
密码题
考察 RSA 算法
解密脚本:
点击查看代码
from sage.all import *
from hashlib import md5
# 题目给出的数据
e = 1551272466605872863416403977607292631633701035332147619334397735304780667522023412306036671510826302844606446851894336124057998125262840234194050349637823374524170830050888493129093785047790768479824853974485992897094462464812937258543116078662875776075027424552496083294550754325632098348117392765307939361584083284301895309303537418746278506279259977605077340832463160592170634327044102649366071222424124607846377204960029629274181604893740461442615864409257969910222040007572920062878004810852999179043297689530784435487435361166796498735890127211075763324999654593958063926772895325778380833510441152388535129397
N = 114844384426038454254660651833814261274384746047765676028021231000119586728613054758356259645955152450739070905660390543576514347506026385857367390443306247721678976046962085618294460484178352540250850748434273095861501307425230078443520622440057792872004498471410258909184474490354627866870431592356692438209
# 1. 恢复 d
# 注意:必须使用与题目相同的计算方式(Integer(N)**RR(...))以复现相同的精度截断
# 题目中: d = Integer(N)**RR(0.31) -> 这是一个实数
# 题目中: e = inverse(Integer(d), phi_N) -> 这里将实数 d 转换为了整数
d_calc = Integer(Integer(N)**RR(0.31))
print(f"[*] Calculated d: {d_calc}")
# 2. 恢复 T (phi_N in challenge code)
# e * d = 1 + k * T => T = (e*d - 1) / k
# 估算 k: T 约为 N^2, 所以 k 约为 (e*d)/N^2
numerator = e * d_calc - 1
k_approx = numerator // (N**2)
# 确保整除
if numerator % k_approx == 0:
T = numerator // k_approx
print(f"[*] Recovered T: {T}")
else:
print("[!] Error: k approximation failed or d is incorrect.")
exit()
# 3. 解一元二次方程求 S = p + q
# 方程: S^2 + (N+1)S + (N^2 - N + 1 - T) = 0
a = 1
b = N + 1
c = N**2 - N + 1 - T
# 使用求根公式
delta = b**2 - 4*a*c
if delta < 0:
print("[!] Delta < 0, check math.")
exit()
delta_sqrt = Integer(delta).isqrt()
if delta_sqrt**2 != delta:
print("[!] Delta is not a perfect square.")
exit()
S = (-b + delta_sqrt) // (2*a)
print(f"[*] Recovered p+q: {S}")
# 4. 生成 Flag
flag_hash = md5(str(S).encode()).hexdigest()
flag = f'flag{{{flag_hash}}}'
print(f"[*] Flag: {flag}")
[GHCTF 2025]ASM?Signin!
12.16
逆向题
考察 ASM 语法。
过程有点枯燥,就是单纯翻译 ASM 语句。
交给 AI 哩


解题脚本:
点击查看代码
# 原始数据 DATA1
data1 = [
0x26, 0x27, 0x24, 0x25, 0x2A, 0x2B, 0x28, 0x00,
0x2E, 0x2F, 0x2C, 0x2D, 0x32, 0x33, 0x30, 0x00,
0x36, 0x37, 0x34, 0x35, 0x3A, 0x3B, 0x38, 0x39,
0x3E, 0x3F, 0x3C, 0x3D, 0x3F, 0x27, 0x34, 0x11
]
# 加密后的 Flag 数据 DATA2
data2 = [
0x69, 0x77, 0x77, 0x66, 0x73, 0x72, 0x4F, 0x46,
0x03, 0x47, 0x6F, 0x79, 0x07, 0x41, 0x13, 0x47,
0x5E, 0x67, 0x5F, 0x09, 0x0F, 0x58, 0x63, 0x7D,
0x5F, 0x77, 0x68, 0x35, 0x62, 0x0D, 0x0D, 0x50
]
# 模拟 DO1 函数:对 data1 进行置换
si = 0
for _ in range(8):
# 计算 DI
di = si + 4
# 模拟 JL NOWRAP 和 SUB DI, 28 逻辑
if di >= 28:
di -= 28
# 模拟 DO2:交换 4 个字节
for k in range(4):
# Python 中的多重赋值可以方便地进行交换
data1[si+k], data1[di+k] = data1[di+k], data1[si+k]
si += 4
# 此时 data1 已经是置换后的密钥了
# print("Permuted Key:", [hex(x) for x in data1])
# 模拟 ENC 的逆过程解密
flag = []
for i in range(0, 32, 4):
# 根据汇编逻辑:
# Input[0] ^ DATA1[1]
# Input[1] ^ DATA1[2]
# Input[2] ^ DATA1[2]
# Input[3] ^ DATA1[3]
# 当前块在 data1 和 data2 中的起始索引为 i
c0 = data2[i+0] ^ data1[i+1]
c1 = data2[i+1] ^ data1[i+2]
c2 = data2[i+2] ^ data1[i+2]
c3 = data2[i+3] ^ data1[i+3]
flag.extend([c0, c1, c2, c3])
# 输出结果
flag_str = "".join(chr(x) for x in flag)
print("Flag:", flag_str)
[GCCCTF 2025]constraint
2025.12.17
看到题目标签里有壳就做了,结果只是正常的 UPX 壳,脱壳后加密函数直接暴露出来了。
加密函数
_BOOL8 __fastcall verify_flag(char *Buffer)
{
_BOOL8 result; // rax
bool v3; // zf
__int64 i; // rdx
unsigned __int8 v5; // [rsp+18h] [rbp-68h]
unsigned __int8 v6; // [rsp+19h] [rbp-67h]
unsigned __int8 v7; // [rsp+1Ah] [rbp-66h]
unsigned __int8 v8; // [rsp+1Bh] [rbp-65h]
unsigned __int8 v9; // [rsp+1Ch] [rbp-64h]
unsigned __int8 v10; // [rsp+1Dh] [rbp-63h]
unsigned __int8 v11; // [rsp+1Eh] [rbp-62h]
unsigned __int8 v12; // [rsp+1Fh] [rbp-61h]
result = 0;
if ( ~(strlen(Buffer) + 1) == -18 )
{
v3 = memcmp(Buffer, "GCCCTF{", 7u) == 0;
result = !v3;
if ( v3 )
{
if ( Buffer[15] == 125 )
{
for ( i = 0; i != 8; ++i )
*(&v5 + i) = Buffer[i + 7];
if ( 13 * v9 + 11 * v7 + 3 * v6 + 7 * v5 == 2145
&& 9 * v10 + 12 * v8 + 8 * v6 + 4 * v12 == 2491
&& 5 * v7 + 6 * v5 + 8 * v11 + 7 * v12 == 2299
&& 11 * v9 + 9 * v8 + 6 * v10 + 10 * v11 == 3165 )
{
return (v10 ^ (unsigned __int8)(v6 ^ v5 ^ v7 ^ v9 ^ v8)) == 95;
}
}
}
else
{
return 0;
}
}
return result;
}
pyhton Z3 脚本求解
点击查看代码
from z3 import *
# 创建求解器
s = Solver()
# 定义 8 个 32 位向量变量 (对应 Buffer[7] 到 Buffer[14])
# 使用 BitVec(32) 可以同时支持加法乘法和异或运算
x = [BitVec(f'x{i}', 32) for i in range(8)]
# 添加 ASCII 可打印字符约束 (32 - 126)
for i in range(8):
s.add(x[i] >= 32)
s.add(x[i] <= 126)
# 变量映射 (根据反编译代码分析)
# v5 -> x[0]
# v6 -> x[1]
# v7 -> x[2]
# v8 -> x[3]
# v9 -> x[4]
# v10 -> x[5]
# v11 -> x[6]
# v12 -> x[7]
# 方程组约束
# 13 * v9 + 11 * v7 + 3 * v6 + 7 * v5 == 2145
s.add(13 * x[4] + 11 * x[2] + 3 * x[1] + 7 * x[0] == 2145)
# 9 * v10 + 12 * v8 + 8 * v6 + 4 * v12 == 2491
s.add(9 * x[5] + 12 * x[3] + 8 * x[1] + 4 * x[7] == 2491)
# 5 * v7 + 6 * v5 + 8 * v11 + 7 * v12 == 2299
s.add(5 * x[2] + 6 * x[0] + 8 * x[6] + 7 * x[7] == 2299)
# 11 * v9 + 9 * v8 + 6 * v10 + 10 * v11 == 3165
s.add(11 * x[4] + 9 * x[3] + 6 * x[5] + 10 * x[6] == 3165)
# XOR 约束
# v10 ^ (v6 ^ v5 ^ v7 ^ v9 ^ v8) == 95
# 注意:C语言中异或操作是右结合的,但异或本身满足交换律和结合律,顺序不影响结果
s.add((x[5] ^ x[1] ^ x[0] ^ x[2] ^ x[4] ^ x[3]) == 95)
# 求解
if s.check() == sat:
model = s.model()
# 提取结果并拼接字符串
flag_content = "".join([chr(model[x[i]].as_long()) for i in range(8)])
print(f"Found internal flag: {flag_content}")
print(f"Full Flag: GCCCTF{{{flag_content}}}")
else:
print("No solution found. Check constraints.")
GCCCTF{F14gH3re}
[VNCTF 2025]Fuko's starfish
2025.12.21
好题。
给出一个 .exe 文件和一个 .dll文件,启动exe, .dll 加载成功会提示我们完成三个游戏。
扔 ida 里头,sub_140001490 里是猜数字游戏
想动调,奈何太菜。干脆开玩(
第二个游戏是贪吃蛇,感觉没坑,索性开玩(
最后一个要输密钥,sub_180001650 函数里头个 AES 加密,可以跟进找到一个生成密钥的函数,带花需要去花指令(恶心心)。
解密脚本:
点击查看代码
#include <stdio.h>
#include <stdlib.h>
int main(){
srand(114514);
for (int i = 0; i < 16; i++)
{
int v10 = rand();
printf("%02x",((unsigned char)(v10 + (v10 / 255))) ^ 0x17);
}
return 0;
}
// 09e5fdeb683175b6b13b840891eb78d2
[网络与数据安全挑战赛 2025]easyReee
2026.1.5
扔 ida 里后先找到主函数

可以看到负责 check 的函数是 sub_11C9
数组的值可在 ida 里直接提取。
python 脚本如下:

竟然只是简单的异或加密。
点击查看代码
# 提取的密文数据
data = [
0x1A, 0x1A, 0xC8, 0x16, 0xC6, 0x14, 0x3A, 0x3D, 0xC5, 0x13,
0xC2, 0x40, 0xBD, 0xBA, 0xBC, 0x74, 0xBB, 0x76, 0xBF, 0x4B,
0x4B, 0x4B, 0x7B, 0xB4, 0x4D, 0x62, 0x61, 0xAD, 0x60, 0x56,
0x63, 0xAF, 0x78, 0x54, 0x56, 0x74
]
flag_inner = ""
for i in range(len(data)):
target = data[i]
# 模拟 (i - 75),并只取低8位,因为最终比较是转为 unsigned __int8
# python的负数异或与C不同,所以这里手动截断为8位整数模拟C的行为
key = (i - 75) & 0xFF
# 反向异或
xor_result = target ^ key
# 反向加法 (减去75),并处理可能的下溢出(& 0xFF)
original_char = (xor_result - 75) & 0xFF
flag_inner += chr(original_char)
print("Decoded string:", flag_inner)
print("Final Flag: DASCTF{" + flag_inner + "}")
[GFCTF 2021]wordy
2026.1.5
花指令好题。
拿 ida 打开之后见到的第一个函数长这样

点进 main 函数里

看到 jmp 指令(警觉)
jmp 到 loc_1144+1
找到 001146 这一行。
按下 C (code:把字节编程汇编指令)
然后就能得到一长串数据。
一直翻到最后会发现还有一些数据很怪,

按 U (undefine :变回原始字节)
之后就能正常查看了。
无意间翻到一个大括号

合理猜测这一整串都是 flag ,但中间穿插了些花指令,出现了一大堆无用的数据。
接下来对着一串一个一个按 C 还原成汇编,逐渐发现规律

ida 识别出的这些带引号的字符连起来就是 flag (一个一个还原太麻烦了,可以用脚本)
[NSSRound#3 Team]jump_by_jump_revenge
2026.1.7
考察花指令
进 main 函数看到这个

在 View-A 里查看 main

没找到花指令。但是有个 jmp main_0
跟着找到 jmp main_0

也没有花指令
跟着继续跳转。

一路跳转到 loc_4118DA
这里 jmp near ptr 0C086A4CCh 通过跳转到一个无效地址使 ida 没法正常反编译
按 D 给它转换成数据(硬指令。?)

[VNCTF 2025]AndroidLux
2026.3.15
安卓逆向好题(但出题人该骂)
解压出来就是一个 .apk文件

扔 jadx 里看看 java 层

顺着找到主函数,开头的不用管,从 onClick 函数开始看。

这函数应该是个用户输入函数。
一步步分析,先是检测输入长度,排掉输入为空的情况。
之后用户输入被扔进这个函数里:

追踪过去看看

这个函数没动咱的 input ,直接传递给了下一个函数
但这里提到的 “mahoshojo” 有点可疑,先往下分析。

这个函数依旧没动用户输入,继续往下找也没有跟 flag 有关的东西。
重新分析 Java 层的主函数,可以确定这里只负责基本输入输出。
去其他层找找,把.apk解压出来,

点进 assets 文件夹发现有个 env 文件大的离谱

拿 7zip 打开发现里头又套了一层
打开发现里头是个类似 linux 目录的东西,root也大的可疑

打开 root 发现里面又有一个 env,还是个.elf 文件,直接 ida 打开
ida 打开发现没法构造函数,只能一点点看了。
翻到个 base64

果然换表了

但依旧解不出来,估计魔改算法了。
继续找也找不出来了,去其他地方找找线索。
出题人应该魔改了不少东西,从文件修改时间入手。
来到打开第一个 env ,修改时间排序可以找到一个可疑文件

众多 14 号里出了个 17 号的,点进去发现是动了这个文件

记事本打开后得到了一个文件路径

顺着路径找到这个文件,也是个 .elf 文件,直接 ida 打开。

发现 strcmp 函数和 read 函数是手写的。
read在读取的时候就进行了一次异或操作。

strncmp 给数组移了 13 位。

现在 base64 的问题还是没解决,回到之前ida 打开的 env ,翻 ASM 代码找到了一个左边列表不显示的base64 函数。
几个加密都解决了,剩下的交给AI。
flag:VNCTF{Ur_go0d_@ndr0id&l1nux_Reve7ser}
总结一下这个题的正常运行步骤:
1.客户端输入原始flag
2.read函数 (异或)
3.strncmp 函数
4.魔改base64
5.与真flag比较
[VNCTF 2025]抽奖转盘
2026.3.15
(也是逆向上鸿蒙了)
解压出来:

ets 里有个 .abc 文件非常可疑 (鸿蒙文件)
需要拿鸿蒙特供版 jadx 反编译。
反编译出来的是 js 代码
js 层里的密文:

一些函数名被混淆后很抽象

搜索找到

libs 里俩文件,一个C++共享库,另一个拿 ida 打开看看

主要函数:

主要干俩事,RC4 后 base64

脚本交给 AI
点击查看代码
import base64
embedded = [
101, 74, 76, 49, 101, 76, 117, 87, 55, 69, 118, 68, 118, 69, 55, 67,
61, 83, 62, 111, 81, 77, 115, 101, 53, 73, 83, 66, 68, 114, 109, 108,
75, 66, 97, 117, 93, 127, 115, 124, 109, 82, 93, 115
]
target = bytes(((x - 1) ^ 7) & 0xff for x in embedded).decode()
print(target)
enc = list(base64.b64decode(target))
key = b"Take_it_easy"
S = list(range(256))
K = [key[i % len(key)] for i in range(256)]
j = 0
for i in range(256):
j = (j + S[i] + K[i]) % 256
S[i], S[j] = S[j], S[i]
j = 0
t = 0
plain = []
for b in enc:
j = (j + 1) % 256
t = (t + S[j]) % 256
S[j], S[t] = S[t], S[j]
ks = S[(S[j] + S[t]) % 256]
x = b ^ ks ^ 40
plain.append((x - 3) & 0xff)
print(bytes(plain))
print(bytes(plain).rstrip(b"\\x00").decode())

浙公网安备 33010602011771号