一、观察接口特征
- 获取密钥接口。第一个接口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函数的返回值
五、代码展示
python
import requests, time, hashlib, execjs, json
from ddddocr import DdddOcr
class Spider():
def __init__(self):
self.cookies = "自己复制cookie"
self.headers = "自己的header"
self.ocr = DdddOcr()
with open("./main.js", "r", encoding="utf-8") as f:
self.ctx = execjs.compile(f.read())
def get_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())
return res.json()["rankey"], res.json()["requestId"]
def get_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)
return slide_url, bg_url, encryptKey, y, token
def get_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)
if res:
gap_x = res['target'][0]
print(f'缺口x坐标: {gap_x}')
return gap_x
else:
print("识别失败")
def checkSlidingVerifyCode(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())
return response.json()["data"]['checkCode']
def login(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和token
slide_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)
结语:本文仅供学习使用,如有侵权,立即删除。