Saturday, August 24, 2013

Data cloaking inside of BMP pictures

Tonight, I decided to create a small program that:
  • encodes any file into a picture
  • decodes this file from the same picture
A schema being better than any long sentence...


How does it work?

So easy... you know a bitmap file is nothing more than a succession of pixels, and a pixel is made of 3 bytes Red Green Blue, right? (let's ignore the file header, the reverse order of rows, etc etc... that's not important here)
The big picture is to hide one byte of the message per pixel. Red is coded on 8 bits, right? Let's use the 2 least significant bits (LSB) to encode 2 bits of this message byte. Green is coded on 8 bits too, so let's use the 3 LSB to encode 3 bits of the message byte. Same thing for Blue, let's use the 3 LSB to encode 3 bits of the message byte. You got it: 2 bits + 3 bits + 3 bits = 1 byte. This is how we will hide each byte from the message in one pixel.
The good news is that we always modified the LSB of the pixel components... when you will look at the modified picture (containing the message), you will not be able to detect the color changes from the original one.

Now, have a closer look at the two interesting parts in this program: encoding of data, decoding of data.

Encoding

The following method inserts a message (a memory Stream, it can be anything) into the original picture. Of course, we have to ensure the picture file is big enough to contain the message.
Note the the first 4 pixels will be used to encode the size of the message. We will need it to decode the message.
The source code is a bit messy, because it contains a homemade BMP writer. But the data cloaking itself is quite straightforward, provided you are familiar with bitmasks.

private void ProcessBmp(string InputFilename, System.IO.MemoryStream Message, string OutputFilename)
{
    using (System.IO.FileStream fs = new System.IO.FileStream(InputFilename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read, 1024 * 1024))
    {
        using (System.Drawing.Bitmap b = new System.Drawing.Bitmap(fs))
        {
            if (b.Width * b.Height < Message.Length + 4)
                throw new Exception(string.Format("BMP file is too small, it must be at least {0} bytes to encode this message", (Message.Length + 4) * 3));
            using (System.IO.BinaryWriter bw = new System.IO.BinaryWriter(new System.IO.FileStream(OutputFilename, System.IO.FileMode.Create, System.IO.FileAccess.Write)))
            {
                int ComplementToAdd = (4 - ((b.Width * 3) % 4)) % 4;
                byte[] BlackPixel = new byte[3] { 0, 0, 0 };
                bw.Write('B');
                bw.Write('M');

                int x, y;

                bw.Write((uint)(54 + b.Width * b.Height * 3 + b.Height * ComplementToAdd));
                bw.Write((uint)0);
                bw.Write((uint)54);
                bw.Write((uint)40);
                bw.Write((uint)b.Width);
                bw.Write((uint)b.Height);
                bw.Write((uint)24 * 0x10000 + 1);   // planes & bitcount
                bw.Write((uint)0);   // compression
                bw.Write((uint)b.Width * b.Height * 3 + ComplementToAdd * b.Height);    // size of image
                bw.Write((uint)0);   // XPPM
                bw.Write((uint)0);   // YPPM
                bw.Write((uint)0);   // color used

                int Idx = 0;
                for (y = b.Height - 1; y >= 0; y--)
                {
                    for (x = 0; x < b.Width; x++)
                    {
                        Color c = b.GetPixel(x, y);
                        byte[] RGB = new byte[3] { c.B , c.G, c.R };

                        byte ByteToEncode;
                        switch (Idx)
                        { // The 4 first bytes wil be used to encode the size of the message
                            case 0: ByteToEncode = (byte)(Message.Length & 0x000000FF); break;
                            case 1: ByteToEncode = (byte)((Message.Length & 0x0000FF00) >> 8); break;
                            case 2: ByteToEncode = (byte)((Message.Length & 0x00FF0000) >> 16); break;
                            case 3: ByteToEncode = (byte)((Message.Length & 0xFF000000) >> 32); break;
                            default: ByteToEncode = Idx - 4 < Message.Length
                                 ? (byte)Message.ReadByte()
                                 : (byte)0; break;
                        }

                        byte EncodeB = (byte)((ByteToEncode & (32 + 64 + 128)) >> 5);
                                       // Find the biggest 3 bits in the byte to encode
                        byte EncodeG = (byte)((ByteToEncode & (4 + 8 + 16)) >> 2);
                                       // Find the middle 3 bits in the byte to encode
                        byte EncodeR = (byte)(ByteToEncode & (1 + 2));
                                       // Find the smallest 2 bits in the byte to encode
                        RGB[0] = (byte)((RGB[0] & 0xF8) + EncodeB);
                                 // In Blue, 3 bits are used to encode (0xF8 = 1111 1000)
                        RGB[1] = (byte)((RGB[1] & 0xF8) + EncodeG);
                                 // In Green, 3 bits are used to encode (0xF8 = 1111 1000)
                        RGB[2] = (byte)((RGB[2] & 0xFC) + EncodeR);
                                 // In Red, 2 bits are used to encode (0xFC = 1111 1100)

                        bw.Write(RGB, 0, 3);
                        Idx++;
                    }
                    // Rows in bmp must be a multiple of 4, so let's fill with black pixels if necessary
                    if (ComplementToAdd != 0)
                        bw.Write(BlackPixel, 0, ComplementToAdd);
                }
            }
        }
    }
}

