Task 431: .MSO File Format
Task 431: .MSO File Format
1. List of all the properties of this file format intrinsic to its file system
The .MSO file format is a proprietary binary container based on the Microsoft Compound File Binary Format (CFBF), also known as Structured Storage or OLE Compound File. It functions like a file system within a single file, with sectors, allocation tables, and directory entries. The intrinsic properties are derived from the file's header (first 512 bytes) and directory structure, which define the "file system" layout and metadata. These include:
- Signature (Magic Number): An 8-byte fixed value (0xD0 0xCF 0x11 0xE0 0xA1 0xB1 0x1A 0xE1) identifying the file as CFBF.
- CLSID: A 16-byte GUID (Class Identifier) for the root storage; typically zero for .MSO files.
- Minor Version: A 2-byte unsigned short indicating the minor format version (commonly 0x003E or 62 in decimal for compatibility).
- Major Version (DLL Version): A 2-byte unsigned short indicating the major format version (3 for 512-byte sectors, 4 for 4096-byte sectors).
- Byte Order: A 2-byte unsigned short (always 0xFFFE for little-endian/Intel byte ordering).
- Sector Shift: A 2-byte unsigned short defining the sector size as a power of 2 (e.g., 9 for 512-byte sectors).
- Mini Sector Shift: A 2-byte unsigned short defining the mini-sector size as a power of 2 (e.g., 6 for 64-byte mini-sectors).
- Reserved (Header Reserved 1): A 2-byte unsigned short that must be zero.
- Reserved (Header Reserved 2): A 4-byte unsigned long that must be zero.
- Number of Directory Sectors: A 4-byte unsigned long indicating the count of sectors in the directory chain (must be zero for 512-byte sectors; used for larger sectors).
- Number of FAT Sectors: A 4-byte unsigned long indicating the count of sectors in the File Allocation Table (FAT) chain.
- First Directory Sector Location: A 4-byte unsigned long pointing to the starting sector ID (SID) of the directory chain.
- Transaction Signature: A 4-byte unsigned long for transaction support (must be zero, as transactions are not supported in the reference implementation).
- Mini Stream Cutoff Size: A 4-byte unsigned long defining the maximum size for streams in the mini-stream (typically 4096 bytes).
- First Mini FAT Sector Location: A 4-byte unsigned long pointing to the starting SID of the MiniFAT chain.
- Number of Mini FAT Sectors: A 4-byte unsigned long indicating the count of sectors in the MiniFAT chain.
- First DIFAT Sector Location: A 4-byte unsigned long pointing to the starting SID of the Double-Indirect FAT (DIFAT) chain.
- Number of DIFAT Sectors: A 4-byte unsigned long indicating the count of sectors in the DIFAT chain.
- DIFAT Array: An array of 109 4-byte unsigned longs listing the first 109 FAT sector locations (unused entries are set to FREESECT: 0xFFFFFFFF).
Additionally, the directory entries (starting from the first directory sector) contain per-stream/storage properties, which are part of the intrinsic file system structure:
- Name: A variable-length Unicode string (up to 32 characters, null-terminated) for the stream or storage.
- Name Length: A 2-byte unsigned short indicating the length of the name in bytes (including null terminator).
- Type: A 1-byte enum (e.g., 0x00 = unknown, 0x01 = storage, 0x02 = stream, 0x05 = root storage).
- Color: A 1-byte enum for red-black tree balancing (0x00 = red, 0x01 = black).
- Left Sibling SID: A 4-byte signed long pointing to the left sibling directory entry.
- Right Sibling SID: A 4-byte signed long pointing to the right sibling directory entry.
- Child SID: A 4-byte signed long pointing to the child directory entry (for storages).
- CLSID: A 16-byte GUID for the storage's class identifier.
- State Bits: A 4-byte unsigned long for user-defined flags.
- Creation Time: An 8-byte FILETIME structure (Windows timestamp).
- Modify Time: An 8-byte FILETIME structure (Windows timestamp).
- Starting Sector Location: A 4-byte unsigned long pointing to the first sector of the stream.
- Stream Size: An 8-byte unsigned long long indicating the size of the stream (or high 4 bytes zero for versions <4).
These properties define the allocation, hierarchy, and metadata within the .MSO file's internal file system.
2. Two direct download links for files of format .MSO
- https://www.stsci.edu/detectors/uv_optical/Stevenson_files/oledata.mso
- http://www.lrrd.org/lrrd15/1/addo151_files/oledata.mso
3. Ghost blog embedded HTML JavaScript that allows a user to drag n drop a file of format .MSO and it will dump to screen all these properties
Drag and Drop .MSO File to Dump Properties
4. Python class that can open any file of format .MSO and decode read and write and print to console all the properties from the above list
import struct
import sys
class MsoFileParser:
def __init__(self, filepath):
self.filepath = filepath
self.header = {}
self.difat = []
def read_header(self):
with open(self.filepath, 'rb') as f:
data = f.read(512)
if len(data) < 512:
raise ValueError("File too small to be a valid .MSO/CFB file.")
# Unpack header fields using little-endian
(
sig, # 8 bytes
clsid, # 16 bytes
minor_ver, # uint16
major_ver, # uint16
byte_order, # uint16
sector_shift, # uint16
mini_sector_shift, # uint16
reserved1, # uint16
reserved2, # uint32
num_dir_sectors, # uint32
num_fat_sectors, # uint32
first_dir_sect, # uint32
trans_sig, # uint32
mini_cutoff, # uint32
first_mini_fat, # uint32
num_mini_fat, # uint32
first_difat, # uint32
num_difat # uint32
) = struct.unpack('<8s16sHHHHHIIIIIiiiiii', data[:76])
self.header = {
'Signature': ' '.join(f'{b:02x}' for b in sig),
'CLSID': '-'.join(f'{int.from_bytes(clsid[i:i+4], "little"):08x}' for i in range(0, 16, 4)),
'Minor Version': minor_ver,
'Major Version': major_ver,
'Byte Order': hex(byte_order),
'Sector Shift': sector_shift,
'Sector Size': 2 ** sector_shift,
'Mini Sector Shift': mini_sector_shift,
'Mini Sector Size': 2 ** mini_sector_shift,
'Reserved1': reserved1,
'Reserved2': reserved2,
'Number of Directory Sectors': num_dir_sectors,
'Number of FAT Sectors': num_fat_sectors,
'First Directory Sector Location': first_dir_sect,
'Transaction Signature': trans_sig,
'Mini Stream Cutoff Size': mini_cutoff,
'First Mini FAT Sector Location': first_mini_fat,
'Number of Mini FAT Sectors': num_mini_fat,
'First DIFAT Sector Location': first_difat,
'Number of DIFAT Sectors': num_difat
}
# DIFAT array (109 uint32)
self.difat = struct.unpack('<109I', data[76:76 + 109*4])
def print_properties(self):
print("MSO File Properties:")
for key, value in self.header.items():
print(f"{key}: {value}")
print("DIFAT Array (first 109 FAT sectors):")
for i, sect in enumerate(self.difat):
print(f" Entry {i}: {sect} (0x{sect:x})")
# Note: Full directory parsing for additional properties (e.g., stream names, times) requires sector chaining, omitted for brevity.
def write_sample(self, output_path):
# Create a minimal .MSO-like CFB file with header (not fully functional, for demo)
header_data = struct.pack(
'<8s16sHHHHHIIIIIiiiiii',
b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', # Signature
b'\x00' * 16, # CLSID zero
0x003E, # Minor ver
3, # Major ver (512-byte)
0xFFFE, # Byte order
9, # Sector shift (512)
6, # Mini sector shift (64)
0, # Reserved1
0, # Reserved2
0, # Num dir sectors
1, # Num FAT sectors (minimal)
0, # First dir sect
0, # Trans sig
4096, # Mini cutoff
1, # First mini FAT
1, # Num mini FAT
0xFFFFFFFE, # First DIFAT (ENDOFCHAIN)
0 # Num DIFAT
)
difat_data = struct.pack('<109I', *( [0xFFFFFFFE] * 109 )) # All ENDOFCHAIN
with open(output_path, 'wb') as f:
f.write(header_data + difat_data + b'\x00' * (512 - len(header_data) - len(difat_data))) # Pad to 512
# Usage example
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python mso_parser.py <path_to_mso_file> [output_path]")
sys.exit(1)
parser = MsoFileParser(sys.argv[1])
parser.read_header()
parser.print_properties()
if len(sys.argv) > 2:
parser.write_sample(sys.argv[2])
print(f"Sample .MSO written to {sys.argv[2]}")
5. Java class that can open any file of format .MSO and decode read and write and print to console all the properties from the above list
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
public class MsoFileParser {
private String filepath;
private ByteBuffer buffer;
public MsoFileParser(String filepath) {
this.filepath = filepath;
}
public void readHeader() throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(filepath, "r")) {
FileChannel channel = raf.getChannel();
buffer = ByteBuffer.allocate(512);
buffer.order(ByteOrder.LITTLE_ENDIAN);
channel.read(buffer);
buffer.flip();
if (buffer.limit() < 512) {
throw new IOException("File too small to be a valid .MSO/CFB file.");
}
}
}
public void printProperties() {
System.out.println("MSO File Properties:");
// Signature (bytes 0-7)
buffer.position(0);
byte[] sigBytes = new byte[8];
buffer.get(sigBytes);
String sig = "";
for (byte b : sigBytes) {
sig += String.format("%02x ", b);
}
System.out.println("Signature: 0x" + sig.trim());
// CLSID (bytes 8-23)
buffer.position(8);
byte[] clsidBytes = new byte[16];
buffer.get(clsidBytes);
String clsid = "";
for (int i = 0; i < 16; i += 4) {
int part = ByteBuffer.wrap(clsidBytes, i, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
clsid += String.format("%08x-", part);
}
System.out.println("CLSID: " + clsid.substring(0, clsid.length() - 1));
// Minor Version (bytes 24-25)
System.out.println("Minor Version: " + buffer.getShort(24));
// Major Version (bytes 26-27)
System.out.println("Major Version: " + buffer.getShort(26));
// Byte Order (bytes 28-29)
System.out.println("Byte Order: 0x" + Integer.toHexString(buffer.getShort(28) & 0xFFFF));
// Sector Shift (bytes 30-31)
short sectorShift = buffer.getShort(30);
System.out.println("Sector Shift: " + sectorShift + " (Sector Size: " + (1 << sectorShift) + " bytes)");
// Mini Sector Shift (bytes 32-33)
short miniSectorShift = buffer.getShort(32);
System.out.println("Mini Sector Shift: " + miniSectorShift + " (Mini Sector Size: " + (1 << miniSectorShift) + " bytes)");
// Reserved1 (bytes 34-35)
System.out.println("Reserved1: " + buffer.getShort(34));
// Reserved2 (bytes 36-39)
System.out.println("Reserved2: " + buffer.getInt(36));
// Number of Directory Sectors (bytes 40-43)
System.out.println("Number of Directory Sectors: " + buffer.getInt(40));
// Number of FAT Sectors (bytes 44-47)
System.out.println("Number of FAT Sectors: " + buffer.getInt(44));
// First Directory Sector Location (bytes 48-51)
System.out.println("First Directory Sector Location: " + buffer.getInt(48));
// Transaction Signature (bytes 52-55)
System.out.println("Transaction Signature: " + buffer.getInt(52));
// Mini Stream Cutoff Size (bytes 56-59)
System.out.println("Mini Stream Cutoff Size: " + buffer.getInt(56));
// First Mini FAT Sector Location (bytes 60-63)
System.out.println("First Mini FAT Sector Location: " + buffer.getInt(60));
// Number of Mini FAT Sectors (bytes 64-67)
System.out.println("Number of Mini FAT Sectors: " + buffer.getInt(64));
// First DIFAT Sector Location (bytes 68-71)
System.out.println("First DIFAT Sector Location: " + buffer.getInt(68));
// Number of DIFAT Sectors (bytes 72-75)
System.out.println("Number of DIFAT Sectors: " + buffer.getInt(72));
// DIFAT Array (bytes 76-511)
System.out.println("DIFAT Array (first 109 FAT sectors):");
for (int i = 0; i < 109; i++) {
int offset = 76 + i * 4;
int sect = buffer.getInt(offset);
System.out.println(" Entry " + i + ": " + sect + " (0x" + Integer.toHexString(sect) + ")");
}
}
public void writeSample(String outputPath) throws IOException {
ByteBuffer outBuffer = ByteBuffer.allocate(512);
outBuffer.order(ByteOrder.LITTLE_ENDIAN);
// Signature
outBuffer.put((byte)0xD0); outBuffer.put((byte)0xCF); outBuffer.put((byte)0x11); outBuffer.put((byte)0xE0);
outBuffer.put((byte)0xA1); outBuffer.put((byte)0xB1); outBuffer.put((byte)0x1A); outBuffer.put((byte)0xE1);
// CLSID zero
for (int i = 0; i < 16; i++) outBuffer.put((byte)0);
// Minor ver
outBuffer.putShort((short)0x003E);
// Major ver
outBuffer.putShort((short)3);
// Byte order
outBuffer.putShort((short)0xFFFE);
// Sector shift
outBuffer.putShort((short)9);
// Mini sector shift
outBuffer.putShort((short)6);
// Reserved1
outBuffer.putShort((short)0);
// Reserved2
outBuffer.putInt(0);
// Num dir sectors
outBuffer.putInt(0);
// Num FAT sectors (minimal)
outBuffer.putInt(1);
// First dir sect
outBuffer.putInt(0);
// Trans sig
outBuffer.putInt(0);
// Mini cutoff
outBuffer.putInt(4096);
// First mini FAT
outBuffer.putInt(1);
// Num mini FAT
outBuffer.putInt(1);
// First DIFAT
outBuffer.putInt(0xFFFFFFFE);
// Num DIFAT
outBuffer.putInt(0);
// DIFAT array all ENDOFCHAIN
for (int i = 0; i < 109; i++) {
outBuffer.putInt(0xFFFFFFFE);
}
// Pad remaining
while (outBuffer.hasRemaining()) outBuffer.put((byte)0);
outBuffer.flip();
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
fos.getChannel().write(outBuffer);
}
}
public static void main(String[] args) throws IOException {
if (args.length < 1) {
System.out.println("Usage: java MsoFileParser <path_to_mso_file> [output_path]");
System.exit(1);
}
MsoFileParser parser = new MsoFileParser(args[0]);
parser.readHeader();
parser.printProperties();
if (args.length > 1) {
parser.writeSample(args[1]);
System.out.println("Sample .MSO written to " + args[1]);
}
}
}
6. JavaScript class that can open any file of format .MSO and decode read and write and print to console all the properties from the above list
const fs = require('fs'); // For Node.js file I/O
class MsoFileParser {
constructor(filepath) {
this.filepath = filepath;
this.header = {};
this.difat = [];
}
readHeader() {
const data = fs.readFileSync(this.filepath);
if (data.length < 512) {
throw new Error('File too small to be a valid .MSO/CFB file.');
}
const dv = new DataView(data.buffer);
// Signature (0-7)
let sig = '';
for (let i = 0; i < 8; i++) {
sig += dv.getUint8(i).toString(16).padStart(2, '0') + ' ';
}
this.header['Signature'] = '0x' + sig.trim();
// CLSID (8-23)
let clsid = '';
for (let i = 8; i < 24; i += 4) {
clsid += dv.getUint32(i, true).toString(16).padStart(8, '0') + '-';
}
this.header['CLSID'] = clsid.slice(0, -1);
// Minor Version (24-25)
this.header['Minor Version'] = dv.getUint16(24, true);
// Major Version (26-27)
this.header['Major Version'] = dv.getUint16(26, true);
// Byte Order (28-29)
this.header['Byte Order'] = '0x' + dv.getUint16(28, true).toString(16);
// Sector Shift (30-31)
const sectorShift = dv.getUint16(30, true);
this.header['Sector Shift'] = sectorShift;
this.header['Sector Size'] = Math.pow(2, sectorShift);
// Mini Sector Shift (32-33)
const miniSectorShift = dv.getUint16(32, true);
this.header['Mini Sector Shift'] = miniSectorShift;
this.header['Mini Sector Size'] = Math.pow(2, miniSectorShift);
// Reserved1 (34-35)
this.header['Reserved1'] = dv.getUint16(34, true);
// Reserved2 (36-39)
this.header['Reserved2'] = dv.getUint32(36, true);
// Number of Directory Sectors (40-43)
this.header['Number of Directory Sectors'] = dv.getUint32(40, true);
// Number of FAT Sectors (44-47)
this.header['Number of FAT Sectors'] = dv.getUint32(44, true);
// First Directory Sector Location (48-51)
this.header['First Directory Sector Location'] = dv.getUint32(48, true);
// Transaction Signature (52-55)
this.header['Transaction Signature'] = dv.getUint32(52, true);
// Mini Stream Cutoff Size (56-59)
this.header['Mini Stream Cutoff Size'] = dv.getUint32(56, true);
// First Mini FAT Sector Location (60-63)
this.header['First Mini FAT Sector Location'] = dv.getUint32(60, true);
// Number of Mini FAT Sectors (64-67)
this.header['Number of Mini FAT Sectors'] = dv.getUint32(64, true);
// First DIFAT Sector Location (68-71)
this.header['First DIFAT Sector Location'] = dv.getUint32(68, true);
// Number of DIFAT Sectors (72-75)
this.header['Number of DIFAT Sectors'] = dv.getUint32(72, true);
// DIFAT Array (76-511)
for (let i = 0; i < 109; i++) {
const offset = 76 + i * 4;
this.difat.push(dv.getUint32(offset, true));
}
}
printProperties() {
console.log('MSO File Properties:');
for (const [key, value] of Object.entries(this.header)) {
console.log(`${key}: ${value}`);
}
console.log('DIFAT Array (first 109 FAT sectors):');
this.difat.forEach((sect, i) => {
console.log(` Entry ${i}: ${sect} (0x${sect.toString(16)})`);
});
}
writeSample(outputPath) {
const buffer = Buffer.alloc(512);
const dv = new DataView(buffer.buffer);
// Signature
const sig = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1];
sig.forEach((b, i) => dv.setUint8(i, b));
// CLSID zero
for (let i = 8; i < 24; i++) dv.setUint8(i, 0);
// Minor ver
dv.setUint16(24, 0x003E, true);
// Major ver
dv.setUint16(26, 3, true);
// Byte order
dv.setUint16(28, 0xFFFE, true);
// Sector shift
dv.setUint16(30, 9, true);
// Mini sector shift
dv.setUint16(32, 6, true);
// Reserved1
dv.setUint16(34, 0, true);
// Reserved2
dv.setUint32(36, 0, true);
// Num dir sectors
dv.setUint32(40, 0, true);
// Num FAT sectors
dv.setUint32(44, 1, true);
// First dir sect
dv.setUint32(48, 0, true);
// Trans sig
dv.setUint32(52, 0, true);
// Mini cutoff
dv.setUint32(56, 4096, true);
// First mini FAT
dv.setUint32(60, 1, true);
// Num mini FAT
dv.setUint32(64, 1, true);
// First DIFAT
dv.setUint32(68, 0xFFFFFFFE, true);
// Num DIFAT
dv.setUint32(72, 0, true);
// DIFAT all ENDOFCHAIN
for (let i = 0; i < 109; i++) {
dv.setUint32(76 + i * 4, 0xFFFFFFFE, true);
}
fs.writeFileSync(outputPath, buffer);
}
}
// Usage example (Node.js)
if (process.argv.length < 3) {
console.log('Usage: node mso_parser.js <path_to_mso_file> [output_path]');
process.exit(1);
}
const parser = new MsoFileParser(process.argv[2]);
parser.readHeader();
parser.printProperties();
if (process.argv[3]) {
parser.writeSample(process.argv[3]);
console.log(`Sample .MSO written to ${process.argv[3]}`);
}
7. C class that can open any file of format .MSO and decode read and write and print to console all the properties from the above list
(Note: C does not have built-in "classes" like object-oriented languages, so this uses a struct with functions for equivalent functionality.)
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h> // For pow
typedef struct {
char filepath[256];
uint8_t header[512];
uint32_t difat[109];
} MsoFileParser;
void read_header(MsoFileParser *parser) {
FILE *fp = fopen(parser->filepath, "rb");
if (fp == NULL) {
perror("Error opening file");
exit(1);
}
size_t read_size = fread(parser->header, 1, 512, fp);
fclose(fp);
if (read_size < 512) {
fprintf(stderr, "File too small to be a valid .MSO/CFB file.\n");
exit(1);
}
// Extract DIFAT
memcpy(parser->difat, &parser->header[76], sizeof(uint32_t) * 109);
}
void print_properties(MsoFileParser *parser) {
printf("MSO File Properties:\n");
// Signature (0-7)
printf("Signature: 0x");
for (int i = 0; i < 8; i++) {
printf("%02x ", parser->header[i]);
}
printf("\n");
// CLSID (8-23)
printf("CLSID: ");
for (int i = 8; i < 24; i += 4) {
uint32_t part = *(uint32_t*)(&parser->header[i]);
printf("%08x-", part);
}
printf("\b\n"); // Remove trailing -
// Minor Version (24-25)
uint16_t minor_ver = *(uint16_t*)(&parser->header[24]);
printf("Minor Version: %u\n", minor_ver);
// Major Version (26-27)
uint16_t major_ver = *(uint16_t*)(&parser->header[26]);
printf("Major Version: %u\n", major_ver);
// Byte Order (28-29)
uint16_t byte_order = *(uint16_t*)(&parser->header[28]);
printf("Byte Order: 0x%04x\n", byte_order);
// Sector Shift (30-31)
uint16_t sector_shift = *(uint16_t*)(&parser->header[30]);
printf("Sector Shift: %u (Sector Size: %d bytes)\n", sector_shift, (int)pow(2, sector_shift));
// Mini Sector Shift (32-33)
uint16_t mini_sector_shift = *(uint16_t*)(&parser->header[32]);
printf("Mini Sector Shift: %u (Mini Sector Size: %d bytes)\n", mini_sector_shift, (int)pow(2, mini_sector_shift));
// Reserved1 (34-35)
uint16_t reserved1 = *(uint16_t*)(&parser->header[34]);
printf("Reserved1: %u\n", reserved1);
// Reserved2 (36-39)
uint32_t reserved2 = *(uint32_t*)(&parser->header[36]);
printf("Reserved2: %u\n", reserved2);
// Number of Directory Sectors (40-43)
uint32_t num_dir_sectors = *(uint32_t*)(&parser->header[40]);
printf("Number of Directory Sectors: %u\n", num_dir_sectors);
// Number of FAT Sectors (44-47)
uint32_t num_fat_sectors = *(uint32_t*)(&parser->header[44]);
printf("Number of FAT Sectors: %u\n", num_fat_sectors);
// First Directory Sector Location (48-51)
uint32_t first_dir_sect = *(uint32_t*)(&parser->header[48]);
printf("First Directory Sector Location: %u\n", first_dir_sect);
// Transaction Signature (52-55)
uint32_t trans_sig = *(uint32_t*)(&parser->header[52]);
printf("Transaction Signature: %u\n", trans_sig);
// Mini Stream Cutoff Size (56-59)
uint32_t mini_cutoff = *(uint32_t*)(&parser->header[56]);
printf("Mini Stream Cutoff Size: %u\n", mini_cutoff);
// First Mini FAT Sector Location (60-63)
uint32_t first_mini_fat = *(uint32_t*)(&parser->header[60]);
printf("First Mini FAT Sector Location: %u\n", first_mini_fat);
// Number of Mini FAT Sectors (64-67)
uint32_t num_mini_fat = *(uint32_t*)(&parser->header[64]);
printf("Number of Mini FAT Sectors: %u\n", num_mini_fat);
// First DIFAT Sector Location (68-71)
uint32_t first_difat = *(uint32_t*)(&parser->header[68]);
printf("First DIFAT Sector Location: %u\n", first_difat);
// Number of DIFAT Sectors (72-75)
uint32_t num_difat = *(uint32_t*)(&parser->header[72]);
printf("Number of DIFAT Sectors: %u\n", num_difat);
// DIFAT Array
printf("DIFAT Array (first 109 FAT sectors):\n");
for (int i = 0; i < 109; i++) {
printf(" Entry %d: %u (0x%x)\n", i, parser->difat[i], parser->difat[i]);
}
}
void write_sample(MsoFileParser *parser, const char *output_path) {
uint8_t header[512] = {0};
// Signature
uint8_t sig[] = {0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1};
memcpy(header, sig, 8);
// CLSID zero (8-23)
// Minor ver (24-25)
*(uint16_t*)(&header[24]) = 0x003E;
// Major ver (26-27)
*(uint16_t*)(&header[26]) = 3;
// Byte order (28-29)
*(uint16_t*)(&header[28]) = 0xFFFE;
// Sector shift (30-31)
*(uint16_t*)(&header[30]) = 9;
// Mini sector shift (32-33)
*(uint16_t*)(&header[32]) = 6;
// Reserved1 (34-35)
*(uint16_t*)(&header[34]) = 0;
// Reserved2 (36-39)
*(uint32_t*)(&header[36]) = 0;
// Num dir sectors (40-43)
*(uint32_t*)(&header[40]) = 0;
// Num FAT sectors (44-47)
*(uint32_t*)(&header[44]) = 1;
// First dir sect (48-51)
*(uint32_t*)(&header[48]) = 0;
// Trans sig (52-55)
*(uint32_t*)(&header[52]) = 0;
// Mini cutoff (56-59)
*(uint32_t*)(&header[56]) = 4096;
// First mini FAT (60-63)
*(uint32_t*)(&header[60]) = 1;
// Num mini FAT (64-67)
*(uint32_t*)(&header[64]) = 1;
// First DIFAT (68-71)
*(uint32_t*)(&header[68]) = 0xFFFFFFFE;
// Num DIFAT (72-75)
*(uint32_t*)(&header[72]) = 0;
// DIFAT all ENDOFCHAIN (76-511)
for (int i = 0; i < 109; i++) {
*(uint32_t*)(&header[76 + i * 4]) = 0xFFFFFFFE;
}
FILE *fp = fopen(output_path, "wb");
if (fp == NULL) {
perror("Error writing file");
exit(1);
}
fwrite(header, 1, 512, fp);
fclose(fp);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <path_to_mso_file> [output_path]\n", argv[0]);
return 1;
}
MsoFileParser parser;
strcpy(parser.filepath, argv[1]);
read_header(&parser);
print_properties(&parser);
if (argc > 2) {
write_sample(&parser, argv[2]);
printf("Sample .MSO written to %s\n", argv[2]);
}
return 0;
}