攻防世界PWN高手进阶区

dice_game

溢出+随机数

攻防世界PWN新手练习区题目

get_shell

直接nc连接cat flag即可:

1
cyberpeace{d1070f116850587b8304cd1aa55565e6}

Linux动态链接库.so的生成与使用

用到以下三个文件,为以后展开相关攻击过程做铺垫。

main.c

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <unistd.h>

int print_test();

int main(){
while(1){
print_test();
sleep(5);
}
}

二进制安全之格式化字符串漏洞

相信每位程序员都写过如下代码:

1
2
3
4
5
6
#include<stdio.h>
int main()
{
printf("Hello world!");
return 0;
}

是的,这应该是每个程序员写的第一个程序,其中printf(),也是一个在C语言中的较为脆弱的函数,我们今天就来探讨一下格式化字符串漏洞。有一点要说的是由于现在的很多编译器都变得更加智能且更加注重安全性,格式化字符串等容易出现问题的函数都会由编译器自动为其添加相应的check函数从而保证函数的安全性,因此格式化字符串漏洞是由很小的可能性会出现在真实的生产环境中的,可能出现这个漏洞最多的情形就是大大小小形式各异的CTF赛题中了,但是由于此漏洞历史悠久并且较为有趣,如果产生此漏洞的话危害也不小,还是有必要学习一下的。

2018腾讯游戏安全竞赛题目分析

今年的腾讯游戏安全竞赛就要开始了,但是我对游戏安全这个方向还不太熟悉,因此找了去年的题目来做赛前练习

资格赛题目

标准版

题目一打开长这样:

赛题说明:

1
2
1.username与regcode是一一对应的关系,填入正确的username和regcode点击Go按钮出现注册成功提示。
2.要求根据CrackMe写出对应注册机,注册机能够根据任意合法用户名生成正确的注册码。

可以看出这是一个CrackMe程序,开始分析。

上图为一神器,可直接通过可视化界面获取到MFC组件的响应函数,神器链接

如果不使用上面的神器的话,此程序是一个MFC程序,获取输入框的方式有两种GetDlgItemGetWindowText,查看这两个函数的调用,发现在sub_4026F0中有多处对GetDlgItem的调用,因此分析这个函数,IDA对此函数生成的主要伪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
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
void __thiscall sub_4026F0(HWND *this)
{
......

v21 = (CWnd *)this;
v1 = GetDlgItem(this[8], 1000);
memset(&lParam, 0, 0x400u);
SendMessageA(v1, 0xDu, 0x400u, (LPARAM)&lParam);
v2 = GetDlgItem(*((HWND *)v21 + 8), 1001);
memset(&v23, 0, 0x400u);
SendMessageA(v2, 0xDu, 0x400u, (LPARAM)&v23);
v3 = v21;
v4 = CWnd::GetDlgItem(v21, 1004);
v20 = SendMessageW(*((HWND *)v4 + 8), 0xF0u, 0, 0) == 1;
v21 = (CWnd *)&v14;
v19 = 15;
v18 = 0;
LOBYTE(v14) = 0;
if ( (_BYTE)v23 )
v5 = strlen((const char *)&v23);
else
v5 = 0;
sub_402A70(&v14, &v23, v5);
v49 = 0;
v13 = 15;
v12 = 0;
v8 = 0;
if ( (_BYTE)lParam )
v6 = strlen((const char *)&lParam);
else
v6 = 0;
sub_402A70(&v8, &lParam, v6);
v49 = -1;
if ( (unsigned __int8)sub_405510(*(LPVOID *)&v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) )
{
v24 = 'e\0R';
v40 = 0;
v7 = (wchar_t *)&v24;
v25 = 'i\0g';
v26 = 't\0s';
v27 = 'r\0e';
v28 = (int)&loc_53001D + 3;
v29 = 'c\0u';
v30 = 'e\0c';
v31 = 's\0s';
v32 = &loc_43002C;
v33 = 'n\0o';
v34 = 'r\0g';
v35 = 't\0a';
v36 = 'l\0u';
v37 = 't\0a';
v38 = 'o\0i';
v39 = '!\0n';
}
else
{
*(_DWORD *)v41 = 'e\0R';
v7 = v41;
v42 = 'i\0g';
v43 = 't\0s';
v44 = 'r\0e';
v45 = &loc_460020;
v46 = 'i\0a';
v47 = 'e\0l';
v48 = 'd';
}
CWnd::SetDlgItemTextW(v3, 1003, v7);
}

