Thread: Could use some help with BMP Steganography

  1. #16
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by Sebastiani View Post
    I can't tell the difference, personally, and I have excellent vision. I honestly doubt a magnifying glass would help much either!
    I can't see any difference either. But my point was that what is visible to a human and to a computer are two different things.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  2. #17
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Quote Originally Posted by brewbuck View Post
    I can't see any difference either. But my point was that what is visible to a human and to a computer are two different things.
    True, but even in the latter case it may not be so easy. A two-dimensional FFT or wavelet transform might be able to isolate the noise in the most simple of images, but otherwise it's an open question (in my opinion at least) whether or not a technique could be found to reliably detect such modifications. In fact, I'd be quite interested to see if anyone here can decisively determine which of the two "art_*.bmp" is real and which is the stego. Feel free to ask for formatted data (generated textual output or what have you) for analytical purposes...
    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;
    }

  3. #18
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Just for fun, I've put together a little utility implementing the LSB-encoding scheme.

    Attachment 13052

    And here's a sample image that stores yet another image (or two). I had a more interesting example, but it exceeded the forum's limit on attachment size...

    Could use some help with BMP Steganography-123-bmp
    Last edited by Sebastiani; 10-13-2013 at 10:35 AM.
    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;
    }

  4. #19
    - - - - - - - - oogabooga's Avatar
    Join Date
    Jan 2008
    Posts
    2,808
    Quote Originally Posted by Sebastiani View Post
    I'd be quite interested to see if anyone here can decisively determine which of the two "art_*.bmp" is real and which is the stego.
    The second one, "Q", smells a little steggy to me.
    The cost of software maintenance increases with the square of the programmer's creativity. - Robert D. Bliss

  5. #20
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Quote Originally Posted by oogabooga View Post
    The second one, "Q", smells a little steggy to me.
    Well, let's see...

    Attachment 13053

    Curious, did you perform any analysis on that data or was that just a hunch?
    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;
    }

  6. #21
    - - - - - - - - oogabooga's Avatar
    Join Date
    Jan 2008
    Posts
    2,808
    A 50/50 chance and I was wrong!
    I just counted the number of 1's and figured that the random bits would be closer to a ratio of 50/50.
    G is 0.496 while Q is 0.501.
    So I guessed Q.
    The cost of software maintenance increases with the square of the programmer's creativity. - Robert D. Bliss

  7. #22
    - - - - - - - - oogabooga's Avatar
    Join Date
    Jan 2008
    Posts
    2,808
    A little more naive analysis, testing pairs of consecutive bits in the data stream. (The data stream is the low-order bits of every color byte of the bmp pixel data, for those who wonder.)

    File: art_G.bmp Size: 46182 Width: 124 Height: 124
    cnt1 : 0.496
    cnt00: 0.251
    cnt01: 0.252
    cnt10: 0.253
    cnt11: 0.244

    File: art_Q.bmp Size: 46182 Width: 124 Height: 124
    cnt1 : 0.501
    cnt00: 0.252
    cnt01: 0.247
    cnt10: 0.247
    cnt11: 0.254

    So G has perhaps fewer 11 combos than one might expect for random data, but not by much compared to the actual random data in Q.

    The quick and dirty program that produced the above. It will not work for bmp's in general (even 24-bit bmps), but it does work on the bmps under investigation.
    Code:
    #include <stdio.h>
    #include <string.h>
    #include <stdint.h>
    
    int main(void) {
        const char *filename[] = {"art_G.bmp", "art_Q.bmp"};
    
        for (int i = 0; i < 2; i++) {
    
            uint32_t size, offset, width, height;
            FILE *f = fopen(filename[i], "rb");
            fseek(f, 2, SEEK_SET);
            fread(&size, sizeof size, 1, f);
            fseek(f, 10, SEEK_SET);
            fread(&offset, sizeof offset, 1, f);
            fread(&width,  sizeof width,  1, f); // skip 4 bytes
            fread(&width,  sizeof width,  1, f);
            fread(&height, sizeof height, 1, f);
            fseek(f, offset, SEEK_SET);
    
            int cnt=0, cnt00=0, cnt01=0, cnt10=0, cnt11=0, last_bit=-1;
    
            for (size_t j = 0; j < width * height; j++)
                for (size_t k = 0; k < 3; k++) {
                    int b = fgetc(f);
                    int this_bit = b & 1;
                    if (last_bit == 0) {
                        if (this_bit == 0) cnt00++;
                        else               cnt01++;
                    }
                    else if (last_bit == 1) { // explicit test for 1 so -1 will skip
                        if (this_bit == 0) cnt10++;
                        else               cnt11++;
                    }
                    last_bit = b & 1;
                }
    
            printf("File: %s  Size: %u  ", filename[i], size);
            printf("Width: %u  Height: %u\n", width, height);
            printf("cnt1 : %.3f\n", (double)cnt/(width*height*3));
            printf("cnt00: %.3f\n", (double)cnt00/(width*height*3-1)); // one less pair of bits
            printf("cnt01: %.3f\n", (double)cnt01/(width*height*3-1));
            printf("cnt10: %.3f\n", (double)cnt10/(width*height*3-1));
            printf("cnt11: %.3f\n", (double)cnt11/(width*height*3-1));
            putchar('\n');
    
            fclose(f);
        }
    
        return 0;
    }
    The cost of software maintenance increases with the square of the programmer's creativity. - Robert D. Bliss

  8. #23
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Quote Originally Posted by oogabooga View Post
    So G has perhaps fewer 11 combos than one might expect for random data, but not by much compared to the actual random data in Q.
    Goes to show just how crappy gcc's default implementation of rand( ) is! Yeah, I've been experimenting with different techniques as well (Haar and Fourier filter banks, among others), but so far haven't come up with anything that can reliably detect stegos (without comparing to their unmodified versions, that is). I'll post back if I do though; if anyone else here does, please let me know...
    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. #24
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Not that it really matters, but I just felt the code needed some cleaning up (clunky pointer declarations, orphaned magic numbers, etc). No bug fixes, just some aesthetic changes.

    Attachment 13054
    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;
    }

  10. #25
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,661
    To prevent this kind of analysis, you should only stego strongly encrypted data to begin with (ie, it passes tests for randomness).
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  11. #26
    Registered User
    Join Date
    Jan 2013
    Posts
    106
    Hi Guys,
    I wanted to hide 512K of data in a bitmap (compressed to png), in an apparently plain black image.
    For iOS Objectionable-C. Hiding the data from the user, not from Apple,
    but it was a bit too close to encryption for my liking, even though there is no key.

    Where each colour component of the image can have a value no greater than 2,
    a byte is spread across three colour components (RGBR), so four pixels store three complete bytes.


    Code:
       unsigned char bone;
        unsigned char btwo;
        unsigned char bthr;
        unsigned char bfor;
    
        int kickindex = 0;
        char kickbyte = 0;
        for (int ix = 2181173; ix > 2181173-(524288*4); ix=ix-4) {
    
            bone = 0;
            btwo = 0;
            bthr = 0;
            bfor = 0;
    
            kickbyte = kick13[kickindex];
            if (kickbyte >> 0 & 1) {bone = 1;} // 10
            if (kickbyte >> 1 & 1) {bone = bone + 2;} // 1
            if (kickbyte >> 2 & 1) {btwo = 1;} // 10
            if (kickbyte >> 3 & 1) {btwo = btwo + 2;} // 1
            if (kickbyte >> 4 & 1) {bthr = 1;} // 10
            if (kickbyte >> 5 & 1) {bthr = bthr + 2;} // 1
            if (kickbyte >> 6 & 1) {bfor = 1;} // 10
            if (kickbyte >> 7 & 1) {bfor = bfor + 2;} // 1
    
            bmpfile[ix] = 0x00 + bone;
            bmpfile[ix-1] = 0x00 + btwo;
            bmpfile[ix-2] = 0x00 + bthr;
            bmpfile[ix-3] = 0x00 + bfor;
            kickindex++;
        }
    
         for (int ix = 54; ix < 84021; ix++) {
        bmpfile[ix] = 0x01;
         }


    Code:
       CGImageRef sourceImage = [[UIImage imageFromResource:@"Codedimage.png"]CGImage];
        CFDataRef theData;
        theData = CGDataProviderCopyData(CGImageGetDataProvider(sourceImage));
        UInt8 *pixelData = (UInt8 *) CFDataGetBytePtr(theData);
        int dataLength = CFDataGetLength(theData);
        NSLog(@"ROM length: %i",dataLength);
        int outbyter;
        int outbyteg;
        int outbyteb;
        int outbytex;
        int index = 0;
        int skip = 0;
        // orientation still needs to be fixed here
        // image needs to be flipped vertical
        while (index < 1000) {
        outbyter = pixelData[index]; index++; skip++;
        if (skip > 2) {skip = 0; index++;}
        outbyteg = pixelData[index]; index++; skip++;
        if (skip > 2) {skip = 0; index++;}
        outbyteb = pixelData[index]; index++; skip++;
        if (skip > 2) {skip = 0; index++;}
        outbytex = pixelData[index]; index++; skip++;
        if (skip > 2) {skip = 0; index++;}
        NSLog(@"ROM file: %i:%i:%i:%i index %i",outbyter,outbyteg,outbyteb,outbytex,index);
        }
    Image resized and uploaded to Photobucket would have lost the data,
    but here it is

    http://img.photobucket.com/albums/v1...ps72d181d0.png
    Last edited by xArt; 10-13-2013 at 11:56 PM. Reason: add image

  12. #27
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Okay, so I've made some IMPORTANT changes to the code. First of all, there was a portability bug (systems > 32 bits would have produced different files - yikes!).

    The second important change was in the way the size information (which is used to indicate when to stop producing output during unpacking) is stored in the packed file; in order to make analysis a little more uncertain, a bunch of random bits are now appended.

    Besides that, I've done some additional cleanup of magic constants, making the code just a bit easier to understand.

    Any comments or suggestions for improvements to the code would be appreciated. Cheers!

    Attachment 13059
    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;
    }

  13. #28
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Fixed a silly logic error and improved error message calculations...

    24bbs.cpp

    And here is the source code...in bitmap format, of course.

    Could use some help with BMP Steganography-steganography-bmp
    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. #29
    C lover
    Join Date
    Oct 2007
    Location
    Virginia
    Posts
    266
    Very cool! I'm glad to see folks to an interest into this thread!

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