-
-
[原创]Tenda堆栈缓冲区溢出漏洞 (CVE-2024-2986)
-
发表于: 1天前 483
-
CNVD 最近公开了 CNVD-2025-31165 (CVE-2024-2986) 漏洞, 漏洞描述为:
Tenda FH1202是腾达品牌推出的一款双频无线路由器,专为大户型家庭、小型办公室或商务休闲区域设计,旨在提供稳定的无线网络覆盖和高速传输。
Tenda FH1202存在堆栈缓冲区溢出漏洞,该漏洞源于/goform/SetSpeedWan文件的formSetSpeedWan方法的speed_dir参数未能正确验证输入数据的长度大小,攻击者可利用该漏洞在系统上执行任意代码或者导致拒绝服务。
软件版本 : AC15_V15.03.05.19
binwalk 分析 Squashfs 的系统文件
执行 binwalk -e -1 解压
根据 ./etc_ro/init.d/rcS 的脚本来创建环境目录
创建 br0 的虚拟网卡
根据 libCfm.so 的 GetValue 函数创建 Unix Domain Socket , 发送信息的格式如下 :
创建 Unix Domain Socket /var/cfm_socket 的 uds_server.py 脚本代码如下 :
default.ini 是 /webroot/default.cfg 转换的, 唯一的问题是程序客户端代码使用的是长连接的方式, 断开后还需要重启 uds_server.py 程序
此外, 还需要模拟 bcm_nvram_* 函数
HOOK NVRAM 的代码 hook_nvram.c 如下 :
/tmp/nvram_default.cfg 是 /webroot/nvram_default.cfg 复制过来的
查看 httpd 的 GCC 版本,使用的是 Buildroot ,C 标准库是 uClibc
根据 GITHUB 上的 buildroot 项目, 编译生成支持 ARMv7 + uClibc + soft-float 的 arm-linux-gcc
编译命令如下 :
生成的 hook_nvram.so ELF 信息文件如下 :
主机运行 python ./uds_server.py 创建 ./var/cfm_socket
sudo chroot . bin/sh 进入固件目录环境 ,再运行 LD_PRELOAD=/tmp/hook_nvram.so /bin/httpd 开启 HTTP 服务
访问 ce6K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8U0p5&6x3W2)9J5k6e0p5$3z5q4)9J5k6e0y4Q4x3X3f1K6i4K6u0r3L8r3!0Y4K9h3&6Q4x3X3g2Z5N6r3#2D9 页面如下

首先查看 httpd 开启的防护如下
使用调试模式启动 httpd , 然后在 0x0061998 BL sprintf 的溢出点下断点
gdb.txt 的加载脚本如下
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 协议进行发送
程序正常断在 sprintf@plt 函数处

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

sprintf 后的栈数据如下

单步运行到函数的结束处

可以看到最后是 r11 0x407ffa34 ◂— 0x61616a61 ('ajaa') 的数据被弹出到 PC 寄存器
偏移地址为 35 处
如果调用 system 函数, 需要首先设置 R0 的值指向需要执行的命令地址
但是 system 的函数汇编代码如下
system 运行结束后 ,弹出到 PC 寄存器的是 LR 的值 ,如果不控制 LR 的值程序就会崩溃,现在需要找一个 BL system 的指令来自动设置 LR 的值
libcommon.so 中的 doSystemCmd 函数正好符合, 然后 flush_dns_cache 的调用 doSystemCmd 代码如下
这段代码非常符合参数的设置,首先设置 R3 的地址指向需要执行的命令 telnetd -l /bin/sh
让弹出到 PC 寄存器的地址指向 libc.so.0 的 _exit 函数
使用 ROPgadget 查找设置 R3 值的指令
选取 0x00015c20 作为设置 R3 值的指令
结合 vmmap 和 info sharedlibrary 计算 libcommon.so 的基址为 0x40854000 , libc.so.0 的基址为 0x409EB000

栈的数据结构如下

开启 telnetd 服务还需要挂载 devpts , 不然程序会提示 telnetd: can't find free pty
运行 POC 后查看运行的进程信息
telnet 192.168.3.3 进行测试
POC 代码如下
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, (const char *)s);}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, (const char *)s);}binwalk US_AC15V1.0BR_V15.03.05.19_multi_TD01.binDECIMAL 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: 0x092 0x5C LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4585280 bytes1875608 0x1C9E98 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 8749996 bytes, 928 inodes, blocksize: 131072 bytes, created: 2017-05-26 02:03:03binwalk US_AC15V1.0BR_V15.03.05.19_multi_TD01.binDECIMAL 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: 0x092 0x5C LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4585280 bytes1875608 0x1C9E98 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 8749996 bytes, 928 inodes, blocksize: 131072 bytes, created: 2017-05-26 02:03:03binwalk -e -1 US_AC15V1.0BR_V15.03.05.19_multi_TD01.binDECIMAL 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: 0x092 0x5C LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4585280 bytes1875608 0x1C9E98 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 8749996 bytes, 928 inodes, blocksize: 131072 bytes, created: 2017-05-26 02:03:03binwalk -e -1 US_AC15V1.0BR_V15.03.05.19_multi_TD01.binDECIMAL 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: 0x092 0x5C LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4585280 bytes1875608 0x1C9E98 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 8749996 bytes, 928 inodes, blocksize: 131072 bytes, created: 2017-05-26 02:03:03mkdir -p /var/etcmkdir -p /var/mediamkdir -p /var/webrootmkdir -p /var/etc/iproutemkdir -p /var/runcp -rf /etc_ro/* /etc/cp -rf /webroot_ro/* /webroot/mkdir -p /var/etc/upanmount -amount -t ramfs /devmkdir /dev/ptsmount -t devpts devpts /dev/ptsmount -t tmpfs none /var/etc/upan -o size=2Mmdev -smkdir /var/runmkdir -p /var/etcmkdir -p /var/mediamkdir -p /var/webrootmkdir -p /var/etc/iproutemkdir -p /var/runcp -rf /etc_ro/* /etc/cp -rf /webroot_ro/* /webroot/mkdir -p /var/etc/upanmount -amount -t ramfs /devmkdir /dev/ptsmount -t devpts devpts /dev/ptsmount -t tmpfs none /var/etc/upan -o size=2Mmdev -smkdir /var/runip link add name br0 type bridgeip link set br0 upip addr add 192.168.3.3/24 dev br0ip link add name br0 type bridgeip link set br0 upip addr add 192.168.3.3/24 dev br000000000 struct CMDINFO // sizeof=0x7E000000000 { // XREF: CommitUrlCfm/r00000000 // CommitUrlCfm/r ...00000000 int cmd;00000004 char name[512]; // XREF: CommitUrlCfm+70/o00000004 // CommitUrlCfm+B4/o ...00000204 char value[1500]; // XREF: GetValue+104/w00000204 // GetUrlValue+100/w ...000007E0 };00000000 struct CMDINFO // sizeof=0x7E000000000 { // XREF: CommitUrlCfm/r00000000 // CommitUrlCfm/r ...00000000 int cmd;00000004 char name[512]; // XREF: CommitUrlCfm+70/o00000004 // CommitUrlCfm+B4/o ...00000204 char value[1500]; // XREF: GetValue+104/w00000204 // GetUrlValue+100/w ...000007E0 };import socketimport osimport timeimport configparserimport structimport 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字节字符串) cmd, name_bytes, value_bytes = struct.unpack('<i512s1500s', data) # 找到第一个0x00的位置并截断 name_end = name_bytes.find(b'\x00') if name_end != -1: name_bytes = name_bytes[:name_end] value_end = value_bytes.find(b'\x00') if value_end != -1: value_bytes = value_bytes[:value_end] # 处理字符串:去除可能的null填充并解码(假设C发送的是ASCII字符串) name = name_bytes.rstrip(b'\x00').decode('ascii', errors='ignore') value = value_bytes.rstrip(b'\x00').decode('ascii', errors='ignore') return cmd, name, valuedef pack_cmdinfo(cmd, name, value): """ 将cmd、name、value打包成2016字节的二进制数据 :param cmd: int, 命令编号 :param name: str, 名称(最多511字符,留1位给null) :param value: str, 值(最多1499字符,留1位给null) :return: bytes, 打包后的二进制数据 """ # 1. 转换为字节(假设C使用ASCII编码,实际可能是UTF-8或其他) name_bytes = name.encode('ascii')[:511] # 截断超长部分 value_bytes = value.encode('ascii')[:1499] # 2. 填充到固定大小(用null字节填充) name_padded = name_bytes + b'\x00' * (512 - len(name_bytes)) value_padded = value_bytes + b'\x00' * (1500 - len(value_bytes)) # 3. 打包(小端序,与解析时一致) # 格式:i (4字节整数) + 512s + 1500s packed = struct.pack('<i512s1500s', cmd, name_padded, value_padded) return packed# --- 服务器逻辑 ---def run_uds_server(): # 1. 清理:如果套接字文件已存在,则删除它 try: if os.path.exists(SOCKET_PATH): os.remove(SOCKET_PATH) except OSError as e: print(f"{e}") return # 2. 创建套接字 server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: # 3. 绑定和监听 server_socket.bind(SOCKET_PATH) server_socket.listen(5) # 允许5个挂起连接 # 4. 确保权限(可选,确保其他程序可以连接) os.chmod(SOCKET_PATH, 0o666) while True: # 5. 接受新连接 conn, addr = server_socket.accept() try: while True: # 6. 接收数据 received_data = conn.recv(BUFFER_SIZE) if received_data: # 打印接收到的数据 # hexdump.hexdump(received_data) # print('\n') cmd, name, value = parse_cmdinfo(received_data) print(f"received_data-> cmd: {cmd} name: {name} value: {value}") if value != "": config.set('DEFAULT', name, value) cmd = cmd + 1 value = config['DEFAULT'].get(name, '') response_data = pack_cmdinfo(cmd, name, value) print(f"response_data-> cmd: {cmd} name: {name} value: {value}") # hexdump.hexdump(response_data) # print('\n') conn.sendall(response_data) else: time.sleep(1) # print("No data received.") except Exception as e: print(f"{e}") finally: # 8. 关闭连接 conn.close() except KeyboardInterrupt: print("Ctrl+C...") except Exception as e: print(f"{e}") finally: # 9. 最终清理 # 将修改写入配置文件 with open('default.ini', 'w', encoding='utf-8') as configfile: config.write(configfile) server_socket.close() if os.path.exists(SOCKET_PATH): os.remove(SOCKET_PATH)if __name__ == "__main__": # 由于 /var 目录通常需要 root 权限,您需要使用 sudo 运行此脚本 run_uds_server()import socketimport osimport timeimport configparserimport structimport 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字节字符串) cmd, name_bytes, value_bytes = struct.unpack('<i512s1500s', data) # 找到第一个0x00的位置并截断 name_end = name_bytes.find(b'\x00') if name_end != -1: name_bytes = name_bytes[:name_end] value_end = value_bytes.find(b'\x00') if value_end != -1: value_bytes = value_bytes[:value_end] # 处理字符串:去除可能的null填充并解码(假设C发送的是ASCII字符串) name = name_bytes.rstrip(b'\x00').decode('ascii', errors='ignore') value = value_bytes.rstrip(b'\x00').decode('ascii', errors='ignore') return cmd, name, valuedef pack_cmdinfo(cmd, name, value): """ 将cmd、name、value打包成2016字节的二进制数据 :param cmd: int, 命令编号 :param name: str, 名称(最多511字符,留1位给null) :param value: str, 值(最多1499字符,留1位给null) :return: bytes, 打包后的二进制数据 """ # 1. 转换为字节(假设C使用ASCII编码,实际可能是UTF-8或其他) name_bytes = name.encode('ascii')[:511] # 截断超长部分 value_bytes = value.encode('ascii')[:1499] # 2. 填充到固定大小(用null字节填充) name_padded = name_bytes + b'\x00' * (512 - len(name_bytes)) value_padded = value_bytes + b'\x00' * (1500 - len(value_bytes)) # 3. 打包(小端序,与解析时一致) # 格式:i (4字节整数) + 512s + 1500s packed = struct.pack('<i512s1500s', cmd, name_padded, value_padded) return packed# --- 服务器逻辑 ---def run_uds_server(): # 1. 清理:如果套接字文件已存在,则删除它 try: if os.path.exists(SOCKET_PATH): os.remove(SOCKET_PATH) except OSError as e: print(f"{e}") return # 2. 创建套接字 server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: # 3. 绑定和监听 server_socket.bind(SOCKET_PATH) server_socket.listen(5) # 允许5个挂起连接 # 4. 确保权限(可选,确保其他程序可以连接) os.chmod(SOCKET_PATH, 0o666) while True: # 5. 接受新连接 conn, addr = server_socket.accept() try: while True: # 6. 接收数据 received_data = conn.recv(BUFFER_SIZE) if received_data: # 打印接收到的数据 # hexdump.hexdump(received_data) # print('\n') cmd, name, value = parse_cmdinfo(received_data) print(f"received_data-> cmd: {cmd} name: {name} value: {value}") if value != "": config.set('DEFAULT', name, value) cmd = cmd + 1 value = config['DEFAULT'].get(name, '') response_data = pack_cmdinfo(cmd, name, value) print(f"response_data-> cmd: {cmd} name: {name} value: {value}") # hexdump.hexdump(response_data) # print('\n') conn.sendall(response_data) else: time.sleep(1) # print("No data received.") except Exception as e: print(f"{e}") finally: # 8. 关闭连接 conn.close() except KeyboardInterrupt: print("Ctrl+C...") except Exception as e: print(f"{e}") finally: # 9. 最终清理 # 将修改写入配置文件 with open('default.ini', 'w', encoding='utf-8') as configfile: config.write(configfile) server_socket.close() if os.path.exists(SOCKET_PATH): os.remove(SOCKET_PATH)if __name__ == "__main__": # 由于 /var 目录通常需要 root 权限,您需要使用 sudo 运行此脚本 run_uds_server()001114D0 bcm_nvram_set .dynsym001114EC bcm_nvram_match .dynsym00111540 bcm_nvram_get .dynsym00111660 bcm_nvram_commit .dynsym001114D0 bcm_nvram_set .dynsym001114EC bcm_nvram_match .dynsym00111540 bcm_nvram_get .dynsym00111660 bcm_nvram_commit .dynsym#include <stdio.h>#include <stdlib.h>#include <string.h>// #include <ctype.h>#include <stdbool.h>#include <errno.h>// 假设配置文件路径#define ROUTE_CONFIG_PATH "/tmp/nvram_default.cfg"#define MAX_LINE_LENGTH 256#define MAX_KEY_LENGTH 64#define MAX_VALUE_LENGTH 128#define isspace(c) my_isspace(c)int my_isspace(int c) { // 标准 C 定义的空白字符:空格、制表符、换行、回车、换页、垂直制表符 return (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v');}int bcm_nvram_set(const char *key, const char *value){ FILE *fp_read, *fp_write; char line[MAX_LINE_LENGTH]; char config_key[MAX_KEY_LENGTH]; char config_value[MAX_VALUE_LENGTH]; char temp_file[] = "/tmp/nvram.ini.tmp"; int found = 0; int result = 0; // 参数检查 if (!key || strlen(key) == 0 || !value) { fprintf(stderr, "Error: Invalid key or value parameter\n"); return 1; } // 验证key不包含等号或换行符 if (strchr(key, '=') != NULL || strchr(key, '\n') != NULL) { fprintf(stderr, "Error: Key contains invalid characters\n"); return 1; } // 验证value不包含换行符 if (strchr(value, '\n') != NULL) { fprintf(stderr, "Error: Value contains newline character\n"); return 1; } // 打开原配置文件用于读取 fp_read = fopen(ROUTE_CONFIG_PATH, "r"); // 创建临时文件用于写入 fp_write = fopen(temp_file, "w"); if (fp_write == NULL) { fprintf(stderr, "Error: Cannot create temp file: %s\n", strerror(errno)); if (fp_read) fclose(fp_read); return 1; } // 如果原文件存在,逐行处理 if (fp_read != NULL) { while (fgets(line, sizeof(line), fp_read) != NULL) { char *trimmed_line = line; bool is_comment_or_empty = false; // 移除换行符 line[strcspn(line, "\n")] = '\0'; // 跳过空行和注释行(以#开头) if (line[0] == '\0' || line[0] == '#') { is_comment_or_empty = true; } // 移除行首空白字符 while (isspace((unsigned char)*trimmed_line)) { trimmed_line++; } // 跳过只有空白字符的行 if (*trimmed_line == '\0') { is_comment_or_empty = true; } // 如果是注释或空行,直接写入临时文件 if (is_comment_or_empty) { fprintf(fp_write, "%s\n", line); continue; } // 解析 key=value 格式 if (sscanf(trimmed_line, "%63[^=]=%127[^\n]", config_key, config_value) >= 1) { // 去除键名可能的尾部空白 char *trimmed_key = config_key + strlen(config_key) - 1; while (trimmed_key > config_key && isspace((unsigned char)*trimmed_key)) { *trimmed_key = '\0'; trimmed_key--; } // 检查是否匹配请求的键 if (strcmp(config_key, key) == 0) { // 找到匹配的键,写入新的值 fprintf(fp_write, "%s=%s\n", key, value); found = 1; } else { // 不是我们要修改的键,原样写入 fprintf(fp_write, "%s\n", line); } } else { // 格式错误的行,原样写入 fprintf(fp_write, "%s\n", line); } } fclose(fp_read); } // 如果没找到键,在文件末尾添加 if (!found) { fprintf(fp_write, "%s=%s\n", key, value); } // 关闭临时文件 fclose(fp_write); // 用临时文件替换原文件 if (rename(temp_file, ROUTE_CONFIG_PATH) != 0) { fprintf(stderr, "Error: Cannot replace config file: %s\n", strerror(errno)); // 尝试删除临时文件 remove(temp_file); result = 1; } printf("[DEBUG] Setting config: %s = %s\n", key, value); return result;}char *bcm_nvram_get(const char *key){ FILE *fp; char line[MAX_LINE_LENGTH]; char config_key[MAX_KEY_LENGTH]; char config_value[MAX_VALUE_LENGTH]; char *result = NULL; int found = 0; // 参数检查 if (!key || strlen(key) == 0) { fprintf(stderr, "Error: Invalid key parameter\n"); return NULL; } // 打开配置文件 fp = fopen(ROUTE_CONFIG_PATH, "r"); if (fp == NULL) { fprintf(stderr, "Error: Cannot open config file %s: %s\n", ROUTE_CONFIG_PATH, strerror(errno)); return NULL; } // 逐行读取配置文件 while (fgets(line, sizeof(line), fp) != NULL) { // 移除换行符 line[strcspn(line, "\n")] = '\0'; // 跳过空行和注释 if (line[0] == '\0' || line[0] == '#') { continue; } // 移除行首空白字符 char *trimmed_line = line; while (isspace((unsigned char)*trimmed_line)) { trimmed_line++; } // 跳过空行(只有空白字符的行) if (*trimmed_line == '\0') { continue; } // 解析 key=value 格式 if (sscanf(trimmed_line, "%63[^=]=%127[^\n]", config_key, config_value) == 2) { // 去除键名可能的尾部空白 char *trimmed_key = config_key + strlen(config_key) - 1; while (trimmed_key > config_key && isspace((unsigned char)*trimmed_key)) { *trimmed_key = '\0'; trimmed_key--; } // 去除键值可能的首部空白 char *trimmed_value = config_value; while (isspace((unsigned char)*trimmed_value)) { trimmed_value++; } // 去除键值可能的尾部空白 char *end_value = trimmed_value + strlen(trimmed_value) - 1; while (end_value > trimmed_value && isspace((unsigned char)*end_value)) { *end_value = '\0'; end_value--; } // 检查是否匹配请求的键 if (strcmp(config_key, key) == 0) { // 分配内存并复制值 result = malloc(strlen(trimmed_value) + 1); if (result) { strcpy(result, trimmed_value); found = 1; } else { fprintf(stderr, "Error: Memory allocation failed\n"); } break; // 找到配置项,退出循环 } } } fclose(fp); if (result) { printf("[DEBUG] Getting config: %s = %s\n", key, result); } else { result = ""; printf("[DEBUG] Config not found: %s\n", key); } if (!found) { // 不打印警告,让调用者决定是否记录 result = ""; } return result;}bool bcm_nvram_match(const char *key, const char *value){ bool result = false; char *config_value = bcm_nvram_get(key); if (config_value && value) { result = (strcmp(config_value, value) == 0); } printf("[DEBUG] Match config: %s = %s, result: %s\n", key, value, result ? "true" : "false"); if (config_value) { free(config_value); } return result;}int bcm_nvram_commit(void){ #ifdef __linux__ sync(); #endif printf("[DEBUG] Save config\n"); return 0;}int bcm_nvram_unset(const char *key){ FILE *fp_read, *fp_write; char line[MAX_LINE_LENGTH]; char config_key[MAX_KEY_LENGTH]; char config_value[MAX_VALUE_LENGTH]; char temp_file[] = "/tmp/route.cfg.tmp"; int found = 0; int result = 0; // 参数检查 if (!key || strlen(key) == 0) { fprintf(stderr, "Error: Invalid key parameter\n"); return 1; } // 打开原配置文件用于读取 fp_read = fopen(ROUTE_CONFIG_PATH, "r"); if (fp_read == NULL) { // 文件不存在,不需要删除 printf("[DEBUG] Unset config: %s (file not exists)\n", key); return 0; } // 创建临时文件用于写入 fp_write = fopen(temp_file, "w"); if (fp_write == NULL) { fprintf(stderr, "Error: Cannot create temp file: %s\n", strerror(errno)); fclose(fp_read); return 1; } // 逐行读取原文件 while (fgets(line, sizeof(line), fp_read) != NULL) { char *trimmed_line = line; bool is_comment_or_empty = false; // 移除换行符 line[strcspn(line, "\n")] = '\0'; // 跳过空行和注释行(以#开头) if (line[0] == '\0' || line[0] == '#') { is_comment_or_empty = true; } // 移除行首空白字符 while (isspace((unsigned char)*trimmed_line)) { trimmed_line++; } // 跳过只有空白字符的行 if (*trimmed_line == '\0') { is_comment_or_empty = true; } // 如果是注释或空行,直接写入临时文件 if (is_comment_or_empty) { fprintf(fp_write, "%s\n", line); continue; } // 解析 key=value 格式 if (sscanf(trimmed_line, "%63[^=]=%127[^\n]", config_key, config_value) == 2) { // 去除键名可能的尾部空白 char *trimmed_key = config_key + strlen(config_key) - 1; while (trimmed_key > config_key && isspace((unsigned char)*trimmed_key)) { *trimmed_key = '\0'; trimmed_key--; } // 检查是否匹配请求的键 if (strcmp(config_key, key) == 0) { // 找到匹配的键,跳过不写入(即删除) found = 1; continue; } else { // 不是我们要删除的键,原样写入 fprintf(fp_write, "%s\n", line); } } else { // 格式错误的行,原样写入 fprintf(fp_write, "%s\n", line); } } // 关闭文件 fclose(fp_read); fclose(fp_write); // 用临时文件替换原文件 if (rename(temp_file, ROUTE_CONFIG_PATH) != 0) { fprintf(stderr, "Error: Cannot replace config file: %s\n", strerror(errno)); // 尝试删除临时文件 remove(temp_file); result = 1; } printf("[DEBUG] Unset config: %s %s\n", key, found ? "deleted" : "not found"); return result;}#include <stdio.h>#include <stdlib.h>#include <string.h>// #include <ctype.h>#include <stdbool.h>#include <errno.h>// 假设配置文件路径#define ROUTE_CONFIG_PATH "/tmp/nvram_default.cfg"#define MAX_LINE_LENGTH 256#define MAX_KEY_LENGTH 64#define MAX_VALUE_LENGTH 128#define isspace(c) my_isspace(c)int my_isspace(int c) { // 标准 C 定义的空白字符:空格、制表符、换行、回车、换页、垂直制表符 return (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v');}int bcm_nvram_set(const char *key, const char *value){ FILE *fp_read, *fp_write; char line[MAX_LINE_LENGTH]; char config_key[MAX_KEY_LENGTH]; char config_value[MAX_VALUE_LENGTH]; char temp_file[] = "/tmp/nvram.ini.tmp"; int found = 0; int result = 0; // 参数检查 if (!key || strlen(key) == 0 || !value) { fprintf(stderr, "Error: Invalid key or value parameter\n"); return 1; } // 验证key不包含等号或换行符 if (strchr(key, '=') != NULL || strchr(key, '\n') != NULL) { fprintf(stderr, "Error: Key contains invalid characters\n"); return 1; } // 验证value不包含换行符 if (strchr(value, '\n') != NULL) { fprintf(stderr, "Error: Value contains newline character\n"); return 1; } // 打开原配置文件用于读取 fp_read = fopen(ROUTE_CONFIG_PATH, "r"); // 创建临时文件用于写入 fp_write = fopen(temp_file, "w"); if (fp_write == NULL) { fprintf(stderr, "Error: Cannot create temp file: %s\n", strerror(errno)); if (fp_read) fclose(fp_read); return 1; } // 如果原文件存在,逐行处理 if (fp_read != NULL) { while (fgets(line, sizeof(line), fp_read) != NULL) { char *trimmed_line = line; bool is_comment_or_empty = false; // 移除换行符 line[strcspn(line, "\n")] = '\0'; // 跳过空行和注释行(以#开头) if (line[0] == '\0' || line[0] == '#') { is_comment_or_empty = true; } // 移除行首空白字符 while (isspace((unsigned char)*trimmed_line)) { trimmed_line++; } // 跳过只有空白字符的行 if (*trimmed_line == '\0') { is_comment_or_empty = true; } // 如果是注释或空行,直接写入临时文件 if (is_comment_or_empty) { fprintf(fp_write, "%s\n", line); continue; } // 解析 key=value 格式 if (sscanf(trimmed_line, "%63[^=]=%127[^\n]", config_key, config_value) >= 1) { // 去除键名可能的尾部空白 char *trimmed_key = config_key + strlen(config_key) - 1; while (trimmed_key > config_key && isspace((unsigned char)*trimmed_key)) { *trimmed_key = '\0'; trimmed_key--; } // 检查是否匹配请求的键 if (strcmp(config_key, key) == 0) { // 找到匹配的键,写入新的值 fprintf(fp_write, "%s=%s\n", key, value); found = 1; } else { // 不是我们要修改的键,原样写入 fprintf(fp_write, "%s\n", line); } } else { // 格式错误的行,原样写入 fprintf(fp_write, "%s\n", line); } } fclose(fp_read); } // 如果没找到键,在文件末尾添加 if (!found) { fprintf(fp_write, "%s=%s\n", key, value); } // 关闭临时文件 fclose(fp_write); // 用临时文件替换原文件 if (rename(temp_file, ROUTE_CONFIG_PATH) != 0) { fprintf(stderr, "Error: Cannot replace config file: %s\n", strerror(errno)); // 尝试删除临时文件 remove(temp_file); result = 1; } printf("[DEBUG] Setting config: %s = %s\n", key, value); return result;}char *bcm_nvram_get(const char *key){ FILE *fp; char line[MAX_LINE_LENGTH]; char config_key[MAX_KEY_LENGTH]; char config_value[MAX_VALUE_LENGTH]; char *result = NULL; int found = 0; // 参数检查 if (!key || strlen(key) == 0) { fprintf(stderr, "Error: Invalid key parameter\n"); return NULL; } // 打开配置文件 fp = fopen(ROUTE_CONFIG_PATH, "r"); if (fp == NULL) { fprintf(stderr, "Error: Cannot open config file %s: %s\n", ROUTE_CONFIG_PATH, strerror(errno)); return NULL; } // 逐行读取配置文件 while (fgets(line, sizeof(line), fp) != NULL) { // 移除换行符 line[strcspn(line, "\n")] = '\0'; [培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
赞赏
- [原创]Tenda堆栈缓冲区溢出漏洞 (CVE-2024-2986) 484
- [原创]NETGEAR 路由器环境模拟与漏洞分析 3457
- [原创]TD路由器环境模拟与漏洞分析 3152
- [原创]TD路由器固件分析 3239
- [原创]TD路由器固件解密 1849