大神的辞职,带走了骚豆腐和烤洋芋,带走了星辰大海,也带走了破解的自动打卡。已被懒惰侵蚀的我,早已无法按时起床。为了回到我温暖的被窝,经过python的指引,终于完成了自动打卡的代码。
抓包分析
之前使用Burpsuite来抓包,后来嫌窗口切来切去麻烦就在windows上装了Fiddler4,其实还是比较好用的。首先做的第一部就是设置代理了,因为公司打卡使用app实现的。在Fiddler4的菜单选择Tools->options,在下图红色框内打上勾。接下来Ipconfig看下自己电脑的IP,手机和电脑连接到同一局域网,在手机中把电脑IP地址设置为代理地址。就可以抓包了。
第一个包,打开应用
打开手机中公司的应用,抓到第一个包,可以看出手机以POST方式像服务器提交了phoneType和userId,两个数据,先将他们存下来,包1-1是http请求头信息。
包1-2是手机客户端主动向服务器发送的数据。
第二个包,客户端登录
接下来手机模拟登录截到第二个包。包2-1显示的为手机客户端登录请求的地址目录为/mobile/login 。
包2-2为手机客户端向服务器发送的数据:
system=android
password=SO1Nc4KbrsEl3KKV1rwY3A%3D%3D
account=我的账号
serialNumbe=869949028710182
version=7
model=FRD-AL10
登录成功后服务器将会回复如下信息:
这里比较重要的信息为token,使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
1、客户端使用用户名跟密码请求登录
2、服务端收到请求,去验证用户名与密码
3、验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
4、客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
5、客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
第三个包,考勤打卡
最后一个包就是打卡的包了,点击打卡发现打卡请求地址为/mobile/busUserClock/saveOrUpdateNewUserClock,其实头文件都是一样的只有上传的长度不同。
点击打卡后客户端向服务器端发送的数据为:
1、之前登录成功后获取的token,这里的token和之前的截图不符是因为,我手机打过卡就无法再打,为了提到截图用的是同事的手机;
2、longitude(精度)、latitude(纬度),中间我省略了几个请求的包,公司打卡的定位是通过调用百度地图API获得定位的,这里的经纬度也是从那里来的;
3、在第一次打开应用时,客户端主动向服务器发送的userId;
4、startTime、endTime我们上下班时间=。=#;
5、position这个是打卡的地址。
服务器返回的数据为签退成功。
python实现
终于到了激动人心的时刻,使用python模拟上述发包的过程实现客户端与服务器的交互。这里先自己检讨下,我写的代码就跟一坨屎一样,但是一边抓包一边写,最后直接拼在一起的,相当的粗糙,等周末再好好改下代码吧=。=#
#! /usr/bin/python
# coding:utf - 8
"""
autho:czy
仅用于自己使用
"""
import requests
url_init = 'http://服务器地址:端口号/mobile/person/getNewVersion'
url_login = 'http://服务器地址:端口号/mobile/login'
url_clock = "http://服务器地址:端口号/mobile/busUserClock/saveOrUpdateNewUserClock"
#打开应用头请求
headers_init = {
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 7.0; FRD-AL10 Build/HUAWEIFRD-AL10)",
"Accept-Encoding": "gzip",
"Content-Length": "52",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Connection": "Keep-Alive"
}
#登录头请求
headers_login = {
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 7.0; FRD-AL10 Build/HUAWEIFRD-AL10)",
"Accept-Encoding": "gzip",
"Content-Length": "129",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Connection": "Keep-Alive"
}
#打卡头请求
headers_clock = {
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 7.0; FRD-AL10 Build/HUAWEIFRD-AL10)",
"Accept-Encoding": "gzip",
"Content-Length": "461",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Connection": "Keep-Alive"
}
#打开应用传送的数据
payload_init = {
"phoneType": "1",
"userId": "402880f25c620dd7015c76227a2019da"
}
#登录传送的数据
payload_login = {
"system":"android",
#注意这里UTF-8编码会将=号转化为%3D,所以发送数据时应使用=号
"password":"SO1Nc4KbrsEl3KKV1rwY3A==",
"account":"我的账号,这里就公开了",
"serialNumber":"869949028710182",
"version":"7.0",
"model":"FRD-AL10",
}
#模拟发送打开应用的包
r = requests.post(url_init,data = payload_init,headers = headers_init)
#print r.text 可以用于检查服务器是否正常返回数据
#模拟发送登录数据的包
r2 = requests.post(url_login,data = payload_login,headers = headers_login)
#从返回的数据中提取token
token = str(r2.text[466:654])
#print token 可以用于检查服务器是否正常返回数据
#打卡发送的数据
payload_clock = {
"token": token,
"longitude": "102.676988",
"userId":"402880f25c620dd7015c76227a2019da",
"latitude": "25.053235",
#注意这里UTF-8编码会将:号转化为%3A,所以发送数据时应使用:号
"startTime": "09:00",
"endTime": "18:00",
#公司打卡分上下班下面注释的是上班发送的数据,没注释的是下班发送的数据。
#"position": "这里写上打卡的地址",
#"isStart": "1"
"position": "这里写上打卡的地址",
"isStart": "0"
}
#模拟打卡发送的包
r3 = requests.post(url_clock,data = payload_clock,headers = headers_clock)
print r3 #查看是否正常打卡
运行结果
服务器自动运行python脚本
脚本已经可以正常使用了接下来就是让机器帮我们运行了,如果天天起来自己点脚本就和用手机打没区别了。在公司的KVM上建个CentOS6.9的服务器(嘘!!!悄悄呢)。
把脚本拷上去取个帅气的名字
这里注意有些同学运行不了会提示缺少模块requests
安装扩展源EPEL。
EPEL( http://fedoraproject.org/wiki/EPEL) 是由 Fedora 社区打造,为 RHEL 及衍生发行版如 CentOS、Scientific Linux 等提供高质量软件包的项目。
~]# yum -y install epel-release
然后再安装pip
~]# sudo yum -y install python-pip
最后安装requests模块
~]#pip install requests
crontab
接下来为服务器设置定时任务
~]#crontab -e
crontab命令格式:
"* * * * * command"
M H D m d command
M: 分(0-59)
H:时(0-23)
D:天(1-31)
m: 月(1-12)
d: 周(0-6) 0为星期日
可以使用如下命令打开邮件查看定时任务执行的情况
~]#tail -f /var/spool/mail/root
这里我测试了一下,执行成功!
后续代码整理
公司更新后软件重新整理了下代码,更新如下:
#! /usr/bin/python
# coding:utf - 8
"""
autho:czy
"""
import requests
import json
import time
#请求和打卡地址
url_login = 'http://服务器地址:端口号/mobileFrame/login'
url_clock = "http://服务器地址:端口号/mobile/Hr/userClock"
#伪装头信息
headers_init = {
"Accept":"application/json",
"Accept-Encoding": "gzip,deflate",
"Accept-Language":"zh-CN,en-US;q=0.8",
"User-Agent": "Mozilla/5.0 (Linux; Android 7.0; FRD-AL10 Build/HUAWEIFRD-AL10; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36 Html5Plus/1.0 (Immersed/24.0)",
"X-Requested-With": "XMLHttpRequest",
"Content-Length": "453",
"Content-Type": "application/x-www-form-urlencoded",
"Origin": "file://",
"Connection": "Keep-Alive"
}
#伪装登录请求信息
payload_login = {
"system":"android",
"password":"XXXXXX", #这里填写截取到的密码
"account":"XXXXX", #这里填写账号
"serialNumber":"869949028710182%2C869949027256583",
"version":"7.0",
"model":"FRD-AL10",
}
#模拟登录系统抓取token
server_request_login = requests.post(url_login,data = payload_login,headers = headers_init)
server_response_login = dict(json.loads(server_request_login.text))
#相比上次切片截取,使用字典截取更为严谨
response_token = server_response_login['data'][0]['token']
#获取当前时间
now_time = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())
#判断上下班时间(上次脚本做了两个这里加入判断就避免写两个脚本了)
if int(time.strftime("%H%M%S"))<85959:
State = "SIGN"
else:
State = "SIGN_OUT"
#伪装打卡请求信息
payload_clock = {
"token": response_token,
"lng": 102.677008,
"lat": 25.053206,
"distance": 46.01784908422327,
"signState": State,
"nowTime": now_time,
"position": "打卡的地址",
}
#模拟打卡
server_request_clock = requests.post(url_clock,data = payload_clock,headers = headers_init)
print(server_request_clock)