上一篇百度登錄加密協(xié)議分析(上)主要講解了codestring,gid,token,rsakey等參數(shù)的產(chǎn)生。好了,廢話不多說(shuō),咱們進(jìn)入今天的主題,咱們接著上一篇的內(nèi)容往下講解,最后還剩三個(gè)字段callback,password,ppui_logintime。(我的新書(shū)《Python爬蟲(chóng)開(kāi)發(fā)與項(xiàng)目實(shí)戰(zhàn)》發(fā)布了,大家在這里可以看到樣章)
<h2>第三部分:</h2>
分析第一次post已經(jīng)產(chǎn)生,第二次post內(nèi)容發(fā)生變化的字段
callback
password
ppui_logintime
1.通過(guò)之前的分析,可以了解到callback 可能沒(méi)啥用,所以放到后面再分析。
2.一般來(lái)說(shuō)password的是最難分析的,所以也放到后面分析。
<h5>3.1</h5> 接下來(lái)分析ppui_logintime,依舊是搜索ppui_logintime,在下面鏈接中找到了ppui_logintime的出處
http://passport.bdimg.com/passApi/js/login_tangram_a829ef5.js

找到了 timeSpan: 'ppui_logintime',接著搜索timespan

找到了 r.timeSpan = (new Date).getTime() - e.initTime,
接著搜索initTime

咱們看一下_initApi什么時(shí)候調(diào)用的,通過(guò)搜索找到以下代碼:
_eventHandler:function() {vare,
t={
focus:function(t) {varn = e.fireEvent('fieldFocus', {
ele:this});
n&& (this.addClass(e.constant.FOCUS_CLASS),this.removeClass(e.constant.ERROR_CLASS), baidu(e.getElement(t + 'Label')).addClass(e.constant.LABEL_FOCUS_CLASS))
},
blur:function(t) {varn = e.fireEvent('fieldBlur', {
ele:this});
n&& (this.removeClass(e.constant.FOCUS_CLASS), baidu(e.getElement(t + 'Label')).removeClass(e.constant.LABEL_FOCUS_CLASS))
},
mouseover:function() {vart = e.fireEvent('fieldMouseover', {
ele:this});
t&&this.addClass(e.constant.HOVER_CLASS)
},
mouseout:function() {vart = e.fireEvent('fieldMouseout', {
ele:this});
t&&this.removeClass(e.constant.HOVER_CLASS)
},
keyup:function() {
e.fireEvent('fieldKeyup', {
ele:this})
}
},
n={
focus: {
userName:function() {
e.config.loginMerge&& e.getElement('loginMerge') && (e.getElement('loginMerge').value = 'true', e.getElement('isPhone').value = '')
},
password:function() {
e._getRSA(function(t) {
e.RSA=t.RSA,
e.rsakey=t.rsakey
})
},
verifyCode:function() {}
},
blur: {
userName:function() {},
password:function(t) {varn =this.get(0).value;
n.length&&e.validate(t)
},
verifyCode:function(t) {varn =this.get(0).value;
n.length&&e.validate(t)
}
},
change: {
userName:function() {vart =this.get(0).value;
e._loginCheck(t)
},
verifyCode:function() {}
},
click: {
verifyCodeChange:function(t, n) {
e.getElement('verifyCode').value = '',
e._doFocus('verifyCode'),
e.getVerifyCode(),
n.preventDefault()
}
},
keyup: {
verifyCode:function() {vart = e.getElement('verifyCode'),
n=baidu(t);
t.value&& 4 == t.value.length ?e._asyncValidate.checkVerifycode.call(e, {
error:function(t) {
n.addClass(e.constant.ERROR_CLASS),
e.setGeneralError(t.msg)
},
success:function() {
n.removeClass(e.constant.ERROR_CLASS),
e.clearGeneralError()
}
}) : e.$hide('verifyCodeSuccess')
}
},
submit:function(t) {
e.submit(),
t.preventDefault()
}
};return{
entrance:function(i) {
e=this;varr =(baidu(i.target), i.target.name);if(!r &&i.target.id) {varo = i.target.id.match(/\d+__(.*)$/);
o&& (r = o[1])
}
r&& (t.hasOwnProperty(i.type) &&t[i.type].apply(baidu(i.target), [
r,
i
]), n.hasOwnProperty(i.type)&& ('function' ==typeofn[i.type] &&n[i.type].apply(baidu(i.target), [
i
]), n[i.type].hasOwnProperty(r)&&n[i.type][r].apply(baidu(i.target), [
r,
i
])), e.initialized|| 'focus' != i.type ||e._initApi())
}
}
}(),
通過(guò)分析上面的js代碼可以看出來(lái),發(fā)生點(diǎn)擊,內(nèi)容改變,按鍵按下等事件可能會(huì)調(diào)用initApi()。
通過(guò)上面的代碼我們可以知道ppui_logintime是從你輸入登錄信息,一直到你點(diǎn)擊登錄按鈕提交的這段時(shí)間,
因此我們通過(guò)之前的抓包,直接把ppui_logintime的值保存下來(lái)即可。
<h4>3.2</h4> 接著咱們分析callback,看看這字段到底是干什么用的(最后發(fā)現(xiàn)沒(méi)啥用,和上一篇得出來(lái)的推斷差不多)。搜索callback,紅色標(biāo)注的地方是不是和post出去的內(nèi)容有重復(fù)。

