某网站极验逆向

jscrambler 混淆器,主要是可见性的问题,一些 unicode 字符的去除,还有 decodeURI 里面的内容不能直接解码,得用源代码保留

混淆器手法

逆向该混淆器的时候有一些通用的模板

首先是类似于这样的形式的代码,我们通过查看能够知道 f3.$_Ct 是一个函数,通过输入对应的索引就能得出对应的字符串,类似于字符串池,而以下的代码有用的只有 v507 和 v509 其余的代码都是死代码( v510 几乎不会被用到)

var v507 = f3.$_Ct;
var v508 = ["$_HCDg"].concat(v507);
var v509 = v508[1];
v508.shift();
var v510 = v508[0];

没什么用的 switch,单步即可

function f151(p529) {
  var v1994 = f3.$_DA()[10][14];
  while (v1994 !== f3.$_DA()[8][13]) {
    switch (v1994) {
      case f3.$_DA()[8][14]:
        var v1995;
        var vV1965 = v1965(42);
        var vV19652 = v1965(42);
        for (v1995 = 0; v1995 <= 3; v1995++) {
          vV1965 += (vV19652 = v1963(37) + (p529 >>> v1995 * 8 & 255)[v1965(339)](16))[v1965(784)](vV19652[v1963(138)] - 2, 2);
        }
        return vV1965;
        break;
    }
  }
}

270 索引的字符串为 prototype ,而 f52 是一个函数,为的是给函数添加新的函数,类似于继承

f52[v465(270)][v467(271)] = function f55(p191) {
  var v539 = f3.$_Ct;
  var v540 = ["$_HDIJ"].concat(v539);
  var v541 = v540[1];
  v540.shift();
  var v542 = v540[0];
  if (p191[v541(236)] < 0 || p191[v541(220)](this[v539(244)]) >= 0) {
    return p191[v541(242)](this[v539(244)]);
  } else {
    return p191;
  }
};
f52[v465(270)][v465(294)] = function f56(p192) {
  var v543 = f3.$_Ct;
  var v544 = ["$_HEDH"].concat(v543);
  var v545 = v544[1];
  v544.shift();
  var v546 = v544[0];
  return p192;
};
f52[v465(270)][v465(241)] = function f57(p193) {
  var v547 = f3.$_Ct;
  var v548 = ["$_HEIt"].concat(v547);
  var v549 = v548[1];
  v548.shift();
  var v550 = v548[0];
  p193[v549(204)](this[v547(244)], null, p193);
};
f52[v467(270)][v467(284)] = function f58(p194, p195, p196) {
  var v551 = f3.$_Ct;
  var v552 = ["$_HFDa"].concat(v551);
  var v553 = v552[1];
  v552.shift();
  var v554 = v552[0];
  p194[v553(213)](p195, p196);
  this[v551(241)](p196);
};

API 信息

  • https://passport.bilibili.com/x/passport-login/captcha

    该接口获取极验的 token、challenge、gt

    获取参数

    获取参数

  • https://api.geetest.com/get.php?

    该接口获取一些必要参数,后面要用到

    获取极验图片

    获取极验图片

  • https://static.geetest.com/static/js/click.3.1.2.js

    https://static.geetest.com/static/js/gct.b71a9027509bc6bcfef9fc6a196424f5.js

    这两个 js 实现了极验的加密(下面详细讲)

  • https://api.geetest.com/ajax.php

    该接口用于提交请求

    点击验证提交

    点击验证提交

逆向流程

构造请求包

首先找到提交 post 请求的函数

post 提交

post 提交

调用者上面就是请求的参数,可以很清晰的看到

通过查看,我们可以发现 gt challenge lang 在上述 get.php 的接口就可以找到,剩下的参数有 pt client_type w

生成的地方

生成的地方

pt 参数

通过搜索 643 这个索引即可找到赋值的地方,它由 v452 决定,可能为 0 或 3

var v452 = /Mobi/i[v11(107)](v450[v11(141)]);

我们通过索引 107 和 141 我们可以知道源代码长这样

var v452 = /Mobi/i.test(v450.userAgent);

