mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 08:47:00 +00:00
589 lines
20 KiB
TypeScript
589 lines
20 KiB
TypeScript
/** Synthesize Shell Link bytes for parser, CLI, and MCP tests. */
|
|
|
|
export const SHELL_LINK_CLSID = Buffer.from([
|
|
0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x46,
|
|
]);
|
|
|
|
export const MY_COMPUTER_CLSID = Buffer.from([
|
|
0xe0, 0x4f, 0xd0, 0x20, 0xea, 0x3a, 0x69, 0x10, 0xa2, 0xd8, 0x08, 0x00, 0x2b,
|
|
0x30, 0x30, 0x9d,
|
|
]);
|
|
|
|
/** 2024-01-15T12:00:00.000Z as FILETIME 100-nanosecond intervals. */
|
|
export const FIXTURE_FILETIME = 133497936000000000n;
|
|
|
|
export const LINK_FLAG = {
|
|
HasLinkTargetIDList: 0x00000001,
|
|
HasLinkInfo: 0x00000002,
|
|
HasName: 0x00000004,
|
|
HasRelativePath: 0x00000008,
|
|
HasWorkingDir: 0x00000010,
|
|
HasArguments: 0x00000020,
|
|
HasIconLocation: 0x00000040,
|
|
IsUnicode: 0x00000080,
|
|
HasExpString: 0x00000200,
|
|
HasExpIcon: 0x00004000,
|
|
} as const;
|
|
|
|
export const EXTRA_SIGNATURE = {
|
|
EnvironmentVariable: 0xa0000001,
|
|
Tracker: 0xa0000003,
|
|
ConsoleFE: 0xa0000004,
|
|
SpecialFolder: 0xa0000005,
|
|
IconEnvironment: 0xa0000007,
|
|
PropertyStore: 0xa0000009,
|
|
KnownFolder: 0xa000000b,
|
|
} as const;
|
|
|
|
export const TRACKER_MAC = Buffer.from([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
|
|
/** UUID timestamp 100ns since 1582-10-15 matching 2024-01-15T12:00:00.000Z. */
|
|
export const TRACKER_UUID_TIMESTAMP = 139289664000000000n;
|
|
|
|
export type FixtureIdListItem =
|
|
| {
|
|
readonly kind: "root";
|
|
readonly sortIndex?: number;
|
|
readonly folderId?: Buffer;
|
|
}
|
|
| { readonly kind: "volume"; readonly name: string }
|
|
| {
|
|
readonly kind: "file";
|
|
readonly classType?: number;
|
|
readonly fileSize?: number;
|
|
readonly shortName: string;
|
|
}
|
|
| { readonly kind: "raw"; readonly data: Buffer };
|
|
|
|
export type FixtureLinkInfo = {
|
|
readonly driveType?: number;
|
|
readonly serial?: number;
|
|
readonly label?: string;
|
|
/** Bytes between the 16-byte header and an ANSI label. */
|
|
readonly volumeLabelGap?: Buffer;
|
|
readonly localBasePath?: string;
|
|
readonly commonPathSuffix?: string;
|
|
readonly netName?: string;
|
|
readonly deviceName?: string;
|
|
readonly networkProviderType?: number;
|
|
readonly networkFlags?: number;
|
|
};
|
|
|
|
export type FixtureExtraBlock =
|
|
| {
|
|
readonly kind: "environment";
|
|
readonly ansi?: string;
|
|
readonly unicode?: string;
|
|
}
|
|
| {
|
|
readonly kind: "icon_environment";
|
|
readonly ansi?: string;
|
|
readonly unicode?: string;
|
|
}
|
|
| {
|
|
readonly kind: "tracker";
|
|
readonly length?: number;
|
|
readonly version?: number;
|
|
readonly machineId?: string;
|
|
readonly droid?: readonly [Buffer, Buffer];
|
|
readonly birthDroid?: readonly [Buffer, Buffer];
|
|
}
|
|
| {
|
|
readonly kind: "known_folder";
|
|
readonly folderId?: Buffer;
|
|
readonly itemOffset?: number;
|
|
}
|
|
| {
|
|
readonly kind: "special_folder";
|
|
readonly folderId?: number;
|
|
readonly itemOffset?: number;
|
|
}
|
|
| { readonly kind: "console_fe"; readonly codePage?: number }
|
|
| { readonly kind: "property_store"; readonly payload?: Buffer }
|
|
| {
|
|
readonly kind: "unknown";
|
|
readonly signature: number;
|
|
readonly payload?: Buffer;
|
|
};
|
|
|
|
export type WindowsShortcutFixtureOptions = {
|
|
readonly headerSize?: number;
|
|
readonly clsid?: Buffer;
|
|
readonly flags?: number;
|
|
readonly fileAttributes?: number;
|
|
readonly creationTime?: bigint;
|
|
readonly accessTime?: bigint;
|
|
readonly writeTime?: bigint;
|
|
readonly fileSize?: number;
|
|
readonly iconIndex?: number;
|
|
readonly showCommand?: number;
|
|
readonly hotKey?: number;
|
|
readonly unicode?: boolean;
|
|
readonly idList?: readonly FixtureIdListItem[] | null;
|
|
readonly linkInfo?: FixtureLinkInfo | null;
|
|
readonly name?: string;
|
|
readonly relativePath?: string;
|
|
readonly workingDir?: string;
|
|
readonly arguments?: string;
|
|
readonly iconLocation?: string;
|
|
readonly extraData?: readonly FixtureExtraBlock[];
|
|
readonly terminalBlock?: boolean;
|
|
readonly truncateAt?: number;
|
|
readonly omitStringBytes?: number;
|
|
};
|
|
|
|
const ansiZ = (value: string): Buffer =>
|
|
Buffer.concat([Buffer.from(value, "latin1"), Buffer.from([0])]);
|
|
|
|
const utf16z = (value: string): Buffer =>
|
|
Buffer.concat([Buffer.from(value, "utf16le"), Buffer.alloc(2)]);
|
|
|
|
const padField = (
|
|
value: string,
|
|
bytes: number,
|
|
encoding: "latin1" | "utf16le",
|
|
) => {
|
|
const field = Buffer.alloc(bytes);
|
|
Buffer.from(value, encoding).copy(field);
|
|
return field;
|
|
};
|
|
|
|
const writeU32 = (target: Buffer, offset: number, value: number): void => {
|
|
target.writeUInt32LE(value >>> 0, offset);
|
|
};
|
|
|
|
const writeU64 = (target: Buffer, offset: number, value: bigint): void => {
|
|
target.writeBigUInt64LE(value, offset);
|
|
};
|
|
|
|
/** Windows GUID bytes for a version-1 UUID with the supplied node and timestamp. */
|
|
export const uuidV1Bytes = (
|
|
timestamp100ns: bigint,
|
|
clockSeq: number,
|
|
node: Buffer,
|
|
): Buffer => {
|
|
const bytes = Buffer.alloc(16);
|
|
bytes.writeUInt32LE(Number(timestamp100ns & 0xffffffffn), 0);
|
|
bytes.writeUInt16LE(Number((timestamp100ns >> 32n) & 0xffffn), 4);
|
|
bytes.writeUInt16LE(Number((timestamp100ns >> 48n) & 0x0fffn) | 0x1000, 6);
|
|
bytes[8] = ((clockSeq >> 8) & 0x3f) | 0x80;
|
|
bytes[9] = clockSeq & 0xff;
|
|
node.copy(bytes, 10, 0, 6);
|
|
return bytes;
|
|
};
|
|
|
|
const defaultTrackerGuid = (clockSeq: number): Buffer =>
|
|
uuidV1Bytes(TRACKER_UUID_TIMESTAMP, clockSeq, TRACKER_MAC);
|
|
|
|
const itemBytes = (item: FixtureIdListItem): Buffer => {
|
|
if (item.kind === "raw") {
|
|
const size = Buffer.alloc(2);
|
|
size.writeUInt16LE(item.data.length + 2, 0);
|
|
return Buffer.concat([size, item.data]);
|
|
}
|
|
if (item.kind === "root") {
|
|
const data = Buffer.alloc(18);
|
|
data[0] = 0x1f;
|
|
data[1] = item.sortIndex ?? 0x50;
|
|
(item.folderId ?? MY_COMPUTER_CLSID).copy(data, 2);
|
|
const size = Buffer.alloc(2);
|
|
size.writeUInt16LE(20, 0);
|
|
return Buffer.concat([size, data]);
|
|
}
|
|
if (item.kind === "volume") {
|
|
const name = Buffer.from(`${item.name}\0`, "latin1");
|
|
const data = Buffer.concat([Buffer.from([0x2f]), name]);
|
|
const size = Buffer.alloc(2);
|
|
size.writeUInt16LE(data.length + 2, 0);
|
|
return Buffer.concat([size, data]);
|
|
}
|
|
const shortName = Buffer.from(`${item.shortName}\0`, "latin1");
|
|
const padded =
|
|
shortName.length % 2 === 0
|
|
? shortName
|
|
: Buffer.concat([shortName, Buffer.from([0])]);
|
|
const data = Buffer.alloc(12 + padded.length);
|
|
data[0] = item.classType ?? 0x32;
|
|
data.writeUInt32LE(item.fileSize ?? 0, 2);
|
|
padded.copy(data, 12);
|
|
const size = Buffer.alloc(2);
|
|
size.writeUInt16LE(data.length + 2, 0);
|
|
return Buffer.concat([size, data]);
|
|
};
|
|
|
|
const idListBytes = (items: readonly FixtureIdListItem[]): Buffer => {
|
|
const body = Buffer.concat([...items.map(itemBytes), Buffer.alloc(2)]);
|
|
const size = Buffer.alloc(2);
|
|
size.writeUInt16LE(body.length, 0);
|
|
return Buffer.concat([size, body]);
|
|
};
|
|
|
|
const volumeIdBytes = (info: FixtureLinkInfo, unicode: boolean): Buffer => {
|
|
const label = unicode
|
|
? utf16z(info.label ?? "VOL")
|
|
: ansiZ(info.label ?? "VOL");
|
|
const gap = info.volumeLabelGap ?? Buffer.alloc(0);
|
|
const header = unicode ? 20 : 16 + gap.length;
|
|
const bytes = Buffer.alloc(header + label.length);
|
|
writeU32(bytes, 0, bytes.length);
|
|
writeU32(bytes, 4, info.driveType ?? 3);
|
|
writeU32(bytes, 8, info.serial ?? 0x1234abcd);
|
|
writeU32(bytes, 12, unicode ? 0x14 : header);
|
|
if (unicode) writeU32(bytes, 16, header);
|
|
else gap.copy(bytes, 16);
|
|
label.copy(bytes, header);
|
|
return bytes;
|
|
};
|
|
|
|
const networkLinkBytes = (info: FixtureLinkInfo, unicode: boolean): Buffer => {
|
|
const netAnsi = ansiZ(info.netName ?? "\\\\server\\share");
|
|
const deviceAnsi = ansiZ(info.deviceName ?? "Z:");
|
|
const netWide = unicode
|
|
? utf16z(info.netName ?? "\\\\server\\share")
|
|
: Buffer.alloc(0);
|
|
const deviceWide = unicode
|
|
? utf16z(info.deviceName ?? "Z:")
|
|
: Buffer.alloc(0);
|
|
const header = unicode ? 0x1c : 0x14;
|
|
const deviceAnsiOffset = header + netAnsi.length;
|
|
const netWideOffset = deviceAnsiOffset + deviceAnsi.length;
|
|
const deviceWideOffset = netWideOffset + netWide.length;
|
|
const bytes = Buffer.alloc(deviceWideOffset + deviceWide.length);
|
|
writeU32(bytes, 0, bytes.length);
|
|
writeU32(bytes, 4, info.networkFlags ?? 0x3);
|
|
writeU32(bytes, 8, header);
|
|
writeU32(bytes, 12, deviceAnsiOffset);
|
|
writeU32(bytes, 16, info.networkProviderType ?? 0x00020000);
|
|
if (unicode) {
|
|
writeU32(bytes, 20, netWideOffset);
|
|
writeU32(bytes, 24, deviceWideOffset);
|
|
}
|
|
netAnsi.copy(bytes, header);
|
|
deviceAnsi.copy(bytes, deviceAnsiOffset);
|
|
netWide.copy(bytes, netWideOffset);
|
|
deviceWide.copy(bytes, deviceWideOffset);
|
|
return bytes;
|
|
};
|
|
|
|
const linkInfoBytes = (info: FixtureLinkInfo, unicode: boolean): Buffer => {
|
|
const hasVolume =
|
|
info.localBasePath !== undefined || info.label !== undefined;
|
|
const hasNetwork =
|
|
info.netName !== undefined || info.deviceName !== undefined;
|
|
const flags = (hasVolume ? 0x1 : 0) | (hasNetwork ? 0x2 : 0) || 0x1;
|
|
const headerSize = unicode ? 0x24 : 0x1c;
|
|
const volume = flags & 0x1 ? volumeIdBytes(info, unicode) : Buffer.alloc(0);
|
|
const localAnsi =
|
|
flags & 0x1
|
|
? ansiZ(info.localBasePath ?? "C:\\target.exe")
|
|
: Buffer.alloc(0);
|
|
const network =
|
|
flags & 0x2 ? networkLinkBytes(info, unicode) : Buffer.alloc(0);
|
|
const suffixAnsi = ansiZ(info.commonPathSuffix ?? "");
|
|
const localWide =
|
|
unicode && flags & 0x1
|
|
? utf16z(info.localBasePath ?? "C:\\target.exe")
|
|
: Buffer.alloc(0);
|
|
const suffixWide = unicode
|
|
? utf16z(info.commonPathSuffix ?? "")
|
|
: Buffer.alloc(0);
|
|
let cursor = headerSize;
|
|
const volumeOffset = flags & 0x1 ? cursor : 0;
|
|
if (flags & 0x1) cursor += volume.length;
|
|
const localOffset = flags & 0x1 ? cursor : 0;
|
|
if (flags & 0x1) cursor += localAnsi.length;
|
|
const networkOffset = flags & 0x2 ? cursor : 0;
|
|
if (flags & 0x2) cursor += network.length;
|
|
const suffixOffset = cursor;
|
|
cursor += suffixAnsi.length;
|
|
const localWideOffset = unicode && flags & 0x1 ? cursor : 0;
|
|
if (unicode && flags & 0x1) cursor += localWide.length;
|
|
const suffixWideOffset = unicode ? cursor : 0;
|
|
if (unicode) cursor += suffixWide.length;
|
|
const bytes = Buffer.alloc(cursor);
|
|
writeU32(bytes, 0, cursor);
|
|
writeU32(bytes, 4, headerSize);
|
|
writeU32(bytes, 8, flags);
|
|
writeU32(bytes, 12, volumeOffset);
|
|
writeU32(bytes, 16, localOffset);
|
|
writeU32(bytes, 20, networkOffset);
|
|
writeU32(bytes, 24, suffixOffset);
|
|
if (unicode) {
|
|
writeU32(bytes, 28, localWideOffset);
|
|
writeU32(bytes, 32, suffixWideOffset);
|
|
}
|
|
if (volumeOffset > 0) volume.copy(bytes, volumeOffset);
|
|
if (localOffset > 0) localAnsi.copy(bytes, localOffset);
|
|
if (networkOffset > 0) network.copy(bytes, networkOffset);
|
|
suffixAnsi.copy(bytes, suffixOffset);
|
|
if (localWideOffset > 0) localWide.copy(bytes, localWideOffset);
|
|
if (suffixWideOffset > 0) suffixWide.copy(bytes, suffixWideOffset);
|
|
return bytes;
|
|
};
|
|
|
|
/** StringData is counted, not NUL-terminated (MS-SHLLINK 2.4). */
|
|
const stringDataField = (value: string, unicode: boolean): Buffer => {
|
|
const text = Buffer.from(value, unicode ? "utf16le" : "latin1");
|
|
const count = Buffer.alloc(2);
|
|
count.writeUInt16LE(value.length, 0);
|
|
return Buffer.concat([count, text]);
|
|
};
|
|
|
|
const environmentBlock = (
|
|
signature: number,
|
|
ansi: string,
|
|
unicode: string,
|
|
): Buffer => {
|
|
const bytes = Buffer.alloc(0x314);
|
|
writeU32(bytes, 0, 0x314);
|
|
writeU32(bytes, 4, signature);
|
|
padField(ansi, 260, "latin1").copy(bytes, 8);
|
|
padField(unicode, 520, "utf16le").copy(bytes, 268);
|
|
return bytes;
|
|
};
|
|
|
|
const extraBlockBytes = (block: FixtureExtraBlock): Buffer => {
|
|
if (block.kind === "environment")
|
|
return environmentBlock(
|
|
EXTRA_SIGNATURE.EnvironmentVariable,
|
|
block.ansi ??
|
|
"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
|
|
block.unicode ??
|
|
"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
|
|
);
|
|
if (block.kind === "icon_environment")
|
|
return environmentBlock(
|
|
EXTRA_SIGNATURE.IconEnvironment,
|
|
block.ansi ?? "%SystemRoot%\\System32\\shell32.dll",
|
|
block.unicode ?? "%SystemRoot%\\System32\\shell32.dll",
|
|
);
|
|
if (block.kind === "tracker") {
|
|
const bytes = Buffer.alloc(0x60);
|
|
writeU32(bytes, 0, 0x60);
|
|
writeU32(bytes, 4, EXTRA_SIGNATURE.Tracker);
|
|
writeU32(bytes, 8, block.length ?? 0x58);
|
|
writeU32(bytes, 12, block.version ?? 0);
|
|
padField(block.machineId ?? "WORKSTATION", 16, "latin1").copy(bytes, 16);
|
|
const droid0 = block.droid?.[0] ?? defaultTrackerGuid(1);
|
|
const droid1 = block.droid?.[1] ?? defaultTrackerGuid(2);
|
|
const birth0 = block.birthDroid?.[0] ?? defaultTrackerGuid(3);
|
|
const birth1 = block.birthDroid?.[1] ?? defaultTrackerGuid(4);
|
|
droid0.copy(bytes, 32);
|
|
droid1.copy(bytes, 48);
|
|
birth0.copy(bytes, 64);
|
|
birth1.copy(bytes, 80);
|
|
return bytes;
|
|
}
|
|
if (block.kind === "known_folder") {
|
|
const bytes = Buffer.alloc(0x1c);
|
|
writeU32(bytes, 0, 0x1c);
|
|
writeU32(bytes, 4, EXTRA_SIGNATURE.KnownFolder);
|
|
(block.folderId ?? MY_COMPUTER_CLSID).copy(bytes, 8);
|
|
writeU32(bytes, 24, block.itemOffset ?? 0);
|
|
return bytes;
|
|
}
|
|
if (block.kind === "special_folder") {
|
|
const bytes = Buffer.alloc(0x10);
|
|
writeU32(bytes, 0, 0x10);
|
|
writeU32(bytes, 4, EXTRA_SIGNATURE.SpecialFolder);
|
|
writeU32(bytes, 8, block.folderId ?? 0x24);
|
|
writeU32(bytes, 12, block.itemOffset ?? 0);
|
|
return bytes;
|
|
}
|
|
if (block.kind === "console_fe") {
|
|
const bytes = Buffer.alloc(0x0c);
|
|
writeU32(bytes, 0, 0x0c);
|
|
writeU32(bytes, 4, EXTRA_SIGNATURE.ConsoleFE);
|
|
writeU32(bytes, 8, block.codePage ?? 437);
|
|
return bytes;
|
|
}
|
|
if (block.kind === "property_store") {
|
|
const payload = block.payload ?? Buffer.alloc(4);
|
|
const bytes = Buffer.alloc(8 + payload.length);
|
|
writeU32(bytes, 0, bytes.length);
|
|
writeU32(bytes, 4, EXTRA_SIGNATURE.PropertyStore);
|
|
payload.copy(bytes, 8);
|
|
return bytes;
|
|
}
|
|
const payload = block.payload ?? Buffer.from([0xde, 0xad]);
|
|
const bytes = Buffer.alloc(8 + payload.length);
|
|
writeU32(bytes, 0, bytes.length);
|
|
writeU32(bytes, 4, block.signature);
|
|
payload.copy(bytes, 8);
|
|
return bytes;
|
|
};
|
|
|
|
const defaultIdList = (): readonly FixtureIdListItem[] => [
|
|
{ kind: "root" },
|
|
{ kind: "volume", name: "C:\\" },
|
|
{
|
|
kind: "file",
|
|
shortName: "POWERS~1.EXE",
|
|
fileSize: 450560,
|
|
classType: 0x32,
|
|
},
|
|
];
|
|
|
|
const derivedFlags = (
|
|
options: WindowsShortcutFixtureOptions,
|
|
unicode: boolean,
|
|
): number => {
|
|
if (options.flags !== undefined) return options.flags;
|
|
let flags = 0;
|
|
if (options.idList !== null) flags |= LINK_FLAG.HasLinkTargetIDList;
|
|
if (options.linkInfo !== null) flags |= LINK_FLAG.HasLinkInfo;
|
|
if (options.name !== undefined) flags |= LINK_FLAG.HasName;
|
|
if (options.relativePath !== undefined) flags |= LINK_FLAG.HasRelativePath;
|
|
if (options.workingDir !== undefined) flags |= LINK_FLAG.HasWorkingDir;
|
|
if (options.arguments !== undefined) flags |= LINK_FLAG.HasArguments;
|
|
if (options.iconLocation !== undefined) flags |= LINK_FLAG.HasIconLocation;
|
|
if (unicode) flags |= LINK_FLAG.IsUnicode;
|
|
const extra = options.extraData ?? [];
|
|
if (extra.some((block) => block.kind === "environment"))
|
|
flags |= LINK_FLAG.HasExpString;
|
|
if (extra.some((block) => block.kind === "icon_environment"))
|
|
flags |= LINK_FLAG.HasExpIcon;
|
|
return flags;
|
|
};
|
|
|
|
/** Build one Shell Link. Omitted options produce a unicode PowerShell shortcut. */
|
|
export const windowsShortcutBytes = (
|
|
options: WindowsShortcutFixtureOptions = {},
|
|
): Buffer => {
|
|
const unicode = options.unicode ?? true;
|
|
const name = options.name ?? "Windows PowerShell";
|
|
const relativePath =
|
|
options.relativePath ?? ".\\WindowsPowerShell\\v1.0\\powershell.exe";
|
|
const workingDir = options.workingDir ?? "C:\\Windows\\System32";
|
|
const argumentText =
|
|
options.arguments ??
|
|
'-WindowStyle Hidden -NoProfile -Command "Get-Process"';
|
|
const iconLocation =
|
|
options.iconLocation ?? "C:\\Windows\\System32\\shell32.dll";
|
|
const idList =
|
|
options.idList === undefined ? defaultIdList() : options.idList;
|
|
const linkInfo =
|
|
options.linkInfo === undefined
|
|
? {
|
|
driveType: 3,
|
|
serial: 0x9a8b7c6d,
|
|
label: "OS",
|
|
localBasePath:
|
|
"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
|
|
commonPathSuffix: "",
|
|
}
|
|
: options.linkInfo;
|
|
const extraData =
|
|
options.extraData === undefined
|
|
? ([
|
|
{ kind: "environment" },
|
|
{ kind: "icon_environment" },
|
|
{ kind: "tracker" },
|
|
{ kind: "console_fe", codePage: 437 },
|
|
{ kind: "special_folder", folderId: 0x24 },
|
|
{ kind: "known_folder" },
|
|
{ kind: "property_store" },
|
|
] satisfies FixtureExtraBlock[])
|
|
: options.extraData;
|
|
const flags = derivedFlags(
|
|
{
|
|
...options,
|
|
name,
|
|
relativePath,
|
|
workingDir,
|
|
arguments: argumentText,
|
|
iconLocation,
|
|
},
|
|
unicode,
|
|
);
|
|
const header = Buffer.alloc(76);
|
|
writeU32(header, 0, options.headerSize ?? 0x4c);
|
|
(options.clsid ?? SHELL_LINK_CLSID).copy(header, 4);
|
|
writeU32(header, 20, flags);
|
|
writeU32(header, 24, options.fileAttributes ?? 0x20);
|
|
writeU64(header, 28, options.creationTime ?? FIXTURE_FILETIME);
|
|
writeU64(header, 36, options.accessTime ?? FIXTURE_FILETIME);
|
|
writeU64(header, 44, options.writeTime ?? FIXTURE_FILETIME);
|
|
writeU32(header, 52, options.fileSize ?? 450560);
|
|
header.writeInt32LE(options.iconIndex ?? 0, 56);
|
|
writeU32(header, 60, options.showCommand ?? 7);
|
|
header.writeUInt16LE(options.hotKey ?? 0, 64);
|
|
const parts: Buffer[] = [header];
|
|
if (flags & LINK_FLAG.HasLinkTargetIDList && idList !== null)
|
|
parts.push(idListBytes(idList));
|
|
if (flags & LINK_FLAG.HasLinkInfo && linkInfo !== null)
|
|
parts.push(linkInfoBytes(linkInfo, unicode));
|
|
const strings: Buffer[] = [];
|
|
if (flags & LINK_FLAG.HasName) strings.push(stringDataField(name, unicode));
|
|
if (flags & LINK_FLAG.HasRelativePath)
|
|
strings.push(stringDataField(relativePath, unicode));
|
|
if (flags & LINK_FLAG.HasWorkingDir)
|
|
strings.push(stringDataField(workingDir, unicode));
|
|
if (flags & LINK_FLAG.HasArguments)
|
|
strings.push(stringDataField(argumentText, unicode));
|
|
if (flags & LINK_FLAG.HasIconLocation)
|
|
strings.push(stringDataField(iconLocation, unicode));
|
|
let stringBytes = Buffer.concat(strings);
|
|
if (options.omitStringBytes !== undefined)
|
|
stringBytes = stringBytes.subarray(
|
|
0,
|
|
Math.max(0, stringBytes.length - options.omitStringBytes),
|
|
);
|
|
parts.push(stringBytes);
|
|
for (const block of extraData) parts.push(extraBlockBytes(block));
|
|
if (options.terminalBlock !== false) parts.push(Buffer.alloc(4));
|
|
const bytes = Buffer.concat(parts);
|
|
return options.truncateAt === undefined
|
|
? bytes
|
|
: bytes.subarray(0, options.truncateAt);
|
|
};
|
|
|
|
export const powershellHiddenIconSpoofShortcut = (): Buffer =>
|
|
windowsShortcutBytes();
|
|
|
|
export const networkShareShortcut = (): Buffer =>
|
|
windowsShortcutBytes({
|
|
showCommand: 1,
|
|
flags:
|
|
LINK_FLAG.HasLinkTargetIDList |
|
|
LINK_FLAG.HasLinkInfo |
|
|
LINK_FLAG.HasName |
|
|
LINK_FLAG.HasRelativePath |
|
|
LINK_FLAG.IsUnicode,
|
|
name: "Share",
|
|
relativePath: ".\\report.docx",
|
|
linkInfo: {
|
|
netName: "\\\\fileserver\\docs",
|
|
deviceName: "Z:",
|
|
networkProviderType: 0x00020000,
|
|
commonPathSuffix: "report.docx",
|
|
},
|
|
extraData: [],
|
|
});
|
|
|
|
export const ansiShortcut = (): Buffer =>
|
|
windowsShortcutBytes({
|
|
unicode: false,
|
|
extraData: [{ kind: "environment", ansi: "C:\\Windows\\notepad.exe" }],
|
|
});
|
|
|
|
export const truncatedStringDataShortcut = (): Buffer =>
|
|
windowsShortcutBytes({
|
|
extraData: [],
|
|
terminalBlock: false,
|
|
omitStringBytes: 8,
|
|
});
|
|
|
|
export const unknownExtraDataShortcut = (): Buffer =>
|
|
windowsShortcutBytes({
|
|
extraData: [
|
|
{ kind: "unknown", signature: 0xa0ffffff, payload: Buffer.from("xx") },
|
|
],
|
|
});
|
|
|
|
export const wrongClsidShortcut = (): Buffer =>
|
|
windowsShortcutBytes({
|
|
clsid: Buffer.alloc(16),
|
|
extraData: [],
|
|
});
|