网易易盾破解补充篇
之前写了两篇易盾破解的文章,分别讲了fp、cb、data
等参数的破解,以及额外的图像识别,但是实际上整个破解过程并不完整,我在文章中有提到watchman
这块没有破解,这篇文章对此进行补充。
acToken
我在之前的文章中一直把acToken
参数当做定值来处理的,但是实际上其并不是定值,我们先来看下其生成过程,点击check
请求的调用栈(get请求是没有acToken
参数的)
跳转到下面这里
然后在这行前面打上断点,滑动图片进行验证,可以看到右边的调用栈,这里不断去找acToken
的源头(不细讲了,需要耐心),大概在我右边框的红框这个请求中,无法再进行上溯,找到的地方是这样的:
这里t._arg
就是acToken
的值,往上看一点,关键就是这个e的值,然后我们在这里打上断点(如图所示),注意这里要清除缓存,然后再滑动验证码定位在断点处
当看到e的值为acToken
的值时,看右边的调用栈,不断寻找值的来源,在我右边红框的请求位置可以看到其跳入了watchman.min.js
,位置内容是这样的
1 | bc({ |
这个就是acToken
,你可以在console中运行这段代码验证。
这些方法的名称不要过于纠结,这些名称是随机生成的,有可能每次调试都不一样
那么我们接下来的重点就放到watchman
文件了,前面的断点可以全部清除,在这里打上新的断点调试,然后点进bc
方法来分析一下:
bc
方法有一个参数a,a有两个属性C
和ka
,其中ka
=false;再往下重新定义了一个a,包含三个属性,前面说了bc
方法执行完的结果就是acToken
的值,那么这里的返回值应该就是acToken
的值,即
1 | La(JSON[b[392]](a)) |
b[392]就是stringify
,所以这里真正做的就是
1 | var a = { |
于是存在三个问题,一个是传入参数的C属性哪里来?二是da().k(qa)
运行结果是啥?三是La
方法做了哪些操作。
这里La
方法就进去扣就完事了,直接贴出来:
1 | var b = ["callPhantom", "floor", "__driver_unwrapped", "beta", "on", "RENDERER", "src", "DevalVRXCtrl.DevalVRXCtrl.1", "globalCompositeOperation", "addBehavior", " ", "spawn", "HIGH_INT", "rangeMax", "batteryInterval", "CAT_WEBGL", "(function(){return 123;})();", "20030107", "stringify", "compatMode", "Windows Phone", "isPrototypeOf", "extensions:", "🧥🐶🍏⚽️✂🈲🚗⌚️❤️🏁▶", " is not a function", "NEWatchmanError", "00000000", "removeChild", "webgl aliased line width range:", "webgl max texture size:", "webgl vertex shader low int precision rangeMax:", "useProgram", "domAutomation", "hostname", "XDomainRequest", "Watchman", "requestStart", "phantom.injectJs", "clearTimeout", "ERROR", "touchend", "state", "webgl max anisotropy:", "ShockwaveFlash.ShockwaveFlash", "height", "webgl vertex shader medium int precision rangeMin:", "EXT_texture_filter_anisotropic", "/v2/collect", "AgControl.AgControl", "touchmove", "d73f85d653b74a259fe620aac656670b", "decodeURI", "clientHeight", "Firefox", "input", "123", "__webdriver_script_func", "WMPlayer.OCX", "72px", "webgl vertex shader low float precision:", "propertyIsEnumerable", "safari", "onreadystatechange", "behavior api response wrong", "document", "dns_city", "webgl fragment shader high float precision rangeMax:", "deviceorientation", "-9999px", "userLanguage", "businessKey is illegal", "pointermove", "arc", "SHADING_LANGUAGE_VERSION", "min", "attack", "LOW_FLOAT", "sessionStorage", "Object prototype may only be an Object: ", "compileShader", "iframe", "escape", "mspointermove", "systemLanguage", "languages", "Skype.Detection", "2d", "ActiveXObject", "absolute", "offsetHeight", "STRING", "XMLHttpRequest", "The server has encountered an error", "colorDepth", "open", "gamma", "domain=", "webgl vertex shader medium float precision rangeMin:", "ratio", "Other", "RealVideo.RealVideo(tm) ActiveX Control (32-bit)", "OfflineAudioContext", "webgl blue bits:", "navigator", "mspointerdown", "#f60", "webgl fragment shader medium int precision:", "isNaN", "fillRect", "frequency", "loaded", "encodeURI", "attachEvent", "webgl max vertex texture image units:", "MAX_VERTEX_TEXTURE_IMAGE_UNITS", "2.7.1_7c08033d", "up", "webgl fragment shader high int precision rangeMax:", "device api response wrong", "createProgram", "GREEN_BITS", "isTrusted", "pageXOffset", "NUMBER", "innerHeight", "monospace", "clientY", "clientX", "constructor", "STATIC_DRAW", "productSub", "BOOLEAN", "opr", "MAX_TEXTURE_IMAGE_UNITS", "abort", "dAWsBhCqtOaNLLJ25hBzWbqWXwiK99Wd", "dns_province", "webgl aliased point size range:", "uniformOffset", "encodeURIComponent", "toLocaleString", "documentElement", "bindBuffer", "onerror", "string", "MEDIUM_FLOAT", "responseEnd", "MAX_COMBINED_TEXTURE_IMAGE_UNITS", "localStorage", "android", "canvas fp:", "destination", "description", "indexedDB", "createBuffer", "__driver_evaluate", "linkProgram", "button", "linux", "createShader", "Chrome", "normal", "webgl stencil bits:", "trident", "yes", "SWCtl.SWCtl", "Reduce of empty array with no initial value", "valueOf", "webgl vertex shader medium float precision:", "start", "WoeTpXnDDPhiAvsJUUIY3RdAo2PKaVwi", "createOscillator", "Does not support CORS", "detachEvent", "target", "parseInt", "gbk", "getUniformLocation", "WM_CONFIG", "\\((.+)\\)$", "shaderSource", "location", "HEX", "window", "initNEWatchman", "disconnect", "appVersion", "mousemove", "type", "webgl fragment shader medium float precision rangeMin:", "webgl vertex shader high int precision rangeMin:", "enableVertexAttribArray", "javaEnabled", "oscpu", "webgl fragment shader medium int precision rangeMax:", "options", "webgl vertex shader low float precision rangeMax:", "MAX_VARYING_VECTORS", "WM_NIKE", "openDatabase", "getParameter", "Buffer", "STENCIL_BITS", "canvas", "HIGH_FLOAT", "webgl vertex shader low int precision rangeMin:", ": ", "scroll", "batteryMax", "WM_NI", "DEPTH_BUFFER_BIT", "createDynamicsCompressor", "iphone", "webgl fragment shader low float precision:", "ip_province", "__selenium_evaluate", "Msxml2.XMLHTTP", "/v3/b", "pageYOffset", "GET", "style", "depthFunc", "Opera", "Can not find configuration", "::", "parseFloat", "webgl fragment shader low float precision rangeMin:", "getAttribLocation", "utf8", "webgl unmasked renderer:", "triangle", "unknown", "undefined", "\\.", "WM_DIV", "WM_TID", "event", "getExtension", "cache_", "offsetWidth", "userAgent", "QuickTime.QuickTime", "JSCookie", "experimental-webgl", "dischargingTime", "__nightmare", "ARRAY_BUFFER", "MEDIUM_INT", "request resource error", "withCredentials", "ip_city", "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/", "Missing business key", "width", "webgl max fragment uniform vectors:", "VERSION", "TDCCtl.TDCCtl", "self", "lineHeight", "Sequentum", "span", "msg", "innerHTML", "cookieEnabled", "rhino", "firefox", "threshold", "appCodeName", "Netscape", "bb99db1_7", "bb99db1_6", "bb99db1_5", "protocol", "fontFamily", "bb99db1_4", "webgl max texture image units:", "bb99db1_9", "://", "scrollLeft", "bb99db1_3", "bb99db1_2", "bb99db1_1", "__fxdriver_evaluate", "[object Function]", "timing", "toSource", "CAT_FONTS", "Cwm fjordbank glyphs vext quiz, 😅😥👶😃🧥🐶🍏⚽️✂🈲🚗⌚️❤️🏁▶", "WM_DID", "application/x-www-form-urlencoded", "Response is empty", "0123456789abcdef", "sans-serif", "webgl max combined texture image units:", "webgl vertex shader high float precision rangeMin:", "history", "webgl vertex shader medium float precision rangeMax:", "webgl fragment shader high int precision rangeMin:", "scrollTop", "webgl vertex shader high int precision:", "FRAGMENT_SHADER", "ipad", "rgba(102, 204, 0, 0.2)", "MacromediaFlashPaper.MacromediaFlashPaper", "send", "domAutomationController", "screenX", "?&", "ALIASED_LINE_WIDTH_RANGE", "renderedBuffer", "Failed to load script(", "platform", "CSS1Compat", "clearColor", "getAttribute", "array", "setInterval", "This browser's implementation of Object.create is a shim and doesn't support a second argument.", "createEvent", "getBattery", "webgl vertex shader high int precision rangeMax:", "value", "win", "vertexAttribPointer", "__webdriver_script_function", "srcElement", "textBaseline", "#069", "__", "move", "orientation", "motion", "phantomjs", "__selenium_unwrapped", "match", "charging", "unescape", "responseStart", "LEQUAL", "webgl vertex shader low float precision rangeMin:", "Date", "decodeURIComponent", "async", "performance", "boolean", "inline", "32288785", "serif", "VENDOR", "CAT_CANVAS", "reduction", "emit", "getContext", "uniform2f", "webgl alpha bits:"] |
然后我们复制一些固定的参数值来验证La
方法是否可用:
1 | function get_actoken(d_enc) { |
变量a的b属性
上面说了要生成acToken
存在三个问题,第三个问题现已解决,现在来看第一个问题
变量a的b属性是传入参数的C属性,回到之前的位置:
C属性的值就是t的值,点击调用栈可以定位到下这个图片最下面的位置(很多步骤这里轻描淡写,实际上需要仔细分析),接着往上面看一点可以看到变量t声明和赋值的地方:
1 | var t = this.h.C = $b(); |
这样问题就很清楚了,C属性的值就是$b()
的返回值,把$b()
方法抠出来就好了。
这里直接贴出来
1 | // b参数 |
这里方法名不用在意,入口是bc
方法,实际就是本次调试的$b方法
变量a的d属性
上面已经解决了a的b属性,下面再看下d属性,网上说这里的b属性实际来自d请求返回的第三个参数值(指的是前面的序号为3,实际是第四个),调试的时候经过对比发现确实相同,至于如何得知这一信息感兴趣可以自行研究下,这里直接看d请求做了什么。
可以看到确实是从这里发起了d请求(只有在清除缓存的情况下才会发送d请求),Pc方法传入的c参数已经包含完整的POST请求参数,即:
1 | d: yyxmP5t+dPzXZBse.tJ.L/vgNH1hGlI9ncjf6qRFDyQ4jvZI8e9apy5ygX+PWHoQxEeeVhtYwZ30ZWkDKNj6v6ZyqV9FMiIJ7o+Z9aImNe9pP.LjvQ7AHkMK.dSa0A3gpIis8X2+nLFPkwEVLTJy51jHubaXOmliK7n8VAzw2WG3qmZyy46LFEhgmqodIBpUB/a0+sgrlPDAKrONie+3.vUndID3kcIZvzKTJBO8uFMZlHPTHTEMsTr4.JaUcb9EOeNXjEKa6gNPXyJ6W/jXbcZKiVlONQEU63m.2FXGEjEK1GFX1p0OSpdwNUGVG/ngYYG7GenZIRXi9QTky6N4voHXzgZE72bxiK4j05UZQLfhQLj1nNRYqmKZ8.PX9.0snt+Fi4qIVcY/t4s023w7.f33.2wqsgQVykU1JYQ+g1p0BqjriKQDPa1hbbl1aWilTkwmWuiwQ.11v1KyK.JfipEMKlY4TFGnveBoo/WyfAW0Yn5RUfYKSzweWZScLUq.oOhwD+nfXaq4TivPEKI4IWhDfp9ZXndc3JYqvaZkjMKspsUp+XTs+0b1lMroxpb2hI6L7+dbPbsqZ+QR6TLzxAbbkSMoZnW.bfmNs01ZFADoH+BYn61wZBGjphswm6gNh7Ov81O1aoY3bY6YO.Ub8qAVNmzbnqd4xTulGyOUqx9zIuyqWZvYTYbTScUguFNRTQkv59NvQmlRvRe1NogpbZ5K2dp8fmLyYzx6giTA5.E5P58E9EQtLAB6mNF5/XI91XqBitAdRq3dvy0dw36BKj+WOn5tGLSEDF1mqslrpuz+ev5GIMhFFrFJdTYKvzoDM4NpBEQOgqkATVtL4s/Up5f3y4XOTXuK4H6EL0yfuOqv0M.InF6Bpe4/.bMZQoMf96M9lm5/B8jgSaVWsvuqWGXZ9ZxAiwDlGmP7kTTRagP.3bdR |
经过追溯,我们可以找到d请求的d参数(v和cb
不重要)的生成位置(下图的e):
这里我就没去一步步扣了,用的周围大佬扣好的,且b请求的d参数的生成方式也是类似的,只是多了一些鼠标行为验证的数据操作
1 |
|
上面b_param_ha
方法是b请求的d参数的生成方法
var d = $a(e, g, "07e2387ab53a4d6f930b8d9a9be71bdf", bc());
这里的e、g分别是d请求返回的第3、4个参数值(索引是2、3),在调用时传入,而07e2387ab53a4d6f930b8d9a9be71bdf
是CaptchaId
,与你的具体网站绑定的,不同的网站有不同的CaptchaId
,这里使用时也要注意修改,或者也改成参数传入的形式。
a变量里面的”_move”、”_up”、”_click”,”_down”这些是滑动验证码时,你的鼠标做的一些其他操作,这里根据某一次滑动时产生的值来固定写死也是可用的,这个步骤目前还没有严格验证,如果有严格验证也可以搞清楚生成规则,自己写算法每生成随机值。
小结
上面内容比较多,我们来理一下主要的逻辑:
1、check
请求的post参数有一个acToken
,我们需要知道它是如何生成的;
2、我们通过调试发现,acToken
的生成需要这样一个变量a,然后执行La
方法生成;
1 | var a = { |
3、a变量有两个属性d和b,d属性是d请求返回结果索引为3的值,b属性是相关Js
生成的值;
4、然后我们发现d请求有一个POST参数d,找出生成它的Js
方法;
5、只执行完d请求就去生成acToken
无法使用,经过观察发现还有一个与d请求很类似的b请求,正好也有一个d参数,其生成方式类似,只是多了一些鼠标验证,这里b和d请求类似一个双重验证,都很重要;
6、执行完d、b请求后就可以生成acToken
,进行验证。
整个易盾破解总结
易盾验证码从请求到破解的完整流程代码(仅包含主函数)
1 | if __name__ == "__main__": |
经过测试,十次的成功率一般都在九次以上。
关于易盾破解的js
方面,一共写了三篇文章,分别讲了fp
、data
、cb
、acToken
的生成过程,以及一些调试、js
文件替换和扣js
的技巧,整个项目自己完整破解下来,提升很大。
关于图像识别这方面写了两篇文章,分别从传统像素处理角度和深度学习方面来尝试识别缺口位置,还插入了极验的图像破解方法,极验的验证码图片其实比较简单,通过传统像素值方式就可以解决,易盾的话有些图片可以通过模板匹配识别,但是更多的目前已经无法通过模板匹配识别了,所以我也是尝试了使用深度学习目标检测的方式破解,效果还不错。
另外补充一点,图像缺口距离的实际值、jigsaw_style_left以及trace数组的最大值,这三个值是三个独立的值,一开始可能会误让人以为jigsaw_style_left就是图像缺口距离的实际值,这样就会导致后续计算出错,导致成功率较低。这三个值的关系,每个网站可能都会有所不同,需要我们多去测试几组数据,来找到他们的关系。