1739 字
9 分钟
小黑盒web获取hkey及nonce
2026-05-06
无标签

前言#

CAUTION

请务必阅读前言

小黑盒作为国内游戏社区,是我个人比较喜欢的社区之一了。

为了社区的良好环境,请不要滥用接口!也请看这篇文章的黑产灰产之类的可以放弃想法。

过程#

任意打开一个页面,将控制台切换为网络,过滤条件为Fetch/XHR。

打开任意一个数据请求,查看其载荷。这里使用发布文章示例。

可以看到,在查询字符串参数中,有两个参数是引人注目的:

hkeynonce

打开启动器:

查看调用堆栈,最下方的为最开始的地方。

通过断点调试,可以得出,在_S堆栈中,构造了请求内容。

再经过细致调试,不难看出,在Bv(bS(e))这个函数嵌套调用中,Bv返回的内容为hkey与nonce。

查看Bv函数

function vv(e) {
var t, n, r, i = Wm[3];
return t = ~~(+lv.w() / 1e3),
r = (0,
Jm.MD5)(t + Math.random((new Date).getTime()).toString()).toString().toLocaleUpperCase(),
n = lv[i](e, t, r),
Hm(Hm(Hm({}, "".concat(Wm[4], "k").concat(Wm[2], "y"), n), "_".concat(Wm[13]).concat(Wm[5]).concat(Wm[6]).concat(Wm[2]), t), "".concat(Wm[7]).concat(Wm[8]).concat(Wm[7], "c").concat(Wm[2]), r)
}

这里是混淆比较复杂的一点,如果你直接搜索hkey是没有结果的,因为hkey为函数最后拼接的字符串。

控制台调试也可以发现。

而三个参数分别对应的是n,t,r(苦也,这也有牛)

仔细观察上方混淆后的代码,不难看出:

t ( _time)#

t的获取是简单的,t = ~~(+lv.w() / 1e3)

其中lv.w()查看可以得到以下函数:

function() {
return new Date
}

仅为获取时间,然后除以1e3(1000)。这就是十位时间戳。

r (nonce)#

r在混淆中也能看出来,其为时间+通过时间戳获取的字符串再转换为md5,类似于随机数吧!

n(hkey)#

n的获取是最难的,在混淆中,其赋值过程为n = lv[i](e, t, r)

lv[i]是什么函数?

i: function(e, t, n) {
return ov(e, t + 3, n)
},

ov又是什么函数?

function ov(e, t, n) {
e = "/".concat(e.split("/").filter(function(e) {
return e
}).join("/"), "/");
var r = "AB45STUVWZEFGJ6CH01D237IXYPQRKLMN89"
, i = function(e) {
for (var t = "", n = function(n) {
e.forEach(function(e) {
n < e.length && (t += e[n])
})
}, r = 0; r < Math.max.apply(Math, iv(e.map(function(e) {
return e.length
}))); r++)
n(r);
return t
}([av(String(t), r, -2), sv(e, r), sv(n, r)]).slice(0, 20)
, o = (0,
Jm.MD5)(i).toString()
, a = "".concat(Km(o.slice(-6).split("").map(function(e) {
return e.charCodeAt()
})).reduce(function(e, t) {
return e + t
}, 0) % 100);
a = a.length < 2 ? "0".concat(a) : a;
var s = av(o.substring(0, 5), r, -4);
return "".concat(s).concat(a)
}

到这里,这个函数又引出了其他函数,而其他函数又引出了其他函数,都是为了混淆加密。

通过函数调用链我们只需要将其搬运过来即可。

ov函数主要获取三个参数:

e,t,n

e:

请求目录,例如,在发送的请求中,除去查询内容,请求url为https://api.xiaoheihe.cn/bbs/app/api/link/post 则e就为/bbs/app/api/link/post

t:

10位时间戳(秒)

n:

nonce

首先获取到e后,先规范目录格式化:

e = "/".concat(e.split("/").filter(function(e) {
return e
}).join("/"), "/");

然后定义一个固定的字符串

var r = "AB45STUVWZEFGJ6CH01D237IXYPQRKLMN89"

再通过av,sv函数共获取包含三个字符串的数组

[av(String(t), r, -2), sv(e, r), sv(n, r)]

将数组按照最短在前排序后,按顺序合成一个新的字符串。

例如数组如下:

[“三个字”,“四个字啊”,“五个字哦哟”]

合成的新字符串如下:

三四五个个个字字字啊哦哟

就是所有数组的第n位按照顺序+=新的字符串,依次类推

然后截取前20位获取MD5。

再获取后六位md5,转换为数组,将其转换为ascii码相加。

再通过Km混淆函数混淆:

这函数诗人想出来的啊?

function Km(e) {
  var t = [0, 0, 0, 0];
  return (
    (t[0] = Gm(e[0]) ^ Ym(e[1]) ^ $m(e[2]) ^ qm(e[3])),
    (t[1] = qm(e[0]) ^ Gm(e[1]) ^ Ym(e[2]) ^ $m(e[3])),
    (t[2] = $m(e[0]) ^ qm(e[1]) ^ Gm(e[2]) ^ Ym(e[3])),
    (t[3] = Ym(e[0]) ^ $m(e[1]) ^ qm(e[2]) ^ Gm(e[3])),
    (e[0] = t[0]),
    (e[1] = t[1]),
    (e[2] = t[2]),
    (e[3] = t[3]),
    e
  );
}

