为了账号安全,请及时绑定邮箱和手机立即绑定

东欧社交巨头 “洗牌” 谜题,乱序验证码逆向拆解全记录

标签:
Python

Q6fbbG.png

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

逆向目标

  • 目标:VK 登录验证码

  • 网址:aHR0cHM6Ly92ay5jb20v

抓包分析

打开网址,选择邮箱登录,随便输入一个未注册的邮箱号,比如 aaw2,方便触发风控验证。输入完点登录会重定向到新的登录页面,重新输入,正常会显示 账号未找到,多点几次就会触发风控,登录有两种验证,如下图所示,分别为一点即过的和滑动拼图,这拼图人都不好滑对:

QFlSL9.png

若触发验证,auth.validateAccount 接口响应返回的 errcode 为 14,redirect_uri 中的 session_token 后续接口会用到:

QFlUGY.png

该接口的请求参数中,login 为输入的邮箱号,client_id 是定值,device_id 加密生成,后文分析,auth_token 是登录重定向接口响应返回的:

QFlCv7.png

not_robot_captcha 接口的响应内容中,show_captcha_type 为触发的验证码类型,滑动拼图为 slider,一点即过的为 checkbox,可以此区分触发的类型,captcha_settings 中的参数会用于后续获取图片的接口,其余部分后文分析:

QFlJnI.png

captchaNotRobot.getContent 接口返回的图片链接,请求参数中的 adFp 如何加密生成,响应返回的 steps 有何作用,后文分析:

QFljLV.png

验证接口为 captchaNotRobot.check,请求参数中,debug_info 为固定值在 not_robot_captcha.js 文件中,hash 加密了一些环境参数,answer 编码了拼图的还原顺序,这些后文都会逐一分析:

  • 参数值异常:{"response":{"status":"ERROR"}}

  • 还原顺序错误:{"response":{"redirect":"","show_captcha_type":"slider","status":"BOT","success_token":""}}

  • 验证通过:{"response":{"redirect":"","show_captcha_type":"","status":"OK","success_token":"eyJ..."}}

逆向分析

device_id

auth.validateAccount 接口跟栈到 auth.js 文件中,直接搜索 device_id 会发现有 100 个匹配项,一个个下断调试显然不现实。跟栈到下图处,此时的 s 中 device_id 已经生成了,s 对应 e.bodyParams,e 是传进来的参数,因此,在最前面下断:

QFHnxs.png

刷新网页,即会断住,但此时还未传入 device_id,下步断点断到该值传入时为止:

QFHfk7.png

向上跟栈到下图处,此时 NQ.deviceIddevice_id 参数的值:

QFHiKI.png

搜索 NQ 可定位到如下代码处,首次生成后会通过 localStorageService 存储到浏览器,代码中有很多类似的位置,这里不是生成前的位置,但是不影响分析,主要走到 Ln 中:

r = TQ.authLocalStorageServiceEverywhere ? NQ.deviceId : Ln();

跟进到 Ln() 中,逻辑如下:

function Ln() {
let e;
try {
e = localStorage.getItem(“deviceId”)
} catch (e) {}
if (!e) {
e = on();
try {
localStorage.setItem(“deviceId”, e)
} catch (e) {}
}
return e
}

remove 掉缓存中的 deviceId,再刷新网页,即会断到 on 中处:

Qeyk0B.png

on 中就是其算法的生成逻辑:

let t = “”
, n = 21;
for (; n–; )
t += “useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict”[64 * Math.random() | 0];
console.log(t);

adFp

adFp 是获取背景图片链接接口的加密参数,从该接口的堆栈跟到 not_robot_captcha.js 文件中,ctrl + s 局部搜索下 adFp,发现就一个位置,下断后重新获取验证图片即会断住。如下图所示,此时 window.rb_sync 中 adFp 的值已经生成了:

N9m78L.png

再回到 Network 看接口,ctrl + s 搜索下 adFp 参数的值,找到第一次生成的位置,出现在 /csp 接口的请求参数中:

N9mErJ.png

blocked-uri 中的接口在首页 vk.com 生成二维码时就会触发,因此该值需要在首页调试,否则都是从封装的 IndexedDB 中取已存储的值,无法定位生成逻辑。

/fp/?id= 堆栈跟到 sync-loader.js 文件中,在 return e(r(t(n))); 处下断点,走的异步流程,刷新网页断住后单步往下跟,跟到下图处就会发现熟悉的 window.rb_sync,此时 id 的值还是 undefined:

N9mDj4.png

接着往下跟,到下图处就会发现,i 即 adFp 参数的值,因为 o 为 undefined,所以生成逻辑就在 Kr() 中:

N9mSph.png

跟进去,逻辑如下:

const Kr = window.crypto ? function () {
var n = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : 21;
return crypto.getRandomValues(new Uint8Array(n)).reduce((function (n, t) {
return n + ((t &= 63) < 36 ? t.toString(36) : t < 62 ? (t - 26).toString(36).toUpperCase() : t > 62 ? “-” : “_”)
}
), “”)
}

也可用 python 复现:

def get_ad_fp(n=21):
result = []

for _ in range(n):

使用 os.urandom 生成密码学安全的随机字节

random_byte = ord(os.urandom(1))

t &= 63 (保留低6位)

t = random_byte & 63

