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; }
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...
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; }
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; }
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
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
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; }
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; }
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.
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; }
Image resized and uploaded to Photobucket would have lost the data,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); }
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
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; }
Fixed a silly logic error and improved error message calculations...
24bbs.cpp
And here is the source code...in bitmap format, of course.
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; }
Very cool! I'm glad to see folks to an interest into this thread!