一、前言
因为每天晚上十点钟要求在易班中的一个轻应用进行校区定位打卡,但是只有一个小时,这个时间点作为和舍友快乐五排的时间,总是忘记打卡。因此就想着做一个自动打卡的,挂载在云函数中,也不需要单独的服务器,每天定时运行,岂不是美滋滋。
二、发现问题
首先,这个易班是一个手机APP,但是我在电脑网页端查看了易班官网,发现就算是登录后依然不能进行打卡相关操作,所以只能把目标再次转回到手机APP中,利用手机的HttpCanary对其进行了抓包。但是呢,因为高版本安卓手机的整数问题,打开抓包APP都不能登陆。轻应用在易班中呈现一种站内链接跳转的方式,因此,决定先关闭抓包,进入APP,在直接点击进入打卡的链接跳转,毕竟只用获取到用于post打卡信息的url即可,最后发现先进入所有打卡列表的页面,再进行抓包是可行的。
通过抓包之后,获取到了post打卡信息的url,发现请求头中有一个Authorization的验证,是一个加密信息。
于是对authorization加密信息进行base64的解密,解密完发现这个东西由三部分组成,前两部分只进行了base64加密,第三部分应该是还有别的加密。base64解码后显示是乱码。
看来这条路是行不通的。但是,经过试验,发现了打卡的轻应用在“我的”页面中是可以点击退出登录的,就去点了一下,结果跳出了登录界面,通过复制链接,发现电脑是可以打开这个登陆页面的,因为平时这个都是app内应用,所以是没有登陆的,看不到登录页面。
那么有了登陆页面就很简单了啊,通过浏览器登陆并抓包发现,登陆成功后,有一个Ajax请求的返回信息正好是携带了token,而且跟前面的请求头中的authorration格式是一样的。
发现这个就很简单了,只要先对这个Ajax发起post拿到token,在用这个token取进行打卡就行了。
然后点到post表单,发现登陆的密码是加密的。查看后应该是一个MD5的加密,进行一个小小的逆向就可以拿到加密方法了。
做完这一步骤以后,又发现打卡的时候post的url,后面携带了一个叫做ID的东西。
最后返回去之后,抓包发现获取打卡列表的时候,响应值中打卡列表中每一个打卡都有一个对应的ID,就是打卡的时候post携带的ID。
那么,做完以上的所有操作,就知道了代码实现的流程了,首先利用逆向出的加密方法进行模拟登陆,获取Ajax响应结果中的token,使用token请求获取打卡列表中对应今天的打卡ID,利用获取到的打卡ID进行打卡操作。
三、代码操作流程
用到的库文件
1 import execjs 2 import requests 3 import time 4 from multiprocessing.dummy import Pool
逆向过程就不贴出来了,一个简单的JS调试就可以拿到逆向方法啦。
首先就是进行模拟登陆
1 # 利用逆向得到的js文件对明文密码进行加密,传入明文密码,返回加密密码 2 def encrypted_password(cleartext_passwords): 3 node = execjs.get() 4 ctx = node.compile(open(file='./zhxgPasswordEncrypt.js', mode='r', encoding='utf-8').read()) 5 function_name = 'getpwd' 6 encrypted_password = ctx.call(function_name, cleartext_passwords) 7 return encrypted_password 8 9 10 # 模拟登陆获取token,传入用户名和加密密码,返回token 11 def get_token(UserName, encrypted_password): 12 url = '' 13 headers = { 14 'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Mobile Safari/537.36 Edg/96.0.1054.62', 15 } 16 data = { 17 'Password': encrypted_password, 18 'UserName': UserName, 19 } 20 response = requests.post(url=url, headers=headers, data=data).json() 21 data = response['data'] 22 token = data['Token'] 23 return token
第二步就是进行打卡ID的获取
1 # 获取当天需要打卡的签到ID,传入authorization需要的token,返回signID 2 def get_signID(token): 3 url = '' 4 headers = { 5 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; YAL-AL00 Build/HUAWEIYAL-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 yiban_android/5.0.7', 6 'Authorization': token, 7 'Platform': 'mobile', 8 } 9 response = requests.get(url=url, headers=headers).json() 10 data = response['data'] 11 signID = data[0]['ID'] 12 return signID
第三部当然就是进行打卡啦
1 # 进行签到操作,传入参数signID,token,返回签到信息 2 def sign(signID, token, UserName): 3 url = '' 4 headers = { 5 'Content-Type': 'application/x-www-form-urlencoded', 6 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; YAL-AL00 Build/HUAWEIYAL-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 yiban_android/5.0.7', 7 'Authorization': token, 8 'Platform': 'mobile', 9 } 10 timestamp = time.time() 11 format_time1 = time.strftime('%Y-%m-%d', time.localtime(timestamp)) 12 format_time2 = time.strftime('%H:%M:%S', time.localtime(timestamp)) 13 requests_body = { 14 'jwd': '', 15 'position': '', 16 'signtime': f'{format_time1}\n{format_time2}', 17 'id': signID, 18 } 19 response = requests.post(url=url, headers=headers, data=requests_body).json() 20 if '成功' in response['message']: 21 message = f'{UserName}晚间签到完成-->{format_time1}' 22 else: 23 message = f'{UserName}晚间签到失败-->{format_time1}' 24 return message
最后可以利用server酱进行一个wx推送的通知。
1 # 微信推送,传入参数:要推送的信息,推送信息的key 2 def send_message(message, key): 3 url = 'https://sctapi.ftqq.com/' + key + '.send' 4 data = { 5 'title': message, 6 'desp': message, 7 } 8 requests.post(url=url, params=data)
那一个宿舍的,可以自动打卡总不能不带上舍友吧。所以就带上舍友们咯。再加一个线程池吧!
1 # 完成所有操作。传入用户名和明文密码。 2 def finish(information_dic): 3 UserName = information_dic['UserName'] 4 cleartext_passwords = information_dic['Password'] 5 key = information_dic['key'] 6 # 加密密码 7 enPassword = encrypted_password(cleartext_passwords) 8 # 获取token 9 token = get_token(UserName, enPassword) 10 # 获取signID 11 signID = get_signID(token) 12 # 签到 13 message = sign(signID, token, UserName) 14 # 微信推送 15 send_message(message, key) 16 17 18 def main(*args): 19 pool = Pool(5) 20 pool.map(finish, information_list)
到此,所有的代码编写还有分析过程就结束了,最后测试没问题,直接挂到服务器或者云函数每天就不用自己动手签到了。
最后把整体的代码也贴出来咯。感谢浏览,因为不是正统程序员出身,只是出于对编程的爱好学习的,因此如果代码中不规范的地方还请包涵,也可以提给我,我会虚心学习的。
1 # -*- coding: utf-8 -*- 2 # @Time : 2022/1/6 21:44 3 # @Author : 遠方 4 # @QQ:2560389931 5 # @File : main.py 6 import execjs 7 import requests 8 import time 9 from multiprocessing.dummy import Pool 10 11 # 信息配置 12 information_list = [ 13 # 自己 14 {'UserName': '', 'Password': '', 'key': '', }, 15 # 张三 16 {'UserName': '', 'Password': '', 'key': '', }, 17 # 李四 18 {'UserName': '', 'Password': '', 'key': '', }, 19 # 王麻子 20 {'UserName': '', 'Password': '', 'key': '', }, 21 ] 22 23 24 # 利用逆向得到的js文件对明文密码进行加密,传入明文密码,返回加密密码 25 def encrypted_password(cleartext_passwords): 26 node = execjs.get() 27 ctx = node.compile(open(file='./zhxgPasswordEncrypt.js', mode='r', encoding='utf-8').read()) 28 function_name = 'getpwd' 29 encrypted_password = ctx.call(function_name, cleartext_passwords) 30 return encrypted_password 31 32 33 # 模拟登陆获取token,传入用户名和加密密码,返回token 34 def get_token(UserName, encrypted_password): 35 url = '' 36 headers = { 37 'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Mobile Safari/537.36 Edg/96.0.1054.62', 38 } 39 data = { 40 'Password': encrypted_password, 41 'UserName': UserName, 42 } 43 response = requests.post(url=url, headers=headers, data=data).json() 44 data = response['data'] 45 token = data['Token'] 46 return token 47 48 49 # 获取当天需要打卡的签到ID,传入authorization需要的token,返回signID 50 def get_signID(token): 51 url = '' 52 headers = { 53 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; YAL-AL00 Build/HUAWEIYAL-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 yiban_android/5.0.7', 54 'Authorization': token, 55 'Platform': 'mobile', 56 } 57 response = requests.get(url=url, headers=headers).json() 58 data = response['data'] 59 signID = data[0]['ID'] 60 return signID 61 62 63 # 进行签到操作,传入参数signID,token,返回签到信息 64 def sign(signID, token, UserName): 65 url = '' 66 headers = { 67 'Content-Type': 'application/x-www-form-urlencoded', 68 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; YAL-AL00 Build/HUAWEIYAL-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 yiban_android/5.0.7', 69 'Authorization': token, 70 'Platform': 'mobile', 71 } 72 timestamp = time.time() 73 format_time1 = time.strftime('%Y-%m-%d', time.localtime(timestamp)) 74 format_time2 = time.strftime('%H:%M:%S', time.localtime(timestamp)) 75 requests_body = { 76 'jwd': '', 77 'position': '', 78 'signtime': f'{format_time1}\n{format_time2}', 79 'id': signID, 80 } 81 response = requests.post(url=url, headers=headers, data=requests_body).json() 82 if '成功' in response['message']: 83 message = f'{UserName}晚间签到完成-->{format_time1}' 84 else: 85 message = f'{UserName}晚间签到失败-->{format_time1}' 86 return message 87 88 89 # 微信推送,传入参数:要推送的信息,推送信息的key 90 def send_message(message, key): 91 url = 'https://sctapi.ftqq.com/' + key + '.send' 92 data = { 93 'title': message, 94 'desp': message, 95 } 96 requests.post(url=url, params=data) 97 98 99 # 完成所有操作。传入用户名和明文密码。 100 def finish(information_dic): 101 UserName = information_dic['UserName'] 102 cleartext_passwords = information_dic['Password'] 103 key = information_dic['key'] 104 # 加密密码 105 enPassword = encrypted_password(cleartext_passwords) 106 # 获取token 107 token = get_token(UserName, enPassword) 108 # 获取signID 109 signID = get_signID(token) 110 # 签到 111 message = sign(signID, token, UserName) 112 # 微信推送 113 send_message(message, key) 114 115 116 def main(*args): 117 pool = Pool(5) 118 pool.map(finish, information_list) 119 120 121 if __name__ == '__main__': 122 main()