HECTF 2024 WriteUp

6.7k words

刚出新手村后正经打的第一场个人赛,单就re方向来说感觉难度还行?

Reverse方向:

babyre:

反编译观察主函数:

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 v3; // rdx
__int64 v4; // rdx
char v6[32]; // [rsp+0h] [rbp-A0h] BYREF
char v7[32]; // [rsp+20h] [rbp-80h] BYREF
char v8[32]; // [rsp+40h] [rbp-60h] BYREF
char v9[40]; // [rsp+60h] [rbp-40h] BYREF
unsigned __int64 v10; // [rsp+88h] [rbp-18h]

v10 = __readfsqword(0x28u);
std::string::basic_string(v6, a2, a3);
std::string::basic_string(v7, a2, v3);
std::string::basic_string(v8, a2, v4);
std::operator<<<std::char_traits<char>>(&std::cout, &unk_21AD); //输出“enter a string:”
std::operator>><char>(&std::cin, v6); //输入
sub_147E();
sub_1558();
sub_13A9(v8, v6);
std::string::operator=(v7, v6);
std::string::basic_string(v9, v6);
sub_1920(v9, v7);
std::string::~string(v9);
std::string::basic_string(v9, v7);
sub_17A7(v9);
std::string::~string(v9);
std::string::~string(v8);
std::string::~string(v7);
std::string::~string(v6);
return 0LL;
}

查看其中的sub_147Esub_1558

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
void sub_147E()
{
__int64 v0; // rax
__int64 v1; // rax
int i; // [rsp+4h] [rbp-Ch]
int j; // [rsp+8h] [rbp-8h]

for ( i = 0; i <= 99; ++i )
{
for ( j = 0; j <= 99; ++j )
{
if ( (((_BYTE)j * (_BYTE)i + (_BYTE)i - (_BYTE)j) & 1) != 0 )
v0 = std::operator<<<std::char_traits<char>>(&std::cout, "Odd result: ");
else
v0 = std::operator<<<std::char_traits<char>>(&std::cout, "Even result: ");
v1 = std::ostream::operator<<(v0, (unsigned int)(j * i + i - j));
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
}
}
}
__int64 sub_1558()
{
__int64 v0; // rax

if ( (double)rand() / 2147483647.0 >= 0.5 )
v0 = std::operator<<<std::char_traits<char>>(&std::cout, "Random value is greater than or equal to 0.5");
else
v0 = std::operator<<<std::char_traits<char>>(&std::cout, "Random value is less than 0.5");
return std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
}

这两个函数仅仅展示了判断奇偶性和随机生成布尔值的算法,再看sub_13A9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 __fastcall sub_13A9(__int64 a1, __int64 a2)
{
char v2; // bl
char v3; // bl
__int64 v5; // [rsp+18h] [rbp-38h] BYREF
__int64 v6; // [rsp+20h] [rbp-30h] BYREF
unsigned __int64 i; // [rsp+28h] [rbp-28h]
unsigned __int64 v8; // [rsp+30h] [rbp-20h]
unsigned __int64 v9; // [rsp+38h] [rbp-18h]

v9 = __readfsqword(0x28u);
v6 = std::string::length(a2);
v5 = std::string::length(a1);
v8 = *(_QWORD *)sub_1DC2(&v5, &v6);
for ( i = 0LL; i < v8; ++i )
{
v2 = *(_BYTE *)std::string::operator[](a1, i);
v3 = *(_BYTE *)std::string::operator[](a2, i) ^ v2;
*(_BYTE *)std::string::operator[](a1, i) = v3;
}
return v9 - __readfsqword(0x28u);
}

发现是一个简单的异或,但实际运行却由于未知原因会产生段错误,查看v8的来源函数sub_1DC2

1
2
3
4
5
6
7
_QWORD *__fastcall sub_1DC2(_QWORD *a1, _QWORD *a2)
{
if ( *a2 >= *a1 )
return a1;
else
return a2;
}

只是比较了两个数组的长度大小,取两个之中更短的那个长度,查看main后续的sub_1920

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
unsigned __int64 __fastcall sub_1920(__int64 a1, __int64 a2)
{
__int64 v2; // rdx
_BYTE *v3; // rax
int i; // [rsp+10h] [rbp-A0h]
int v6; // [rsp+14h] [rbp-9Ch]
int j; // [rsp+18h] [rbp-98h]
int v8; // [rsp+1Ch] [rbp-94h]
_WORD *v9; // [rsp+20h] [rbp-90h]
unsigned __int64 v10; // [rsp+28h] [rbp-88h]
char v11[44]; // [rsp+30h] [rbp-80h] BYREF
int v12; // [rsp+5Ch] [rbp-54h]
__int64 v13[6]; // [rsp+60h] [rbp-50h] BYREF
__int16 v14; // [rsp+90h] [rbp-20h]
unsigned __int64 v15; // [rsp+98h] [rbp-18h]

v15 = __readfsqword(0x28u);
memset(v13, 0, sizeof(v13));
v14 = 0;
for ( i = 0; i <= 63; ++i )
*((_BYTE *)v13 + i) = dword_2040[i];
v10 = std::string::size(a1);
v9 = (_WORD *)std::string::data(a1);
std::string::basic_string(v11, a2, v2);
v12 = 0;
v6 = 0;
for ( j = 0; j < (int)(v10 / 3); ++j )
{
*(_WORD *)((char *)&v12 + 1) = *v9;
v3 = v9 + 1;
v9 = (_WORD *)((char *)v9 + 3);
HIBYTE(v12) = *v3;
std::string::operator+=(v11, (unsigned int)*((char *)v13 + (BYTE1(v12) >> 2)));
std::string::operator+=(
v11,
(unsigned int)*((char *)v13 + (((unsigned __int8)(16 * BYTE1(v12)) | (BYTE2(v12) >> 4)) & 0x3F)));
std::string::operator+=(
v11,
(unsigned int)*((char *)v13 + (((unsigned __int8)(4 * BYTE2(v12)) | (HIBYTE(v12) >> 6)) & 0x3F)));
std::string::operator+=(v11, (unsigned int)*((char *)v13 + (HIBYTE(v12) & 0x3F)));
v6 += 4;
if ( v6 == 76 )
{
std::string::operator+=(v11, "\r\n");
v6 = 0;
}
}
v8 = v10 % 3;
if ( v8 == 1 )
{
BYTE1(v12) = *(_BYTE *)v9;
std::string::operator+=(v11, (unsigned int)*((char *)v13 + (BYTE1(v12) >> 2)));
std::string::operator+=(v11, (unsigned int)*((char *)v13 + ((16 * BYTE1(v12)) & 0x30)));
std::string::operator+=(v11, "==");
}
else if ( v8 == 2 )
{
*(_WORD *)((char *)&v12 + 1) = *v9;
std::string::operator+=(v11, (unsigned int)*((char *)v13 + (BYTE1(v12) >> 2)));
std::string::operator+=(
v11,
(unsigned int)*((char *)v13 + ((16 * BYTE1(v12)) & 0x30 | (unsigned int)(BYTE2(v12) >> 4))));
std::string::operator+=(v11, (unsigned int)*((char *)v13 + ((4 * BYTE2(v12)) & 0x3C)));
std::string::operator+=(v11, "=");
}
std::string::operator=(a2, v11);
std::string::~string(v11);
return v15 - __readfsqword(0x28u);
}

