Steganography - PNG file

· Read in about 9 min · (1767 words)

Steganography

The steganography is a domain which consist to hide data in other medias, such a document, a picture, a video, etc. It’s different from the cryptography, because, the cryptography consist to encrypt a plain-text message with a key and to have a cryptogram, which can be read by people who have only the key.

I challenged myself and decided to made a project for hiding a message in a PNG file and to read it. For doing that, it’s important to understand how the PNG make an image, so, for, this reason, I read the PNG specification and I will explain it in the section below.

In this article, we will see how the PNG works, how we can execute the program and I will explain my program and what kind I do for improving it.

How PNG works

The PNG structure is defined on this document. A PNG file is composed by a signature and a set of chunks.

PNG signature

The 8 first bytes of the PNG file is for the signature which is always 137 80 78 71 13 10 26 10 in decimal values. This signature indicate it’s a single PNG file.

Chunk layout

A PNG file is a set of chunks and they have a specific layout:

Chunk layout

Length:

This field is encoded in 4 bytes unsigned and give the length of the data field, it must no include the field itself, the field type and the CRC.

Type:

Encoded in 4 bytes, that define the type of the chunk and the data associated. PNG defined some critical chunks and auxiliary chunks.

Data:

Contains the data of the chunk and depends on the type of chunk. The size of this field is variable and it’s defined by the length field.

CRC:

The CRC (Cyclic Redundancy Check) field is encoded in 4 bytes and is calculated by the type field and the data field.

Chunks

PNG has 4 critical chunks: IHDR, IPLTE, IDAT and IEND. IHDR is the first chunk after the PNG signature and the data contains these fields:

Width 4 bytes
Height 4 bytes
Bit depth 1 byte
Color type 1 byte
Compression method 1 byte
Filter method 1 byte
Interlace method 1 byte

The width and the height fields defined the size of the image. For the others fields, I recommand to read the specification on the website. The PLTE chunk need to be between the IHDR and the first IDAT and it contain the palette of the image. The IDAT chunk contain all the data of the image and the last critical chunk is the IEND and must be at the end of the file. This chunk not contain any data, so, the size of the data is 0. I will not explain in details these chunks and if you want to learn more about all chunks, I suggest to read the specification.

Execute the program

You can download the program here.

I do not give to you the binaries, so, you must compile it:

$ gcc -Wno-all -ggdb -O0 main.c -lz -o main

As you see, I specify the library zlib, because I need to use it for the function crc32() for generating the CRC, you may need to install the package zlib1g-dev.

If you do not have any errors during the compilation, you will have the main file binary:

$ file main
main: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d02e0b3b61f7c8e7bf5d61b741945afce57a7faa, for GNU/Linux 3.2.0, with debug_info, not stripped

You can execute it:

$ ./main 
Usage: ./main <hide|unhide> <picture> <text file>

The program take 3 arguments: the first one is the action to do: hide or unhide a message.

If you specify the hide argument, the second argument is the PNG file which will contain the message and the third argument is the file with the message to hide in it.

If you specify the unhide argument, the second argument is the PNG file which contain the message and the third argument is the text output file for the message.

I use one of my photo when I took during a hike, the picture is not perfect, but I am not a professional photographer 🙂

Moutains

And the text I will hide in the picture:

$ cat text.txt
Hello world!
I am a message hidden in the picture :)

And now, we can execute it:

$ ./main hide moutains.png text.txt
Signature
89 50 4e 47 d a 1a a 

Size of the image:
Width: 5184px
Height: 3456px

That will generate a new picture:

$ ls -la dest.png 
-rw-r--r-- 1 geoffrey geoffrey 18048344 Oct 19 16:24 dest.png

Open the file to be sure if you can display it. The message is hidden in the PNG file itself.

If you compare the size of the original picture and the picture which contain the message, you can see the destination picture is bigger than the original:

-rw-r--r--  1 geoffrey geoffrey 18048374 Oct 19 10:40 dest.png
-rw-r--r--  1 geoffrey geoffrey 18048279 Oct 14 09:14 moutains.png

Why ? The picture is bigger than the original, because we put the text, but it is not all, but, we will see in the next section why.

And to get the message, you need to specify the output image and the text file which will contain our text:

$ ./main unhide dest.png output.txt
Signature
89 50 4e 47 d a 1a a 

Size of the image:
Width: 5184px
Height: 3456px
$ cat output.txt 
Hello world!
I am a message hidden in the picture :)

That’s works 🙂

Anatomy of the program