可以看出这是程序的主流程。下面就一步一步分析此函数:

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
void __thiscall sub_4026F0(HWND *this)
{
......

v21 = (CWnd *)this;
UserName_Text_ID = GetDlgItem(this[8], 1000);
memset(&UserName, 0, 02000u);
SendMessageA(UserName_Text_ID, '\r', 02000u, (LPARAM)&UserName);// 存储UserName
RegCode_Text_ID = GetDlgItem(*((HWND *)v21 + 8), 1001);
memset(&RegCode, 0, 02000u);
SendMessageA(RegCode_Text_ID, '\r', 02000u, (LPARAM)&RegCode);// 存储RegCode
v3 = v21;
User_Choice = CWnd::GetDlgItem(v21, 1004);
v20 = SendMessageW(*((HWND *)User_Choice + 8), 0xF0u, 0, 0) == 1;// 判断选择的是标准版还是进阶版
v21 = (CWnd *)&v14;
v19 = 15;
v18 = 0;
LOBYTE(v14) = 0;
if ( (_BYTE)RegCode )
v5 = strlen((const char *)&RegCode); // 判断RegCode长度
else
v5 = 0;
assign_sub_402A70(&v14, &RegCode, v5); // string assign
// 将RegCode的值赋给v14
v49 = 0;
v13 = 15;
v12 = 0;
v8 = 0;
if ( (_BYTE)UserName )
v6 = strlen((const char *)&UserName); // 判断UserName长度
else
v6 = 0;
assign_sub_402A70(&v8, &UserName, v6); // string assign
// 将Username的值赋给v8
v49 = -1;
if ( sub_405510(*(LPVOID *)&v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) )// 主检验函数
{
v24 = 'e\0R';
v40 = 0;
v7 = (wchar_t *)&v24;
v25 = 'i\0g';
v26 = 't\0s';
v27 = 'r\0e';
v28 = (int)&loc_53001D + 3;
v29 = 'c\0u';
v30 = 'e\0c'; // RegisterSuccess,Congratulation!
v31 = 's\0s';
v32 = &loc_43002C;
v33 = 'n\0o';
v34 = 'r\0g';
v35 = 't\0a';
v36 = 'l\0u';
v37 = 't\0a';
v38 = 'o\0i';
v39 = '!\0n';
}
else
{
*(_DWORD *)v41 = 'e\0R';
v7 = v41;
v42 = 'i\0g';
v43 = 't\0s';
v44 = 'r\0e'; // RegisterFailed
v45 = &loc_460020;
v46 = 'i\0a';
v47 = 'e\0l';
v48 = 'd';
}
CWnd::SetDlgItemTextW(v3, 1003, v7);
}

以上步骤完成了基本的数据获取、校验操作,其中有一个主要的校验函数sub_405510,下面来重点分析这个函数:

当返回值v13True时才能注册成功,因此我们需要关注使v13True的函数。

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
if ( sub_404F00((int *)&UserName) )
{
sub_405040((int)&UserName, &v26, &v27, &v28, (int *)&v29, &v30);
v31 = '\0';
v32 = 0;
LOBYTE(v34) = 2;
v14 = &a7;
if ( (unsigned int)a12 >= 0x10 )
v14 = a7;
if ( (unsigned __int8)sub_406080(v14, &v31)
&& ((v16 = HIDWORD(v31), v17 = (__m128i *)v31, a13)
|| (v33 = xmmword_5AC470, (unsigned __int8)sub_403010(&v33, v15, v31)))
&& v16 - (_DWORD)v17 == 32
&& v17[1].m128i_i32[2] == '2018'
&& !v17[1].m128i_i32[3] )
{
v18 = *v17;
*((_QWORD *)&v33 + 1) = v17[1].m128i_i64[0];
v13 = sub_402F20(
v26,
v27,
v28,
v29,
v30,
__PAIR__(_mm_cvtsi128_si32(_mm_srli_si128(v18, 4)), _mm_cvtsi128_si32(v18)),
__PAIR__(_mm_cvtsi128_si32(_mm_srli_si128(v18, 12)), _mm_cvtsi128_si32(_mm_srli_si128(v18, 8))),
*((__int64 *)&v33 + 1));
}
else
{
v13 = 0;
}
sub_405740(&v31);
}
else
{
v13 = 0;
}

首先我们查看 sub_404F00函数,同样按照逆向思维,寻找使返回值为True的条件。此函数中有如下几个关键片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
v1 = UserName;
if ( UserName[4] != 39 ) // 判断UserName的长度是否为39
return 0;
v3 = UserName[5];
if ( v3 < 020 )
v23 = UserName;
else
v23 = (int *)*UserName;
if ( v3 < 020 )
v4 = UserName;
else
v4 = (int *)*UserName; // String的相关函数,用于决定字符串的存储位置等
v5 = (unsigned int)v4 + 39;
if ( v3 < 0x10 )
v6 = UserName;
else
v6 = (int *)*UserName;
v7 = v5 - (_DWORD)v6;
i = 0;
if ( (unsigned int)v6 > v5 )
v7 = 0;

UserName中的小写字母转换为大写:

1
2
3
4
5
6
7
8
9
10
11
if ( v7 )
{
Username_len = v7;
do
{
*((_BYTE *)v23 + i) = toupper(*((char *)v6 + i));// 将小写字母转换为大写字母
++i;
}
while ( i != Username_len );
v1 = UserName;
}
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
v16 = 0;                                      // v16为一个字符串
assign_sub_402A70(&v16, &unk_5AC430, '\x01'); // 给v16赋值为'#'
sub_404D70((int)v24, (int)UserName_Upper, *(LPVOID *)&v16, v17, v18, v19, v20, v21);// 将大写的UserName用#分割,分割后的结果保存在v24中
v10 = (_DWORD *)v24[0];
if ( (unsigned int)(v24[1] - v24[0] - 192) >= 24 )
goto LABEL_30;
if ( v24[0] != v24[1] )
{
while ( 1 )
{
v11 = v10[4];
v12 = 0;
if ( v11 > 0 )
break;
LABEL_28:
v10 += 6;
if ( v10 == (_DWORD *)v24[1] ) // 字符串验证成功
goto LABEL_29;
}
while ( 1 )
{
v13 = v10[5] < 0x10u ? v10 : *v10;
v14 = *((_BYTE *)v13 + v12);
if ( (v14 > 57 || v14 < 48) && (unsigned __int8)(v14 - 'A') > 5u )// 要求Username的范围为数字和ABCDEF
break;
if ( ++v12 >= v11 )
goto LABEL_28;
}
LABEL_30:
v15 = 0;
goto LABEL_31;
}
LABEL_29:
v15 = 1;
LABEL_31:
sub_405A00((char **)v24);
return v15;
}

分析到这里我们可以得出关于UserName的几个信息:

  • UserName的长度必须为39
  • 存放UserName的数组的元素个数为8
  • UserName会被#分割
  • UserName由数字和ABCDEF组成

因此可以得知UserName的形式为:

xxxx#xxxx#xxxx#xxxx#xxxx#xxxx#xxxx#xxxx

base64:YtTJUVEn0$HI34y#8rsFewxlm+/u5a^2welcomegslab2018zQfghDRSG@di*kABZO6Kq79L&CPWvNop

未完待续… … 📐

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×