某王VPN分析记录

WARING

有IP地址等信息上传,调试学习时,请注意屏蔽该查询接口。

1
curl 'http://ip-api.com/json/?lang=zh-CN' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36' -H 'Host: ip-api.com' -H 'Connection: Keep-Alive' -H 'Accept-Encoding: gzip' --compressed

请求分析

请求路径

  1. http://ddf312fdd.xyz/mji4a/adj4x

  2. http://80.251.220.183/mji4a/adj4x

    等等..

请求类型:POST

有两个参数

参数c030 :为时间戳函数

首先从Native获取一些检测系统的信息通过MD5、sha、sha256一顿处理,出现 f3:1a:b9:05:4f:a7:ae:d7:1d:19:44:b5:95:f8:64:48:cb:63:65:89特征(我称之为hardcode1),一般是不会有变动的。与时间戳拼接后,再与qpald0394cjis()的Bytes进行拼接,qpald0394cjis()返回的校验签名就是hardcode1 。中间用cvmnsPidf3984os隔开。

自写签名2:

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
public static String a(String arg6, boolean arg7) {
StringBuilder v7;
byte[] v6_2;
int v3;
try {
v3 = 0;
v6_2 = MessageDigest.getInstance("MD5").digest(arg6.getBytes("UTF-8"));
}
catch(NoSuchAlgorithmException v6_1) {
v6_1.printStackTrace();
return null;
}
catch(UnsupportedEncodingException v6) {
v6.printStackTrace();
return null;
}

StringBuilder v0 = new StringBuilder(v6_2.length * 2);
int v2; // 获取MD5生成的字符的Hex
for(v2 = 0; v2 < v6_2.length; ++v2) { // 获取MD5生成的字符的Hex
int v4 = v6_2[v2] & 0xFF;
if(v4 < 16) {
v0.append("0");
}

v0.append(Integer.toHexString(v4));
}

String v6_3 = v0.toString();
String v0_1 = "x";
while(v3 < v6_3.length()) {
int v1 = v3 + 1;
String v2_1 = v6_3.substring(v3, v1);
try {
Integer.parseInt(v2_1);
}
catch(Exception unused_ex) {
v0_1 = v2_1;
break;
}

v3 = v1;
}

if(arg7) {
v7 = new StringBuilder();
v7.append(v0_1);
v6_3 = v6_3.substring(6, 9);
}
else {
v7 = new StringBuilder();
v7.append(v0_1);
}

v7.append(v6_3);
return v7.toString();
}

获取签名,然后MD5一下,加了一部分自己的代码,对native层的数据进行摘要提取。

自写签名1:

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
public static String a(byte[] arg5) {
if(arg5 == null) {
return "null";
}

ByteArrayInputStream v0 = new ByteArrayInputStream(arg5);
StringBuilder v1 = new StringBuilder();
try {
CertificateFactory.getInstance("X509").generateCertificate(v0);
try {
MessageDigest v0_1 = MessageDigest.getInstance("MD5");
v0_1.update(arg5);
v0_1.digest();
v0_1.reset();
MessageDigest v0_2 = MessageDigest.getInstance("SHA");
v0_2.update(arg5);
v1.append(Tools.b(v0_2.digest()));
v0_2.reset();
MessageDigest v0_3 = MessageDigest.getInstance("SHA256");
v0_3.update(arg5);
v0_3.digest();
}
catch(NoSuchAlgorithmException v5) {
v5.printStackTrace();
}

v1.append("\n");
}
catch(CertificateException unused_ex) {
}

return v1.toString();
}

这里似乎就是使用了sha进行了签名的hash,防止篡改hash原文。

最终加密

1
2
3
4
5
6
7
8
9
10
11
12
public static byte[] a(byte[] arg2, byte[] arg3) {
try {
X509EncodedKeySpec v0 = new X509EncodedKeySpec(arg3);
PublicKey v3 = KeyFactory.getInstance(Tools.a).generatePublic(v0); // RSA
Cipher v0_1 = Cipher.getInstance(Tools.b); // RSA/ECB/PKCS1Padding
v0_1.init(1, v3);
return v0_1.doFinal(arg2);
}
catch(Exception unused_ex) {
return null;
}
}

Base64.encodeToString(Tools.a(v1_1 + "issdi210pdifd" + v1_1 + "c9830lp1038Pidd" + v1_1 + Tools.a(Tools.a(Tools.qpoe03994jcjis(arg5)), false) + v1_1 + arg5.getResources().getString(0x7F0E0140) + v1_1 + Tools.qpald0394cjis().getBytes(), v0.getEncoded()), 0); // string:pwoim3984a "cvmnsPidf3984os"

