首页
社区
课程
招聘
[原创]Tenda堆栈缓冲区溢出漏洞 (CVE-2024-2986)
发表于: 1天前 483

[原创]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 页面如下

HTTP LOGIN

首先查看 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 开始处

GDB START

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

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

GDB SPRINTF

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

GDB SPRINTF

sprintf 后的栈数据如下

GDB SPRINTF

单步运行到函数的结束处

GDB 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

POC

栈的数据结构如下

POC

开启 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.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 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 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 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
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
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
ip link add name br0 type bridge
ip link set br0 up
ip addr add 192.168.3.3/24 dev br0
ip link add name br0 type bridge
ip link set br0 up
ip addr add 192.168.3.3/24 dev br0
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 };
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 };
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字节字符串)
    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, value
 
def 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 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字节字符串)
    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, value
 
def 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   .dynsym
001114EC        bcm_nvram_match .dynsym
00111540        bcm_nvram_get   .dynsym
00111660        bcm_nvram_commit    .dynsym
001114D0        bcm_nvram_set   .dynsym
001114EC        bcm_nvram_match .dynsym
00111540        bcm_nvram_get   .dynsym
00111660        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实战!

最后于 1天前 被易之生生编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (1)
雪    币: 2221
活跃值: (536)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
看起来还是比较简单的漏洞,没想到现在还有这种
8小时前
0
游客
登录 | 注册 方可回帖
返回