January 6, 2022

MRCTF2020-EasyCpp

0x00 日常查壳

无壳64位

image-20220107113452652

0x01 分析主函数

由于没有系统的学过C++,分析起来还是比较累的,大部分思路是看别人认真写的wp,再配合动调看各条语句的效果。(参考了别的师傅的注释,写的很好,在文末有链接),如有错误,欢迎指正。(评论系统还没做,首页有我b站链接hh)

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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
__int64 v4; // rbx
__int64 v5; // rax
int v6; // ebx
__int64 v7; // rax
__int64 v8; // rax
__int64 v9; // rax
__int64 v10; // rax
__int64 v11; // rax
__int64 v12; // rax
char v14[40]; // [rsp+0h] [rbp-140h] BYREF
__int64 v15; // [rsp+28h] [rbp-118h] BYREF
__int64 v16; // [rsp+30h] [rbp-110h] BYREF
int v17; // [rsp+3Ch] [rbp-104h] BYREF
char v18[32]; // [rsp+40h] [rbp-100h] BYREF
char v19[48]; // [rsp+60h] [rbp-E0h] BYREF
char v20[31]; // [rsp+90h] [rbp-B0h] BYREF
char v21; // [rsp+AFh] [rbp-91h] BYREF
char v22[47]; // [rsp+B0h] [rbp-90h] BYREF
char v23; // [rsp+DFh] [rbp-61h] BYREF
char v24[36]; // [rsp+E0h] [rbp-60h] BYREF
unsigned int v25; // [rsp+104h] [rbp-3Ch]
char *v26; // [rsp+108h] [rbp-38h]
int *v27; // [rsp+110h] [rbp-30h]
_DWORD *v28; // [rsp+118h] [rbp-28h]
int *v29; // [rsp+120h] [rbp-20h]
int i; // [rsp+128h] [rbp-18h]
unsigned int v31; // [rsp+12Ch] [rbp-14h]

v31 = 0;

/* 创建一个int型容器 */
std::vector<int>::vector(v20, argv, envp);

/* 创建一个bool型容器 */
std::vector<bool>::vector(v19);

/* 创建一个字符型变量 v21
basic_string<char,std::char_traits<char>,std::allocator<char>> 是类模板
basic_string代表是构建函数 */
std::allocator<char>::allocator(&v21);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v18, &unk_500E, &v21);
std::allocator<char>::~allocator(&v21);

/* 输出字符串 */
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "give me your key!");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);

/* 输入的数据放到keys里 */
for ( i = 0; i <= 8; ++i )
{
/* 从缓冲区获取一串字符串 */
std::istream::operator>>(&std::cin, &keys[i]);

/* 调用string头文件里的to_string全局函数(可以理解成库函数)把输入的数据以字符形式放入v22 */
std::__cxx11::to_string((std::__cxx11 *)v22, keys[i]);

/* 调用string头文件里的basic_string类模板中的operator+函数把输入数据存于v18(可以理解成 v18 += v22) */
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(v18, v22);

/* 调用string头文件里basic_string类模板中折构函数(销毁字符串) */
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v22);
}

v28 = keys;
v29 = keys;
v27 = (int *)&unk_83E4;
while ( v29 != v27 )
{
v17 = *v29;
/* 将输入的数据压入v20容器(上面创建过的int容器) */
std::vector<int>::push_back(v20, &v17);
++v29;
}

/* 获取容器结尾 */
v4 = std::vector<int>::end(v20);

/* 获取容器开头 */
v5 = std::vector<int>::begin(v20);

