实现 srun 校园网的远程认证登录

远程认证登录程序:Windows_pyinstaller Source code


0x00 起因

其实是因为本人买了巨型游戏本, 完全不想带出去上课…

借着之前为了玩 galgame 捣鼓内网 sunshine 串流的经验,想着在校园网这个大内网上再实现一遍, 顺便实现公网串流, 这样笔记本就可以直接放在宿舍里不动窝了, 出门只需要携带一个平板。

开始捣鼓后才逐渐发现这里面的水属实是有点深了…

首先是 500 多块一年的校园网居然只允许 2 个设备同时在线, 有事没事还会掉线…

上行带宽只有 20Mbps, 甚至大部分时候连走内网的 localsend 都跑不满这所谓的 20Mbps带宽…

下行的 100 Mbps 带宽更是开玩笑, 看个 4k 视频梦回 3g 时代…

还有就是连 ipv6 都不支持, 公网串流直接半路夭折…

作为一个 I 占比 99% 的 纯正 I 人, 校园网办都办了, 根本不想去退…

臭豆腐和腐乳已经买到了, 那就研究一下怎么把这坨玩意儿消化了吧。


0x01 数据包分析

今天这篇是专门针对 srun 校园网认证系统的远程认证的, 更详细的校园网串流经验会在之后再写一篇出来。

远程串流的掉线非常频繁, 而且一旦掉线你就得等回到宿舍的时候再重新认证, 一个不小心你就有可能半天都用不了电脑, 如果这个时候非常不幸的你正在上程设的话…

你就只能一边用着平板那极为糟糕的文字处理逻辑 coding 一边祈祷你写的代码没有 bug 了…

之前为了规避这种情况, 我使用的是 py 脚本自动打开认证网页, 然后油猴脚本自动点击登录键的方案. 可行是可行,但是这种需要手动打开, 每次重新认证都会弹出一个页面,回到宿舍会有一万个认证页面等着你的实现实在是不够优雅.

于是我开始研究校园网是如何认证的, 毕竟是个 web 页面,刚好撞到枪口上了。

Yakit 抓包研究一下认证过程中发生的事情先:

找到一个有价值的包:

可以看到有 callback 参数, 当前 ip 以及一个神秘参数 _:

  • callback 是利用 jsonp 跨域获取数据时需要使用到的一个随机生成的字符串

  • ip 显而易见是 Request 端的 ip 地址

  • _ 明眼人都可以看出这是时间戳

而回传的包中返回了一个很可疑的字符串 challenge

暂时看不出什么名堂,而且没有看到密码被传输,说明还有进一步的认证流程, 继续分析:

这个包里面的信息就非常多了。

  • callback: 和上个包相同

  • action: 有含义, 固定是 login

  • username: 你的登陆账号 / 用户名

  • ip: dddd

  • n: 固定是 200

  • _: 时间戳

  • os: 很好理解, 可填 Windows 10

  • name: 依葫芦画瓢即可

  • double_stack 固定是 0

  • chksum 需要研究, 不知道是什么的校验和

  • info 需要研究, SRBX1 ? 不知道什么玩意儿, 后面看起来像 base64

  • password: 需要研究, 和正常的 md5 明显不一致

不过既然加密过程是在前端进行的, 大概是可以对 js 进行分析从而获取加密手段的。


0x02 js 分析

F12 看源码, 搜一下我们需要分析的参数:

Chksum

1
2
3
4
5
6
7
8
9
var str = token + username;
str += token + hmd5;
str += token + ac_id;
str += token + ip;
str += token + n;
str += token + type;
str += token + i;

// chksum: sha1(str)

chksumchkstr 的 sha1 , 而 chkstr 由以上参数拼接而成。

password (HMAC-MD5)

1
var hmd5 = md5(password, token);

token 作为 key 对 password 进行加密。

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
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
var i = _classPrivateFieldGet(_assertThisInitialized(_this), _encodeUserInfo).call(_assertThisInitialized(_this), {
username: username,
password: password,
ip: ip,
acid: ac_id,
enc_ver: enc
}, token);
//-------------<以下是加密部分>--------------//
_encodeUserInfo.set(_assertThisInitialized(_this), {
writable: true,
value: function value(info, token) {
// 克隆自 $.base64,防止污染
var base64 = _this.clone($.base64); // base64 设置 Alpha


base64.setAlpha('LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA'); // 用户信息转 JSON

info = JSON.stringify(info);

function encode(str, key) {
if (str === '') return '';
var v = s(str, true);
var k = s(key, false);
if (k.length < 4) k.length = 4;
var n = v.length - 1,
z = v[n],
y = v[0],
c = 0x86014019 | 0x183639A0,
m,
e,
p,
q = Math.floor(6 + 52 / (n + 1)),
d = 0;

while (0 < q--) {
d = d + c & (0x8CE0D9BF | 0x731F2640);
e = d >>> 2 & 3;

for (p = 0; p < n; p++) {
y = v[p + 1];
m = z >>> 5 ^ y << 2;
m += y >>> 3 ^ z << 4 ^ (d ^ y);
m += k[p & 3 ^ e] ^ z;
z = v[p] = v[p] + m & (0xEFB8D130 | 0x10472ECF);
}

y = v[0];
m = z >>> 5 ^ y << 2;
m += y >>> 3 ^ z << 4 ^ (d ^ y);
m += k[p & 3 ^ e] ^ z;
z = v[n] = v[n] + m & (0xBB390742 | 0x44C6F8BD);
}

return l(v, false);
}

function s(a, b) {
var c = a.length;
var v = [];

for (var i = 0; i < c; i += 4) {
v[i >> 2] = a.charCodeAt(i) | a.charCodeAt(i + 1) << 8 | a.charCodeAt(i + 2) << 16 | a.charCodeAt(i + 3) << 24;
}

if (b) v[v.length] = c;
return v;
}

function l(a, b) {
var d = a.length;
var c = d - 1 << 2;

if (b) {
var m = a[d - 1];
if (m < c - 3 || m > c) return null;
c = m;
}

for (var i = 0; i < d; i++) {
a[i] = String.fromCharCode(a[i] & 0xff, a[i] >>> 8 & 0xff, a[i] >>> 16 & 0xff, a[i] >>> 24 & 0xff);
}

return b ? a.join('').substring(0, c) : a.join('');
}

return '{SRBX1}' + base64.encode(encode(info, token));
}
});

把一些信息拉去转 base64 了,同样需要 token. 需要注意的是这个 base64 的编码表被更改了, 同时还进行了一些奇怪的加密, 抄就好了.

token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
_getToken.set(_assertThisInitialized(_this), {
writable: true,
value: function value(host, ip, callback) {
_this.ajax.jsonp({
host: host,
url: _classPrivateFieldGet(_assertThisInitialized(_this), _api).token,
params: {
username: _this.userInfo.username + _this.userInfo.domain,
ip: _this.portalInfo.doub && host ? '' : _this.userInfo.ip
},
success: function success(res) {
return callback(res.challenge);
},
// 获取 Token 失败给出弹框
error: function error(res) {
return _this.confirm(_this.translate(res));
}
});
}
});

可以发现其实 token 就是之前获取的 challenge .


0x03 python加密复现

然后就发现其实这个项目已经有前辈做过了…

https://blog.csdn.net/hackermengzhi/article/details/130499424

不过也好,省得我再把 js 转 py 了。

代码在下面:

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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
#coding=utf-8
import ctypes
import hashlib
import json
import math
import random
import socket
import time

import requests


conf = ConfigParser()
conf.read("1.config",encoding='utf-8')
username = conf.get('mysql', 'username')
password = conf.get('mysql', 'password')
init_url="http://172.19.0.5"

def init_getip():
"""网络获得ip(似乎看起来更靠谱) """
init_res=requests.get(init_url,headers=header)
print("初始化获取ip")
ip=re.search('id="wlanuserip" value="(.*?)"',init_res.text).group(1)
return ip

def GetLocalIPByPrefix(prefix):
""" 多网卡情况下,根据前缀获取IP(Windows 下适用) """
localIP = ''
for ip in socket.gethostbyname_ex(socket.gethostname())[2]:
if ip.startswith(prefix):
localIP = ip
return localIP
local_ip=GetLocalIPByPrefix('10.')
#local_ip=init_getip()

class MD5(object):

'''MD5加密//根据js'''
def a(self, n):
r = ''
e = 32 * len(n)
for i in range(0,e,8):
r += chr((n[i >> 5] >> i % 32 & 0xffffffff >> i % 32) & 255)
return r