发现是base64加密,查看有没有换表,查看dword_2040发现是yzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/abcdefghijklmnopqrstuvwx,说明换表了,接着往后查看sub_17A7

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
unsigned __int64 __fastcall sub_17A7(__int64 a1)
{
int v2; // [rsp+10h] [rbp-1B0h]
int i; // [rsp+14h] [rbp-1ACh]
int j; // [rsp+18h] [rbp-1A8h]
int v5; // [rsp+1Ch] [rbp-1A4h]
int v6[102]; // [rsp+20h] [rbp-1A0h] BYREF
unsigned __int64 v7; // [rsp+1B8h] [rbp-8h]

v7 = __readfsqword(0x28u);
v5 = std::string::length(a1);
memset(v6, 0, 0x190uLL);
v2 = 0;
for ( i = 0; i < v5; ++i )
v6[i] = *(char *)std::string::operator[](a1, i) ^ (i / 3); //将字符与索引号整除以三进行异或
for ( j = 0; j < v5; ++j )
{
if ( v6[j] != dword_4020[j] ) //比较
{
v2 = 1;
std::operator<<<std::char_traits<char>>(&std::cout, "Wrong Value");
break;
}
}
if ( !v2 )
std::operator<<<std::char_traits<char>>(&std::cout, "Currunt Value");
return v7 - __readfsqword(0x28u);
}

发现是比较结果,查看dword_4020[0x51, 0x43, 0x54, 0x43, 0x55, 0x42, 0x5A, 0x76, 0x4F, 0x46, 0x48, 0x73, 0x5C, 0x46, 0x7D, 0x6B, 0x4E, 0x50, 0x55, 0x68, 0x51, 0x55, 0x7D, 0x3E, 0x45, 0x5D, 0x43, 0x67, 0x45, 0x3E, 0x3B, 0x3D, 0x47, 0x49, 0x53, 0x20, 0x54, 0x59, 0x43, 0x60, 0x40, 0x5F, 0x49, 0x7E, 0x45, 0x38, 0x75, 0x38, 0x47, 0x7C, 0x25, 0x29, 0x5A, 0x7D, 0x59, 0x63, 0x5F, 0x46, 0x57, 0x38, 0x5F, 0x42, 0x79, 0x28],至此已经分析完毕,输入的字符串先与一个key进行异或(疑似会报错),接着经过了换表的base64,再之后又异或了索引号整除以3的值,最后与加密的flag进行比较,先把最后的异或索引号逆向:

1
2
3
4
enc = [0x51, 0x43, 0x54, 0x43, 0x55, 0x42, 0x5A, 0x76, 0x4F, 0x46, 0x48, 0x73, 0x5C, 0x46, 0x7D, 0x6B, 0x4E, 0x50, 0x55, 0x68, 0x51, 0x55, 0x7D, 0x3E, 0x45, 0x5D, 0x43, 0x67, 0x45, 0x3E, 0x3B, 0x3D, 0x47, 0x49, 0x53, 0x20, 0x54, 0x59, 0x43, 0x60, 0x40, 0x5F, 0x49, 0x7E, 0x45, 0x38, 0x75, 0x38, 0x47, 0x7C, 0x25, 0x29, 0x5A, 0x7D, 0x59, 0x63, 0x5F, 0x46, 0x57, 0x38, 0x5F, 0x42, 0x79, 0x28]
for i in range(64):
enc[i] ^= i//3
print('%#x' % enc[i],end=" ")

输出0x51 0x43 0x54 0x42 0x54 0x43 0x58 0x74 0x4d 0x45 0x4b 0x70 0x58 0x42 0x79 0x6e 0x4b 0x55 0x53 0x6e 0x57 0x52 0x7a 0x39 0x4d 0x55 0x4b 0x6e 0x4c 0x37 0x31 0x37 0x4d 0x42 0x58 0x2b 0x58 0x55 0x4f 0x6d 0x4d 0x52 0x47 0x70 0x4b 0x37 0x7a 0x37 0x57 0x6c 0x35 0x38 0x4b 0x6c 0x4b 0x71 0x4d 0x55 0x44 0x2b 0x4b 0x56 0x6d 0x3d,转换得到换表后的base64:QCTBTCXtMEKpXBynKUSnWRz9MUKnL717MBX+XUOmMRGpK7z7Wl58KlKqMUD+KVm=,带入cyberchef解密直接得到flag了:HECTF{8c7d051e5a0e9c567c86fed492720cc8d3389af1}.

littleasm:

观察汇编代码:

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
   .section   .data
flag: .space 28
key: .ascii "rev" ;密钥
data: .byte 0x6a,0x28,0x3d,0x4e,0x2b,0x5,0x63,0x1e,0xd,0x73,0x10,0x1c,0x73,0x24,0x21,0x73,0x5e,0x21,0x31,0x5d,0x21,0x3f,0xc,0xd,0x6d,0x4c,0x3 ;明显是加密的flag
msg_wrong: .ascii "WRONG!!!\n"
msg_right: .ascii "Right!!\n"
.section .text
.globl main
main:
pushl %ebp
movl %esp, %ebp
subl $28, %esp

leal flag, %eax
pushl %eax
call scanf ;输入flag
addl $4, %esp

movl $0, %ecx
encrypt_loop: ;加密循环
cmpl $27, %ecx ;比较输入长度
jge check_loop

movl %ecx, %edx
addl $2, %edx
movl $3, %eax
divl %eax
movzx %dl, %edx ;三个字符一组,每组第一个
movzx data(,%ecx,1), %eax
movzx flag(,%ecx,1), %ebx
movzx key(,%edx,1), %esi
xorl %ebx, %esi ;与key异或
addl $0x2C, %esi ;加上0x2C
movb %sil, data(,%ecx,1)