/* 调用for_each的函数模板从头到尾指针的每一项执行lambda函数(这里标记为lambda1) */
std::for_each<__gnu_cxx::__normal_iterator<int *,std::vector<int>>,main::{lambda(int &)#1}>(v5, v4);

/* 和上面一样获取开头结尾指针 */
v26 = v20;
v16 = std::vector<int>::begin(v20);
v15 = std::vector<int>::end(v26);

/* while循环的条件用的是gcc编译器的使用的C++库中定义的命名空间__gnu_cxx,这里就是判断指针是否到了结尾 */
while ( (unsigned __int8)__gnu_cxx::operator!=<int *,std::vector<int>>(&v16, &v15) )
{
/* 取出第一组值放到v25 */
v25 = *(_DWORD *)__gnu_cxx::__normal_iterator<int *,std::vector<int>>::operator*(&v16);

/* 同上,创建一个字符型变量v23
basic_string<char,std::char_traits<char>,std::allocator<char>> 是类模板
basic_string代表是构建函数 */
std::allocator<char>::allocator(&v23);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v14, &unk_500E, &v23);
std::allocator<char>::~allocator(&v23);

/* v25分离因子放到v14(待会细说) */
depart(v25, v14);

/* 又是一个lambda函数这里是作用于v14(标记为lambda2),这边其实就是个替换 */
{lambda(std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>> &)#1}::operator()(&func, v14);

/* 又是一个类模板然后basic_string构造函数 */
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(v24, v14);

/* 这边又是一个lambda函数这边是flag判断是否正确然后返回一个v6 */
v6 = {lambda(std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>,int)#2}::operator()(
&check,
v24,
v31) ^ 1;

/* 折构函数(就是销毁字符串) */
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v24);

/* 显而易见 */
if ( (_BYTE)v6 )
{
v7 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong password!");
std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
system("pause");
exit(0);
}
++v31;
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v14);
__gnu_cxx::__normal_iterator<int *,std::vector<int>>::operator++(&v16);
}

/* 下面就是输出flag 那串英文就是提醒你的拿到的flag md5加密后全部大写 */
v8 = std::operator<<<std::char_traits<char>>(&std::cout, "right!");
std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
v9 = std::operator<<<std::char_traits<char>>(&std::cout, "flag:MRCTF{md5(");
v10 = std::operator<<<char>(v9, v18);
v11 = std::operator<<<std::char_traits<char>>(v10, ")}");
std::ostream::operator<<(v11, &std::endl<char,std::char_traits<char>>);
v12 = std::operator<<<std::char_traits<char>>(
&std::cout,
"md5()->{32/upper case/put the string into the function and transform into md5 hash}");
std::ostream::operator<<(v12, &std::endl<char,std::char_traits<char>>);
system("pause");
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(v18);
std::vector<bool>::~vector(v19);
std::vector<int>::~vector(v20);
return 0;
}

0x02 三个lambda与depart

辛苦!看完了主函数,现在只要看下三个简单的lambda函数与depart即可

depart

首先跟随思路取出第一组数值(key[0])放到v25传入depart

image-20220107163429506

看到递归不要怕 一步步跟着流程就肯定能看懂

其实就是分解因子(一个数取余因子必为0)

当是因子:进入判断向下递归继续找因子

当不是因子: i++

举个例子:(建议先看遍代码再看看我这个例子)

第一层,我们的a1是22,22 % 2 == 0,于是进入判断,第一层v6 = 2,第二层depart(11, a2)

进入第二层,v6 = 11,i一直加到11,11 % 11 == 0,第二层 v6 = 11,第三层depart(1, a2)

进入第三层 ,v6 = 1,for条件不成立,然后v3 = ‘ ‘ + 1,然后再附加到a2上

回到第二层,直接break,此时v6是11,再附加到a2(此时a2是 ‘ ‘+1+’ ‘+11)

回到第一层,直接break,此时v6是2,再附加到a2

最后a2的数据为’ ‘+1+’ ‘+11+’ ‘+2(没错就是着个数的因子)

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 depart(int a1, __int64 a2)
{
char v3[32]; // [rsp+20h] [rbp-60h] BYREF
char v4[40]; // [rsp+40h] [rbp-40h] BYREF
int i; // [rsp+68h] [rbp-18h]
int v6; // [rsp+6Ch] [rbp-14h]

v6 = a1;
for ( i = 2; std::sqrt<int>((unsigned int)a1) >= (double)i; ++i )
{
if ( !(a1 % i) ) //当是因子的时候进入判断(比如11 % 11 == 0)
{
v6 = i;
depart(a1 / i, a2);
break;
}
}

/* v4 = v6(转成字符串到v4) */
std::__cxx11::to_string((std::__cxx11 *)v4, v6);

/* v3 = ' '(&byte_500C) + v4 */
std::operator+<char>(v3, &byte_500C, v4);

/* a2 += v3 */
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(a2, (__int64)v3);

/* 折构函数(释放v3 v4的内存) */
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string((__int64)v3);
return std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string((__int64)v4);
}

现在开始分析我们的三个lambda

image-20220107123745843

lambda1

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
__int64 __fastcall std::for_each<__gnu_cxx::__normal_iterator<int *,std::vector<int>>,main::{lambda(int &)#1}>(__int64 a1, __int64 a2)
{
unsigned int v2; // ebx
__int64 v3; // rax
char v5; // [rsp+Fh] [rbp-21h] BYREF
__int64 v6; // [rsp+10h] [rbp-20h] BYREF
__int64 v7[3]; // [rsp+18h] [rbp-18h] BYREF

v7[0] = a1;
v6 = a2;

/* 这边就是头尾指针判断 */
while ( (unsigned __int8)__gnu_cxx::operator!=<int *,std::vector<int>>(v7, &v6) )
{
/* 这里是取v7指向的数 */
v3 = __gnu_cxx::__normal_iterator<int *,std::vector<int>>::operator*(v7);

/* 又有个lambda函数 其实就是^1的操作(先吃饭去了) */
main::{lambda(int &)#1}::operator()(&v5, v3);

/* 指针向后移 */
__gnu_cxx::__normal_iterator<int *,std::vector<int>>::operator++(v7);
}
return v2;
}

看看里面的lambda函数,就是对每项进行 ^1 的操作

1
2
3
4
5
6
7
8
_DWORD *__fastcall main::{lambda(int &)#1}::operator()(__int64 a1, _DWORD *a2)
{
_DWORD *result; // rax

result = a2;
*a2 ^= 1u;
return result;
}

lambda2

稍微看一下就知道这是在字符替换(注意空是换成’=’)

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
139
140
141
142
143
144
145
146
147
__int64 __fastcall {lambda(std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>> &)#1}::operator()(__int64 a1, __int64 a2)
{
__int64 v2; // rbx
__int64 v3; // rax
__int64 v4; // rbx
__int64 v5; // rax
__int64 v6; // rbx
__int64 v7; // rax
__int64 v8; // rbx
__int64 v9; // rax
__int64 v10; // rbx
__int64 v11; // rax
__int64 v12; // rbx
__int64 v13; // rax
__int64 v14; // rbx
__int64 v15; // rax
__int64 v16; // rbx
__int64 v17; // rax
__int64 v18; // rbx
__int64 v19; // rax
__int64 v20; // rbx
__int64 v21; // rax
__int64 v22; // rbx
__int64 v23; // rax
char v25; // [rsp+1Ah] [rbp-26h] BYREF
char v26; // [rsp+1Bh] [rbp-25h] BYREF
char v27; // [rsp+1Ch] [rbp-24h] BYREF
char v28; // [rsp+1Dh] [rbp-23h] BYREF
char v29; // [rsp+1Eh] [rbp-22h] BYREF
char v30; // [rsp+1Fh] [rbp-21h] BYREF
char v31; // [rsp+20h] [rbp-20h] BYREF
char v32; // [rsp+21h] [rbp-1Fh] BYREF
char v33; // [rsp+22h] [rbp-1Eh] BYREF
char v34; // [rsp+23h] [rbp-1Dh] BYREF
char v35; // [rsp+24h] [rbp-1Ch] BYREF
char v36; // [rsp+25h] [rbp-1Bh] BYREF
char v37; // [rsp+26h] [rbp-1Ah] BYREF
char v38; // [rsp+27h] [rbp-19h] BYREF
char v39; // [rsp+28h] [rbp-18h] BYREF
char v40; // [rsp+29h] [rbp-17h] BYREF
char v41; // [rsp+2Ah] [rbp-16h] BYREF
char v42; // [rsp+2Bh] [rbp-15h] BYREF
char v43; // [rsp+2Ch] [rbp-14h] BYREF
char v44; // [rsp+2Dh] [rbp-13h] BYREF
char v45; // [rsp+2Eh] [rbp-12h] BYREF
char v46[17]; // [rsp+2Fh] [rbp-11h] BYREF

v25 = 'O';
v26 = '0';
v2 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(a2);
v3 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(a2);
std::replace<__gnu_cxx::__normal_iterator<char *,std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>,char>(
v3,
v2,
&v26,
&v25);
v27 = 'l';
v28 = '1';
v4 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(a2);
v5 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(a2);
std::replace<__gnu_cxx::__normal_iterator<char *,std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>,char>(
v5,
v4,
&v28,
&v27);
v29 = 'z';
v30 = '2';
v6 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(a2);
v7 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(a2);
std::replace<__gnu_cxx::__normal_iterator<char *,std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>,char>(
v7,
v6,
&v30,
&v29);
v31 = 'E';
v32 = '3';
v8 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(a2);
v9 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(a2);
std::replace<__gnu_cxx::__normal_iterator<char *,std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>,char>(
v9,
v8,
&v32,
&v31);
v33 = 'A';
v34 = '4';
v10 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(a2);
v11 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(a2);
std::replace<__gnu_cxx::__normal_iterator<char *,std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>,char>(
v11,
v10,
&v34,
&v33);
v35 = 's';
v36 = '5';
v12 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(a2);
v13 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(a2);
std::replace<__gnu_cxx::__normal_iterator<char *,std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>,char>(
v13,
v12,
&v36,
&v35);
v37 = 'G';
v38 = '6';
v14 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(a2);
v15 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(a2);
std::replace<__gnu_cxx::__normal_iterator<char *,std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>,char>(
v15,
v14,
&v38,
&v37);
v39 = 'T';
v40 = '7';
v16 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(a2);
v17 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(a2);
std::replace<__gnu_cxx::__normal_iterator<char *,std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>,char>(
v17,
v16,
&v40,
&v39);
v41 = 'B';
v42 = '8';
v18 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(a2);
v19 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(a2);
std::replace<__gnu_cxx::__normal_iterator<char *,std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>,char>(
v19,
v18,
&v42,
&v41);
v43 = 'q';
v44 = '9';
v20 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(a2);
v21 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(a2);
std::replace<__gnu_cxx::__normal_iterator<char *,std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>,char>(
v21,
v20,
&v44,
&v43);
v45 = '=';
v46[0] = ' ';
v22 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(a2);
v23 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(a2);
return std::replace<__gnu_cxx::__normal_iterator<char *,std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>,char>(
v23,
v22,
v46,
&v45);
}

lambda3

这边细看一下可以发现从ans[abi:cxx11]取数据作比较

1
2
3
4
5
6
7
8
9
_BOOL8 __fastcall {lambda(std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>,int)#2}::operator()(__int64 a1, __int64 a2, int a3)
{
const char *v3; // rbx
const char *v4; // rax

v3 = (const char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str((char *)&ans[abi:cxx11] + 32 * a3);
v4 = (const char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(a2);
return strcmp(v4, v3) == 0;
}

image-20220107163105568

image-20220107163131053

这似曾相识‘=’与数字是不是有想法了

image-20220107163204033

0x03 GetFlag!

好了我们来理一下逻辑

  1. 首先是我们输入9个key

  2. 经过第一个lambda1(就是每个key异或1)

  3. 再到了depart(分离因子用空格隔开)

  4. 再到我们的lambda2(做了字符串替换)

  5. 最后我们的lambda3(进行判断)

于是用一句话逆回去就是,替换回去再相乘异或1,解毕,上脚本。(参考了别的师傅的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
strs=["=zqE=z=z=z","=lzzE","=ll=T=s=s=E","=zATT","=s=s=s=E=E=E","=EOll=E","=lE=T=E=E=E","=EsE=s=z","=AT=lE=ll"]

def replacediy(str):
str=str.replace("O","0")
str=str.replace("l","1")
str=str.replace("z","2")
str=str.replace("E","3")
str=str.replace("A","4")
str=str.replace("s","5")
str=str.replace("G","6")
str=str.replace("T","7")
str=str.replace("B","8")
str=str.replace("q","9")
str=str.replace("="," ")
return str

flag = ""
for i in strs:
tmp = replacediy(i).split(" ")[1:] #每个字符串以空分割 再取空之后的所有字符
print(tmp)
sum = 1
for j in range(len(tmp)):
sum *= int(tmp[j], 10) #转十进制再承回原来的数
sum ^= 1
flag += str(sum)

print(flag)

image-20220107171206120

得到这串后MD5加密后全部换大写

GetFlag!

image-20220107171315453

参考文献

https://blog.csdn.net/m0_46296905/article/details/115904045

https://www.cnblogs.com/YenKoc/p/12779874.html

https://www.apiref.com/cpp-zh/cpp/string/basic_string.html

DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

This post is written by P.Z, licensed under CC BY-NC 4.0.