C语言零长数组使用技巧

在标准C语言(ANSI C)中规定不能定义长度为0的数组。标准可见 ISO 9899:2011 章节 6.7.6.2

数组长度的声明表达式中,如果表达式是一个常量表达式,它的值应该大于零。

但是,有些编译器就把0长度的数组成员作为自己的非标准扩展,例如GNU C —— Arrays of Length Zero

什么是零长数组

1
2
3
4
5
6
7
8
#include <stdio.h>

int main() {
int buf[0];
printf("sizeof(int[0]) = %lu", sizeof(buf));
// sizeof(int[0]) = 0
return 0;
}

在程序中定义一个零长度数组,sizeof() 计算出大小为0,也就是说,零长数组是不占用内存空间的

怎么使用零长数组

零长度数组一般不单独使用,它常常作为结构体的最后一个成员,构成一个变长结构体。

我们定义一个结构体用于接受对端发送的所有传感器数据:

1
2
3
4
5
typedef struct {
uint8_t sensor_num; // 传感器数量
uint8_t single_info_size; // 单个传感器数据size
SENSOR_INFO_S info[0]; // 定义一个零长结构体数组,表示具体数据,数据大小 = sensor_num * sensor_info_size
} SENSOR_RSP_S;

当然我们也可以申请一个指针,指向紧挨SENSOR_RSP_S 结构体的下一个地址,比起指针,用零长数组有这样的优势:

  1. 不需要初始化,数组名直接就是所在的偏移;
  2. 不占任何空间,指针需要占用空间,空数组不占任何空间。意味着无需初始化,数组名就是后面元素的地址,直接就能当指针使用。

使用以上定义的结构体实现以下功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 对rsp地址赋值
const SENSOR_RSP_S *rsp = get_sensor_data();
size_t rsp_len = get_sensor_data_length();

// 判断长度合法性,直接使用sizeof(SENSOR_RSP_S)
if (rsp_len != sizeof(SENSOR_RSP_S) + rsp->sensor_num * rsp->single_info_size) {
...
}

// 遍历访问数据,数据描述和数据内容均来自rsp,理解清晰
for (size_t i = 0; i < rsp->sensor_num; i++) {
show_sensor(rsp->info[i]);
}

正则表达式

常用表达式语法

