一、观察接口特征
- 获取密钥接口。第一个接口getRankey:
- 请求载荷分析:
- 响应值分析:
2.获取滑块图片接口。 跳过isShowSlide接口直接看getSlidingVerifyCode接口,因为返回值无重要信息: - 请求载荷里面含requestId,大概率就是第一次分析的接口中返回回来的
- 响应值:很明显能看见返回了很多重要信息,可见这里是验证码的核心接口,因为含有背景和滑块图的地址。
- 验证滑块接口。滑动滑块,分析滑块验证接口checkSlidingVerifyCode:
- 请求载荷:
- 响应载荷对比(失败和成功):
- 登录验证接口。accountLogin:
二、定位每个接口的sign值生成位置并分析其加密逻辑
- 直接全局搜索sign
- 直接点进去断点观察
- 接着对r进行了一次编码,但得到的新r没有变化,可以不用管
- 对r进行了标准的MD5加密
- 将md5加密后的字符串传入Y函数,断点跟进Y函数内部可以看见是一个AES对称加密算法,上帝视角后面的sign值都走的这个逻辑,因为后面都是要挨个断点调试的,所以就能看出来。所以我们直接将这个AES加密过程拿到本地js文件中保存下来,方便后续纯nodejs调用。
三、定位checkSlidingVerifyCode接口中的point_json
- 还是直接全局搜索point_json
- 定位到point_json生成位置,就是将滑块到缺口的横纵坐标对象和getSlidingVerifyCode接口返回的encryptKey值当参数传给Y函数进行加密。
- 注意x的值,浏览渲染比例是350,所以它除的是350,而我们拿到的图片比例要除以408。
四、理清逆向思路
我们发现:
- 关键有4个接口:
- getRankey接口获取密钥 rankey值 和 requestId值
- getSlidingVerifyCode接口获取滑块、背景图片,encryptKey、token和y值
- checkSlidingVerifyCode接口获取 checkCode 值
- accountLogin接口做最后的登录处理
- 每个接口请求载荷中:
- 均需要时间戳模拟,直接上time库
- permanent_id均从cookie中直接取值
- 后3个接口中的requestId值均从第一个接口的返回值中拿
- 每个接口的sign值都是将该接口的请求参数拼接后经过md5加密,再和rankey值一起(除getRankey接口传空字符串)传入Y函数,也就是AES对称加密函数加密得到的值
- checkSlidingVerifyCode接口中的point_json值就是将明文密码和’qrcode_p@ssw0rdK’传入Y函数的返回值
五、代码展示
importrequests,time,hashlib,execjs,jsonfromddddocrimportDdddOcrclassSpider():def__init__(self):self.cookies="自己复制cookie"self.headers="自己的header"self.ocr=DdddOcr()withopen("./main.js","r",encoding="utf-8")asf:self.ctx=execjs.compile(f.read())defget_rankey_requestId(self):url="https://login.dangdang.com/api/customer/loginapi/getRankey"ts=int(time.time()*1000)data={"t":f'{ts}',"ct":"pc","permanent_id":self.cookies["__permanent_id"],"requestId":"","sign":"dxuL7I267hpFvpCnlF0IvrEpJz3GO2NkHMGo2A5LdukezDpDPhqo+7lqMpklKBlS"}# 检测了ct开头params_data=f"ct=pc&permanent_id={data['permanent_id']}&t={ts}"sign=self.ctx.call("aes",hashlib.md5(params_data.encode()).hexdigest(),'')data["sign"]=sign res=requests.post(url,headers=self.headers,cookies=self.cookies,data=data)print(res.json())returnres.json()["rankey"],res.json()["requestId"]defget_VerifyImage(self,rankey,requestId):url="https://login.dangdang.com/api/customer/loginapi/getSlidingVerifyCode"ts=int(time.time()*1000)data={"t":f'{ts}',"ct":"pc","permanent_id":self.cookies["__permanent_id"],"requestId":requestId,"situation":"login",}params_data=f"ct=pc&permanent_id={data['permanent_id']}&requestId={requestId}&situation=login&t={ts}"sign=self.ctx.call("aes",hashlib.md5(params_data.encode()).hexdigest(),rankey)data["sign"]=sign response=requests.post(url,headers=self.headers,cookies=self.cookies,data=data).json()slide_url=response['data']['slideImg']bg_url=response['data']['bgImg']encryptKey=response['data']['encryptKey']y=response['data']['y']token=response['data']['token']print(response)returnslide_url,bg_url,encryptKey,y,tokendefget_move_x(self,slide_url,bg_url):slide_bytes=requests.get(slide_url).content bg_bytes=requests.get(bg_url).content res=self.ocr.slide_match(slide_bytes,bg_bytes,simple_target=True)ifres:gap_x=res['target'][0]print(f'缺口x坐标:{gap_x}')returngap_xelse:print("识别失败")defcheckSlidingVerifyCode(self,x,y,encryptKey,requestId,token,rankey):url="https://login.dangdang.com/api/customer/loginapi/checkSlidingVerifyCode"ts=int(time.time()*1000)data={"t":f'{ts}',"ct":"pc","permanent_id":self.cookies["__permanent_id"],"requestId":requestId,"situation":"login","verifyToken":token,"slide_cost_time":"108","need_new_verifydata":"0","sign":"uPjqm2GFKsc2NxfEE5oZreAFgvmlZB9RTsNNgUXJAZ6TUiQS9ClQ8w02BLTquYMj"}t={"x":x/408,"y":y}point_json=self.ctx.call("aes",json.dumps(t,separators=(',',':')),encryptKey)data['point_json']=point_json params_data=f"ct=pc&need_new_verifydata=0&permanent_id={data['permanent_id']}&point_json={data['point_json']}&requestId={requestId}&situation=login&slide_cost_time=108&t={ts}&verifyToken={token}"sign=self.ctx.call("aes",hashlib.md5(params_data.encode()).hexdigest(),rankey)data['sign']=sign response=requests.post(url,headers=self.headers,cookies=self.cookies,data=data)print(response.json())returnresponse.json()["data"]['checkCode']deflogin(self,requestId,token,checkCode,rankey):url="https://login.dangdang.com/api/customer/loginapi/accountLogin"ts=int(time.time()*1000)username='123456789'psw='123456'password=self.ctx.call("aes",psw,"qrcode_p@ssw0rdK")data={"t":f"{ts}","ct":"pc","permanent_id":self.cookies["__permanent_id"],"requestId":requestId,"username":username,"password":password,"autokey":"off","token":token,"check_code":checkCode,"check_code_type":"1",}params_data=f"autokey=off&check_code={checkCode}&check_code_type=1&ct=pc&password={data['password']}&permanent_id={data['permanent_id']}&requestId={requestId}&t={ts}&token={token}&username={username}"sign=self.ctx.call('aes',hashlib.md5(params_data.encode()).hexdigest(),rankey)data['sign']=sign response=requests.post(url,headers=self.headers,cookies=self.cookies,data=data)print(response.json())if__name__=='__main__':s=Spider()# 第一个接口拿密钥rankey,requestId=s.get_rankey_requestId()# 第二个接口拿图片地址,encryptKey,y和tokenslide_url,bg_url,encryptKey,y,token=s.get_VerifyImage(rankey,requestId)# ocr识别 x 距离x=s.get_move_x(slide_url,bg_url)# 第三个接口获取checkCode值checkCode=s.checkSlidingVerifyCode(x,y,encryptKey,requestId,token,rankey)# 第4个接口直接登录s.login(requestId,token,checkCode,rankey)结语:本文仅供学习使用,如有侵权,立即删除。