In trying out this challenge, the first mental roadblock was my unfamiliarity with PNG files and their format. As a first step, I just viewed the raw data within the file to ascertain the type of data in the "IDAT" chunk. This led me to send a feedback question as it seemed the data chunk seemed too small to hold the test message in the stamp sized files. A feedback answer alerted me to the obvious fact that the data chunk contained compressed data. Also, from what I was seeing in the feedback was that solutions seemed to tilt towards a Python programming solution. However, I still wanted to pursue a "C" program solution, so searching the stack overflow site, I found an answer to a scenario that fit for compressing and decompressing PNG file data. Following is the link - "https://stackoverflow.com/questions/1362945/how-to-decode-a-png-image-to-raw-bytes-from-c-code-with-libpng"
That information pointed me to the "libpng" library and its assortment of functions. Using that as an initial basis, I was able to discern the structure of the pixels and thus find the LSB values I was needing to construct an array of ones and zeros and subsequently parse the array to produce ASCII characters and text.
Following, it the initial decoder program.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <png.h>
#define SIZE 256000
png_bytep *row_pointers;
unsigned int width;
unsigned int height;
static void read_png_file(char *filename)
{
FILE *fp = fopen(filename, "rb");
png_byte bit_depth, color_type;
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png)
abort();
png_infop info = png_create_info_struct(png);
if (!info)
abort();
if (setjmp(png_jmpbuf(png)))
abort();
png_init_io(png, fp);
png_read_info(png, info);
width = png_get_image_width(png, info);
height = png_get_image_height(png, info);
color_type = png_get_color_type(png, info);
bit_depth = png_get_bit_depth(png, info);
if (bit_depth == 16) /* Read any color_type into 8bit depth, RGBA format. */
png_set_strip_16(png);
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png);
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) /* PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth. */
png_set_expand_gray_1_2_4_to_8(png);
if (png_get_valid(png, info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png);
/* These color_type don't have an alpha channel then fill it with 0xff. */
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
png_read_update_info(png, info);
row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
for (int y = 0; y < height; y++)
{
row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png,info));
}
png_read_image(png, row_pointers);
fclose(fp);
}
static void process_png()
{
int idx = 0;
unsigned int w[SIZE];
char chr;
for (unsigned int y = 0; y < height; y++)
{
png_bytep row = row_pointers[y];
/* Ascertain LSB from the RGB channels - disregard the A channel */
for (unsigned int x = 0; x < width; x++)
{
png_bytep px = &(row[x * 4]);
//printf("%d%d%d", px[0] % 2, px[1] % 2, px[2] % 2);
//printf("%5d, %5d = RGBA(%3d, %3d, %3d, %3d) index: %d\n", y, x, px[0], px[1], px[2], px[3], idx);
w[idx + 0] = px[0] % 2;
w[idx + 1] = px[1] % 2;
w[idx + 2] = px[2] % 2;
idx += 3;
if (idx > SIZE)
break;
}
if (idx > SIZE)
break;
}
printf("Header byte values: ");
for (int i = 2; i < 18; i++)
printf("%d", w[i]);
printf("\n");
idx = w[4] * 8192 + w[5] * 4096 + w[6] * 2048 + w[7] * 1024 + w[8] * 512 + w[9] * 256 + w[10] * 128 + w[11] * 64 + w[12] * 32 + w[13] * 16 + w[14] * 8 + w[15] * 4 + w[16] * 2 + w[17];
printf("Text length.......: %d\nMESSAGE\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n", idx);
/* Generate an ASCII character using 8 consecutive pseudo-bit values */
for (int i = 18; i <= idx; i = i + 8)
{
chr = w[i] * 128 + w[i + 1] * 64 + w[i + 2] * 32 + w[i + 3] * 16 + w[i + 4] * 8 + w[i + 5] * 4 + w[i + 6] * 2 + w[i + 7];
printf("%c", chr);
}
printf("\n");
}
int main(int argc, char *argv[])
{
char *in;
if (argc > 1)
{
in = argv[1];
}
else
{
in = "a.png";
}
read_png_file(in);
process_png();
for (int y = 0; y < height; y++)
free(row_pointers[y]);
free(row_pointers);
return EXIT_SUCCESS;
}
Once compiled, I tested out the first three small image files and returned the same repetitive test message.
craig@Vera:~/C_Programs/Console/Steganography/bin/Release$ ./Steganography flower.png
Header byte values: 0000101110010000
Text length.......: 2960
MESSAGE
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Tes
I then executed the decoder program over the image of the cat and got the additional hidden instructions.
craig@Vera:~/C_Programs/Console/Steganography/bin/Release$ ./Steganography Kitty.png
Header byte values: 0001010000101000
Text length.......: 5160
MESSAGE
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The first part of the sentence is "Three may keep a secret, ...".
### Task 2: Decode a binary image
A binary image is hidden in the least significant bits of the butterfly image, the encoding scheme is:
- Simple LSB Encoding**
- Header: `[1, 0]`
- Next 16 bits: image `width` in pixels
- Next 16 bits: image `height` in pixels
- Raw pixels of the binary image
Hint: Create and fill an image array of the derived `width` and `height` with the encoded hidden pixels.
In order to view your image in classic viewers, it might be helpful to map 0 -> 0 and 1 -> 255.
Derive the second part of the sentence and answer the question about the image
However, that is where I have hit a brick wall. I was able to clone and refactor the above code a bit to ascertain the height and width values in the butterfly file, but in trying to go past that point, I have had to stop for now.
craig@Vera:~/C_Programs/Console/Steg2/bin/Release$ ./Steg2 Butterfly.png
Width: 900 Height: 900
After getting words of encouragement from Andre and to be brief, I did some further research in the "libpng" functionality and its use of pixel data, I devised a second program to read and process the "moth/butterfly" image, producing a monochrome image depicting the iconic scene of Benjamin Franklin flying a kite in a thunderstorm with a key attached to the kite string and including the closing portion of the saying "... if two of them are dead".
Following is the second program to complete task 2 sprinkled with comments in case anyone wants to reference the use of "libpng" in their future code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <png.h>
#define HEADER 34
/* Prototypes */
void read_png_file(char *filename, unsigned int *input_width, unsigned int *input_height);
unsigned int *process_png(unsigned int *output_width, unsigned int *output_height, unsigned int input_width, unsigned int input_height);
unsigned int writeImage(char *filename, unsigned int wt, unsigned int ht, unsigned int *buffer, char *title);
png_bytep *row_pointers;
int main(int argc, char *argv[])
{
unsigned int rx, ht, wt, hx, wx, * buffer = NULL;
read_png_file(argv[1], &wx, &hx);
buffer = process_png(&wt, &ht, wx, hx);
printf("Output height: %d Output width: %d\n", ht, wt);
rx = writeImage(argv[2], wt, ht, buffer, "Hidden Image");
if (rx != 0)
{
printf("Exit error: %d\n", rx);
return 1;
}
for (int y = 0; y < hx - 1; y++)
{
free(row_pointers[y]);
}
free(row_pointers);
free(buffer);
return EXIT_SUCCESS;
}
void read_png_file(char *filename, unsigned int *input_width, unsigned int *input_height)
{
FILE *fp = fopen(filename, "rb");
png_byte bit_depth, color_type;
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png)
abort();
png_infop info = png_create_info_struct(png);
if (!info)
abort();
if (setjmp(png_jmpbuf(png)))
abort();
png_init_io(png, fp);
png_read_info(png, info);
*input_width = png_get_image_width(png, info);
*input_height = png_get_image_height(png, info);
color_type = png_get_color_type(png, info);
bit_depth = png_get_bit_depth(png, info);
if (bit_depth == 16) /* Read any color_type into 8bit depth, RGBA format. */
png_set_strip_16(png);
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png);
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) /* PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16 bit depth. */
png_set_expand_gray_1_2_4_to_8(png);
if (png_get_valid(png, info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png);
/* These color_type don't have an alpha channel then fill it with 0xff. */
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)
{
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
}
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
png_read_update_info(png, info);
row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * *input_height);
for (int y = 0; y < *input_height; y++)
{
row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png,info));
}
png_read_image(png, row_pointers);
printf("Input height.: %d Input width.: %d\n", *input_height, *input_width);
fclose(fp);
return;
}
unsigned int *process_png(unsigned int *output_width, unsigned int *output_height, unsigned int input_width, unsigned int input_height)
{
int ix = 0;
png_bytep row = row_pointers[0];
/* Construct width and height values using the LSB of bytes 2 - 33 to convert from binary to integer */
*output_width = (row[22] % 2) + (row[21] % 2) * 2 + (row[20] % 2) * 4 + (row[18] % 2) * 8 + (row[17] % 2) * 16
+ (row[16] % 2) * 32 + (row[14] % 2) * 64 + (row[13] % 2) * 128 + (row[12] % 2) * 256 + (row[10] % 2) * 512;
*output_height = (row[44] % 2) + (row[42] % 2) * 2 + (row[41] % 2) * 4 + (row[40] % 2) * 8 + (row[38] % 2) * 16
+ (row[37] % 2) * 32 + (row[36] % 2) * 64 + (row[34] % 2) * 128 + (row[33] % 2) * 256 + (row[32] % 2) * 512;
unsigned int *buffer = (unsigned int *) malloc((*output_width * *output_height + HEADER) * sizeof(unsigned int));
/* Store the LSB of each pixel as a character in a large character array */
/* Only go as far as the two-dimensional array size */
for (unsigned int y = 0; y < input_height; y++)
{
png_bytep row = row_pointers[y];
for (unsigned int x = 0; x < input_width; x++)
{
png_bytep px = &(row[x * 4]);
buffer[ix] = px[0] % 2 + '0';
ix++;
buffer[ix] = px[1] % 2 + '0';
ix++;
buffer[ix] = px[2] % 2 + '0';
ix++;
if (ix > (*output_width * *output_height + HEADER))
break;
}
if (ix > (*output_width * *output_height + HEADER))
break;
}
for (int i = 0; i < (*output_width * *output_height); i++)
buffer[i] = buffer[i + HEADER];
return buffer;
}
unsigned int writeImage(char *filename, unsigned int wt, unsigned int ht, unsigned int *buffer, char *title)
{
int code = 0;
FILE *fp = NULL;
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
png_bytep row = NULL;
fp = fopen(filename, "wb"); /* Open file for writing - binary mode */
if (fp == NULL)
{
fprintf(stderr, "Could not open file %s for writing\n", filename);
code = 1;
goto finalise;
}
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); /* Initialize write structure */
if (png_ptr == NULL)
{
fprintf(stderr, "Could not allocate write struct\n");
code = 1;
goto finalise;
}
info_ptr = png_create_info_struct(png_ptr); /* Initialize info structure */
if (info_ptr == NULL)
{
fprintf(stderr, "Could not allocate info struct\n");
code = 1;
goto finalise;
}
if (setjmp(png_jmpbuf(png_ptr))) /* Set up exception handling */
{
fprintf(stderr, "Error during png creation\n");
code = 1;
goto finalise;
}
png_init_io(png_ptr, fp);
/* Write header (8 bit colour depth) */
png_set_IHDR(png_ptr, info_ptr, wt, ht, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
if (title != NULL) /* Set the title */
{
png_text title_text;
title_text.compression = PNG_TEXT_COMPRESSION_NONE;
title_text.key = "Title";
title_text.text = title;
png_set_text(png_ptr, info_ptr, &title_text, 1);
}
png_write_info(png_ptr, info_ptr);
row = (png_bytep) malloc(3 * wt * sizeof(png_byte)); /* Allocate memory for one row (3 bytes per pixel - RGB) */
for (int i = 0; i < ht; i++) /* Write the image data */
{
for (int j = 0; j < wt; j++)
{
if (buffer[i * wt + j] == '1')
{
row[j * 3 + 0] = 255; /* These RGB values could be changed to */
row[j * 3 + 1] = 255; /* produce a different background colour */
row[j * 3 + 2] = 255;
}
else
{
row[j * 3 + 0] = 0; /* These RGB values could be changed to */
row[j * 3 + 1] = 0; /* produce a different foreground colour */
row[j * 3 + 2] = 0;
}
}
png_write_row(png_ptr, row);
}
png_write_end(png_ptr, NULL); /* Denote end of writing */
finalise:
if (fp != NULL)
fclose(fp);
if (info_ptr != NULL)
png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
if (png_ptr != NULL)
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
if (row != NULL)
free(row);
return code;
}
Executing the above code resulted in the diagnostic information in the terminal and the subsequent monochrome image.
craig@Vera:~/C_Programs/Console/StegImage/bin/Release$ ./StegImage Moth.png ben.png
Input height.: 480 Input width.: 640
Output height: 900 Output width: 900
Thanks again. This was a nice challenge.