然后将所有数字相加,获得最后两位数,不足补齐0,获得a

然后通过av函数获得s,将s和a拼接为字符串及可获取hkey

a = a.length < 2 ? "0".concat(a) : a;
var s = av(o.substring(0, 5), r, -4);

简化文件#

运行环境为nodejs

const crypto = require("crypto");
const time = Math.floor(Date.now() / 1000);
function md5(c) {
  return crypto.createHash("md5").update(c).digest("hex");
}
function CreateNonce() {
  return md5(
    time + Math.random(new Date().getTime()).toString(),
  ).toLocaleUpperCase();
}
function rv(e, t) {
  (null == t || t > e.length) && (t = e.length);
  for (var n = 0, r = new Array(t); n < t; n++) r[n] = e[n];
  return r;
}
function av(e, t, n) {
  for (var r = "", i = t.slice(0, n), o = 0; o < e.length; o++)
    r += i[e.charCodeAt(o) % i.length];
  return r;
}
function sv(e, t) {
  for (var n = "", r = 0; r < e.length; r++) n += t[e.charCodeAt(r) % t.length];
  return n;
}
function CreateNewStr(Str1, Str2, Str3) {
  let str = "";
  let arr = [];
  if (Str1.length < Str2.length) {
    if (Str3.length < Str1.length) {
      arr = [Str3, Str1, Str2];
    }
    if (Str3.length > Str2.length) {
      arr = [Str1, Str2, Str3];
    }
    if (Str3 > Str1 && Str3 < Str2) {
      arr = [Str1, Str3, Str2];
    }
  }
  if (Str2.length < Str1.length) {
    if (Str3.length < Str1.length) {
      arr = [Str3, Str2, Str1];
    }
    if (Str3.length > Str2.length) {
      arr = [Str2, Str1, Str3];
    }
    if (Str3 > Str1 && Str3 < Str2) {
      arr = [Str2, Str3, Str1];
    }
  }
  const theMax = Math.max(Str1.length, Str2.length, Str3.length);
  for (let i = 0; i < theMax; i++) {
    if (arr[0].length < i + 1 && arr[1].length > i) {
      str += arr[1][i];
      str += arr[2][i];
      continue;
    }
    if (arr[1].length < i + 1) {
      str += arr[2][i];
      continue;
    }
    str += arr[0][i];
    str += arr[1][i];
    str += arr[2][i];
  }
  return str;
}
function Vm(e) {
  return 128 & e ? 255 & ((e << 1) ^ 27) : e << 1;
}
function qm(e) {
  return Vm(e) ^ e;
}
function $m(e) {
  return qm(Vm(e));
}
function Ym(e) {
  return $m(qm(Vm(e)));
}
function Gm(e) {
  return Ym(e) ^ $m(e) ^ qm(e);
}
function Km(e) {
  var t = [0, 0, 0, 0];
  return (
    (t[0] = Gm(e[0]) ^ Ym(e[1]) ^ $m(e[2]) ^ qm(e[3])),
    (t[1] = qm(e[0]) ^ Gm(e[1]) ^ Ym(e[2]) ^ $m(e[3])),
    (t[2] = $m(e[0]) ^ qm(e[1]) ^ Gm(e[2]) ^ Ym(e[3])),
    (t[3] = Ym(e[0]) ^ $m(e[1]) ^ qm(e[2]) ^ Gm(e[3])),
    (e[0] = t[0]),
    (e[1] = t[1]),
    (e[2] = t[2]),
    (e[3] = t[3]),
    e
  );
}
function CreateHkey(path, time, nonce) {
  path = "/".concat(
    path
      .split("/")
      .filter(function (e) {
        return e;
      })
      .join("/"),
    "/",
  );
  const r = "AB45STUVWZEFGJ6CH01D237IXYPQRKLMN89"
  const str1 = av(String(time), r, -2)
  const str2 = sv(path, r);
  const str3 = sv(nonce, r);
  const NewStr = CreateNewStr(str1, str2, str3)
  const strMd5 = md5(NewStr.slice(0, 20))
  const last = strMd5.slice(-6).split("")
  const ascil = last.map((c) => c.charCodeAt())
  const mixed = Km(ascil);
  const sum = mixed.reduce((s, n) => s + n, 0)
  const a = (sum % 100).toString().padStart(2, "0")
  const s = av(strMd5.substring(0, 5), r, -4)
  return "".concat(s).concat(a);
}
const nonce = CreateNonce();
const key = CreateHkey("/bbs/app/api/link/post", time, nonce)
console.log(time, nonce, key)

注意!#

不要滥用!等到官方重视起来的话大家都没得用!!!

日后谈#

有空写个go的

小黑盒web获取hkey及nonce
https://blog.sakurasen.cn/post/1778064732895/
作者
SEN
发布于
2026-05-06
许可协议
SEN-FREE-License