CTF 杂项之隐写术

隐写在 CTF 中需要经验以及特别需要脑洞...没有系统的学习方式, 只能慢慢积累...以下是我开过的脑洞

图片隐写

图种

原理

正常的文件一般都有有开始符与结束符的. 以 jpg 为例.
FF D9 这是 jpg 文件的结束符, 利用 winhex 可以看到正常的 jpg 结尾都是 FF D9 的. 图片查看器会忽视 jpg 结束符之后的内容, 所以我们可以在 jpg 结束符之后插入 flag. 当然, 插入一个 zip 也是没问题的, 既不会影响图片的显示, 还隐藏了文件. 当我们把图片以 zip 方式解压时, 解压软件会找到 zip 头到 zip 尾之间的内容, 然后进行解压. 也就是说, 这时候, zip 头之前的 jpg 部分对 zip 解压同样没有影响.
如果你想尝试, 可以利用 copy 命令, 将两个文件以二进制方式连接起来, 生成 output.jpg 的新文件. 首先制作一个 zip(1.zip), 把想要隐藏的东西放进去, 再准备一张 jpg 图片(1.jpg), 然后打开 cmd 可以执行一个命令 copy /b 1.jpg + 1.zip output.jpg 就可以得到一个图种.

解法

这种类型的隐写很容易被发现, 用 winhex 打开看一下图片是不是以标准的结束符结尾的. 解法也简单, 如果发现藏着 zip 文件, 无脑点的解法, 把图片改后缀为 zip 解压就行了. 最好是用 winhex 手动提取, 因为有些宿主图片的数据块部分可能会包含 zip 头(如下图), 直接解压会有问题(解压软件先遇到第一个假头). 如果是其他的文件, 可以用 winhex 提取到新的文件里, 再进行下一步处理.
出现混合的情况
### LSB 像素隐写
#### 吐槽
- LSB 隐写的原理很简单, 但是我没找到哪个博客把它写清楚了...都是抄来抄去, 没意思...Stegsolve 谁都会用, 但是为什么就能够这样隐写呢? (如果遇到不懂的地方, 建议先往下看, 也许就懂了)

我们得先知道

  • 啥叫 LSB?
    • (Least Significant Bit), 即最低有效位. 图片有很多种色彩模式, 其中有一种叫做"RGB"的色彩模式. 具体介绍看这里百度百科.
  • 能修改其他位么?
    • LSB 修改的是最低位, 也就是 8 位中的最后一位. 要修改其他位也是可以的, 但是, 修改的位数太高会出现问题, 这个后面再提.

原理

我们举 png 为例好了

  1. LSB 隐写信息藏在哪?

    在 RGB 色彩模式中, 每个颜色可以取 0~255, 对应的二进制就是 00000000~11111111, 8 位. LSB 隐写就是修改了 RGB 中的某分量(或者说通道)最低的 1bit(如[0, 0, 0]修改为[0, 0, 1], 即 00000000 -> 00000001).见下图.
    修改前

    修改后
    不仅是 0 -> 1, 1 -> 0 也是一样的, 在人眼看来没有区别. 刚才我们修改的是 b 的值, 也就是蓝色通道的值, 当然, 修改红色或者绿色通道的值也是一样的(如[0, 0, 0]修改为[1, 0, 0]). 这个地方先记住, 后面用得着.

  2. 啰嗦了那么多, 还不如看个栗子.

    下图是原图. 如果我们知道它是 LSB 隐写, 要怎么提取呢?
    lsb.png

    RGB 既然是 3 个数, 那么一张图片就可以看做是一个很大的 3 维数组(记为 I).这时 I(x, y, 1) == 坐标(x, y)处的 r 值. 我们先提取某个通道, 比如 r 通道(记为 R). 提取后就成了 R(x, y) == 坐标(x, y)处的 r 值.这样, R 矩阵就存放着每个像素点的 r 值.再把 r 值转为 2 进制数, 提取 8 位中的最后一位.上面已经说了原理, LSB 中, 最低位 0 可能变为 1, 0 也可能变为 1.不管怎么变, 总是会周围 r 值不一样.那我们令 1 为白, 0 为黑, 然后画出图像(也就是二值图像).这样就可以很明显的看出隐藏的部分.

    2.1 MATLAB 实现

    1
    2
    3
    4
    5
    clc;clear;
    I=imread('lsb.png'); % 读取图片, 返回值为一个 3 维矩阵
    y=bitget(I, 1); % 第 1 个位
    imshow(logical(y(:, :, 1))); % 红色通道
    title(['红色 第 1 个位']);

    2.2. 数据截图
    原始 r 通道数据
    提取最后一位

    2.3. 运行截图
    细心点会发现, 数据截图里的红色笔迹框起来的部分就是二维码的左上角的一部分. 并且是将最低位变为 0, 也就是 255 -> 254.
    提取结果

    g, b 通道的 LSB 隐写和 r 通道是一样的. 只需要对 r, g, b 分别进行上述操作, 就可以分别提取 3 个通道的图片.

