大神的辭職,帶走了騷豆腐和烤洋芋溶锭,帶走了星辰大海宝恶,也帶走了破解的自動(dòng)打卡。已被懶惰侵蝕的我趴捅,早已無法按時(shí)起床垫毙。為了回到我溫暖的被窩,經(jīng)過python的指引驻售,終于完成了自動(dòng)打卡的代碼露久。
抓包分析
之前使用Burpsuite來抓包,后來嫌窗口切來切去麻煩就在windows上裝了Fiddler4欺栗,其實(shí)還是比較好用的。首先做的第一部就是設(shè)置代理了,因?yàn)楣敬蚩ㄊ褂胊pp實(shí)現(xiàn)的迟几。在Fiddler4的菜單選擇Tools->options,在下圖紅色框內(nèi)打上勾消请。接下來Ipconfig看下自己電腦的IP,手機(jī)和電腦連接到同一局域網(wǎng)类腮,在手機(jī)中把電腦IP地址設(shè)置為代理地址臊泰。就可以抓包了。
第一個(gè)包蚜枢,打開應(yīng)用
打開手機(jī)中公司的應(yīng)用缸逃,抓到第一個(gè)包,可以看出手機(jī)以POST方式像服務(wù)器提交了phoneType和userId厂抽,兩個(gè)數(shù)據(jù)需频,先將他們存下來,包1-1是http請求頭信息筷凤。
包1-2是手機(jī)客戶端主動(dòng)向服務(wù)器發(fā)送的數(shù)據(jù)昭殉。
第二個(gè)包,客戶端登錄
接下來手機(jī)模擬登錄截到第二個(gè)包藐守。包2-1顯示的為手機(jī)客戶端登錄請求的地址目錄為/mobile/login 挪丢。
包2-2為手機(jī)客戶端向服務(wù)器發(fā)送的數(shù)據(jù):
system=android
password=SO1Nc4KbrsEl3KKV1rwY3A%3D%3D
account=我的賬號
serialNumbe=869949028710182
version=7
model=FRD-AL10
登錄成功后服務(wù)器將會(huì)回復(fù)如下信息:
這里比較重要的信息為token,使用基于 Token 的身份驗(yàn)證方法卢厂,在服務(wù)端不需要存儲用戶的登錄記錄乾蓬。大概的流程是這樣的:
1、客戶端使用用戶名跟密碼請求登錄
2慎恒、服務(wù)端收到請求任内,去驗(yàn)證用戶名與密碼
3、驗(yàn)證成功后巧号,服務(wù)端會(huì)簽發(fā)一個(gè) Token族奢,再把這個(gè) Token 發(fā)送給客戶端
4、客戶端收到 Token 以后可以把它存儲起來丹鸿,比如放在 Cookie 里或者 Local Storage 里
5越走、客戶端每次向服務(wù)端請求資源的時(shí)候需要帶著服務(wù)端簽發(fā)的 Token
服務(wù)端收到請求,然后去驗(yàn)證客戶端請求里面帶著的 Token,如果驗(yàn)證成功,就向客戶端返回請求的數(shù)據(jù)
第三個(gè)包管怠,考勤打卡
最后一個(gè)包就是打卡的包了豹障,點(diǎn)擊打卡發(fā)現(xiàn)打卡請求地址為/mobile/busUserClock/saveOrUpdateNewUserClock,其實(shí)頭文件都是一樣的只有上傳的長度不同。
點(diǎn)擊打卡后客戶端向服務(wù)器端發(fā)送的數(shù)據(jù)為:
1徽诲、之前登錄成功后獲取的token,這里的token和之前的截圖不符是因?yàn)椋沂謾C(jī)打過卡就無法再打,為了提到截圖用的是同事的手機(jī)肋殴;
2囤锉、longitude(精度)、latitude(緯度)护锤,中間我省略了幾個(gè)請求的包官地,公司打卡的定位是通過調(diào)用百度地圖API獲得定位的,這里的經(jīng)緯度也是從那里來的烙懦;
3驱入、在第一次打開應(yīng)用時(shí),客戶端主動(dòng)向服務(wù)器發(fā)送的userId氯析;
4亏较、startTime、endTime我們上下班時(shí)間=掩缓。=#雪情;
5、position這個(gè)是打卡的地址拾因。
服務(wù)器返回的數(shù)據(jù)為簽退成功旺罢。
python實(shí)現(xiàn)
終于到了激動(dòng)人心的時(shí)刻,使用python模擬上述發(fā)包的過程實(shí)現(xiàn)客戶端與服務(wù)器的交互绢记。這里先自己檢討下扁达,我寫的代碼就跟一坨屎一樣,但是一邊抓包一邊寫蠢熄,最后直接拼在一起的跪解,相當(dāng)?shù)拇植冢戎苣┰俸煤酶南麓a吧=签孔。=#
#! /usr/bin/python
# coding:utf - 8
"""
autho:czy
僅用于自己使用
"""
import requests
url_init = 'http://服務(wù)器地址:端口號/mobile/person/getNewVersion'
url_login = 'http://服務(wù)器地址:端口號/mobile/login'
url_clock = "http://服務(wù)器地址:端口號/mobile/busUserClock/saveOrUpdateNewUserClock"
#打開應(yīng)用頭請求
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"
}
#打開應(yīng)用傳送的數(shù)據(jù)
payload_init = {
"phoneType": "1",
"userId": "402880f25c620dd7015c76227a2019da"
}
#登錄傳送的數(shù)據(jù)
payload_login = {
"system":"android",
#注意這里UTF-8編碼會(huì)將=號轉(zhuǎn)化為%3D,所以發(fā)送數(shù)據(jù)時(shí)應(yīng)使用=號
"password":"SO1Nc4KbrsEl3KKV1rwY3A==",
"account":"我的賬號叉讥,這里就公開了",
"serialNumber":"869949028710182",
"version":"7.0",
"model":"FRD-AL10",
}
#模擬發(fā)送打開應(yīng)用的包
r = requests.post(url_init,data = payload_init,headers = headers_init)
#print r.text 可以用于檢查服務(wù)器是否正常返回?cái)?shù)據(jù)
#模擬發(fā)送登錄數(shù)據(jù)的包
r2 = requests.post(url_login,data = payload_login,headers = headers_login)
#從返回的數(shù)據(jù)中提取token
token = str(r2.text[466:654])
#print token 可以用于檢查服務(wù)器是否正常返回?cái)?shù)據(jù)
#打卡發(fā)送的數(shù)據(jù)
payload_clock = {
"token": token,
"longitude": "102.676988",
"userId":"402880f25c620dd7015c76227a2019da",
"latitude": "25.053235",
#注意這里UTF-8編碼會(huì)將:號轉(zhuǎn)化為%3A,所以發(fā)送數(shù)據(jù)時(shí)應(yīng)使用:號
"startTime": "09:00",
"endTime": "18:00",
#公司打卡分上下班下面注釋的是上班發(fā)送的數(shù)據(jù),沒注釋的是下班發(fā)送的數(shù)據(jù)饥追。
#"position": "這里寫上打卡的地址",
#"isStart": "1"
"position": "這里寫上打卡的地址",
"isStart": "0"
}
#模擬打卡發(fā)送的包
r3 = requests.post(url_clock,data = payload_clock,headers = headers_clock)
print r3 #查看是否正常打卡
運(yùn)行結(jié)果<Response [200]>http狀態(tài)表返回成功图仓,查看手機(jī)打卡成功。
服務(wù)器自動(dòng)運(yùn)行python腳本
腳本已經(jīng)可以正常使用了接下來就是讓機(jī)器幫我們運(yùn)行了但绕,如果天天起來自己點(diǎn)腳本就和用手機(jī)打沒區(qū)別了救崔。在公司的KVM上建個(gè)CentOS6.9的服務(wù)器(噓!D笏场六孵!悄悄呢)。
把腳本拷上去取個(gè)帥氣的名字
這里注意有些同學(xué)運(yùn)行不了會(huì)提示缺少模塊requests
安裝擴(kuò)展源EPEL幅骄。
EPEL(http://fedoraproject.org/wiki/EPEL) 是由 Fedora 社區(qū)打造劫窒,為 RHEL 及衍生發(fā)行版如 CentOS、Scientific Linux 等提供高質(zhì)量軟件包的項(xiàng)目拆座。
~]# yum -y install epel-release
然后再安裝pip
~]# sudo yum -y install python-pip
最后安裝requests模塊
~]#pip install requests
crontab
接下來為服務(wù)器設(shè)置定時(shí)任務(wù)
~]#crontab -e
crontab命令格式:
"* * * * * command"
M H D m d command
M: 分(0-59)
H:時(shí)(0-23)
D:天(1-31)
m: 月(1-12)
d: 周(0-6) 0為星期日
可以使用如下命令打開郵件查看定時(shí)任務(wù)執(zhí)行的情況
~]#tail -f /var/spool/mail/root
這里我測試了一下主巍,執(zhí)行成功冠息!
后續(xù)代碼整理
公司更新后軟件重新整理了下代碼,更新如下:
#! /usr/bin/python
# coding:utf - 8
"""
autho:czy
"""
import requests
import json
import time
#請求和打卡地址
url_login = 'http://服務(wù)器地址:端口號/mobileFrame/login'
url_clock = "http://服務(wù)器地址:端口號/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",
}
#模擬登錄系統(tǒng)抓取token
server_request_login = requests.post(url_login,data = payload_login,headers = headers_init)
server_response_login = dict(json.loads(server_request_login.text))
#相比上次切片截取煤禽,使用字典截取更為嚴(yán)謹(jǐn)
response_token = server_response_login['data'][0]['token']
#獲取當(dāng)前時(shí)間
now_time = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())
#判斷上下班時(shí)間(上次腳本做了兩個(gè)這里加入判斷就避免寫兩個(gè)腳本了)
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)