树莓派校园网自动拨号

学校的移动宽带每天断网后第二天又需要打开网页登录,既然网页登录那么应该是可以模拟登录的,实际意义不大,但是我今天试了试。

1、抓包分析网页拨号过程

Chrome打开网页,按下F12,填入用户名(test)、密码(test)。点击移动有线登录。然后在控制可以看到如下:
POST连接

可以得出如下结论:


  • 数据是POST提交的
  • 提交的数据以from表单的形式传到到后台

再看看POST提交的数据:
POST-DATA

可以得出如下结论:


  • username和password是我们填入的,显然password是经过加密的
  • ym的值是@cmccgx 代表我们是登录移动有线宽带

由此我产生了一个疑问,user_mac是空的那个后台如何得到我的路由器的mac呢?
cmd tracert 一下那个post请求的IP地址:
tracert

1
2
猜想:
我登录提交的服务器相当于我的路由器的网关,http是应用层的协议,而提交数据的服务器可以通过网络层的ARP协议得到我的路由器的MAC地址,倘若我输入的用户名密码正确,那么将我的路由器的MAC添加到一个已认证名单之类的表里面,然后返回数据告诉我登录成功,此后我的路由器发出的请求都会在网关服务器根据ARP协议得到发出者的MAC地址,如果已认证那么放出去,没认证的MAC地址就拦截。这也是为什么我登录之后不管路由器接多少设备都不会掉线的原因,比如手机 笔记本的http请求由路由器转发到学校网关时,源MAC已经是路由器的MAC了。

查看返回数据:
返回数据

提示我已经在线是因为我已经登录过了

2.模拟登录

1.刚才提到密码是被加密过的,其实很显然是Base64加密
2.我用Python写一个超级简单的POST按照抓包提示的内容提交数据就行

思路:

  1. 拨号之前判断网络能不能用,方法:随便get请求一下某个不会挂的网址比如百度之类的,如果responce.getcode()是200那说明当前路由器是可以访问外网的,也就不用拨号了
  2. 如果responce.getcode()不是200 那说明网络没接通,应该拨号,但是晚上12点到早上6点之间断网的拨号没有意义。所以还应该判断一下时间
  3. !实际测试发现,在路由器不能访问外网的情况下,1会302跳转到登录界面,所以应该防止302跳转
    4.

代码

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
# coding=UTF-8
from urllib import request
from urllib import parse
import base64
import time
import os
import logging

class MyRedirectHandler(request.HTTPRedirectHandler):
def http_error_302(self, req, fp, code, msg, hdrs):
return fp

class Data:
headers = {
"Accept": "*/*",
"Content-Type": "application/x-www-form-urlencoded",
"DNT": "1",
"Origin": "xxxxxxxxx",
"Referer": "http://xxxxxxxxx/xxx_portal_pc.php?ac_id=2&",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"
}
postData =" "

def setPostData(self,phoneNum,passWord):
self.postData=parse.urlencode(
{
"action": "login",
"username": phoneNum,
"ym": "@cmccgx",
"password": "{B}" + str(base64.b64encode(passWord.encode('utf-8')), 'utf-8'),
"ac_id": "2",
"user_ip": "",
"nas_ip": "",
"user_mac": "",
"save_me": "0",
"ajax": "1"
}
).encode('utf-8')

class LogUtil:
log_path=os.path.join(os.getcwd(), 'xxxx_log.txt')
logging.basicConfig(filename=log_path, level=logging.DEBUG)

def getDate(self):
return str(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))+":"
def Log(self,text):
self.deleteLog()#防止日志文件过大
logging.debug(self.getDate() + text)
def deleteLog(self):
if os.path.getsize(self.log_path)/float(1024) > 50: #日志文件大于50K就删除
os.remove(self.log_path)

class NetUtil:
logUtil=LogUtil()

def Login(self):
data = Data()
data.setPostData("手机号", "宽带密码")
url = "http://xxxxxxxxx/include/auth_action_ori.php"
req = request.Request(method="POST", url=url, data=data.postData, headers=data.headers)
try:
resp = request.urlopen(req)
except IOError as e:
self.logUtil.Log("网络异常")
return False
if resp.getcode() == 200:
if "login_ok" in resp.read().decode("utf8"):
self.logUtil.Log("自动登陆成功")
else:
self.logUtil.Log("自动登陆失败:" + resp.read().decode("utf8"))
return True
else:
self.logUtil.Log( "自动登陆失败:" + str(resp.getcode()))
return False

def Ping(self):
url = "http://txt.go.sohu.com/ip/soip"
myHandler = MyRedirectHandler()
opener = request.build_opener(myHandler)
req = request.Request(method="GET", url=url)
try:
resp = opener.open(req)
except IOError as e:
self.logUtil.Log("网络异常")
return False
if resp.getcode() != 200:
return False
return True

def main():
date=time.strftime('%H', time.localtime(time.time()))
netUtil=NetUtil()
logUtil=LogUtil()
if int(date) < 6 :
return
if netUtil.Ping() == True:
logUtil.Log("已链接网路")
return
netUtil.Login()

main()

将该xxx.py(xxx是自定义的脚本名字)扔到树莓派的用户目录下,需要设置一个定时任务,让树莓派每隔三分钟执行一次脚本,
连接终端,输入:

1
crontab -e

在文件底部添加一行

1
*/3 * * * *  python /home/pi/xxx.py #三分钟运行一次脚本

然后保存退出

1
systemctl restert cron.service #重启cron服务