Task 716: .SYMBOLICLINK File Format

Task 716: .SYMBOLICLINK File Format

Upon thorough investigation, no standard file format specifically associated with the extension ".SYMBOLICLINK" was identified in common file systems such as NTFS, ext4, or others. However, the closest and most relevant structure is the Symbolic Link Reparse Data Buffer used in the NTFS file system on Windows, which defines the internal format for symbolic links via reparse points. This is a binary format intrinsic to NTFS, enabling redirection to target paths. The properties (fields) of this format are as follows:

  • ReparseTag: A 32-bit unsigned integer identifying the reparse point type. Must be 0xA000000C for symbolic links.
  • ReparseDataLength: A 16-bit unsigned integer specifying the length in bytes of the reparse data following the common header (includes the PathBuffer and additional fixed fields).
  • Reserved: A 16-bit unsigned integer reserved for future use. Should be set to 0 and ignored on read.
  • SubstituteNameOffset: A 16-bit unsigned integer indicating the byte offset to the substitute name string within the PathBuffer (divided by 2 for UTF-16 indexing).
  • SubstituteNameLength: A 16-bit unsigned integer specifying the length in bytes of the substitute name string (excludes null terminator if present).
  • PrintNameOffset: A 16-bit unsigned integer indicating the byte offset to the print name string within the PathBuffer (divided by 2 for UTF-16 indexing).
  • PrintNameLength: A 16-bit unsigned integer specifying the length in bytes of the print name string (excludes null terminator if present).
  • Flags: A 32-bit unsigned integer indicating whether the substitute name is absolute (0x00000000) or relative (0x00000001, SYMLINK_FLAG_RELATIVE).
  • SubstituteName: A UTF-16LE encoded string representing the functional target path of the symbolic link.
  • PrintName: A UTF-16LE encoded string representing a user-friendly display name for the target path.

These properties are encoded in a fixed 20-byte header followed by a variable-length PathBuffer array containing the strings. The format supports null-terminated strings, but lengths exclude the terminator. This structure is specific to NTFS and accessed via file system APIs like DeviceIoControl.

No direct download links for files with the ".SYMBOLICLINK" extension were located, as this does not appear to be a standard or recognized file extension for symbolic link data. Searches across web resources, including GitHub repositories and documentation sites, yielded no such files. Symbolic link data is typically embedded within the file system (e.g., NTFS reparse points) rather than stored as standalone files with this extension. If such files exist in proprietary contexts, they were not publicly available.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop File Dump

The following is a self-contained HTML page with embedded JavaScript that allows users to drag and drop a file assumed to contain the raw Symbolic Link Reparse Data Buffer. It parses the binary data and displays all properties on the screen. This can be embedded in a Ghost blog post or similar platform.

SYMBOLICLINK File Parser
Drag and drop a .SYMBOLICLINK file here
import struct
import sys

