SJTUCTF2025 Writeup (3)
观前提示:本Writeup含有过量Deepseek The Flag的内容,建议谨慎观看
SmartGrader
来试试我写的分数换算小工具吧!
评价:又是JS抽象时刻
先上jd-gui逆呗
1 | package BOOT-INF.classes.com.sjtuctf2025.smartgrader.controller; |
逻辑相当简洁易懂,就是布尔盲注,让"(" + leftScore + leftSymbol + "x && x" + rightSymbol + rightScore + ")"透出flag,锁了可控字段的总长度
直接上payload了:
1 | (async()=>{ |
拼起来是
1 | +-+-leftScore rightScore-+-+ |
确实挺神人的,反正deepseek绝对想不到这个
AreYouReady
Donald J. Trump’s gonna Make America Great Again! But how?
简单的逆向,C#直接上ILSpy,阅读毫无难度:
1 | public class Composition |
大意是说,有一堆state,还有一堆乐器,每个乐器都会对state敲敲打打;
然后程序每说一句话都要让乐器启动一下,
最后state肯定面目全非了,但它确实存了flag的值
我也不知道为什么它showflag那个toString会炸,可能这就是神秘的CSharp力量吧
反正拿C++重复一遍它的行为就正常了
PoC:
1 |
|
Expr-Warmup
算术小课堂开课了
难度定位:入门/简单
直接爆逆,逆完之后你就deepseek吧活爹
Deepseek time:
判断以下代码在做什么:
1
2
3
4
5
6
7 double *__fastcall sub_39D5(double *a1, double *a2, double *a3)
{
*a1 = a2[1] * a3[2] - a2[2] * a3[1];
a1[1] = a2[2] * *a3 - *a2 * a3[2];
a1[2] = *a2 * a3[1] - a2[1] * *a3;
return a1;
}
这段代码实现了两个三维向量的叉乘(向量积)运算。具体分析如下:
函数功能:
• 输入两个三维向量a2和a3,计算它们的叉乘结果,并将结果存储在a1指向的内存中。
• 返回结果向量a1的指针。数学表达式:
叉乘结果的三个分量按如下方式计算:
• 第一个分量:a1[0] = a2[1] * a3[2] - a2[2] * a3[1]
• 第二个分量:a1[1] = a2[2] * a3[0] - a2[0] * a3[2]
• 第三个分量:a1[2] = a2[0] * a3[1] - a2[1] * a3[0]这与标准的三维向量叉乘公式完全一致:
$$
\mathbf{c} = \mathbf{a} \times \mathbf{b} = \begin{pmatrix}
a_y b_z - a_z b_y \
a_z b_x - a_x b_z \
a_x b_y - a_y b_x
\end{pmatrix}
$$参数解释:
•a2和a3:输入向量的指针,分别对应公式中的 (\mathbf{a}) 和 (\mathbf{b})。
•a1:输出向量的指针,存储计算结果 (\mathbf{c})。调用约定:
• 使用__fastcall约定(可能通过寄存器传递参数,提升性能),但不影响函数的核心逻辑。
总结:该函数接收两个三维向量 a2 和 a3,计算它们的叉乘并将结果写入 a1,最终返回 a1 的指针。这是三维几何运算中标准的叉乘实现。
此外,你还知道sub_38FE是向量赋值,sub_37EE是初始化函数,请说出下面代码在干什么:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 bool __fastcall sub_2711(__int64 a1, __int64 a2)
{
char v3[32]; // [rsp+10h] [rbp-C0h] BYREF
char v4[32]; // [rsp+30h] [rbp-A0h] BYREF
char v5[8]; // [rsp+50h] [rbp-80h] BYREF
double v6; // [rsp+58h] [rbp-78h]
double v7; // [rsp+60h] [rbp-70h]
double v8[4]; // [rsp+70h] [rbp-60h] BYREF
double v9[4]; // [rsp+90h] [rbp-40h] BYREF
double v10[4]; // [rsp+B0h] [rbp-20h] BYREF
*(_QWORD *)&v10[3] = __readfsqword(0x28u);
sub_37EE(v3);
sub_37EE(v4);
sub_37EE(v5);
sub_38FE(v8, 1.0, 0.0, 0.0);
sub_38FE(v9, 0.0, 1.0, 0.0);
sub_38FE(v10, 0.0, 0.0, 1.0);
sub_39D5(v3, v9, v10);
sub_39D5(v4, v8, a2);
sub_39D5(v5, v4, v3);
return fabs(v6 / v7 - *(double *)(a1 + 8) * *(double *)(a1 + 8) / (*(double *)(a1 + 16) * *(double *)(a1 + 16))) < 9.999999999999999e-12;
}
根据代码和上下文信息,这段函数 sub_2711 的功能是 验证某个几何条件是否成立,具体分析如下:
关键步骤解析
初始化与标准基向量:
•v8 = (1.0, 0.0, 0.0)(x轴单位向量)
•v9 = (0.0, 1.0, 0.0)(y轴单位向量)
•v10 = (0.0, 0.0, 1.0)(z轴单位向量)向量叉乘操作:
•v3 = v9 × v10
结果为(1.0, 0.0, 0.0)(即x轴单位向量)。
•v4 = v8 × a2
计算x轴与参数向量a2的叉乘,结果v4垂直于x轴和a2构成的平面。
•v5 = v4 × v3
再次叉乘x轴单位向量,结果v5的物理意义为a2在垂直于x轴的平面(yz平面)上的投影。缓冲区溢出导致变量覆盖:
•v5定义为char[8](仅能容纳1个double),但叉乘结果写入3个double(24字节),导致后续变量v6和v7被覆盖:
◦v6 = v5[1](叉乘结果的y分量)
◦v7 = v5[2](叉乘结果的z分量)最终条件判断:
• 比较v6 / v7与从a1中提取的参数的平方比是否近似相等:1
fabs(v6 / v7 - (*(a1+8)^2 / *(a1+16)^2)) < 1e-12
• 这相当于验证
a2的投影在yz平面上的分量比(y/z)是否满足特定条件,例如椭球方程中的归一化比例。
参数与功能总结
• 输入:
• a1: 可能指向一个包含几何参数的结构体(如椭球半轴长度 b 和 c,存储在 a1+8 和 a1+16)。
• a2: 待验证的三维向量。
• 输出:
• 返回 true 表示 a2 满足预设的几何条件(如属于某个椭球面或双曲面),否则返回 false。
数学意义
代码验证了以下关系是否成立:
$$
\frac{y^2}{b^2} \approx \frac{z^2}{c^2}
$$
其中 y 和 z 是 a2 在yz平面投影的分量,b 和 c 是来自 a1 的参数。这可能用于检查 a2 是否位于某个二次曲面(如双曲面或椭圆柱面)上。
总结
此函数通过叉乘运算和缓冲区溢出的副作用,验证输入向量 a2 的几何特性是否符合 a1 中定义的参数条件,最终返回布尔结果。代码可能用于图形学、物理引擎或几何库中的约束检查。
以下代码做了什么:
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 unsigned __int64 __fastcall Expr::processToken(__int64 a1, __int64 a2)
{
std::invalid_argument *exception; // rbx
std::invalid_argument *v3; // rbx
std::invalid_argument *v4; // rbx
std::invalid_argument *v5; // rbx
std::invalid_argument *v6; // rbx
std::invalid_argument *v7; // rbx
double v9; // [rsp+20h] [rbp-70h] BYREF
double v10; // [rsp+28h] [rbp-68h]
double v11; // [rsp+30h] [rbp-60h]
double v12; // [rsp+38h] [rbp-58h]
double x; // [rsp+40h] [rbp-50h]
double v14; // [rsp+48h] [rbp-48h]
double v15; // [rsp+50h] [rbp-40h]
double v16; // [rsp+58h] [rbp-38h]
double v17; // [rsp+60h] [rbp-30h]
double v18; // [rsp+68h] [rbp-28h]
double v19; // [rsp+70h] [rbp-20h]
unsigned __int64 v20; // [rsp+78h] [rbp-18h]
v20 = __readfsqword(0x28u);
switch ( *(_BYTE *)std::string::operator[](a2, 0LL) )
{
case 'a':
std::stack<double>::push(a1, a1 + 80);
break;
case 'b':
std::stack<double>::push(a1, a1 + 88);
break;
case 'c':
std::stack<double>::push(a1, a1 + 96);
break;
case 'd':
if ( (unsigned __int64)std::stack<double>::size(a1) <= 1 )
{
exception = (std::invalid_argument *)__cxa_allocate_exception(0x10uLL);
std::invalid_argument::invalid_argument(exception, "Error: Stack underflow");
__cxa_throw(
exception,
(struct type_info *)&`typeinfo for'std::invalid_argument,
(void (__fastcall *)(void *))&std::invalid_argument::~invalid_argument);
}
v18 = *(double *)std::stack<double>::top(a1);
std::stack<double>::pop(a1);
v19 = *(double *)std::stack<double>::top(a1);
std::stack<double>::pop(a1);
v9 = v19 / v18;
std::stack<double>::push(a1, &v9);
break;
case 'm':
if ( (unsigned __int64)std::stack<double>::size(a1) <= 1 )
{
v3 = (std::invalid_argument *)__cxa_allocate_exception(0x10uLL);
std::invalid_argument::invalid_argument(v3, "Error: Stack underflow");
__cxa_throw(
v3,
(struct type_info *)&`typeinfo for'std::invalid_argument,
(void (__fastcall *)(void *))&std::invalid_argument::~invalid_argument);
}
v16 = *(double *)std::stack<double>::top(a1);
std::stack<double>::pop(a1);
v17 = *(double *)std::stack<double>::top(a1);
std::stack<double>::pop(a1);
v9 = v17 - v16;
std::stack<double>::push(a1, &v9);
break;
case 'p':
if ( (unsigned __int64)std::stack<double>::size(a1) <= 1 )
{
v4 = (std::invalid_argument *)__cxa_allocate_exception(0x10uLL);
std::invalid_argument::invalid_argument(v4, "Error: Stack underflow");
__cxa_throw(
v4,
(struct type_info *)&`typeinfo for'std::invalid_argument,
(void (__fastcall *)(void *))&std::invalid_argument::~invalid_argument);
}
v14 = *(double *)std::stack<double>::top(a1);
std::stack<double>::pop(a1);
v15 = *(double *)std::stack<double>::top(a1);
std::stack<double>::pop(a1);
v9 = v15 + v14;
std::stack<double>::push(a1, &v9);
break;
case 'r':
if ( (unsigned __int8)std::stack<double>::empty(a1) )
{
v5 = (std::invalid_argument *)__cxa_allocate_exception(0x10uLL);
std::invalid_argument::invalid_argument(v5, "Error: Stack underflow");
__cxa_throw(
v5,
(struct type_info *)&`typeinfo for'std::invalid_argument,
(void (__fastcall *)(void *))&std::invalid_argument::~invalid_argument);
}
x = *(double *)std::stack<double>::top(a1);
std::stack<double>::pop(a1);
v9 = sqrt(x);
std::stack<double>::push(a1, &v9);
break;
case 's':
if ( (unsigned __int8)std::stack<double>::empty(a1) )
{
v6 = (std::invalid_argument *)__cxa_allocate_exception(0x10uLL);
std::invalid_argument::invalid_argument(v6, "Error: Stack underflow");
__cxa_throw(
v6,
(struct type_info *)&`typeinfo for std::invalid_argument,
(void (__fastcall *)(void *))&std::invalid_argument::~invalid_argument);
}
v12 = *(double *)std::stack<double>::top(a1);
std::stack<double>::pop(a1);
v9 = v12 * v12;
std::stack<double>::push(a1, &v9);
break;
case 'x':
if ( (unsigned __int64)std::stack<double>::size(a1) <= 1 )
{
v7 = (std::invalid_argument *)__cxa_allocate_exception(0x10uLL);
std::invalid_argument::invalid_argument(v7, "Error: Stack underflow");
__cxa_throw(
v7,
(struct type_info *)&`typeinfo for'std::invalid_argument,
(void (__fastcall *)(void *))&std::invalid_argument::~invalid_argument);
}
v10 = *(double *)std::stack<double>::top(a1);
std::stack<double>::pop(a1);
v11 = *(double *)std::stack<double>::top(a1);
std::stack<double>::pop(a1);
v9 = v11 * v10;
std::stack<double>::push(a1, &v9);
break;
default:
v9 = std::stod(a2, 0LL);
std::stack<double>::push(a1, &v9);
break;
}
return v20 - __readfsqword(0x28u);
}
这段代码实现了一个基于栈的表达式求值器,用于处理不同的操作符和操作数,支持变量、算术运算及数学函数。以下是详细分析:
函数作用:
• 处理表达式中的各个token(符号),根据不同的字符执行相应的操作,包括变量压栈、算术运算和数学函数计算,使用栈结构存储中间结果。参数与结构:
•a1:指向Expr类实例的指针,包含操作数栈及成员变量(如a、b、c)。
•a2:字符串参数,表示当前处理的token。主要逻辑(switch-case):
• 变量压栈(’a’, ‘b’, ‘c’):
◦ 将a1偏移地址(+80, +88, +96)的double值压入栈,可能对应类中的预定义变量。
• 除法操作(’d’):
◦ 检查栈大小≥2,弹出两个值计算次顶值 / 栈顶值,结果压栈。
• 减法(’m’):
◦ 弹出两个值计算次顶值 - 栈顶值,结果压栈。
• 加法(’p’):
◦ 弹出两个值相加,结果压栈。
• 平方根(’r’):
◦ 检查栈非空,弹出栈顶值计算平方根,结果压栈。
• 平方(’s’):
◦ 弹出栈顶值计算平方,结果压栈。
• 乘法(’x’):
◦ 弹出两个值相乘,结果压栈。
• 默认处理:
◦ 将token转换为double数值压栈。错误处理:
• 操作前检查栈是否满足所需操作数数量,否则抛出std::invalid_argument异常,提示”Stack underflow”。栈安全机制:
• 使用__readfsqword(0x28u)进行栈溢出保护,但此部分不影响核心逻辑。
总结:该函数模拟逆波兰表达式求值,处理变量、数值及运算符,利用栈结构维护中间结果,适用于实现计算器或表达式解析功能,确保在操作数不足时抛出异常保证健壮性。
到这里就很好懂了
1 | ➜ sjtuctf--expr-warmup nc instance.penguin.0ops.sjtu.cn 18496 |
AnatahEtodokuSakebi
shout out your inner thoughts!!!!! maybe encrypted with AES…
这一题是deepseek大失败题,所以PoC是我手写的((
1 | import requests |
NoisyCat
电话里听到了奇怪的声音。
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
1 | void __fastcall sub_140001008(int filesize, __int64 input_file_buf, __int64 output_file_buf) |
省流就是:先输出两个0,然后每个byte逆转,在两边加上一个1一个0.
1 | void __fastcall sub_1400010B4(int input_data_bit, __int64 a2) |
直接嗯逆好吧
1 | from scipy.io import wavfile |
总之出来的结果相当简洁明了
1 | And cat said, "Let there be flag," and there was flag. |