而其中 v450 为 Navigator,是浏览器提供的结构,至此我们可以知道 pt 的值代表着 UA 字符串中是否 Mobi 字样,如果有,则为 3 ,如果没有则为 0

client_type 参数

在 pt 参数附近,见上图,也就是

v452 ? v11(683) : v11(660);

翻译一下就是

V452 ? 'web_mobile' : 'web'

同理,非 Mobi 字样 UA 选择 'web'

w 参数

w 是一个组合的参数

w: v2021 + v2019
var v2019 = vThis25[v1925(751)]();
var v2020 = vF7[v1927(374)](vF10[v1927(109)](vO16), vThis25[v1925(750)]());
var v2021 = vO8[v1927(774)](v2020);

分析该三个数据即可

v2019

$_CABQ: function (p538) {
  var v2081 = f3.$_Ct;
  var v2082 = ["$_CBGDt"].concat(v2081);
  var v2083 = v2082[1];
  v2082.shift();
  var v2084 = v2082[0];
  var v2085 = new vF6()[v2081(374)](this[v2081(750)](p538));
  while (!v2085 || v2085[v2083(138)] !== 256) {
    v2085 = new vF6()[v2081(374)](this[v2081(750)](true));
  }
  return v2085;
}

其中主要的是 new vF6()[v2081(374)](this[v2081(750)](p538));

内部的函数调用 $_CACd,而通过调试我们可以知道它会返回 v1811 ,它从 vF9() 初始化得到

vF9

可知它返回一个 16 字符长度的字符串,也就是 AES key,然后该生成的随机 AES key 会通过 RSA 加密,其中构造 vF6 的时候会自动加载公钥,然后调用 encrypt 函数

通过断点 setPublic 我们可以知道它会获取公钥

00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81

10001

这里它虽然写的是这个,但是后面的解析其实是不需要首部的 00 字节的,所以我们去掉 00 然后转换为 PEM 格式公钥

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDB45NNFhRGWzMFPn9I7k7IexS5
XviJR3E9Je7L/350x5d9AtwdlFH3ndXRwQwprLaptNb7fQoCebZxnhdyVl8Jr2J3
FZGSIa75GJnK4IwNaG10iyCjYDviMYymvCtZcGWSqSGdC/Bcn2UCOiHSMwgHJSrg
Bm1Zzu+l8nSOqAurgQIDAQAB
-----END PUBLIC KEY-----

通过跟踪 encrypt 函数,我们可以从中发现填充的代码,通过动态跟踪我们可以知道这是 RSAES-PKCS1-v1_5 填充

var v_ = function f91(p240, p241) {
  var v740 = f3.$_Ct;
  var v741 = ["$_JBIY"].concat(v740);
  var v742 = v741[1];
  v741.shift();
  var v743 = v741[0];
  if (p241 < p240[v742(138)] + 11) {
    if (console && console[v742(65)]) {
      console[v740(65)](v740(365));
    }
    return null;
  }
  var vA5 = [];
  var v744 = p240[v742(138)] - 1;
  while (v744 >= 0 && p241 > 0) {
    var v745 = p240[v742(166)](v744--);
    if (v745 < 128) {
      vA5[--p241] = v745;
    } else if (v745 > 127 && v745 < 2048) {
      vA5[--p241] = v745 & 63 | 128;
      vA5[--p241] = v745 >> 6 | 192;
    } else {
      vA5[--p241] = v745 & 63 | 128;
      vA5[--p241] = v745 >> 6 & 63 | 128;
      vA5[--p241] = v745 >> 12 | 224;
    }
  }
  vA5[--p241] = 0;
  var v746 = new f42();
  var vA6 = [];
  while (p241 > 2) {
    vA6[0] = 0;
    while (vA6[0] == 0) {
      v746[v740(217)](vA6);
    }
    vA5[--p241] = vA6[0];
  }
  vA5[--p241] = 2;
  vA5[--p241] = 0;
  return new f44(vA5);
}(p239, this[v736(219)][v738(383)]() + 7 >> 3);

至此我们分析完了 v2019 的流程,也就是随机生成 AES key,然后再拿 RSA 公钥加密 AES key,返回 hex 格式字符串

v2020

将 vO16 对象序列化,然后调用 AES 加密,密钥是刚刚生成的 AES key