class SymbolicLinkParser:
    def __init__(self, filename=None):
        self.reparse_tag = None
        self.reparse_data_length = None
        self.reserved = None
        self.substitute_name_offset = None
        self.substitute_name_length = None
        self.print_name_offset = None
        self.print_name_length = None
        self.flags = None
        self.substitute_name = None
        self.print_name = None
        if filename:
            self.read(filename)

    def read(self, filename):
        with open(filename, 'rb') as f:
            data = f.read()
        if len(data) < 20:
            raise ValueError("File too small for header")
        (self.reparse_tag,
         self.reparse_data_length,
         self.reserved,
         self.substitute_name_offset,
         self.substitute_name_length,
         self.print_name_offset,
         self.print_name_length,
         self.flags) = struct.unpack_from('<IHHHHHHI', data, 0)
        if self.reparse_tag != 0xA000000C:
            raise ValueError("Invalid ReparseTag")
        path_buffer = data[20:]
        sub_start = self.substitute_name_offset
        sub_end = sub_start + self.substitute_name_length
        print_start = self.print_name_offset
        print_end = print_start + self.print_name_length
        self.substitute_name = path_buffer[sub_start:sub_end].decode('utf-16le')
        self.print_name = path_buffer[print_start:print_end].decode('utf-16le')

    def print_properties(self):
        print(f"ReparseTag: 0x{self.reparse_tag:08X}")
        print(f"ReparseDataLength: {self.reparse_data_length}")
        print(f"Reserved: {self.reserved}")
        print(f"SubstituteNameOffset: {self.substitute_name_offset}")
        print(f"SubstituteNameLength: {self.substitute_name_length}")
        print(f"PrintNameOffset: {self.print_name_offset}")
        print(f"PrintNameLength: {self.print_name_length}")
        print(f"Flags: 0x{self.flags:08X}")
        print(f"SubstituteName: {self.substitute_name}")
        print(f"PrintName: {self.print_name}")

    def write(self, filename):
        if any(v is None for v in [self.reparse_tag, self.substitute_name, self.print_name]):
            raise ValueError("All properties must be set before writing")
        sub_bytes = self.substitute_name.encode('utf-16le')
        print_bytes = self.print_name.encode('utf-16le')
        self.substitute_name_length = len(sub_bytes)
        self.print_name_length = len(print_bytes)
        # Assume substitute first, then print
        self.substitute_name_offset = 0
        self.print_name_offset = self.substitute_name_length
        path_buffer = sub_bytes + print_bytes
        self.reparse_data_length = len(path_buffer) + 12  # 12 for the four UINT16s and Flags
        header = struct.pack('<IHHHHHHI',
                             self.reparse_tag,
                             self.reparse_data_length,
                             self.reserved or 0,
                             self.substitute_name_offset,
                             self.substitute_name_length,
                             self.print_name_offset,
                             self.print_name_length,
                             self.flags)
        with open(filename, 'wb') as f:
            f.write(header + path_buffer)

# Example usage: if sys.argv[1] is provided, read and print; else set properties and write
if len(sys.argv) > 1:
    parser = SymbolicLinkParser(sys.argv[1])
    parser.print_properties()
else:
    parser = SymbolicLinkParser()
    parser.reparse_tag = 0xA000000C
    parser.flags = 0
    parser.substitute_name = r"\??\C:\Target"
    parser.print_name = "Target"
    parser.write("output.symboliclink")
import java.io.*;
import java.nio.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;

public class SymbolicLinkParser {
    private long reparseTag;
    private int reparseDataLength;
    private int reserved;
    private int substituteNameOffset;
    private int substituteNameLength;
    private int printNameOffset;
    private int printNameLength;
    private long flags;
    private String substituteName;
    private String printName;

    public SymbolicLinkParser(String filename) throws IOException {
        read(filename);
    }

    public SymbolicLinkParser() {}

    public void read(String filename) throws IOException {
        byte[] data = Files.readAllBytes(Paths.get(filename));
        if (data.length < 20) {
            throw new IOException("File too small for header");
        }
        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
        reparseTag = buffer.getInt() & 0xFFFFFFFFL;
        reparseDataLength = buffer.getShort() & 0xFFFF;
        reserved = buffer.getShort() & 0xFFFF;
        substituteNameOffset = buffer.getShort() & 0xFFFF;
        substituteNameLength = buffer.getShort() & 0xFFFF;
        printNameOffset = buffer.getShort() & 0xFFFF;
        printNameLength = buffer.getShort() & 0xFFFF;
        flags = buffer.getInt() & 0xFFFFFFFFL;
        if (reparseTag != 0xA000000CL) {
            throw new IOException("Invalid ReparseTag");
        }
        byte[] pathBuffer = new byte[data.length - 20];
        System.arraycopy(data, 20, pathBuffer, 0, pathBuffer.length);
        substituteName = new String(pathBuffer, substituteNameOffset, substituteNameLength, StandardCharsets.UTF_16LE);
        printName = new String(pathBuffer, printNameOffset, printNameLength, StandardCharsets.UTF_16LE);
    }

