“隐写术与内存执行”演示代码中的 JavaScript 部分 进行详细拆解,逐段解释其设计思路、关键函数、位操作以及执行流程。方便你彻底理解这段代码是如何实现“图片隐写 → 提取 → 执行”的完整链路。
整个 JavaScript 代码按顺序完成以下五件事:
0x00。eval() 执行。const payload = "alert('🎯 合法验证:隐写术内存执行成功!\\n图片中隐藏的代码已被提取并执行。');";
alert() 弹出一个提示框。functionstringToBytes(str) {
const encoder = new TextEncoder();
return encoder.encode(str);
}
TextEncoder 是浏览器原生 API,用于将字符串按 UTF-8 编码转换为 Uint8Array(字节数组)。接着:
const payloadBytes = stringToBytes(payload);
const dataBytes = newUint8Array(payloadBytes.length + 1);
dataBytes.set(payloadBytes);
dataBytes[payloadBytes.length] = 0x00; // 结束标志
dataBytes,长度比 payloadBytes 多 1,用于存放结束标志 0x00(空字节)。dataBytes 的前面部分为 payload 字节,最后一位写入 0x00。0x00,就停止提取,避免读取无关像素。const canvas = document.getElementById('stegoCanvas');
const ctx = canvas.getContext('2d');
const width = canvas.width; // 400
const height = canvas.height; // 300
<canvas> 元素及其 2D 绘图上下文。绘制渐变背景:
const gradient = ctx.createLinearGradient(0, 0, width, height);
gradient.addColorStop(0, '#ff6b6b');
gradient.addColorStop(0.3, '#feca57');
gradient.addColorStop(0.6, '#48dbfb');
gradient.addColorStop(1, '#1dd1a1');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data; // Uint8ClampedArray
getImageData() 返回一个 ImageData 对象,其 data 属性是一个 Uint8ClampedArray,包含所有像素的 RGBA 值,每个像素占 4 个字节(R, G, B, A),按行连续排列。width * height * 4 = 400 * 300 * 4 = 480,000 字节。let bitIndex = 0;
let byteIndex = 0;
for (let i = 0; i < data.length; i += 4) {
const blueIndex = i + 2; // 蓝色通道索引
if (byteIndex >= dataBytes.length) break;
const currentByte = dataBytes[byteIndex];
const bit = (currentByte >> (7 - bitIndex)) & 1;
data[blueIndex] = (data[blueIndex] & 0xFE) | bit;
bitIndex++;
if (bitIndex === 8) {
bitIndex = 0;
byteIndex++;
}
}
i+2)。bitIndex 表示当前正在处理当前字节的第几位(0~7,从高位到低位)。byteIndex 表示当前正在处理的 dataBytes 中的第几个字节。(data[blueIndex] & 0xFE) 将蓝色通道的最低位清零,然后 | bit 写入新位。byteIndex++,bitIndex 重置为 0。dataBytes(包括结束标志)都写入后,循环停止(提前 break)。注意:由于我们只修改了每个像素蓝色通道的最后一位,对颜色的影响微乎其微,人眼几乎无法察觉。
ctx.putImageData(imageData, 0, 0);
functiondecodeAndExecute() {
// ...
}
这个函数是核心,完成提取、还原、执行三步。
const imgData = ctx.getImageData(0, 0, width, height);
const pixels = imgData.data;
const extractedBytes = [];
let currentByte = 0;
let bitPos = 0;
for (let i = 0; i < pixels.length; i += 4) {
const blue = pixels[i + 2];
const bit = blue & 1; // 提取最低位
currentByte = (currentByte << 1) | bit;
bitPos++;
if (bitPos === 8) {
extractedBytes.push(currentByte);
if (currentByte === 0x00) {
break; // 遇到结束标志,停止提取
}
currentByte = 0;
bitPos = 0;
}
}
blue & 1)。currentByte 并 | 上新位。extractedBytes 数组。0x00,说明已经到达结束标志,立刻跳出循环(不再继续读取后续像素)。if (extractedBytes.length > 0 && extractedBytes[extractedBytes.length - 1] === 0x00) {
extractedBytes.pop();
}
0x00,提取时也会包含它。这里将其移除,得到纯 payload 字节数组。const decoder = new TextDecoder();
const hiddenString = decoder.decode(newUint8Array(extractedBytes));
TextDecoder 是 TextEncoder 的逆操作,将 Uint8Array 按 UTF-8 解码为字符串。try {
console.log('提取的代码:', hiddenString);
eval(hiddenString);
statusEl.textContent = '✅ 代码已执行 (弹窗已显示)';
} catch (e) {
statusEl.textContent = `❌ 执行失败: ${e.message}`;
}
eval() 执行还原出的 JavaScript 代码。window.addEventListener('load', function() {
setTimeout(decodeAndExecute, 500);
});
document.getElementById('decodeBtn').addEventListener('click', decodeAndExecute);
| LSB 隐写 | |
| TextEncoder / TextDecoder | |
| Canvas getImageData / putImageData | |
&, ` | , , >>`)** |
| eval() | |
结束标志(0x00) |
eval() 常被用于执行经过混淆或加密的恶意载荷,以绕过静态检测。eval(),除非在完全受控的环境下(如本演示)。如果你对某一部分还有疑问,比如位操作的细节、Canvas 像素排列方式,或者 TextEncoder 与 Blob 的区别,随时可以继续提问。我会进一步展开解释。