签到题

一键F5直接获得flag:NCTF{We1come_2_Reverse_Engineering}

Shadowbringer

C++逆向,反编译后乍一看头皮发麻:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[16]; // [rsp+20h] [rbp-60h] BYREF
char v5[15]; // [rsp+30h] [rbp-50h] BYREF
char v6; // [rsp+3Fh] [rbp-41h] BYREF
char v7[16]; // [rsp+40h] [rbp-40h] BYREF
char v8[16]; // [rsp+50h] [rbp-30h] BYREF
char v9[16]; // [rsp+60h] [rbp-20h] BYREF
char v10[32]; // [rsp+70h] [rbp-10h] BYREF

_main();
youknowwhat();
std::string::string((std::string *)v5);
((void (__fastcall *)(char *))std::allocator<char>::allocator)(&v6);
std::string::string(v4, "U>F2UsQXN`5sXMELT=:7M_2<X]^1ThaWF0=KM?9IUhAsTM5:T==_Ns&<Vhb!", &v6);
((void (__fastcall *)(char *))std::allocator<char>::~allocator)(&v6);
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Welcome.Please input your flag:\n");
std::operator>><char>(refptr__ZSt3cin, v5);
std::string::string((std::string *)v8, (const std::string *)v5);
Emet(v7, v8);
((void (__fastcall *)(char *, char *))std::string::operator=)(v5, v7);
std::string::~string((std::string *)v7);
std::string::~string((std::string *)v8);
std::string::string((std::string *)v10, (const std::string *)v5);
Selch(v9, v10);
((void (__fastcall *)(char *, char *))std::string::operator=)(v5, v9);
std::string::~string((std::string *)v9);
std::string::~string((std::string *)v10);
if ( (unsigned __int8)std::operator==<char>(v5, v4) )
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Right.");
else
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Wrong.");
std::string::~string((std::string *)v4);
std::string::~string((std::string *)v5);
return 0;
}

其实大部分都是库函数,关键函数只有:Emet(v7, v8);Selch(v9, v10);
进入Emet函数
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
std::string *__fastcall Emet(std::string *a1, std::string *a2)
{
int i; // ebx
char *v3; // rax
unsigned __int64 v4; // rax
int j; // ebx
unsigned int v6; // eax
char *v7; // rax
unsigned __int64 v9; // [rsp+20h] [rbp-90h] BYREF
char v10; // [rsp+2Eh] [rbp-82h] BYREF
char v11; // [rsp+2Fh] [rbp-81h] BYREF
char v12[16]; // [rsp+30h] [rbp-80h] BYREF
char v13[16]; // [rsp+40h] [rbp-70h] BYREF
char v14[16]; // [rsp+50h] [rbp-60h] BYREF
char v15[16]; // [rsp+60h] [rbp-50h] BYREF
char v16[16]; // [rsp+70h] [rbp-40h] BYREF
char v17[16]; // [rsp+80h] [rbp-30h] BYREF
char v18[16]; // [rsp+90h] [rbp-20h] BYREF
char v19[16]; // [rsp+A0h] [rbp-10h] BYREF

std::allocator<char>::allocator(&v10);
std::string::string(&v9, &unk_48A000, &v10);
std::allocator<char>::~allocator(&v10);
std::allocator<char>::allocator(&v11);
std::string::string(a1, &unk_48A000, &v11);
std::allocator<char>::~allocator(&v11);
for ( i = 0; i < (unsigned __int64)std::string::size(a2); ++i )
{
v3 = (char *)std::string::operator[](a2, i);
std::bitset<8ull>::bitset(v14, (unsigned int)*v3);
std::bitset<8ull>::to_string(v13, v14);
std::operator+<char>(v12, &v9, v13);
std::string::operator=(&v9, v12);
std::string::~string((std::string *)v12);
std::string::~string((std::string *)v13);
}
while ( 1 )
{
v4 = std::string::size((std::string *)&v9);
if ( v4 == 6 * (v4 / 6) )
break;
std::operator+<char>(v15, &v9, 48i64);
std::string::operator=(&v9, v15);
std::string::~string((std::string *)v15);
}
for ( j = 0; j < (unsigned __int64)std::string::size((std::string *)&v9); j += 6 )
{
std::string::substr((std::string *)v18, (unsigned __int64)&v9, j);
std::bitset<6ull>::bitset<char,std::char_traits<char>,std::allocator<char>>(v17, v18, 0i64);
v6 = std::bitset<6ull>::to_ulong(v17);
v7 = (char *)std::string::operator[](&hisoralce, v6);
std::operator+<char>(v16, a1, (unsigned int)*v7);
std::string::operator=(a1, v16);
std::string::~string((std::string *)v16);
std::string::~string((std::string *)v18);
}
while ( (std::string::size(a1) & 3) != 0 )
{
std::operator+<char>(v19, a1, 33i64);
std::string::operator=(a1, v19);
std::string::~string((std::string *)v19);
}
std::string::~string((std::string *)&v9);
return a1;
}

发现可以数组hisoralce,但是进入以后

发现是问号,考虑用动态调试,在Emet后面下断点:

查看该数组后转化成字符串得到一个base64索引表:

1
#$%&'()*+,-.s0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[h]^_`ab

用相同的方法进入Selch函数得到另一个base64索引表:

1
ba`_^]h[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210s.-,+*)('&%$#

正好相反,于是开心地打开base64decode脚本开始跑,然后就错辣!

发现问题

考虑明文

1
U>F2UsQXN`5sXMELT=:7M_2<X]^1ThaWF0=KM?9IUhAsTM5:T==_Ns&<Vhb!

和索引表
1
2
#$%&'()*+,-.s0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[h]^_`ab
ba`_^]h[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210s.-,+*)('&%$#

我们发现=也在这个索引表里,由于base64算法是在末尾补=,因此按照b64解密脚本直接跑肯是错的,正确的方法应该是建立换表到原表的映射再通过b64解密。
于是得到exp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import base64

flag = "U>F2UsQXN`5sXMELT=:7M_2<X]^1ThaWF0=KM?9IUhAsTM5:T==_Ns&<Vhb!"
std_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
my_table = '#$%&\x27()*+,-.s0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[h]^_`ab'
my_table2 = 'ba`_^]h[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210s.-,+*)(\x27&%$#'

flag = flag.translate(str.maketrans(my_table2, std_table))
print(flag) #NkcwNzRKUCtzKVdWOlorVDwmKFExOGBLcylXVjpZNGhzOVtoOllDUz8mMGA!
flag = flag.replace("!", '=').encode()
print(flag) #b'NkcwNzRKUCtzKVdWOlorVDwmKFExOGBLcylXVjpZNGhzOVtoOllDUz8mMGA='
flag = base64.b64decode(flag).decode()
print(flag) #6G074JP+s)WV:Z+T<&(Q18`Ks)WV:Y4hs9[h:YCS?&0`
flag = flag.translate(str.maketrans(my_table, std_table))
print(base64.b64decode(flag)) #NCTF{H0m3_r1d1n9_h0m3_dy1n9_h0p3}


得到flag:NCTF{H0m3_r1d1n9_h0m3_dy1n9_h0p3}