长安大学吧 关注:348,444贴子:12,374,123

校园网进阶使用指南

只看楼主收藏回复

众所周知,我们学校的校园网,正常使用途径只有一个,即为连接SSID名为CHD-WIFI的无线网络。但两个月使用下来有如下几个问题:1.Win10系统连接Wifi不稳定,经常自动断开连接,或与公网联通却显示小地球图标。2.CHD-WIFI连接费时,并且容易掉验证。3.局域网IP不固定4.对多个设备兼容不好。
于是我就购入了一台路由器,型号小米wr30u,用来在校园网环境下搭建一个小型私人局域网,由路由器连接校园网,作为上游网络(校园网)与私人局域网的网关。这样做有几个好处。1.我的终端设备,包括电脑、rasppi等等都可以有一个固定局域网ip,不用每次ssh都要ifconfig查ip。2.该局域网内部可以实现超高速互联,适合部署nas或者网络影院等等。3.路由器可以设置定时任务自动登录校园网,防止掉线。路由器提前刷好了openwrt,测试下来路由器如果直接通过无线网卡连接校园网的wifi,是可以正常通过dhcp获取到校园网ip(10...),但如果按照这样操作,本质上还是通过无线信号接入校园网,稳定性无法保障,而且双频路由器还要废掉一个频段,所以果断放弃,转而研究有线接入校园网的方式。
首先明确一个目标,就是让路由器接入校园网。我的目的是路由器ssh执行ifconfig时能看到wan侧ip为10.81.*.*。
我住东区9号楼宿舍,房间内墙壁上有一个网口,还有门框上面的一个猫。先测试墙上的网口,连接电脑一点反应没有,推测是已经弃用,于是用网线连接电脑和猫的lan口。通过ipconfig发现笔记本自动获取的ip处于192.168的网段之中,识别到的网络名称是CMCC而不是CHD-Wifi,访问网关192.168.1.1会跳转到光猫的配置页面。通过猫背面的用户名密码可以登录后台,但显然只有普通用户权限,几乎什么都不能做。但可以查看终端(猫)下挂设备。大概是这样:

这里我通过wifi连接CHD-Wifi,本质上是连接了位于宿舍内的校园网接入点。每个宿舍门框上的猫都是一个无线ap。如果你扫描过宿舍楼的wifi信号,就会明白是怎么回事。

当然,如果你直接连接CHD-Wifi,dhcp广播会自动给你分配a类大网ip,我这里手动设置了ip 192.168.1.100,网关192.168.1.1,才可以进入猫的配置页面。
然后问题出在这里,虽然终端下挂设备中连接LAN和WLAN的设备是并列的,但它们无法互通数据。这就意味着通过网线直接连接猫的设备,只局限在猫内部的小局域网,而无法和校园网通信。看了吧内的“校园网使用教程”,我尝试使用拨号方式连接校园网,但一直返回651错误无法连接。
这时我推测猫内部对不同接口划分了vlan(虚拟局域网),将校园网网关(猫的上游设备)和通过其SSID接口连接的设备划分到一个子网中,其余的LAN设备处于默认子网中,所以无法互通数据。但问题是用户权限太低,无法修改vlan配置,于是斥巨资找淘宝查询光猫超级密码,成功登录超级管理员账号。在“上行链路”配置项中,找到一个名称为 OTHER_B_VID_2109 的连接,发现其绑定SSID5接口,而SSID5设备是唯一能接入校园网的设备,于是断定此连接就是和上级校园网相通的连接。勾选上LAN1(网线插在光猫LAN1接口),再查看下挂设备信息,LAN设备已经成功在校园大网中广播dhcp请求,并获取到校园网ip。与此同时,以太网适配器一栏也已经成功识别出CHD-Wifi。以下是连接无线网和以太网的对比。可以看到跃点数相差一个,以太网比无线网更加稳定。



通过ping测试出设备与深蓝计费系统的连接正常。接下来只需要写一个python自动登录脚本,放在路由器上定时执行,就可以实现7*24小时不掉线。

以下是软件层面上的分析。f12抓包,登录接口的参数有点多,而且还有chksum和时间戳校验,所以定时curl肯定是不可以的。好在该请求的触发器portal.js没有加密,我们可以直接重现其认证logic。以下是这个函数ajax的提交代码。实际上portal.js中有两段差不多的ajax代码,可以通过下断点找出实际执行的是哪一段ajax。
params: {
action: 'login',
username: username,
password: _this.userInfo.otp ? '{OTP}' + password : '{MD5}' + hmd5,
os: _this.portalInfo.userDevice.device,
name: _this.portalInfo.userDevice.platform,
double_stack: _this.portalInfo.doub && !host ? 1 : 0,
chksum: sha1(str),
info: i,
ac_id: ac_id,
ip: ip,
n: n,
type: type
}
此处password可以看出有两种加密方式,一个otp,一个md5 HASH,但由于我抓包时网页按照md5加密提交,我这里就偷个懒,不实现otp了。
接下来我们一个一个实现参数的生成。
Callback:就是一个回调函数,通过eval返回的js直接实现用户端的信息更新。action: loginusername: 学号。password: hmd5(密码,token)。os: Linux。name: Linux。double_stack: 0(双栈认证,默认为0)。chksum: 多参数文本拼接后进行sha1哈希运算。
info:_encodeUserInfo,这里base64的元字符被重定义了,不知道开发者在想什么。encode函数中有明显的溢出漏洞,纯垃圾代码。这里由于python中整数表示法和js的不一样(后者为32位整数),在某些加密过程中js中数据发生溢出,但python中的数据未溢出,就会造成偏差。当然,我也尝试过使用execjs,可惜结果不正确,而且每次初始化compile的时间太长。

