Thread: Could use some help with BMP Steganography

  1. #1
    C lover
    Join Date
    Oct 2007
    Location
    Virginia
    Posts
    266

    Could use some help with BMP Steganography

    What I am intending to do is encode data into the 4 least significant bits of each blue sample of every pixel regardless of bits per pixel in a BMP file. Lets assume 24 bits per pixel however. I am posting the relevant loop which seems to be troublesome. The file gets duplicated however it's an exact copy of the original (confirmed with hex editor). Please advise. I can post more of the code if need be:

    Code:
        do
        {
    
            bmp_temp_ptr = bmp_rd_ptr;
    
            if(!(bmp_rd_bytes = fread(bmp_rd_ptr, 1, bmp_row_size, bmp_rd_file)))
                break;
    
            for(bmp_row_index = 0; bmp_row_index < bmp_rd_bytes; bmp_row_index++)
            {
    
                switch(color)
                {
                case BLUE:
    
                    if(!done)
                    {
    
                        bmp_secret_bits = *bmp_secret++;
    
                        *bmp_temp_ptr = (*bmp_temp_ptr & ~(0x0f)) | (bmp_secret_bits & ~0x0f);
    
                        if(bmp_info_header->bpp == 24)
                        {
    
                            bmp_temp_ptr += 3;
                            bmp_row_index += 3;
    
                            *bmp_temp_ptr &= ~(0x0f) | ((bmp_secret_bits << 4) & ~0x0f);
    
                        }else
                        {
    
    
    
                        }
    
                        if(!bmp_secret_size--)
                            done = 1;
    
                    }else
                        continue;
    
                    break;
                case GREEN:
    
                    break;
                case RED:
    
                    break;
                case ALPHA:
    
                    break;
                }
    
                bmp_info_header->bpp == 32 ? ++color == 4 ? color = 0 : color : ++color == 3 ? color = 0 : color;
                bmp_temp_ptr++;
    
            }
    
            fwrite(bmp_rd_ptr, 1,bmp_rd_bytes, bmp_wr_file);
    
            bmp_rd_total += bmp_rd_bytes;
    
        }while(bmp_rd_total <= bmp_pixel_array_size);

  2. #2
    C lover
    Join Date
    Oct 2007
    Location
    Virginia
    Posts
    266
    I guess this is no longer needed... I thought of another way of doing it thats still undetectable to the human eye and still only modifies the blue samples of each 3rd pixel:

    Code:
    do
        {
    
            bmp_temp_ptr = bmp_rd_ptr;
    
            if(!(bmp_rd_bytes = fread(bmp_rd_ptr, 1, bmp_row_size, bmp_rd_file)))
                break;
    
            for(bmp_row_index = 0; bmp_row_index < bmp_rd_bytes; bmp_row_index++)
            {
    
                switch(color)
                {
                case BLUE:
    
                    if(!(pass++ % 3))
                    {
    
                        if(--bmp_secret_size > 0)
                        {
    
                            *bmp_temp_ptr = bmp_secret[bmp_secret_index++];
    
                        }
    
                    }
    
    
                    break;
                case GREEN:
    
                    break;
                case RED:
    
                    break;
                case ALPHA:
    
                    break;
                }
    
                bmp_info_header->bpp == 32 ? ++color == 4 ? color = 0 : color : ++color == 3 ? color = 0 : color;
                bmp_temp_ptr++;
    
            }
    
            fwrite(bmp_rd_ptr, 1,bmp_rd_bytes, bmp_wr_file);
    
            bmp_rd_total += bmp_rd_bytes;
    
        }while(bmp_rd_total <= bmp_pixel_array_size);

  3. #3
    - - - - - - - - oogabooga's Avatar
    Join Date
    Jan 2008
    Posts
    2,808
    Your code demonstrates various kinds of bad style.
    The worst is this:
    Code:
            bmp_info_header->bpp == 32 ? ++color == 4 ? color = 0 : color : ++color == 3 ? color = 0 : color;
    Ouch! How about:
    Code:
    if (++color == bmp_info_header->bpp / 8)
        color = 0;
    Also, it's not good style to use !a when you mean a == 0. Even though they act equivalently, they "say" different things.

    As for logic errors, in your original code this looks wrong:
    Code:
                    *bmp_temp_ptr = (*bmp_temp_ptr & ~(0x0f)) | (bmp_secret_bits & ~0x0f);
    Shouldn't the last part be:
    Code:
        (bmp_secret_bits & 0x0f)
    And in your second post, are you completely replacing the blue component with a new value? If so, I don't think you can say that it's definitely undetectable to the human eye.
    The cost of software maintenance increases with the square of the programmer's creativity. - Robert D. Bliss

  4. #4
    C lover
    Join Date
    Oct 2007
    Location
    Virginia
    Posts
    266
    In the second post, yes, I opted to replace the whole blue sample in pixels. It's not consecutive pixels it would be every 3rd (or whatever I choose). So far it's undetectable to me and thats with a large message. I appreciate the pointers... ha, I was being lazy and that chunk of code determines if the ALPHA case gets checked in the event of a 32bit image. For 24 bit images it's not used. Would you like to see the final code and run it for yourself? I think it's pretty cool. A hex editor quickly reveals the hidden message though. I suppose I'll go back and see if I can figure out my preferred encoding.

    I figured I'd be the only one to ever read the code so thats why you see some of the "bad style".
    Last edited by Syscal; 10-10-2013 at 09:25 PM.

  5. #5
    - - - - - - - - oogabooga's Avatar
    Join Date
    Jan 2008
    Posts
    2,808
    Oh, I missed that it was every third pixel. That's not so bad.
    I wouldn't mind giving it a run if you want to post the code here or PM it to me.
    The cost of software maintenance increases with the square of the programmer's creativity. - Robert D. Bliss

  6. #6
    C lover
    Join Date
    Oct 2007
    Location
    Virginia
    Posts
    266
    This was all for fun, and I enjoyed researching bitmaps. It's not complete by any means but a 24 bit bmp file is advised for now. You'll need to name the file accordingly and modify the macro BMP_FILE in the header below. As always, if there are other things you think I should do better please point them out. I'd like to know what else I can improve on.

    main.c

    Code:
    #include "bmp.h"
    
    int main()
    {
        FILE * bmp_rd_file, * bmp_wr_file;
        unsigned char * bmp_headers;
    
    
        if(!(bmp_rd_file = fopen(BMP_FILE, "r+b")) || !(bmp_wr_file = fopen("out.bmp", "w+b")))
           bmp_err("main: fopen()");
    
    
        bmp_headers = get_headers(bmp_rd_file);
    
        if(((bmp_file_h *) bmp_headers)->sig != 0x4d42)
            bmp_err("main: This is not a valid bitmap image!");
    
    
        if(!(fwrite(bmp_headers, 1, BMP_FILE_HDR_SIZ + BMP_INFO_HDR_SIZ, bmp_wr_file)))
            bmp_err("main: fwrite()");
    
    
        fseek(bmp_rd_file, ((bmp_file_h *)bmp_headers)->offset, SEEK_SET);
    
    
        switch(((bmp_info_h *) (bmp_headers + BMP_FILE_HDR_SIZ))->bpp)
        {
    
        case 24:
        case 32:
            bmp_24_32(bmp_rd_file, bmp_wr_file, bmp_headers);
    
            break;
    
        default:
    
            bmp_err("BPP in bitmap image not supported!");
    
            break;
    
        }
    
        free(bmp_headers);
        fclose(bmp_rd_file);
        fclose(bmp_wr_file);
    
        return 0;
    }
    bmp_24.c

    Code:
    #include "bmp.h"
    
    void bmp_24_32(FILE * bmp_rd_file, FILE * bmp_wr_file, unsigned char * bmp_headers)
    {
    
        size_t bmp_rd_total = 0,
            bmp_pixel_array_size,
            bmp_rd_bytes,
            bmp_row_index,
            bmp_row_size,
            pass = 0;
    
        unsigned char * bmp_temp_ptr,
            * bmp_rd_ptr;
    
        color_e color = 0;
    
        bmp_info_h * bmp_info_header = (bmp_info_h *)(bmp_headers + BMP_FILE_HDR_SIZ);
    
    
        bmp_row_size = (((bmp_info_header->bpp * bmp_info_header->bmp_width) + 31) / 32) * 4;
        bmp_pixel_array_size = bmp_row_size * bmp_info_header->bmp_height;
    
        bmp_rd_ptr = malloc(sizeof(unsigned char) * bmp_row_size);
    
        char bmp_secret_bits;
        char * bmp_secret = BMP_SECRET;
        int done = 0, bmp_secret_size, bmp_secret_index = 0;
    
        bmp_secret_size = strlen(BMP_SECRET);
    
    
    
        do
        {
    
            bmp_temp_ptr = bmp_rd_ptr;
    
            if(!(bmp_rd_bytes = fread(bmp_rd_ptr, 1, bmp_row_size, bmp_rd_file)))
                break;
    
            for(bmp_row_index = 0; bmp_row_index < bmp_rd_bytes; bmp_row_index++)
            {
    
                switch(color)
                {
                case BLUE:
    
                    if(!(pass++ % 10))
                    {
    
                        if(--bmp_secret_size > 0)
                        {
    
                            *bmp_temp_ptr = bmp_secret[bmp_secret_index++];
    
                        }
    
                    }
    
    
                    break;
                case GREEN:
    
                    break;
                case RED:
    
                    break;
                case ALPHA:
    
                    break;
                }
    
                bmp_info_header->bpp == 32 ? ++color == 4 ? color = 0 : color : ++color == 3 ? color = 0 : color;
                bmp_temp_ptr++;
    
            }
    
            fwrite(bmp_rd_ptr, 1,bmp_rd_bytes, bmp_wr_file);
    
            bmp_rd_total += bmp_rd_bytes;
    
        }while(bmp_rd_total <= bmp_pixel_array_size);
    
        if(errno)
            bmp_err(strerror(errno));
    
        while((bmp_rd_bytes = fread(bmp_rd_ptr, 1, BMP_BUF_SIZ, bmp_rd_file)))
            fwrite(bmp_rd_ptr, 1, bmp_rd_bytes, bmp_wr_file);
    
    }
    geat_headers.c

    Code:
    #include "bmp.h"
    
    unsigned char * get_headers(FILE * bmp_rd_file)
    {
    
        unsigned char * bmp_headers;
        int bytes;
        bmp_file_h temp_file;
        bmp_info_h temp_info;
    
        if(!(bmp_headers = malloc(sizeof(unsigned char) * BMP_FILE_HDR_SIZ + BMP_INFO_HDR_SIZ)))
            bmp_err("get_headers: malloc()\n");
    
        if(!(bytes = fread(&temp_file, 1, BMP_FILE_HDR_SIZ, bmp_rd_file)))
            bmp_err("get_headers: fread()\n");
    
        if(!(bytes = fread(&temp_info, 1, BMP_INFO_HDR_SIZ, bmp_rd_file)))
            bmp_err("get_headers: fread()\n");
    
        memcpy(bmp_headers,&temp_file,BMP_FILE_HDR_SIZ);
        memcpy(bmp_headers + BMP_FILE_HDR_SIZ,&temp_info,BMP_INFO_HDR_SIZ);
    
    
        return bmp_headers;
    }
    bmp_err.c

    Code:
    #include "bmp.h"
    
    void bmp_err(char * bmp_err_buff)
    {
    
        fprintf(stderr, "%s\n", bmp_err_buff);
        exit(EXIT_FAILURE);
    
    }

    bmp.h

    Code:
    #ifndef _BMP_H
    #define _BMP_H
    
    #include <stdio.h>
    #include <string.h>
    #include <stdint.h>
    #include <errno.h>
    #include <stdlib.h>
    
    #define BMP_FILE "bmpfun.bmp"
    
    #define BMP_SECRET \
    "O friends, no more these sounds!" \
    "Let us sing more cheerful songs," \
    "more full of joy!" \
    "Joy, bright spark of divinity," \
    "Daughter of Elysium," \
    "Fire-inspired we tread" \
    "Thy sanctuary." \
    "Thy magic power re-unites" \
    "All that custom has divided," \
    "All men become brothers" \
    "Under the sway of thy gentle wings." \
    "Whoever has created " \
    "An abiding friendship," \
    "Or has won" \
    "A true and loving wife," \
    "All who can call at least one soul theirs," \
    "Join in our song of praise;" \
    "But any who cannot must creep tearfully" \
    "Away from our circle." \
    "All creatures drink of joy" \
    "At nature's breast." \
    "Just and unjust" \
    "Alike taste of her gift;" \
    "She gave us kisses and the fruit of the vine," \
    "A tried friend to the end." \
    "Even the worm can feel contentment," \
    "And the cherub stands before God!" \
    "Gladly, like the heavenly bodies" \
    "Which He set on their courses" \
    "Through the splendor of the firmament;" \
    "Thus, brothers, you should run your race," \
    "As a hero going to conquest." \
    "You millions, I embrace you." \
    "This kiss is for all the world!" \
    "Brothers, above the starry canopy" \
    "There must dwell a loving Father." \
    "Do you fall in worship, you millions?" \
    "World, do you know your creator?" \
    "Seek him in the heavens;" \
    "Above the stars must He dwell." \
    
    #define BMP_BUF_SIZ 1024
    #define BMP_FILE_HDR_SIZ 14
    #define BMP_INFO_HDR_SIZ 40
    #define BMP_ERR_BUF_SIZ 64
    
    #pragma pack(1)
    
    typedef enum
    {
        BLUE,
        GREEN,
        RED,
        ALPHA
    } color_e;
    
    typedef struct
    {
    
        uint16_t sig;
        uint32_t filesize;
        uint16_t reserved1;
        uint16_t reserved2;
        uint32_t offset;
    
    }bmp_file_h;
    
    typedef struct
    {
    
        uint32_t hdr_size;
        uint32_t bmp_width;
        uint32_t bmp_height;
        uint16_t planes;
        uint16_t bpp;
        uint32_t compression;
        uint32_t image_size;
        uint32_t xppm;
        uint32_t yppm;
        uint32_t no_colors;
        uint32_t no_imp_colors;
    
    }bmp_info_h;
    
    void bmp_err(char *);
    
    unsigned char *get_headers(FILE *);
    void bmp_24_32(FILE *, FILE *, unsigned char *);
    
    #endif // _BMP_H
    Last edited by Syscal; 10-10-2013 at 09:48 PM.

  7. #7
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Quote Originally Posted by oogabooga View Post
    I don't think you can say that it's definitely undetectable to the human eye.
    Agree. Rather than modify every third blue channel, just use the lowest bit of every channel in each and every sample to store the data. The slight variations in color would most likely be unnoticeable even to the most discerning eye...
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  8. #8
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Quote Originally Posted by Syscal View Post
    This was all for fun, and I enjoyed researching bitmaps. It's not complete by any means but a 24 bit bmp file is advised for now. You'll need to name the file accordingly and modify the macro BMP_FILE in the header below. As always, if there are other things you think I should do better please point them out. I'd like to know what else I can improve on.
    A few suggestions:
    1) Avoid returning malloc'ed memory from functions. If at all possible, have the user supply a buffer. At the very least, name the function and comment the declaration accordingly to remind the user to clean up the memory later (your code never does free up the memory, incidentally).
    2) The input file, output file, and data are all compile-time contants (defined in a header file no less!). Not only does this make testing difficult, it basically renders the program useless in practice. Instead of hard-coding parameters, just pass them along on the command line.
    3) Try to think (and design) in terms of providing a useful API rather than just plodding along to achieve some arcane task. Key to this are extensibility, modularity, maintainability, readability, and stability, among others.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  9. #9
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Who would have ever thought my area of professional experience would come up on CBoard...

    What you're doing is technically watermarking, not steganography. To be stego, a technique should be undetectable by adversaries. Embedding information into the LSBs of pixel values is easily detectable by statistics, even if it isn't visible to the human eye, so it's not really a stego technique. But it's the textbook technique, so congrats on the implementation.

    Now... What if I rotated, scaled or blurred the image, how could you recover the information?
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  10. #10
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Quote Originally Posted by brewbuck View Post
    Who would have ever thought my area of professional experience would come up on CBoard...

    What you're doing is technically watermarking, not steganography. To be stego, a technique should be undetectable by adversaries. Embedding information into the LSBs of pixel values is easily detectable by statistics, even if it isn't visible to the human eye, so it's not really a stego technique. But it's the textbook technique, so congrats on the implementation.

    Now... What if I rotated, scaled or blurred the image, how could you recover the information?
    If the bitmap were a natural image, imbedding the (preferably encrypted!) data in the LSB *might* pass statistical tests. For simple graphics images, such as a red square on a white background, no way. In the latter case, I wonder what techniques, if any, could be used to encode the data in such a way that would be resistant to analysis?
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  11. #11
    C lover
    Join Date
    Oct 2007
    Location
    Virginia
    Posts
    266
    Quote Originally Posted by Sebastiani View Post
    A few suggestions:
    1) Avoid returning malloc'ed memory from functions. If at all possible, have the user supply a buffer. At the very least, name the function and comment the declaration accordingly to remind the user to clean up the memory later (your code never does free up the memory, incidentally).
    2) The input file, output file, and data are all compile-time contants (defined in a header file no less!). Not only does this make testing difficult, it basically renders the program useless in practice. Instead of hard-coding parameters, just pass them along on the command line.
    3) Try to think (and design) in terms of providing a useful API rather than just plodding along to achieve some arcane task. Key to this are extensibility, modularity, maintainability, readability, and stability, among others.
    I beg to differ on #1. It gets freed at the end of main. I appreciate the feedback!

  12. #12
    Registered User
    Join Date
    Sep 2006
    Posts
    8,868
    If the malloc'd memory comes from the heap, what's wrong with returning a pointer to it?

  13. #13
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Quote Originally Posted by Syscal View Post
    I beg to differ on #1. It gets freed at the end of main. I appreciate the feedback!
    Ah, right. Sorry!
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  14. #14
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by Sebastiani View Post
    If the bitmap were a natural image, imbedding the (preferably encrypted!) data in the LSB *might* pass statistical tests. For simple graphics images, such as a red square on a white background, no way. In the latter case, I wonder what techniques, if any, could be used to encode the data in such a way that would be resistant to analysis?
    It is usually easier to detect bit manipulation in a "natural" image because the energy content has a smooth distribution which is relatively 1/f in nature. The presence of uncorrelated noise in the lower bits will appear on an image spectrogram as a higher energy at frequencies near Nyquist that might be otherwise expected in a smooth image. Further, the distribution of natural quantization noise in the LSBs does not match the distribution produced by embedding random bits.

    Basically, because the energy content of most images is low at higher frequencies, one can estimate the expected quantization noise and then compare that noise characteristic against that of random bits.

    Embedding data in the LSBs of a completely random image is much less detectable, but obviously, such images look "weird" to the human eye. People don't normally send images of noise to each other.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  15. #15
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Quote Originally Posted by brewbuck View Post
    It is usually easier to detect bit manipulation in a "natural" image because the energy content has a smooth distribution which is relatively 1/f in nature. The presence of uncorrelated noise in the lower bits will appear on an image spectrogram as a higher energy at frequencies near Nyquist that might be otherwise expected in a smooth image. Further, the distribution of natural quantization noise in the LSBs does not match the distribution produced by embedding random bits.

    Basically, because the energy content of most images is low at higher frequencies, one can estimate the expected quantization noise and then compare that noise characteristic against that of random bits.

    Embedding data in the LSBs of a completely random image is much less detectable, but obviously, such images look "weird" to the human eye. People don't normally send images of noise to each other.
    Your first point certainly rings true, but I still think that a good number of "natural" images would nonetheless defy analysis. Camera sensors are inherently analog devices and slight fluctuations in readings between adjacent pixels are plausible due to the chaotic nature of the scattering of light and what have you. That said, the more "controlled" the setting (ie: a studio vs. outdoors), the easier to detect "suspicious" noise.

    As to your last point, I think you might be surprised to to find that even the most simple images (a red and white rectangle generated from a paint program, for instance) look completely normal and even indistinguishable from an unmodified copy. To demonstrate, I've put together a simple program that sets the LSB of every color channel to some random value. Here are the results, two pairs of images laid side-by-side:

    Could use some help with BMP Steganography-bicolor_e-bmpCould use some help with BMP Steganography-bicolor_x-bmp Could use some help with BMP Steganography-art_g-bmpCould use some help with BMP Steganography-art_q-bmp

    I can't tell the difference, personally, and I have excellent vision. I honestly doubt a magnifying glass would help much either!

    Oh, and here's the relevant snippet of code that does the work:

    Code:
        for( size_t index = 0; index < count; ++index )
            pixels[ index ] = ( pixels[ index ] & 0xfe ) | ( rand( ) % 2 );
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Did some steganography, any way to improve this?
    By Syscal in forum C Programming
    Replies: 0
    Last Post: 05-11-2012, 08:00 AM
  2. Steganography in C
    By Syscal in forum C Programming
    Replies: 1
    Last Post: 09-07-2010, 07:15 AM
  3. Replies: 5
    Last Post: 07-31-2006, 12:13 PM
  4. Steganography
    By aksen in forum C Programming
    Replies: 7
    Last Post: 11-13-2001, 08:42 AM