扩展

  1. 能不能擦除隐写痕迹?
    可以. 就以上面的原图为例, 我们对 R 进行遍历, 遇到 254 就换成 255. MATLAB 里只需添加一句 I(I(:, :, 1)==254)=255;. 当然修改二进制也是一样的.
    数据截图
    擦除后的数据

    擦除后的最后一位

    结果截图
    擦除

  2. 能不能把 r 通道的隐写换成其他通道, 比如 g?
    可以. 也是遍历, 修改 g 通道的值为 r 通道的值 并擦除 r 通道的隐写. (不擦除的话这 2 个通道都有隐写). 添加以下代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [w, h, ~]=size(I);
    for i=1:w
    for j=1:h
    if I(i, j, 1)==254
    I(i, j, 1)=255;
    I(i, j, 2)=254; % 转为绿色通道隐写
    end
    end
    end

    结果截图
    转移后的 r 通道
    转移后的 g 通道

  3. 以 r 通道隐写为例, 怎么转移到其他位?
    LSB 是最低位隐写, 要是修改其他位(如 00000000 -> 00000010), 这样应该不算 LSB 隐写, 但是还是挺常见的. 只需要对 R 进行遍历, 遇到 254 改为 255-2^1 就行.

    结果截图
    移动后的 r 通道第 1 位

    移动后的 r 通道第 2 位
  4. 修改的位数太高, 会怎么样?
    之前遗留的一个问题. 修改的位数太高, 会导致与周围像素差别过大, 不用提取用肉眼就可以看出来.

    结果截图
    修改红色通道第五位

  5. 上面的结果和 stegsolve 的一样, 但是 stegsolve 里还有 Alpha 通道, 这是什么?
    上面说的都是 RGB 色彩模式. 还有一种色彩模式, 叫RGBA. 多了一个叫 Alpha 的通道. MATLAB 的 imread 函数返回值其实是 3 个, [I, (没用到), alpha]. 这样, Alpha 的通道的做法其实和 I 一样, 只不过 I 是 3 维的, 而 alpha 是 2 维的.

    原图如下图
    hctf.png
    5.1 代码

    1
    2
    3
    4
    5
    clc;clear;
    [I, ~, alpha]=imread('hctf.png');
    y=bitget(alpha, 1); % 位数
    imshow(logical(y));
    title(['alpha 第 1 个位']);

    5.2 运行结果

    alpha 通道第 1 位
  6. stegsolve 里还有个叫 Random colour map 的变换.
    6.1 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    clc;clear;
    [I0, ~, alpha]=imread('hctf.png'); % 特别注意这里要用 3 个参数来接收返回值, 原因看下面.
    imshow(I0);
    pause(1);
    while 1
    I=I0;
    y0=bitget(I, 1);
    for i=1:3
    y=y0(:, :, i);
    y(find(y == 0))=randi([1, 255]);
    y(find(y == 1))=randi([1, 255]);
    I(:, :, i)=y;
    end
    imshow(I);
    pause(0.5)
    end

    6.2 为什么要用 3 个参数接收?
    以 hctf.png 为例好了
    RGBA(r1, g1, b1, alpha)转 RGB(r, g, b)的公式为 r=r1*alpha, g=g1*alpha, b=b1*alpha.(alpha 为值域[0, 1]之间的小数)
    当使用 [I0, ~, alpha]=imread('hctf.png'); 时, 由于 hctf.png 为 RGBA 色彩模式. 得到的 I0 实际上是(r1, g1, b1). 这里的 alpha 值域为[1, 255]的整数, 在转为 rgb 的时候要/255 再乘(r1, g1, b1).
    当使用 I1=imread('hctf.png'); 时, 得到 I1 实际上是(r , g , b).
    可以用下面的代码检验

    1
    2
    3
    4
    5
    6
    [I0, ~, alpha]=imread('hctf.png');
    I1=imread('hctf.png');
    for i=1:3
    c=uint8(double(I0(:, :, i)).*(double(alpha)/255)); %RGBA 转 RGB
    isequal(c, I1(:, :, i)) % 判断矩阵是否相等
    end

    结果均为 1.

    RGBA 模式的图片使用 RGB 提取的话, 无法分别体现 r, g, b, alpha 4 个通道, 因为已经经过一次转换了. 换句话说, 会丢失数据.

  7. 还有个 colour inversion (xor)
    xor 指的是 8 位二进制与 11111111 异或, 实际上就是用 255-像素值.

    7.1 代码

    1
    imshow(255-imread('lsb.png'))

    7.2 结果
    xor

  8. 还有个 Gray bits
    实际上是当某个点的 RGB3 个值相等时, 变成为白色, 否则变为黑色
    8.1 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    clc;clear;
    [I, ~, alpha]=imread('lsb.png');
    I0=I;
    [i,j,k]=size(I0);
    for x=1:i
    for y =1:j
    if isequal(I0(x,y,1),I0(x,y,2),I0(x,y,3))==1 %RGB3 个值相等
    I0(x,y,1)=255;
    I0(x,y,2)=255;
    I0(x,y,3)=255;
    else
    I0(x,y,1)=0;
    I0(x,y,2)=0;
    I0(x,y,3)=0;
    end
    end
    end
    imshow(I0)

    8.2 结果截图
    Gray bits

  9. 最后还有 4 个 Full red, Full green, Full blue, Full alpha. 不过这些都用的少.
    9.1 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [I, ~, alpha] = imread('lsb.png');
    R=I;R(:,:,[2 3])=0;
    G=I;G(:,:,[1 3])=0;
    B=I;B(:,:,[1 2])=0;
    A=alpha;A(:,:,[2 3])=0;
    subplot(4,1,1);imshow(R);
    subplot(4,1,2);imshow(G);
    subplot(4,1,3);imshow(B);
    subplot(4,1,4);imshow(A);

    9.2 结果截图
    full

代码总结

MATLAB 实现 stegsolve

字符串隐写

LSB 字符隐写

其实和 LSB 像素隐写原理差不多, 有空再更新.

Base64 隐写

Base64 隐写术

来呀快活呀


CTF 杂项之隐写术
https://www.tr0y.wang/2017/06/07/CtfMiscStega/
作者
Tr0y
发布于
2017年6月7日
更新于
2024年3月28日
许可协议