Decoding

This one is much easier than the encoding source code. So straightforward that is does not need any comment...

private System.IO.MemoryStream UnprocessBmp(string InputFilename)
{
    System.IO.MemoryStream Result = new System.IO.MemoryStream();
    using (System.IO.FileStream fs = new System.IO.FileStream(InputFilename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read, 1024 * 1024))
    {
        using (System.Drawing.Bitmap b = new System.Drawing.Bitmap(fs))
        {
            int x, y, MessageSize = 0, Idx = 0;
            for (y = b.Height - 1; y >= 0; y--)
            {
                for (x = 0; x < b.Width; x++)
                {
                    Color c = b.GetPixel(x, y);
                    byte EncodedB = (byte)(c.B & 0x07);
                                    // Find the last 3 bits of the blue component
                    byte EncodedG = (byte)(c.G & 0x07);
                                    // Find the last 3 bits of the green component
                    byte EncodedR = (byte)(c.R & 0x03);
                                    // Find the last 2 bits of the red component
                    byte EncodedByte = (byte)((EncodedB << 5) + (EncodedG << 2) + EncodedR);
                                    // combine the 3 + 3 + 2 bits to make a byte

                    switch (Idx)
                    { // The 4 first bytes wil be used to encode the size of the message
                        case 0: MessageSize = EncodedByte; break;
                        case 1: MessageSize = MessageSize + (EncodedByte << 8); break;
                        case 2: MessageSize = MessageSize + (EncodedByte << 16); break;
                        case 3: MessageSize = MessageSize + (EncodedByte << 32); break;
                        default: if (Idx - 4 < MessageSize) Result.WriteByte(EncodedByte); break;
                    }
                    Idx++;
                }
            }
        }
    }
    Result.Seek(0, System.IO.SeekOrigin.Begin);
    return Result;
}

You can download the solution here. Please, pay special attention to my high-level skills in GUI design... \m/

Thursday, August 22, 2013

Get ASCII codes from a VARCHAR SQL string

Today I faced a strange issue in SQL Server. I tried to select a specific row in SCCM table about OS Windows 7 Entreprise. So I typed the name of the OS in management studio, and ran a select on the proper table using a where clause on the OS name. Unexpectedly, no rows returned.
So I copied the name of the OS (from the SCCM table), and tested it against my own OS name. It can be summarized as below:

SELECT
  CASE WHEN 'Microsoft Windows 7 Entreprise' -- coming from SCCM
          = 'Microsoft Windows 7 Entreprise' -- what I typed
    THEN 'Equal'
    ELSE 'Not equal'
  END AS IsEqual
 

Wow... everyone who ever fought against special characters already knows the solution: a non-breakable space (or whatever) is present in one of the strings.

So I wrote a small query to inspect the ASCII characters of a string, and wished to share it. SO here it is:
 
DECLARE @Str VARCHAR(255) = 'Microsoft Windows 7 Entreprise' -- Coming from SCCM
;

WITH MyCounter AS
(
 SELECT 1 AS Idx
 UNION ALL
 SELECT a.Idx + 1 AS Idx
 FROM MyCounter a
 WHERE a.Idx < LEN(@Str)
)
SELECT Idx AS [Index], SUBSTRING(@Str, Idx, 1) AS [Character], ASCII(SUBSTRING(@Str, Idx, 1)) AS [AsciiCode]
FROM MyCounter
ORDER BY Idx asc
 

As expected, a non breakable space (ascii code 160) was present in SCCM table content, instead of standard whitespace (ascii code 32).

This small code can easily be turned into scalar valued function, if needed...
Hope it helps!