语法 描述 用例
\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个进制转义符。 n匹配字符n\n匹配一个换行符。连续\\匹配\,而\(匹配(
^ 匹配输入字符串的开始位置
$ 匹配输入字符串的结束位置
* 匹配前面的子表达式零次或多次*等价于{0,} zo*能匹配z以及zoo
+ 匹配前面的子表达式一次或多次+等价于{1,} zo+能匹配zo以及zoo,但不能匹配z
? 匹配前面的子表达式零次或一次?等价于{0,1} password(s)?可以匹配pawwsordpasswords
{n} n是一个非负整数。必须匹配n次 fo{2}d不能匹配fo,但是能匹配foo
{n,} n是一个非负整数。至少匹配n次 go{2,}d不能匹配god,但是能匹配goodgooood
{n,m} m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次,请注意在逗号和两个数之间不能有空格。
? 当该字符紧跟在任何一个其他限制符 *,+,?, {n}, {n,}, {n,m} 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串 对于字符串ooooo+?将匹配单个o,而o+将匹配所有o
. 匹配除\n之外的任何单个字符。要匹配包括\n在内的任何字符,请使用像(.\|\n)的模式。
(pattern) 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,要匹配圆括号字符,请使用\(\)
x\|y 匹配x或y,也可以多个一起使用x\|y\|z (b\|f)oo能匹配boofoob\|foo则匹配bfoo
[xyz] 匹配所包含的任意一个字符 [abc]{2}可以匹配aaacbb
[^xyz] 匹配未包含的任意一个字符
[a-z] 字符范围。匹配指定范围内的任意字符 [a-z]+可以匹配任意长度的小写字母字符串
\d 匹配一个数字字符。等价于[0-9]
\D 匹配一个非数字字符。等价于[^0-9]
\n 匹配一个换行符。等价于\x0a
\r 匹配一个回车符。等价于\x0d
\s 匹配一个任何空白字符,包括空格、制表符、换页符等等。等价于[\f\n\r\t\v]
\S 匹配一个任何非空白字符,等价于[^\f\n\r\t\v]
\t 匹配一个制表符。等价于\x09
\w 匹配一个包括下划线的任何单词字符。等价于[A-Za-z0-9_]
\W 匹配任何非单词字符。等价于[^A-Za-z0-9_]
\un 匹配Unicode字符,其中n是一个用四个十六进制数字表示的Unicode字符 \u00A9匹配版权符号©[\u4e00-\u9fa5]匹配中文字符

家庭网络拓扑及IPTV单线复用方案

搬新家快接近一年了,家里网络布局经过多轮设备更替和演进,总算稳定下来。主要介绍下当前网络的拓扑结构,以及如何利用弱电箱到客厅的一根网线,实现既可以上网,又可以看 IPTV。

家庭网络拓扑介绍

需求分析

  1. 网络套餐为千兆,为保证 WIFI 信号质量不成为带宽的约束,且确保智能家居的接入稳定,需每个房间放置无线接入点
  2. 家庭接入设备量大,存在更多网络需求,需软路由进行拨号、DHCP、DNS等功能;
  3. 弱点箱到客厅仅预埋一根网线,需支持上网和IPTV两个功能;
  4. 尽可能利用当前预埋线,不另走明线破坏装修。

实现方案

整体网络拓扑大致由 光猫 —— 路由 —— 二层交换机 —— AP —— 接入设备 组成,呈现 树状结构,避免网络环路导致的网络风暴。

特点如下:

  1. 光猫 Internet 网口修改为桥接,仅作光电调制解调(modem)功能;
  2. 软路由和光猫同放弱电箱,软路由作为家庭网络中枢,实现以下功能:
    • 拨号上网,且多播实现宽带叠加;
    • 网关,同时存在一般网关和科学上网网关;
    • DNS 服务,实现去广告和加速功能;
    • DHCP 服务,根据设备 mac 分配分配上述功能,且带IPv6;
  3. 增配网管型交换机实现 VLAN 划分,分离 IPTV 线路,同时增加网口供更多设备使用;
  4. 多台无线路由器组Mesh,保证全屋 WIFI 覆盖,取消 DHCP 服务,当作 AP(Access Point) 使用;
  5. 搭建服务器,提供 NAS 存储服务供家庭数据存储,提供虚拟机服务供开发。

利用 VLAN 实现单线复用接入 IPTV

因为 IPTV 的特殊性,最佳是光猫 IPTV 接口走单独网线直达机顶盒,避免和家庭网络的 Internet 存在冲突。 实际上因为弱电箱到客厅预埋线仅一根,必须实现单线复用,且做好区分,才能保证两者独立工作正常,解决方案就是 划分 VLAN

VLAN(Virtual Local Area Network)即虚拟局域网,是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。 每个VLAN是一个广播域,VLAN内的主机间可以直接通信,而VLAN间则不能直接互通。这样,广播报文就被限制在一个VLAN内。

具体流程如下:

1. 光猫 IPTV 端口带上 VLAN 标签(tag)

我的目标是让 IPTV 端口出来的数据全部带上 VLAN 标签接入局域网,进而也因为 tag 标签避免对其他设备影响。 光猫一般用户是无权配置 VLAN 的,因此找宽带供应商工程师拿到光猫超级管理员账号(telecomadmin)和密码。在 网络 —— 网络设置 —— VLAN绑定 中新增配置,指定 iTV 端口的 3_OTHER_B_VID_43( WAN 侧服务名)绑定用户侧 VLAN,VLAN ID 我改为43。

配置过后,光猫 IPTV 出来的原始数据都会带上 VLAN 43 tag 的标签,也会接受相同 tag 的数据帧,并在内部去除 tag。 将光猫 IPTV 端口接入到软路由中,这样弱电箱到客厅单根网线上,就同时有 正常Internet + IPTV(tag) 两种数据。

2. 网管交换机指定端口解 VLAN 标签(untag)

接下来目标是客厅的交换机指定端口解 VLAN 标签,让 IPTV 数据走该端口,其他数据摒弃。

交换机物理上1口是接入单线复用,4口上连接机顶盒。进入交换机后台,创建一个名为 IPTV 的 VLAN 配置,VLAN ID 为43:

  • 4口选择“不带标签”,这样4口发出的数据帧是具有标签的将会去除标签,进来的数据将会在交换机内部加上标签;
  • 1口选择“带标签”,交换机中属于该 VLAN 的数据发出1口时会带上标签,其他正常数据也会流转;
  • 其他端口选择“非成员”,相当于不敢感知 VLAN 数据。

同时配置 VLAN PVID,让4口仅接受来自机顶盒未打标签的数据帧,当未打标签的数据进入交换机时,将会以 PVID(43) 打上标签,避免机顶盒可能发出 VLAN 数据对链路的影响。

如上两步配置,实现基于 VLAN 的单线复用。

成都电信光猫盒子(天邑808AE)破解超级密码记录

最近换了工作地点,新租房的房东默认配置了电信宽带,附带上宽带盒子,这个盒子算是一个多功能盒子,包括以下功能:

  • 光猫
  • 电视盒子(IPTV安卓4.4系统 + 带红外遥控 + 视频/音频HDMI接口 + 音频SPDIF接口)
  • 路由(一个千兆网口 + 一个百兆网口)
  • 2.4G WIFI(带WPS)
  • 电话(RJ11接口)

还带两个USB2.0接口和SD卡接口,但侧边的USB和SD卡接口都是给IPTV安卓系统使用,只有后面的USB接口可以拿来做存储使用,该口也用作后面的破解密码使用


进入正题,接下来介绍具体的过程:

1. 获取管理界面

一般运营商所附赠的盒子都会在背面标注配置地址,常为192.168.1.1,也会标注上配置账号和密码(下称useradmin账户),访问网页结果如下,目前该设备的账号名为useradmin

使用给定的useradmin账户登录,查看设备详情,如下图。如果是相同或者相近的产品型号,可以根据我的思路进行破解。

需要注意,该界面,基本只能用于修改修改wifi配置信息,该配置地址192.168.1.1也是一个非运维地址,真正运维地址是192.168.1.1:8080,该网页访问情况如下:

目前,可以使用useradmin账户登录该管理界面,但也存在权限问题。虽然相较于之前的页面多了一些配置内容,但基本都是些无关痛痒的操作,并不能实现对宽带拨号、桥接、IPV6等的修改。要实现完全功能的修改,需要使用真正给运维人员的账户(下称telecomadmin账户),接下来,我们将使用useradmin账户+运维地址192.168.1.1:8080来获取telecomadmin账户。

2. 利用小trick拿到备份文件

在广大网友的智慧下,使用useradmin账户登录运维地址,然后打开开发者界面(快捷键F12),切换到网络面板。然后网页访问:管理→设备管理,在网络请求中找到:MD_Device_user.html,见下图:

该HTML源码中有个函数enableClick(),作用是实现将盒子配置内容备份到USB存储设备中。但在网页中并没有提供触发这个函数的操作(比如点击某个按钮),需要我们手动触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//USB 快速恢复 启用/禁用
function enableClick() {
var loc = 'usbbackup.cmd?action=backupeble';

with ( document.forms[0] ) {
if ( enable[0].checked == true ) {
if(list.length > 1) {
btnDown.disabled = true;
}

loc += '&enabled=1';
} else if ( enable[1].checked == true ) {
if(list.length > 1) {
btnDown.disabled = false;
}

loc += '&enabled=0';
}
}

loc += '&sessionKey=' + sessionKey;

var code = 'location="' + loc + '"';
eval(code);
}

分析该函数,发现只需要拼接一个字串,就可以访问该功能。其中enabled代表是否启用USB快速恢复功能,当然此处需要启用。最后拼接如下:

1
loc = usbbackup.cmd?action=backupeble&enabled=1&sessionKey=1795138985

这里的sessionKey来源在源码的上方

1
2
3
4
5
6
7
var obj1Items = '1|7|0';
var usblist = '';
var list = usblist.split("|");
var sessionKey = '1795138985';
/*var obj1Cols = numOfCol(obj1Items);*/
/* MDMOID_RESTORE is Type0 object, follow variables are single value */
var usbbackenable = getValueFromList(obj1Items, 0);

给出了 sessionKey 的值,复制过来,拼接成一个完整的字串。

懂URL编码的同学可以发现,该字串就是http请求url的一部分,其中的 enabledsessionKey 都是GET请求的 params

然后补上前面的地址部分,形成一个完整的访问网址:

192.168.1.1:8080/usbbackup.cmd?action=backupeble&enabled=1&sessionKey=1795138985

一定要创建一个新页面,然后打开开发者工具,去访问该网页。如果顺利,会得到一个这样的网页:

但是由于 sessionKey 有访问时间、次数限制,如果短时间没有进行操作或者重复操作,会访问失败,得到如下结果。

如遇到这样的情况,请重新访问:管理→设备管理,拿到新的 sessionKey 来访问该网页。下图是是正常的访问页面。

在某些设备中,除上图中的三个按钮(恢复出厂设置重启保存设置)外,还有有一个备份配置的按钮,如下:

  • 注:该图来源于网络,我的设备没有备份配置按钮

如果你有备份配置按钮,可以提前插入U盘(最好格式化为Fat32格式),然后点击备份配置,得到一个 cfg 类型文件,跳过之后的步骤到第3步。

如果你遇到我这样没有备份配置按钮的情况,那么😒,还需要绕一圈。我们继续使用开发者工具,查看该界面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
var product_type = '3'; //added by hlb,2014.04.21
...
if(product_type == '4')
{
document.write("<div>");
document.write("<b id='Title_usb_backup_lable'>USB备份配置:</b>");
document.write("<div style='text-align:center'>");
document.write("<p id='Title_usb_fast_backup_lable'>快速恢复:&nbsp;&nbsp;&nbsp;");
document.write("<label id='RecEnable_checkbox'><input type='radio' name='enable' onClick='enableClick()' />启用</label>");
//...
document.write("<input type='button' name='btnDown' onClick='btnApply()' value=' 备份配置 ' />");
document.write("</p>");
document.write("</div>");
document.write("</div>");
}

发现product_type还做了限制,必须product_type=4的设备才能有该功能。不过我们可以继续梳理逻辑,如果存在该功能,当点击备份配置按钮的时候,会访问到btnApply()函数,继续找到该函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function btnApply() {
var loc = 'usbbackup.cmd?action=backup';
with ( document.forms[0] ) {
if ( usbsubarea.value == "" || usbsubarea.value == "none" ) {
msg = '请插入USB设备';
alert(msg);
return;
}
//usbsubarea queueintf
idx = usbsubarea.selectedIndex;
val = usbsubarea.options[idx].value;
loc += '&subarea=' + val;
loc += '&sessionKey=' + sessionKey;
}
var code = 'location.assign("' + loc + '")';
eval(code);
}

又是一个拼接函数,继续按照逻辑拼接,得到如下的结果:

1
loc = usbbackup.cmd?action=backup&subarea=usb1_1&sessionKey=1222473631

其中的 subarea 值和 sessionKey 的值可以在该页面的上方找到:

1
2
3
4
var obj1Items = '1|7|0';
var usblist = 'usb1_1|';
var list = usblist.split("|");
var sessionKey = '781652989';
  • 注:若usblist 值为空串, 请插上U盘重试,建议U盘格式化为Fat32格式。

保证设备后面插上U盘,并形成一个完整的链接:

192.168.1.1:8080/usbbackup.cmd?action=backup&subarea=usb1_1&sessionKey=781652989

  • 注:subarea 字段值为 sublist|隔断的第一个字串。

访问该链接,如果顺利,不会有访问异常,而且在U盘目录下新生成有如下cfg类型文件:

如果没有该文件,请尝试重试以上步骤🙄,尤其是 sessionKey 的获取有效。

3. 使用routerpassview 获取超级密码

如果拿到cfg文件,在网络上搜索下载routerpassview软件,并用该软件打开U盘内的cfg文件。如果软件被杀毒软件拦截,请酌情放行。

在软件中查找telecom关键词(快捷键Ctrl + F),如果能找到如图的内容,恭喜你破解telecomadmin账户成功👍。账号名为telecomadmin,账户密码为图中Password中间标识的字段,该密码跟随设备,相同设备间一般不一样,甚至同一设备恢复出厂设置后都有可能会修改。

最后,使用telcomadmin账号登录运维地址

附上 useradmin 账号和 telcomadmin 账号登录运维地址的截图,可以看到多出来的配置功能。

telecomadmin账号登录运维截图

useradmin账号登录运维截图

0%