movl %ecx, %edx ;第二个
addl $1, %edx
movl $3, %eax
divl %eax
movzx %dl, %edx
movzx data(,%ecx + 1,1), %eax
movzx flag(,%ecx + 1,1), %ebx
movzx key(,%edx,1), %esi
xorl %ebx, %esi ;与key异或
addl $0x8, %esi ;加上0x8
movb %sil, data(,%ecx + 1,1)

movl %ecx, %edx ;第三个
movl $3, %eax
divl %eax
movzx %dl, %edx
movzx data(,%ecx + 2,1), %eax
movzx flag(,%ecx + 2,1), %ebx
movzx key(,%edx,1), %esi
xorl %ebx, %esi ;与key异或
xorl $0xC, %esi ;异或0xC
movb %sil, data(,%ecx + 2,1)

addl $3, %ecx
jmp encrypt_loop

check_loop:
movl $0, %ecx
movl $0, %edx
compare_loop:
cmpl $27, %ecx
jge check_result

movzx flag(,%ecx,1), %eax
movzx data(,%ecx,1), %ebx
cmpb %al, %bl
jne set_wrong

addl $1, %ecx
jmp compare_loop

set_wrong:
movl $1, %edx

check_result:
cmpl $0, %edx
jne print_wrong

leal msg_right, %eax
pushl %eax
call printf
addl $4, %esp

jmp end_main

print_wrong:
leal msg_wrong, %eax
pushl %eax
call printf
addl $4, %esp

end_main:
movl %ebp, %esp
popl %ebp
ret

编写解密脚本:

1
2
3
4
5
6
7
8
enc_flag = [0x6a, 0x28, 0x3d, 0x4e, 0x2b, 0x05, 0x63, 0x1e, 0x0d, 0x73, 0x10, 0x1c, 0x73, 0x24, 0x21, 0x73, 0x5e, 0x21, 0x31, 0x5d, 0x21, 0x3f, 0x0c, 0x0d, 0x6d, 0x4c, 0x03]
key = "rev"
flag = bytearray(28)
for i in range(0, 27, 3):
flag[i] = (enc_flag[i] - 0x2C) ^ ord(key[(i + 2) % len(key)]) & 0xFF
flag[i + 1] = (enc_flag[i + 1] - 0x8) ^ ord(key[(i + 1) % len(key)]) & 0xFF
flag[i + 2] = ((enc_flag[i + 2] ^ 0xC) ^ ord(key[i % len(key)])) & 0xFF
print(flag.decode('utf-8'))

运行得到flag:HECTF{Ass1mb1y_13_s0_eas7!}.

PE?py?:

查看压缩包发现需要密码,pyre联系名称是python逆向,用pyinstxtractor提取后再用pycdc反编译pyre.pyc,得到:

1
2
3
4
print('PE?py?\n')
data = 'the_key_is_:'
key = 'Have_a_cup_of_tea_together'
print('Can you uncover the secrets of this file?')

则压缩包密码就是“Have_a_cup_of_tea_together”,解压缩得到main1.exe,发现就是正常的程序,直接用ida反编译,查看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
int __fastcall main(int argc, const char **argv, const char **envp)
{
int i_0; // [rsp+44h] [rbp-Ch]
int flag; // [rsp+48h] [rbp-8h]
int i; // [rsp+4Ch] [rbp-4h]

_main();
scanf("%s", cipher); //输入
for ( i = 0; i <= 11; i += 2 ) //加密6次,一次带入8个字符,共48字符
encrypt(0x20u, (uint32_t *)&cipher[4 * i], (const uint32_t *)keys); //加密
flag = 0;
for ( i_0 = 0; i_0 <= 49; ++i_0 )
{
if ( cipher[i_0] != data[i_0] ) //比较
{
puts("error T_T");
flag = 1;
break;
}
}
if ( !flag )
puts("Right!!");
return 0;
}

发现结构很清晰,再查看加密函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void __cdecl encrypt(unsigned int num_rounds, uint32_t *v, const uint32_t *key)
{
uint32_t sum; // [rsp+10h] [rbp-10h]
uint32_t v1; // [rsp+14h] [rbp-Ch]
uint32_t v0; // [rsp+18h] [rbp-8h]
unsigned int i; // [rsp+1Ch] [rbp-4h]

v0 = *v;
v1 = v[1];
sum = 0;
for ( i = 0; i < num_rounds; ++i )
{
v0 += (((v1 >> 5) ^ (16 * v1)) + v1) ^ (key[sum & 3] + sum);
sum += 0x9E3779B9;
v1 += (((v0 >> 5) ^ (16 * v0)) + v0) ^ (key[(sum >> 11) & 3] + sum);
}
*v = v0;
v[1] = v1;
}

发现是XTEA,查看数组得到key为[0x48, 0x45, 0x43, 0x54, 0x46, 0x32, 0x30, 0x32, 0x34, 0x77, 0x61, 0x69, 0x74, 0x79, 0x6F, 0x75]HECTF2024waityou),转换为32位整数得到[0x54434548, 0x32303246, 0x69617734, 0x756F7974],而加密的flag则是[0x94, 0x61, 0x59, 0xDF, 0xD6, 0x74, 0xFE, 0x2C, 0x4D, 0xAE, 0x55, 0x13, 0x87, 0x7A, 0x71, 0xB6, 0x7B, 0xB5, 0x7B, 0xF2, 0xC5, 0x36, 0xD4, 0x08, 0xAF, 0xE1, 0xC8, 0xC5, 0x8F, 0xBD, 0x85, 0x0A, 0x32, 0x00, 0xA7, 0x19, 0xF4, 0xFE, 0x0C, 0x40, 0xFC, 0xE1, 0x02, 0xAF, 0xB4, 0xCF, 0xED, 0xCD](48长度,带入XTEA加密,4个字符一组是[0xDF596194, 0x2CFE74D6, 0x1355AE4D, 0xB6717A87, 0xF27BB57B, 0x08D436C5, 0xC5C8E1AF, 0x0A85BD8F, 0x19A70032, 0x400CFEF4, 0xAF02E1FC, 0xCDEDCFB4]),基本信息已经获得完全,还原加密函数:

1
2
3
4
5
6
7
8
9
10
11
m1 = 0x34333231  # 小端序前四字节
m2 = 0x38373635 # 小端序后四字节
# XTEA
key = [0x54434548, 0x32303246, 0x69617734, 0x756F7974] # 密钥
ttl = 0 # sum
for i in range(32):
m1 = (m1 + ((((m2 >> 5) ^ (m2 << 4)) + m2) ^ (key[ttl & 3] + ttl))) & 0xFFFFFFFF
ttl = (ttl + 0x9E3779B9) & 0xFFFFFFFF
m2 = (m2 + ((((m1 >> 5) ^ (m1 << 4)) + m1) ^ (key[(ttl >> 11) & 3] + ttl))) & 0xFFFFFFFF

print('%#x' % m1, '%#x' % m2)

带入检验发现输出结果相同,编写逆向解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 密文
enc = [0xDF596194, 0x2CFE74D6, 0x1355AE4D, 0xB6717A87, 0xF27BB57B, 0x08D436C5,
0xC5C8E1AF, 0x0A85BD8F, 0x19A70032, 0x400CFEF4, 0xAF02E1FC, 0xCDEDCFB4]
c1 = [0xDF596194, 0x1355AE4D, 0xF27BB57B, 0xC5C8E1AF, 0x19A70032, 0xAF02E1FC]
c2 = [0x2CFE74D6, 0xB6717A87, 0x08D436C5, 0x0A85BD8F, 0x400CFEF4, 0xCDEDCFB4]
key = [0x54434548, 0x32303246, 0x69617734, 0x756F7974] # 密钥
# TEA解密
for i in range(len(c1)):
ttl = (32 * 0x9E3779B9) & 0xFFFFFFFF
for j in range(32):
c2[i] = (c2[i] - ((((c1[i] >> 5) ^ (c1[i] << 4)) + c1[i]) ^ (key[(ttl >> 11) & 3] + ttl))) & 0xFFFFFFFF
ttl = (ttl - 0x9E3779B9) & 0xFFFFFFFF
c1[i] = (c1[i] - ((((c2[i] >> 5) ^ (c2[i] << 4)) + c2[i]) ^ (key[ttl & 3] + ttl))) & 0xFFFFFFFF

print('%#x' % c1[i], '%#x' % c2[i])

输出:0x54434548 0x38357b46 0x31306564 0x612d6366 0x2d623666 0x36666338 0x6338652d 0x62642d61 0x34363935 0x62306563 0x7d6531 0x0,十六进制转字符串得TCEH 85{F 10ed a-cf -b6f 6fc8 c8e- bd-a 4695 b0ec }e1,转换端序得到flag:HECTF{58de01fc-af6b-8cf6-e8ca-db5964ce0b1e}.

ezAndroid:

题目提示说该APK不能正常运行,那么只能静态分析了,查看MainActivity,发现了引用的本地库ezandroid,还发现了key字符串为“HECTF”,接下来给出了一个RC4解密函数,然后用RC4和key解密了一个名为enc.png的文件,尝试写入/sdcardright.txt,此外还发现了一个CheckActivity,点击按钮时执行下面的代码:

1
2
3
4
5
6
7
8
9
public void onClick(View view) {
CheckActivity.this.d0func();
if (CheckActivity.this.stringFromJNI(CheckActivity.this.input.getText().toString().trim()) == 1) {
Toast.makeText(CheckActivity.this, "you get it", 0).show();
}
else {
Toast.makeText(CheckActivity.this, "try again", 0).show();
}
}

那么关键的加密应该都在so层,而此外CheckActivity还给出了一个func函数,会将16长度的数组与“hectf2024”循环异或,但貌似没找到引用,至此线索分成两个方向,一个是enc.png,另一个是so文件,提取资源文件,发现enc.png有7.3M,先将enc.png试图进行RC4解密,得到了right.txt,发现其实是png文件,查看得到flag格式是HECTF{填入数据的MD5},接下来查看so文件,找到两个函数并反编译:

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
unsigned __int64 __fastcall Java_com_hectf2024_ezandroid_CheckActivity_d0func(__int64 a1)
{
int v1; // ecx
int v2; // r8d
int v3; // r9d
int v4; // r8d
int v5; // r8d
int v6; // r9d
__int64 v8; // [rsp+20h] [rbp-F0h]
__int64 v9; // [rsp+28h] [rbp-E8h]
__int64 v10; // [rsp+30h] [rbp-E0h]
__int64 v11; // [rsp+38h] [rbp-D8h]
int v12; // [rsp+40h] [rbp-D0h]
int v13; // [rsp+48h] [rbp-C8h]
int v14; // [rsp+50h] [rbp-C0h]
__int64 v15; // [rsp+58h] [rbp-B8h]
int i; // [rsp+64h] [rbp-ACh]
int j; // [rsp+64h] [rbp-ACh]
pthread_t newthread; // [rsp+78h] [rbp-98h] BYREF
int v19[16]; // [rsp+80h] [rbp-90h] BYREF
int v20[18]; // [rsp+C0h] [rbp-50h] BYREF
unsigned __int64 v21; // [rsp+108h] [rbp-8h]

v21 = __readfsqword(0x28u);
pthread_create(&newthread, 0LL, start_routine, 0LL);
pthread_join(newthread, 0LL);
v15 = sub_2F60(a1, "com/hectf2024/ezandroid/CheckActivity");
v14 = sub_2F90(a1, v15, (__int64)"<init>", (__int64)"()V");
v13 = sub_2FF0(a1, v15, v14, v1, v2, v3);
v12 = sub_2F90(a1, v15, (__int64)"func", (__int64)"([I)Ljava/lang/String;"); //刚刚的循环异或
for ( i = 0; i < 16; ++i )
v20[i] = dword_5000[i]; //[0x58, 0x12, 0x1A, 0x0C, 0x1B, 0x5E, 0x58, 0x40, 0x46, 0x06, 0x0A, 0x26, 0x44, 0x0C, 0x56, 0x48]
for ( j = 0; j < 16; ++j )
v19[j] = dword_5040[j]; //[0x15, 0x10, 0x48, 0x06, 0x19, 0x42, 0x6D, 0x18, 0x12, 0x0F, 0x27, 0x2F, 0x33, 0x1B, 0x50, 0x41]
v11 = sub_31A0(a1, 16LL);
sub_31D0(a1, v11, 0, 0x10u, (__int64)v20);
v10 = sub_3230(a1, v13, v12, v11, v4, 0);
v9 = sub_31A0(a1, 16LL);
sub_31D0(a1, v9, 0, 0x10u, (__int64)v19);
v8 = sub_3230(a1, v13, v12, v9, v5, v6);
qword_51C0 = sub_2BF0(a1, v10, 0LL);
qword_51C8 = sub_2BF0(a1, v8, 0LL);
sub_33E0(a1, v11);
sub_33E0(a1, v9);
sub_33E0(a1, v15);
return __readfsqword(0x28u);
}