    public void printProperties() {
        System.out.printf("ReparseTag: 0x%08X%n", reparseTag);
        System.out.printf("ReparseDataLength: %d%n", reparseDataLength);
        System.out.printf("Reserved: %d%n", reserved);
        System.out.printf("SubstituteNameOffset: %d%n", substituteNameOffset);
        System.out.printf("SubstituteNameLength: %d%n", substituteNameLength);
        System.out.printf("PrintNameOffset: %d%n", printNameOffset);
        System.out.printf("PrintNameLength: %d%n", printNameLength);
        System.out.printf("Flags: 0x%08X%n", flags);
        System.out.printf("SubstituteName: %s%n", substituteName);
        System.out.printf("PrintName: %s%n", printName);
    }

    public void write(String filename) throws IOException {
        if (substituteName == null || printName == null || reparseTag == 0) {
            throw new IllegalStateException("All properties must be set before writing");
        }
        byte[] subBytes = substituteName.getBytes(StandardCharsets.UTF_16LE);
        byte[] printBytes = printName.getBytes(StandardCharsets.UTF_16LE);
        substituteNameLength = subBytes.length;
        printNameLength = printBytes.length;
        substituteNameOffset = 0;
        printNameOffset = substituteNameLength;
        byte[] pathBuffer = new byte[subBytes.length + printBytes.length];
        System.arraycopy(subBytes, 0, pathBuffer, 0, subBytes.length);
        System.arraycopy(printBytes, 0, pathBuffer, subBytes.length, printBytes.length);
        reparseDataLength = pathBuffer.length + 12;
        ByteBuffer header = ByteBuffer.allocate(20).order(ByteOrder.LITTLE_ENDIAN);
        header.putInt((int) reparseTag);
        header.putShort((short) reparseDataLength);
        header.putShort((short) (reserved));
        header.putShort((short) substituteNameOffset);
        header.putShort((short) substituteNameLength);
        header.putShort((short) printNameOffset);
        header.putShort((short) printNameLength);
        header.putInt((int) flags);
        try (FileOutputStream fos = new FileOutputStream(filename)) {
            fos.write(header.array());
            fos.write(pathBuffer);
        }
    }

    // Example usage
    public static void main(String[] args) throws IOException {
        if (args.length > 0) {
            SymbolicLinkParser parser = new SymbolicLinkParser(args[0]);
            parser.printProperties();
        } else {
            SymbolicLinkParser parser = new SymbolicLinkParser();
            parser.reparseTag = 0xA000000CL;
            parser.flags = 0;
            parser.substituteName = "\\??\\C:\\Target";
            parser.printName = "Target";
            parser.write("output.symboliclink");
        }
    }
}
const fs = require('fs'); // For Node.js environment

class SymbolicLinkParser {
    constructor(filename = null) {
        this.reparseTag = null;
        this.reparseDataLength = null;
        this.reserved = null;
        this.substituteNameOffset = null;
        this.substituteNameLength = null;
        this.printNameOffset = null;
        this.printNameLength = null;
        this.flags = null;
        this.substituteName = null;
        this.printName = null;
        if (filename) {
            this.read(filename);
        }
    }

    read(filename) {
        const data = fs.readFileSync(filename);
        if (data.length < 20) {
            throw new Error('File too small for header');
        }
        const view = new DataView(data.buffer);
        this.reparseTag = view.getUint32(0, true);
        this.reparseDataLength = view.getUint16(4, true);
        this.reserved = view.getUint16(6, true);
        this.substituteNameOffset = view.getUint16(8, true);
        this.substituteNameLength = view.getUint16(10, true);
        this.printNameOffset = view.getUint16(12, true);
        this.printNameLength = view.getUint16(14, true);
        this.flags = view.getUint32(16, true);
        if (this.reparseTag !== 0xA000000C) {
            throw new Error('Invalid ReparseTag');
        }
        const pathBuffer = data.subarray(20);
        const decoder = new TextDecoder('utf-16le');
        this.substituteName = decoder.decode(pathBuffer.subarray(this.substituteNameOffset, this.substituteNameOffset + this.substituteNameLength));
        this.printName = decoder.decode(pathBuffer.subarray(this.printNameOffset, this.printNameOffset + this.printNameLength));
    }

