JS调试技巧:如何让时间和随机数“听你指挥”?升级版!

大家好,之前写过一篇JS调试技巧:如何让时间和随机数“听你指挥”?的文章,讲到了如何固定时间和随机数,让随机的结果变得固定。本篇文章分享一种升级版的方法,让固定方法不那么容易被发现。

固定随机数

上一篇文章中提到,使用 hook 方法固定随机数,如果只是单纯的固定随机数让其返回一个固定的值,在某些情况下容易被发现,一旦被检测到随机数的返回值固定不变,就会导致代码死循环或者报错。

猿人学 2024 新春题目就会检测随机数,如果我固定 0.5 的话,就会卡死浏览器,猜测应该是走了错误的分支。

这个时候,要如何让随机数的结果“固定”,但是又不“固定”呢?答案就是伪随机数。

伪随机数的生成有两种方式,一种是伪随机数数组,使用一个固定的数组来模拟,另一种就是伪随机算法。

伪随机数数组

伪随机数数组的实现方案有两种,一种是使用固定的随机数数组来模拟随机数,另一种就是使用伪随机数算法,来动态的计算随机数。一个是实时计算,另一个是提前计算然后存储起来,大同小异。

这里我使用固定的随机数数组来模拟伪随机数,首先看下之前的固定随机数的方案:

1
2
3
4
5
6
7
8
9
10
// 备份原函数
const originalRandom = Math.random;
// 重写为固定返回值
Math.random = function() {
return 0.123456789; // 替换为所需固定值
};
// 防止检测:伪装原生函数特征
Math.random.toString = function() {
return "function random() { [native code] }";
};

为了能获取一些随机数用于填充数组,要先修改上面的方法,新建一个数组来存储获取到的随机数,然后运行一遍程序,填充数组后再重放即可。

1
2
3
4
5
6
7
const originalRandom = Math.random;
let list = [];
Math.random = function () {
let value = originalRandom();
list.push(value);
return value;
};

执行上面的 hook,list 数组会被填充,获取 list 数组的值并保存下来。

1
2
3
4
5
6
const originalRandom = Math.random;
let list = [0.1, 0.2, 0.3, 0.4, 0.5];
let count = 0
Math.random = function () {
return list[count++];
};

修改 hook 代码后使用数组的值重放即可,后面无论如何调用随机数函数,都会获得一个固定的随机数序列。

这里会有一个问题,如果 hook 以后随机数调用的次数超出了数组的长度怎么办?

这个时候需要定义一个比较长的数组,或者使用后备方案,如果真的超出了,为了不报错就使用真实的随机数函数好了。

修改代码如下:

1
2
3
4
5
6
const originalRandom = Math.random;
let list = [0.1, 0.2, 0.3, 0.4, 0.5];
let count = 0
Math.random = function () {
return list[count++] || originalRandom();
};

在数组越界后调用真实的随机数即可。

伪随机算法

如果不想保存一个很大的数组,可以考虑使用伪随机算法,伪随机算法可以保证对于同一个种子,输出相同的随机数。举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createXorshift32(seed) {
var state = typeof seed === 'number' ? seed : 1;

return function() {
var x = state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
state = x;
return (x >>> 0) / 0xFFFFFFFF; // 转为 [0, 1)
};
}

// 使用示例
var xorshift = createXorshift32(456);
console.log(xorshift());

执行结果:

image-20250527231646750

可以看到,针对相同的种子,获取的随机数是相同的。

当然这里大家也可以使用自己的伪随机算法,或者自己创造一个都可以,其实伪随机算法就是一个比较简单的计算,根据输入计算输出即可,只要结果符合随机数的范围。

还有更多伪随机算法我这里只写一个,大家可以自行搜集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createMulberry32(seed) {
var state = typeof seed === 'number' ? seed : 1;

return function() {
state += 0x6D2B79F5;
var t = state;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 0xFFFFFFFF;
};
}

// 使用示例
var mulberry = createMulberry32(789);
console.log(mulberry());

有了伪随机算法后,更改随机数 hook 方案为以下代码即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

let count = 0
Math.random = function () {
return createXorshift32(count++)()
};

function createXorshift32(seed) {
var state = typeof seed === 'number' ? seed : 1;

return function() {
var x = state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
state = x;
return (x >>> 0) / 0xFFFFFFFF; // 转为 [0, 1)
};
}

运行结果如下:

image-20250527232021998

结果符合预期。只是数据不是特别“随机”,这个就需要大家去调整伪随机算法了。

注:以上部分方案来自于零点大佬的 B 站视频(https://www.bilibili.com/video/BV1Eu4m1A7Zm/),在此感谢零点大佬的分享。

固定时间

固定时间的方案和固定随机数相同,只不过时间是递增的,也可以使用数组保存起来重放,具体代码就留给大家自行实现啦。

总结

以上就是升级版的固定随机数和时间的方案了,对于猿人学 2024 新春题目可以达到完美的固定效果,大家感兴趣可以尝试一下。

本文章首发于个人博客 LLLibra146’s blog

本文作者:LLLibra146

更多文章请关注公众号 (LLLibra146):LLLibra146

版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!

本文链接
https://blog.d77.xyz/archives/aaa4b750.html