def d(self, n):
r = [0 for _ in range(len(n) >> 2)]
e = 8 * len(n)
for i in range(0,e,8):
if i >> 5 == len(r):
r.append(0)
r[i >> 5] |= ctypes.c_int32((255 & ord(n[i // 8])) << i % 32).value
return r

def t(self, n, t):
r = (65535 & n) + (65535 & t)
a = ctypes.c_int32(n >> 16).value
b = ctypes.c_int32(t >> 16).value
c = ctypes.c_int32(r >> 16).value
d = ctypes.c_int32(a + b + c << 16).value
return d | 65535 & r

def r(self, n, t):
return ctypes.c_int32(n << t).value | (n >> 32 - t & 0xffffffff >> 32 - t)

def e(self, n, e, o, u, c, f):
return self.t(self.r(self.t(self.t(e, n), self.t(u, f)), c), o)

def o(self, n, t, r, o, u, c, f):
return self.e(t & r | ~t & o, n, t, u, c, f)

def u(self, n, t, r, o, u, c, f):
return self.e(t & o | r & ~o, n, t, u, c, f)

def c(self, n, t, r, o, u, c, f):
return self.e(t ^ r ^ o, n, t, u, c, f)

def f(self, n, t, r, o, u, c, f):
return self.e(r ^ (t | ~o), n, t, u, c, f)

def i(self, n, r):
x = 14 + (r + 64 >> 9 << 4) + 1
n.extend([0 for _ in range(x - len(n))])
n[-1] = r
n[r >> 5] |= ctypes.c_int32(128 << r % 32).value
l = 1732584193
g = -271733879
v = -1732584194
m = 271733878
for j in range(0,len(n),16):
if j + 15 >= len(n):
x = j + 15 - len(n) + 1
for _ in range(x):
n.append(0)
i = l
a = g
d = v
h = m
l = self.o(l, g, v, m, n[j], 7, -680876936)
m = self.o(m, l, g, v, n[j + 1], 12, -389564586)
v = self.o(v, m, l, g, n[j + 2], 17, 606105819)
g = self.o(g, v, m, l, n[j + 3], 22, -1044525330)
l = self.o(l, g, v, m, n[j + 4], 7, -176418897)
m = self.o(m, l, g, v, n[j + 5], 12, 1200080426)
v = self.o(v, m, l, g, n[j + 6], 17, -1473231341)
g = self.o(g, v, m, l, n[j + 7], 22, -45705983)
l = self.o(l, g, v, m, n[j + 8], 7, 1770035416)
m = self.o(m, l, g, v, n[j + 9], 12, -1958414417)
v = self.o(v, m, l, g, n[j + 10], 17, -42063)
g = self.o(g, v, m, l, n[j + 11], 22, -1990404162)
l = self.o(l, g, v, m, n[j + 12], 7, 1804603682)
m = self.o(m, l, g, v, n[j + 13], 12, -40341101)
v = self.o(v, m, l, g, n[j + 14], 17, -1502002290)
g = self.o(g, v, m, l, n[j + 15], 22, 1236535329)
l = self.u(l, g, v, m, n[j + 1], 5, -165796510)
m = self.u(m, l, g, v, n[j + 6], 9, -1069501632)
v = self.u(v, m, l, g, n[j + 11], 14, 643717713)
g = self.u(g, v, m, l, n[j], 20, -373897302)
l = self.u(l, g, v, m, n[j + 5], 5, -701558691)
m = self.u(m, l, g, v, n[j + 10], 9, 38016083)
v = self.u(v, m, l, g, n[j + 15], 14, -660478335)
g = self.u(g, v, m, l, n[j + 4], 20, -405537848)
l = self.u(l, g, v, m, n[j + 9], 5, 568446438)
m = self.u(m, l, g, v, n[j + 14], 9, -1019803690)
v = self.u(v, m, l, g, n[j + 3], 14, -187363961)
g = self.u(g, v, m, l, n[j + 8], 20, 1163531501)
l = self.u(l, g, v, m, n[j + 13], 5, -1444681467)
m = self.u(m, l, g, v, n[j + 2], 9, -51403784)
v = self.u(v, m, l, g, n[j + 7], 14, 1735328473)
g = self.u(g, v, m, l, n[j + 12], 20, -1926607734)
l = self.c(l, g, v, m, n[j + 5], 4, -378558)
m = self.c(m, l, g, v, n[j + 8], 11, -2022574463)
v = self.c(v, m, l, g, n[j + 11], 16, 1839030562)
g = self.c(g, v, m, l, n[j + 14], 23, -35309556)
l = self.c(l, g, v, m, n[j + 1], 4, -1530992060)
m = self.c(m, l, g, v, n[j + 4], 11, 1272893353)
v = self.c(v, m, l, g, n[j + 7], 16, -155497632)
g = self.c(g, v, m, l, n[j + 10], 23, -1094730640)
l = self.c(l, g, v, m, n[j + 13], 4, 681279174)
m = self.c(m, l, g, v, n[j], 11, -358537222)
v = self.c(v, m, l, g, n[j + 3], 16, -722521979)
g = self.c(g, v, m, l, n[j + 6], 23, 76029189)
l = self.c(l, g, v, m, n[j + 9], 4, -640364487)
m = self.c(m, l, g, v, n[j + 12], 11, -421815835)
v = self.c(v, m, l, g, n[j + 15], 16, 530742520)
g = self.c(g, v, m, l, n[j + 2], 23, -995338651)
l = self.f(l, g, v, m, n[j], 6, -198630844)
m = self.f(m, l, g, v, n[j + 7], 10, 1126891415)
v = self.f(v, m, l, g, n[j + 14], 15, -1416354905)
g = self.f(g, v, m, l, n[j + 5], 21, -57434055)
l = self.f(l, g, v, m, n[j + 12], 6, 1700485571)
m = self.f(m, l, g, v, n[j + 3], 10, -1894986606)
v = self.f(v, m, l, g, n[j + 10], 15, -1051523)
g = self.f(g, v, m, l, n[j + 1], 21, -2054922799)
l = self.f(l, g, v, m, n[j + 8], 6, 1873313359)
m = self.f(m, l, g, v, n[j + 15], 10, -30611744)
v = self.f(v, m, l, g, n[j + 6], 15, -1560198380)
g = self.f(g, v, m, l, n[j + 13], 21, 1309151649)
l = self.f(l, g, v, m, n[j + 4], 6, -145523070)
m = self.f(m, l, g, v, n[j + 11], 10, -1120210379)
v = self.f(v, m, l, g, n[j + 2], 15, 718787259)
g = self.f(g, v, m, l, n[j + 9], 21, -343485551)
l = self.t(l, i)
g = self.t(g, a)
v = self.t(v, d)
m = self.t(m, h)
return [l, g, v, m]

def l(self, n, t):
o = self.d(n)
u = [0 for _ in range(16)]
c = [0 for _ in range(16)]
if len(o) > 16:
o = self.i(o, 8 * len(n))
for j in range(16):
u[j] = 909522486 ^ o[j]
c[j] = 1549556828 ^ o[j]
u.extend(self.d(t))
e = self.i(u, 512 + 8 * len(t))
c.extend(e)
r = self.a(self.i(c, 640))
return r

def s(self, n, t):
return self.l(n, t)

def g(self, n):
d = list('0123456789abcdef')
e = []
for c in list(n):
c = ord(c)
e.append(d[c >> 4 & 15])
e.append(d[15 & c])
r = ''.join(e)
return r

def C(self, n, t):
return self.g(self.s(n, t))

def __call__(self, password, token):
return self.C(token, password)
class BASE64:
#js:atob() base64加密
def __init__(self):
self.base64Alpha = 'LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA'

def encode(self, s):
r = []
x = len(s) % 3
if x:
s = s + '\0'*(3 - x)
for i in range(0,len(s),3):
d = s[i:i+3]
a = ord(d[0]) << 16 | ord(d[1]) << 8 | ord(d[2])
r.append(self.base64Alpha[a>>18])
r.append(self.base64Alpha[a>>12 & 63])
r.append(self.base64Alpha[a>>6 & 63])
r.append(self.base64Alpha[a & 63])
if x == 1:
r[-1] = '='
r[-2] = '='
if x == 2:
r[-1] = '='
return ''.join(r)
def getTime():
#取时间 等同于js里面的 Date.toValue()
t = time.time()
return int(round(t * 1000))
callback = 'jQuery{0}_{1}'.format(random.getrandbits(100), getTime())
def get_challenge():
#登录认证第一步
url = 'http://172.19.0.5/cgi-bin/get_challenge'
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Edg/95.0.1020.30'
}
#callback = 'jQuery{0}_{1}'.format(random.getrandbits(100), getTime())
params = {'callback':callback,
'username':username,
'ip':local_ip,
'_':getTime()}
r = requests.get(url, params=params, headers=headers)
#print(r.url)
print(r.text)
data = r.text[len(callback)+1:-1]
token = json.loads(data)['challenge']
return token
def statusTest():
#ping 百度来测试网络是否连通
import subprocess
ret = subprocess.run("ping baidu.com -n 1", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
s1 = str(ret.stdout, encoding='gbk')
return True if "0% 丢失" in s1 else False
def encode_userinfo(userinfo, token):
#对用户信息进行SRBX1
userinfo = json.dumps(userinfo).replace(' ','')
if len(userinfo) == 0:
return ''
sc = len(userinfo)
if sc % 4:
userinfo += '\0' * (4-(sc%4))
sv = []
for i in range(0, sc, 4):
while i >> 2 >= len(sv):
sv.append(0)
sv[i >> 2] = ord(userinfo[i]) | ctypes.c_int32(ord(userinfo[i+1]) << 8).value | \
ctypes.c_int32(ord(userinfo[i+2]) << 16).value | ctypes.c_int32(ord(userinfo[i+3]) << 24).value
sv.append(sc)
v = sv[:]
sc = len(token)
sv = []
for i in range(0, sc, 4):
while i >> 2 >= len(sv):
sv.append(0)
sv[i >> 2] = ord(token[i]) | ctypes.c_int32(ord(token[i+1]) << 8).value | \
ctypes.c_int32(ord(token[i+2]) << 16).value | ctypes.c_int32(ord(token[i+3]) << 24).value
k = sv[:]
while len(k) < 4:
k.append(0)
n = len(v) - 1
z = v[n]
y = v[0]
c = ctypes.c_int32(0x86014019 | 0x183639A0).value
q = math.floor(6 + 52 / (n + 1))
d = 0
m = None
e = None
while q > 0:
d = d + c & (0x8CE0D9BF | 0x731F2640)
d = ctypes.c_int32(d).value
e = (d >> 2 & 0xFFFFFFFF >> 2) & 3
for p in range(n):
y = v[p + 1]
m = (z >> 5 & 0xFFFFFFFF >> 5) ^ ctypes.c_int32(y << 2).value
m += (y >> 3 & 0xFFFFFFFF >> 3) ^ ctypes.c_int32(z << 4).value ^ (d ^ y)
m += k[p & 3 ^ e] ^ z
v[p] = ctypes.c_int32(v[p] + m & (0xEFB8D130 | 0x10472ECF)).value
z = v[p]
y = v[0]
m = (z >> 5 & 0xFFFFFFFF >> 5) ^ ctypes.c_int32(y << 2).value
m += (y >> 3 & 0xFFFFFFFF >> 3) ^ ctypes.c_int32(z << 4).value ^ (d ^ y)
m += k[(p + 1) & 3 ^ e] ^ z
v[n] =ctypes.c_int32(v[n] + m & (0xBB390742 | 0x44C6F8BD)).value
z = v[n]
q -= 1
lv = v[:]
ld = len(lv)
lc = ctypes.c_int32(ld - 1 << 2).value
for i in range(ld):
lv[i] = ''.join([chr(lv[i] & 0xff), chr((lv[i] >> 8 & 0xFFFFFFFF >> 8) & 0xff),
chr((lv[i] >> 16 & 0xFFFFFFFF >> 16) & 0xff),
chr((lv[i] >> 24 & 0xFFFFFFFF >> 24) & 0xff)])
l = ''.join(lv)
base64 = BASE64()
return r'{SRBX1}' + base64.encode(l)

def srun_portal(username,password):
#登录认证第二部
url = 'http://172.19.0.5/cgi-bin/srun_portal'
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.47'
}
#callback = 'jQuery{0}_{1}'.format(random.getrandbits(100),getTime())
md5 = MD5()
token= get_challenge()
hmd5 = md5(password, token)
userinfo = {'username':username,
'password':password,
'ip':local_ip,
'acid':'28',
'enc_ver': 'srun_bx1'
}
info = encode_userinfo(userinfo, token)
chkstr = token+username+token+hmd5+token+'28'+token+local_ip+token+'200'+token+'1'+token+info
sha1 = hashlib.sha1()
sha1.update(chkstr.encode())
chksum = sha1.hexdigest()
params = {'callback':callback,
'action':'login',
'username':username,
'password':r'{MD5}' + hmd5,
'os':'Windows10',
'name':'Windows',
'double_stack':0,
'chksum':chksum,
'info':info,
'ac_id':28,
'ip':local_ip,
'n':200,
'type':1,
'_':getTime()}
r = requests.get(url, params=params, headers=headers)
print(r.text)
if 'Login is successful' in r.text:
print('登录认证成功!')
elif 'ip_already_online' in r.text:
print("您已在线!")
elif statusTest()==True:
print("您虽然不在线但是有网")
else:
print('登陆失败,请检查acid或者密码是否正确')
def main():
if local_ip == '':
print('本机IP获取失败,请检查网络连接')

username=input("用户名")
password=input("密码")
print('登录用户名:{}'.format(username))
print('本机ipv4地址:{}'.format(local_ip))
srun_portal(username,password)

if __name__ == '__main__':
temp=local_ip
while True:
main()
time.sleep(3600)


0x04 实现远程验证

不过这位师傅的源代码,直接 copy 下来大概率是不能用的,需要改网关,还有硬编码的 ac_id 也需要改, 最开始的注释处也多了一个空格的缩进导致是不能开箱即用。

而且我的需求也不是自动登录, 电脑一直自动登录肯定会占用一个设备位导致我的平板或者手机总有一个用不了校园网。

不过重新研究之前的成果,可以思考一下服务端是如何判断是哪台设备正在进行验证的。

然后就会发现,可以直接从 Request 中获取的 ip , 在 GET 参数之中出现了…

大胆猜想,是不是我们通过更改参数中的 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
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
#coding=utf-8
from configparser import ConfigParser
import ctypes
import hashlib
import json
import math
import random
import time
import sys
import re

import requests


conf = ConfigParser()
conf.read("config.ini",encoding='utf-8')
username = conf.get('mysql', 'username')
password = conf.get('mysql', 'password')
gateway = conf.get('mysql', 'gateway')

init_url="http://www.baidu.com/"

class MD5(object):

'''MD5加密//根据js'''
def a(self, n):
r = ''
e = 32 * len(n)
for i in range(0,e,8):
r += chr((n[i >> 5] >> i % 32 & 0xffffffff >> i % 32) & 255)
return r

def d(self, n):
r = [0 for _ in range(len(n) >> 2)]
e = 8 * len(n)
for i in range(0,e,8):
if i >> 5 == len(r):
r.append(0)
r[i >> 5] |= ctypes.c_int32((255 & ord(n[i // 8])) << i % 32).value
return r

def t(self, n, t):
r = (65535 & n) + (65535 & t)
a = ctypes.c_int32(n >> 16).value
b = ctypes.c_int32(t >> 16).value
c = ctypes.c_int32(r >> 16).value
d = ctypes.c_int32(a + b + c << 16).value
return d | 65535 & r

def r(self, n, t):
return ctypes.c_int32(n << t).value | (n >> 32 - t & 0xffffffff >> 32 - t)

def e(self, n, e, o, u, c, f):
return self.t(self.r(self.t(self.t(e, n), self.t(u, f)), c), o)

def o(self, n, t, r, o, u, c, f):
return self.e(t & r | ~t & o, n, t, u, c, f)

def u(self, n, t, r, o, u, c, f):
return self.e(t & o | r & ~o, n, t, u, c, f)

def c(self, n, t, r, o, u, c, f):
return self.e(t ^ r ^ o, n, t, u, c, f)

def f(self, n, t, r, o, u, c, f):
return self.e(r ^ (t | ~o), n, t, u, c, f)

def i(self, n, r):
x = 14 + (r + 64 >> 9 << 4) + 1
n.extend([0 for _ in range(x - len(n))])
n[-1] = r
n[r >> 5] |= ctypes.c_int32(128 << r % 32).value
l = 1732584193
g = -271733879
v = -1732584194
m = 271733878
for j in range(0,len(n),16):
if j + 15 >= len(n):
x = j + 15 - len(n) + 1
for _ in range(x):
n.append(0)
i = l
a = g
d = v
h = m
l = self.o(l, g, v, m, n[j], 7, -680876936)
m = self.o(m, l, g, v, n[j + 1], 12, -389564586)
v = self.o(v, m, l, g, n[j + 2], 17, 606105819)
g = self.o(g, v, m, l, n[j + 3], 22, -1044525330)
l = self.o(l, g, v, m, n[j + 4], 7, -176418897)
m = self.o(m, l, g, v, n[j + 5], 12, 1200080426)
v = self.o(v, m, l, g, n[j + 6], 17, -1473231341)
g = self.o(g, v, m, l, n[j + 7], 22, -45705983)
l = self.o(l, g, v, m, n[j + 8], 7, 1770035416)
m = self.o(m, l, g, v, n[j + 9], 12, -1958414417)
v = self.o(v, m, l, g, n[j + 10], 17, -42063)
g = self.o(g, v, m, l, n[j + 11], 22, -1990404162)
l = self.o(l, g, v, m, n[j + 12], 7, 1804603682)
m = self.o(m, l, g, v, n[j + 13], 12, -40341101)
v = self.o(v, m, l, g, n[j + 14], 17, -1502002290)
g = self.o(g, v, m, l, n[j + 15], 22, 1236535329)
l = self.u(l, g, v, m, n[j + 1], 5, -165796510)
m = self.u(m, l, g, v, n[j + 6], 9, -1069501632)
v = self.u(v, m, l, g, n[j + 11], 14, 643717713)
g = self.u(g, v, m, l, n[j], 20, -373897302)
l = self.u(l, g, v, m, n[j + 5], 5, -701558691)
m = self.u(m, l, g, v, n[j + 10], 9, 38016083)
v = self.u(v, m, l, g, n[j + 15], 14, -660478335)
g = self.u(g, v, m, l, n[j + 4], 20, -405537848)
l = self.u(l, g, v, m, n[j + 9], 5, 568446438)
m = self.u(m, l, g, v, n[j + 14], 9, -1019803690)
v = self.u(v, m, l, g, n[j + 3], 14, -187363961)
g = self.u(g, v, m, l, n[j + 8], 20, 1163531501)
l = self.u(l, g, v, m, n[j + 13], 5, -1444681467)
m = self.u(m, l, g, v, n[j + 2], 9, -51403784)
v = self.u(v, m, l, g, n[j + 7], 14, 1735328473)
g = self.u(g, v, m, l, n[j + 12], 20, -1926607734)
l = self.c(l, g, v, m, n[j + 5], 4, -378558)
m = self.c(m, l, g, v, n[j + 8], 11, -2022574463)
v = self.c(v, m, l, g, n[j + 11], 16, 1839030562)
g = self.c(g, v, m, l, n[j + 14], 23, -35309556)
l = self.c(l, g, v, m, n[j + 1], 4, -1530992060)
m = self.c(m, l, g, v, n[j + 4], 11, 1272893353)
v = self.c(v, m, l, g, n[j + 7], 16, -155497632)
g = self.c(g, v, m, l, n[j + 10], 23, -1094730640)
l = self.c(l, g, v, m, n[j + 13], 4, 681279174)
m = self.c(m, l, g, v, n[j], 11, -358537222)
v = self.c(v, m, l, g, n[j + 3], 16, -722521979)
g = self.c(g, v, m, l, n[j + 6], 23, 76029189)
l = self.c(l, g, v, m, n[j + 9], 4, -640364487)
m = self.c(m, l, g, v, n[j + 12], 11, -421815835)
v = self.c(v, m, l, g, n[j + 15], 16, 530742520)
g = self.c(g, v, m, l, n[j + 2], 23, -995338651)
l = self.f(l, g, v, m, n[j], 6, -198630844)
m = self.f(m, l, g, v, n[j + 7], 10, 1126891415)
v = self.f(v, m, l, g, n[j + 14], 15, -1416354905)
g = self.f(g, v, m, l, n[j + 5], 21, -57434055)
l = self.f(l, g, v, m, n[j + 12], 6, 1700485571)
m = self.f(m, l, g, v, n[j + 3], 10, -1894986606)
v = self.f(v, m, l, g, n[j + 10], 15, -1051523)
g = self.f(g, v, m, l, n[j + 1], 21, -2054922799)
l = self.f(l, g, v, m, n[j + 8], 6, 1873313359)
m = self.f(m, l, g, v, n[j + 15], 10, -30611744)
v = self.f(v, m, l, g, n[j + 6], 15, -1560198380)
g = self.f(g, v, m, l, n[j + 13], 21, 1309151649)
l = self.f(l, g, v, m, n[j + 4], 6, -145523070)
m = self.f(m, l, g, v, n[j + 11], 10, -1120210379)
v = self.f(v, m, l, g, n[j + 2], 15, 718787259)
g = self.f(g, v, m, l, n[j + 9], 21, -343485551)
l = self.t(l, i)
g = self.t(g, a)
v = self.t(v, d)
m = self.t(m, h)
return [l, g, v, m]

def l(self, n, t):
o = self.d(n)
u = [0 for _ in range(16)]
c = [0 for _ in range(16)]
if len(o) > 16:
o = self.i(o, 8 * len(n))
for j in range(16):
u[j] = 909522486 ^ o[j]
c[j] = 1549556828 ^ o[j]
u.extend(self.d(t))
e = self.i(u, 512 + 8 * len(t))
c.extend(e)
r = self.a(self.i(c, 640))
return r

def s(self, n, t):
return self.l(n, t)

def g(self, n):
d = list('0123456789abcdef')
e = []
for c in list(n):
c = ord(c)
e.append(d[c >> 4 & 15])
e.append(d[15 & c])
r = ''.join(e)
return r

def C(self, n, t):
return self.g(self.s(n, t))

def __call__(self, password, token):
return self.C(token, password)
class BASE64:
#js:atob() base64加密
def __init__(self):
self.base64Alpha = 'LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA'

def encode(self, s):
r = []
x = len(s) % 3
if x:
s = s + '\0'*(3 - x)
for i in range(0,len(s),3):
d = s[i:i+3]
a = ord(d[0]) << 16 | ord(d[1]) << 8 | ord(d[2])
r.append(self.base64Alpha[a>>18])
r.append(self.base64Alpha[a>>12 & 63])
r.append(self.base64Alpha[a>>6 & 63])
r.append(self.base64Alpha[a & 63])
if x == 1:
r[-1] = '='
r[-2] = '='
if x == 2:
r[-1] = '='
return ''.join(r)
def getTime():
#取时间 等同于js里面的 Date.toValue()
t = time.time()
return int(round(t * 1000))

callback = 'jQuery{0}_{1}'.format(random.getrandbits(100), getTime())

def get_challenge():
#登录认证第一步
url = f'http://{gateway}/cgi-bin/get_challenge'
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Edg/95.0.1020.30'
}
#callback = 'jQuery{0}_{1}'.format(random.getrandbits(100), getTime())
params = {'callback':callback,
'username':username,
'ip':local_ip,
'_':getTime()}
r = requests.get(url, params=params, headers=headers)

data = r.text[len(callback)+1:-1]
token = json.loads(data)['challenge']

if is_debug_mode :
print("\n[get_challenge request url]: " + r.url)
print("\n[get_challenge request body]: " + str(params))
print("\n[get_challenge response body]: " + r.text)

return token

def statusTest():
#ping 百度来测试网络是否连通
import subprocess
ret = subprocess.run("ping baidu.com -n 1", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
s1 = str(ret.stdout, encoding='gbk')
return True if "丢失 = 0" in s1 else False

def encode_userinfo(userinfo, token):
#对用户信息进行SRBX1
userinfo = json.dumps(userinfo).replace(' ','')
if len(userinfo) == 0:
return ''
sc = len(userinfo)
if sc % 4:
userinfo += '\0' * (4-(sc%4))
sv = []
for i in range(0, sc, 4):
while i >> 2 >= len(sv):
sv.append(0)
sv[i >> 2] = ord(userinfo[i]) | ctypes.c_int32(ord(userinfo[i+1]) << 8).value | \
ctypes.c_int32(ord(userinfo[i+2]) << 16).value | ctypes.c_int32(ord(userinfo[i+3]) << 24).value
sv.append(sc)
v = sv[:]
sc = len(token)
sv = []
for i in range(0, sc, 4):
while i >> 2 >= len(sv):
sv.append(0)
sv[i >> 2] = ord(token[i]) | ctypes.c_int32(ord(token[i+1]) << 8).value | \
ctypes.c_int32(ord(token[i+2]) << 16).value | ctypes.c_int32(ord(token[i+3]) << 24).value
k = sv[:]
while len(k) < 4:
k.append(0)
n = len(v) - 1
z = v[n]
y = v[0]
c = ctypes.c_int32(0x86014019 | 0x183639A0).value
q = math.floor(6 + 52 / (n + 1))
d = 0
m = None
e = None
while q > 0:
d = d + c & (0x8CE0D9BF | 0x731F2640)
d = ctypes.c_int32(d).value
e = (d >> 2 & 0xFFFFFFFF >> 2) & 3
for p in range(n):
y = v[p + 1]
m = (z >> 5 & 0xFFFFFFFF >> 5) ^ ctypes.c_int32(y << 2).value
m += (y >> 3 & 0xFFFFFFFF >> 3) ^ ctypes.c_int32(z << 4).value ^ (d ^ y)
m += k[p & 3 ^ e] ^ z
v[p] = ctypes.c_int32(v[p] + m & (0xEFB8D130 | 0x10472ECF)).value
z = v[p]
y = v[0]
m = (z >> 5 & 0xFFFFFFFF >> 5) ^ ctypes.c_int32(y << 2).value
m += (y >> 3 & 0xFFFFFFFF >> 3) ^ ctypes.c_int32(z << 4).value ^ (d ^ y)
m += k[(p + 1) & 3 ^ e] ^ z
v[n] =ctypes.c_int32(v[n] + m & (0xBB390742 | 0x44C6F8BD)).value
z = v[n]
q -= 1
lv = v[:]
ld = len(lv)
lc = ctypes.c_int32(ld - 1 << 2).value
for i in range(ld):
lv[i] = ''.join([chr(lv[i] & 0xff), chr((lv[i] >> 8 & 0xFFFFFFFF >> 8) & 0xff),
chr((lv[i] >> 16 & 0xFFFFFFFF >> 16) & 0xff),
chr((lv[i] >> 24 & 0xFFFFFFFF >> 24) & 0xff)])
l = ''.join(lv)
base64 = BASE64()
return r'{SRBX1}' + base64.encode(l)

def srun_portal(username,password):
#登录认证第二步
url = f'http://{gateway}/cgi-bin/srun_portal'
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.47'
}
#callback = 'jQuery{0}_{1}'.format(random.getrandbits(100),getTime())
md5 = MD5()
token = get_challenge()
hmd5 = md5(password, token)
userinfo = {'username':username,
'password':password,
'ip':local_ip,
'acid':ac_id,
'enc_ver': 'srun_bx1'
}
info = encode_userinfo(userinfo, token)
chkstr = token + username + token + hmd5 + token + ac_id + token + local_ip + token + '200' + token + '1' + token + info
sha1 = hashlib.sha1()
sha1.update(chkstr.encode())
chksum = sha1.hexdigest()

if is_debug_mode:
print("\n[callback]: " + callback)
print("\n[hmd5]: "r'{MD5}' + hmd5)
print("\n[chksum]: " + chksum)
print("\n[info]: " + info)

params = {'callback':callback,
'action':'login',
'username':username,
'password':r'{MD5}' + hmd5,
'os':'Windows 10',
'name':'Windows',
'double_stack':0,
'chksum':chksum,
'info':info,
'ac_id':ac_id,
'ip':local_ip,
'n':200,
'type':1,
'_':getTime()}

r = requests.get(url, params=params, headers=headers)

if re.search("ServerFlag", r.text) != None :
is_connected = True

else:
is_connected = False

if is_debug_mode :
print("\n[srun_portal request url]: " + r.url)
print("\n[srun_portal request body]: " + str(params))
print("\n[srun_portal response body]: " + r.text)

return is_connected

def main():

global local_ip, ac_id, is_debug_mode

try:
if sys.argv[1] == "--debug": is_debug_mode = True ; print("---Debug mode Enabled.---")

else: is_debug_mode = False ; print()

except:
is_debug_mode = False

print(f"[{time.strftime('%H:%M:%S')}] Username: {username}")

while True:

local_ip = input(f"[{time.strftime('%H:%M:%S')}] Please input the ip: ")
ac_id = input(f"[{time.strftime('%H:%M:%S')}] Please input the ac_id: ")

print(f"[{time.strftime('%H:%M:%S')}] Connecting...")

if srun_portal(username, password):
print(f"[{time.strftime('%H:%M:%S')}] Connect Success.")

else:
print(f"[{time.strftime('%H:%M:%S')}] Something went wrong, use --debug to get more information.")

time.sleep(2)

if __name__ == '__main__':

main()

改的很烂

为了自己用的更加舒服,改了一些交互.

ac_id 暂时需要手动获取,至于自动获取嘛…

已经在 Todo list 里了💦💦💦

使用这个脚本需要在脚本同目录下创建一个 config.ini 文件, 内容大概如下:

1
2
3
4
5
[mysql]
username=替换成你的用户名或者学号什么的
password=你的密码
ac_id=不同区域的 ac_id 是不一样的,错误了会导致连不上
gateway=你的网关,不需要加 https, e.g. 114.514.191.981

文首也提供了 pyinstaller 打包的 windows 版本,开箱改一下 config.ini 即用。


0x05 More…?

不过,这个脚本主要还是提供给移动设备,比如手机使用的。

手机可以通过 Termux 来使用 python .

关于 Termux 设置 python 环境,网上已经有很多教程了, 我也会在后面会出的串流心得中也会详细说设置方法,这里只说一点我踩过的坑:

  • pip 报错很正常,有一些 pip 需要的依赖 Termux 并没有原生自带,需要你去自己用 pkg 装, 这个命令大概可以解决依赖问题 : pkg install openssl pkg-config build-essential

最后嘛…

To Be Continue…