逆向万例| No.00001【滑块篇】喜某拉某web端登录滑块逆
声明:本文仅供学习交流用途,切勿用于非法行为,否则由此产生的一切后果均与作者本人无关。未经授权禁止转载本文。如有侵权,请及时通过本人公众号「逆向扬」联系删除。
0x1 开始之前
网站地址:YUhSMGNITTZMeTkzZDNjdWVHbHRZV3hoZVdFdVkyOXRMdz09
(提示:如果解密不成功,可以试着多考虑一步)
本文目标:逆向该网站登录滑块验证
进入网站后,选择密码登录,在输入账号(手机号或邮箱)和密码后,会弹出一个滑块验证:
这个网站的滑块难度属于入门级别,因此很适合逆向新手将这个作为滑块逆向的入门例子。
下面我们来逆向这个滑块。
0x2 抓包分析
2.1 整体流程
首先我们对网站的整个登录请求过程进行分析。打开 chrome 的开发者工具,分析一下网络包。
在点击登录按钮后,会发送请求,用于获取滑块的底图和缺口图片:
滑块后,会发送一个 slider 请求。如果没滑到正确位置,会返回验证失败的响应信息:
若滑到了正确位置,则会返回验证成功的信息和一个 token 值:
这个 token 会被放到 cookie 的 fds_otp
中,并发送一个请求,获取之后请求登录接口所需要用到的 nonce 参数值:
最后,会发起对登录接口的请求,不过在本文中不讲解这一步。关于如何逆向该网站登录接口的参数,将在之后的文章中讲到:
2.2 关键包分析
这里最关键的是滑块验证请求 slider 。
这个包的请求参数分析:
- bpId:固定值,到时候直接写死就行。
- captchaText:关键参数,格式是:
"鼠标在x方向的滑动距离,鼠标在y方向的滑动距离"
。 - sessionId:固定值,直接写死就行。
- startTime:滑动开始时间。
- startX:滑动开始时,鼠标与浏览器左边界的距离。这个参数不是很关键,可以随便填,但有范围要求,只要在一个合理范围内即可,我简单验证过。
- startY:滑动开始时,鼠标与浏览器下边界的距离。值的要求同 startX 。
- type:固定值,写死就行。
因此,在这里,我们需要弄明白的是captchaText
这个参数是如何生成的,只要能逆向这个参数,就能拿下这个网站的滑块。下面我们开始分析。
0x3 验证码底图获取
通过 get 这个请求可以获取到验证码的底图,获取到底图后,需要注意网站会以 0.8 的比例显示滑块验证码图片:
获取底图的代码:
payload = {
"bpId": "139",
"sessionId": "xm_leqnng8w237jib"
}
r = requests.get("https://mobile.ximalaya.com/captcha-web/check/slide/get",
headers=headers,
params=payload,
cookies=cookie)
print(r.json())
data = r.json()["data"]
image_url_list = {"fg": data["fgUrl"], "bg": data["bgUrl"]}
for k, v in image_url_list.items():
rr = requests.get(v, headers=headers)
with open(f"{k}.png", "wb") as f:
f.write(rr.content)
拿到底图后,我们就可以用验证码识别库,比如 ddddocr 。可以直接拿着原图识别,而不需要事先将滑块图片和底图缩小为原来的 0.8,然后将通过原图识别出来的距离缩小为原来的 0.8 倍即可。
要注意这里有个小坑,captchaText 的 x 值时,你直接写这个识别出来的距离再乘上 0.8 是不行的,得加上一个偏移量,在 10 像素左右都行,至于偏移量的来源,我目前还没不知道。如果有知道的大佬,欢迎在评论区留言。
0x4 captchaText 参数分析
废话不多说,直接开始调试分析。我们需要定位到这个参数是如何生成的。我个人目前比较习惯直接通过请求的调用栈来做定位。点击 slider 请求,可以找到下图的方法中出现了我们想要分析的参数:
我们可以看到这里的实参 o 是从外部传到方法中的,因此继续往上跟栈,一步步跟,最终可以找到 captchaText 参数的生成位置:
captchaText 的生成规则如下:
其中,c 值就是滑动滑块时鼠标在 y 方向的滑动距离,a 值则是滑动滑块时鼠标在 x 方向的滑动距离。d.ZOOM 则是图像的缩小比例。
按照常规来说,直接传递 a 值和 c 值给网站后台即可通过滑块验证,但是网站前端在 getSliderLeft 方法中还对 a 值进行了一些额外的计算处理。
getSliderLeft 方法如下,其中的计算逻辑复现很简单,因此就不细讲了:
用 python 复现一下 getSliderLeft 方法,供大家参考:
def get_img_left(t):
return -12 * 0.8 + (t + 10) * (380 - 84.8 + 24 * 0.8) / (380 - 40)
def get_slider_left(t):
return int(get_img_left(t) / 0.8 + 44)
最后贴上滑块验证的代码:
def verify_slide():
payload = {
"bpId": "139",
"sessionId": "xm_leqnng8w237jib"
}
r = requests.get("https://mobile.ximalaya.com/captcha-web/check/slide/get",
headers=headers,
params=payload,
cookies=cookie)
print(r.json())
data = r.json()["data"]
image_url_list = {"fg": data["fgUrl"], "bg": data["bgUrl"]}
for k, v in image_url_list.items():
rr = requests.get(v, headers=headers)
with open(f"{k}.png", "wb") as f:
f.write(rr.content)
det = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)
with open('fg.png', 'rb') as f:
fg_bytes = f.read()
with open('bg.png', 'rb') as f:
bg_bytes = f.read()
res = det.slide_match(fg_bytes, bg_bytes, simple_target=True)
print(res)
def get_img_left(t):
return -12 * 0.8 + (t + 10) * (380 - 84.8 + 24 * 0.8) / (380 - 40)
def get_slider_left(t):
return int(get_img_left(t) / 0.8 + 44)
x = int((res["target"][0] + 25) * 0.8)
distance = int(get_slider_left(x))
slide_payload = {
"bpId": 139,
"sessionId": "xm_leqnng8w237jib",
"type": "slider",
"captchaText": f'{distance},0',
"startX": 563,
"startY": 357,
"startTime": int((time.time() - 2) * 1000)
}
print(slide_payload)
slide_r = requests.post("https://mobile.ximalaya.com/captcha-web/valid/slider",
headers=headers,
cookies=cookie,
json=slide_payload)
print(slide_r.text)
print(slide_r.json()["token"])
验证
可以看到滑块验证通过,并且拿到了 token 值: