Here are the “restored” images, thanks to tillberg's further research:
As expected, there is a 5-byte block marker every about 0x4020 bytes. The format appears to be the following:
struct marker {
uint8_t tag; /* 1 if this is the last marker in the file, 0 otherwise */
uint16_t len; /* size of the following block (little-endian) */
uint16_t notlen; /* 0xffff - len */
};
Once the marker has been read, the next marker.len
bytes form a block that is part of the file. marker.notlen
is a control variable such that marker.len + marker.notlen == 0xffff
. The last block is such that marker.tag == 1
.
The structure is probably as follows. There are still unknown values.
struct file {
uint8_t name_len; /* number of bytes in the filename */
/* (not sure whether it's uint8_t or uint16_t) */
char name[name_len]; /* filename */
uint32_t file_len; /* size of the file (little endian) */
/* eg. "40 25 01 00" is 0x12540 bytes */
uint16_t unknown; /* maybe a checksum? */
marker marker1; /* first block marker (tag == 0) */
uint8_t data1[marker1.len]; /* data of the first block */
marker marker2; /* second block marker (tag == 0) */
uint8_t data2[marker2.len]; /* data of the second block */
/* ... */
marker lastmarker; /* last block marker (tag == 1) */
uint8_t lastdata[lastmarker.len]; /* data of the last block */
uint32_t unknown2; /* end data? another checksum? */
};
I haven't figured out what is at the end, but since PNGs accept padding, it's not too dramatic. However, the encoded file size clearly indicates that the last 4 bytes should be ignored...
Since I did not have access to all block markers just before the beginning of the file, I wrote this decoder that starts at the end and attempts to find the block markers. It's not robust at all but well, it worked for your test images:
#include <stdio.h>
#include <string.h>
#define MAX_SIZE (1024 * 1024)
unsigned char buf[MAX_SIZE];
/* Usage: program infile.png outfile.png */
int main(int argc, char *argv[])
{
size_t i, len, lastcheck;
FILE *f = fopen(argv[1], "rb");
len = fread(buf, 1, MAX_SIZE, f);
fclose(f);
/* Start from the end and check validity */
lastcheck = len;
for (i = len - 5; i-- > 0; )
{
size_t off = buf[i + 2] * 256 + buf[i + 1];
size_t notoff = buf[i + 4] * 256 + buf[i + 3];
if (buf[i] >= 2 || off + notoff != 0xffff)
continue;
else if (buf[i] == 1 && lastcheck != len)
continue;
else if (buf[i] == 0 && i + off + 5 != lastcheck)
continue;
lastcheck = i;
memmove(buf + i, buf + i + 5, len - i - 5);
len -= 5;
i -= 5;
}
f = fopen(argv[2], "wb+");
fwrite(buf, 1, len, f);
fclose(f);
return 0;
}
Older research
This is what you get when removing byte 0x4022
from the second image, then by removing byte 0x8092
:
It doesn’t really “repair” the images; I did this by trial and error. However, what it tells is that there is unexpected data every 16384 bytes. My guess is that the images are packed in some kind of filesystem structure and the unexpected data is simply block markers that you should remove when reading the data.
I don’t know where exactly the block markers are and their size, but the block size itself is most certainly 2^14 bytes.
It would help if you could also provide a hex dump (a few dozen bytes) of what appears right before the image and right after. This would give hints about what kind of information is stored at the beginning or end of the blocks.
Of course there’s also the possibility that there is a bug in your extraction code. If you are using a buffer of 16384 bytes for your file operations, then I would first check there.