起因
很久很久以前,有一道送分题没做出来,后来看writeup,只要zsteg就行了。
命令运行的结果
root@LAPTOP-GE0FGULA:/mnt/d# zsteg 瞅啥.bmp
[?] 2 bytes of extra data after image end (IEND), offset = 0x269b0e
extradata:0 .. ["x00" repeated 2 times]
imagedata .. text: ["r" repeated 18 times]
b1,lsb,bY ..
b1,msb,bY .. text: "qwxf{you_say_chick_beautiful?}"
b2,msb,bY .. text: "i2,C8&k0."
b2,r,lsb,xY .. text: "UUUUUU9VUUUUUUUUUUUUUUUUUUUUUU"
b2,g,msb,xY .. text: ["U" repeated 22 times]
b2,b,lsb,xY .. text: ["U" repeated 10 times]
b3,g,msb,xY .. text: "V9XDR\d@"
b4,r,lsb,xY .. file: TIM image, Pixel at (4353,4112) Size=12850x8754
b4,g,lsb,xY .. text: "3"""""3###33##3#UDUEEEEEDDUETEDEDDUEEDTEEEUT#!"
b4,g,msb,xY .. text: """""""""""""""""""""DDDDDDDDDDDD""""DDDDDDDDDDDD*LD"
b4,b,lsb,xY .. text: "gfffffvwgwfgwwfw"
b1,msb,bY读取到的flag,看的一脸懵逼,msb是啥?不是lsb隐写么?bY的b又是啥?我用stegsolve怎么没找到flag?
结论
两个工具的一些参数在理解上有点疑问,因此查看了源码。
Stegsolve的Data Extract功能,Bit Order选项MSBFirst和LSBFirst的区别,这个在扫描顺序中说明
zsteg不理解参数更多
-c:rgba的组合理解,r3g2b3则表示r通道的低3bit,g通道2bit,r通道3bit,如果设置为rbg不加数字的,则表示每个通道读取bit数相同,bit数有-b参数设置
-b:设置每个通道读取的bit数,从低位开始,如果不是顺序的低位开始,则可以使用掩码,比如取最低位和最高位,则可以-b 10000001或者-b 0x81
-o:设置行列的读取顺序,xy就是从上到下,从左到右,xy任意有大写的,表示倒序,不过栗子中有个bY令我费解,查看源码知道对于BMP的图片,可以不管通道,直接按字节读取,就是b的意思了,b再顺带表示x,也就是bY的顺序和xY是一样的,Yb和Yx的顺序是一样的,但是b这个的读取模式跟-c bgr -o xY好像是一样的(因为看BMP图片通道排列顺序是BGR),不太理解专门弄个这个出来干嘛。
--msb和--lsb这个在组合顺序中说明
扫描顺序
行列顺序
先说下行列的扫描顺序
zsteg可以通过-o选项设置的8种组合(xy,xY,Xy,XY,yx,yX,Yx,YX),个人认为常用的就xy和xY吧
Stegsolve只有选项设置Extract By Row or Column,对应到zsteg的-o选项上就是xy和yx
字节顺序
然后是字节上的扫描顺序,因为是读取的bit再拼接数据的,那么一个字节有8bit数据,从高位开始读还是从低位开始读的顺序
Stegsolve:字节上的读取顺序与Bit Order选项有关,如果设置了MSBFirst,是从高位开始读取,LSBFirst是从低位开始读取
zsteg:只能从高位开始读,比如-b 0x81,在读取不同通道数据时,都是先读取一个字节的高位,再读取该字节的低位。对应到Stegsolve就是MSBFirst的选项。
组合顺序
对于Stegsolve和zsteg,先读取到bit数据都是先拿出来组合的,每8bit组合成一个字节,按照最先存放的Bit在低地址理解的话。
zsteg的--lsb和--msb决定了组合顺序
--lsb:大端存放
--msb:小端存放
源码片段,a内存储的是读取的Bit数据,所以msb是低地址的是低位,因此是小端存放。
if a.size >= 8
byte = 0
if params[:bit_order] == :msb
8.times{ |i| byte |= (a.shift<
else
8.times{ |i| byte |= (a.shift<
end
Stegsolve则是只有大端存放,即对应zsteg的—lsb,因为代码中有个extractBitPos变量,初始值是128,每组合1bit,就右移一次,到0后循环。
源码片段
private void addBit(int num)
{
if(num!=0)
{
extract[extractBytePos]+=extractBitPos;
}
extractBitPos>>=1;
if(extractBitPos>=1)
return;
extractBitPos=128;
extractBytePos++;
if(extractBytePos
extract[extractBytePos]=0;
}
Stegsolve
了解一下Data Extract以及不同通道存储图片的隐写
Data Extract
功能简要说明
面板
配置选项后,是通过Preview按钮进行数据的读取,因此直接跟进该按钮事件。
Bit Planes:选取通道要读取的bit位。
Bit Plane Order:一个像素值包含多个通道,不同通道的读取数据,Alpha一直是最先读的,然后会根据该项的配置决定读取顺序。
Bit Order:读取数据时,每次仅读取1Bit,该项是控制读取一个通道字节数时,读取的方向,MSBFirst表示从高位读取到低位,LSBFirst表示从低位读取到高位。因此只有当通道勾选的Bit个数大于1时,该选项才会影响返回的结果。
代码分析
文件:Extract.java
按钮事件:
/**
* Generate the extract and generate the preview
* @param evt Event
*/
private void previewButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_previewButtonActionPerformed
generateExtract();
generatePreview();
}//GEN-LAST:event_previewButtonActionPerformed
跟进generateExtract(),存在内部调用,先列举了另外两个方法。
/**
* Retrieves the mask from the bits selected on the form
*/
/*读取Bit Planes的配置,图片getRGB会返回一个整型,如果存在alpha,那么范围最大值就是0xffffffff,从高位至低位,每一个字节按顺序对应为 A R G B,所以getMask就是获取要获取对应Bit的掩码,存为this.mask,this.maskbits记录是全部要读取的Bit数。
*/
private void getMask()
{
mask = 0;
maskbits = 0;
if(ab7.isSelected()) { mask += 1<<31; maskbits++;}
if(ab6.isSelected()) { mask += 1<<30; maskbits++;}
if(ab5.isSelected()) { mask += 1<<29; maskbits++;}
if(ab4.isSelected()) { mask += 1<<28; maskbits++;}
if(ab3.isSelected()) { mask += 1<<27; maskbits++;}
if(ab2.isSelected()) { mask += 1<<26; maskbits++;}
if(ab1.isSelected()) { mask += 1<<25; maskbits++;}
if(ab0.isSelected()) { mask += 1<<24; maskbits++;}
if(rb7.isSelected()) { mask += 1<<23; maskbits++;}
if(rb6.isSelected()) { mask += 1<<22; maskbits++;}
if(rb5.isSelected()) { mask += 1<<21; maskbits++;}
if(rb4.isSelected()) { mask += 1<<20; maskbits++;}
if(rb3.isSelected()) { mask += 1<<19; maskbits++;}
if(rb2.isSelected()) { mask += 1<<18; maskbits++;}
if(rb1.isSelected()) { mask += 1<<17; maskbits++;}
if(rb0.isSelected()) { mask += 1<<16; maskbits++;}
if(gb7.isSelected()) { mask += 1<<15; maskbits++;}
if(gb6.isSelected()) { mask += 1<<14; maskbits++;}
if(gb5.isSelected()) { mask += 1<<13; maskbits++;}
if(gb4.isSelected()) { mask += 1<<12; maskbits++;}
if(gb3.isSelected()) { mask += 1<<11; maskbits++;}
if(gb2.isSelected()) { mask += 1<<10; maskbits++;}
if(gb1.isSelected()) { mask += 1<<9; maskbits++;}
if(gb0.isSelected()) { mask += 1<<8; maskbits++;}
if(bb7.isSelected()) { mask += 1<<7; maskbits++;}
if(bb6.isSelected()) { mask += 1<<6; maskbits++;}
if(bb5.isSelected()) { mask += 1<<5; maskbits++;}
if(bb4.isSelected()) { mask += 1<<4; maskbits++;}
if(bb3.isSelected()) { mask += 1<<3; maskbits++;}
if(bb2.isSelected()) { mask += 1<<2; maskbits++;}
if(bb1.isSelected()) { mask += 1<<1; maskbits++;}
if(bb0.isSelected()) { mask += 1; maskbits++;}
}
/**
* Retrieve the ordering options from the form
*/
/* 读取Order setting的配置,主要就是rgbOrder的不同值对应的顺序
*/
private void getBitOrderOptions()
{
if(byRowButton.isSelected()) rowFirst = true;
else rowFirst = false;
if(LSBButton.isSelected()) lsbFirst = true;
else lsbFirst = false;
if(RGBButton.isSelected()) rgbOrder = 1;
else if (RBGButton.isSelected()) rgbOrder = 2;
else if (GRBButton.isSelected()) rgbOrder = 3;
else if (GBRButton.isSelected()) rgbOrder = 4;
else if (BRGButton.isSelected()) rgbOrder = 5;
else rgbO