成都电信光猫盒子(天邑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账号登录运维截图

“中华人民共和国公民身份号码” 验证算法

中华人民共和国公民身份号码由18位组成,其中前17位由阿拉伯数字0~9组成,最后一位0~9或X 构成,按构造顺序如下:

位置 长度 说明
区域码 1~6位 6 指公民常住户口所在地的行政划区代码,由省(2位)+市(2位)+区(县)(2位)构成
出生日期码 7~14位 8 指公民出生的公历日期,由年(4位)+月(2位)+日(2位)构成
顺序码 15、16位 2 表示在同一区域码所标识的区域范围内,对同年、同月、同日出生的人编定的顺序号
性别码 17位 1 奇数代表男性,偶数代表女性
校验码 18位 1 通过前17位计算所得,见国标GB11643-1999

根据标准,最后一位校验码采用ISO 7064:1983.MOD 11-2校验码计算法,具体计算方法解释如下,令\(N_i (i=1,2...16,17)\)表示每一位身份证号,需要首先计算各位的权重\(W_i\)\[ W_i = 2^{17-(i-1)} \mod 11 \] 计算所得权重数据如下表:

位数\(i\) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
权重\(W_i\) 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2

最后一位校验码 \(C\) 由下式计算而出: \[ C= (12 - (\sum_{i=1}^{17}{(N_i W_i)} \mod 11 ) \mod 11 \] 可以发现,\(C\)因为最后与\(11\)求余计算,导致\(C\)必然小于等于\(10\)。又因为阿拉伯数字限制,如果\(C=10\),则校验码为字符"X";如果\(C \leq 10\),则校验码为数字"C"。

我们可以根据计算前17位身份证码所得校验码与所提供的第18位校验码进行是否相等判定,从而得出所输入的18位身份证号是否有误。这也是一些网站初步验证身份证号的机制。

附上本人所写的一个提取身份号码有关信息的小工具——“中华人民共和国公民身份号码”验证工具

动态规划之背包问题

背包问题(Knapsack problem)是一种组合优化NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。

也可以将背包问题描述为决定性问题,即在总重量不超过\(W\)的前提下,总价值是否能达到\(V\)

定义

我们有\(n\)种物品,物品\(i\)的重量为\(w_i\),价值为\(v_i\)

我们假定所有物品的重量和价值都是非负数,背包所能承受的最大重量为\(W\)

  • 如果限定每种物品最多能选择1个,则该背包称为01背包

    可以用公式表示为:

\[ \begin{align} 最大化 & \quad \sum^{n}_{i=1}{v_{i} x_i} \\ 受限于 & \quad \sum^{n}_{i=1}{w_{i} x_i} \leq W, \quad x_i \in \{0,1\} \\ \end{align} \]

  • 如果限定物品\(i\)最多只能选择\(b_i\)个,则问题称为多重背包问题(有界背包问题)

    可以用公式表示为: \[ \begin{align} 最大化 & \quad \sum^{n}_{i=1}{v_{j} x_i} \\ 受限于 & \quad \sum^{n}_{i=1}{w_{i} x_i} \leq W, \quad x_i \in \{0,1…,b_i \} \\ \end{align} \]

  • 如果不限定每种物品的数量,则问题称为完全背包问题(无界背包问题)

  • 各类复杂的背包问题总可以变换为简单的0-1背包问题进行求解。

01背包

设一共有\(N\)件物品,第\(i\)件物品的重量为\(w[i]\),价值为\(v[i]\),最大总重量为\(W\)

如果采取暴力枚举方法,每件物品都存在装入和不装入两种情况,所以时间复杂度为\(O(n^2)\),使用动态规划可以把时间复杂度控制在\(O(N*W)\)。求解的总目标是背包物品的总价值,变量有是否放入物品书包限重

定义\(dp[i][j]\)表示将前\(i\)件物品放入限重为\(j\)的书包可以获得的最大价值,其中$0 i N,  0 j  W $。

状态转移:

  1. 不装入第\(i\)物品:\(dp[i][j] = dp[i-1][j]\)
  2. 在能装入的前提下,装入第\(i\)件物品:\(dp[i][j]=dp[i-1][j-w[i]]+v[i]\)

即状态转移方程为:

1
dp[i][j] = max(dp[i-1][j], j-w[i] >= 0 ? dp[i-1][j-w[i]]+v[i] : 0)

由状态方程可知,dp[i][j]的值只与dp[i-1][0,…,j-1]有关,所以可以采用滚动数组对空间优化。需要注意的是,为了防止上一层循环dp[0,…,j-1]被覆盖,需要逆向枚举\(j\)(空间优化前没有这个限制)。代码如下:

1
2
3
4
5
6
7
8
9
10
int solution01Knapsack(int N, int W, std::vector<int> &w, std::vector<int> &v) {
// w[i]重量 v[i]价值
std::vector<int> dp(W+1);
for (auto i = 0; i < N; i++) {
for (auto j = W; j >= w[i]; j--) {
dp[j] = std::max(dp[j], dp[j - w[i]] + v[i]);
}
}
return dp[W];
}

时间复杂度为\(O(NW)\),空间复杂度为\(O(W)\)。第\(i\)件物品装入或者不装入而获得的最大价值完全可以由前面\(i-1\)件物品的最大价值决定,暴力枚举忽略了这个事实。

完全背包

设一共有\(N\)件物品,第\(i\)件物品的重量为\(w[i]\),价值为\(v[i]\),每件可以无限多个,最大总重量为\(W\)

与01背包非常相似,状态dp定义一致,只是状态转移稍微不同:

  1. 不装入第\(i\)物品:\(dp[i][j] = dp[i-1][j]\)(同01背包)
  2. 在能装入的前提下,因为每件物品有无限个,所以不从\(dp[i-1][j-w[i]]\)转移,而是从\(dp[i][j-w[i]]\)转移,即装入第\(i\)种商品后还可以再继续装入该种商品。即装入第\(i\)件物品:\(dp[i][j]=dp[i][j-w[i]]+v[i]\)

状态转移方程为:

1
dp[i][j] = max(dp[i-1][j], j-w[i] >= 0 ? dp[i][j-w[i]]+v[i] : 0)

和01背包问题相似,也可以进行空间优化,不同点在于完全背包的\(j\)只能正向枚举而01背包只能逆向枚举,因为\(max\)第二项是dp[i]而01背包是dp[i-1],即完全背包需要覆盖而01背包需要避免覆盖。代码如下:

1
2
3
4
5
6
7
8
9
10
int solutionUnboundedKnapsack(int N, int W, std::vector<int> &w, std::vector<int> &v) {
// w[i]重量 v[i]价值
std::vector<int> dp(W+1);
for (auto i = 0; i < N; i++) {
for (auto j = w[i]; j <= W; j++) {
dp[j] = std::max(dp[j], dp[j - w[i]] + v[i]);
}
}
return dp[W];
}

完全背包问题也可以转化为01背包问题来解:将一种物品转换成最多能放下的若干件。

最简单的想法是,考虑到第\(i\)种物品最多装入\(W/w[i]\)件,于是把第\(i\)种物品转化为 \(W/w[i]\)件费用及价值不变的物品,然后求解这个01背包问题。时间复杂度为:\(O(NW\frac{\sum{\frac{W}{w_i}}}{N}) = O(W\sum{\frac{W}{w_i}})\)

更高效的转化方法是采用二进制的思想:把第\(i\)种物品拆成重量为\(w_i 2^k\)、价值为\(v_i 2^k\)若干件物品,其中 k 取遍满足\(w_i 2^k \leq W\)的非负整数。这是因为不管最优策略选几件第\(i\)种物品,总可以表示成若干个刚才这些物品的和(例:\(13 = 2^0 + 2^2 + 2^3\))。这样就将转换后的物品数目降成了对数级别。时间复杂度为:\(O(NW\frac{\sum\log_2{\frac{W}{w_i}}}{N}) = O(W\sum\log_2{\frac{W}{w_i}})\)

多重背包

设一共有\(N\)件物品,第\(i\)件物品的重量为\(w[i]\),价值为\(v[i]\),数量限制为\(n[i]\),最大总重量为\(W\)

参考完全背包转为01背包的思想,\(k\)表示装入第\(i\)种物品的件数,k <= min(n[i],j/w[i])​

1
dp[i][j] = max{(dp[i-1][j-k*w[i]]+k*v[i]) for range(k)}

同理也可以进行空间优化,而且\(j\)也必须逆向枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int solutionBoundedKnapsack(int N, int W,
std::vector<int> &w,
std::vector<int> &v,
std::vector<int> &n) {
// w[i]重量 v[i]价值
std::vector<int> dp(W + 1);
for (auto i = 0; i < N; i++) {
for (auto j = W; j >= w[i]; j--) {
for (auto k = 1; k <= std::min(n[i], j / w[i]); k++) {
dp[j] = std::max(dp[j], dp[j - k * w[i]] + k * v[i]);
}
}
}
return dp[W];
}

时间复杂度为:\(O(NW\bar n) = O(W\sum{n_i})\),空间复杂度为:\(O(W)\)

当然也可以采用二进制思路,将第\(i\)种物品分成了\(log_2{n_i}\)件物品,将时间复杂度优化为:\(O(W\sum{\log_2{n_i}})\)

杂项

背包问题还有一些其他变种问题。

恰好装满

要求:必须恰好装满背包。

只需要修改初始化,将\(dp[0,…,N][0]\)初始为0,其它\(dp\)值均初始化为-inf,因为此时只有容量为0的背包可以在什么也不装情况下被“恰好装满”,其它容量的背包初始均没有合法的解,应该被初始化为-inf

总方案数

要求:求背包装满或则装至某一指定容量的总方案数。

修改转移方程中的\(max\)\(sum\)

二维背包

上文讨论的背包都只有一个限制:最大容量。二维背包指一个背包存在两个限制(比如重量和体积限制)。

此类问题的解法和一维背包相似,需要\(dp\)数组要多开一维,其他和一维背包一致。

输出最优方案

要求:输出背包问题的解方案。

可以参照一般动态规划的方案输出方法:记录每一个状态是由那条策略推导而出,以便回溯出上一个状态。

以01背包为例,再开辟一个数组\(P[i][j]\)来记录方案,\(P[i][j]=0\)表示\(dp[i][j]\)采用第一种状态转移策略,即\(dp[i][j] = dp[i-1][j]\)\(P[i][j]=1\)表示采用第二种状态转移策略,即\(dp[i][j]=dp[i-1][j-w[i]]+v[i]\),从而反推方案。

另外我们也可以从求出的\(dp\)数组进行反推,若 \(dp[i][j] = dp[i−1][j]\) 说明未选第\(i\)个物品,反之说明选了。

数据库SQL JOINS笔记汇总

可见下图,一共包括7种连接

  1. 内连接——inner join
  2. 左连接(左外连接)——left join(left outer join)
  3. 右连接(右外连接)——right join(right outer join)
  4. 左连接不包含内连接——left join excluding inner join
  5. 右连接不包含内连接——right join excluding inner join
  6. 全连接(全外连接)——full join(full outer join)
  7. 全连接不包含内连接——full outer join excluding inner join

创建测试数据

以下SQL目的在于创建用于测试的test数据集,并创建user_infomail_info两个信息表

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
26
27
28
29
30
31
32
33
34
35
36
37
drop database if exists test;

create database `test` default character set utf8mb4 collate utf8mb4_unicode_ci;

use test;


create table user_info
(
user_id int auto_increment comment '用户ID'
primary key,
name varchar(20) not null comment '用户名',
lv int default 1 not null comment '等级'
)
comment '用户信息表';

create table mail_info
(
mail_id int auto_increment comment '邮件ID'
primary key,
title varchar(50) not null comment '邮件标题',
user_id int not null comment 'user_info.user_id外键(不设约束)'
)
comment '邮件信息表';


insert into user_info(user_id, name, lv)
values (1001, 'Bill', 100),
(1002, 'William', 220),
(1003, 'Joseph', 80);


insert into mail_info(mail_id, title, user_id)
values (1, 'Happy Birthday', 1002),
(2, 'Congrats on obtaining', 1006),
(3, 'Enjoy Charm Beach', 1010);

user_id表

mail_id表

内连接——inner join

内连接是一种一一映射关系,就是两张表都有的才能显示出来 用韦恩图表示是两个集合的交集,如图:

1
2
3
4
# 内连接
select ui.user_id, ui.name, ui.lv, mi.mail_id, mi.title, mi.user_id
from user_info as ui
inner join mail_info as mi on ui.user_id = mi.user_id;

查询结果

左连接(左外连接)——left join(left outer join)

左连接是左边表的所有数据都有显示出来,右边的表数据只显示共同有的那部分,没有对应的部分只能补空显示,所谓的左边表其实就是指放在left join的左边的表 用韦恩图表示如下:

1
2
3
4
# 左连接
select ui.user_id, ui.name, ui.lv, mi.mail_id, mi.title, mi.user_id
from user_info as ui
left join mail_info mi on ui.user_id = mi.user_id;

查询结果

右连接(右外连接)——right join(right outer join)

右连接正好是和左连接相反的,这里的右边也是相对right join来说的,在这个右边的表就是右表 用韦恩图表示如下:

1
2
3
4
# 右连接
select ui.user_id, ui.name, ui.lv, mi.mail_id, mi.title, mi.user_id
from user_info as ui
right join mail_info mi on ui.user_id = mi.user_id;

查询结果:

左连接不包含内连接——left join excluding inner join

这个查询是只查询左边表有的数据,共同有的也不查出来 韦恩图表示如下:

1
2
3
4
5
# 左连接不包含内连接
select ui.user_id, ui.name, ui.lv, mi.mail_id, mi.title, mi.user_id
from user_info as ui
left join mail_info mi on ui.user_id = mi.user_id
where mi.user_id is null;

查询结果:

右连接不包含内连接——right join excluding inner join

右连接正好是和左连接相反的,这里的右边也是相对right join来说的,在这个右边的表就是右表 用韦恩图表示如下:

1
2
3
4
5
# 右连接不包含内连接
select ui.user_id, ui.name, ui.lv, mi.mail_id, mi.title, mi.user_id
from user_info as ui
right join mail_info mi on ui.user_id = mi.user_id
where ui.user_id is null;

查询结果:

全连接(全外连接)——full join(full outer join)

查询出左表和右表所有数据,但是去除两表的重复数据 韦恩图表示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 全连接
# mysql 不支持全连接
# select ui.user_id, ui.name, ui.lv, mi.mail_id, mi.title, mi.user_id
# from user_info as ui
# full join mail_info mi on ui.user_id = mi.user_id;
# 用以下方式实现,全链接 = 左连接 union 右连接
select ui.user_id, ui.name, ui.lv, mi.mail_id, mi.title, mi.user_id
from user_info as ui
left join mail_info mi on ui.user_id = mi.user_id
union
distinct
select ui.user_id, ui.name, ui.lv, mi.mail_id, mi.title, mi.user_id
from user_info as ui
right join mail_info mi on ui.user_id = mi.user_id;

查询结果:

全连接不包含内连接——full outer join excluding inner join

意思就是查询左右表各自拥有的那部分数据 韦恩图表示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 全连接不包含内连接
# select ui.user_id, ui.name, ui.lv, mi.mail_id, mi.title, mi.user_id
# from user_info as ui
# full join mail_info mi on ui.user_id = mi.user_id
# where ui.user_id is null
# or mi.user_id is null;
# 用以下方式实现,全连接不包含内连接 = 左连接不包含内连接 union all 右连接不包含内连接
select ui.user_id, ui.name, ui.lv, mi.mail_id, mi.title, mi.user_id
from user_info as ui
left join mail_info mi on ui.user_id = mi.user_id
where mi.user_id is null
union all
select ui.user_id, ui.name, ui.lv, mi.mail_id, mi.title, mi.user_id
from user_info as ui
right join mail_info mi on ui.user_id = mi.user_id
where ui.user_id is null;

查询结果:

0%