草庐IT

破解极验三代滑动验证,成功率百分之百(三):构造参数,发起请求

__aoko 2023-07-28 原文

声明

原创文章,请勿转载!

本文内容仅限于安全研究,不公开具体源码。维护网络安全,人人有责。

环节概述

首先我们还是需要使用请求转发工具,把目标代码替换为我们处理过的代码。

  1. 观察验证过程中触发了哪些请求
  2. 破解 w参数
  3. 根据参数构造请求

验证过程中触发的请求

  1. 请求总览

    请求里有两个参数:gt 和 challenge,这两个参数非常重要,后续的请求也都会携带他们,极验通过这两个参数来标识你在做什么操作,以及后续还需什么流程。

    其中 gt 是定值,这个是和极验申请的 ID。

    challenge 是一个行为的 ID,上一步的操作会返回你新操作的 challenge,然后下一个操作带上这个新 challenge 即可。

    同时每个请求返回的都是 JSONP,我们自己解析返回值的时候注意处理下格式即可(比如用正则匹配出来结果)

    请求中另外一个最重要的参数是 w。它是被加密过的一段数据,是校验通过与否的核心,我们这次的目的就是破解这个 w 参数。

  2. gettype.php:获取核心 JS 文件链接

  3. get.php:无感验证的一部分,收集浏览器信息并上报。其中 c 和 s 比较关键,后续的请求会用到它

  4. ajax.php:执行无感验证。如果验证失败会返回验证类型,比如滑块,点选等。

  5. slide.x.x.x.js:正式进入滑动验证的部分,这里是在加载相关 JS 文件

  6. get.php:获取滑动验证的一些基本数据,需要关注的有 bg, c, challenge, fullbg, gt, slice, s

  7. ajax.php:执行滑动验证,成功后返回 validate

破解 w 参数

因为本次介绍的内容是滑动验证,所以 fullpage.js 发起的无感验证的相关请求,里面加密的 w 参数随便填,其他的参数看着填即可。这样最后就会触发滑动验证。