貌似是把这两个数组带入了func函数进行了加密,异或后的结果为:

1
2
Xor5000 = [0x30, 0x77, 0x79, 0x78, 0x7d, 0x6c, 0x68, 0x72, 0x72, 0x6e, 0x6f, 0x45, 0x30, 0x6a, 0x64, 0x78]
Xor5040 = [0x7d, 0x75, 0x2b, 0x72, 0x7f, 0x70, 0x5d, 0x2a, 0x26, 0x67, 0x42, 0x4c, 0x47, 0x7d, 0x62, 0x71]

但并不清楚哪个是flag,再看另一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall Java_com_hectf2024_ezandroid_CheckActivity_stringFromJNI(__int64 a1, __int64 a2, __int64 a3)
{
char *v4; // [rsp+28h] [rbp-38h]
pthread_t newthread[2]; // [rsp+50h] [rbp-10h] BYREF

newthread[1] = __readfsqword(0x28u);
pthread_create(newthread, 0LL, start_routine, 0LL);
pthread_join(newthread[0], 0LL);
v4 = (char *)sub_2BF0(a1, a3, 0LL);
sub_2090();
return (unsigned int)sub_1028(v4);
}

其中sub_1028查看后内部是一个标准的TEA加密,只会返回1且带入的两个数全部相同:

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall sub_1028(const char *a1)
{
pthread_t newthread[2]; // [rsp+20h] [rbp-10h] BYREF

newthread[1] = __readfsqword(0x28u);
sub_1140(qword_51C8, qword_51C8);
pthread_create(newthread, 0LL, start_routine, 0LL);
pthread_join(newthread[0], 0LL);
return 1LL;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall sub_1140(unsigned int *a1, _DWORD *a2)
{
__int64 result; // rax
unsigned int i; // [rsp+14h] [rbp-20h]
int v4; // [rsp+18h] [rbp-1Ch]
unsigned int v5; // [rsp+1Ch] [rbp-18h]
unsigned int v6; // [rsp+20h] [rbp-14h]

v6 = *a1;
v5 = a1[1];
v4 = 0;
for ( i = 0; i < 0x20; ++i )
{
v4 += 0x9E3779B9;
v6 += (a2[1] + (v5 >> 5)) ^ (v4 + v5) ^ (*a2 + 16 * v5);
v5 += (a2[3] + (v6 >> 5)) ^ (v4 + v6) ^ (a2[2] + 16 * v6);
}
*a1 = v6;
result = v5;
a1[1] = v5;
return result;
}

但没找到这些加密与上述两个数组之间的关系,再仔细观察,发现sub_2090有明显的ollvm混淆,用d810去混淆后,发现会检测sub_1028并将其替换为sub_1E20,也就是之前的sub_1028实际上是假的,真正的加密逻辑是sub_1E20,观察sub_1E20发现还是很混乱,再次去除ollvm混淆得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
__int64 __fastcall sub_1E20(__int64 a1)
{
int j; // [rsp+14h] [rbp-3Ch]
int i; // [rsp+18h] [rbp-38h]
unsigned int v4; // [rsp+1Ch] [rbp-34h]
pthread_t newthread; // [rsp+28h] [rbp-28h] BYREF
char v6[24]; // [rsp+30h] [rbp-20h] BYREF
unsigned __int64 v7; // [rsp+48h] [rbp-8h]

v7 = __readfsqword(0x28u);
pthread_create(&newthread, 0LL, start_routine, 0LL);
pthread_join(newthread, 0LL);
sub_1220();
v4 = 1;
for ( i = 0; i < 16; ++i )
v6[i] = *(_BYTE *)(a1 + i);
sub_1C90(v6, qword_51C0, qword_51C8); //加密,51C0是异或后的5000(X5000),51C8是异或后的5040(X5040)
for ( j = 0; j < 16; ++j )
{
if ( dword_5080[j] != (unsigned __int8)v6[j] )//真正的比较:[0xC6, 0x9B, 0x16, 0x0F, 0x67, 0x03, 0x68, 0xB9, 0x3F, 0xD7, 0xE9, 0x16, 0x0E, 0x97, 0x63, 0xBB]
v4 = 0;
}
return v4;
}

观察sub_1220:

1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 sub_1220()
{
__int64 result; // rax
int i; // [rsp+0h] [rbp-Ch]

for ( i = 0; i < 256; ++i )
{
byte_51D0[i] = byte_50C0[i];
byte_52D0[i] = byte_51D0[i] ^ 0x18;
result = (unsigned int)(i + 1);
}
return result;
}

发现是把256项数组byte_50C0异或0x18后存入byte_52D0,猜测可能是s盒之类的东西,提取出来数据为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
byte_50C0 = [0x7B, 0x64, 0x6F, 0x63, 0x0E, 0x73, 0x77, 0xDD, 0x28, 0x19, 0x7F, 0x33, 0xE6, 0xCF, 0xB3, 0x6E,
0xD2, 0x9A, 0xD1, 0x65, 0xE2, 0x41, 0x5F, 0xE8, 0xB5, 0xCC, 0xBA, 0xB7, 0x84, 0xBC, 0x6A, 0xD8,
0xAF, 0xE5, 0x8B, 0x3E, 0x2E, 0x27, 0xEF, 0xD4, 0x2C, 0xBD, 0xFD, 0xE9, 0x69, 0xC0, 0x29, 0x0D,
0x1C, 0xDF, 0x3B, 0xDB, 0x00, 0x8E, 0x1D, 0x82, 0x1F, 0x0A, 0x98, 0xFA, 0xF3, 0x3F, 0xAA, 0x6D,
0x11, 0x9B, 0x34, 0x02, 0x03, 0x76, 0x42, 0xB8, 0x4A, 0x23, 0xCE, 0xAB, 0x31, 0xFB, 0x37, 0x9C,
0x4B, 0xC9, 0x18, 0xF5, 0x38, 0xE4, 0xA9, 0x43, 0x72, 0xD3, 0xA6, 0x21, 0x52, 0x54, 0x40, 0xD7,
0xC8, 0xF7, 0xB2, 0xE3, 0x5B, 0x55, 0x2B, 0x9D, 0x5D, 0xE1, 0x1A, 0x67, 0x48, 0x24, 0x87, 0xB0,
0x49, 0xBB, 0x58, 0x97, 0x8A, 0x85, 0x20, 0xED, 0xA4, 0xAE, 0xC2, 0x39, 0x08, 0xE7, 0xEB, 0xCA,
0xD5, 0x14, 0x0B, 0xF4, 0x47, 0x8F, 0x5C, 0x0F, 0xDC, 0xBF, 0x66, 0x25, 0x7C, 0x45, 0x01, 0x6B,
0x78, 0x99, 0x57, 0xC4, 0x3A, 0x32, 0x88, 0x90, 0x5E, 0xF6, 0xA0, 0x0C, 0xC6, 0x46, 0x13, 0xC3,
0xF8, 0x2A, 0x22, 0x12, 0x51, 0x1E, 0x3C, 0x44, 0xDA, 0xCB, 0xB4, 0x7A, 0x89, 0x8D, 0xFC, 0x61,
0xFF, 0xD0, 0x2F, 0x75, 0x95, 0xCD, 0x56, 0xB1, 0x74, 0x4E, 0xEC, 0xF2, 0x7D, 0x62, 0xB6, 0x10,
0xA2, 0x60, 0x3D, 0x36, 0x04, 0xBE, 0xAC, 0xDE, 0xF0, 0xC5, 0x6C, 0x07, 0x53, 0xA5, 0x93, 0x92,
0x68, 0x26, 0xAD, 0x7E, 0x50, 0x1B, 0xEE, 0x16, 0x79, 0x2D, 0x4F, 0xA1, 0x9E, 0xD9, 0x05, 0x86,
0xF9, 0xE0, 0x80, 0x09, 0x71, 0xC1, 0x96, 0x8C, 0x83, 0x06, 0x9F, 0xF1, 0xD6, 0x4D, 0x30, 0xC7,
0x94, 0xB9, 0x91, 0x15, 0xA7, 0xFE, 0x5A, 0x70, 0x59, 0x81, 0x35, 0x17, 0xA8, 0x4C, 0xA3, 0x0E]

则byte52d0为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
byte_52d0 = [0x63, 0x7c, 0x77, 0x7b, 0x16, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16]

发现是AES的S盒,再观察sub_1C90:

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned __int64 __fastcall sub_1C90(_BYTE *a1, __int64 a2, __int64 a3)
{
int i; // [rsp+14h] [rbp-6Ch]
char v5[24]; // [rsp+60h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+78h] [rbp-8h]

v6 = __readfsqword(0x28u);
for ( i = 0; i < 16; ++i )
v5[i] = *(_BYTE *)(a3 + i) ^ a1[i]; //v5为X5040异或v6
sub_1A30(v5, a2); //带入了v5的值和X5000的值
memcpy(a1, v5, 0x10uLL); //v6为处理后的结果
return __readfsqword(0x28u);
}

再看sub_1A30主要逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__memcpy_chk(v6, a1, 16LL, 16LL);
sub_1860(a2, v4); //密钥扩展
sub_1810(v6, v4); //异或
for ( i = 1; i < 10; ++i ) //循环9次
{
sub_1280(v6); //赋值
sub_12D0(v6); //换位
sub_13A0(v6); //加密
sub_1810(v6, &v4[16 * i]); //异或
}
sub_1280(v6); //赋值
sub_12D0(v6); //换位
sub_1810(v6, &v5); //异或
memcpy(a1, v6, 0x10uLL);

发现就是AES_CBC加密,则总的加密逻辑是(sub_1A30转换成的python代码附在下面):

Dword5000—func—XOR hectf2024→X5000(key)

Dword5040—func—XOR hectf2024→X5040(iv)

Byte50D0—sub1220—51D0—XOR 0x18→52D0(S盒)

AES_CBC(input,X5000,X5040)→dword_5080(密文)

先从要比较的dword_5080提取得到[0xC6, 0x9B, 0x16, 0x0F, 0x67, 0x03, 0x68, 0xB9, 0x3F, 0xD7, 0xE9, 0x16, 0x0E, 0x97, 0x63, 0xBB],用X5000和X5040AES_CBC解得明文十六进制为d8ac2d59f876b54a554242e4e6d32078,计算32位小写md5带入检验发现不对,再仔细检测so文件发现还有三个函数,其中一个对dword_5000每个数的值加上了1,另一个对dword_5040每个数的值先加上了2又异或了0x18,再次代入,得到xor5000和xor5040的真正值:

1
2
Xor5000:0x31 0x76 0x78 0x79 0x7a 0x6d 0x69 0x73 0x73 0x6f 0x6e 0x44 0x31 0x6b 0x65 0x79(1vxyzmissonD1key)
Xor5040:0x67 0x6f 0x31 0x64 0x65 0x6e 0x47 0x30 0x38 0x61 0x54 0x4a 0x59 0x63 0x78 0x6b(go1denG08aTJYcxk)

AES_CBC解密得到明文“adHIHFBDCJsoiak”,计算md5得到cfa76d5f2d5aecdb53e79f644cb90fc2即为flag.

easyree:

拖入exeinfope发现是upx,但被篡改了,010editor打开发现UPX头(UPX012、UPX!)被替换成了CTF,修改后用UPX脱壳成功,查看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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
int __fastcall main(int argc, const char **argv, const char **envp)
{
int data[27]; // [rsp+20h] [rbp-A0h]
int k; // [rsp+8Ch] [rbp-34h]
char str[30]; // [rsp+90h] [rbp-30h] BYREF
int flag; // [rsp+B8h] [rbp-8h]
int i; // [rsp+BCh] [rbp-4h]

_main();
data[0] = 35; //加密的flag
data[1] = 33;
data[2] = 32;
data[3] = 40;
data[4] = 37;
data[5] = 126;
data[6] = 40;
data[7] = 70;
data[8] = 82;
data[9] = 4;
data[10] = 75;
data[11] = 82;
data[12] = 76;
data[13] = 3;
data[14] = 82;
data[15] = 4;
data[16] = 72;
data[17] = 79;
data[18] = 123;
data[19] = 79;
data[20] = 125;
data[21] = 66;
data[22] = 68;
data[23] = 4;
data[24] = 79;
data[25] = 73;
data[26] = 112;
flag = 0;
std::operator>><char,std::char_traits<char>>(refptr__ZSt3cin, str); //输入str
encrypt(str, 8); //加密函数
if ( strlen(str) != 27 ) //长度为27
{
flag = 1;
printf(“Your length is wrong 0.0”);
}
puts("Guess how many xor’s there are");
std::istream::operator>>(refptr__ZSt3cin); //输入k
for ( I = 0; I <= 26; ++I )
{
If ( str[i] – 21 != (data[i] ^ k) ) //加密+比较
{
flag = 1;
break;
}
}
if ( flag == 1 )
puts("YOU ARE WRONG!");
else
puts("Genius!");
return 0;
}

发现data是被加密的flag的中间态,提取得到[0x23, 0x21, 0x20, 0x28, 0x25, 0x7E, 0x28, 0x46, 0x52, 0x04, 0x4B, 0x52, 0x4C, 0x03, 0x52, 0x04, 0x48, 0x4F, 0x7B, 0x4F, 0x7D, 0x42, 0x44, 0x04, 0x4F, 0x49, 0x70],观察加密函数encrypt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __cdecl encrypt(char *a, int tot)			//起始tot = 8
{
int i; // [rsp+2Ch] [rbp-54h]

for ( i = 0; i < strlen(a); ++i )
{
if ( a[i] <= 64 || a[i] > 90 ) //不是大写英语字母
{
if ( a[i] > 96 && a[i] <= 122 ) //是小写英语字母
a[i] = (a[i] - 97 + tot++) % 26 + 97; //字母索引+tot+位
}
else //大写英语字母
{
a[i] = (a[i] - 65 + tot++) % 26 + 65; //字母索引+tot+位
}
}
}

发现是一个简单的变位凯撒密码,在下方还有一个小的加密,将每项的ascii减去21再与data每项与k异或得到的值进行比较, 由于flag格式是HECTF{,第六个字符不参与变位凯撒加密,所以k其实可以计算出来,有123({)-21 = 102(0x66) = 126(0x7E)^k,解得k=0x18(24),同理带入最后一个字符125(})-21 = 104(0x68) = 112(0x70)^0x18,验证通过,则data^k = encflag-21 = [0x3b, 0x39, 0x38, 0x30, 0x3d, 0x66, 0x30, 0x5e, 0x4a, 0x1c, 0x53, 0x4a, 0x54, 0x1b, 0x4a, 0x1c, 0x50, 0x57, 0x63, 0x57, 0x65, 0x5a, 0x5c, 0x1c, 0x57, 0x51, 0x68],得到encflag = [0x50, 0x4e, 0x4d, 0x45, 0x52, 0x7b, 0x45, 0x73, 0x5f, 0x31, 0x68, 0x5f, 0x69, 0x30, 0x5f, 0x31, 0x65, 0x6c, 0x78, 0x6c, 0x7a, 0x6f, 0x71, 0x31, 0x6c, 0x66, 0x7d],转换得PNMER{Es_1h_i0_1elxlzoq1lf},代换回凯撒加密之前得到HECTF{Re_1s_s0_1nterest1ng}.

附解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def encrypt(s, tot=8):
result = []
for char in s:
if 'a' <= char <= 'z': # 小写英语字母
offset = ord('a')
encrypted_char = chr((ord(char) - offset - tot) % 26 + offset)
tot += 1
elif 'A' <= char <= 'Z': # 大写英语字母
offset = ord('A')
encrypted_char = chr((ord(char) - offset - tot) % 26 + offset)
tot += 1
else: # 不是英文字母
encrypted_char = char
result.append(encrypted_char)

return ''.join(result)

original_text = "PNMER{Es_1h_i0_1elxlzoq1lf}"
encrypted_text = encrypt(original_text)
print("Encrypted:", encrypted_text)

Pwn方向:

sign in:

观察程序主函数,要先输入用户名和密码,用户名不能超过8个字符,密码要与m(“HECTF2024!”)相等,之后就可以进入backd00r函数并拿到shell,但在拿到之前程序关闭了输出(close(1)),因此拿到shell之后将flag内容重定向到标准错误(输入cat /flag >&2),得到flag:HECTF{01a86b7bc0e0e58d25ea10edccedfc2762f8e3fd}.

Crypto方向:

迷茫的艾米莉:

题目提示了维尼吉亚密码栅栏密码,由于维尼吉亚密码不会改变大括号的位置,所以先用栅栏密码,将原密码Y2w9Iobe_v_Ufbm0ajI05bfzvTP1b_c}{lr带入cyberchef选择Rail Fence,栏数选6得到初步解密结果YIUIT{P0fo2bb51lbbmew_0f_rczav9_jv},再用维尼吉亚密码解密,带入key=responsibility得到flag:HECTF{C0ng2at51ations_0n_comin9_in}.

seven more:

发现e与p-1,q-1均不互质,无法求得模逆元d,又观察发现p-1是e的整数倍,q-1与e共因数1009,考虑使用AMM算法得到原信息,建立脚本:

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 Crypto.Util.number import *
from gmpy2 import *
from random import randint

def AMM(c, r, p):
s = p - 1
t = 0
while s % r == 0:
t += 1
s //= r

non_residue = None
while True:
non_residue = int(randint(1, p - 1))
if pow(non_residue, (p - 1) // r, p) != 1:
break

e = pow(non_residue, s * (r ** (t - 1)), p)
root = pow(int(c), gmpy2.isqrt(pow(r, t - 1, int(s * (r ** (t - 1))))), int(p))
for _ in range(t - 1):
k = 0
tmp = c
for i in range(1, r):
tmp_next = (tmp * non_residue ** (s * (r ** (t - 2)) * i)) % p
if tmp_next == root:
k = r - i
break
root = (root * pow(non_residue, s * k * (r ** (t - 2)), p)) % p

d = GCD(r, s)
alpha = inverse(r // d, s // d)
final_root = pow(root, alpha, p)
return final_root, d, s, t, non_residue, r

def ALL_Solution(root, r, p, s, t, non_residue):
K = set()
e = pow(non_residue, int(s * (r ** (t - 1))), p)
for i in range(r):
K.add(pow(e, i, p))
K = list(K)
solutions = []
for k in K:
solution = (root * pow(k, -1, p)) % p
solutions.append(solution)
return solutions

# 已知参数
e = 1009 * 7
n = 211174039496861685759253930135194075344490160159278597570478160714793843648384778026214533259531963057737358092962077790023796805017455012885781079402008604439036453706912819711606916173828620000813663524065796636039272173716362247511054616756763830945978879273812551204996912252317081836281439680223663883250992957309172746671265758427396929152878633033380299036765665530677963287445843653357154379447802151146728382517702550201
c = 191928992610587693825282781627928404831411364407297375816921425636703444790996279718679090695773598752804431891678976685083991392082287393228730341768083530729456781668626228660243400914135691435374881498580469432290771039798758412160073826112909167507868640830965603769520664582121780979767127925146139051005022993085473836213944491149411881673257628267851773377966008999511673741955131386600993547529438576918914852633139878066
p = 31160882390461311665815471693453819123352546432384109928704874241292707178454748381602275005604671000436222741183159072136366212086549437801626015758789167455043851748560416003501637268653712148286072544482747238223
q = 6776895366785389188349778634427547683984792095011326393872759455291221057085426285502176493658280343252730331506803173791893339840460125807960788857396637337440004750209164671124188980183308151635629356496128717687

# 求解p和q上的c的e次方根
pr = AMM(c % p, e, p)
qr = AMM(pow(c,inverse(7,q-1),q) , e//7, q)

pl = ALL_Solution(pr[0], e, p, pr[2], pr[3], pr[4])
ql = ALL_Solution(qr[0], e, q, qr[2], qr[3], qr[4])

# 中国剩余定理合并解
p_inv = inverse(p, q)
q_inv = inverse(q, p)
found = 0
for i in pl:
if found :
break
for j in ql:
m = (i*q*q_inv + j*p*p_inv)%n #CRT
if b'HECTF' in long_to_bytes(m):
print(long_to_bytes(m))
found = 1
break

运行得到结果:

1
b'HECTF{go0d_jOb_At_AmM}D~u3<1\xdd\x9f\x81b:,\xbe\xf2\x1c\xf1\xd5\xeeN\xb7w\xce\xae?\xf3~\x99\xbd\xce\xf1\xf1\x10"\xc6:\x85\x08\xf0\xef4h\x8c%d\x9f\xf1\xaf\xfdS\xd1\xcc\x99\x1c\xc3\xe5\x06Z\xdags\xb5R>\xc9\xd4\xb7\x99\xde\x9f\xb0\xccP\xf2\xba\x82A\xfd\xbb\xda\x0e\xf9\xa5\xaa\xfcm\x92\xba\xa8\xff\xf8*\x8a\x95p\xb6\xbe\xc5\xbf\xbe\xe8c'

前面的即为flag:HECTF{go0d_jOb_At_AmM}.

翻一翻:

发现是对称质数emirp,找到2015年ASIS总决赛的RSASR的wp脚本作为模板,更改脚本,爆破pq:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
n = 404647938065363927581436797059920217726808592032894907516792959730610309231807721432452916075249512425255272010683662156287639951458857927130814934886426437345595825614662468173297926187946521587383884561536234303887166938763945988155320294755695229129209227291017751192918550531251138235455644646249817136993
root_n_len = len(str(n)) // 2

def t(a, b, k):
if k == root_n_len:
if a*b == n:
print(a, b)
return
for i in range(10):
for j in range(10):
a1 = a + i*(10**k) + j*(10**(root_n_len-k))
b1 = b + j*(10**k) + i*(10**(root_n_len-k))
if a1*b1 > n:
continue
if (a1+(10**(root_n_len-k)))*(b1+(10**(root_n_len-k))) < n:
continue
if ((a1*b1)%(10**(k+1))) != (n%(10**(k+1))):
continue
t(a1, b1, k+1)

for i in range(10):
t(i*(10**root_n_len), i*(10**root_n_len), 0)

得到:

1
2
p=39316409865082827891559777929907275271727781922450971403181273772573121561800306699150395758615464222134092274991810028405823897933152302724628919678029201
q=10292087691982642720325133979832850482001819947229043122246451685759305199660300816512137527737218130417905422918772717257270992977795519872828056890461393

解得明文SEVDVEZ7SV9yZWExbHlfbDB2ZV9jMnlwdG8hfQ==,base64解密得到HECTF{I_rea1ly_l0ve_c2ypto!}.

Misc方向:

funny:

打开第一张图片看到广告为JK FUN,高德地图查询只有一个结果,在北京市西城区,查看得到所在广场为“西外文化休闲广场”,再查看周围的水系,找到“京城水系慈禧水道”,查看图片发现就是这个地方,填入flag:HECTF{北京市-西城区-西外文化休闲广场-京城水系慈禧水道}发现正确.

简单的压缩包:

查看Re.txt发现给出了压缩包密码的正则表达式:^([a-z]){2}\d([^a-z])\D$,说明密码长度为5,且前两个字符为小写字母,第三个字符为数字,第四个字符不是小写字母,第五个字符不是数字,按要求生成字典文件:

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

lowercase_letters = string.ascii_lowercase
digits = string.digits
non_lowercase_letters = string.ascii_uppercase + string.digits + string.punctuation
non_digits = string.ascii_letters + string.punctuation

with open('dic.txt', 'w') as file:
for combo in itertools.product(lowercase_letters, repeat=2):
for digit in digits:
for non_lower in non_lowercase_letters:
for non_digit in non_digits:
password = ''.join(combo) + digit + non_lower + non_digit
if re.match(r'^([a-z]){2}\d([^a-z])\D$', password):
file.write(password + '\n')
print("complete")

生成后用Archpr爆破,得到密码:np76_,解开压缩包,提取出来图片,猜测有copy/B隐写,直接改后缀为rar,发现果然有,里面有一个zip.zip和一个getzip.py,查看发现zip以key = b”abcdefghijklmnop”iv = b”qwertyuiopasdfgh”进行了加密,AES_CBC解密后打开被解密的zip,发现一个文件夹,里面就是flag.txt,得到flag:HECTF{c292af1-2b2ee35-6398bd4934f7626afc}.

附:python解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import binascii

def decrypt(key, iv, encrypted_data_hex):
encrypted_data = binascii.a2b_hex(encrypted_data_hex)
cipher = AES.new(key, AES.MODE_CBC, iv)
pt = cipher.decrypt(encrypted_data)
unpadded_pt = unpad(pt, AES.block_size)
return unpadded_pt

key = b"abcdefghijklmnop"
iv = b"qwertyuiopasdfgh"

with open("zip.zip", "rb") as f:
encrypted_content_hex = f.read()

if isinstance(encrypted_content_hex, bytes):
encrypted_content_hex = encrypted_content_hex.decode('utf-8')

decrypted_content = decrypt(key, iv, encrypted_content_hex)

with open("dezip.zip", "wb") as f:
f.write(decrypted_content)

Rem_YOU:

下载发现是png,但是010发现是jpg+压缩包,改后缀名为rar,打开发现有一个文件夹,里面是9个二维码的切片,ps合并扫描得到JBCUGVCGPN2VMM3YPBRTOUZYNF4UETSUPB2GM6DWKBZE6N2SIZCTGZ2MOBZG6OLBGNAVOMSLKB6Q====,是flag的base32,解密得到HECTF{uV3xxc7S8iyBNTxtfxvPrO7RFE3gLpro9a3AW2KP}.