Task 716: .SYMBOLICLINK File Format
Task 716: .SYMBOLICLINK File Format
1. List of Properties of the .SYMBOLICLINK File Format Intrinsic to Its File System
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.
2. Two Direct Download Links for Files of Format .SYMBOLICLINK
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.
4. Python Class for Handling .SYMBOLICLINK Files
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")
5. Java Class for Handling .SYMBOLICLINK Files
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");
}
}
}
6. JavaScript Class for Handling .SYMBOLICLINK Files
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');
}
7. C Class (Implemented as C++ Class) for Handling .SYMBOLICLINK Files
#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;
}