基本上就是:

最终加密(时间戳 + issdi210pdifd + 时间戳 + c9830lp1038Pidd + 自写签名2(自编写签名(qpoe03994jcjis(),false) + cvmnsPidf3984os + qpoe03994jcjis().bytes),密钥)

qpoe03994jcjis()

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
jbyteArray __cdecl Java_com_sticktoit_util_Tools_qpoe03994jcjis(JNIEnv *a1)
{
char *v1; // eax
char *v2; // esi
_BYTE *v3; // eax
int v4; // eax
jbyteArray v5; // edi
jbyte *v6; // ST24_4
jbyteArray result; // eax
void *v8; // [esp+8h] [ebp-24h]
jsize v9; // [esp+10h] [ebp-1Ch]
int v10; // [esp+14h] [ebp-18h]
int v11; // [esp+18h] [ebp-14h]

v1 = sub_A3608230(); // 检测包名是否被修改,是否加载Substrated的so库,或者说是否被Substrate Hook
if ( !v1 )
goto LABEL_8;
v2 = v1;
v10 = 0;
v9 = 0;
v3 = (_BYTE *)GetApkSign(v1, (int)&v10); // 似乎获取APK签名
if ( !v3 )
{
free(v2);
LABEL_8:
v5 = 0;
goto LABEL_9;
}
v8 = v3;
v4 = sub_A3608F50(v3, v10, &v9);
if ( v4 || (v5 = 0, v9) )
{
v6 = (jbyte *)v4;
v5 = (*a1)->NewByteArray(a1, v9);
(*a1)->SetByteArrayRegion(a1, v5, 0, v9, v6);
}
free(v8);
free(v2);
sub_A3609120();
LABEL_9:
result = (jbyteArray)_stack_chk_guard;
if ( _stack_chk_guard == v11 )
result = v5;
return result;
}

大概就检测一下Hook,然后拿个签名完事……

参数e81e :为设备信息

加密代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static String a(String arg5, String arg6) {
try {
byte[] v5_1 = arg5.getBytes();
SecretKeySpec v0 = new SecretKeySpec(MessageDigest.getInstance("MD5").digest(v5_1), "AES");
IvParameterSpec v3 = new IvParameterSpec(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
Cipher v5_2 = Cipher.getInstance("AES/CBC/PKCS5Padding");
v5_2.init(1, v0, v3);
byte[] v6 = arg6.getBytes();
return Base64.encodeToString(v5_2.doFinal(v6, 0, v6.length), 0);
}
catch(Exception v5) {
v5.printStackTrace();
return null;
}
}

加密密钥: d87d1281c0087cf191aedd889789cc68b

模拟器抓包解密示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"packageName": "com.sticktoit",
"platform": "Android",
"manufacturer": "HUAWEI",
"model": "ELE-AL00",
"cpu": "arm",
"androidVersion": 22,
"device": "aosp",
"androidID": "865da7d81397cb58",
"uuid": "6035f867-ec6d-4a39-85ac-9234a91742ee",
"isVersionStore": true,
"versionCode": 58,
"language": "zh",
"country": "CN",
"displayName": "中文 (中国)",
"ipInfo": "人工马赛克", //此处提交的便是上传的IP信息,通过ip-api.com的接口
"isSupportAes": true
}

返回包

解密密钥:d87d1281c0087cf191aedd889789cc68b

