Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724/** * SMB2/3 Message Parser * Handles parsing of SMB2 protocol messages from binary data */
import type { SMB2Header, SMB2TransformHeader, SMB2NegotiateRequest, SMB2NegotiateResponse, SMB2NegotiateContext, SMB2SessionSetupRequest, SMB2SessionSetupResponse, SMB2TreeConnectRequest, SMB2TreeConnectResponse, SMB2CreateRequest, SMB2CreateResponse, SMB2CreateContext, SMB2CloseRequest, SMB2CloseResponse, SMB2ReadRequest, SMB2ReadResponse, SMB2WriteRequest, SMB2WriteResponse, SMB2QueryDirectoryRequest, SMB2QueryDirectoryResponse, SMB2QueryInfoRequest, SMB2QueryInfoResponse, SMB2SetInfoRequest, SMB2SetInfoResponse, SMB2FileId, SMB2Guid, FileBasicInformation, FileStandardInformation, FileDirectoryInformation,} from './types.js';import { SMB2_PROTOCOL_ID, SMB2_TRANSFORM_PROTOCOL_ID, SMB2_HEADER_SIZE, SMB2_TRANSFORM_HEADER_SIZE, SMB2_COMMANDS, SMB2_DIALECTS,} from './constants.js';
export interface ParseResult<T> { readonly result: T; readonly bytesRead: number;}
export class SMB2MessageParser { /** * Parse SMB2 header from buffer */ public parseHeader(buffer: Buffer, offset: number = 0): ParseResult<SMB2Header> { if (buffer.length < offset + SMB2_HEADER_SIZE) { throw new Error('Buffer too small for SMB2 header'); }
const protocolId = buffer.readUInt32LE(offset); if (protocolId !== SMB2_PROTOCOL_ID) { throw new Error(`Invalid SMB2 protocol ID: 0x${protocolId.toString(16)}`); }
const header: SMB2Header = { protocolId, structureSize: buffer.readUInt16LE(offset + 4), creditCharge: buffer.readUInt16LE(offset + 6), status: buffer.readUInt32LE(offset + 8), command: buffer.readUInt16LE(offset + 12), creditRequestResponse: buffer.readUInt16LE(offset + 14), flags: buffer.readUInt32LE(offset + 16), nextCommand: buffer.readUInt32LE(offset + 20), messageId: buffer.readBigUInt64LE(offset + 24), reserved: buffer.readUInt32LE(offset + 32), treeId: buffer.readUInt32LE(offset + 36), sessionId: buffer.readBigUInt64LE(offset + 40), signature: buffer.subarray(offset + 48, offset + 64), };
return { result: header, bytesRead: SMB2_HEADER_SIZE, }; }
/** * Parse SMB2 Transform header for encrypted messages */ public parseTransformHeader(buffer: Buffer, offset: number = 0): ParseResult<SMB2TransformHeader> { if (buffer.length < offset + SMB2_TRANSFORM_HEADER_SIZE) { throw new Error('Buffer too small for SMB2 transform header'); }
const protocolId = buffer.readUInt32LE(offset); if (protocolId !== SMB2_TRANSFORM_PROTOCOL_ID) { throw new Error(`Invalid SMB2 transform protocol ID: 0x${protocolId.toString(16)}`); }
const header: SMB2TransformHeader = { protocolId, signature: buffer.subarray(offset + 4, offset + 20), nonce: buffer.subarray(offset + 20, offset + 36), originalMessageSize: buffer.readUInt32LE(offset + 36), reserved: buffer.readUInt16LE(offset + 40), flags: buffer.readUInt16LE(offset + 42), sessionId: buffer.readBigUInt64LE(offset + 44), };
return { result: header, bytesRead: SMB2_TRANSFORM_HEADER_SIZE, }; }
/** * Parse SMB2 Negotiate Request */ public parseNegotiateRequest(buffer: Buffer, offset: number = 0): ParseResult<SMB2NegotiateRequest> { if (buffer.length < offset + 36) { throw new Error('Buffer too small for negotiate request'); }
const structureSize = buffer.readUInt16LE(offset); const dialectCount = buffer.readUInt16LE(offset + 2); const securityMode = buffer.readUInt16LE(offset + 4); const reserved = buffer.readUInt16LE(offset + 6); const capabilities = buffer.readUInt32LE(offset + 8); const clientGuid = this.parseGuid(buffer, offset + 12); const clientStartTime = buffer.readBigUInt64LE(offset + 28);
// Parse dialects const dialects: SMB2_DIALECTS[] = []; let dialectOffset = offset + 36; for (let i = 0; i < dialectCount; i++) { if (dialectOffset + 2 > buffer.length) break; dialects.push(buffer.readUInt16LE(dialectOffset)); dialectOffset += 2; }
// Parse negotiate contexts (SMB 3.1.1+) const negotiateContexts: SMB2NegotiateContext[] = []; // TODO: Implement negotiate context parsing for SMB 3.1.1
const request: SMB2NegotiateRequest = { structureSize, dialectCount, securityMode, reserved, capabilities, clientGuid, clientStartTime, dialects, negotiateContexts, };
return { result: request, bytesRead: dialectOffset - offset, }; }
/** * Parse SMB2 Session Setup Request */ public parseSessionSetupRequest(buffer: Buffer, offset: number = 0): ParseResult<SMB2SessionSetupRequest> { if (buffer.length < offset + 25) { throw new Error('Buffer too small for session setup request'); }
const structureSize = buffer.readUInt16LE(offset); const flags = buffer.readUInt8(offset + 2); const securityMode = buffer.readUInt8(offset + 3); const capabilities = buffer.readUInt32LE(offset + 4); const channel = buffer.readUInt32LE(offset + 8); const securityBufferOffset = buffer.readUInt16LE(offset + 12); const securityBufferLength = buffer.readUInt16LE(offset + 14); const previousSessionId = buffer.readBigUInt64LE(offset + 16);
// Parse security buffer let securityBuffer = Buffer.alloc(0); if (securityBufferLength > 0 && securityBufferOffset > 0) { const bufferStart = securityBufferOffset - SMB2_HEADER_SIZE; securityBuffer = buffer.subarray(bufferStart, bufferStart + securityBufferLength); }
const request: SMB2SessionSetupRequest = { structureSize, flags, securityMode, capabilities, channel, securityBufferOffset, securityBufferLength, previousSessionId, securityBuffer, };
return { result: request, bytesRead: 24, }; }
/** * Parse SMB2 Tree Connect Request */ public parseTreeConnectRequest(buffer: Buffer, offset: number = 0): ParseResult<SMB2TreeConnectRequest> { if (buffer.length < offset + 9) { throw new Error('Buffer too small for tree connect request'); }
const structureSize = buffer.readUInt16LE(offset); const flags = buffer.readUInt16LE(offset + 2); const pathOffset = buffer.readUInt16LE(offset + 4); const pathLength = buffer.readUInt16LE(offset + 6);
// Parse path (UTF-16LE) let path = ''; if (pathLength > 0 && pathOffset > 0) { const pathStart = pathOffset - SMB2_HEADER_SIZE; const pathBuffer = buffer.subarray(pathStart, pathStart + pathLength); path = pathBuffer.toString('utf16le'); }
const request: SMB2TreeConnectRequest = { structureSize, flags, pathOffset, pathLength, path, };
return { result: request, bytesRead: 8, }; }
/** * Parse SMB2 Create Request */ public parseCreateRequest(buffer: Buffer, offset: number = 0): ParseResult<SMB2CreateRequest> { if (buffer.length < offset + 57) { throw new Error('Buffer too small for create request'); }
const structureSize = buffer.readUInt16LE(offset); const securityFlags = buffer.readUInt8(offset + 2); const requestedOplockLevel = buffer.readUInt8(offset + 3); const impersonationLevel = buffer.readUInt32LE(offset + 4); const smbCreateFlags = buffer.readBigUInt64LE(offset + 8); const reserved = buffer.readBigUInt64LE(offset + 16); const desiredAccess = buffer.readUInt32LE(offset + 24); const fileAttributes = buffer.readUInt32LE(offset + 28); const shareAccess = buffer.readUInt32LE(offset + 32); const createDisposition = buffer.readUInt32LE(offset + 36); const createOptions = buffer.readUInt32LE(offset + 40); const nameOffset = buffer.readUInt16LE(offset + 44); const nameLength = buffer.readUInt16LE(offset + 46); const createContextsOffset = buffer.readUInt32LE(offset + 48); const createContextsLength = buffer.readUInt32LE(offset + 52);
// Parse name (UTF-16LE) let name = ''; if (nameLength > 0 && nameOffset > 0) { const nameStart = nameOffset - SMB2_HEADER_SIZE; const nameBuffer = buffer.subarray(nameStart, nameStart + nameLength); name = nameBuffer.toString('utf16le'); }
// Parse create contexts const createContexts: SMB2CreateContext[] = []; if (createContextsLength > 0 && createContextsOffset > 0) { // TODO: Implement create context parsing }
const request: SMB2CreateRequest = { structureSize, securityFlags, requestedOplockLevel, impersonationLevel, smbCreateFlags, reserved, desiredAccess, fileAttributes, shareAccess, createDisposition, createOptions, nameOffset, nameLength, createContextsOffset, createContextsLength, name, createContexts, };
return { result: request, bytesRead: 56, }; }
/** * Parse SMB2 Close Request */ public parseCloseRequest(buffer: Buffer, offset: number = 0): ParseResult<SMB2CloseRequest> { if (buffer.length < offset + 24) { throw new Error('Buffer too small for close request'); }
const structureSize = buffer.readUInt16LE(offset); const flags = buffer.readUInt16LE(offset + 2); const reserved = buffer.readUInt32LE(offset + 4); const fileId = this.parseFileId(buffer, offset + 8);
const request: SMB2CloseRequest = { structureSize, flags, reserved, fileId, };
return { result: request, bytesRead: 24, }; }
/** * Parse SMB2 Read Request */ public parseReadRequest(buffer: Buffer, offset: number = 0): ParseResult<SMB2ReadRequest> { if (buffer.length < offset + 49) { throw new Error('Buffer too small for read request'); }
const structureSize = buffer.readUInt16LE(offset); const padding = buffer.readUInt8(offset + 2); const flags = buffer.readUInt8(offset + 3); const length = buffer.readUInt32LE(offset + 4); const offsetValue = buffer.readBigUInt64LE(offset + 8); const fileId = this.parseFileId(buffer, offset + 16); const minimumCount = buffer.readUInt32LE(offset + 32); const channel = buffer.readUInt32LE(offset + 36); const remainingBytes = buffer.readUInt32LE(offset + 40); const readChannelInfoOffset = buffer.readUInt16LE(offset + 44); const readChannelInfoLength = buffer.readUInt16LE(offset + 46);
// Parse read channel info let readChannelInfo = Buffer.alloc(0); if (readChannelInfoLength > 0 && readChannelInfoOffset > 0) { const infoStart = readChannelInfoOffset - SMB2_HEADER_SIZE; readChannelInfo = buffer.subarray(infoStart, infoStart + readChannelInfoLength); }
const request: SMB2ReadRequest = { structureSize, padding, flags, length, offset: offsetValue, fileId, minimumCount, channel, remainingBytes, readChannelInfoOffset, readChannelInfoLength, readChannelInfo, };
return { result: request, bytesRead: 48, }; }
/** * Parse SMB2 Write Request */ public parseWriteRequest(buffer: Buffer, offset: number = 0): ParseResult<SMB2WriteRequest> { if (buffer.length < offset + 49) { throw new Error('Buffer too small for write request'); }
const structureSize = buffer.readUInt16LE(offset); const dataOffset = buffer.readUInt16LE(offset + 2); const length = buffer.readUInt32LE(offset + 4); const offsetValue = buffer.readBigUInt64LE(offset + 8); const fileId = this.parseFileId(buffer, offset + 16); const channel = buffer.readUInt32LE(offset + 32); const remainingBytes = buffer.readUInt32LE(offset + 36); const writeChannelInfoOffset = buffer.readUInt16LE(offset + 40); const writeChannelInfoLength = buffer.readUInt16LE(offset + 42); const flags = buffer.readUInt32LE(offset + 44);
// Parse write channel info let writeChannelInfo = Buffer.alloc(0); if (writeChannelInfoLength > 0 && writeChannelInfoOffset > 0) { const infoStart = writeChannelInfoOffset - SMB2_HEADER_SIZE; writeChannelInfo = buffer.subarray(infoStart, infoStart + writeChannelInfoLength); }
// Parse data let data = Buffer.alloc(0); if (length > 0 && dataOffset > 0) { const dataStart = dataOffset - SMB2_HEADER_SIZE; data = buffer.subarray(dataStart, dataStart + length); }
const request: SMB2WriteRequest = { structureSize, dataOffset, length, offset: offsetValue, fileId, channel, remainingBytes, writeChannelInfoOffset, writeChannelInfoLength, flags, writeChannelInfo, data, };
return { result: request, bytesRead: 48, }; }
/** * Parse SMB2 Query Directory Request */ public parseQueryDirectoryRequest(buffer: Buffer, offset: number = 0): ParseResult<SMB2QueryDirectoryRequest> { if (buffer.length < offset + 33) { throw new Error('Buffer too small for query directory request'); }
const structureSize = buffer.readUInt16LE(offset); const fileInformationClass = buffer.readUInt8(offset + 2); const flags = buffer.readUInt8(offset + 3); const fileIndex = buffer.readUInt32LE(offset + 4); const fileId = this.parseFileId(buffer, offset + 8); const fileNameOffset = buffer.readUInt16LE(offset + 24); const fileNameLength = buffer.readUInt16LE(offset + 26); const outputBufferLength = buffer.readUInt32LE(offset + 28);
// Parse filename (UTF-16LE) let fileName = ''; if (fileNameLength > 0 && fileNameOffset > 0) { const nameStart = fileNameOffset - SMB2_HEADER_SIZE; const nameBuffer = buffer.subarray(nameStart, nameStart + fileNameLength); fileName = nameBuffer.toString('utf16le'); }
const request: SMB2QueryDirectoryRequest = { structureSize, fileInformationClass, flags, fileIndex, fileId, fileNameOffset, fileNameLength, outputBufferLength, fileName, };
return { result: request, bytesRead: 32, }; }
/** * Parse SMB2 Query Info Request */ public parseQueryInfoRequest(buffer: Buffer, offset: number = 0): ParseResult<SMB2QueryInfoRequest> { if (buffer.length < offset + 41) { throw new Error('Buffer too small for query info request'); }
const structureSize = buffer.readUInt16LE(offset); const infoType = buffer.readUInt8(offset + 2); const fileInfoClass = buffer.readUInt8(offset + 3); const outputBufferLength = buffer.readUInt32LE(offset + 4); const inputBufferOffset = buffer.readUInt16LE(offset + 8); const reserved = buffer.readUInt16LE(offset + 10); const inputBufferLength = buffer.readUInt32LE(offset + 12); const additionalInformation = buffer.readUInt32LE(offset + 16); const flags = buffer.readUInt32LE(offset + 20); const fileId = this.parseFileId(buffer, offset + 24);
// Parse input buffer let inputBuffer = Buffer.alloc(0); if (inputBufferLength > 0 && inputBufferOffset > 0) { const bufferStart = inputBufferOffset - SMB2_HEADER_SIZE; inputBuffer = buffer.subarray(bufferStart, bufferStart + inputBufferLength); }
const request: SMB2QueryInfoRequest = { structureSize, infoType, fileInfoClass, outputBufferLength, inputBufferOffset, reserved, inputBufferLength, additionalInformation, flags, fileId, inputBuffer, };
return { result: request, bytesRead: 40, }; }
/** * Parse SMB2 Set Info Request */ public parseSetInfoRequest(buffer: Buffer, offset: number = 0): ParseResult<SMB2SetInfoRequest> { if (buffer.length < offset + 33) { throw new Error('Buffer too small for set info request'); }
const structureSize = buffer.readUInt16LE(offset); const infoType = buffer.readUInt8(offset + 2); const fileInfoClass = buffer.readUInt8(offset + 3); const bufferLength = buffer.readUInt32LE(offset + 4); const bufferOffset = buffer.readUInt16LE(offset + 8); const reserved = buffer.readUInt16LE(offset + 10); const additionalInformation = buffer.readUInt32LE(offset + 12); const fileId = this.parseFileId(buffer, offset + 16);
// Parse buffer let bufferData = Buffer.alloc(0); if (bufferLength > 0 && bufferOffset > 0) { const dataStart = bufferOffset - SMB2_HEADER_SIZE; bufferData = buffer.subarray(dataStart, dataStart + bufferLength); }
const request: SMB2SetInfoRequest = { structureSize, infoType, fileInfoClass, bufferLength, bufferOffset, reserved, additionalInformation, fileId, buffer: bufferData, };
return { result: request, bytesRead: 32, }; }
/** * Parse File Basic Information */ public parseFileBasicInformation(buffer: Buffer, offset: number = 0): ParseResult<FileBasicInformation> { if (buffer.length < offset + 40) { throw new Error('Buffer too small for file basic information'); }
const info: FileBasicInformation = { creationTime: buffer.readBigUInt64LE(offset), lastAccessTime: buffer.readBigUInt64LE(offset + 8), lastWriteTime: buffer.readBigUInt64LE(offset + 16), changeTime: buffer.readBigUInt64LE(offset + 24), fileAttributes: buffer.readUInt32LE(offset + 32), reserved: buffer.readUInt32LE(offset + 36), };
return { result: info, bytesRead: 40, }; }
/** * Parse File Standard Information */ public parseFileStandardInformation(buffer: Buffer, offset: number = 0): ParseResult<FileStandardInformation> { if (buffer.length < offset + 24) { throw new Error('Buffer too small for file standard information'); }
const info: FileStandardInformation = { allocationSize: buffer.readBigUInt64LE(offset), endOfFile: buffer.readBigUInt64LE(offset + 8), numberOfLinks: buffer.readUInt32LE(offset + 16), deletePending: buffer.readUInt8(offset + 20) !== 0, directory: buffer.readUInt8(offset + 21) !== 0, reserved: buffer.readUInt16LE(offset + 22), };
return { result: info, bytesRead: 24, }; }
/** * Parse File Directory Information */ public parseFileDirectoryInformation(buffer: Buffer, offset: number = 0): ParseResult<FileDirectoryInformation> { if (buffer.length < offset + 64) { throw new Error('Buffer too small for file directory information'); }
const nextEntryOffset = buffer.readUInt32LE(offset); const fileIndex = buffer.readUInt32LE(offset + 4); const creationTime = buffer.readBigUInt64LE(offset + 8); const lastAccessTime = buffer.readBigUInt64LE(offset + 16); const lastWriteTime = buffer.readBigUInt64LE(offset + 24); const changeTime = buffer.readBigUInt64LE(offset + 32); const endOfFile = buffer.readBigUInt64LE(offset + 40); const allocationSize = buffer.readBigUInt64LE(offset + 48); const fileAttributes = buffer.readUInt32LE(offset + 56); const fileNameLength = buffer.readUInt32LE(offset + 60);
// Parse filename (UTF-16LE) let fileName = ''; if (fileNameLength > 0) { const nameBuffer = buffer.subarray(offset + 64, offset + 64 + fileNameLength); fileName = nameBuffer.toString('utf16le'); }
const info: FileDirectoryInformation = { nextEntryOffset, fileIndex, creationTime, lastAccessTime, lastWriteTime, changeTime, endOfFile, allocationSize, fileAttributes, fileNameLength, fileName, };
return { result: info, bytesRead: 64 + fileNameLength, }; }
/** * Parse SMB2 File ID */ private parseFileId(buffer: Buffer, offset: number): SMB2FileId { if (buffer.length < offset + 16) { throw new Error('Buffer too small for file ID'); }
return { persistent: buffer.readBigUInt64LE(offset), volatile: buffer.readBigUInt64LE(offset + 8), }; }
/** * Parse SMB2 GUID */ private parseGuid(buffer: Buffer, offset: number): SMB2Guid { if (buffer.length < offset + 16) { throw new Error('Buffer too small for GUID'); }
const guidBytes = buffer.subarray(offset, offset + 16); return Array.from(guidBytes) .map(b => b.toString(16).padStart(2, '0')) .join('') .toUpperCase(); }
/** * Parse UTF-16LE string */ private parseUtf16String(buffer: Buffer, offset: number, length: number): string { if (buffer.length < offset + length) { throw new Error('Buffer too small for UTF-16 string'); }
return buffer.subarray(offset, offset + length).toString('utf16le'); }
/** * Parse FILETIME (Windows 64-bit timestamp) */ private parseFileTime(buffer: Buffer, offset: number): bigint { if (buffer.length < offset + 8) { throw new Error('Buffer too small for FILETIME'); }
return buffer.readBigUInt64LE(offset); }
/** * Convert FILETIME to JavaScript Date */ private fileTimeToDate(filetime: bigint): Date { // FILETIME is 100-nanosecond intervals since January 1, 1601 const FILETIME_EPOCH_DIFF = 11644473600000n; // milliseconds between 1601 and 1970 const timestamp = Number(filetime / 10000n) - Number(FILETIME_EPOCH_DIFF); return new Date(timestamp); }
/** * Validate buffer has minimum required size */ private validateBufferSize(buffer: Buffer, offset: number, requiredSize: number, context: string): void { if (buffer.length < offset + requiredSize) { throw new Error(`Buffer too small for ${context}: need ${requiredSize} bytes, have ${buffer.length - offset}`); } }}