因为我们破解的关键在于 w 参数,所以先在本地代码搜索下它,然后在那里写一个 debugger 进去,这样我们可以在它执行时随意调试了。

  1. 观察 w 是怎么来的

    l 来自
    w
    h
    u
    r.$_CCDv()
    m.$_GGc(l)
    V.encrypt(pt.stringify(o), r.$_CCEB())

    可以看出 w 参数有两个部分组成,其中每个部分有自己独特的构造逻辑。我们要做的就是把不会变的逻辑单独拿出来,然后会变动的部分想办法做破解。

  2. 破解 u 参数

    我们进入 r.$_CCDv() 这个函数,他是这样的:

    里面首先有一个 U 对象,并且调用了它内部的一个函数。我们不太需要关心这个对象具体的逻辑,把他复制出来直接调用即可。

    然后它还会调用上方的一个 $_CCEB 函数。这个函数的逻辑其实很简单,里面的 rt 函数点击去是这样的。

    可以看出它的功能只是生成一个随机值。

    这些代码我们都复制出来,u 参数已经破解好了。

    function getRandomTextHelper() {
      const helper = () => ((65536 * (1 + Math.random())) | 0).toString(16).substring(1)
      return helper() + helper() + helper() + helper()
    }
    // 随机值的缓存
    var oldRandomText = getRandomTextHelper()
    // 这个就是刚才生成随机值的函数
    function getRandomText(needRefresh) {
      if (needRefresh) {
        oldRandomText = getRandomTextHelper()
      }
      return oldRandomText
    }
    
    // 获取 u 参数
    function getU() {
      // U 对象自己复制出来即可,太长了
      var e = new U().encrypt(getRandomText())
    
      while (!e || 256 !== e.length) e = new U().encrypt(getRandomText(true))
    
      return e
    }
    
    // 自行把这个对象复制过来
    var U = ...
    
  3. 破解 h 参数

    这个 h 参数的生成相对复杂一些。从刚才的代码我们可以看出,h来自于ll又来自于o

    其中 hl 的逻辑比较简单,只是简单的复制工作。

    1. h 参数:var h = m.$_GGc(l)

      这个 $_GGc 函数其实也是固定内容,复制下来即可。

    2. l 参数:var l = V.encrypt(pt.stringify(o), r.$_CCEB())

      V 这个对象和上面的 U 差不多,复制出来即可。

      pt.stringify 这个函数也是固定的函数,复制出来即可。

      r.$_CCEB() 就是刚才的生成随机数的函数。

    3. o 参数

      这个参数是最复杂的一个部分。我们先看下这个参数是在哪里创建的。

      从这里我们已经可以确定一些字段了。

      • lang 是语言,我们可以直接设为 zh-cn
      • userresponse:H 是一个固定的函数,可以复制出来,t 具体是什么暂时不确定,i.challenge 看起来是接口返回的 challenge ,我们一会儿可以确认一下。
      • passtime:验证的通过时间,来自函数入参 n
      • imgload:验证码图片的加载时间,搞个 30 - 100ms 的随机值即可
      • aa:暂时还不确定是什么,来自函数入参 e
      • ep:点进去是这样的

      里面的内容可以简单填写为这样:

      现在我们需要继续研究 ten 三个参数的生成逻辑,但是目前不好通过分析代码来获得更多内容了,所以我们根据刚才的 debugger 断点,去页面上具体研究。

    4. ten 参数

      进入断点之后,我们可以看到变量的具体内容。先确定先找个函数的入参 t e n 分别是数字,奇怪的字符串,数字。

      因为这三个值是函数的入参,所以我们根据调用栈,前往它的上层函数看看。

      同时这个函数具体的代码是这样的:

      对于 passtime 对应的 n 参数:

      我们根据代码可以确定,它就是整个滑动验证中,鼠标从摁下到抬起经过的时间。

      对于 userresponse 对应的 t 参数,也就是这里的 u 参数,我们添加额外的 debugger,结合上述操作步骤,我们发现它其实是滑块的滑动距离,同时challenge字段确实就是接口返回到的challenge 字段。

      对于 aa 对应的 e 参数,也就是这里的 l 参数,我们通过观察代码,结合 debugger 数据可以发现,里面 cs是来自于接口的cs字段,_BBES 是固定的函数,同样也是复制下来即可。不过里面的$_GFJ() 部分有些特殊,通过断点我们发现里面涉及到一个大数组。

      这个大数组其实是对鼠标拖动滑块的轨迹的记录。

      我们观察一下发现:

      • 大数组有多个小数组构成,小数组的定义是:[x, y, time]

      • 第一行的 x y 是负数,并非从 0 开始,第二项所有的值均为 0

      • 每次记录的耗时大约在 10ms

      根据这个结论,我们大致实现一个函数来构造轨迹

      function generateSlideTrace(distance: number) {
        // 前两行按照刚才的观察来构造
        const trace = [
          [random(-50, -10), random(-50, -10), 0],
          [0, 0, 0],
        ]
        // 轨迹记录数量
        const count = 30 + Math.floor(distance / 2)
        // 耗时
        let t = random(50, 100)
        // 记录上一个轨迹
        let lastX = 0
        let lastY = 0
        let lastYCount = 0
        for (let i = 0; i < count; i++) {
          // 已滑动的距离
          const x = Math.round(i == count ? 1 : (1 - Math.pow(2, (-10 * i) / count)) * distance)
          // 耗时
          t += random(10, 20)
          if (x === lastX) {
            // 不合理
            continue
          }
          lastYCount += 1
          if (lastYCount > random(5, 10)) {
            // y 的变动不太大,连续多个 y 之后再考虑更新
            lastYCount = 0
            lastY = random(-2, 2)
          }
          lastX = x
          trace.push([x, lastY, t])
        }
        return trace
      }
      
  4. 合并 uh

    至此,我们已经完成了所有的待破解元素。接下来把两个参数合并就是最后的 w 参数了。整体的代码是这样的(变量名做了点优化):

    // 计算 w 参数,props 里是 c、s、trace(滑动轨迹数组)、challenge
    export function getSlideW(props) {
      return getSlideLeft(props) + getRight()
    }
    
    function getSlideLeft(props) {
      // encodeLeft 是扣出来的代码,getL 同理
      return encodeLeft(getL(props))
    }
    
    function getL({ c, s, trace, challenge }) {
      const o = {
        lang: 'zh-cn',
        userresponse: H(trace.at(-1)[0], challenge),
        passtime: trace.at(-1)[2],
        imgload: Math.floor(Math.random() * 50 + 30),
        // getAA 是抠出来的代码,encodeTrace 同理
        aa: getAA(encodeTrace(trace), c, s),
        ep: {
          v: '7.8.8',
          $_BIQ: false,
          me: true,
          tm: -1,
          td: -1,
        },
      }
      // 都是抠出来的代码
      return V.encrypt(stringify(o), getRandomText())
    }
    
    function getRight() {
      var e = new U().encrypt(getRandomText())
    
      while (!e || 256 !== e.length) e = new U().encrypt(getRandomText(true))
    
      return e
    }
    
    function getRandomTextHelper() {
      const helper = () => ((65536 * (1 + Math.random())) | 0).toString(16).substring(1)
      return helper() + helper() + helper() + helper()
    }
    var oldRandomText = getRandomTextHelper()
    function getRandomText(needRefresh) {
      if (needRefresh) {
        oldRandomText = getRandomTextHelper()
      }
      return oldRandomText
    }
    
    // 下面的是从 slide 文件里抠出来的逻辑
    function stringify(t, e, n) { ... }
    function H(t, e) { ... }
    function getAA(t, e, n) { ... }
    function encodeTrace(arr) { ... }
    function encodeLeft(l) { ... }
    
    var U = ...
    var V = ...
    
    

构造请求

构造请求的过程其实和真是进行验证时的过程相同,按照上面对请求过程的分析,使用合适的参数发出对应的请求即可。

大致的顺序为:ajax.php -> get.php -> 得到基本数据,计算参数 -> ajax.php -> 验证成功!

总结

这一节内容里,我们分析了网络请求和代码逻辑,理清了请求的顺序、作用,并得到了核心参数 w 的计算逻辑,最后成功根据参数通过了验证。

至此极验三代滑动验证码的分析已结束,之后我会更新其他验证方案的分析过程,感兴趣的朋友欢迎和我交流。


本期文章到这里就结束了,如果对您有帮助,记得收藏关注,有什么想法也可以联系我哦。

后续内容持续更新中。。。

有关破解极验三代滑动验证,成功率百分之百(三):构造参数,发起请求的更多相关文章

  1. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  2. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  3. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

    我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

  4. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  5. ruby-on-rails - 在默认方法参数中使用 .reverse_merge 或 .merge - 2

    两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option

  6. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  7. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  8. ruby - rails 3 redirect_to 将参数传递给命名路由 - 2

    我没有找到太多关于如何执行此操作的信息,尽管有很多关于如何使用像这样的redirect_to将参数传递给重定向的建议:action=>'something',:controller=>'something'在我的应用程序中,我在路由文件中有以下内容match'profile'=>'User#show'我的表演Action是这样的defshow@user=User.find(params[:user])@title=@user.first_nameend重定向发生在同一个用户Controller中,就像这样defregister@title="Registration"@user=Use

  9. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

  10. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

随机推荐