还有一些参数就是固定写死在js里的。
var type = 1;
var n = 200;
var enc = 'srun_bx1'; // 用户信息
token是从一个get-challenge的方法中获取,这个方法不需要鉴权,所以直接请求即可。最后一个_就是时间戳。于是就可以写出如下Python脚本生成ajax Url。(源码有点长放在二楼)
我之所以写这个脚本是因为我的校园网接入设备(路由器)类似服务器,需要时刻监控运行状态。但理论上来说,个人windows电脑也可以使用此脚本,但没有什么意义。以下是最终接线完成的硬件。



IP属地:陕西1楼2023-12-06 01:40回复
    该源码在 https://github.com/myxewd/CHD_Wifi_login 开源,后续如果有bug也是在这里更新。下面的代码仅供参考。不保证稳定性。
    import time
    import re
    import json
    import socket
    import hashlib
    from urllib.parse import urlencode, urlunparse
    import requests
    authServer = "172.16.16.5"
    username = ""
    password = ""
    def get_time():
    current_time_seconds = time.time()
    timestamp = int(current_time_seconds * 1000)
    return timestamp
    def get_wan_ip():
    hostname = socket.gethostname()
    ip_address = socket.gethostbyname(hostname)
    return ip_address
    def hmd5(password, token):
    combined_str = password + token
    hashed = hashlib.md5(combined_str.encode()).hexdigest()
    return hashed
    def get_token():
    url = f"http://{authServer}/cgi-bin/get_challenge?callback=yiyin&username={username}&ip={get_wan_ip()}&_={get_time()}"
    try:
    response = requests.get(url)
    if response.status_code == 200:
    pattern = r'"challenge":"(.*?)"'
    match = re.search(pattern, response.text)
    if match:
    challenge_value = match.group(1)
    return challenge_value
    else:
    print("bad_format")
    else:
    print(f"ajax_error {response.status_code}")
    except requests.RequestException as e:
    print(f"request_error {e}")
    def base64_encode(input_string):
    base64_chars = "LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA"
    def encode_block(block):
    indices = [block >> 18 & 63, block >> 12 & 63, block >> 6 & 63, block & 63]
    return "".join(base64_chars[i] for i in indices)
    byte_string = input_string.encode('latin_1')
    encoded = ""
    for i in range(0, len(byte_string), 3):
    block = (byte_string[i] << 16) + (byte_string[i + 1] << 8) + byte_string[i + 2] if i + 2 < len(byte_string) else 0
    encoded += encode_block(block)
    padding = len(byte_string) % 3
    if padding == 1:
    encoded = encoded[:-2] + "=="
    elif padding == 2:
    encoded = encoded[:-1] + "="
    return encoded
    #Dword
    def int32(value):
    mask = 0xFFFFFFFF
    if value & (1 << 31):
    return -((~value + 1) & mask)
    else:
    return value & mask
    def s(a, b):
    c = len(a)
    v = []
    for i in range(0, c, 4):
    val = 0
    for j in range(4):
    if i + j < c:
    val |= ord(a[i + j]) << (8 * j)
    v.append(int32(val))
    if b:
    v.append(int32(c))
    return v
    def l(a, b):
    d = len(a)
    c = (d - 1) << 2
    if b:
    m = a[d - 1]
    if m < c - 3 or m > c:
    return None
    c = m
    a = ["".join([
    chr(x & 0xff),
    chr((x >> 8) & 0xff),
    chr((x >> 16) & 0xff),
    chr((x >> 24) & 0xff),
    ]) for x in a]
    return "".join(a)[:c] if b else "".join(a)
    def encode(str, key):
    v = s(str, True)
    k = s(key, False)
    if len(k) < 4:
    k += [0] * (4 - len(k))
    n = len(v) - 1
    z = v[n]
    y = v[0]
    c = 0x86014019 | 0x183639A0
    m = 0
    e = 0
    p = 0
    q = int(6 + 52 / (n + 1))
    d = 0
    #js <<<
    def unsigned_right_shift(n, bits):
    binary = bin(n & 0xFFFFFFFF)[2:].zfill(32)
    shifted = '0' * bits + binary[:-bits]
    return int(shifted, 2)
    while q > 0:
    d = int32((d + c) & (0x8CE0D9BF | 0x731F2640))
    e = unsigned_right_shift(d, 2) & 3
    for p in range(n):
    y = v[p + 1]
    m = unsigned_right_shift(z, 5) ^ (y << 2)
    m += unsigned_right_shift(y, 3) ^ (z << 4) ^ (d ^ y)
    m += k[p & 3 ^ e] ^ z
    m = int32(m)
    z = int32((v[p] + m) & (0xEFB8D130 | 0x10472ECF))
    v[p] = z
    p = p + 1
    y = v[0]
    m = unsigned_right_shift(z, 5) ^ (y << 2)
    m += unsigned_right_shift(y, 3) ^ (z << 4) ^ (d ^ y)
    m = int32(m)
    m += k[p & 3 ^ e] ^ z
    m = int32(m)
    z = int32((v[n] + m) & (0xBB390742 | 0x44C6F8BD))
    v[n] = z
    print(v)
    q -= 1
    return l(v, False)
    def encode_user_info(info, token):
    return '{SRBX1}' + base64_encode(encode(info,token))
    def login(username, password):
    type = 1
    n = 200
    enc = "srun_bx1"
    wan_ip = get_wan_ip()
    timestamp = get_time()
    token = get_token()
    callback = f"yiyin_{timestamp}"
    action = "login"
    hpmd5 = hmd5(password, token)
    os = "Linux"
    name = "Linux"
    #double-stack = 1
    user_data = {
    "username": username,
    "password": password,
    "ip": wan_ip,
    "acid": "1",
    "enc_ver": enc
    }
    user_data_str = json.dumps(user_data, separators=(',', ':'))
    i = encode_user_info(user_data_str,token)
    cstr = (
    str(token) + str(username) +
    str(token) + str(hpmd5) +
    str(token) + str(1) +
    str(token) + str(wan_ip) +
    str(token) + str(n) +
    str(token) + str(type) +
    str(token) + str(i)
    )
    cstr = hashlib.sha1(cstr.encode()).hexdigest()
    base_url = f"http://{authServer}/cgi-bin/srun_portal"
    params = {
    'callback': callback,
    'action': action,
    'username': username,
    'password': '{MD5}' + hpmd5,
    'os': os,
    'name': name,
    'double_stack': 0,
    'chksum': cstr,
    'info': i,
    'ac_id': 1,
    'ip': wan_ip,
    'n': n,
    'type': type,
    '_': timestamp
    }
    url = base_url + '?' + urlencode(params)
    requests.get(url)
    return
    if __name__ == "__main__":
    #check_if_login
    url = f'http://{authServer}/cgi-bin/rad_user_info?callback=yiyin_{get_time()}&_={get_time()}'
    response = requests.get(url)
    if response.status_code == 200:
    json_data = response.text.split('(', 1)[1].rsplit(')', 1)[0]
    parsed_data = json.loads(json_data)
    user_name = parsed_data.get('user_name')
    else:
    print("rad_user_info error")
    exit()
    if user_name != username:
    login(username,password)
    else:
    print("already_login")


    IP属地:陕西2楼2023-12-06 01:42
    回复
      2025-08-25 21:08:47
      广告
      不感兴趣
      开通SVIP免广告
      挺搞的,本来之前一个拨号就能解决的问题,被学校改网改的这么麻烦


      IP属地:湖北来自Android客户端3楼2023-12-06 08:15
      回复
        好复杂,氵个经验再看


        IP属地:内蒙古来自Android客户端4楼2023-12-06 08:23
        回复
          大佬


          IP属地:陕西来自Android客户端5楼2023-12-06 09:37
          回复


            通过百度相册上传6楼2023-12-06 09:41
            回复


              IP属地:安徽来自Android客户端7楼2023-12-06 10:31
              回复
                我进错地方了?


                IP属地:江西来自iPhone客户端8楼2023-12-06 10:41
                回复
                  2025-08-25 21:02:47
                  广告
                  不感兴趣
                  开通SVIP免广告


                  IP属地:广东来自Android客户端9楼2023-12-06 11:10
                  回复
                    看不懂


                    IP属地:浙江来自Android客户端10楼2023-12-06 11:14
                    回复


                      IP属地:陕西来自Android客户端11楼2023-12-06 11:19
                      回复
                        牛大了


                        IP属地:甘肃来自Android客户端12楼2023-12-06 11:41
                        回复
                          太长不看


                          IP属地:河南来自Android客户端14楼2023-12-06 12:06
                          回复


                            IP属地:四川来自iPhone客户端15楼2023-12-06 12:32
                            回复
                              2025-08-25 20:56:47
                              广告
                              不感兴趣
                              开通SVIP免广告
                              🐮


                              IP属地:吉林来自Android客户端16楼2023-12-06 13:05
                              回复