声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖!
文章目录
- 一、前言
- 二、抓包流程分析
- 1.刷新页面
- 2.点击按钮进行验证
- 3.滑动验证码
- 三、图片还原
- 四、w值
- ①u值
- ②l值
- ③h值
- ④l中的o值
- aa参数
- passtime参数
- userresponse参数
- rp参数
- 五、结果
一、前言
看一下极验3的流程分析
aHR0cHM6Ly93d3cuZ2VldGVzdC5jb20vU2Vuc2Vib3Q=
二、抓包流程分析
1.刷新页面
①第1个包,register-slide 注册滑块条
返回gt和challenge
②第2个包,gettype.php 获取验证码
携带①包的gt值
③第3个包,get.php
携带的值:
gt: ①返回的
challenge: ①返回的
lang: zh-cn
pt: 0
client_type: web
w:加密值,可为空
callback: geetest_1705890896070
而第二个get请求是携带gt和challenge和一个加密值w,经测试w值可以不带
2.点击按钮进行验证
④第4个包,ajax.php
携带的值
gt: 第1个包返回
challenge: 第1个包返回
lang: zh-cn
pt: 0
client_type: web
w: 可以置空
callback: geetest_1705891494273
返回
⑤第五个包,get.php 获取图片和新challenge
携带参数:
is_next: true
type: slide3
gt: 第1个包返回
challenge: 第1个包返回
lang: zh-cn
https: true
protocol: https://
offline: false
product: embed
api_server: api.geetest.com
isPC: true
autoReset: true
width: 100%
callback: geetest_1705891493735
返回了图片的下载地址,其中包含打乱顺序的底图和滑块图,还有新的challenge
3.滑动验证码
⑥第6个包,ajax.php 校验滑块,w参数包含轨迹,携带gt和challenge
成功返回
失败返回
像前边除了校验的,我们都可以直接请求出来
然后获取到图片后进行图片的还原
三、图片还原
在我们抓包的过程中,我们抓到了图片的包,是一个打乱的图片,一共有两个打乱的图片,一个是完整的底图,一个是有滑块缺口的底图
我们写完前四个流程的代码,第四个包返回的图片的url也是这样
而页面中的图片也是通过canvas渲染上去的
那我们就要想办法还原底图,我们直接勾选canvas断点
点击加载图片按钮,会直接断住,经过跟值会锁定的这个位置,是底图还原的关键代码
这里会得到一个正确顺序的数组,只要根据上诉算法还原即可。这里直接用十一姐大佬的代码
无序图:
还原后
然后识别出距离即可,这里还是用的ddddocr,需要距离-10,因为比实际多算了10px
四、w值
搞完前几部分,只剩下w值,这个w值应该会和滑动的轨迹相关。那我们分析一下这个w值,从滑动的包的第一个堆栈进入
打上断点,然后断住往上找堆栈,很快就会找到这个位置
f值中的\u0077
是w的Unicode编码,它在这里生成
w = h + u
u = r[$_CAHJS(737)]()
l = V[$_CAHJS(392)](gt[$_CAIAK(254)](o), r[$_CAIAK(744)]())
h = m[$_CAIAK(792)](l)
由于极验是动态js,这里的h和u名称是动态变化的,这里暂时是u和h
①u值
先来看u值,进入u值的断点,看到返回的是e
大概就是将this[$_CBGAK(744)](t)
生成的16位字符串,用new U()['encrypt']
进行了加密,我们看一下这个16位的字符串是怎么生成的
进入后
再进,挂住断点发现断不住,需要重新刷新页面可以断住
该值就是一个随机数拼接,自己创建个函数。
创建出16位的字符串后,被new U()['encrypt']
加密,我们看一下new U()
,能看到setPublic,一般RSA才有这些类似方法
而rsa需要两个参数:加密文本+公钥。获取公钥进入setPublic的函数
在这里打断点,刷新一下页面
这个就是公钥
我们用python库实现
import rsa
from binascii import b2a_hex
def rsa_encrypt_text(key, _text: str):
"""
RSA加密
:param key: 公钥的参数
:param _text: 待加密的明文
:return: 加密后的数据
"""
e = int('010001', 16)
n = int(key, 16)
pub_key = rsa.PublicKey(e=e, n=n)
return b2a_hex(rsa.encrypt(_text.encode(), pub_key)).decode()
if __name__ == '__main__':
key = '00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81'
_text = 'd4e0165efa3c7712'
result = rsa_encrypt_text(key, _text)
print(result)
②l值
再看h值,h值参数是l,需要先看l
控制台输出一下各个值,清晰一点
l = V["encrypt"](gt["stringify"](o), r["$_CCEV"]())
h = m["$_FEE"](l)
相当于在 V["encrypt"]
中传入了两个参数,一个是那一个json串,估计是加密值,另一个经过跟栈就是上边生成的随机16个字符串,最后结果是得到一个数组
o值应该包含轨迹加密,暂时先放放,我们先看看 V["encrypt"]
在这里看到iv关键字,是AES加密,那么需要找到key,iv,加密内容
经上边看到iv是0000000000000000
,key是随机16位字符串(与u值中使用的字符串要相等),这里用js实现
var CryptoJS = require("crypto-js");
// 随机生成16位字符串
function get_random_str() {
var random_str = '';
for (var i = 0; i < 4; i++) {
random_str += (65536 * (1 + Math['random']()) | 0)['toString'](16)['substring'](1);
}
return random_str;
}
// aes
function aes_encrypt(key, _iv, value) {
var e = CryptoJS.enc.Utf8.parse(key);
var iv = CryptoJS.enc.Utf8.parse(_iv);
var l = CryptoJS.enc.Utf8.parse(value);
return CryptoJS.AES.encrypt(l, e, {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
iv: iv
})
};
var get_arry_i = function (i, a) {
var o = [];
for (var s = 0; s < a; s++) {
var c = i[s >>> 2] >>> 24 - s % 4 * 8 & 255;
o["push"](c);
}
return o;
};
var value = '{"lang":"zh-cn","userresponse":"cccccc66c6c6688d75","passtime":350,"imgload":64,"aa":"O!)(!!H(xysyttsysAt(!!(Zb--5-Y7*7*96$*8_","ep":{"v":"7.9.0","$_BIo":false,"me":true,"tm":{"a":1680165934423,"b":1680165934777,"c":1680165934777,"d":0,"e":0,"f":1680165934429,"g":1680165934433,"h":1680165934440,"i":1680165934440,"j":1680165934535,"k":1680165934442,"l":1680165934535,"m":1680165934772,"n":1680165934774,"o":1680165934779,"p":1680165935041,"q":1680165935041,"r":1680165935044,"s":1680165935070,"t":1680165935070,"u":1680165935071},"td":-1},"h9s9":"1816378497","rp":"56f9ced1740a4656d430a1dfa0bd697b"}';
var aes_result = aes_encrypt(get_random_str(), '0000000000000000', value);
var u = get_arry_i(aes_result["ciphertext"]["words"], aes_result['ciphertext']['sigBytes']);
console.log(u);
③h值
h = m[$_CAIAj(783)](l)
断点进入
再进去
这里我们抠出这个函数即可,把这个函数对象的方法都扣下来
运行一下,缺什么补什么
修改一下获取h的方法,变量先用浏览器抠的固定值
都抠出来
运行一下
④l中的o值
看刚才l的o值
示例:
{
"lang": "zh-cn",
"userresponse": "cccccc66c6c6688d75",
"passtime": 350,
"imgload": 64,
"aa": "O!)(!!H(xysyttsysAt(!!(Zb--5-Y7*7*96$*8_",
"ep": {
"v": "7.9.0",
"$_BIo": false,
"me": true,
"tm": {
"a": 1680165934423,
"b": 1680165934777,
"c": 1680165934777,
...省略
},
"td": -1
},
"h9s9": "1816378497",
"rp": "56f9ced1740a4656d430a1dfa0bd697b"
}
- h9s9:【可以写死】随机参数影响不大
- imgload:【可以写死】图片加载时间,随机生成
- ep:【可以写死】涉及v和tm,v是js文件的版本,tm和
window["performance"]["timing"]
相关,可以根据Performance.timing
时间产生的先后顺序以及时间间隔,用当前的时间戳减去相应的值来模拟。 - aa:对轨迹和响应的c和s参数进行了加密
- passtime:轨迹滑动总时长
- rp://gt + 32 位 challenge + passtime,再经过 MD5 加密
- userresponse: 滑动距离x + challenge 的值
这些参数往上翻一点,可以看到生成的地方
aa参数
这里看一下aa,是第二个参数,追上一个栈
就是这里的l,
l是由上一行的函数生成,需要三个参数,其中后两个是我们曾经抓包的响应c和s
第一个参数就是加密后的轨迹,而前边的部分就是轨迹数组,应该是x,y,t
进去这个函数看看怎么加密的轨迹
进来到这里使用了轨迹列表
看最后跟到这里变成了加密
我们直接把这个方法抠出来,传一个轨迹
把使用的地方,改为我们的轨迹
然后抠一个浏览器的轨迹运行,缺什么补什么,注意抠ct的时候有个坑需要补一下
最后抠出来这几个
拿浏览器的轨迹试验对比一下
到此轨迹的加密就获取出来了
接下来继续,后边的参数是c和s,直接断点抠下来就可以
至此这个l就是我们的aa参数
passtime参数
取轨迹的最后一个列表的的最后一位数
passtime = track[-1][-1] #滑动时间
userresponse参数
t为我们轨迹的滑动x距离,另外一个为challenge,然后H函数
抠出来,修改亿点点
function H(t, e) {
var $_CJES = mwbxQ.$_Cg
, $_BEGI_ = ['$_BEHCp'].concat($_CJES)
, $_BEHAO = $_BEGI_[1];
$_BEGI_.shift();
var $_DAJEI = mwbxQ.$_DW()[9][13];
for (; $_DAJEI !== mwbxQ.$_DW()[0][11];) {
switch ($_DAJEI) {
case mwbxQ.$_DW()[6][13]:
for (var n = e[$_CJES(187)](-2), r = [], i = 0; i < n[$_CJES(192)]; i++) {
var o = n["charCodeAt"](i);
r[i] = 57 < o ? o - 87 : o - 48;
}
n = 36 * r[0] + r[1];
var s, a = Math[$_CJES(158)](t) + n, _ = [[], [], [], [], []], c = {}, u = 0;
i = 0;
$_DAJEI = mwbxQ.$_DW()[6][12];
break;
case mwbxQ.$_DW()[0][12]:
for (var l = (e = e[$_CJES(187)](0, -2))['length']; i < l; i++)
c[s = e[$_CJES(125)](i)] || (c[s] = 1,
_[u][$_CJES(137)](s),
u = 5 == ++u ? 0 : u);
var h, f = a, d = 4, p = '', g = [1, 2, 5, 10, 50];
while (0 < f)
0 <= f - g[d] ? (h = parseInt(Math['random']() * _[d]['length'], 10),
p += _[d][h],
f -= g[d]) : (_[$_CJES(115)](d, 1),
g[$_CJES(115)](d, 1),
d -= 1);
return p;
break;
}
}
}
rp参数
这个值在w生成的上一点点
结果32位
有点像md5,试验一下
至此,所有参数搞定
五、结果
效果展示
其他情况
// challenge 不对
geetest_xxxxxxxxxxxxx({"status": "error", "error": "illegal challenge", "user_error": "网络不给力", "error_code": "error_23"})
// w 生成不对
geetest_xxxxxxxxxxxxx({"status": "error", "error": "param decrypt error", "user_error": "网络不给力", "error_code": "error_03"})
// 滑动验证没有轨迹
geetest_xxxxxxxxxxxxx({"status": "error", "error": "not proof", "user_error": "网络不给力", "error_code": "error_21"})
// 轨迹、缺口距离、参数问题
geetest_xxxxxxxxxxxxx({"success": 0, "message": "fail"})
geetest_xxxxxxxxxxxxx({"success": 0, "message": "forbidden"})