JavaScript 的 toString(36) 逻辑

if t < 36:

使用 Python 的 base36 转换

if t < 10:
result.append(str(t))
else:
result.append(chr(ord(‘a’) + t - 10))
elif t < 62:

(t - 26).toString(36).toUpperCase()

实际上就是大写字母 A-Z

result.append(chr(ord(‘A’) + t - 36))
elif t > 62:
result.append(’-’)
else: # t == 62
result.append(’_’)

return ‘’.join(result)

browser_fp

从验证接口 captchaNotRobot.check 跟到 not_robot_captcha.js 文件中,搜索后发现仅有两处,都打上断点,刷新网页断住,n.analyticsModel.fingerprintbrowser_fp 参数的值:

N9mUG9.png

刷新网页,断到上面的 n.analyticsModel = e 处,此时 fingerprint 值还未生成,也就是生成流程在中间这一块了:

N9m1tY.png

这里就不单步慢慢跟了,搜索 analyticsModel.fingerprint 定位到下图处,t.visitorId 就是目标值:

N9mPoH.png

t 定位在上面一行,跟到 e.get 中去,发现就是 Of(this.components) 生成了 browser_fp 参数的值,this.components 是一堆环境参数,如 audio、canvas、plugins 等等:

N9mYyZ.png

Of 其实就是高度混淆后的 MurmurHash3(x64 128-bit)哈希算法的实现,特征点很多,以输出结构为例(4 × 32bit):

(“00000000” + (a[0] >>> 0).toString(16)).slice(-8)
(“00000000” + (a[1] >>> 0).toString(16)).slice(-8)
(“00000000” + (s[0] >>> 0).toString(16)).slice(-8)
(“00000000” + (s[1] >>> 0).toString(16)).slice(-8)

MurmurHash3 是一种非加密型哈希函数,由 Austin Appleby 在 2008 年设计。它以其高性能、良好的分布性和低碰撞率而闻名,广泛应用于哈希表、布隆过滤器、缓存键等场景。其比 MD5、SHA-1 等加密哈希快得多,但他不能称为加密算法,并非以安全为设计目标。

感兴趣的小伙伴可以去了解下该算法,网页抠出来的 Of 算法以及 python 复现的代码都会分享到知识星球中,以供学习交流。

connectionDownlink、connectionRtt 都是网络相关的参数,一个是下载速度或下行带宽,一个统计数据包从发送端到接收端再返回发送端所需的总时间,至此请求参数分析完成了。

图像识别

验证接口请求参数中的 answer 就和滑动距离、轨迹有关,其就定义在 browser_fp 下面,base64 编码:

wO(JSON.stringify({value: t}))

N9dFzZ.png

t 就是小图的移动路径,是怎么生成的呢?向上跟栈到下图处,this.checkResult 中的 e.value 就是 t 值,type 为 slider:

Nkq1jf.png

直接搜索 checkResult 即可定位到位置:

NkqRpc.png

再网上跟栈就能到 const IS 中,这里就是滑动拼图的组件,分析这段代码,再结合 captchaNotRobot.getContent 接口返回的 steps,就能知道 t 的生成逻辑,对比如下:

// steps
const steps = [
5,13,2,24,1,19,20,5,6,1,14,5,22,24,1,20,16,24,6,8,
2,9,12,13,24,17,16,4,2,22,14,23,16,10,14,2,5,12,23,
15,24,21,17,2,13,18,22,8,2,9,0,8,19,14,9,2,16,15,10,
23,3,21,16,2,13,20,15,0,14,16,4,16,1,5,11,2,24,2,19,
23,16,3,22,7,2,7,19,13,14,19,20,1,9,22,17,16,14,14,7,2
];

// 滑动正确的结果
const t = [
13,2,24,1,19,20,5,6,1,14,5,22,24,1,20,16,24,6,8,2,
9,12,13,24,17,16,4,2,22,14,23,16,10,14,2,5,12,23,15,
24,21,17,2,13,18,22,8,2,9,0,8,19,14,9,2,16,15,10,23,
3,21,16,2,13,20,15,0,14,16,4
];

综上,根据 steps 路径移动小图,移到拼成的点为止,截取 steps 就能得到正确的 t 值:

// 拖动滑块的距离
const sliderValue = 36; // 0 - 49

// 生成路径 t
// P = i.slice(0, 2 * w)
const t = steps.slice(1, sliderValue % 2 === 0 ? 2 * sliderValue - 1 : 2 * sliderValue + 1);
// t = steps[1: (2 * slider_value - 1) if slider_value % 2 == 0 else (2 * slider_value + 1)]

剩下的就是需要分析,移动到哪拼图能被还原,找到 “还原点”。

我们将图片切成 25 个块(5×5),根据分析,拖动滑块,每次都是按照移动序列执行 “两两 swap”(1 2、3 4、5 6),每一步都计算整张图的边缘总不连续度:

  • 右邻边:block[i].right vs block[i+1].left

  • 下邻边:block[i].bottom vs block[i+5].top

找全程最优状态,当整张图的边缘误差达到全局最小值时,就说明所有块都回到了原始相对位置,这一步就是 “还原点”。

连续性评分,公式描述如下:

相关还原算法会分享到知识星球中,以供学习交流,还原效果如下:

NTEps9.png

结果验证

图片描述

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消