破解易班校本化签到
原起 —— 为什么对易班晚签下手
学校还在用易班进行晚点名,每天晚上9点都得在指定范围内签到
有时候一忙就给忘了绝对不是打游戏,忘签还要扣分,原信工系的我肯定要找一点解放双手的方法出来
先在全世界最大的同性交友网站上搜索,自动脚本结果全都是疫情期间上报健康情况的脚本,且都是好几年前根本没法使用。遂开始自行编写脚本之旅
过程 —— 分析如何实现目标
在吾爱上混迹了好些年,自己也跟着破解使用了不少app,对app破解也有一定经验,以下是详细分析过程:
第一步 – 获取签到数据
获取签到秘钥
根据同性交友网站上的指点,易班使用RSA算法对用户密码进行加密,现有方法里RSA秘钥都已经失效,无法签到成功(反正我无法成功登录),那么第一步就应该从apk内获取到RSA秘钥,
这里我使用算法助手拦截应用内所有加密算法
通过搜索自己登陆的明文密码,看一下哪有调用
搜索结果里有2处调用,但RSA秘钥都是一样的,同时也获取到加密加密方式,使用小黄鸟抓包获取签到所需参数
利用Python模拟登陆:
# -*- coding: utf-8 -*-
import requests
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
import base64
def encryptPassword(pwd):
# 密码加密
PUBLIC_KEY = '''-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzq0rgsM++ZxLRGHpdfre
Hu6UXhdlUS5P2WOxRG14qU8/iWSb/CkOqgOl8AGcOhlthkvolCdpUvVcVsVUxBv0
YRN0Jb64zPrn5aLVwQT4RJn5tXvoqLdHIXis7pljXAMDPVZOVlWJkDMk8YU6HDaA
MqsD6l5p9lg2LMP4OhMgaPX+CkO370LB5vRjJTHp03n+IqfxXoC7DEd+kxRIEM2C
EDgUSYDJBDgwBvGALZmvB/a1b0im9t1P/EmnuE7uN9NRFoWyVpOiEwo/Ti7rmJGf
qNT3vvtfWo4nXsm1rYQXsPayoKDSRaba3gFY/1SYWLAuSO2q2da5ZCcsAk5RKy0V
c1hUg8n6y0YLAvuzoXY5VyNMXkhH5Zc5Kg64b5RxILeZpZG0MV7GFY3sw//k7SNg
darKT8A0Iv3l3lfguX3HNi6dkf97kS/EiA0tbkIB/JNjv13mq8HL7LijRt2hkKqP
PhQW88xC/exZilU5pAavoZOPuZIOTUHqtpRq4ZeKl+wDf+e5lPYFDpihWGjplGpa
4BOSmGeo/SyVFPji9QF4Pk0DRJF/NjwJoAC60xHAVt5Z4gQSOOOjNZDCswA0ry2L
e8m5cv5vPGY75uVrGqALQ6Xm961PPc5cJ1q7tmEZMj+z5HE7tgAdhiPI6acKgrAv
+1k4N0OVqKamMS+PVpD05hUCAwEAAQ==
-----END PUBLIC KEY-----'''
cipher = PKCS1_v1_5.new(RSA.importKey(PUBLIC_KEY))
cipher_text = base64.b64encode(cipher.encrypt(bytes(pwd, encoding="utf8")))
return cipher_text.decode("utf-8")
def login():
params = {
"mobile": "", # 必要,登录手机号
"password": encryptPassword(""), # 必要,RSA加密后的登录密码,如 "password": encryptPassword("123456"),
"ct": "2", # 必要,固定参数
"identify": "0", # 必要,固定参数
}
HEADERS = {
"AppVersion": "5.0.17" # 必要,由最新app版本得到
}
COOKIES = {}
# 配置登录数据
response = requests.post("https://m.yiban.cn/api/v4/passport/login", data=params, allow_redirects=False, cookies=COOKIES, headers=HEADERS).json()
if response is not None and response["response"] == 100:
access_token = response["data"]["access_token"]
HEADERS["Authorization"] = "Bearer " + access_token
COOKIES["loginToken"] = access_token
print('用户信息:',response,"\n")
print('loginToken:',access_token,"\n")
if __name__ == '__main__':
login()
#输出######
用户信息: {
'response': 100,
'message': '请求成功',
'is_mock': False,
'data': {
'user': {
'sex': '1',
'name': '***',
'nick': '易班16043*****',
'pic': {
's': 'http://img02.fs.yiban.cn/4568****/avatar/user/68',
'm': 'http://img02.fs.yiban.cn/4568****/avatar/user/88',
'b': 'http://img02.fs.yiban.cn/4568****/avatar/user/160',
'o': 'http://img02.fs.yiban.cn/4568****/avatar/user/'
},
'user_id': 4568****,
'phone': '***********',
'authority': '1',
'isSchoolVerify': True,
'school': {
'isVerified': True,
'schoolName': '*****学院',
'schoolId': 310 ** ,
'schoolOrgId': 2005 ** * ,
'collegeName': '***系',
'collegeId': ** ** * ,
'className': '',
'classId': 0,
'joinSchoolYear': '2020',
'type': 1
}
},
'access_token': 'cdd6d4432743414c9e1faf3e792*****',
'urlBlackListLastUpdateTime': 0
}
}
其中'access_token': 'cdd6d4432743414c9e1faf3e792*****' 是获取cookie的必须参数
写入COOKIE "loginToken" = “cdd6d4432743414c9e1faf3e792*****”
第二步 – 获取易班COOKIE
此步骤可直接参考GITHUB上的现成代码
def auth(self):
COOKIES = {}
CSRF = "00000000000000000000000000000000"
HEADERS = {
"Origin": "m.yiban.cn",
"origin":"api.uyiban.com",
"origin":"https://c.uyiban.com",
"authority": "api.uyiban.com",
"AppVersion": "5.0.17",
"x-requested-with": "com.yiban.app",
"user-agent":"Mozilla/5.0 (Linux; Android 12; XIAOMI Build/SKQ1.211006.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/112.0.5615.48 Mobile Safari/537.36;webank/h5face;webank/1.0 yiban_android/5.0.17"
}
iapp = requests.get("http://f.yiban.cn/iapp/index?act=iapp7463", headers=HEADERS, allow_redirects=False, cookies=COOKIES) # 利用 loginToken 访问获取 verifyRequest跳转数据
# 此处cookie带有 "loginToken" = “cdd6d4432743414c9e1faf3e792*****”
act = iapp.headers["Location"] # 返回302跳转目标
verifyRequest = re.findall(r"verify_request=(.*?)&", act)[0] # 正则取302跳转目标,得到 verify_request 数据
json = requests.get("https://api.uyiban.com/base/c/auth/yiban?verifyRequest=" + verifyRequest + "&CSRF=" + CSRF, headers=HEADERS, allow_redirects=False, cookies={'csrf_token': CSRF})
cookies = requests.utils.dict_from_cookiejar(json.cookies) # 获取cookie
Attendancecookies = cookies # 签到cookies赋值self.Attendancecookies
print('Location:',act,"\n")
print('cookies:',Attendancecookies,"\n")
###打印数据:
Location: https://c.uyiban.com/#/?verify_request=c9e814****53de92ccc', 'cpi': 'eyJD******%3D%3D', 'is_certified': '1'}
cookies: {'PHPSESSID': 'fa1a28bc38cc**c4b1e87a8ce51f****', 'cpi': 'eyJDaG**%3D%3D', 'is_certified': '1'}
接下来签到仅需要 PHPSESSID
第三步 – 获取签到信息
易班app内校本化似乎使用iapp,本人利用算法助手 小黄鸟等均无法正常抓包。这个时候,我发现可以利用ADB连接手机,用chrome://inspect/#devices
调试获取到接下来操作所需的信息:
通过分析控制台可以得出2个非常重要的链接:
GET固定需要CSRF
可以是任意32位字符,但不能为空
提交上述此请求必须带有cookie(++只需要PHPSESSID++)及特定headers
signIn
请求数据:
https://api.uyiban.com/nightAttendance/student/index/signIn?CSRF=(32位字符)
返回数据:
{
"code": 0, # 返回0有校本化晚签任务 返回999则校本化未授权
"msg": "", # 如已签到则返回 "已签到"
"data": {
"State": 1,
"Msg": "未达签到时段",
"OutState": "on",
"Remark": "因受不可抗因素影响导致定位有误,允许在范围之外签到,但需填写原因并上传照片,必须在本校范围,本人站在宿舍门口露出宿舍门牌号的位置进行拍照后上传",
"FileUrl": "https://res.uyiban.cn/2533a357c60f2949fa60d896bb4264dd/public/ninghtattendance/202109/14068d3bc5aa49115f0dca01d279d**e.png",
"Type": "campus",
"Position": [
{
"Id": "695652bc99ffcf3c16cc8b40dc62****",
"UniversityId": "2533a357c60f2949fa60d896bb42****",
"Campus": "**校区",
"BuildingId": "None",
"Type": "campus",
"Title": "**学院(**校区)",
"Address": "**学院(**校区)",
"LngLat": "118.254877,26.583077", #学校经纬度
"Range": 300,
"MapType": 2,
"Points": [
"118.2522103916109,26.586591497111847",
"118.25298823222522,26.586193330554728",
"118.25805224284528,26.584600650478848",
"118.25933970317243,26.583180651969975",
"118.25771964892749,26.582077262591984",
"118.25699008807544,26.580916293593525",
"118.25672019079332,26.580653035039877",
"118.25655758187179,26.580159498580716",
"118.25550280317668,26.579863255747707",
"118.25429446801547,26.58022066603373",
"118.25376741394405,26.580678821211876",
"118.2531652580202,26.580868319414655",
"118.25260065302257,26.58111058881208",
"118.25177319154147,26.58144400823378",
"118.25107849940662,26.582067667855167",
"118.25012363299732,26.582432267291093",
"118.24921168193225,26.58308950281557",
"118.24800737008457,26.58372394739835",
"118.24755407676105,26.584914870742125",
"118.2478518019617,26.586220914427095",
"118.24896491870288,26.586836152984155",
"118.25036503180866,26.587512551792482",
"118.2522103916109,26.586591497111847"
], # 签到范围
"AddressName": "自定义多边形",
"CreateTime": 1596529630
}
],
"Range": {
"StartTime": 1683810000, # 签到开始时间戳
"EndTime": 1683815400, # 签到结束时间戳
"SignDay": 1,
"RelatType": 1,
"RelatTimeType": 1
},
"IsNeedPhoto": 2,
"AttachmentFileName": ""
}
}
signPosition
请求数据:
https://api.uyiban.com/nightAttendance/student/index/signPosition?CSRF=(32位字符)
POST:
{
"Code": "",
"PhoneModel": "",
"SignInfo": '{
"Reason": "",
"AttachmentFileName": "",
"LngLat": "经度,纬度", # 如 "LngLat": "118.254877,26.583077"
"Address": "**地址"# 如 "Address": "*省*市*区*镇*学院(*校区)"
}',
"OutState": "1"
}
返回数据:
签到成功:
{'code': 0, 'msg': '', 'data': True}
签到失败:
{'code': 500, 'msg': '非法签到', 'data': None}
圆满 —— 目标实现代码
# -*- coding: utf-8 -*-
"""
@Time : 2023/4/14 11:40
@Auth : apecode.
@File : yiban.py
@Blog : https://wcyuns.cn
"""
import json
import re
import sys
import os
import time
import urllib
from urllib import parse
import random
import requests
import numpy
import jsonpath
import base64
import msvcrt
try:
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
except ModuleNotFoundError:
print("缺少pycryptodome依赖!程序将尝试安装依赖!")
os.system("pip3 install pycryptodome -i https://pypi.tuna.tsinghua.edu.cn/simple")
os.execl(sys.executable, 'python3', __file__, *sys.argv)
def encryptPassword(pwd): # 登录密码加密
# 密码加密
PUBLIC_KEY = '''-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzq0rgsM++ZxLRGHpdfre
Hu6UXhdlUS5P2WOxRG14qU8/iWSb/CkOqgOl8AGcOhlthkvolCdpUvVcVsVUxBv0
YRN0Jb64zPrn5aLVwQT4RJn5tXvoqLdHIXis7pljXAMDPVZOVlWJkDMk8YU6HDaA
MqsD6l5p9lg2LMP4OhMgaPX+CkO370LB5vRjJTHp03n+IqfxXoC7DEd+kxRIEM2C
EDgUSYDJBDgwBvGALZmvB/a1b0im9t1P/EmnuE7uN9NRFoWyVpOiEwo/Ti7rmJGf
qNT3vvtfWo4nXsm1rYQXsPayoKDSRaba3gFY/1SYWLAuSO2q2da5ZCcsAk5RKy0V
c1hUg8n6y0YLAvuzoXY5VyNMXkhH5Zc5Kg64b5RxILeZpZG0MV7GFY3sw//k7SNg
darKT8A0Iv3l3lfguX3HNi6dkf97kS/EiA0tbkIB/JNjv13mq8HL7LijRt2hkKqP
PhQW88xC/exZilU5pAavoZOPuZIOTUHqtpRq4ZeKl+wDf+e5lPYFDpihWGjplGpa
4BOSmGeo/SyVFPji9QF4Pk0DRJF/NjwJoAC60xHAVt5Z4gQSOOOjNZDCswA0ry2L
e8m5cv5vPGY75uVrGqALQ6Xm961PPc5cJ1q7tmEZMj+z5HE7tgAdhiPI6acKgrAv
+1k4N0OVqKamMS+PVpD05hUCAwEAAQ==
-----END PUBLIC KEY-----'''
cipher = PKCS1_v1_5.new(RSA.importKey(PUBLIC_KEY))
cipher_text = base64.b64encode(cipher.encrypt(bytes(pwd, encoding="utf8")))
return cipher_text.decode("utf-8")
####################################################################################################################################################################################################
####################################################################################################################################################################################################
class yiban:
COOKIES = {}
def __init__(self, mobile, password): # 全局变量
self.mobile = mobile # 登录手机号
self.password = password # 登录密码
self.session = requests.session()
self.HEADERS = {
"Origin": "m.yiban.cn",
"origin":"api.uyiban.com",
"origin":"https://c.uyiban.com",
"authority": "api.uyiban.com",
"AppVersion": "5.0.17",
"x-requested-with": "com.yiban.app",
"user-agent":"Mozilla/5.0 (Linux; Android 12; Redmi K30 Pro Build/SKQ1.211006.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/112.0.5615.48 Mobile Safari/537.36;webank/h5face;webank/1.0 yiban_android/5.0.17"
}
self.Attendancecookies = {}
self.CSRF = "00000000000000000000000000000000"
def login(self): # 登录
params = {
"mobile": self.mobile, #登录手机号
"password": encryptPassword(self.password), #RSA加密后的登录密码
"ct": "2", #固定参数
"identify": "0", #固定参数
}
# 配置登录数据
response = requests.post("https://m.yiban.cn/api/v4/passport/login", data=params, allow_redirects=False, cookies=self.COOKIES, headers=self.HEADERS).json()
if response is not None and response["response"] == 100:
self.access_token = response["data"]["access_token"]
self.HEADERS["Authorization"] = "Bearer " + self.access_token
# 增加cookie
self.COOKIES["loginToken"] = self.access_token
print('用户信息:',response,"\n")
print('loginToken:',self.access_token,"\n")
print('用户姓名:',response["data"]["user"]["name"],"\n手机号码:",response["data"]["user"]["phone"])
return 1
else:
print(response)
return self.mobile
#返回https://f.yiban.cn/iapp/index?act=iapp7463所需的Cookie“loginToken=ce196e5fb2900bc35b44e1f1b4ed****”
#通过获取的loginToken访问iapp后台得到值verifyRequest
#利用verifyRequest访问api获取签到用的cookie
#返回cookie内容范例
#{'PHPSESSID': '1aef34b976315dca8400711255f1a9af', 'cpi': 'eyJDaGFubmVsIjoieWliYW......TliNWViZTUxYWMifQ%3D%3D', 'is_certified': '1'}
#PHPSESSID为关键值,有这一个参数就可以访问签到等相关内容
#cpi 使用base64编码 有个人相关信息
def auth(self): # 获取签到cookie
iapp = requests.get("http://f.yiban.cn/iapp/index?act=iapp7463", headers=self.HEADERS, allow_redirects=False, cookies=self.COOKIES) # 利用 loginToken 访问获取 verifyRequest
act = iapp.headers["Location"] # 返回302跳转目标
verifyRequest = re.findall(r"verify_request=(.*?)&", act)[0] # 正则取302跳转目标,得到 verify_request 数据
json = requests.get("https://api.uyiban.com/base/c/auth/yiban?verifyRequest=" + verifyRequest + "&CSRF=" + self.CSRF, headers=self.HEADERS, allow_redirects=False, cookies={'csrf_token': self.CSRF})
#访问api获取cookie CSRF值按此可用,只验证位数
cookies = requests.utils.dict_from_cookiejar(json.cookies) # 获取cookie
self.Attendancecookies = cookies # 签到cookies赋值self.Attendancecookies
print('Location:',act,"\n")
print('cookies:',self.Attendancecookies,"\n")
return
def authorization(self, testauth): # 授权校本化
self.testauth = testauth
authhtml = requests.get("https://oauth.yiban.cn/code/html?client_id=95626fa3080300ea&redirect_uri=https://f.yiban.cn/iapp7463") # 利用 loginToken 访问获取 verifyRequest
HTML_PUBLIC_KEY = re.search('<input type="test" id="key" value="((?:.|\n)+)?" style="display:none">',authhtml.text)[1] # 获取网页签到加密 password 证书
webcookies = requests.utils.dict_from_cookiejar(authhtml.cookies) # 获取校本化授权 cookie 登录需要
print()
print("获取的网页cookie:",webcookies)
print("获取的网页加密证书:",HTML_PUBLIC_KEY,"\n")
HTML_cipher = PKCS1_v1_5.new(RSA.importKey(HTML_PUBLIC_KEY))
HTML_cipher_text = base64.b64encode(HTML_cipher.encrypt(bytes(self.password, encoding="utf8")))
HTML_cipher_decode = HTML_cipher_text.decode("utf-8")
print("密码网页加密结果:",HTML_cipher_decode)
webparams = {
"oauth_uname": self.mobile , # 账号
"oauth_upwd": HTML_cipher_decode , # 加密后密码
"client_id": "95626fa3080300ea", # 应用端编号
"redirect_uri": "http://f.yiban.cn/iapp7463", # 应用端回调地址
}
webauth = requests.post("https://oauth.yiban.cn/code/usersure", data=webparams, cookies=webcookies, headers=self.HEADERS).json()
print("授权结果",webauth,"\n")
print("授权成功,返回获取签到信息中...")
return
def signPosition(self):
Position = requests.get("https://api.uyiban.com/nightAttendance/student/index/signPosition?CSRF=" + self.CSRF, allow_redirects=False, cookies={'PHPSESSID': self.Attendancecookies['PHPSESSID'], 'csrf_token': self.CSRF}, headers=self.HEADERS).json()
if Position["code"] == 0: # 判断是否登录成功
print("校本化已授权,有晚签任务\n")
print("可签到数据:",Position,"\n")
State=Position["data"]["State"] # 获取状态码
Msg=Position["data"]["Msg"] # 获取返回信息
StartTime=Position["data"]["Range"]["StartTime"] # 获取签到开始时间
self.StartTime = StartTime
EndTime=Position["data"]["Range"]["EndTime"] # 获取签到结束时间
pause = StartTime - int(time.time()) # 获取系统现在时间
if State == 2: # 没有签到任务 如学校没有晚签
print("您",Position["data"]["Msg"])
return
else:
if State == 3: # 已签到
print("[*] 签到接口返回数据:",Position["data"]["Msg"])
return
else:
self.Address=jsonpath.jsonpath(Position,'$...Address')[0] # 获取可签到位置 地址
self.LngLat=jsonpath.jsonpath(Position,'$...LngLat')[0] # 获取可签到位置 经纬度
self.lonss = '%.6f' %float(round(float(self.LngLat.split(",")[0]),3) + float(random.uniform(0.000100,0.000999))) # 根据地址随机修改经度后3位,达到每次定位位置不一样
self.latss = '%.6f' %float(round(float(self.LngLat.split(",")[1]),3) + float(random.uniform(0.000100,0.000999))) # 根据地址随机修改纬度后3位,达到每次定位位置不一样
if State == 0: # 已经到达签到时间
print("可以签到,立即执行!\n")
self.nightAttendance() # 签到跳转
else:
if State == 1: # 还未开始签到
pause = StartTime - int(time.time()) # 获取剩余时间
while pause >= 60 :
list = ["\\", "|", "/", "—"]
index = pause % 4
print("[*] 距签到时间还有 {} 秒 {}".format(pause,list[index]), end="\r", flush=True)
pause = StartTime - int(time.time())
time.sleep(1)
else:
while pause <= 60 and pause >=10 :
print("[*] 距签到时间还有 {} 秒 {}".format(pause,list[index]), end="\r", flush=True)
pause = StartTime - int(time.time())
time.sleep(0.5)
else:
while pause <= 10 :
print("[*] 距签到时间还有 {} 秒 {}").format(pause,list[index], end="\r", flush=True)
pause = StartTime - int(time.time())
while pause <= 0.25 :
print("到达签到时间!")
self.nightAttendance() # 签到跳转
return
time.sleep(0.25)
if Position["code"] == 999:
print("[*] 晚签信息获取失败\n[*] 有可能是校本化未授权或您的签到页面非本脚本所适配")
self.authorization()
return
def nightAttendance(self): # 执行签到
paramss = {
"Code": "",
"PhoneModel": "",
"SignInfo": '{"Reason":"","AttachmentFileName":"","LngLat":"' + self.lonss + ',' + self.latss + '","Address":"' + self.Address + '"}',
"OutState": "1"
}
nightsign = requests.post("https://api.uyiban.com/nightAttendance/student/index/signIn?CSRF=" + self.Attendancecookies['PHPSESSID'], data=paramss, cookies={'csrf_token': self.Attendancecookies['PHPSESSID'], 'PHPSESSID': self.Attendancecookies['PHPSESSID']}, headers=self.HEADERS).json()
print("签到返回数据:",nightsign)
#print(nightsign)
return self.signPosition() # 跳转回signPosition验证是否签到成功
def loginin(self): # 登陆校验
# 应当先判断列表账号是否都能正常登陆
loginin = self.login() # 登录
if loginin != 1:
print("用户",loginin,"登陆失败,请处理")
print("按任意键退出脚本")
ord(msvcrt.getch())
sys.exit()
self.authorization(0) # 校本化授权先执行
return
def setall(self): # 多人签到
loginin = self.login() # 登录
self.auth() # 授权
self.signPosition() # 判断能否签到&自动跳转签到
#self.authorization(0) # 校本化授权 已写入
return
def main(): # 主函数,填写账号密码
yiban("","").loginin() # 如 yiban("18655558888","123456").loginin()
time.sleep(0.5)
print("#签到检查通过#################\n")
yiban("","").setall() # 如 yiban("18655558888","123456").setall()
time.sleep(0.5)
print("##############################")
print("签到任务已执行完成!")
sys.exit()
if __name__ == '__main__':
main()
评论