    printProperties() {
        console.log(`ReparseTag: 0x${this.reparseTag.toString(16).toUpperCase()}`);
        console.log(`ReparseDataLength: ${this.reparseDataLength}`);
        console.log(`Reserved: ${this.reserved}`);
        console.log(`SubstituteNameOffset: ${this.substituteNameOffset}`);
        console.log(`SubstituteNameLength: ${this.substituteNameLength}`);
        console.log(`PrintNameOffset: ${this.printNameOffset}`);
        console.log(`PrintNameLength: ${this.printNameLength}`);
        console.log(`Flags: 0x${this.flags.toString(16).toUpperCase()}`);
        console.log(`SubstituteName: ${this.substituteName}`);
        console.log(`PrintName: ${this.printName}`);
    }

    write(filename) {
        if (!this.substituteName || !this.printName || !this.reparseTag) {
            throw new Error('All properties must be set before writing');
        }
        const encoder = new TextEncoder('utf-16le');
        const subBytes = encoder.encode(this.substituteName);
        const printBytes = encoder.encode(this.printName);
        this.substituteNameLength = subBytes.length;
        this.printNameLength = printBytes.length;
        this.substituteNameOffset = 0;
        this.printNameOffset = this.substituteNameLength;
        const pathBuffer = new Uint8Array(subBytes.length + printBytes.length);
        pathBuffer.set(subBytes, 0);
        pathBuffer.set(printBytes, subBytes.length);
        this.reparseDataLength = pathBuffer.length + 12;
        const header = new ArrayBuffer(20);
        const headerView = new DataView(header);
        headerView.setUint32(0, this.reparseTag, true);
        headerView.setUint16(4, this.reparseDataLength, true);
        headerView.setUint16(6, this.reserved || 0, true);
        headerView.setUint16(8, this.substituteNameOffset, true);
        headerView.setUint16(10, this.substituteNameLength, true);
        headerView.setUint16(12, this.printNameOffset, true);
        headerView.setUint16(14, this.printNameLength, true);
        headerView.setUint32(16, this.flags, true);
        const fullBuffer = new Uint8Array(header.byteLength + pathBuffer.length);
        fullBuffer.set(new Uint8Array(header), 0);
        fullBuffer.set(pathBuffer, header.byteLength);
        fs.writeFileSync(filename, fullBuffer);
    }
}

// Example usage: node script.js input.symboliclink
if (process.argv.length > 2) {
    const parser = new SymbolicLinkParser(process.argv[2]);
    parser.printProperties();
} else {
    const parser = new SymbolicLinkParser();
    parser.reparseTag = 0xA000000C;
    parser.flags = 0;
    parser.substituteName = '\\??\\C:\\Target';
    parser.printName = 'Target';
    parser.write('output.symboliclink');
}
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstdint>
#include <cstring>

class SymbolicLinkParser {
private:
    uint32_t reparseTag;
    uint16_t reparseDataLength;
    uint16_t reserved;
    uint16_t substituteNameOffset;
    uint16_t substituteNameLength;
    uint16_t printNameOffset;
    uint16_t printNameLength;
    uint32_t flags;
    std::wstring substituteName;
    std::wstring printName;

public:
    SymbolicLinkParser(const std::string& filename = "") {
        if (!filename.empty()) {
            read(filename);
        }
    }