跟踪到里面就可以获取 IV 是 0000000000000000 字符串,然后返回字节数组

v2020 生成的内容

v2020 生成的内容

v2021

自定义编码函数,类似于 base64 但是拓展到了 74 位

()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

这里可以直接把代码扣下来

let $_DBe = f3.$_Ct(174);
let $_DCz = f3.$_Ct(71);
let $_DDr = 7274496;
let $_DEA = 9483264;
let $_DFp = 19220;
let $_DGb = 235;
let $_DHo = 24;

function $_EAe(p143) {
    var v387 = f3.$_Ct;
    var v391 = $_DBe;
    if (p143 < 0 || p143 >= v391.length) {
        return v387(71);
    } else {
        return v391.charAt(p143);
    }
}

function $_ECv(p145, p146) {
    return p145 >> p146 & 1;
}

function $_EDt(p147) {
    var v402 = f3.$_Ct;

    var vF4 = function (p149, p150) {
        var vLN09 = 0;
        for (var v408 = $_DHo - 1; v408 >= 0; v408 -= 1) {
            if ($_ECv(p150, v408) === 1) {
                vLN09 = (vLN09 << 1) + $_ECv(p149, v408);
            }
        }
        return vLN09;
    };
    var vV402 = v402(42);
    var vV4022 = v402(42);
    for (var v409 = p147.length, vLN010 = 0; vLN010 < v409; vLN010 += 3) {
        var v410;
        if (vLN010 + 2 < v409) {
            v410 = (p147[vLN010] << 16) + (p147[vLN010 + 1] << 8) + p147[vLN010 + 2];
            vV402 += $_EAe(vF4(v410, $_DDr)) + $_EAe(vF4(v410, $_DEA)) + $_EAe(vF4(v410, $_DFp)) + $_EAe(vF4(v410, $_DGb));
        } else {
            var v411 = v409 % 3;
            if (v411 == 2) {
                v410 = (p147[vLN010] << 16) + (p147[vLN010 + 1] << 8);
                vV402 += $_EAe(vF4(v410, $_DDr)) + $_EAe(vF4(v410, $_DEA)) + $_EAe(vF4(v410, $_DFp));
                vV4022 = $_DCz;
            } else if (v411 == 1) {
                v410 = p147[vLN010] << 16;
                vV402 += $_EAe(vF4(v410, $_DDr)) + $_EAe(vF4(v410, $_DEA));
                vV4022 = $_DCz + $_DCz;
            }
        }
    }
    return {
        res: vV402,
        end: vV4022
    };
}

function $_EFu_EncodeAESBytes(p152) {
    var v421 = $_EDt(p152);
    return v421.res + v421.end;
}

构造数据包

至此,我们能够构造出请求包 vO18 了,但是别忘了,AES 加密的内容还不知道是什么,也就是 vO16 ,vO16 在函数头部进行定义

其中 lang pic 已知,在 get.php 中可以找到

a passtime 是传入的点击位置与消耗时间

其余需要构造

tt 参数

tt: function (p476, p477, p478) {
  var v1932 = f3.$_Ct;
  var v1933 = ["$_CADIZ"].concat(v1932);
  var v1934 = v1933[1];
  v1933.shift();
  var v1935 = v1933[0];
  if (!p477 || !p478) {
    return p476;
  }
  var v1936;
  var vLN052 = 0;
  var vP476 = p476;
  var v1937 = p477[0];
  var v1938 = p477[2];
  var v1939 = p477[4];
  while (v1936 = p478[v1932(784)](vLN052, 2)) {
    vLN052 += 2;
    var vParseInt = parseInt(v1936, 16);
    var v1940 = String[v1932(181)](vParseInt);
    var v1941 = (v1937 * vParseInt * vParseInt + v1938 * vParseInt + v1939) % p476[v1932(138)];
    vP476 = vP476[v1934(784)](0, v1941) + v1940 + vP476[v1932(784)](v1941);
  }
  return vP476;
}(v1931, v1929[v1927(729)], v1929[v1927(236)])

其中三个参数分别为:鼠标移动点位数据,get.php 提供的 c ,get.php 提供的 s,然后通过该函数能够得出编码的轨迹数据

