警告
本文最后更新于 2021-06-13,文中内容可能已过时。
- ELF 64 位程序, 无壳, 无符号表
- 运行提示输入 key, 随便输点东西, 直接退出
拖进 IDA 找 start 函数, 第一个参数就是 main 函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // positive sp value has been detected, the output may be wrong!
void __fastcall __noreturn start(__int64 a1, __int64 a2, int a3)
{
__int64 v3; // rax
int v4; // esi
__int64 v5; // [rsp-8h] [rbp-8h] BYREF
void *retaddr; // [rsp+0h] [rbp+0h] BYREF
v4 = v5;
v5 = v3;
sub_402B60(
(unsigned int)main,
v4,
(unsigned int)&retaddr,
(unsigned int)sub_4037C0,
(unsigned int)sub_403860,
a3,
(__int64)&v5);
__halt();
}
|
对 main 函数中的函数名和变量进行重命名, 通过它们调用的一些系统调用可以做到这一点, 我没处理完, 不过大概长这样:
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
| // local variable allocation has failed, the output may be wrong!
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // edx
__int64 v4; // rsi
__int64 v5; // rax
__int64 v6; // rdx
__int64 v7; // rcx
u32 *v8; // r8
u32 v9; // er9
__int64 v10; // rax
__int64 v11; // rdx
__int64 v12; // rcx
u32 *v13; // r8
u32 v14; // er9
fd_set v15; // [rsp+10h] [rbp-90h] BYREF
unsigned __int64 v16; // [rsp+98h] [rbp-8h]
v16 = __readfsqword(0x28u);
socket = sub_40245B();
sockfd = sub_4024EB();
dword_4C8570 = sub_40257B(*(__int64 *)&argc, (__int64)argv, v3);
printf((__int64)"Please enter the key:");
while ( 1 )
{
memset(&v15, 0, sizeof(v15));
v15.fds_bits[dword_4C8570 / 64] |= 1LL << (dword_4C8570 % 64);
v15.fds_bits[sockfd / 64] |= 1LL << (sockfd % 64);
sub_44E080(1024, &v15, 0LL, 0LL, 0LL);
if ( (v15.fds_bits[sockfd / 64] & (1LL << (sockfd % 64))) != 0 )
sub_40223C(); // here
v4 = v15.fds_bits[dword_4C8570 / 64];
if ( (v4 & (1LL << (dword_4C8570 % 64))) != 0 )
{
((void (__fastcall *)(__int64))((char *)&sub_401D44 + 1))((__int64)&unk_4C5120);
sub_411D40(v5, v4, v6, v7, v8, v9);
sub_402072();
((void (__fastcall *)(__int64))((char *)&sub_401D44 + 1))((__int64)&unk_4C5130);
sub_411D40(v10, v4, v11, v12, v13, v14);
}
}
}
|
容易找到 sub_40223C
, 再同样进行重命名如下:
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
| // local variable allocation has failed, the output may be wrong!
unsigned __int64 sub_40223C()
{
__int64 v0; // rsi
__int64 v1; // rdi
__int64 v2; // rdx
int v3; // ecx
int v4; // er8
int v5; // er9
unsigned __int64 result; // rax
socklen_t fromlen; // [rsp+8h] [rbp-18h] OVERLAPPED BYREF
signed int isRecv_4; // [rsp+Ch] [rbp-14h]
void *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v10; // [rsp+18h] [rbp-8h]
v10 = __readfsqword(0x28u);
isRecv_4 = 0;
fromlen = 16;
buf = (void *)malloc(10LL);
sub_401120();
v0 = (__int64)buf;
v1 = (unsigned int)sockfd;
isRecv_4 = recvfrom(sockfd, buf, 5uLL, 0, &from, &fromlen);
if ( isRecv_4 > 0 )
{
v1 = (__int64)buf;
sub_4022DE((char *)buf); // here
}
result = __readfsqword(0x28u) ^ v10;
if ( result )
sub_450C80(v1, v0, v2, v3, v4, v5);
return result;
}
|
到这里, 结合 hint, 猜测是用户通过 tty 输入内容, 并通过 socket 发送至本地, 再判断对错. 容易发现 sub_4022DE
是接收到输入后的下一步操作:
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
| unsigned __int64 __fastcall sub_4022DE(char *a1)
{
char *v2; // rdi
__int64 v3; // rcx
__int64 v4; // r8
__int64 v5; // r9
__int64 v6; // rdx
unsigned __int64 result; // rax
int i; // [rsp+18h] [rbp-18h]
int v9; // [rsp+1Ch] [rbp-14h]
char v10[5]; // [rsp+23h] [rbp-Dh] BYREF
unsigned __int64 v11; // [rsp+28h] [rbp-8h]
v11 = __readfsqword(0x28u);
if ( !inp )
inp = malloc(30LL);
sub_401090();
v2 = (char *)inp;
v9 = (unsigned int)sub_401190();
v6 = (unsigned int)cnt;
if ( v9 >= 5 * cnt )
{
for ( i = 0; i <= 4; ++i )
{
nullsub_3();
LayerOne((__int64)v10, i);
a1 = v10;
v2 = (char *)(5 * cnt + inp);
v3 = (unsigned int)((int)LayerTwo(v2, v10, 5) % 257);
v6 = (__int64)res;
res[5 * cnt + i] = v3;
}
++cnt;
}
if ( cnt > 4 )
return proc_then();
result = __readfsqword(0x28u) ^ v11;
if ( result )
sub_450C80((__int64)v2, (__int64)a1, v6, v3, v4, v5);
return result;
}
|
注: 这里有一个 mod257, 待会儿反解的时候是需要考虑到的.
俩加密函数内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| __int64 __fastcall LayerOne(__int64 a1, int a2)
{
int v2; // eax
int counter; // [rsp+18h] [rbp-4h]
counter = 0;
while ( a2 <= 24 )
{
v2 = counter++;
*(_BYTE *)(a1 + v2) = aFlagAreYouSure[a2];
a2 += 5;
}
return nullsub_2();
}
|
这个是把 fake flag 分成五组, 取每一组的第一个字符, 放在 a1 中.
1
2
3
4
5
6
7
8
9
10
11
| // a3 = 5
__int64 __fastcall LayerTwo(char *a1, char *a2, int a3)
{
unsigned int v4; // [rsp+1Ch] [rbp-8h]
int i; // [rsp+20h] [rbp-4h]
v4 = 0;
for ( i = 0; i < a3; ++i )
v4 += a1[i] * a2[i];
return v4;
}
|
把两个数组的对应位置相乘再相加
结合前面 flag
是一列一列取, 而 inp
是一行一行取, 还有一个 LayerTwo
是相乘, 容易想到这就是一个矩阵乘法. 有了这个认知后, 跟进 proc_then
进行查看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| unsigned __int64 __fastcall proc_then()
{
__int64 v0; // rbp
__int64 aim; // rdi
__int64 v2; // rdx
__int64 v3; // rcx
__int64 v4; // r8
__int64 v5; // r9
unsigned __int64 result; // rax
aim = (__int64)&unk_4C5150;
if ( !(unsigned int)sub_4010F0() )
{
aim = 3LL;
sub_40FF90(3);
}
result = __readfsqword(0x28u) ^ *(_QWORD *)(v0 - 8);
if ( result )
sub_450C80(aim, (__int64)res, v2, v3, v4, v5);
return result;
}
|
发现 aim
是乘完的结果, 跟进 sub_40FF90
, 发现他是 syscall
函数, 结合系统调用号3, 这里应该是close掉socket连接.
写一下解密脚本, 需要一点线性代数知识:
$$
AB=X \\
\rarr A=XB^{-1} \\
其中A代表输入,因为取的是行 \\
B代表fake\quad flag生成的矩阵,因为取的是列 \\
而X就是加密后的结果了
$$
使用 sage 求解 (用 numpy
、matlab
都可以):
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
| sage: x = Matrix(GF(257), [[0x38,0x6D,0x4B,0x4B,0xB9],
....: [0x8A,0xF9,0x8A,0xBB,0x5C],
....: [0x8A,0x9A,0xBA,0x6B,0xD2],
....: [0xC6,0xBB,0x05,0x90,0x56],
....: [0x93,0xE6,0x12,0xBD,0x4F]])
sage: b = Matrix(GF(257), [[102, 108, 97, 103, 123],
....: ....: [97, 114, 101, 95, 121],
....: ....: [111, 117, 95, 115, 117],
....: ....: [114, 101, 95, 102, 114],
....: ....: [105, 101, 110, 100, 125]])
sage: flag = x*b.inverse()
sage: for i in flag:
....: for j in i:
....: print(chr(j))
....:
H
a
2
3
_
f
0
n
_
9
n
d
_
G
0
o
d
-
1
u
c
k
-
O
H
|
得到 flag{Ha23_f0n_9nd_G0od_luck_OH}