callback ='bd__cbs__'+Math.floor(2147483648 *Math.random()).toString(36)
這個(gè)時(shí)候callback的生成當(dāng)時(shí)也就確定了
<h4>3.3</h4> 最后分析password的加密方式:搜索password,發(fā)現(xiàn)敏感內(nèi)容。
http://passport.bdimg.com/passApi/js/login_tangram_a829ef5.js


通過(guò)下斷點(diǎn),動(dòng)態(tài)調(diào)試可以知道password,是通過(guò)公鑰pubkey對(duì)密碼進(jìn)行加密,最后輸出進(jìn)行base64編碼,
上圖的xn()就是在進(jìn)行base64編碼。
如果大家對(duì)javascript的RSA加密不熟悉,可以推薦看一下 https://github.com/travist/jsencrypt/blob/master/src/jsencrypt.js。
等你看完了這個(gè)開(kāi)源項(xiàng)目,你會(huì)發(fā)現(xiàn),百度使用的RSA加密函數(shù)和上面的連命名幾乎一樣,這也就是為什么能這么快分析出RSA加密的原因。
<h4>3.4</h4> 如何使用python進(jìn)行RSA加密呢
采用的是RSA加密方式:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
password='xxxxxxxx'with open('pub.pem') as f:
pubkey=f.read()
rsakey=RSA.importKey(pubkey)
cipher=PKCS1_v1_5.new(rsakey)
cipher_text=base64.b64encode(cipher.encrypt(password))printcipher_text
<h4>3.5 </h4>
由于之前安裝了pyv8,所以不把gid,callback等js函數(shù)翻譯成python了,翻譯過(guò)來(lái)也很簡(jiǎn)單,如果你電腦上沒(méi)裝pyv8,就試著翻譯一下。
function callback()
{return'bd__cbs__'+Math.floor(2147483648 * Math.random()).toString(36)
}
function gid(){return'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(e) {vart = 16 * Math.random() | 0,
n= 'x' == e ? t : 3 & t | 8;returnn.toString(16)
}).toUpperCase()
}
<h4>3.6</h4> 似乎還有驗(yàn)證碼沒(méi)說(shuō),其實(shí)就是兩個(gè)鏈接,一個(gè)是獲取驗(yàn)證碼的鏈接,一個(gè)是檢測(cè)驗(yàn)證碼是否正確的鏈接。驗(yàn)證碼獲取很簡(jiǎn)單,這里就不詳細(xì)說(shuō)了。下面我會(huì)把整個(gè)登錄的源代碼,貼在下面有興趣的,可以去玩一下。
<h4>總結(jié):</h4>
下面我用python模擬了一下登錄,使用了requests和pyv8(其實(shí)想偷懶),代碼如下:
#coding:utf-8
import base64
import json
import re
from Crypto.CipherimportPKCS1_v1_5
from Crypto.PublicKeyimportRSA
import PyV8
import requests
import time
if__name__=='__main__':
s=requests.Session()
s.get('http://yun.baidu.com')
js='''function callback(){
return 'bd__cbs__'+Math.floor(2147483648 * Math.random()).toString(36)
}
function gid(){
return 'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (e) {
var t = 16 * Math.random() | 0,
n = 'x' == e ? t : 3 & t | 8;
return n.toString(16)
}).toUpperCase()
}'''ctxt=PyV8.JSContext()
ctxt.enter()
ctxt.eval(js)
###########獲取gid#############################3
gid =ctxt.locals.gid()
###########獲取callback#############################3
callback1 =ctxt.locals.callback()
###########獲取token#############################3
tokenUrl="https://passport.baidu.com/v2/api/?getapi&tpl=netdisk&subpro=netdisk_web&apiver=v3"\"&tt=%d&class=login&gid=%s&logintype=basicLogin&callback=%s"%(time.time()*1000,gid,callback1)
token_response=s.get(tokenUrl)
pattern= re.compile(r'"token"\s*:\s*"(\w+)"')
match=pattern.search(token_response.text)
if match:
token= match.group(1)
else:
raise Exception
###########獲取callback#############################3
callback2 =ctxt.locals.callback()
###########獲取rsakey和pubkey#############################3
rsaUrl ="https://passport.baidu.com/v2/getpublickey?token=%s&"\"tpl=netdisk&subpro=netdisk_web&apiver=v3&tt=%d&gid=%s&callback=%s"%(token,time.time()*1000,gid,callback2)
rsaResponse=s.get(rsaUrl)
pattern= re.compile("\"key\"\s*:\s*'(\w+)'")
match=pattern.search(rsaResponse.text)
if match:
key= match.group(1)print key
else:
raise Exception
pattern= re.compile("\"pubkey\":'(.+?)'")
match=pattern.search(rsaResponse.text)
if match:
pubkey= match.group(1)print pubkey
else:
raise Exception
################加密password########################3
password ='xxxxxxx'#填上自己的密碼
pubkey= pubkey.replace('\\n','\n').replace('\\','')
rsakey=RSA.importKey(pubkey)
cipher=PKCS1_v1_5.new(rsakey)
password=base64.b64encode(cipher.encrypt(password))print password
###########獲取callback#############################3
callback3 =ctxt.locals.callback()
data={'apiver':'v3','charset':'utf-8','countrycode':'','crypttype':12,'detect':1,'foreignusername':'','idc':'','isPhone':'','logLoginType':'pc_loginBasic','loginmerge':True,'logintype':'basicLogin','mem_pass':'on','quick_user':0,'safeflg':0,'staticpage':'http://yun.baidu.com/res/static/thirdparty/pass_v3_jump.html','subpro':'netdisk_web','tpl':'netdisk','u':'http://yun.baidu.com/','username':'xxxxxxxxx',#填上自己的用戶名'callback':'parent.'+callback3,'gid':gid,'ppui_logintime':71755,'rsakey':key,'token':token,'password':password,'tt':'%d'%(time.time()*1000),
}
###########第一次post#############################3
post1_response = s.post('https://passport.baidu.com/v2/api/?login',data=data)
pattern= re.compile("codeString=(\w+)&")
match=pattern.search(post1_response.text)
if match:
###########獲取codeString#############################3
codeString = match.group(1)print codeString
else:
raise Exception
data['codestring']=codeString
#############獲取驗(yàn)證碼###################################
verifyFail =True
while verifyFail:
genimage_param=''
if len(genimage_param)==0:
genimage_param=codeString
verifycodeUrl="https://passport.baidu.com/cgi-bin/genimage?%s"%genimage_param
verifycode=s.get(verifycodeUrl)
#############下載驗(yàn)證碼##################################
with open('verifycode.png','wb') as codeWriter:
codeWriter.write(verifycode.content)
codeWriter.close()
#############輸入驗(yàn)證碼###################################
verifycode = raw_input("Enter your input verifycode:");
callback4=ctxt.locals.callback()
#############檢驗(yàn)驗(yàn)證碼###################################
checkVerifycodeUrl='https://passport.baidu.com/v2/?'\'checkvcode&token=%s'\'&tpl=netdisk&subpro=netdisk_web&apiver=v3&tt=%d'\'&verifycode=%s&codestring=%s'\'&callback=%s'%(token,time.time()*1000,verifycode,codeString,callback4)printcheckVerifycodeUrl
state=s.get(checkVerifycodeUrl)print state.text
if state.text.find(u'驗(yàn)證碼錯(cuò)誤')!=-1:
print'驗(yàn)證碼輸入錯(cuò)誤...已經(jīng)自動(dòng)更換...'
callback5=ctxt.locals.callback()
changeVerifyCodeUrl="https://passport.baidu.com/v2/?reggetcodestr"\"&token=%s"\"&tpl=netdisk&subpro=netdisk_web&apiver=v3"\"&tt=%d&fr=login&"\"vcodetype=de94eTRcVz1GvhJFsiK5G+ni2k2Z78PYRxUaRJLEmxdJO5ftPhviQ3/JiT9vezbFtwCyqdkNWSP29oeOvYE0SYPocOGL+iTafSv8pw"\"&callback=%s"%(token,time.time()*1000,callback5)printchangeVerifyCodeUrl
verifyString=s.get(changeVerifyCodeUrl)
pattern= re.compile('"verifyStr"\s*:\s*"(\w+)"')
match=pattern.search(verifyString.text)
if match:
###########獲取verifyString#############################3
verifyString = match.group(1)
genimage_param=verifyString print verifyString
else:
verifyFail=False raise Exception
else:
verifyFail=False
data['verifycode']=verifycode
###########第二次post#############################3
data['ppui_logintime']=81755
post2_response= s.post('https://passport.baidu.com/v2/api/? login',data=data)
if post2_response.text.find('err_no=0')!=-1:
print'登錄成功'
else:print'登錄失敗'
我把整個(gè)代碼上傳到git上了:https://github.com/qiyeboy/baidulogin.git,
大家可以star和fork。
今天的分享就到這里,如果大家覺(jué)得還可以呀,記得推薦呦