加密代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static String a(String arg5, String arg6) {
try {
byte[] v5_1 = arg5.getBytes();
SecretKeySpec v0 = new SecretKeySpec(MessageDigest.getInstance("MD5").digest(v5_1), "AES");
IvParameterSpec v3 = new IvParameterSpec(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
Cipher v5_2 = Cipher.getInstance("AES/CBC/PKCS5Padding");
v5_2.init(2, v0, v3);
byte[] v6 = Base64.decode(arg6, 0);
return new String(v5_2.doFinal(v6, 0, v6.length));
}
catch(Exception v5) {
v5.printStackTrace();
return null;
}
}

返回包范例:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
{
"hookMessage": "警告,这是破解版,极有可能是有心人士想窃取你的隐私所做的陷阱版本,非常危险,请尽快移除,并安装谷歌商店的正式安全版本。",
"isCheckCrack": true,
"isConnectionCodePage": true,
"specialTxt": "",
"rewardADChance": 10,
"nativeADChance": 0,
"isNativeADMuted": true,
"groupTitle": "老王用户群",
"groupUrl": "https://t.me/wangvpn_users",
"groupDescription": "老王的用户支持群是透过Telegram软件创建的,此软件需要连到外网才能正常使用,拥有此软件并创建帐号后即可成功加入。\n\n加入群组后禁止讨论任何违法事项,诸如色情、暴力、政治等等一盖通通不可讨论,违规者需退出群组。\n\n此群组目 的系讨论“老王VPN”一切大小事务,可以提需求,提建议或是回报错误,总之老王欢迎你,也期待与你一同相伴相行。",
"announceVersion": -1,
"announceImage": "http://jcijeytbdks.club:5050/WangCute.jpeg",
"announceTitle": "09/23 新版本审核中",
"announceContent": "我是老王\n\n今天最新版已经成功制作完成了,\n目前正在谷歌商店审核当中,\n一旦通过审核就能进行更新了,\n也请大家放心,\n到时将会尽快告 知大家,\n这几天也会陆续把适合新版本的新协议给配置上去,\n\n感谢大家的耐心等候。\n\n老王爱你们。",
"updateMessage": "目前最新版 2.2.16 已经推出了。\n新版本除了能加快线路稳定与速度外,\n也大幅提高了大家在乎的安全性,\n理论上也修复了不能使用谷歌商店的错误。\n\n但因为线路要跟旧版本兼容,导致新版本暂时无法开启新的加速设置。\n为了上诉诸多原因,\n请大家尽可能地尽早更新至最新版本,\n 等全数迁移至新版本时,老王将会开启加速设置。\n\n-------------------\n注意!如果你的地区无法更新,\n请来信给老王。\nwangvpn666.tech@gmail.com\n-------------------",
"serverStatus": true,
"serverStatusMessage": "如果你看到这个讯息\n代表服务器有问题\n请你马上联系老王\n\nwangvpn666.tech@gmail.com\n\n",
"storeUrl": "com.sticktoit",
"updateUrl": "",
"storeStatus": true,
"storeStatusAnotherSource": true,
"isShowSuggestUpdate": false,
"serverStatusMaxVersion": 59,
"userStatusHighVersion": 57,
"userStatusMinVersion": 57,
"isTempUpdateStatus": false,
"isDetectVPN": true,
"isOldAD": false,
"ipUrlHeader": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36",
"ipUrl": "http://ip-api.com/json/?lang=zh-CN",
"locationDataList": [
{
"locationCode": "USWest1",
"onlineIcon": "http://jcijeytbdks.club:5050/unitedstates.png",
"localIcon": "unitedstates.png",
"displayName": "美西1",
"displayDescription": "(稳定 电信可选)",
"pingAddress": "23.239.0.237"
},
{
"locationCode": "USWest2",
"onlineIcon": "http://jcijeytbdks.club:5050/unitedstates.png",
"localIcon": "unitedstates.png",
"displayName": "美西2",
"displayDescription": "( 稳定 电信可选)",
"pingAddress": "173.230.151.209"
},
{
"locationCode": "JP1",
"onlineIcon": "http://jcijeytbdks.club:5050/japan.png",
"localIcon": "japan.png",
"displayName": "日本1",
"displayDescription": "(低延迟 联通首选)",
"pingAddress": "139.162.81.51"
},
{
"locationCode": "JP2",
"onlineIcon": "http://jcijeytbdks.club:5050/japan.png",
"localIcon": "japan.png",
"displayName": "日本2",
"displayDescription": "(低延迟 联通首选)",
"pingAddress": "172.104.73.170"
},
{
"locationCode": "Singapore1",
"onlineIcon": "http://jcijeytbdks.club:5050/singapore.png",
"localIcon": "singapore.png",
"displayName": "新加坡1",
"displayDescription": "(低延迟 移动首选)",
"pingAddress": "139.162.50.126"
},
{
"locationCode": "Singapore2",
"onlineIcon": "http://jcijeytbdks.club:5050/singapore.png",
"localIcon": "singapore.png",
"displayName": "新加坡2",
"displayDescription": "(低延迟 移动首选)",
"pingAddress": "172.104.51.86"
},
{
"locationCode": "Germany1",
"onlineIcon": "http://jcijeytbdks.club:5050/germany.png",
"localIcon": "germany.png",
"displayName": "德国1",
"displayDescription": "(稳定 高延迟)",
"pingAddress": "172.104.154.241"
},
{
"locationCode": "Germany2",
"onlineIcon": "http://jcijeytbdks.club:5050/germany.png",
"localIcon": "germany.png",
"displayName": "德国2",
"displayDescription": "(稳定 高延迟)",
"pingAddress": "172.105.245.163"
},
{
"locationCode": "India",
"onlineIcon": "http://jcijeytbdks.club:5050/india.png",
"localIcon": "india.png",
"displayName": "印度",
"displayDescription": "(低延迟)",
"pingAddress": "172.105.56.226"
},
{
"locationCode": "USCenter",
"onlineIcon": "http://jcijeytbdks.club:5050/unitedstates.png",
"localIcon": "unitedstates.png",
"displayName": "美中",
"displayDescription": "(稳定 电信首选)",
"pingAddress": "45.79.0.53"
},
{
"locationCode": "USSouthEast",
"onlineIcon": "http://jcijeytbdks.club:5050/unitedstates.png",
"localIcon": "unitedstates.png",
"displayName": "美东南",
"displayDescription": "(稳定 中延迟)",
"pingAddress": "66.228.61.38"
},
{
"locationCode": "Australia",
"onlineIcon": "http://jcijeytbdks.club:5050/australia.png",
"localIcon": "australia.png",
"displayName": "澳大利亚",
"displayDescription": "(稳定 中延迟)",
"pingAddress": "172.105.169.37"
},
{
"locationCode": "Canada",
"onlineIcon": "http://jcijeytbdks.club:5050/canada.png",
"localIcon": "canada.png",
"displayName": "加拿大",
"displayDescription": "(稳定 中延迟)",
"pingAddress": "172.105.106.144"
},
{
"locationCode": "USEast",
"onlineIcon": "http://jcijeytbdks.club:5050/unitedstates.png",
"localIcon": "unitedstates.png",
"displayName": "美东",
"displayDescription": "(稳定 中延迟)",
"pingAddress": "23.239.11.118"
},
{
"locationCode": "UK",
"onlineIcon": "http://jcijeytbdks.club:5050/unitedkingdom.png",
"localIcon": "unitedkingdom.png",
"displayName": "英国",
"displayDescription": "(稳定 高延迟)",
"pingAddress": "109.74.193.17"
}
]
}

再起波澜

很神奇,没有发现任何代理相关数据,了解人都知道套壳了X2ray,所以必然有再次获取的地方。所以继续分析会看到他会再次发送一次请求,请求method均未变,只是路径和Body有所变化。

请求路径变为

POST /qp039x/ydj83pa

当然,请求包里面加了点料,就是选择的速度最优的服务器"connectLocation":"JP2"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"packageName": "com.sticktoit",
"platform": "Android",
"manufacturer": "HUAWEI",
"model": "ELE-AL00",
"cpu": "x86",
"androidVersion": 22,
"device": "aosp",
"androidID": "865da7d81397cb58",
"uuid": "5c208567-da21-4fdc-8170-215e627d7659",
"isVersionStore": true,
"versionCode": 58,
"language": "zh",
"country": "CN",
"displayName": "中文 (中国)",
"ipInfo": "再次人工马赛克",
"connectLocation": "JP2",
"isSupportAes": true
}