In the main() function, we dispatch the action to do: hide or unhide. So, we have the function static int hide_message(int, char **) for hiding the message in the picture and the function static int unhide_message(int, char **) for unhiding the message from a picture.

Get the IHDR

For identifying the PNG signature and IHDR chunk, I made the function static int getHeader(int fd_r, char *buf).

In this function, I initialized two structures:

struct signature{
    unsigned char bytes[8];
} __attribute__((packed));

struct iHDR{
    unsigned char width[4];
    unsigned char height[4];
    char depth;
    char type;
    char compression;
    char filter;
    char interlace;
    // Chunk crc
    unsigned char crc[4];
} __attribute__((packed));

After I initialized these structures, I will read 33 first bytes in the PNG file. The 33 bytes is the size of the PNG signature, 8 bytes, and the size of the chunk IHDR, 25 bytes. After I read these bytes, I will display it in the output and I will write to the new destination PNG file.

Hiding the message

For hiding the message in the PNG file, I wrote the function static int hide_message(int, char **). In this function, I read the header of the PNG file. After that, I insert my own chunk.

My own chunk

I decided to create my own chunk. When I explained lately, the destination image is bigger than the original:

-rw-r--r--  1 geoffrey geoffrey 18048344 Oct 19 10:40 dest.png
-rw-r--r--  1 geoffrey geoffrey 18048279 Oct 14 09:14 moutains.png

The original picture has a size of 18048279 and the destination file has a size of 18048344, because, my own chunk with the text in it. If we want to get the size of the data I put in the PNG file, we can easily find it. First, we must to get the delta between the two PNG files, after that, we can subtract the size of chunk. The size of the chunk is 4 bytes for the length, 4 bytes for the chunk type code and 4 bytes for the CRC, so, the chunk size is 12. With that, we can identify the size of the text message

$$ text = (18048344 - 18048279) - 12 = 53 bytes $$

For injecting my chunk, I made the function static size_t insertChunk(int, const char *, size_t, int).

In this function, I write in the destination file my chunk and the data of the text:

struct chunk s_chunk; // Initialization of the chunk
/* .... */
/* Fill my chunk */
convert_from_uint(s_chunk.length, text_length); // Convert to bytes
s_chunk.type[0] = 's';
s_chunk.type[1] = 'T';
s_chunk.type[2] = 'E';
s_chunk.type[3] = 'G';

/* Write to the file */
write(fd_w, s_chunk.length, 4);
write(fd_w, s_chunk.type, 4);
/* Write the text to the file */
length = write(fd_w, buf, text_length);

As you see in the simple code above, I specified the type code sTEG for Steganography. If have spotted, I specified the first letter in lowercase, because, in the PNG specification, in the section for the chunk naming convention, all ancillary chunk need to start with a lowercase letter.

After that, I calculated the CRC from the chunk type code and the data and I put in the file:

/* crc32 is provided by the zlib library */
crc_z = crc32(0L, Z_NULL, 0);
crc_z = crc32(crc_z, s_chunk.type, 4);
crc_z = crc32(crc_z, buf, text_length);

convert_from_uint(crc, crc_z); 
length = write(fd_w, crc, 4);

After I put my own chunk, I can continue to read the PNG file and to write all bytes to the destination file. When I read the chunk IEND, that’s means it’s the end of the file, I can close the file.

Reading the message

For getting the text from the PNG file, I wrote the function static int unhide_message(int, char **) and it’s really simple. I read all the file to get all chunks. I read all chunks for having the chunk type code and to find my own chunk. When I found the chunk sTEG, I will write to the output text file:

/* Read the 8 bytes for the chunk length and the chunk type code */
length = read(fd_r, tmp, 8); 
s_chunk = (struct chunk*)(tmp);

/* Identifying the chunk type */
if (s_chunk->type[0] == 's' && s_chunk->type[1] == 'T' &&
    s_chunk->type[2] == 'E' && s_chunk->type[3] == 'G'){
    write(fd_w, buf, length);
}

So, it’s really simple to get the message.

Improving the program

The inconvenient of my method is the size of the image. I add chunks and the text in the file, so, the size of the image increase. I want to update that method and to change just some pixel for hiding the message.

In created my own chunk, so, it’s easily to identify if the picture is altered, because this chunk is not defined in the PNG specification. We can easily made a program and to get all chunks.

Otherwise, if I continue to use my own chunk, I would like to split the text to hide in different chunks and to put them between IDAT chunks. In my current program, the chunk is write after the IHDR chunk.