使用天翼家庭云来提速家用宽带, 以及反编译天翼家庭云apk获得Signature算法
2019年8月3日 这个已经无法使用, 电信已经关闭接口了
折腾文, 记录下自己折腾的过程. 最近发现iPhone上的电信推出来的天翼家庭云app, 里面有一个提速功能, 可以让宽带速度提升到500M下行, 50M上行. 然后就想能否持续为其他应用加速…果然查了可行, 但是网上的方法只提到了Signature以及SessionKey 2个参数, 但是我实测还需要Date的参数, 似乎Signature参数是通过Date进行计算出来的.
(English version translate by GPT-3.5)
效果图(原宽带 200M / 30M)
下载
说明: 下载蜗牛游戏出品的一款航海世纪游戏, 测得速度.
上传速度
说明: 上传速度是由其他城市(由于电信的宽带提供公网IP, 因此我做了资源的公网暴露)的服务器下载存储在个人家用服务器的资源, 所得出的下载速度即为家用服务器所使用宽带的上传速度, 由于资源属于私人资源, 不公开, 因此以上包含私人内容(包括IP, 下载地址, 具体大小等)均做遮掩.
原文参考
准备工作
- 准备好 Charles(抓包工具) 下载 Charles Web Debugging Proxy HTTP Monitor / HTTP Proxy / HTTPS
- Android反编译工具 dex2jar 下载 dex2jar SourceForge
- Jd-GUI 下载 Jd-GUI benow.ca 下载Jd-GUI Github
- 天翼家庭云 Android版本 下载天翼家庭云 Android 189.cn,地址无效 下载天翼家庭云 Android 7.3.0 地址已经无效
先按照原文思路来一次
首先, 按照原文参考的文章, 我先来一遍(从StartQos之前, 本文将不在阐述)
从Charles中我得到了这些参数(我看到请求返回了400, 但是实际已经提速了, 所以我就认为400是一个正常的返回code)
我看到了文章所阐述的SessionKey和Signature, 我立刻使用PostMan进行模拟发送, 得到了这样的消息(其中Body为空, 尽管上面需要什么prodCode啊, clientType啊等等, 先不填)
发现返回了并不是我的预期值, 似乎需要Date参数, 添加Date后, 得到了预期结果.
但是我修改了Date, 就得到
1
2
3
4
5
<error>
<code>InvalidArgument</code>
<message>sessionsignature is not match</message>
</error>所以从上得知, 应该是天翼做了升级, 追加了Date进行验证.
折腾开始
我想, 既然加了Date验证, 那服务端很有可能加了对时间判断, 可是IOS版的不能反编译(汇编啥的不算), 所以我用Android平台上的dex2jar工具进行反编译操作, 研究下这个Date怎么玩的, 然后这个Signature又是怎么算出来的.
下载好天翼家庭云 Android版, 这里用7.3.0版本(2019-2-9此刻的最新版本), 准备好dex2jar, 反编译可以参考Android APK 反编译实践, 或者自行搜索dex2jar的使用, 本文不再阐述.
反编译过程会提示错误, 不用管它, 此时得到了3个文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18D:\********>cd D:\********\dex2jar-2.0
D:\********\dex2jar-2.0>d2j-dex2jar.bat C:\********\classes.dex
dex2jar C:\********\classes.dex -> .\classes-dex2jar.jar
Detail Error Information in File .\classes-error.zip
Please report this file to http://code.google.com/p/dex2jar/issues/entry if possible.
D:\********\dex2jar-2.0>d2j-dex2jar.bat C:\********\classes2.dex
dex2jar C:\********\classes2.dex -> .\classes2-dex2jar.jar
Detail Error Information in File .\classes2-error.zip
Please report this file to http://code.google.com/p/dex2jar/issues/entry if possible.
D:\********\dex2jar-2.0>d2j-dex2jar.bat C:\********\classes3.dex
dex2jar C:\********\classes3.dex -> .\classes3-dex2jar.jar
Detail Error Information in File .\classes3-error.zip
Please report this file to http://code.google.com/p/dex2jar/issues/entry if possible.
D:\********\dex2jar-2.0>用jd-gui来将这些文件都提取出源码来后, 解压这些源码, 这样可以方便使用Sublime等工具进行全文搜索(Jd-GUI搜索我嫌麻烦, 这一步可以跳过.)
调用的接口既然是startQos.action, 这就是一个关键字, 先全文搜索这个
startQos
来获取一点线索, 全文搜索得到了这些, 从这里看出最可疑的就是第三段, 那个来自classes2.jar里面的StartQosRequest.java这个文件(p.s. 这么快就找到了, 太出乎预料了…)使用Jd-GUI可以看到代码如下, 从这段代码看到, 它调用了send方法, 然后参数就是请求地址, 但是在调用请求之前, 调用了addSessionHeaders, 这应该就是这次的目标了(加密都不加全一点….), 不犹豫, 方法直接进去!
进入addSessionHeaders方法中, 会看到它又调用了
HelperUtil.addSessionHeader(this.mHttpRequest, paramSession, paramString);
方法, 再进去, 来到了这里, 看到这次的目标了.这一行, 特别明显
1
getSignatrue(paramString, str2, paramSession.getSessionSecret(), paramHttpRequestBase.getMethod(), str1)
很明显, 这里应该就是那个Sign的算法了, 进去后, 代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public static String getSignatrue(String paramString1, String paramString2, String paramString3, String paramString4, String paramString5)
{
StringBuilder localStringBuilder1 = new StringBuilder();
StringBuilder localStringBuilder2 = new StringBuilder();
localStringBuilder2.append(FamilyConfig.SessionKey);
localStringBuilder2.append("=");
localStringBuilder1.append(localStringBuilder2.toString());
localStringBuilder1.append(paramString2);
localStringBuilder1.append("&Operate=");
localStringBuilder1.append(paramString4);
if (paramString1.startsWith("/")) {
localStringBuilder1.append("&RequestURI=");
} else {
localStringBuilder1.append("&RequestURI=/");
}
localStringBuilder1.append(paramString1);
localStringBuilder1.append("&Date=");
localStringBuilder1.append(paramString5);
DLog.v("httpSignature", localStringBuilder1.toString());
return CodecUtil.hmacsha1(localStringBuilder1.toString(), paramString3);
}再进到CodecUtil.hmacsha1后, 如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static String hmacsha1(String paramString1, String paramString2)
{
try
{
try
{
Mac localMac = Mac.getInstance("HmacSHA1");
localMac.init(new SecretKeySpec(paramString2.getBytes(), "HmacSHA1"));
paramString1 = localMac.doFinal(paramString1.getBytes());
}
catch (InvalidKeyException paramString1)
{
paramString1.printStackTrace();
}
}
catch (NoSuchAlgorithmException paramString1)
{
for (;;) {}
}
paramString1 = null;
return ByteFormat.toHex(paramString1);
}分析: 从以上代码得出, 这个Sign需要以下参数:
- 第一个paramString是固定的值
family/qos/startQos.action
- 第二个paramString是sessionKey, 它调用了
paramSession.getSessionKey()
- 第三个paramString是sessionSecret, 一个新的参数
- 第四个paramString是request的请求方式, 因为请求是POST, 所以它的值也是固定的
POST
- 第五个paramString是那个时间Date. 最后调用
hmacsha1
使用HmacSHA1算法
进行sha1加密, 得出Signature - syncServerTime方法中使用了
SystemClock.elapsedRealtime()
, 这里Android用来获取开机时间的方法, 即开机的那个时间到现在的时间数, 然后FamilyConfig.pre_elapsed_time
可以猜测出这是上次启动的时间, 但是服务器怎么知道我什么时候开机的? 我这里2个值就使用写死的固定值.
- 第一个paramString是固定的值
将这些代码复制出来, 然后稍作整理, 最后整理后的代码如下, 其中
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66private static final String SESSION_KEY= "SessionKey";
private static final String ACCESS_URL = "family/qos/startQos.action";
private static String syncServerDate() {
SimpleDateFormat localSimpleDateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
Date localObject1 = new Date();
localSimpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
String str = localSimpleDateFormat.format((Date)localObject1);
long l1 = 16000; // 原 SystemClock.elapsedRealtime() 系统启动时间, 随便填
long l2 = 12500; // 原 FamilyConfig.pre_elapsed_time, 上次系统启动时间, 随便填
Date localObject2 = new Date(localObject1.getTime() + (l1 - l2));
if (localObject2 != null) {
try {
localSimpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
return localSimpleDateFormat.format((Date)localObject2);
} catch (Exception localException2) {
localException2.printStackTrace();
}
}
return str;
}
public static String getSignatrue(String accessUrl, String sessionKey, String sessionSecret, String requestMethod, String syncServerDate)
{
StringBuilder localStringBuilder1 = new StringBuilder();
StringBuilder localStringBuilder2 = new StringBuilder();
localStringBuilder2.append(SESSION_KEY);
localStringBuilder2.append("=");
localStringBuilder1.append(localStringBuilder2.toString());
localStringBuilder1.append(sessionKey);
localStringBuilder1.append("&Operate=");
localStringBuilder1.append(requestMethod);
if (accessUrl.startsWith("/")) {
localStringBuilder1.append("&RequestURI=");
} else {
localStringBuilder1.append("&RequestURI=/");
}
localStringBuilder1.append(accessUrl);
localStringBuilder1.append("&Date=");
localStringBuilder1.append(syncServerDate);
return hmacsha1(localStringBuilder1.toString(), sessionSecret);
}
public static String hmacsha1(String paramString1, String paramString2) {
try {
Mac localMac = Mac.getInstance("HmacSHA1");
localMac.init(new SecretKeySpec(paramString2.getBytes(), "HmacSHA1"));
return toHex(localMac.doFinal(paramString1.getBytes()));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String toHex(byte[] paramArrayOfByte) {
if ((paramArrayOfByte != null) && (paramArrayOfByte.length != 0)) {
StringBuilder localStringBuilder = new StringBuilder();
int i = 0;
while (i < paramArrayOfByte.length) {
localStringBuilder.append(HEX[(paramArrayOfByte[i] >> 4 & 0xF)]);
localStringBuilder.append(HEX[(paramArrayOfByte[i] & 0xF)]);
i += 1;
}
return localStringBuilder.toString();
}
return "";
}以上就知道了Sign的算法, 现在有新的问题出来了, 这个Secret怎么得到?
尝试抓包获得Secret的相关信息
猜测: 一般secret是用来和服务器通信时使用的token, 很有可能是app和服务端使用的令牌, 而且作为这么重要的内容, 估计电信不会傻到用http来传输, 估计这里使用的是https了(结果与猜测一致), Charles是支持使用https进行抓包的, 这里需要做一些配置.
Charles配置Https抓包
Android设备参考此处, 以下是IOS的配置, 首先, 回到Charles, 选择Tab栏
Help -> SSL Proxying -> Install Charles Root Certificate on a Mobile Device or Remote Browser
我这里其实已经设置好了SSL, 所以可以看到https的包也能抓到
点击后出现如下图, 告诉我们在设备中配置Charles为
192.168.1.100:xxxx
这个地址(这句话也同时告诉了我们, 手机和运行Charles的电脑要处于同一个局域网), 然后, 然后使用手机浏览器访问chls.pro/ssl
来下载和安装这个证书, 并且有特别提示,对于IOS10及以上的系统, 必须进入 设置 -> 通用 -> 关于本机 -> 证书信任设置 -> 在针对根证书启用完全信任中打开Charles证书的完全信任
照着它的方法做, 基本步骤如下,
如果你有Apple Watch, 可能会有第二张图的提示, 选择iPhone
, 最后别忘了去设置信任证书
这样做应该就可以了, 去Charles菜单栏
Proxy -> SSL Proxying Settings...
, 打开后如右图, 直接设置*, 端口443, 用来捕获所有的443端口的SSL请求(tips. ssl不一定就是443端口, 还有8443)
再次尝试抓包获得Secret的相关信息
重新运行家庭云, 看到包都抓到了, 不断寻找后, 定位到这么一条(直接说结果吧, 就是它.)
经过测试后, 发现这就是我们要的sessionKey, 将其值放在变量中, 算出Signanture, 最后发送请求, 得到了与app中发送相同的结果
我也尝试去获得它是如何取得sessionKey和sessionSecret, 但是折腾了一半不想折腾了..就放弃了…
2019年3月30日补充: 表示这篇文章中获得是sessionKey, 到今天为止依然能用….
不要忘了及时关闭charles证书信任, 对一个自签根证书设为信任是有一定风险的.
试着自动化
然后就可以写一个java, 然后利用Crontab来定时发送, 或其他想得到的操作来让其维持提速(似乎需要10分钟发一次提速就可以了, 所以代码中也是10分钟的线程睡眠时间).
代码如下
1 | package com.ruterfu; |