全世界最(不)好的目录

  • 一、需求分析
  • 二、实现过程
    • 2.1 分析api
      • 2.1.1 连接到校园网,自动弹出登录认证界面
      • 2.1.2 先输入错误的账号密码,按F12看会获取哪些信息
      • 2.1.3 api
    • 2.2 分析加密
    • 2.3 流程总结
  • 三.模拟登录
    • 3.1 编写配置文件
    • 3.2.功能封装与定时
  • 四.题外话

一、需求分析

指路:https://www.srun.com/cn/list/info?id=44

Srun4K 校园网解决方案,这群人自以为自己的系统搞得很好,可以帮助使用者偷懒的管理网络,但是对于用户端其实每天充斥着断网、卡顿、认证不跳等诸多问题,所以写个客户端便捷的进行断网重连。

二、实现过程

这里本来不想写的,但是某人要求教教他抓包,我就不点名了
由于人在公司,所以截图部分等我想弄的时候再弄吧

2.1 分析api

2.1.1 连接到校园网,自动弹出登录认证界面

http://172.19.0.5/srun_portal_success?ac_id=28&theme=pro&wlanacname=&wlanuserip=10.161.21.150

2.1.2 先输入错误的账号密码,按F12看会获取哪些信息

2.1.3 api

get_challenge
请求方式为get,地址为
http://172.19.0.5/cgi-bin/get_challenge

请求的数据为

参数分析
callbackjsonp解决跨域的参数
username校园网账户
ip本机自动获取到的ip
_当前时间戳(32)位

这里是不需要分析的。唯一需要注意的是challenge,因为太过诡异。


srun_portal
请求方式为get,地址为
http://172.19.0.5/cgi-bin/srun_portal
请求数据为

看到这一大串,可能不太好下手。确实参数有点多。
多观察几次,先找不变的参数,排除掉。

参数分析
callback和第一次的callback意思一样,排除
actionlogin
username账户
ac_id1(意义不详,每次都一样可以排除了)
ip自动获取的ip
n200(和ac_id一样排除)
type1(和ac_id一样排除)
oswindows10
namewindows
double_stack0
_当前时间戳(13位)

一共14个参数,一下子pass11个,还剩下3个参数分别是 password chksum info
接下来开始分析js文件,一共4个
搜索关键字:password

这个是什么,这个不就是我们请求的参数吗,一下全都出来了,继续搜索对应关键字。

hmd5就找出来了,是经过md5加密之后的密码,还做过加盐处理
至于token就是之前的challenge

接下来是chksum一样的方法,搜索关键字。

可以发现是简单的字符串拼接。然后用sha1加密得出chksum。
可是还有个i是什么呢。继续找吧

都出来了,对象转为字符串之后进行xEncode(天知道是什么加密方式)进行加密,反正也是加盐的加密。也用到了token。之后再进行一次base64加密。i就出来了
i出来了,chksum也出来了。顺序不能错。
处理顺序

参数加密方式
passwordHmac MD5加密
chksumsha1处理
3个参数都需要用到token,至于加密方式更加是坑
只有sha1和md5是可以直接用的。
xencode更加不知道是什么鬼东西,base64是经过改动的。和正常的base64是不一样的。

2.2 分析加密

python版的xencode

import mathdef force(msg):ret = []for w in msg:ret.append(ord(w))return bytes(ret)def ordat(msg, idx):if len(msg) > idx:return ord(msg[idx])return 0def sencode(msg, key):l = len(msg)pwd = []for i in range(0, l, 4):pwd.append(ordat(msg, i) | ordat(msg, i + 1) << 8 | ordat(msg, i + 2) << 16| ordat(msg, i + 3) << 24)if key:pwd.append(l)return pwddef lencode(msg, key):l = len(msg)ll = (l - 1) << 2if key:m = msg[l - 1]if m < ll - 3 or m > ll:returnll = mfor i in range(0, l):msg[i] = chr(msg[i] & 0xff) + chr(msg[i] >> 8 & 0xff) + chr(msg[i] >> 16 & 0xff) + chr(msg[i] >> 24 & 0xff)if key:return "".join(msg)[0:ll]return "".join(msg)def get_xencode(msg, key):if msg == "":return ""pwd = sencode(msg, True)pwdk = sencode(key, False)if len(pwdk) < 4:pwdk = pwdk + [0] * (4 - len(pwdk))n = len(pwd) - 1z = pwd[n]y = pwd[0]c = 0x86014019 | 0x183639A0m = 0e = 0p = 0q = math.floor(6 + 52 / (n + 1))d = 0while 0 < q:d = d + c & (0x8CE0D9BF | 0x731F2640)e = d >> 2 & 3p = 0while p < n:y = pwd[p + 1]m = z >> 5 ^ y << 2m = m + ((y >> 3 ^ z << 4) ^ (d ^ y))m = m + (pwdk[(p & 3) ^ e] ^ z)pwd[p] = pwd[p] + m & (0xEFB8D130 | 0x10472ECF)z = pwd[p]p = p + 1y = pwd[0]m = z >> 5 ^ y << 2m = m + ((y >> 3 ^ z << 4) ^ (d ^ y))m = m + (pwdk[(p & 3) ^ e] ^ z)pwd[n] = pwd[n] + m & (0xBB390742 | 0x44C6F8BD)z = pwd[n]q = q - 1return lencode(pwd, False)