比如这里选择的JP2。服务器名字在第一次请求里面有返回可选择的服务器列表,以及ping测试的服务器IP。

返回内容为:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
{
"isAppCheck": true,
"vpnBuilderMode": "proxy",
"appPackageNameBlackList": [
"xunlei",
"magnet",
"bt",
"torr",
"p2p",
"com.delphicoder.flud",
"com.magicsearch",
"com.foossi.bitcloud",
"com.taobao",
"com.xunmeng",
"com.bilibili",
"com.tencent",
"com.sina",
"com.sdu.didi",
"com.sankuai",
"com.ss.android.article.news",
"AlipayGphone"
],
"appPackageNameWhiteList": [
"com.android.providers.downloads",
"com.paypal.android.p2pmobile",
"twitter",
"com.cutpasteall.tumbdown",
"instagram",
"tumblr",
"nova.all.video.downloader",
"video.downloader.videodownloader",
"com.free.video.downloader.download.free",
"facebook",
"google",
"youtube",
"android",
"twitter",
"samsung",
"xiaomi",
"mi",
"honor",
"huawei",
"com.android.providers.downloads.ui",
"com.android.providers.downloads"
],
"appNameBlackList": [
"種子",
"磁力",
"种子",
"bt",
"torr",
"p2p",
"迅雷",
"xunlei"
],
"appNameWhiteList": [
"google",
"谷歌",
"youtube",
"推特",
"twitter",
"android",
"samsung",
"三星",
"小米",
"华为",
"華為"
],
"domainAgentList": [
"translate.google.cn",
"services.googleapis.cn",
"googleapis.cn",
"google.cn"
],
"domainDirectList": [
"speedtest.net",
"speedtest.cn",
"ip.sb",
"skk.moe",
"whoer.net",
"whatismyipaddress.com",
"ez2o.com",
"whatismyip.com",
"geoipview.com",
"ifreesite.com",
"nkuht.edu",
"myip.com",
"j4.com",
"ip-api.com",
"ipapi.co",
"ipstack.com",
"iplocation.net",
"expressvpn.com",
"kinsta.com",
"nordvpn.com",
"whatismybrowser.com",
"ip2location.com",
"whatsmyip.org",
"ipip.net",
"ip.cn",
"chinaz.com",
"ip138.com",
"baidu.com",
"bdstatic.com",
"bcebos.com"
],
"domainBlockList": [],
"isFakeForever": false,
"trafficMax": -1,
"isShowTrafficMax": false,
"isLocalDnsEnabled": false,
"isRemoteDnsHost": true,
"remoteDns": "1.1.1.1,1.0.0.1,8.8.8.8,8.8.4.4,localhost",
"muxConcurrency": -1,
"allowInsecure": false,
"connectSecurity": "none",
"routingStrategy": "AsIs",
"isIPv6": false,
"sotProtocol": "",
"ttNetwork": "",
"e8c1": "",
"d9ff": "fjM7pTs9a7AtSQyUxhmQoT+L4L5lE9yixfu+wu59l3te80Apqfl7zPPiVVKv/GT1vixncMLiDTaU7KMPoxysPsCOW5uTVsEWs97zpH9bLo2+Rfj1+9ecNWrk2lnJ2KrszADZ8/XMSgUPwrhfEWOxynj/mxXoA2xfgAaiT2mMHS2e5Mb+VzmcC4b7IJhmrP/Kgypd2Yrbeh+vXU6Qlb085J6Vrc9+8P/oyH7bSOzlqRfN2J2UCfl82ykw9lMFp5s0OS/tYgydgQsba/LTKg9ZABvtd9M0Nb3C2jCO6hfvmr+FCex7gZ3ipUoTEjMuOOJ/xQJ5zDNa9TBMQoO/AJq8aQ==",
"connectionCode": "xV3Vv9qdqn0tqsLOUyrHeqqpc1i1Ep9iXJZqchF8lUSDZ0JmGyHaZJHzHqhydLTHaNULSyfQ2E2wFAX7HQHbYp8JMAwY0YsHlrs496dfkAaLBj+M18Ka17DUbfog7l7jIC/mVoPcHwRNhSuOm5Ai5qghHKD1w2SLxp0wN7FdjQwt0ksLMREeFumswL565p04UbEdBfVUMCI2Ho74zhQSuvxQ8nh5RtWY6G3GXnn+pvMi091mSDwq3jdwcSlNMNFZz8XEZD+YpZioIwpmqYqbNVFygDrghpq4J8MBx8nwpHWEiTEPazi0NbNGDp/zG5QZ+LwhYiNskUv4D4Uorc+PrpOfcjxy3oOP1JAeEuu6tUm7LX+0U4PZXwXV2pFdKxQ3SO1zL6KOGNT53fS/YOS9tdI9lVYplpl9foo0Dw+RJ964lr6uu0Bt58o1U5D6PBCTBE0Vm/Cn1xbwJ68YCH9Lqfkr3JsiXi4n9Rw68hpSkVmEfm29MoK7QdVZRuPN7H/vKLAek+eenWHFO2Ll+mbM3hsq1pdlCtBzf2CH+VMM0m4="
}

上述本身是用AES加密的,密钥未变,仍如上,但是此处很明显的一段代码 connectionCode,虽是AES加密,但是密钥有所变动,变为dof034nvnci927494vpisapxmcjru289c,解密数据就不再给出了。

此时就已经拿到了连接地址,但是蛮可惜的就是很难拿到多个地址,似乎是服务器随机下发的,没有看到任何根据地址进行请求的代码。

尾声

此处整体分析,已经算是结束了,相比之前的老王,服务器数据下发做了拆包处理,不再一次下发所有服务器,并且代码进行了混淆,并且字符串进行了Base64加密,同时进行了lib介入,保障了Java层的直接突破签名校验。而且还检测了Substrate这类Hook,不过奇怪的是这东西好像八百年不更新了,干嘛要检测他呢……

没什么意外,分析就到此结束了。