![抓包校友邦小程序实现自动签到](https://dogefs.s3.ladydaily.com/~/source/unsplash/photo-1559251606-c623743a6d76?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTN8fHBpbmt8ZW58MHx8fHwxNjYzMDQ0Nzc2&ixlib=rb-1.2.1&w=1920)
抓包校友邦小程序实现自动签到
由于 Android 7.0
之后不在信任用户证书,无法抓取 HTTPS
请求,根据网上的教程配置了一番无法配置成功,干脆使用了 Android 5.0
,同时微信版本不要太高,貌似最新版本不兼容 Android 5.0
,一直崩溃。
所用工具
工具除 Burpsuite
文末均会附上下载地址
- 夜神模拟器
我比较常用国际版6.5.0.3,国内最新版也行,注意最新版默认是 Android 7
需要在 多开器 中新建 Android 5
- 微信 (旧版8.0.0)
- Fiddler (汉化版5.0.2)
- Burpsuite
配置环境
选择网卡
使用 ipconfig
查询本机网卡信息,网卡的选择随意即可,我这里选择了 Vmnet8
网卡,
![image-20221215151352444](https://img.5ime.cn/xybsyw/image-20221215151352444.png)
Burpsuite
新增一个 监听器
,监听地址
使用我们上面选择的 192.168.8.1
,端口的话就默认 8080
即可
![image-20221215151248441](https://img.5ime.cn/xybsyw/image-20221215151248441.png)
Fiddler
在菜单栏 工具
,选项
中勾选配置如下图即可。
![image-20221215151219960](https://img.5ime.cn/xybsyw/image-20221215151219960.png)
连接
这里可以自定义 Fiddler
监听端口,我们保持默认 8888
即可
![image-20221215151909746](https://img.5ime.cn/xybsyw/image-20221215151909746.png)
这里配置 网关
的目的,是让流量可以被 Burpsuite
接收到,这里代理配置填写 Burpsuite
监听器中的配置。
![image-20221215151225025](https://img.5ime.cn/xybsyw/image-20221215151225025.png)
夜神模拟器
配置代理
在 WLAN
长按 修改网络
,勾选 高级选项
,然后在配置如下图所示
1 2
| 代理服务器主机名:填写你选择网卡IP 代理服务器端口:填写Fiddler监听的端口,默认8888
|
![image-20221215150036716](https://img.5ime.cn/xybsyw/image-20221215150036716.png)
安装证书
修改完毕后前往浏览器访问 代理IP:端口
,点击 FiddlerRoot certficate
下载证书
![image-20221215150025007](https://img.5ime.cn/xybsyw/image-20221215150025007.png)
然后在手机 设置
,安全
里选择 从SD卡中安装
![image-20221215150345935](https://img.5ime.cn/xybsyw/image-20221215150345935.png)
我们依次选择 内部存储空间
,Download
,会看到 FidderRoot.crt
,点击进行安装操作即可,名称随意。
![image-20221215150345935](https://img.5ime.cn/xybsyw/image-20221215150507256.png)
开始抓包
此时我们在模拟器中,访问一个页面,可以看到 Fiddler
和 Burpsuite
均接收到了流量
![image-20221215152200619](https://img.5ime.cn/xybsyw/image-20221215152200619.png)
打开 校友邦
小程序,默认只能 微信登录
,登录后在 设置
里 退出登录
,即可 手机号登录,可以通过 忘记密码
功能来重设密码。
![image-20221225172318576](https://img.5ime.cn/xybsyw/image-20221225172318576.png)
模拟登录
我们在账号密码登录的时候直接抓包,得到如下请求(已删除无用参数进行精简)
1 2 3 4 5 6 7 8
| POST /login/login.action HTTP/1.1 Host: xcx.xybsyw.com Connection: close Content-Length: 39 charset: utf-8 content-type: application/x-www-form-urlencoded
username=手机号&password=md5(密码)
|
![image-20221225173048459](https://img.5ime.cn/xybsyw/image-20221225173048459.png)
响应如下,对我们有用的目前只发现 sessionId
和 loginId
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { "code": "200", "data": { "activate": true, "sessionId": "******", "needComplete": false, "loginerId": ******, "phone": "手机号", "loginKey": "手机号" }, "msg": "登录成功", "mstv": { "t": 时间戳, "m": "*****", "s": "*****", } }
|
直接编写代码,实现登录功能(为方便演示代码使用的 Python
,但是感觉写起来没 PHP
顺手)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| globalData = {'username' : 13888888888, 'password' : '密码'}
def md5(s): m = hashlib.md5() m.update(s.encode('utf-8')) return m.hexdigest()
def userLogin(u, p): url = 'https://xcx.xybsyw.com/login/login.action' data = {'username': u, 'password': md5(p)} r = requests.post(url, data=data) if r.json()['code'] == '200': globalData['sessionId'] = r.json()['data']['sessionId'] globalData['loginerId'] = r.json()['data']['loginerId'] return(globalData) else: return(r.json()['msg'])
|
获取信息
继续抓包,得到一个包含 个人信息
的请求
1 2 3 4
| POST /account/LoadAccountInfo.action HTTP/1.1 Host: xcx.xybsyw.com Connection: close cookie: JSESSIONID=*******
|
![image-20221225175855340](https://img.5ime.cn/xybsyw/image-20221225175855340.png)
继续编写代码,请求中的 Cookie
来自 登录成功
返回的 sessionId
,取出 姓名
和 学校ID
,后面会用到
1 2 3 4 5 6 7 8 9 10
| def getUserInfo(c): url = 'https://xcx.xybsyw.com/account/LoadAccountInfo.action' headers = {'Cookie': 'JSESSIONID=' + c} r = requests.get(url, headers=headers) if r.json()['code'] == '200': globalData['username'] = r.json()['data']['loginer'] globalData['schoolId'] = r.json()['data']['schoolId'] return globalData else: return None
|
健康上报
![image-20221227122206457](https://img.5ime.cn/xybsyw/image-20221227122206457.png)
其中 healthCodeImg
为健康码照片,留空则为不提交,如果提交的话,直接填写图片url地址即可。
1 2 3 4 5 6 7 8 9
| def postHealthData(c, img): url = 'https://xcx.xybsyw.com/student/clock/saveEpidemicSituation.action' data = {'healthCodeStatus': 0, 'locationRiskLevel': 0, 'healthCodeImg': img} headers = {'Cookie': 'JSESSIONID=' + c} r = requests.post(url, data=data, headers=headers) if r.json()['code'] == '200': return True else: return None
|
进行签到
进入签到页面,发现请求了一个接口,返回如下(已删除无用参数,只展示一下我们用得到的),发现提供了经纬度(由于自己签了一次,不知道经纬度是最初报名时的还是最后一次签到时的),不过这个接口也可以用作签到判断。
![image-20221227123753129](https://img.5ime.cn/xybsyw/image-20221227123753129.png)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "code": "200", "data": { "clockInfo": { "inAddress": "签到地址", "inTime": "00:15:02", "outTime": "", }, "postInfo": { "address": "好像是报名时自行填写的地址", "lat": '维度', "lng": '经度' } } }
|
请求该接口的时候提交了 traineeId
参数,翻一下历史流量找到了 traineeId
参数值的来源
![image-20221227125702526](https://img.5ime.cn/xybsyw/image-20221227125702526.png)
直接编写代码取出 traineeId
1 2 3 4 5 6 7 8 9
| def getSignState(c, traineeId): url = 'https://xcx.xybsyw.com/student/clock/GetPlan!detail.action' data = {'traineeId': traineeId} headers = {'Cookie': 'JSESSIONID=' + c} r = requests.post(url, data=data, headers=headers) if r.json()['code'] == '200': return r.json()['data'] else: return None
|
在从 实习成长
页面一步步进入 签到页面
中,每请起一次都会附带请求 behavior/Duration.action
接口,暂不知道用途,先模拟出来
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
| def getUserIP(): url = 'https://xcx.xybsyw.com/behavior/Duration!getIp.action' r = requests.get(url) if r.json()['code'] == '200': return r.json()['data']['ip'] else: return None
def postSignData(d): url = 'https://app.xybsyw.com/behavior/Duration.action' data = { 'app': 'wx_student', 'appVersion': '1.6.36', 'userId': d['loginerId'], 'deviceToken': '', 'userName': d['username'], 'country': d['location']['country'], 'province': d['location']['province'], 'city': d['location']['city'], 'deviceModel': 'microsoft', 'operatingSystem': 'android', 'operatingSystemVersion': '11', 'screenHeight': '800', 'screenWidth': '450', 'eventTime': int(time.time()), 'pageId': '2', 'pageName': '成长', 'pageUrl': 'pages/growup/growup', 'eventType': 'click', 'eventName': 'clickSignEvent', 'clientIP': getUserIP(), 'reportSrc': '2', 'login': '1', 'netType': 'WIFI', 'itemID': 'none', 'itemType': '其他' } headers = {'Cookie': 'JSESSIONID=' + d['sessionId']} r = requests.post(url, data=data, headers=headers) if r.json()['code'] == '200': return r.json() else: return None
|
至此,该获取的东西基本就获取到了,直接开始进行签到操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| def sign(c, traineeId, d): url = 'https://xcx.xybsyw.com/student/clock/Post!autoClock.action' data = { 'traineeId': traineeId, 'adcode': d['location']['adcode'], 'lat': d['location']['lat'], 'lng': d['location']['lng'], 'address': d['location']['address'], 'deviceName': 'microsoft', 'punchInStatus': '1', 'clockStatus': '2', 'imgUrl': '', 'reason': '' } headers = {'Cookie': 'JSESSIONID=' + c} r = requests.post(url, data=data, headers=headers) if r.json()['code'] == '200': return True else: return None
|
写在后面
2023/3/22 更新:关于加密逻辑可以前往 校友邦小程序签到加密逻辑解析