python版base64

_PADCHAR = "="_ALPHA = "LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA"def _getbyte(s, i):x = ord(s[i]);if (x > 255):print("INVALID_CHARACTER_ERR: DOM Exception 5")exit(0)return xdef get_base64(s):i=0b10=0x = []imax = len(s) - len(s) % 3;if len(s) == 0:return sfor i in range(0,imax,3):b10 = (_getbyte(s, i) << 16) | (_getbyte(s, i + 1) << 8) | _getbyte(s, i + 2);x.append(_ALPHA[(b10 >> 18)]);x.append(_ALPHA[((b10 >> 12) & 63)]);x.append(_ALPHA[((b10 >> 6) & 63)]);x.append(_ALPHA[(b10 & 63)])i=imaxif len(s) - imax ==1:b10 = _getbyte(s, i) << 16;x.append(_ALPHA[(b10 >> 18)] + _ALPHA[((b10 >> 12) & 63)] + _PADCHAR + _PADCHAR);else:b10 = (_getbyte(s, i) << 16) | (_getbyte(s, i + 1) << 8);x.append(_ALPHA[(b10 >> 18)] + _ALPHA[((b10 >> 12) & 63)] + _ALPHA[((b10 >> 6) & 63)] + _PADCHAR);return "".join(x)

md5

import hmacimport hashlibdef get_md5(password,token):return hmac.new(token.encode(), password.encode(), hashlib.md5).hexdigest()

sha1

import hmacimport hashlibdef get_md5(password,token):return hmac.new(token.encode(), password.encode(), hashlib.md5).hexdigest()

2.3 流程总结

第一次get_challenge是获取token。
中间做了3个信息的处理
最后一步就是登录和认证了

三.模拟登录

完整代码如下:

#coding=utf-8import ctypesimport hashlibimport jsonimport mathimport randomimport socketimport timeimport requestsconf = 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 ipdef GetLocalIPByPrefix(prefix):""" 多网卡情况下,根据前缀获取IP(Windows 下适用) """localIP = ''for ip in socket.gethostbyname_ex(socket.gethostname())[2]:if ip.startswith(prefix):localIP = ipreturn localIPlocal_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 rdef 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).valuereturn rdef t(self, n, t):r = (65535 & n) + (65535 & t)a = ctypes.c_int32(n >> 16).valueb = ctypes.c_int32(t >> 16).valuec = ctypes.c_int32(r >> 16).valued = ctypes.c_int32(a + b + c << 16).valuereturn d | 65535 & rdef 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) + 1n.extend([0 for _ in range(x - len(n))])n[-1] = rn[r >> 5] |= ctypes.c_int32(128 << r % 32).valuel = 1732584193g = -271733879v = -1732584194m = 271733878for j in range(0,len(n),16):if j + 15 >= len(n):x = j + 15 - len(n) + 1for _ in range(x):n.append(0)i = la = gd = vh = ml = 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 rdef 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 rdef 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) % 3if 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 tokendef statusTest():#ping 百度来测试网络是否连通import subprocessret = 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 Falsedef encode_userinfo(userinfo, token):#对用户信息进行SRBX1userinfo = 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).valuesv.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).valuek = sv[:]while len(k) < 4:k.append(0)n = len(v) - 1z = v[n]y = v[0]c = ctypes.c_int32(0x86014019 | 0x183639A0).valueq = math.floor(6 + 52 / (n + 1))d = 0m = Nonee = Nonewhile q > 0:d = d + c & (0x8CE0D9BF | 0x731F2640)d = ctypes.c_int32(d).valuee = (d >> 2 & 0xFFFFFFFF >> 2) & 3for p in range(n):y = v[p + 1]m = (z >> 5 & 0xFFFFFFFF >> 5) ^ ctypes.c_int32(y << 2).valuem += (y >> 3 & 0xFFFFFFFF >> 3) ^ ctypes.c_int32(z << 4).value ^ (d ^ y)m += k[p & 3 ^ e] ^ zv[p] = ctypes.c_int32(v[p] + m & (0xEFB8D130 | 0x10472ECF)).valuez = v[p]y = v[0]m = (z >> 5 & 0xFFFFFFFF >> 5) ^ ctypes.c_int32(y << 2).valuem += (y >> 3 & 0xFFFFFFFF >> 3) ^ ctypes.c_int32(z << 4).value ^ (d ^ y)m += k[(p + 1) & 3 ^ e] ^ zv[n] =ctypes.c_int32(v[n] + m & (0xBB390742 | 0x44C6F8BD)).valuez = v[n]q -= 1lv = v[:]ld = len(lv)lc = ctypes.c_int32(ld - 1 << 2).valuefor 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+infosha1 = 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_ipwhile True:main()time.sleep(3600)

3.1 编写配置文件

配置文件如下:
1.config

[mysql]username=password=

3.2.功能封装与定时

完成上面的步骤之后我们就可以开始对登陆代码进行封装了,然后定时一小时重新登陆一次即可。

四.题外话


校园网安全认证和提高信息技术水平有个毛关系?要么是为了限制设备数圈钱(说这的那的,网费和居民区一样贵服务还差),要么就是…
总之没见过这么对付的商业计划书。