    void read(const std::string& filename) {
        std::ifstream file(filename, std::ios::binary | std::ios::ate);
        if (!file) {
            throw std::runtime_error("Unable to open file");
        }
        std::streamsize size = file.tellg();
        file.seekg(0, std::ios::beg);
        std::vector<char> data(size);
        if (!file.read(data.data(), size)) {
            throw std::runtime_error("Error reading file");
        }
        if (size < 20) {
            throw std::runtime_error("File too small for header");
        }
        memcpy(&reparseTag, data.data() + 0, sizeof(uint32_t));
        memcpy(&reparseDataLength, data.data() + 4, sizeof(uint16_t));
        memcpy(&reserved, data.data() + 6, sizeof(uint16_t));
        memcpy(&substituteNameOffset, data.data() + 8, sizeof(uint16_t));
        memcpy(&substituteNameLength, data.data() + 10, sizeof(uint16_t));
        memcpy(&printNameOffset, data.data() + 12, sizeof(uint16_t));
        memcpy(&printNameLength, data.data() + 14, sizeof(uint16_t));
        memcpy(&flags, data.data() + 16, sizeof(uint32_t));
        if (reparseTag != 0xA000000C) {
            throw std::runtime_error("Invalid ReparseTag");
        }
        char* pathBuffer = data.data() + 20;
        substituteName = std::wstring(reinterpret_cast<wchar_t*>(pathBuffer + substituteNameOffset), substituteNameLength / 2);
        printName = std::wstring(reinterpret_cast<wchar_t*>(pathBuffer + printNameOffset), printNameLength / 2);
    }

    void printProperties() const {
        std::cout << "ReparseTag: 0x" << std::hex << reparseTag << std::dec << std::endl;
        std::cout << "ReparseDataLength: " << reparseDataLength << std::endl;
        std::cout << "Reserved: " << reserved << std::endl;
        std::cout << "SubstituteNameOffset: " << substituteNameOffset << std::endl;
        std::cout << "SubstituteNameLength: " << substituteNameLength << std::endl;
        std::cout << "PrintNameOffset: " << printNameOffset << std::endl;
        std::cout << "PrintNameLength: " << printNameLength << std::endl;
        std::cout << "Flags: 0x" << std::hex << flags << std::dec << std::endl;
        std::wcout << L"SubstituteName: " << substituteName << std::endl;
        std::wcout << L"PrintName: " << printName << std::endl;
    }

    void write(const std::string& filename) {
        if (substituteName.empty() || printName.empty() || reparseTag == 0) {
            throw std::runtime_error("All properties must be set before writing");
        }
        std::vector<char> subBytes(reinterpret_cast<const char*>(substituteName.data()), reinterpret_cast<const char*>(substituteName.data()) + substituteName.size() * 2);
        std::vector<char> printBytes(reinterpret_cast<const char*>(printName.data()), reinterpret_cast<const char*>(printName.data()) + printName.size() * 2);
        substituteNameLength = subBytes.size();
        printNameLength = printBytes.size();
        substituteNameOffset = 0;
        printNameOffset = substituteNameLength;
        std::vector<char> pathBuffer;
        pathBuffer.insert(pathBuffer.end(), subBytes.begin(), subBytes.end());
        pathBuffer.insert(pathBuffer.end(), printBytes.begin(), printBytes.end());
        reparseDataLength = pathBuffer.size() + 12;
        std::vector<char> header(20);
        memcpy(header.data() + 0, &reparseTag, sizeof(uint32_t));
        memcpy(header.data() + 4, &reparseDataLength, sizeof(uint16_t));
        memcpy(header.data() + 6, &reserved, sizeof(uint16_t));
        memcpy(header.data() + 8, &substituteNameOffset, sizeof(uint16_t));
        memcpy(header.data() + 10, &substituteNameLength, sizeof(uint16_t));
        memcpy(header.data() + 12, &printNameOffset, sizeof(uint16_t));
        memcpy(header.data() + 14, &printNameLength, sizeof(uint16_t));
        memcpy(header.data() + 16, &flags, sizeof(uint32_t));
        std::ofstream file(filename, std::ios::binary);
        if (!file) {
            throw std::runtime_error("Unable to write file");
        }
        file.write(header.data(), header.size());
        file.write(pathBuffer.data(), pathBuffer.size());
    }
};

// Example usage: ./program input.symboliclink
int main(int argc, char* argv[]) {
    try {
        if (argc > 1) {
            SymbolicLinkParser parser(argv[1]);
            parser.printProperties();
        } else {
            SymbolicLinkParser parser;
            parser.reparseTag = 0xA000000C;
            parser.flags = 0;
            parser.substituteName = L"\\??\\C:\\Target";
            parser.printName = L"Target";
            parser.write("output.symboliclink");
        }
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
    return 0;
}