<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】LSB隱寫工具對比(Stegsolve與zsteg)

    VSole2022-05-16 08:15:19

    起因

    很久很久以前,有一道送分題沒做出來,后來看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           .. "x00x8ExEE", data="x1EfxDEx9ExF6xAExFAxCEx86x9E"..., even=false>
    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<<(7-i))}
      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 rgbOrder = 6;
        }
         /**
         * Generates the extract from the selected options
         */
        private void generateExtract()
        {
            getMask();//獲取掩碼,每個像素值要獲取的對應Bit的掩碼,以及每個像素值獲取Bit的個數。
            getBitOrderOptions();//獲取Order settings
            int len = bi.getHeight() * bi.getWidth();//獲取總的像素點
            len = len * maskbits; // 總的像素點*每個像素點獲取的Bit數=總的Bit數
            len = (len +7)/8; // 總的Bit數轉換到總的字節數,+7是沒滿一個字節的Bit數也對應到一個字節。(極端點比如總的Bit數就1~7Bit,也是要轉為1字節,所以需要+7)
            extract = new byte[len];//存儲讀取到的字節數據
            extractBitPos = 128; // 每8個Bit組成一個字節數據,extractBitPos相當于權值,從128開始,因此讀取的每8Bit,先讀到的在高位。
            extractBytePos = 0;
            //System.out.println(bi.getHeight()+" "+bi.getWidth()+" "+len+" "+mask);
            // 根據rowFirst參數來選擇讀取順序,調用extractBits讀取數據
            if(rowFirst)
            {
               for(int j=0;j              for(int i=0;i              {
                      //System.out.println(i+" "+j+" "+extractBytePos);
                      extractBits(bi.getRGB(i, j));
                  }
            }
            else
            {
               for(int i=0;i              for(int j=0;j                 extractBits(bi.getRGB(i, j));
            }
        }
    

    讀取數據是extractBits,nextByte是讀取到的一個像素點的值,如果是lsbFirst(也就是選了Bitorder為LSBFirst,默認是MSBFirst),則是從低位從高位按順序讀取(每個通道選取2Bit以上才會有影響,如果只讀取1Bit則無所謂了)。

    栗子:讀取alpha通道,lsbFirst,extract8Bits(nextByte,1<<24),掩碼是從24位開始,依次左移1位,左移8次;msbFirst,extract8Bits(nextByte,1<<31),掩碼是從31位開始,依次右移,右移8次。

        
    /**
         * Extract bits from the given byte taking account of
         * the options selected
         * @param nextByte the byte to extract bits from
         */
        private void extractBits(int nextByte)
        {
            if(lsbFirst)
            {
                extract8Bits(nextByte,1<<24);
                switch(rgbOrder)
                {
                    case 1: //rgb
                        extract8Bits(nextByte,1<<16);
                        extract8Bits(nextByte,1<<8);
                        extract8Bits(nextByte,1);
                        break;
                    case 2: //rbg
                        extract8Bits(nextByte,1<<16);
                        extract8Bits(nextByte,1);
                        extract8Bits(nextByte,1<<8);
                        break;
                    case 3: //grb
                        extract8Bits(nextByte,1<<8);
                        extract8Bits(nextByte,1<<16);
                        extract8Bits(nextByte,1);
                        break;
                    case 4: //gbr
                        extract8Bits(nextByte,1<<8);
                        extract8Bits(nextByte,1);
                        extract8Bits(nextByte,1<<16);
                        break;
                    case 5: //brg
                        extract8Bits(nextByte,1);
                        extract8Bits(nextByte,1<<16);
                        extract8Bits(nextByte,1<<8);
                        break;
                    case 6: //bgr
                        extract8Bits(nextByte,1);
                        extract8Bits(nextByte,1<<8);
                        extract8Bits(nextByte,1<<16);
                        break;
                }
            }
            else
            {
                extract8Bits(nextByte,1<<31);
                switch(rgbOrder)
                {
                    case 1: //rgb
                        extract8Bits(nextByte,1<<23);
                        extract8Bits(nextByte,1<<15);
                        extract8Bits(nextByte,1<<7);
                        break;
                    case 2: //rbg
                        extract8Bits(nextByte,1<<23);
                        extract8Bits(nextByte,1<<7);
                        extract8Bits(nextByte,1<<15);
                        break;
                    case 3: //grb
                        extract8Bits(nextByte,1<<15);
                        extract8Bits(nextByte,1<<23);
                        extract8Bits(nextByte,1<<7);
                        break;
                    case 4: //gbr
                        extract8Bits(nextByte,1<<15);
                        extract8Bits(nextByte,1<<7);
                        extract8Bits(nextByte,1<<23);
                        break;
                    case 5: //brg
                        extract8Bits(nextByte,1<<7);
                        extract8Bits(nextByte,1<<23);
                        extract8Bits(nextByte,1<<15);
                        break;
                    case 6: //bgr
                        extract8Bits(nextByte,1<<7);
                        extract8Bits(nextByte,1<<15);
                        extract8Bits(nextByte,1<<23);
                        break;
                }
            }
        }
    

    extract8Bits方法,針對每個通道是要單獨調用一次的,nextByte是讀取的一個像素點的數據,bitMask是對應通道的掩碼(根據extractBits方法的說明可知,如果是lsbFirst則是對應通道掩碼的最低位,msbFirst則是對應通道掩碼的最高位),在extract8Bits方法最后也有根據是lsbFirst的值選擇是左移還是右移,循環8次。

    bitMask循環,與this.mask與,如果不為0,說明是要讀取的bit,此時就將nextByte與bitMask想與,把該bit的值存入extract

    /**
         * Examine 8 bits and check them against the mask to
         * see if any should be extracted
         * @param nextByte The byte to be examined
         * @param bitMask The bitmask to be applied
         */
        private void extract8Bits(int nextByte, int bitMask)
        {
            for(int i=0;i<8;i++)
            {
                if((mask&bitMask)!=0)
                {
                    //System.out.println("call "+ mask+" "+bitMask+" "+nextByte);
                    addBit(nextByte & bitMask);
                }
                if(lsbFirst)
                   bitMask<<=1;
                else
                   bitMask>>>=1;
            }
        }
    

    addBit方法,num是讀取的像素值與相應bit的掩碼相與后的結果,如果不為0,表示那個Bit為1,否則為0,extractBitPos相當于權值,如果為1,就加extractBitPos,然后extractBitPos右移一位,如果為0就不需要加,但每次extractBitPos都是需要右移一位的,如果extractBitPos還是大于1的,說明還沒循環過8次,所以就return了,如果不大于1,說明8次了,那么重置extractBitPos為128,extractBytePos+1,新的字節extract[extractBytePos]的初始值為0。

    /**
         * Adds another bit to the extract
         * @param num Non-zero if adding a 1-bit
         */
        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;
        }
    

    不同通道讀取圖片

    功能簡要說明

    首先生成的圖片僅是黑白圖片,每個像素點的值根據讀取的bit位的值,如果為1設置為白色,如果為0設置為黑色。

    代碼分析

    打開圖片后,程序主界面上的<>按鈕可以獲取不同通道的圖片,這里僅討論Alpha7~0,Red7~0,Green7~0,Blue7~0,也就是每個通道。

    StegSolve.java中定位到按鈕方法

    private void forwardButtonActionPerformed(ActionEvent evt) {
            if(bi == null) return;
            transform.forward();
            updateImage();
        }
        private void fileOpenActionPerformed(ActionEvent evt) {
            JFileChooser fileChooser = new JFileChooser(System.getProperty("user.dir"));
            FileNameExtensionFilter filter = new FileNameExtensionFilter("Images", "jpg", "jpeg", "gif", "bmp", "png");
            fileChooser.setFileFilter(filter);
            int rVal = fileChooser.showOpenDialog(this);
            System.setProperty("user.dir", fileChooser.getCurrentDirectory().getAbsolutePath());
            if(rVal == JFileChooser.APPROVE_OPTION)
            {
                sfile = fileChooser.getSelectedFile();
                try
                {
                    bi = ImageIO.read(sfile);
                    transform = new Transform(bi);
                    newImage();
                }
                catch (Exception e)
                {
                    JOptionPane.showMessageDialog(this, "Failed to load file: " +e.toString());
                }
            }
        }
    

    主要方法定位到了Transform類,打開文件時初始化,參數是圖片的數據。

    Transform.java

    構造函數,originalImage記錄原始圖片數據,transform是轉換后的數據,先初始化為原始圖片數據,transNum的值對應不同的操作。

    /*
     * transforms
     * 0 - none
     * 1 - inversion
     * 2-9 - alpha planes
     * 10-17 - r planes
     * 18-25 - g planes
     * 26-33 - b planes
     * 34 full alpha
     * 35 full red
     * 36 full green
     * 37 full blue
     * 38 random color1
     * 39 random color2
     * 40 random color3
     * 41 gray bits
     */
        Transform(BufferedImage bi)
        {
            originalImage = bi;
            transform = originalImage;
            transNum=0;
        }
    

    forward方法,,每次點擊一次按鈕,為加一次transNum,然后根據transNum的值去執行對應的操作。transNum值對應的操作除了注釋中的說明,也可以從getText方法中獲取,栗子:Alpha plane 0對應的transNum值為9

    public void forward()
        {
            transNum++;
            if(transNum>MAXTRANS) transNum=0;
            calcTrans();
        }
        public String getText()
        {
            switch(transNum)
            {
                case 0:
                  return "Normal Image";
                case 1:
                  return "Colour Inversion (Xor)";
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                case 8:
                case 9:
                  return "Alpha plane " + (9 - transNum);
                case 10:
                case 11:
                case 12:
                case 13:
                case 14:
                case 15:
                case 16:
                case 17:
                  return "Red plane " + (17 - transNum);
                case 18:
                case 19:
                case 20:
                case 21:
                case 22:
                case 23:
                case 24:
                case 25:
                  return "Green plane " + (25 - transNum);
                case 26:
                case 27:
                case 28:
                case 29:
                case 30:
                case 31:
                case 32:
                case 33:
                  return "Blue plane " + (33 - transNum);
                case 34:
                  return "Full alpha";
                case 35:
                  return "Full red";
                case 36:
                  return "Full green";
                case 37:
                  return "Full blue";
                case 38:
                  return "Random colour map 1";
                case 39:
                  return "Random colour map 2";
                case 40:
                  return "Random colour map 3";
                case 41:
                  return "Gray bits";
                default:
                  return "";
            }
        }
    

    calcTrans方法,是一個switch方法,根據transNum的值調用方法,而我關心的不同通道獲取的圖片都是調用transfrombit方法,這里僅截取關心的

    private void calcTrans()
        {
            switch(transNum)
            {
                case 2:
                    transfrombit(31);
                    return;
                case 3:
                    transfrombit(30);
                    return;
                case 4:
                    transfrombit(29);
                    return;
                case 5:
                    transfrombit(28);
                    return;
                case 6:
                    transfrombit(27);
                    return;
                case 7:
                    transfrombit(26);
                    return;
                case 8:
                    transfrombit(25);
                    return;
                case 9:
                    transfrombit(24);
                    return;
                case 10:
                    transfrombit(23);
                    return;
                case 11:
                    transfrombit(22);
                    return;
                case 12:
                    transfrombit(21);
                    return;
                case 13:
                    transfrombit(20);
                    return;
                case 14:
                    transfrombit(19);
                    return;
                case 15:
                    transfrombit(18);
                    return;
                case 16:
                    transfrombit(17);
                    return;
                case 17:
                    transfrombit(16);
                    return;
                case 18:
                    transfrombit(15);
                    return;
                case 19:
                    transfrombit(14);
                    return;
                case 20:
                    transfrombit(13);
                    return;
                case 21:
                    transfrombit(12);
                    return;
                case 22:
                    transfrombit(11);
                    return;
                case 23:
                    transfrombit(10);
                    return;
                case 24:
                    transfrombit(9);
                    return;
                case 25:
                    transfrombit(8);
                    return;
                case 26:
                    transfrombit(7);
                    return;
                case 27:
                    transfrombit(6);
                    return;
                case 28:
                    transfrombit(5);
                    return;
                case 29:
                    transfrombit(4);
                    return;
                case 30:
                    transfrombit(3);
                    return;
                case 31:
                    transfrombit(2);
                    return;
                case 32:
                    transfrombit(1);
                    return;
                case 33:
                    transfrombit(0);
                    return;
                default:
                   transform = originalImage;
                   return;
            }
        }
    

    transfrombit方法,參數d基本就是讀取第dbit的數據,根據之前的說明 Alpha 7是getRGB的數據的最高位,第31bit,根據getText方法可以知道Aplpha 7對應的transNum值為2,再看calcTrans的case2就是調用transfrombit(31)。

    private void transfrombit(int d)
        {
            transform = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_INT_RGB);
            for(int i=0;i            for(int j=0;j            {
                    int col=0;
                    int fcol = originalImage.getRGB(i,j);
                    if(((fcol>>>d)&1)>0)//右移d個bit位,再取最低位,如果大于0表示對應Bit位為1,那么就設置對應像素值為0xffffff,也就是(255,255,255),對應白色,如果Bit位為0,則是設置為(0,0,0),對應為黑色
                       col=0xffffff;
                    transform.setRGB(i, j, col);
                 }
        
    

    zsteg

    跟進一下代碼執行流程,了解各個參數的意義。

    入口

    程序執行流程的文件

    /bin/zsteg

    /lib/zsteg.rb run方法

    /lib/zsteg/cli/cli.rb run方法,這里會對參數解析,這里截取一些之后需要用到的參數,完整的自行看源碼吧,解析完參數后,主要是最后的動態方法調用,@actions=[‘check’],因此動態調用check方法

    def run
    @actions = []
    @options = {
     :verbose => 0,
     :limit => Checker::DEFAULT_LIMIT,
     :order => Checker::DEFAULT_ORDER
    }
    optparser = OptionParser.new do |opts|
     opts.banner = "Usage: zsteg [options] filename.png [param_string]"
     opts.separator ""
     opts.on("-c", "--channels X", /[rgba,1-8]+/,
             "channels (R/G/B/A) or any combination, comma separated",
             "valid values: r,g,b,a,rg,bgr,rgba,r3g2b3,..."
     ) do |x|
       @options[:channels] = x.split(',')
       # specifying channels on command line disables extra checks
       @options[:extra_checks] = false
     end
     opts.on("-b", "--bits N", "number of bits, single int value or '1,3,5' or range '1-8'",
             "advanced: specify individual bits like '00001110' or '0x88'"
     ) do |x|
       a = []
       x = '1-8' if x == 'all'
       x.split(',').each do |x1|
         if x1['-']
           t = x1.split('-')
           a << Range.new(parse_bits(t[0]), parse_bits(t[1])).to_a
         else
           a << parse_bits(x1)
         end
       end
       @options[:bits] = a.flatten.uniq
       # specifying bits on command line disables extra checks
       @options[:extra_checks] = false
     end
     opts.on "--lsb", "least significant BIT comes first" do
       @options[:bit_order] = :lsb
     end
     opts.on "--msb", "most significant BIT comes first" do
       @options[:bit_order] = :msb
     end
     opts.on("-o", "--order X", /all|auto|[bxy,]+/i,
             "pixel iteration order (default: '#{@options[:order]}')",
             "valid values: ALL,xy,yx,XY,YX,xY,Xy,bY,...",
     ){ |x| @options[:order] = x.split(',') }
      if (argv = optparser.parse(@argv)).empty?
        puts optparser.help
        return
      end
      @actions = DEFAULT_ACTIONS if @actions.empty?
      argv.each do |arg|
        if arg[','] && !File.exist?(arg)
          @options.merge!(decode_param_string(arg))
          argv.delete arg
        end
      end
      argv.each_with_index do |fname,idx|
        if argv.size > 1 && @options[:verbose] >= 0
          puts if idx > 0
          puts "[.] #{fname}".green
        end
        next unless @img=load_image(@fname=fname)
        @actions.each do |action|
          if action.is_a?(Array)
            self.send(*action) if self.respond_to?(action.first)
          else
            self.send(action) if self.respond_to?(action)
          end
        end
      end
    rescue Errno::EPIPE
      # output interrupt, f.ex. when piping output to a 'head' command
      # prevents a 'Broken pipe -  (Errno::EPIPE)' message
    end
    

    /lib/zsteg/cli/cli.rb check方法

    def check Checker.new(@img, @options).check end
    

    /lib/zsteg/checker.rb initialize方法,初始化一些成員變量,@extractor也是傳入了圖像數據的,通道判斷了圖片屬性是否有alpha通道。

     
    def initialize image, params = {}
    @params = params
       @cache = {}; @wastitles = Set.new
       @image = image.is_a?(ZPNG::Image) ? image : ZPNG::Image.load(image)
       @extractor = Extractor.new(@image, params)
       @channels = params[:channels] ||
         if@image.alpha_used?
           %w'r g b a rgb bgr rgba abgr'
         else
           %w'r g b rgb bgr'
         end
    @verbose = params[:verbose] || -2
    @file_cmd = FileCmd.new
       @results = []
       @params[:bits]  ||= DEFAULT_BITS
    @params[:order] ||= DEFAULT_ORDER
    @params[:limit] ||= DEFAULT_LIMIT
       if@params[:min_str_len]
    @min_str_len = @min_wholetext_len = @params[:min_str_len]
       else
    @min_str_len = DEFAULT_MIN_STR_LEN
         @min_wholetext_len = @min_str_len - 2
       end
    @strings_re = /[x20-x7ernt]{#@min_str_len,}/
    @extra_checks = params.fetch(:extra_checks, DEFAULT_EXTRA_CHECKS)
     end
    

    /lib/zsteg/checker.rb check方法,截取部分,會判斷圖片是否是bmp的,只有bmp的-o選項內才有b,如果設置為all也只是多了bY的選項,但是通過之后代碼分析是可以by yb Yb的。判斷order中是否有b用的是正則,因此大小寫一樣。接著數據讀取就到check_channels方法了。

    def check
    @found_anything = false
    @file_cmd.start!
      if@image.format == :bmp
        case params[:order].to_s.downcase
        when /all/
          params[:order] = %w'bY xY xy yx XY YX Xy yX Yx'
        when /auto/
          params[:order] = %w'bY xY'
        end
      else
        case params[:order].to_s.downcase
        when /all/
          params[:order] = %w'xy yx XY YX Xy yX xY Yx'
        when /auto/
          params[:order] = 'xy'
        end
      end
      Array(params[:order]).uniq.each do |order|
        (params[:prime] == :all ? [false,true] : [params[:prime]]).each do |prime|
          Array(params[:bits]).uniq.each do |bits|
            p1 = @params.merge :bits => bits, :order => order, :prime => prime
            if order[/b/i]
              # byte iterator does not need channels
              check_channels nil, p1
            else
              channels.each{ |c| check_channels c, p1 }
            end
          end
        end
      end
      if@found_anything
        print "r" + " "*20 + "r" if@need_cr
      else
        puts "r[=] nothing :(" + " "*20 # line cleanup
      end
      if@extra_checks
        Analyzer.new(@image).analyze!
      end
      # return everything found if this method was called from some code
    @results
    ensure
    @file_cmd.stop!
    end
    

    /lib/zsteg/checker.rb check_channels方法,首先判斷是否設置了bit_order,沒設置則兩個都測試,之后就是區分兩種模式了,channels有值的,最后是去的color_extractor.rb,沒有值的去的byte_extractor.rb。

    color_extractor模式,還要判斷channels指定的模式,是就rgb還是會單獨指定每個通道讀取多少Bit的。確定過每個像素讀取多少bit,然后乘以總的像素點除以8確認讀取字節數。

    byte)extractor模式,nbits是-b參數指定的讀取bit數,乘以一行的字節數,再乘以高/8。

    show_title title輸出當前模式

    data = @extractor.extract p1讀取數據

    def check_channels channels, params
          unless params[:bit_order]
            check_channels(channels, params.merge(:bit_order => :lsb))
            check_channels(channels, params.merge(:bit_order => :msb))
            return
          end
          p1 = params.clone
          # number of bits
          # equals to params[:bits] if in range 1..8
          # otherwise equals to number of 1's, like 0b1000_0001
          nbits = p1[:bits] <= 8 ? p1[:bits] : (p1[:bits]&0xff).to_s(2).count("1")
          show_bits = true
          # channels is a String
          if channels
            p1[:channels] =
              if channels[1] && channels[1] =~ /AdZ/
                # 'r3g2b3'
                a=[]
                cbits = 0
                (channels.size/2).times do |i|
                  a << (t=channels[i*2,2])
                  cbits += t[1].to_i
                end
                show_bits = false
                @max_hidden_size = cbits * @image.width
                a
              else
                # 'rgb'
                a = channels.chars.to_a
                @max_hidden_size = a.size * @image.width * nbits
                a
              end
            # p1[:channels] is an Array
          elsif params[:order] =~ /b/i
            # byte extractor
            @max_hidden_size = @image.scanlines[0].decoded_bytes.size * nbits
          else
            raise "invalid params #{params.inspect}"
          end
          @max_hidden_size *= @image.height/8
          bits_tag =
            if show_bits
              if params[:bits] > 0x100
                if params[:bits].to_s(2) =~ /(1{1,8})$/
                  # mask => number of bits
                  "b#{$1.size}"
                else
                  # mask
                  "b#{(params[:bits]&0xff).to_s(2)}"
                end
              else
                # number of bits
                "b#{params[:bits]}"
              end
            end
          title = [
            bits_tag,
            channels,
            params[:bit_order],
            params[:order],
            params[:prime] ? 'prime' : nil
          ].compact.join(',')
          return if @wastitles.include?(title)
          @wastitles << title
          show_title title
          p1[:title] = title
          data = @extractor.extract p1
          if p1[:invert]
            data.size.times{ |i| data.setbyte(i, data.getbyte(i)^0xff) }
          end
          @need_cr = !process_result(data, p1) # carriage return needed?
          @found_anything ||= !@need_cr
        end
    

    /lib/zsteg/extractor.rb 根據-o選項中是否包含b選擇不同模式

     
    def extract params = {}
    @limit = params[:limit].to_i
    @limit = 2**32 if@limit <= 0
       if params[:order] =~ /b/i
         byte_extract params
       else
         color_extract params
       end
     end
    

    在分類說明兩個模式的時候,先將一個方法拿出來做個說明,bit_indexes

    bit_indexes

    通過代碼可以知道,在掃描一個字節的時候,zsteg是固定的從高位掃描至低位的

     
    def bit_indexes bits
       if (1..8).include?(bits)
         # number of bits
         # 1 => [0]
         # ...
         # 8 => [7,6,5,4,3,2,1,0]
         bits.times.to_a.reverse
       else
         # mask
         mask = bits & 0xff
         r = []
         8.times do |i|
           r << i if mask[i] == 1
         end
         r.reverse
       end
     end
    

    byte_extract

    /lib/zsteg/extractor/byte_extractor.rb data列表是用于存儲字節數據,a是用于存儲bit數據。

    通過byte_iterator方法遍歷每個字節,會根據order參數是否有小寫b,決定x方向的正序還是倒序,是否有小寫y決定y方向的正序還是倒序。

    根據x,y的值讀取到對應字節,然后根據bit_indexes獲取的bidx(注定只能高位至低位)去讀取對應Bit值

    當a.size為8時,就會組成一個字節,根據bit_order的值決定a中的8bit數據是大端還是小端

    msb是小端,lsb是大端。

    module ZSteg
    class Extractor
     # ByteExtractor extracts bits from each scanline bytes
     # actual for BMP+wbStego combination
     module ByteExtractor
       def byte_extract params = {}
         bidxs = bit_indexes params[:bits]
         if params[:prime]
           pregenerate_primes(
             :max   => @image.scanlines[0].size * @image.height,
             :count => (@limit*8.0/bidxs.size).ceil
           )
         end
         data = ''.force_encoding('binary')
         a = [0]*params[:shift].to_i        # prepend :shift zero bits
         byte_iterator(params) do |x,y|
           sl = @image.scanlines[y]
           value = sl.decoded_bytes.getbyte(x)
           bidxs.each do |bidx|
             a << value[bidx]
           end
           if a.size >= 8
             byte = 0
             if params[:bit_order] == :msb
               8.times{ |i| byte |= (a.shift<         else
               8.times{ |i| byte |= (a.shift<<(7-i))}
             end
             #printf "[d] %02x %08bn", byte, byte
             data << byte.chr
             if data.size >= @limit
               print "[limit #@limit]".gray if@verbose > 1
               break
             end
           end
         end
         if params[:strip_tail_zeroes] != false && data[-1,1] == "x00"
           oldsz = data.size
           data.sub!(/x00+Z/,'')
           print "[zerotail #{oldsz-data.size}]".gray if@verbose > 1
         end
         data
       end
       # 'xy': b=0,y=0; b=1,y=0; b=2,y=0; ...
       # 'yx': b=0,y=0; b=0,y=1; b=0,y=2; ...
       # ...
       # 'xY': b=0,  y=MAX; b=1,    y=MAX; b=2,    y=MAX; ...
       # 'XY': b=MAX,y=MAX; b=MAX-1,y=MAX; b=MAX-2,y=MAX; ...
       def byte_iterator params
         type = params[:order]
         if type.nil? || type == 'auto'
           type = @image.format == :bmp ? 'bY' : 'by'
         end
         raise "invalid iterator type #{type}" unless type =~ /A(by|yb)Z/i
         sl0 = @image.scanlines.first
         # XXX don't try to run it on interlaced PNGs!
         x0,x1,xstep =
           if type.index('b')
             [0, sl0.decoded_bytes.size-1, 1]
           else
             [sl0.decoded_bytes.size-1, 0, -1]
           end
         y0,y1,ystep =
           if type.index('y')
             [0, @image.height-1, 1]
           else
             [@image.height-1, 0, -1]
           end
         # cannot join these lines from ByteExtractor and ColorExtractor into
         # one method for performance reason:
         #   it will require additional yield() for EACH BYTE iterated
         if type[0,1].downcase == 'b'
           # ROW iterator
           if params[:prime]
             idx = 0
             y0.step(y1,ystep){ |y| x0.step(x1,xstep){ |x|
               yield(x,y) if@primes.include?(idx)
               idx += 1
             }}
           else
             y0.step(y1,ystep){ |y| x0.step(x1,xstep){ |x| yield(x,y) }}
           end
         else
           # COLUMN iterator
           if params[:prime]
             idx = 0
             x0.step(x1,xstep){ |x| y0.step(y1,ystep){ |y|
               yield(x,y) if@primes.include?(idx)
               idx += 1
             }}
           else
             x0.step(x1,xstep){ |x| y0.step(y1,ystep){ |y| yield(x,y) }}
           end
         end
       end
     end
    end
    end
    

    color_extractor

    /lib/zsteg/extractor/color_extractor.rb data列表是用于存儲字節數據,a是用于存儲bit數據。

    通過coord_iterator方法遍歷每個字節,會根據order參數是否有小寫x,決定x方向的正序還是倒序,是否有小寫y決定y方向的正序還是倒序。

    根據x,y的值讀取到對應字節,然后根據bit_indexes獲取的ch_masks(注定只能高位至低位)去讀取對應Bit值,只是還要根據channel的值,如果是單個字符,表示讀取的bit數是通過-b設置的,因此傳入params[:bits],否則就是2個字符,讀取第2個字符表示讀取的bit數。

    當a.size為8時,就會組成一個字節,根據bit_order的值決定a中的8bit數據是大端還是小端 msb是小端,lsb是大端。

    module ZSteg
      class Extractor
        # ColorExtractor extracts bits from each pixel's color
        module ColorExtractor
          def color_extract params = {}
            channels = Array(params[:channels])
            #pixel_align = params[:pixel_align]
            ch_masks = []
            case channels.first.size
            when 1
              # ['r', 'g', 'b']
              channels.each{ |c| ch_masks << [c[0], bit_indexes(params[:bits])] }
            when 2
              # ['r3', 'g2', 'b3']
              channels.each{ |c| ch_masks << [c[0], bit_indexes(c[1].to_i)] }
            else
              raise "invalid channels: #{channels.inspect}" if channels.size != 1
              t = channels.first
              if t =~ /A[rgba]+Z/
                return color_extract(params.merge(:channels => t.split('')))
              end
              raise "invalid channels: #{channels.inspect}"
            end
            # total number of bits = sum of all channels bits
            nbits = ch_masks.map{ |x| x[1].size }.inject(&:+)
            if params[:prime]
              pregenerate_primes(
                :max   => @image.width * @image.height,
                :count => (@limit*8.0/nbits/channels.size).ceil
              )
            end
            data = ''.force_encoding('binary')
            a = [0]*params[:shift].to_i        # prepend :shift zero bits
            catch :limit do
              coord_iterator(params) do |x,y|
                color = @image[x,y]
                ch_masks.each do |c,bidxs|
                  value = color.send(c)
                  bidxs.each do |bidx|
                    a << value[bidx]
                  end
                end
                #p [x,y,a.size,a]
                while a.size >= 8
                  byte = 0
                  #puts a.join
                  if params[:bit_order] == :msb
                    8.times{ |i| byte |= (a.shift<              else
                    8.times{ |i| byte |= (a.shift<<(7-i))}
                  end
                  #printf "[d] %02x %08bn", byte, byte
                  data << byte.chr
                  if data.size >= @limit
                    print "[limit #@limit]".gray if @verbose > 1
                    throw :limit
                  end
                  #a.clear if pixel_align
                end
              end
            end
            if params[:strip_tail_zeroes] != false && data[-1,1] == "x00"
              oldsz = data.size
              data.sub!(/x00+Z/,'')
              print "[zerotail #{oldsz-data.size}]".gray if @verbose > 1
            end
            data
          end
          # 'xy': x=0,y=0; x=1,y=0; x=2,y=0; ...
          # 'yx': x=0,y=0; x=0,y=1; x=0,y=2; ...
          # ...
          # 'xY': x=0,  y=MAX; x=1,    y=MAX; x=2,    y=MAX; ...
          # 'XY': x=MAX,y=MAX; x=MAX-1,y=MAX; x=MAX-2,y=MAX; ...
          def coord_iterator params
            type = params[:order]
            if type.nil? || type == 'auto'
              type = @image.format == :bmp ? 'xY' : 'xy'
            end
            raise "invalid iterator type #{type}" unless type =~ /A(xy|yx)Z/i
            x0,x1,xstep =
              if type.index('x')
                [0, @image.width-1, 1]
              else
                [@image.width-1, 0, -1]
              end
            y0,y1,ystep =
              if type.index('y')
                [0, @image.height-1, 1]
              else
                [@image.height-1, 0, -1]
              end
            # cannot join these lines from ByteExtractor and ColorExtractor into
            # one method for performance reason:
            #   it will require additional yield() for EACH BYTE iterated
            if type[0,1].downcase == 'x'
              # ROW iterator
              if params[:prime]
                idx = 0
                y0.step(y1,ystep){ |y| x0.step(x1,xstep){ |x|
                  yield(x,y) if @primes.include?(idx)
                  idx += 1
                }}
              else
                y0.step(y1,ystep){ |y| x0.step(x1,xstep){ |x| yield(x,y) }}
              end
            else
              # COLUMN iterator
              if params[:prime]
                idx = 0
                x0.step(x1,xstep){ |x| y0.step(y1,ystep){ |y|
                  yield(x,y) if @primes.include?(idx)
                  idx += 1
                }}
              else
                x0.step(x1,xstep){ |x| y0.step(y1,ystep){ |y| yield(x,y) }}
              end
            end
          end
        end
      end
    end
    

    結果

    起因里的zsteg的參數現在都解釋過了,而用stegsolve沒有看到flag是因為8bit數據是按照大端模式組成的字節,而flag是需要以小端模式組成,所以當我選擇stegsolve來做題時,注定是拿不到flag了,都是時辰的錯。

    然后bY其實和xY的結果是一樣的,只是要確定通道的排列方式,bmp按順序存的通道順序是bgr。

    root@LAPTOP-GE0FGULA:/mnt/d# zsteg -c bgr -o xY --msb -b1 瞅啥.bmp
    [?] 2 bytes of extra data after image end (IEND), offset = 0x269b0e
    b1,bgr,msb,xY       .. text: "qwxf{you_say_chick_beautiful?}"
    

    再來看下stegsolve,首先知道-o是Y,因此圖片需要倒一下,所以手動修改bmp的高度為原值的負值,圖片就倒過來了。

    選中的序列和flag的值,生成二進制序列對比一下,應是每8個bit都是倒序的。

    #encoding:utf-8
    from binascii import b2a_hex,a2b_hex
    flag = "qwxf{you_say_chick_beautiful?}"
    stegsolve = "8eee1e66de9ef6aeface869efac61696c6d6fa46a686ae2e9666ae36fcbe"
    flag = bin(int(b2a_hex(flag),16))[2:]
    stegsolve = bin(int(stegsolve,16))[2:]
    def show(a,b):
        if len(a) % 2 != 0:
            a = '0'+a
        if len(b) % 2 != 0:
            b = '0'+b
        for i in xrange(0,len(a),8):
            print a[i:i+8]+"  "+b[i:i+8]
    show(flag,stegsolve)
    

    自己看下結果吧。

    bityx
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    很久很久以前,有一道送分題沒做出來,后來看writeup,只要zsteg就行了。
    在二進制里面,每一位只要大于等于?則都要向高位進一。為了方便表示,還衍生出了二進制的子類,比如八進制,十六進制等,主要是二進制向這些進制轉換較為容易,而計算機平時又都處理二進制數據,因此就出現了這些常見的進制計數。信息存儲大多數計算機使用的都是8位的塊,或者叫字節,字節是作為計算機可尋址的最小單位。一般來說我們并不習慣于將一個字節寫成八位二進制的數,而是會寫成兩位十六進制的數。
    前言本文主要著眼于glibc下的一些漏洞及利用技巧和IO調用鏈,由淺入深,分為 “基礎堆利用漏洞及基本IO攻擊” 與 “高版本glibc下的利用” 兩部分來進行講解,前者主要包括了一些glibc相關的基礎知識,以及低版本glibc下常見的漏洞利用方式,后者主要涉及到一些較新的glibc下的IO調用鏈。
    Kerberos Bronze Bit 攻擊的概念驗證利用代碼于本周在網上發布。以及繞過Kerberos身份驗證的Silver Ticket攻擊。造成攻擊的根本原因是未簽名包含可轉發標志的Kerberos服務票證組件,并且Kerberos進程無法檢測服務票證操縱。通過確保在S4U2self交換中收到的任何服務票證都是不可轉發的,可以強制實施此保護,除非請求的服務是TrustedToAuthForDelegation。同樣,通過確保代表受保護帳戶在S4U2self交換中收到的任何服務票證均不可轉發,可以強制執行此操作。
    “星火·鏈網”英雄榜招募由中關村區塊鏈產業聯盟、中國信通院“智能+學院”、世紀互聯共同發起,誠邀全球廣大開發者加入“星火·鏈網”的開發。
    據悉,BTC.com是全球最大的比特幣轉移平臺之一,在全球擁有數百萬用戶。BTC.com在新聞稿中透露,屬于其客戶的價值約70萬美元的加密貨幣和屬于該公司的價值約230萬美元的加密貨幣在這次網絡攻擊中被盜。BTC.com已向中國深圳的執法部門報告了這一事件。BTC.com表示將投入大量精力來追回被盜的數字資產。BTC.com另外補充道,該公司目前照常經營業務,除數字資產服務外,客戶資金服務不受影響。
    本文主要介紹MAC地址相關的7種配置示例。
    java版本: java version "1.8.0_131" Java(TM) SE Runtime Environment (build 1.8.0_131-b11) Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
    UrlhunterUrlhunter是一款網絡偵察和信息收集工具,該工具基于Go語言開發。在該工具的幫助下,廣大研究人員可以輕松搜索通過短鏈接服務暴露的URL以及相關資源,比如說bit.ly和goo.gl等等。
    10月24日,中國信通院牽頭發布了“星火·鏈網”1024開發宣言,面向全球搭建產教融合國家交流平臺。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类