Issues modifying file attachment streams with Outlook .OFT files

920 Views Asked by At

I'm attempting to programmatically replace an embedded image within an OFT file (An Outlook message template), which is in Compound File Binary Format (because using anything human readable would make my life too easy).

To work with this file, I'm using OpenMCDF.

Since embedded images are basically file attachments, I can get the stream for the image like so:

static string FOOTER_IMG = "__substg1.0_37010102"; //Stream ID for embedded JPEG footer image
static string ATTACHMENT2 = "__attach_version1.0_#00000001"; //Storage ID for attached footer image
// ...
CFStream imgStream2 = file.RootStorage.GetStorage(ATTACHMENT2).GetStream(FOOTER_IMG);

I can then update that stream with the bytes from my desired image like so:

byte[] img2 = File.ReadAllBytes(footerimgFile); // New file
imgStream2.SetData(img2);

However, when I load the .OFT file in Outlook, the image no longer loads and I get a red X saying the image could not be loaded. I spent hours analyzing every bit of that OFT file, and the only thing that changed between the original template and the new template is that one stream that I replaced.

Here's where things get weird:

I noticed I could replace the bytes with the same exact bytes I had before and save it, so my saving mechanism is working. I thought maybe the OFT template stores some sort of hash of the image which has to match up. So I modified a few random bytes, and the image still loads (sometimes with some funky colors). Eventually, I realized it only breaks if the new image contains fewer bytes than the original image. I can replace the image with a larger image, and that works! I can also just pad a smaller image with trailing zeros at the end of the stream, and it still works.

This led me to come up with a true hackerific masterpiece:

if (img2.Length < 5585) img2 = img2.Concat(new byte[5585 - img2.Length]).ToArray();

Basically, if img2 is too small, I pad on enough bytes to make it the same size as the original image (5585 bytes to be exact). So this works. But.. yea.

My Question:

Does the Microsoft OFT file format store the byte count for attachments in some other stream or some other CDF container? If this was a standard property of CDF, you'd think OpenMCDF would update this count. This leads me to believe this is a property of the OFT file format, which OpenMCDF would of course know nothing about.

Why would writing a smaller stream corrupt the file, where writing a larger stream work?

Update:

From what I've read so far, the __properties_version1.0 stream contains a list of pointers (offsets?) to mark where various other streams are. I'm guessing something in here needs to be updated. Currently, I have these streams in the attachment container:

enter image description here

From what I can tell __properties_version1.0 doesn't change hardly at all between the first attachment (a 36,463 byte file) and the second attachment (a 5,585 byte file). The __properties_version1.0 for the second attachment is:

enter image description here

There's only a set of 8 bytes that change between those two attachments. In attachment 1 we have:

6F 8E 00 00 03 00 2D 00

In attachment 2 (pictured above) we have:

D1 15 00 00 03 00 6F 08

Are those offsets? Doesn't seem to be a range, or the numbers would go up. Those numbers are also way too big to be file sizes. Plus, it seems redundant to store file sizes in here anyway. So, I'm once again at a loss as to why changing the size of the 0x37010102 stream causes the image to no longer load.

Another thing that makes zero sense. I can change the size of the first attachment with either larger or smaller files, and nothing breaks. However, there's absolutely no difference between any stream in those two containers except the data in the 0x37010102 stream. Why does this approach work in one attachment and not the other?

Update 2:

I have noticed the two differences in the __properties_version1.0 stream between the two attachments do correspond to the file sizes:

6F 8E 00 00 03 00 2D 00 // Attachment 1
D1 15 00 00 03 00 6F 08 // Attachment 2

6F 8E seems to be a little-Endian representation of the file size, as 8E6F in decimal would be 36463, which is the number of bytes in the first attachment. 15D1 in decimal is 5585, the size of the second attachment. So, this stream definitely is storing file sizes. Now to see if I fix those bytes if the file becomes uncorrupted.

Update 3:

So, changing those bytes fixes a previously corrupted file, so that's the key! Now just to find a way to do this programmatically.

2

There are 2 best solutions below

0
On BEST ANSWER

Well, it's times like this I feel like an uber nerd. Here's the code that fixes the problem. Note, the byte offsets in propBytes might be different if you had other properties in the attachment.

// Fix file size on prop stream
var propStream = file.RootStorage.GetStorage(ATTACHMENT2).GetStream("__properties_version1.0");
var propBytes = propStream.GetData();
propBytes[0xb0] = (byte)(img2.Length & 0xFF);
propBytes[0xb1] = (byte)(img2.Length >> 8);
propStream.SetData(propBytes);

However, I like this solution better than padding extra zeros.

I think the real solution would be to use a third party library that deals with .MSG format, however I could not find any that don't make you install Outlook or Exchange on the server (which we can't do) or that are free (we have zero budget for this).

4
On

Are you working with embedded HTML images (which are just regular image attachments) or embedded RTF images (which OLE storage)? Do you simply truncate a particular stream without adjusting any other properties?