ep 参数

调用以下函数,其中

this[v2086(617)][v2088(630)] 为点位数据中的点击数据,见下

v 是极验的版本 3.1.2

$_FG 为 touchEvent 的值

me 为 mouseEvent 的值

tm 为浏览器中 window.performance.timing 的数据

$_CAAe: function () {
  var v2086 = f3.$_Ct;
  var v2087 = ["$_CBGIo"].concat(v2086);
  var v2088 = v2087[1];
  v2087.shift();
  var v2089 = v2087[0];
  console.log(`let dots = JSON.parse('${JSON.stringify(this[v2086(617)][v2088(630)])}')`);
  return {
    ca: this[v2088(617)] && this[v2086(617)][v2088(630)] || v2086(42),
    v: v2086(759),
    $_FG: vV1612[v2086(614)],
    me: vV1612[v2086(648)],
    tm: new f129()[v2086(731)]()
  };
}

点击数据(注意该数据是所有点击的位置,而传入参数中的点击只有验证图像中的点击位置)

点击数据的构造由该函数添加,监听 click 事件,p577 第一个参数是元素,第二个参数是点击事件,其中这几个值的对应的元素是这样的

vThis44[v2487(600)][v2489(587)](v2487(453), function (p577) {
  var v2494 = f3.$_Ct;
  var v2495 = ["$_CFDIW"].concat(v2494);
  var v2496 = v2495[1];
  v2495.shift();
  var v2497 = v2495[0];
  var v2498 = p577[v2496(413)][v2496(942)] || p577[v2496(413)][v2496(998)];
  var v2499 = v2492(v2496(719))[v2494(112)];
  var v2500 = v2492(v2496(737))[v2496(112)];
  var v2501 = v2492(v2496(762))[v2494(112)];
  var v2502 = v2492(v2496(748))[v2494(112)];
  var v2503 = vThis44[v2496(736)](v2494(832))[v2494(112)];
  var vO21 = {
    x: p577[v2496(509)](),
    y: p577[v2496(567)]()
  };
  if (f156(v2503, v2498)) {
    vO21[v2496(278)] = 1;
  } else if (f156(v2502, v2498)) {
    vO21[v2496(278)] = 3;
  } else if (f156(v2500, v2498)) {
    vO21[v2494(278)] = 4;
  } else if (f156(v2499, v2498)) {
    vO21[v2496(278)] = 5;
  } else if (f156(v2501, v2498)) {
    vO21[v2494(278)] = 6;
  } else {
    vO21[v2494(278)] = 0;
  }
  if (v2496(865) !== v2491[v2496(744)]) {
    var v2504 = v2498[v2496(436)] || v2494(42);
    if (v2504[v2496(191)](vV11 + v2496(990)) >= 0) {
      var v2505 = v2504[v2496(191)](vV11 + v2496(813)) >= 0;
      vO21[v2496(278)] = v2505 ? 1 : 2;
    }
    if (v2504[v2496(191)](vV11 + v2494(969)) >= 0) {
      vO21[v2494(278)] = 2;
    }
  }
  vThis44[v2496(901)](vO21);
  vThis44[v2494(630)][v2494(108)](vO21);
  vThis44[v2494(954)]();
});

v2498 是当前点击的元素

vThis44[v2496(901)](vO21); 添加时间差


所以点击的结构体是这样的

t 是点击的元素,1 为点击验证码图像,3 为点击提交按钮(最后一个一定是 3

dt 为距离上一个 pointerdown 的时间差(毫秒)

[{"x":943,"y":365,"t":1,"dt":3881}, {...}]

附加额外字段

除此之外还有一个 vO17 对象需要构造,但是直接使用 gct.js 即可,然后 vO16 会附加上 vO17 的部分数据(也可以直接给定值)

"h9s9" 为 "1816378497"

gct.js 中含有反调试,建议是直接给固定结果 1803797734,该值会永远计算出 1816378497

gct.js 中含有反调试,建议是直接给固定结果 1803797734,该值会永远计算出 1816378497

最后 vO16 还会使用 gt + challenge + passtime 字符串进行 md5 计算得出 rp 字段

结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值