当前位置:首页>python>Python实现“隐写术与内存执行”演示

Python实现“隐写术与内存执行”演示

  • 2026-10-11 06:56:05
Python实现“隐写术与内存执行”演示

“隐写术与内存执行”演示代码中的 JavaScript 部分 进行详细拆解,逐段解释其设计思路、关键函数、位操作以及执行流程。方便你彻底理解这段代码是如何实现“图片隐写 → 提取 → 执行”的完整链路。


🧩 整体结构概览

整个 JavaScript 代码按顺序完成以下五件事:

  1. 构造负载(Payload) → 准备要隐藏的 JavaScript 代码(弹窗)。
  2. 字符串转字节数组 → 将负载转换为 UTF-8 编码的字节序列,并附加结束标志 0x00。
  3. 图像绘制与隐写嵌入 → 生成渐变图像,获取像素数据,将字节按位写入蓝色通道的最低有效位。
  4. 解码与执行函数 → 从图像中提取隐藏的字节,还原为字符串,通过 eval() 执行。
  5. 自动触发与按钮绑定 → 页面加载后自动解码执行,并提供手动触发按钮。

🔍 详细解析(按代码顺序)

1. 准备 Payload

const payload = "alert('🎯 合法验证:隐写术内存执行成功!\\n图片中隐藏的代码已被提取并执行。');";
  • 这是一个普通的 JavaScript 字符串,内容是调用 alert() 弹出一个提示框。
  • 在实际攻击场景中,这里可能是下载木马、窃取 Cookie 等恶意代码,但本演示仅用于合法验证。
  • 我们刻意让 payload 包含中文和特殊字符,以测试 UTF-8 编码的完整性。

2. 字符串 → 字节数组(UTF-8)

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,就停止提取,避免读取无关像素。

3. 图像绘制与隐写嵌入

3.1 获取 Canvas 上下文并绘制渐变背景

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);
  • 用四种颜色绘制从左下到右上的线性渐变,使图片看起来自然、美观。
  • 然后在中央写入装饰文字 “🖼️ 风景”,增加“正常图片”的伪装。

3.2 获取像素数据

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 字节。

3.3 嵌入数据(LSB 隐写)

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++;
    }
}
  • 循环遍历所有像素(每次跳 4 个字节),只修改蓝色通道(i+2)。
  • bitIndex 表示当前正在处理当前字节的第几位(0~7,从高位到低位)。
  • byteIndex 表示当前正在处理的 dataBytes 中的第几个字节。
  • 对于每个像素,取出当前字节的当前位(高位优先),然后将该位写入蓝色通道的最低有效位(LSB)。
    • (data[blueIndex] & 0xFE) 将蓝色通道的最低位清零,然后 | bit 写入新位。
  • 每处理完 8 个像素,就完成一个字节的嵌入,然后 byteIndex++,bitIndex 重置为 0。
  • 当所有 dataBytes(包括结束标志)都写入后,循环停止(提前 break)。

注意:由于我们只修改了每个像素蓝色通道的最后一位,对颜色的影响微乎其微,人眼几乎无法察觉。

3.4 将修改后的像素数据放回 Canvas

ctx.putImageData(imageData, 0, 0);
  • 将修改后的像素数组重新渲染到 Canvas 上,此时图片表面看起来与原来完全一样,但实际已隐藏数据。

4. 解码与执行函数

functiondecodeAndExecute() {
// ...
}

这个函数是核心,完成提取、还原、执行三步。

4.1 提取像素数据

const imgData = ctx.getImageData(0, 0, width, height);
const pixels = imgData.data;
  • 再次获取当前 Canvas 的像素数据(此时已包含嵌入信息)。

4.2 提取字节

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 并 | 上新位。
  • 当累计 8 位后,形成一个完整的字节,存入 extractedBytes 数组。
  • 如果该字节是 0x00,说明已经到达结束标志,立刻跳出循环(不再继续读取后续像素)。
  • 注意:这里提取的顺序与嵌入时一致(高位优先),因此能正确还原。

4.3 去除结尾的空字节

if (extractedBytes.length > 0 && extractedBytes[extractedBytes.length - 1] === 0x00) {
    extractedBytes.pop();
}
  • 由于嵌入时在末尾添加了 0x00,提取时也会包含它。这里将其移除,得到纯 payload 字节数组。

4.4 将字节数组转为字符串

const decoder = new TextDecoder();
const hiddenString = decoder.decode(newUint8Array(extractedBytes));
  • TextDecoder 是 TextEncoder 的逆操作,将 Uint8Array 按 UTF-8 解码为字符串。
  • 如果嵌入时编码正确,这里将还原出原始 payload 字符串。

4.5 执行代码

try {
console.log('提取的代码:', hiddenString);
eval(hiddenString);
    statusEl.textContent = '✅ 代码已执行 (弹窗已显示)';
} catch (e) {
    statusEl.textContent = `❌ 执行失败: ${e.message}`;
}
  • 在控制台输出提取到的代码,便于调试。
  • 使用 eval() 执行还原出的 JavaScript 代码。
  • 如果执行成功,会弹出 alert 窗口,并更新状态文本。
  • 如果执行失败(例如语法错误),捕获异常并提示。

5. 自动触发与按钮绑定

window.addEventListener('load', function() {
    setTimeout(decodeAndExecute, 500);
});
  • 页面加载完成后,延迟 500ms 自动执行解码函数,确保 Canvas 已渲染完毕。
  • 这样做是为了让用户看到状态变化的过程。
document.getElementById('decodeBtn').addEventListener('click', decodeAndExecute);
  • 为按钮绑定点击事件,允许用户手动触发解码执行。

🧠 关键技术点总结

技术点
作用
LSB 隐写
将数据隐藏在蓝色通道的最低有效位,视觉无影响,容量大(每像素 1 位)。
TextEncoder / TextDecoder
实现字符串与 UTF-8 字节数组之间的转换,支持中文等多字节字符。
Canvas getImageData / putImageData
获取/修改像素级数据,是前端图像处理的核心 API。
**位操作(&, `
, 
<<, >>`)**
eval()
执行动态生成的代码,是“内存执行”的关键,也是安全风险所在(本演示合法)。
结束标志(0x00)
防止读取多余像素,确保数据边界清晰。

⚠️ 安全提示

  • 本演示仅供学习 隐写术 和 代码动态执行 原理,请勿用于非法用途。
  • 实际攻击中,eval() 常被用于执行经过混淆或加密的恶意载荷,以绕过静态检测。
  • 在正常开发中,应避免使用 eval(),除非在完全受控的环境下(如本演示)。

如果你对某一部分还有疑问,比如位操作的细节、Canvas 像素排列方式,或者 TextEncoder 与 Blob 的区别,随时可以继续提问。我会进一步展开解释。

最新文章

随机文章