From 8af8bcb3d621ae4bf3f277a0350653bc2a76e651 Mon Sep 17 00:00:00 2001 From: ardean Date: Mon, 1 Jun 2020 15:00:18 +0200 Subject: [PATCH 01/11] implement server authentication --- .gitignore | 3 +- demo/index.ts | 6 +- demo/runServer.ts | 19 +- src/client/Client.ts | 14 +- src/client/Directory.ts | 26 +- src/client/File.ts | 18 +- src/client/Session.ts | 17 +- src/client/Tree.ts | 12 +- src/index.ts | 27 +- src/{protocol => }/models/ChangeEntry.ts | 2 +- src/{protocol => }/models/DirectoryEntry.ts | 0 src/protocol/ntlm/util.ts | 333 ----------- src/protocol/smb2/Capability.ts | 6 - src/{protocol => protocols}/Packet.ts | 4 +- .../ProtocolIds.ts} | 4 +- src/{protocol => protocols}/Request.ts | 0 src/{protocol => protocols}/Response.ts | 0 src/{protocol => protocols}/Structure.ts | 0 src/{protocol => protocols}/StructureField.ts | 1 + src/{protocol => protocols}/Value.ts | 0 src/protocols/dtyp/util.ts | 13 + src/protocols/ntlm/MessageType.ts | 7 + .../ntlm/NegotiateFlag.ts | 0 src/protocols/ntlm/Version.ts | 62 ++ .../ntlm/attributeValue/AttributeValueId.ts | 15 + .../ntlm/attributeValue/AttributeValuePair.ts | 7 + src/protocols/ntlm/attributeValue/util.ts | 111 ++++ src/protocols/ntlm/util.ts | 560 ++++++++++++++++++ src/{protocol => protocols}/smb/Header.ts | 0 src/{protocol => protocols}/smb/Packet.ts | 0 src/{protocol => protocols}/smb/PacketType.ts | 0 src/{protocol => protocols}/smb/Request.ts | 0 src/{protocol => protocols}/smb/Response.ts | 0 src/protocols/smb2/Capability.ts | 7 + src/protocols/smb2/ControlCode.ts | 19 + .../smb2/CreateDispositionType.ts | 0 src/{protocol => protocols}/smb2/Dialect.ts | 0 .../smb2/DirectoryAccess.ts | 0 .../smb2/FileAction.ts | 0 .../smb2/FileAttribute.ts | 0 .../smb2/FilePipePrinterAccess.ts | 4 +- src/{protocol => protocols}/smb2/Header.ts | 3 +- .../smb2/HeaderFlag.ts | 0 src/{protocol => protocols}/smb2/Packet.ts | 2 +- .../smb2/PacketType.ts | 0 src/{protocol => protocols}/smb2/Request.ts | 0 src/{protocol => protocols}/smb2/Response.ts | 0 src/protocols/smb2/SecurityMode.ts | 6 + src/protocols/smb2/SessionFlag.ts | 7 + .../smb2/ShareAccessType.ts | 0 .../smb2/StatusCode.ts | 1 + src/protocols/smb2/TreeConnectShareFlag.ts | 18 + src/protocols/smb2/TreeConnectShareType.ts | 7 + .../smb2/packets/ChangeNotify.ts | 2 +- .../smb2/packets/Close.ts | 0 .../smb2/packets/Create.ts | 0 .../smb2/packets/Echo.ts | 0 .../smb2/packets/Flush.ts | 0 .../smb2/packets/InputOutputControl.ts | 124 ++++ .../smb2/packets/LogOff.ts | 0 .../smb2/packets/Negotiate.ts | 0 .../smb2/packets/QueryDirectory.ts | 11 +- .../smb2/packets/Read.ts | 0 .../smb2/packets/SessionSetup.ts | 0 .../smb2/packets/SetInfo.ts | 0 .../smb2/packets/TreeConnect.ts | 7 +- .../smb2/packets/TreeDisconnect.ts | 0 .../smb2/packets/Write.ts | 0 .../smb2/packets/index.ts | 3 +- src/{protocol => protocols}/structureUtil.ts | 45 +- src/{protocol => protocols}/util.ts | 28 + src/server/AuthenticationProvider.ts | 20 + src/server/Client.ts | 38 +- src/server/RemoteService.ts | 3 + src/server/Server.ts | 88 ++- src/server/Session.ts | 7 + src/server/Share.ts | 3 + src/server/ShareProvider.ts | 12 + src/server/Smb2Request.ts | 2 +- src/server/Smb2Response.ts | 15 +- src/server/SmbRequest.ts | 2 +- src/server/SmbResponse.ts | 8 +- src/server/User.ts | 4 + .../FileAuthenticationProvider.ts | 14 + src/server/authenticationProviders/index.ts | 1 + src/server/middlewares/requestType.ts | 2 +- src/server/middlewares/unhandledRequest.ts | 7 + .../remoteServices/WorkstationService.ts | 7 + src/server/requestHandlers/smb/Negotiate.ts | 8 +- .../smb2/InputOutputControl.ts | 21 + src/server/requestHandlers/smb2/Negotiate.ts | 29 +- .../requestHandlers/smb2/SessionSetup.ts | 137 ++++- .../requestHandlers/smb2/TreeConnect.ts | 43 ++ src/server/requestHandlers/smb2/index.ts | 4 +- .../shareProviders/FileShareProvider.ts | 14 + src/server/shareProviders/index.ts | 1 + 96 files changed, 1547 insertions(+), 504 deletions(-) rename src/{protocol => }/models/ChangeEntry.ts (65%) rename src/{protocol => }/models/DirectoryEntry.ts (100%) delete mode 100644 src/protocol/ntlm/util.ts delete mode 100644 src/protocol/smb2/Capability.ts rename src/{protocol => protocols}/Packet.ts (91%) rename src/{protocol/protocolIds.ts => protocols/ProtocolIds.ts} (81%) rename src/{protocol => protocols}/Request.ts (100%) rename src/{protocol => protocols}/Response.ts (100%) rename src/{protocol => protocols}/Structure.ts (100%) rename src/{protocol => protocols}/StructureField.ts (92%) rename src/{protocol => protocols}/Value.ts (100%) create mode 100644 src/protocols/dtyp/util.ts create mode 100644 src/protocols/ntlm/MessageType.ts rename src/{protocol => protocols}/ntlm/NegotiateFlag.ts (100%) create mode 100644 src/protocols/ntlm/Version.ts create mode 100644 src/protocols/ntlm/attributeValue/AttributeValueId.ts create mode 100644 src/protocols/ntlm/attributeValue/AttributeValuePair.ts create mode 100644 src/protocols/ntlm/attributeValue/util.ts create mode 100644 src/protocols/ntlm/util.ts rename src/{protocol => protocols}/smb/Header.ts (100%) rename src/{protocol => protocols}/smb/Packet.ts (100%) rename src/{protocol => protocols}/smb/PacketType.ts (100%) rename src/{protocol => protocols}/smb/Request.ts (100%) rename src/{protocol => protocols}/smb/Response.ts (100%) create mode 100644 src/protocols/smb2/Capability.ts create mode 100644 src/protocols/smb2/ControlCode.ts rename src/{protocol => protocols}/smb2/CreateDispositionType.ts (100%) rename src/{protocol => protocols}/smb2/Dialect.ts (100%) rename src/{protocol => protocols}/smb2/DirectoryAccess.ts (100%) rename src/{protocol => protocols}/smb2/FileAction.ts (100%) rename src/{protocol => protocols}/smb2/FileAttribute.ts (100%) rename src/{protocol => protocols}/smb2/FilePipePrinterAccess.ts (92%) rename src/{protocol => protocols}/smb2/Header.ts (97%) rename src/{protocol => protocols}/smb2/HeaderFlag.ts (100%) rename src/{protocol => protocols}/smb2/Packet.ts (98%) rename src/{protocol => protocols}/smb2/PacketType.ts (100%) rename src/{protocol => protocols}/smb2/Request.ts (100%) rename src/{protocol => protocols}/smb2/Response.ts (100%) create mode 100644 src/protocols/smb2/SecurityMode.ts create mode 100644 src/protocols/smb2/SessionFlag.ts rename src/{protocol => protocols}/smb2/ShareAccessType.ts (100%) rename src/{protocol => protocols}/smb2/StatusCode.ts (88%) create mode 100644 src/protocols/smb2/TreeConnectShareFlag.ts create mode 100644 src/protocols/smb2/TreeConnectShareType.ts rename src/{protocol => protocols}/smb2/packets/ChangeNotify.ts (97%) rename src/{protocol => protocols}/smb2/packets/Close.ts (100%) rename src/{protocol => protocols}/smb2/packets/Create.ts (100%) rename src/{protocol => protocols}/smb2/packets/Echo.ts (100%) rename src/{protocol => protocols}/smb2/packets/Flush.ts (100%) create mode 100644 src/protocols/smb2/packets/InputOutputControl.ts rename src/{protocol => protocols}/smb2/packets/LogOff.ts (100%) rename src/{protocol => protocols}/smb2/packets/Negotiate.ts (100%) rename src/{protocol => protocols}/smb2/packets/QueryDirectory.ts (88%) rename src/{protocol => protocols}/smb2/packets/Read.ts (100%) rename src/{protocol => protocols}/smb2/packets/SessionSetup.ts (100%) rename src/{protocol => protocols}/smb2/packets/SetInfo.ts (100%) rename src/{protocol => protocols}/smb2/packets/TreeConnect.ts (83%) rename src/{protocol => protocols}/smb2/packets/TreeDisconnect.ts (100%) rename src/{protocol => protocols}/smb2/packets/Write.ts (100%) rename src/{protocol => protocols}/smb2/packets/index.ts (84%) rename src/{protocol => protocols}/structureUtil.ts (82%) rename src/{protocol => protocols}/util.ts (68%) create mode 100644 src/server/AuthenticationProvider.ts create mode 100644 src/server/RemoteService.ts create mode 100644 src/server/Session.ts create mode 100644 src/server/Share.ts create mode 100644 src/server/ShareProvider.ts create mode 100644 src/server/User.ts create mode 100644 src/server/authenticationProviders/FileAuthenticationProvider.ts create mode 100644 src/server/authenticationProviders/index.ts create mode 100644 src/server/middlewares/unhandledRequest.ts create mode 100644 src/server/remoteServices/WorkstationService.ts create mode 100644 src/server/requestHandlers/smb2/InputOutputControl.ts create mode 100644 src/server/requestHandlers/smb2/TreeConnect.ts create mode 100644 src/server/shareProviders/FileShareProvider.ts create mode 100644 src/server/shareProviders/index.ts diff --git a/.gitignore b/.gitignore index 1e47279..198b5f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /node_modules /dist .DS_Store -*.log \ No newline at end of file +*.log +*.json \ No newline at end of file diff --git a/demo/index.ts b/demo/index.ts index b7274ea..4fd3723 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -1,4 +1,4 @@ -// import runServer from "./runServer"; +import runServer from "./runServer"; import runClient from "./runClient"; (async () => { @@ -10,6 +10,6 @@ import runClient from "./runClient"; SHARE: share = "test" } = process.env; - // await runServer(); - await runClient(host, domain, username, password, share); + await runServer(); + // await runClient(host, domain, username, password, share); })(); \ No newline at end of file diff --git a/demo/runServer.ts b/demo/runServer.ts index 1fc8245..acb6698 100644 --- a/demo/runServer.ts +++ b/demo/runServer.ts @@ -1,7 +1,22 @@ -import { Server } from "../src"; +import { + Server, + serverAuthenticationProviders, + serverShareProviders +} from "../src"; + +const { FileAuthenticationProvider } = serverAuthenticationProviders; +const { FileShareProvider } = serverShareProviders; export default async () => { const server = new Server(); + + const authenticationProvider = new FileAuthenticationProvider(`./users.json`); + server.use(authenticationProvider); + + const shareProvider = new FileShareProvider(`./shares.json`); + server.use(shareProvider); + + await server.init(); await server.listen(); - console.log("server started", server.port); + console.log("server started on port", server.port); }; \ No newline at end of file diff --git a/src/client/Client.ts b/src/client/Client.ts index e404052..a3f3353 100644 --- a/src/client/Client.ts +++ b/src/client/Client.ts @@ -1,14 +1,14 @@ import crypto from "crypto"; import { Socket } from "net"; import { EventEmitter } from "events"; -import Packet from "../protocol/Packet"; -import Request from "../protocol/smb2/Request"; -import Response from "../protocol/smb2/Response"; -import Header from "../protocol/smb2/Header"; -import StatusCode from "../protocol/smb2/StatusCode"; -import Smb2PacketType from "../protocol/smb2/PacketType"; +import Packet from "../protocols/Packet"; +import Header from "../protocols/smb2/Header"; +import Request from "../protocols/smb2/Request"; +import Response from "../protocols/smb2/Response"; +import StatusCode from "../protocols/smb2/StatusCode"; import Session, { AuthenticateOptions } from "./Session"; -import * as structureUtil from "../protocol/structureUtil"; +import Smb2PacketType from "../protocols/smb2/PacketType"; +import * as structureUtil from "../protocols/structureUtil"; export interface Options { port?: number; diff --git a/src/client/Directory.ts b/src/client/Directory.ts index 649e59c..466c321 100644 --- a/src/client/Directory.ts +++ b/src/client/Directory.ts @@ -1,18 +1,18 @@ import Tree from "./Tree"; import { EventEmitter } from "events"; -import * as util from "../protocol/util"; -import Response from "../protocol/smb2/Response"; -import StatusCode from "../protocol/smb2/StatusCode"; -import PacketType from "../protocol/smb2/PacketType"; -import FileAttribute from "../protocol/smb2/FileAttribute"; -import ShareAccessType from "../protocol/smb2/ShareAccessType"; -import DirectoryAccess from "../protocol/smb2/DirectoryAccess"; -import { CreateOptions } from "../protocol/smb2/packets/Create"; -import * as structureUtil from "../protocol/structureUtil"; -import DirectoryEntry from "../protocol/models/DirectoryEntry"; -import { InfoType, FileInfoClass } from "../protocol/smb2/packets/SetInfo"; -import CreateDispositionType from "../protocol/smb2/CreateDispositionType"; -import { Flags as ChangeNotifyFlags } from "../protocol/smb2/packets/ChangeNotify"; +import * as util from "../protocols/util"; +import Response from "../protocols/smb2/Response"; +import StatusCode from "../protocols/smb2/StatusCode"; +import PacketType from "../protocols/smb2/PacketType"; +import FileAttribute from "../protocols/smb2/FileAttribute"; +import ShareAccessType from "../protocols/smb2/ShareAccessType"; +import DirectoryAccess from "../protocols/smb2/DirectoryAccess"; +import { CreateOptions } from "../protocols/smb2/packets/Create"; +import * as structureUtil from "../protocols/structureUtil"; +import DirectoryEntry from "../models/DirectoryEntry"; +import { InfoType, FileInfoClass } from "../protocols/smb2/packets/SetInfo"; +import CreateDispositionType from "../protocols/smb2/CreateDispositionType"; +import { Flags as ChangeNotifyFlags } from "../protocols/smb2/packets/ChangeNotify"; interface OpenOptions { desiredAccess?: DirectoryAccess; diff --git a/src/client/File.ts b/src/client/File.ts index ad7d0ea..cf1a7f9 100644 --- a/src/client/File.ts +++ b/src/client/File.ts @@ -1,14 +1,14 @@ import Tree from "./Tree"; import { EventEmitter } from "events"; -import * as util from "../protocol/util"; -import StatusCode from "../protocol/smb2/StatusCode"; -import PacketType from "../protocol/smb2/PacketType"; -import FileAttribute from "../protocol/smb2/FileAttribute"; -import ShareAccessType from "../protocol/smb2/ShareAccessType"; -import { CreateOptions } from "../protocol/smb2/packets/Create"; -import CreateDispositionType from "../protocol/smb2/CreateDispositionType"; -import FilePipePrinterAccess from "../protocol/smb2/FilePipePrinterAccess"; -import { FileInfoClass, InfoType } from "../protocol/smb2/packets/SetInfo"; +import * as util from "../protocols/util"; +import StatusCode from "../protocols/smb2/StatusCode"; +import PacketType from "../protocols/smb2/PacketType"; +import FileAttribute from "../protocols/smb2/FileAttribute"; +import ShareAccessType from "../protocols/smb2/ShareAccessType"; +import { CreateOptions } from "../protocols/smb2/packets/Create"; +import CreateDispositionType from "../protocols/smb2/CreateDispositionType"; +import FilePipePrinterAccess from "../protocols/smb2/FilePipePrinterAccess"; +import { FileInfoClass, InfoType } from "../protocols/smb2/packets/SetInfo"; const maxReadChunkLength = 0x00010000; const maxWriteChunkLength = 0x00010000 - 0x71; diff --git a/src/client/Session.ts b/src/client/Session.ts index 1e410a1..3638e51 100644 --- a/src/client/Session.ts +++ b/src/client/Session.ts @@ -1,10 +1,10 @@ import Tree from "./Tree"; import Client from "./Client"; import { EventEmitter } from "events"; -import Dialect from "../protocol/smb2/Dialect"; -import Header from "../protocol/smb2/Header"; -import * as ntlmUtil from "../protocol/ntlm/util"; -import PacketType from "../protocol/smb2/PacketType"; +import Dialect from "../protocols/smb2/Dialect"; +import Header from "../protocols/smb2/Header"; +import * as ntlmUtil from "../protocols/ntlm/util"; +import PacketType from "../protocols/smb2/PacketType"; export interface AuthenticateOptions { domain: string; @@ -68,19 +68,20 @@ class Session extends EventEmitter { }); const sessionSetupResponse = await this.request( { type: PacketType.SessionSetup }, - { buffer: ntlmUtil.encodeNegotiationMessage(this.client.host, options.domain) } + { buffer: ntlmUtil.serializeNegotiationMessage(this.client.host, options.domain) } ); this._id = sessionSetupResponse.header.sessionId; - const nonce = ntlmUtil.decodeChallengeMessage(sessionSetupResponse.body.buffer as Buffer); + const challengeMessage = ntlmUtil.parseChallengeMessage(sessionSetupResponse.body.buffer as Buffer); + await this.request( { type: PacketType.SessionSetup }, { - buffer: ntlmUtil.encodeAuthenticationMessage( + buffer: ntlmUtil.serializeAuthenticationMessage( options.username, this.client.host, options.domain, - nonce, + challengeMessage.serverChallenge, options.password ) } diff --git a/src/client/Tree.ts b/src/client/Tree.ts index 28a869e..e52c6b0 100644 --- a/src/client/Tree.ts +++ b/src/client/Tree.ts @@ -2,12 +2,12 @@ import File from "./File"; import Session from "./Session"; import Directory from "./Directory"; import { EventEmitter } from "events"; -import Header from "../protocol/smb2/Header"; -import * as util from "../protocol/util"; -import Response from "../protocol/smb2/Response"; -import PacketType from "../protocol/smb2/PacketType"; -import DirectoryAccess from "../protocol/smb2/DirectoryAccess"; -import FilePipePrinterAccess from "../protocol/smb2/FilePipePrinterAccess"; +import Header from "../protocols/smb2/Header"; +import * as util from "../protocols/util"; +import Response from "../protocols/smb2/Response"; +import PacketType from "../protocols/smb2/PacketType"; +import DirectoryAccess from "../protocols/smb2/DirectoryAccess"; +import FilePipePrinterAccess from "../protocols/smb2/FilePipePrinterAccess"; interface Tree { on(event: "connect" | "disconnect", callback: (tree: Tree) => void): this; diff --git a/src/index.ts b/src/index.ts index 4f408a4..a42131f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,33 @@ import { default as Client } from "./client/Client"; -// import { default as Server } from "./server/Server"; + +import { default as Server } from "./server/Server"; + +import ServerAuthenticationProvider from "./server/AuthenticationProvider"; +import * as serverAuthenticationProviders from "./server/authenticationProviders"; + +import ServerShareProvider from "./server/ShareProvider"; +import * as serverShareProviders from "./server/shareProviders"; export { Client, - // Server + + Server, + + ServerAuthenticationProvider, + serverAuthenticationProviders, + + ServerShareProvider, + serverShareProviders }; export default { Client, - // Server + + Server, + + ServerAuthenticationProvider, + serverAuthenticationProviders, + + ServerShareProvider, + serverShareProviders }; \ No newline at end of file diff --git a/src/protocol/models/ChangeEntry.ts b/src/models/ChangeEntry.ts similarity index 65% rename from src/protocol/models/ChangeEntry.ts rename to src/models/ChangeEntry.ts index 8349047..7481cfb 100644 --- a/src/protocol/models/ChangeEntry.ts +++ b/src/models/ChangeEntry.ts @@ -1,4 +1,4 @@ -import FileAction from "../smb2/FileAction"; +import FileAction from "../protocols/smb2/FileAction"; export default interface ChangeEntry { action: FileAction; diff --git a/src/protocol/models/DirectoryEntry.ts b/src/models/DirectoryEntry.ts similarity index 100% rename from src/protocol/models/DirectoryEntry.ts rename to src/models/DirectoryEntry.ts diff --git a/src/protocol/ntlm/util.ts b/src/protocol/ntlm/util.ts deleted file mode 100644 index 4a610df..0000000 --- a/src/protocol/ntlm/util.ts +++ /dev/null @@ -1,333 +0,0 @@ -import crypto from "crypto"; -import NegotiateFlag from "./NegotiateFlag"; - -export const encodeNegotiationMessage = (hostname: string, domain: string) => { - hostname = hostname.toUpperCase(); - domain = domain.toUpperCase(); - - const hostnameLength = Buffer.byteLength(hostname, "ascii"); - const domainLength = Buffer.byteLength(domain, "ascii"); - - let offset = 0; - const buffer = Buffer.alloc(32 + hostnameLength + domainLength); - - buffer.write("NTLMSSP", offset, 7, "ascii"); - offset += 7; - buffer.writeUInt8(0, offset); - offset += 1; - - buffer.writeUInt32LE(1, offset); - offset += 4; - - const negotiateFlags = NegotiateFlag.UnicodeEncoding | NegotiateFlag.NTLMSessionSecurity | NegotiateFlag.AlwaysSign; - buffer.writeUInt32LE(negotiateFlags, offset); - offset += 4; - - buffer.writeUInt16LE(domainLength, offset); - offset += 2; - buffer.writeUInt16LE(domainLength, offset); - offset += 2; - - const domainOffset = 0x20 + hostnameLength; - buffer.writeUInt32LE(domainOffset, offset); - offset += 4; - - buffer.writeUInt16LE(hostnameLength, offset); - offset += 2; - buffer.writeUInt16LE(hostnameLength, offset); - offset += 2; - - buffer.writeUInt32LE(0x20, offset); - offset += 4; - - buffer.write(hostname, 0x20, hostnameLength, "ascii"); - buffer.write(domain, domainOffset, domainLength, "ascii"); - - return buffer; -}; - -export const decodeNegotiationMessage = (buffer: Buffer) => { - let offset = 0; - - const protocol = buffer.slice(0, 7).toString("ascii"); - if ( - protocol !== "NTLMSSP" || - buffer.readInt8(7) !== 0x00 - ) throw new Error("ntlmssp_header_not_found"); - offset += 8; - - const type = buffer.readUInt32LE(offset); - if (type !== 0x01) throw new Error("ntlmssp_type_is_not_one"); - offset += 4; - - const negotiateFlags = buffer.readUInt32LE(offset); - offset += 4; - - const domainLength = buffer.readUInt16LE(offset); - offset += 2; - const domainMaxLength = buffer.readUInt16LE(offset); - offset += 2; - const domainOffset = buffer.readUInt32LE(offset); - offset += 4; - - const hostnameLength = buffer.readUInt16LE(offset); - offset += 2; - const hostnameMaxLength = buffer.readUInt16LE(offset); - offset += 2; - const hostnameOffset = buffer.readUInt32LE(offset); - offset += 4; - - const domain = buffer.slice(domainOffset, domainOffset + domainLength).toString("ascii"); - const hostname = buffer.slice(hostnameOffset, hostnameOffset + hostnameLength).toString("ascii"); - - return { - negotiateFlags, - domain, - hostname - }; -}; - -export const encodeChallengeMessage = (negotiateFlags: number) => { - let offset = 0; - const buffer = Buffer.alloc(64); - - buffer.write("NTLMSSP", offset, 7, "ascii"); - offset += 7; - buffer.writeUInt8(0, offset); - offset += 1; - - buffer.writeUInt32LE(2, offset); - offset += 4; - - buffer.writeUInt16LE(0, offset); - offset += 2; - - buffer.writeUInt16LE(0, offset); - offset += 2; - - buffer.writeUInt32LE(0, offset); - offset += 4; - - buffer.writeUInt32LE(negotiateFlags, offset); - offset += 4; - - generateServerChallenge().copy(buffer, offset); - offset += 8; - - buffer.fill(0, offset, offset + 8); - offset += 8; - - return buffer; -}; - -export const decodeChallengeMessage = (buffer: Buffer) => { - let offset = 0; - - const protocol = buffer.slice(0, 7).toString("ascii"); - if ( - protocol !== "NTLMSSP" || - buffer.readInt8(7) !== 0x00 - ) throw new Error("ntlmssp_header_not_found"); - offset += 8; - - const type = buffer.readUInt32LE(offset); - if (type !== 0x02) throw new Error("ntlmssp_type_is_not_two"); - offset += 4; - - const targetNameLength = buffer.readUInt16LE(offset); - offset += 2; - - const targetNameMaxLength = buffer.readUInt16LE(offset); - offset += 2; - - const targetNameOffset = buffer.readUInt32LE(offset); - offset += 4; - - const negotiateFlags = buffer.readUInt32LE(offset); - offset += 4; - - const serverChallenge = buffer.slice(offset, offset + 8); - offset += 8; - - offset += 8; // Reserved - - return serverChallenge; -} - -export const encodeAuthenticationMessage = (username: string, hostname: string, domain: string, nonce: Buffer, password: string) => { - hostname = hostname.toUpperCase(); - domain = domain.toUpperCase(); - - const lmHash = Buffer.alloc(21); - createLmHash(password).copy(lmHash); - lmHash.fill(0x00, 16); - - const ntHash = Buffer.alloc(21); - createNtHash(password).copy(ntHash); - ntHash.fill(0x00, 16); - - const lmResponse = createResponse(lmHash, nonce); - const ntResponse = createResponse(ntHash, nonce); - - const usernameLength = Buffer.byteLength(username, "ucs2"); - const hostnameLength = Buffer.byteLength(hostname, "ucs2"); - const domainLength = Buffer.byteLength(domain, "ucs2"); - const lmResponseLength = 0x18; - const ntResponseLength = 0x18; - - const domainOffset = 0x40; - const usernameOffset = domainOffset + domainLength; - const hostnameOffset = usernameOffset + usernameLength; - const lmResponseOffset = hostnameOffset + hostnameLength; - const ntResponseOffset = lmResponseOffset + lmResponseLength; - - let offset = 0; - const messageLength = 64 + domainLength + usernameLength + hostnameLength + lmResponseLength + ntResponseLength; - const buffer = Buffer.alloc(messageLength); - - buffer.write("NTLMSSP", offset, 7, "ascii"); // byte protocol[8]; - offset += 7; - buffer.writeUInt8(0, offset); - offset++; - - buffer.writeUInt8(0x03, offset); // byte type; - offset++; - - buffer.fill(0x00, offset, offset + 3); // byte zero[3]; - offset += 3; - - buffer.writeUInt16LE(lmResponseLength, offset); // short lm_resp_len; - offset += 2; - buffer.writeUInt16LE(lmResponseLength, offset); // short lm_resp_len; - offset += 2; - buffer.writeUInt16LE(lmResponseOffset, offset); // short lm_resp_off; - offset += 2; - buffer.fill(0x00, offset, offset + 2); // byte zero[2]; - offset += 2; - - buffer.writeUInt16LE(ntResponseLength, offset); // short nt_resp_len; - offset += 2; - buffer.writeUInt16LE(ntResponseLength, offset); // short nt_resp_len; - offset += 2; - buffer.writeUInt16LE(ntResponseOffset, offset); // short nt_resp_off; - offset += 2; - buffer.fill(0x00, offset, offset + 2); // byte zero[2]; - offset += 2; - - buffer.writeUInt16LE(domainLength, offset); // short dom_len; - offset += 2; - buffer.writeUInt16LE(domainLength, offset); // short dom_len; - offset += 2; - buffer.writeUInt16LE(domainOffset, offset); // short dom_off; - offset += 2; - buffer.fill(0x00, offset, offset + 2); // byte zero[2]; - offset += 2; - - buffer.writeUInt16LE(usernameLength, offset); // short user_len; - offset += 2; - buffer.writeUInt16LE(usernameLength, offset); // short user_len; - offset += 2; - buffer.writeUInt16LE(usernameOffset, offset); // short user_off; - offset += 2; - buffer.fill(0x00, offset, offset + 2); // byte zero[2]; - offset += 2; - - buffer.writeUInt16LE(hostnameLength, offset); // short host_len; - offset += 2; - buffer.writeUInt16LE(hostnameLength, offset); // short host_len; - offset += 2; - buffer.writeUInt16LE(hostnameOffset, offset); // short host_off; - offset += 2; - buffer.fill(0x00, offset, offset + 6); // byte zero[6]; - offset += 6; - - buffer.writeUInt16LE(messageLength, offset); // short msg_len; - offset += 2; - buffer.fill(0x00, offset, offset + 2); // byte zero[2]; - offset += 2; - - const negotiateFlags = NegotiateFlag.UnicodeEncoding | NegotiateFlag.NTLMSessionSecurity | NegotiateFlag.AlwaysSign; - buffer.writeUInt32LE(negotiateFlags, offset); - offset += 4; - - buffer.write(domain, domainOffset, domainLength, "ucs2"); - buffer.write(username, usernameOffset, usernameLength, "ucs2"); - buffer.write(hostname, hostnameOffset, hostnameLength, "ucs2"); - lmResponse.copy(buffer, lmResponseOffset, 0, lmResponseLength); - ntResponse.copy(buffer, ntResponseOffset, 0, ntResponseLength); - - return buffer; -}; - -export const generateServerChallenge = () => { - return crypto.randomBytes(8); -}; - -const fixOddParity = (buffer: Buffer) => { - for (let index = 0; index < buffer.length; index++) { - let parity = 1; - for (let index2 = 1; index2 < 8; index2++) { - parity = (parity + ((buffer[index] >> index2) & 1)) % 2; - } - buffer[index] |= parity & 1; - } - return buffer; -}; - -const createDESKey = (key56: Buffer) => { - const key64 = Buffer.alloc(8); - - key64[0] = key56[0] & 0xFE; - key64[1] = ((key56[0] << 7) & 0xFF) | (key56[1] >> 1); - key64[2] = ((key56[1] << 6) & 0xFF) | (key56[2] >> 2); - key64[3] = ((key56[2] << 5) & 0xFF) | (key56[3] >> 3); - key64[4] = ((key56[3] << 4) & 0xFF) | (key56[4] >> 4); - key64[5] = ((key56[4] << 3) & 0xFF) | (key56[5] >> 5); - key64[6] = ((key56[5] << 2) & 0xFF) | (key56[6] >> 6); - key64[7] = (key56[6] << 1) & 0xFF; - - return key64; -}; - -const createLmHash = (text: string) => { - const upperCaseText = text.substring(0, 14).toUpperCase(); - const upperCaseTextLength = Buffer.byteLength(upperCaseText, "ascii"); - - const paddingBuffer = Buffer.alloc(14); - paddingBuffer.write(upperCaseText, 0, upperCaseTextLength, "ascii"); - paddingBuffer.fill(0, upperCaseTextLength); - - const halves = [ - fixOddParity(createDESKey(paddingBuffer.slice(0, 7))), - fixOddParity(createDESKey(paddingBuffer.slice(7, 14))) - ]; - - const buffer = Buffer.alloc(16); - let offset = 0; - for (const halve of halves) { - const cipher = crypto.createCipheriv("DES-ECB", halve, ""); - const string = cipher.update("KGS!@#$%", "binary", "binary"); - buffer.write(string, offset, offset + 8, "binary"); - offset += 8; - } - - return buffer; -}; - -const createNtHash = (str: string) => { - const ucs2 = Buffer.from(str, "ucs2"); - const md4 = crypto.createHash("md4"); - md4.update(ucs2); - return Buffer.from(md4.digest("hex"), "hex"); -}; - -const createResponse = (hash: Buffer, nonce: Buffer) => { - const buffer = Buffer.alloc(24); - for (let index = 0; index < 3; index++) { - const keyBuffer = fixOddParity(createDESKey(hash.slice(index * 7, index * 7 + 7))); - const cipher = crypto.createCipheriv("DES-ECB", keyBuffer, ""); - const string = cipher.update(nonce.toString("binary"), "binary", "binary"); - buffer.write(string, index * 8, index * 8 + 8, "binary"); - } - return buffer; -}; \ No newline at end of file diff --git a/src/protocol/smb2/Capability.ts b/src/protocol/smb2/Capability.ts deleted file mode 100644 index 1b48003..0000000 --- a/src/protocol/smb2/Capability.ts +++ /dev/null @@ -1,6 +0,0 @@ -enum Capability { - DistributedFileSystem = 0x00000001, - MultiCreditSupport = 0x00000004 -} - -export default Capability; \ No newline at end of file diff --git a/src/protocol/Packet.ts b/src/protocols/Packet.ts similarity index 91% rename from src/protocol/Packet.ts rename to src/protocols/Packet.ts index 75e1bb9..50f372c 100644 --- a/src/protocol/Packet.ts +++ b/src/protocols/Packet.ts @@ -1,4 +1,4 @@ -import * as protocolIds from "./protocolIds"; +import * as protocolIds from "./ProtocolIds"; import StructureField from "./StructureField"; import * as structureUtil from "./structureUtil"; @@ -6,7 +6,7 @@ export const protocolIdStructureField: StructureField = { type: String, encoding: "hex", size: 4, - defaultValue: protocolIds.smb2 + defaultValue: protocolIds.Smb2 }; export default class Packet { diff --git a/src/protocol/protocolIds.ts b/src/protocols/ProtocolIds.ts similarity index 81% rename from src/protocol/protocolIds.ts rename to src/protocols/ProtocolIds.ts index 3442d5d..adb68f2 100644 --- a/src/protocol/protocolIds.ts +++ b/src/protocols/ProtocolIds.ts @@ -1,4 +1,4 @@ -export const smb = Buffer +export const Smb = Buffer .from([ 0xff, "S".charCodeAt(0), @@ -7,7 +7,7 @@ export const smb = Buffer ]) .toString("hex"); -export const smb2 = Buffer +export const Smb2 = Buffer .from([ 0xfe, "S".charCodeAt(0), diff --git a/src/protocol/Request.ts b/src/protocols/Request.ts similarity index 100% rename from src/protocol/Request.ts rename to src/protocols/Request.ts diff --git a/src/protocol/Response.ts b/src/protocols/Response.ts similarity index 100% rename from src/protocol/Response.ts rename to src/protocols/Response.ts diff --git a/src/protocol/Structure.ts b/src/protocols/Structure.ts similarity index 100% rename from src/protocol/Structure.ts rename to src/protocols/Structure.ts diff --git a/src/protocol/StructureField.ts b/src/protocols/StructureField.ts similarity index 92% rename from src/protocol/StructureField.ts rename to src/protocols/StructureField.ts index 41e27fe..df1a256 100644 --- a/src/protocol/StructureField.ts +++ b/src/protocols/StructureField.ts @@ -9,6 +9,7 @@ type StructureField = { count?: number; sizeFieldName?: string; size?: number; + offsetFieldName?: string; defaultValue?: Value; }; diff --git a/src/protocol/Value.ts b/src/protocols/Value.ts similarity index 100% rename from src/protocol/Value.ts rename to src/protocols/Value.ts diff --git a/src/protocols/dtyp/util.ts b/src/protocols/dtyp/util.ts new file mode 100644 index 0000000..93d3357 --- /dev/null +++ b/src/protocols/dtyp/util.ts @@ -0,0 +1,13 @@ +import moment from "moment-timezone"; + +export const parseFiletime = (buffer: Buffer) => { + const milliseconds = Number(buffer.readBigUInt64LE(0) / 10000n); + return moment.utc("1601-01-01").add(milliseconds, "milliseconds").toDate(); +}; + +export const serializeFiletime = (date: Date) => { + const milliseconds = moment(date).diff(moment.utc("1601-01-01"), "milliseconds"); + const buffer = Buffer.allocUnsafe(8); + buffer.writeBigInt64LE(BigInt(milliseconds) * 10000n, 0); + return buffer; +}; \ No newline at end of file diff --git a/src/protocols/ntlm/MessageType.ts b/src/protocols/ntlm/MessageType.ts new file mode 100644 index 0000000..7dac97a --- /dev/null +++ b/src/protocols/ntlm/MessageType.ts @@ -0,0 +1,7 @@ +enum MessageType { + Negotiation = 1, + Challenge = 2, + Authentication = 3 +} + +export default MessageType; \ No newline at end of file diff --git a/src/protocol/ntlm/NegotiateFlag.ts b/src/protocols/ntlm/NegotiateFlag.ts similarity index 100% rename from src/protocol/ntlm/NegotiateFlag.ts rename to src/protocols/ntlm/NegotiateFlag.ts diff --git a/src/protocols/ntlm/Version.ts b/src/protocols/ntlm/Version.ts new file mode 100644 index 0000000..f25613e --- /dev/null +++ b/src/protocols/ntlm/Version.ts @@ -0,0 +1,62 @@ +export const Server2003Revision = 0x0F; + +export default class Version { + public static WindowsXP = new Version(5, 1, 2600, Server2003Revision); + public static Server2003 = new Version(5, 2, 3790, Server2003Revision); + + constructor( + public majorVersion: number, + public minorVersion: number, + public buildNumber: number, + public revision: number + ) { } + + serialize() { + let offset = 0; + + const buffer = Buffer.allocUnsafe(8); + + buffer.writeInt8(this.majorVersion, offset); + offset += 1; + + buffer.writeInt8(this.minorVersion, offset); + offset += 1; + + buffer.writeUInt16LE(this.buildNumber, offset); + offset += 2; + + buffer.writeInt8(0, offset); + offset += 1; + + buffer.writeInt8(0, offset); + offset += 1; + + buffer.writeInt8(0, offset); + offset += 1; + + buffer.writeInt8(this.revision, offset); + offset += 1; + + return buffer; + } + + static parse(buffer: Buffer) { + let offset = 0; + + const majorVersion = buffer.readInt8(offset); + offset += 1; + + const minorVersion = buffer.readInt8(offset); + offset += 1; + + const buildNumber = buffer.readUInt16LE(offset); + offset += 2; + + offset += 3; + + const revision = buffer.readInt8(offset); + offset += 1; + + return new Version(majorVersion, minorVersion, buildNumber, revision); + } +} \ No newline at end of file diff --git a/src/protocols/ntlm/attributeValue/AttributeValueId.ts b/src/protocols/ntlm/attributeValue/AttributeValueId.ts new file mode 100644 index 0000000..5918d02 --- /dev/null +++ b/src/protocols/ntlm/attributeValue/AttributeValueId.ts @@ -0,0 +1,15 @@ +enum AttributeValueId { + EndOfLine = 0x0000, + NetBiosComputerName = 0x0001, + NetBiosDomainName = 0x0002, + DnsComputerName = 0x0003, + DnsDomainName = 0x0004, + DnsTreeName = 0x0005, + Flags = 0x0006, + Timestamp = 0x0007, + SingleHost = 0x0008, + TargetName = 0x0009, + ChannelBindings = 0x000A +} + +export default AttributeValueId; \ No newline at end of file diff --git a/src/protocols/ntlm/attributeValue/AttributeValuePair.ts b/src/protocols/ntlm/attributeValue/AttributeValuePair.ts new file mode 100644 index 0000000..6555eef --- /dev/null +++ b/src/protocols/ntlm/attributeValue/AttributeValuePair.ts @@ -0,0 +1,7 @@ +import AttributeValueId from "./AttributeValueId"; + +export default interface AttributeValuePair { + id: AttributeValueId; + buffer?: Buffer; + value?: string | Date; +}; \ No newline at end of file diff --git a/src/protocols/ntlm/attributeValue/util.ts b/src/protocols/ntlm/attributeValue/util.ts new file mode 100644 index 0000000..d78a254 --- /dev/null +++ b/src/protocols/ntlm/attributeValue/util.ts @@ -0,0 +1,111 @@ +import * as dtypUtil from "../../dtyp/util"; +import AttributeValueId from "./AttributeValueId"; +import AttributeValuePair from "./AttributeValuePair"; +import moment from "moment-timezone"; + +export const serializePairs = (pairs: AttributeValuePair[]) => { + for (const pair of pairs) { + serializePair(pair); + } + + const length = pairs.reduce((prev, current) => + prev + (current.buffer.length + 4), + 0 + ) + 4; + + const buffer = Buffer.allocUnsafe(length); + let offset = 0; + for (const pair of pairs) { + buffer.writeUInt16LE(pair.id, offset); + offset += 2; + + buffer.writeUInt16LE(pair.buffer.length, offset); + offset += 2; + + pair.buffer.copy(buffer, offset); + offset += pair.buffer.length; + } + + buffer.writeUInt16LE(AttributeValueId.EndOfLine, offset); + offset += 2; + + buffer.writeUInt16LE(0, offset); + offset += 2; + + return buffer; +}; + +export const serializePair = (pair: AttributeValuePair) => { + if (Buffer.isBuffer(pair.buffer)) return pair; + + if ( + pair.id === AttributeValueId.NetBiosComputerName || + pair.id === AttributeValueId.NetBiosDomainName || + pair.id === AttributeValueId.DnsComputerName || + pair.id === AttributeValueId.DnsDomainName || + pair.id === AttributeValueId.DnsTreeName + ) { + pair.buffer = Buffer.from(pair.value as string, "ucs2"); + delete pair.value; + } else if (pair.id === AttributeValueId.Timestamp) { + pair.buffer = dtypUtil.serializeFiletime(pair.value as Date); + delete pair.value; + } + + return pair; +}; + +export const parsePairs = (buffer: Buffer) => { + let offset = 0; + + let lastId: AttributeValueId; + + const pairs: AttributeValuePair[] = []; + while (AttributeValueId.EndOfLine !== lastId) { + lastId = buffer.readUInt16LE(offset); + offset += 2; + + const length = buffer.readUInt16LE(offset); + offset += 2; + + const data = buffer.slice(offset, offset + length); + offset += length; + + const pair = { + id: lastId, + buffer: data + }; + + parsePair(pair); + + pairs.push(pair); + } + + offset += 4; + + pairs.pop(); + + return pairs; +}; + +export const parsePair = (pair: AttributeValuePair) => { + if ( + pair.id === AttributeValueId.NetBiosComputerName || + pair.id === AttributeValueId.NetBiosDomainName || + pair.id === AttributeValueId.DnsComputerName || + pair.id === AttributeValueId.DnsDomainName || + pair.id === AttributeValueId.DnsTreeName + ) { + if (typeof pair.value === "string") return pair; + + pair.value = pair.buffer.toString("ucs2"); + delete pair.buffer; + } else if (pair.id === AttributeValueId.Timestamp) { + if (pair.value instanceof Date) return pair; + + pair.value = dtypUtil.parseFiletime(pair.buffer); + delete pair.buffer; + } + + return pair; +}; \ No newline at end of file diff --git a/src/protocols/ntlm/util.ts b/src/protocols/ntlm/util.ts new file mode 100644 index 0000000..28444db --- /dev/null +++ b/src/protocols/ntlm/util.ts @@ -0,0 +1,560 @@ +import crypto from "crypto"; +import Version from "./Version"; +import MessageType from "./MessageType"; +import NegotiateFlag from "./NegotiateFlag"; +import * as attributeValueUtil from "./attributeValue/util"; +import AttributeValuePair from "./attributeValue/AttributeValuePair"; + +export const serializeNegotiationMessage = (hostname: string, domain: string) => { + hostname = hostname.toUpperCase(); + domain = domain.toUpperCase(); + + const hostnameLength = Buffer.byteLength(hostname, "ascii"); + const domainLength = Buffer.byteLength(domain, "ascii"); + + let offset = 0; + const buffer = Buffer.alloc(32 + hostnameLength + domainLength); + + buffer.write("NTLMSSP", offset, 7, "ascii"); + offset += 7; + buffer.writeUInt8(0, offset); + offset += 1; + + buffer.writeUInt32LE(MessageType.Negotiation, offset); + offset += 4; + + const negotiateFlags = NegotiateFlag.UnicodeEncoding | + NegotiateFlag.NTLMSessionSecurity | + NegotiateFlag.AlwaysSign; + buffer.writeUInt32LE(negotiateFlags, offset); + offset += 4; + + buffer.writeUInt16LE(domainLength, offset); + offset += 2; + buffer.writeUInt16LE(domainLength, offset); + offset += 2; + + const domainOffset = 0x20 + hostnameLength; + buffer.writeUInt32LE(domainOffset, offset); + offset += 4; + + buffer.writeUInt16LE(hostnameLength, offset); + offset += 2; + buffer.writeUInt16LE(hostnameLength, offset); + offset += 2; + + buffer.writeUInt32LE(0x20, offset); + offset += 4; + + buffer.write(hostname, 0x20, hostnameLength, "ascii"); + buffer.write(domain, domainOffset, domainLength, "ascii"); + + return buffer; +}; + +export const parseNegotiationMessage = (buffer: Buffer) => { + let offset = 0; + + const protocol = buffer.slice(0, 7).toString("ascii"); + if ( + protocol !== "NTLMSSP" || + buffer.readInt8(7) !== 0x00 + ) throw new Error("ntlmssp_header_not_found"); + offset += 8; + + const type = parseMessageType(buffer); + if (type !== MessageType.Negotiation) throw new Error("ntlm_message_type_is_not_negotiation"); + offset += 4; + + const negotiateFlags = buffer.readUInt32LE(offset); + offset += 4; + + const domainLength = buffer.readUInt16LE(offset); + offset += 2; + const domainMaxLength = buffer.readUInt16LE(offset); + offset += 2; + const domainOffset = buffer.readUInt32LE(offset); + offset += 4; + + const hostnameLength = buffer.readUInt16LE(offset); + offset += 2; + const hostnameMaxLength = buffer.readUInt16LE(offset); + offset += 2; + const hostnameOffset = buffer.readUInt32LE(offset); + offset += 4; + + const domain = buffer.slice(domainOffset, domainOffset + domainLength).toString("ascii"); + const hostname = buffer.slice(hostnameOffset, hostnameOffset + hostnameLength).toString("ascii"); + + return { + negotiateFlags, + domain, + hostname + }; +}; + +export const serializeChallengeMessage = ( + targetName: string, + targetInfo: AttributeValuePair[], + negotiateFlags: number, + serverChallenge: Buffer +) => { + let targetNameBuffer: Buffer; + if ((negotiateFlags & NegotiateFlag.TargetNameSupplied) === 0) { + targetNameBuffer = Buffer.from([]); + } else { + targetNameBuffer = Buffer.from(targetName, "ucs2"); + } + + let targetInfoBuffer: Buffer; + if ((negotiateFlags & NegotiateFlag.TargetInfo) === 0) { + targetInfoBuffer = Buffer.from([]); + } else { + targetInfoBuffer = attributeValueUtil.serializePairs(targetInfo); + } + + let bufferLength = 48; + const shouldSupplyVersion = (negotiateFlags & NegotiateFlag.Version) > 0; + if (shouldSupplyVersion) { + bufferLength += 8; + } + + let offset = 0; + const buffer = Buffer.alloc( + bufferLength + + targetNameBuffer.length + + targetInfoBuffer.length + ); + + buffer.write("NTLMSSP", offset, 7, "ascii"); + offset += 7; + buffer.writeUInt8(0, offset); + offset += 1; + + buffer.writeUInt32LE(MessageType.Challenge, offset); + offset += 4; + + const targetNameBufferPointerOffset = offset; + offset += 8; + + buffer.writeUInt32LE(negotiateFlags, offset); + offset += 4; + + serverChallenge.copy(buffer, offset); + offset += 8; + + buffer.fill(0, offset, offset + 8); + offset += 8; + + const targetInfoBufferPointerOffset = offset; + offset += 8; + + if (shouldSupplyVersion) { + Version.Server2003.serialize().copy(buffer, offset); + offset += 8; + } + + serializeBufferPointer(targetNameBuffer.length, offset).copy(buffer, targetNameBufferPointerOffset); + targetNameBuffer.copy(buffer, offset); + offset += targetNameBuffer.length; + + serializeBufferPointer(targetInfoBuffer.length, offset).copy(buffer, targetInfoBufferPointerOffset); + targetInfoBuffer.copy(buffer, offset); + offset += targetInfoBuffer.length; + + return buffer; +}; + +export const parseChallengeMessage = (buffer: Buffer) => { + let offset = 0; + + const protocol = buffer.slice(0, 7).toString("ascii"); + if ( + protocol !== "NTLMSSP" || + buffer.readInt8(7) !== 0x00 + ) throw new Error("ntlmssp_header_not_found"); + offset += 8; + + const type = parseMessageType(buffer); + if (type !== MessageType.Challenge) throw new Error("ntlm_message_type_is_not_challenge"); + offset += 4; + + const targetNameBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); + const targetName = buffer.slice( + targetNameBufferPointer.offset, + targetNameBufferPointer.offset + targetNameBufferPointer.length + ).toString("ucs2"); + offset += 8; + + const negotiateFlags = buffer.readUInt32LE(offset); + offset += 4; + + const serverChallenge = buffer.slice(offset, offset + 8); + offset += 8; + + offset += 8; // Reserved + + const targetInfoBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); + const targetInfo = attributeValueUtil.parsePairs( + buffer.slice( + targetInfoBufferPointer.offset, + targetInfoBufferPointer.offset + targetInfoBufferPointer.length + ) + ); + offset += 8; + + let version: Version; + if ((negotiateFlags & NegotiateFlag.Version) > 0) { + version = Version.parse(buffer.slice(offset, offset + 8)); + } + + return { + targetName, + negotiateFlags, + serverChallenge, + targetInfo, + version + }; +} + +export const serializeAuthenticationMessage = (username: string, hostname: string, domain: string, serverChallenge: Buffer, password: string) => { + hostname = hostname.toUpperCase(); + domain = domain.toUpperCase(); + + const lmResponse = createLmResponse(password, serverChallenge); + const ntResponse = createNtResponse(password, serverChallenge); + + const usernameLength = Buffer.byteLength(username, "ucs2"); + const hostnameLength = Buffer.byteLength(hostname, "ucs2"); + const domainLength = Buffer.byteLength(domain, "ucs2"); + + const lmResponseLength = 24; + const ntResponseLength = 24; + + const domainOffset = 64; + const usernameOffset = domainOffset + domainLength; + const hostnameOffset = usernameOffset + usernameLength; + const lmResponseOffset = hostnameOffset + hostnameLength; + const ntResponseOffset = lmResponseOffset + lmResponseLength; + + let offset = 0; + const messageLength = 64 + domainLength + usernameLength + hostnameLength + lmResponseLength + ntResponseLength; + const buffer = Buffer.alloc(messageLength); + + buffer.write("NTLMSSP", offset, 7, "ascii"); + offset += 7; + buffer.writeUInt8(0, offset); + offset += 1; + + buffer.writeUInt32LE(MessageType.Authentication, offset); + offset += 4; + + serializeBufferPointer(lmResponseLength, lmResponseOffset).copy(buffer, offset); + offset += 8; + + serializeBufferPointer(ntResponseLength, ntResponseOffset).copy(buffer, offset); + offset += 8; + + serializeBufferPointer(domainLength, domainOffset).copy(buffer, offset); + offset += 8; + + serializeBufferPointer(usernameLength, usernameOffset).copy(buffer, offset); + offset += 8; + + serializeBufferPointer(hostnameLength, hostnameOffset).copy(buffer, offset); + offset += 8; + + buffer.writeUInt32LE(messageLength, offset); + offset += 4; + + const negotiateFlags = NegotiateFlag.UnicodeEncoding | + NegotiateFlag.NTLMSessionSecurity | + NegotiateFlag.AlwaysSign; + buffer.writeUInt32LE(negotiateFlags, offset); + offset += 4; + + lmResponse.copy(buffer, lmResponseOffset, 0, lmResponseLength); + ntResponse.copy(buffer, ntResponseOffset, 0, ntResponseLength); + buffer.write(domain, domainOffset, domainLength, "ucs2"); + buffer.write(username, usernameOffset, usernameLength, "ucs2"); + buffer.write(hostname, hostnameOffset, hostnameLength, "ucs2"); + + return buffer; +}; + +export const parseAuthenticationMessage = (buffer: Buffer) => { + let offset = 0; + + const protocol = buffer.slice(0, 7).toString("ascii"); + if ( + protocol !== "NTLMSSP" || + buffer.readInt8(7) !== 0x00 + ) throw new Error("ntlmssp_header_not_found"); + offset += 8; + + const type = parseMessageType(buffer); + if (type !== MessageType.Authentication) throw new Error("ntlm_message_type_is_not_authentication"); + offset += 4; + + const lmResponseBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); + const lmResponse = buffer.slice( + lmResponseBufferPointer.offset, + lmResponseBufferPointer.offset + lmResponseBufferPointer.length + ); + offset += 8; + + const ntResponseBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); + const ntResponse = buffer.slice( + ntResponseBufferPointer.offset, + ntResponseBufferPointer.offset + ntResponseBufferPointer.length + ); + offset += 8; + + const domainBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); + const domain = buffer.slice( + domainBufferPointer.offset, + domainBufferPointer.offset + domainBufferPointer.length + ).toString("ucs2"); + offset += 8; + + const usernameBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); + const username = buffer.slice( + usernameBufferPointer.offset, + usernameBufferPointer.offset + usernameBufferPointer.length + ).toString("ucs2"); + offset += 8; + + const hostnameBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); + const hostname = buffer.slice( + hostnameBufferPointer.offset, + hostnameBufferPointer.offset + hostnameBufferPointer.length + ).toString("ucs2"); + offset += 8; + + const messageLength = buffer.readUInt32LE(offset); + offset += 4; + + const negotiateFlags = buffer.readUInt32LE(offset); + offset += 4; + + let version: Version; + if ((negotiateFlags & NegotiateFlag.Version) > 0) { + version = Version.parse(buffer.slice(offset, offset + 8)); + } + + return { + lmResponse, + ntResponse, + domain, + username, + hostname, + messageLength, + negotiateFlags, + version + }; +}; + +export const generateServerChallenge = () => { + return crypto.randomBytes(8); +} + +export const parseMessageType = (buffer: Buffer) => { + return buffer.readUInt32LE(8); +}; + +export const matchPasswordV2 = (password: string, serverChallenge: Buffer, lmResponse: Buffer, ntResponse: Buffer, domain: string, username: string) => { + if (lmResponse.length === 24) { + const clientChallenge = lmResponse.slice(16, 24); + const lmV2Response = createLmV2Response(password, serverChallenge, clientChallenge, domain, username); + if (lmV2Response.equals(lmResponse)) return true; + } + + if (isNtV2Response(ntResponse)) { + const clientProof = ntResponse.slice(0, 16); + const clientChallenge = ntResponse.slice(16); + + const validClientProof = createNtV2Proof(password, serverChallenge, clientChallenge, domain, username); + return clientProof.equals(validClientProof); + } + + return false; +}; + +export const matchPassword = (password: string, serverChallenge: Buffer, lmResponse: Buffer, ntResponse: Buffer) => { + const validLmResponse = createLmResponse(password, serverChallenge); + if (validLmResponse.equals(lmResponse)) return true; + + const validNtResponse = createNtResponse(password, serverChallenge); + return validNtResponse.equals(ntResponse); +}; + +const fixOddParity = (buffer: Buffer) => { + for (let index = 0; index < buffer.length; index++) { + let parity = 1; + for (let index2 = 1; index2 < 8; index2++) { + parity = (parity + ((buffer[index] >> index2) & 1)) % 2; + } + buffer[index] |= parity & 1; + } + return buffer; +}; + +const createDESKey = (key56: Buffer) => { + const key64 = Buffer.alloc(8); + + key64[0] = key56[0] & 0xFE; + key64[1] = ((key56[0] << 7) & 0xFF) | (key56[1] >> 1); + key64[2] = ((key56[1] << 6) & 0xFF) | (key56[2] >> 2); + key64[3] = ((key56[2] << 5) & 0xFF) | (key56[3] >> 3); + key64[4] = ((key56[3] << 4) & 0xFF) | (key56[4] >> 4); + key64[5] = ((key56[4] << 3) & 0xFF) | (key56[5] >> 5); + key64[6] = ((key56[5] << 2) & 0xFF) | (key56[6] >> 6); + key64[7] = (key56[6] << 1) & 0xFF; + + return key64; +}; + +const createLmHash = (text: string) => { + const upperCaseText = text.substring(0, 14).toUpperCase(); + const upperCaseTextLength = Buffer.byteLength(upperCaseText, "ascii"); + + const paddingBuffer = Buffer.alloc(14); + paddingBuffer.write(upperCaseText, 0, upperCaseTextLength, "ascii"); + paddingBuffer.fill(0, upperCaseTextLength); + + const halves = [ + fixOddParity(createDESKey(paddingBuffer.slice(0, 7))), + fixOddParity(createDESKey(paddingBuffer.slice(7, 14))) + ]; + + const buffer = Buffer.alloc(16); + let offset = 0; + for (const halve of halves) { + const cipher = crypto.createCipheriv("DES-ECB", halve, ""); + const string = cipher.update("KGS!@#$%", "binary", "binary"); + buffer.write(string, offset, offset + 8, "binary"); + offset += 8; + } + + return buffer; +}; + +const createNtHash = (value: string) => { + const buffer = Buffer.from(value, "ucs2"); + return createMd4HashBuffer(buffer); +}; + +const createResponse = (hash: Buffer, serverChallenge: Buffer) => { + const buffer = Buffer.alloc(24); + for (let index = 0; index < 3; index++) { + const keyBuffer = fixOddParity(createDESKey(hash.slice(index * 7, index * 7 + 7))); + const cipher = crypto.createCipheriv("DES-ECB", keyBuffer, ""); + const string = cipher.update(serverChallenge.toString("binary"), "binary", "binary"); + buffer.write(string, index * 8, index * 8 + 8, "binary"); + } + return buffer; +}; + +const serializeBufferPointer = (pointerLength: number, pointerOffset: number) => { + let offset = 0; + + const buffer = Buffer.allocUnsafe(8); + buffer.writeUInt16LE(pointerLength, offset); + offset += 2; + + buffer.writeUInt16LE(pointerLength, offset); + offset += 2; + + buffer.writeUInt32LE(pointerOffset, offset); + offset += 4; + + return buffer; +}; + +const parseBufferPointer = (buffer: Buffer) => { + let offset = 0; + + const pointerLength = buffer.readUInt16LE(offset); + offset += 2; + + offset += 2; + + const pointerOffset = buffer.readUInt32LE(offset); + offset += 4; + + return { + length: pointerLength, + offset: pointerOffset + }; +}; + +const createLmResponse = (password: string, serverChallenge: Buffer) => { + const lmHash = Buffer.alloc(21); + createLmHash(password).copy(lmHash); + lmHash.fill(0x00, 16); + + return createResponse(lmHash, serverChallenge); +}; + +const createV2Hash = (domain: string, username: string, password: string) => { + const passwordBuffer = Buffer.from(password, "ucs2"); + const md4Buffer = createMd4HashBuffer(passwordBuffer); + + const phrase = username.toUpperCase() + domain; + const phraseBuffer = Buffer.from(phrase, "ucs2"); + + return createMd5HmacBuffer(phraseBuffer, md4Buffer); +}; + +const createLmV2Response = (password: string, serverChallenge: Buffer, clientChallenge: Buffer, domain: string, username: string) => { + const v2Hash = createV2Hash(domain, username, password); + + const challengeBuffer = Buffer.concat([serverChallenge, clientChallenge]); + const md5Buffer = createMd5HmacBuffer(challengeBuffer, v2Hash); + + return Buffer.concat([md5Buffer, clientChallenge]); +}; + +const createNtV2Proof = (password: string, serverChallenge: Buffer, clientChallenge: Buffer, domain: string, username: string) => { + const v2Hash = createV2Hash(domain, username, password); + + const challengeBuffer = Buffer.concat([serverChallenge, clientChallenge]); + return createMd5HmacBuffer(challengeBuffer, v2Hash); +}; + +const createNtResponse = (password: string, serverChallenge: Buffer) => { + const ntHash = Buffer.alloc(21); + createNtHash(password).copy(ntHash); + ntHash.fill(0x00, 16); + + return createResponse(ntHash, serverChallenge); +}; + +export const isExtendedSessionSecurityLmResponse = (lmResponse: Buffer) => { + if (lmResponse.length === 24) { + if (lmResponse.slice(0, 8).equals(Buffer.alloc(8, 0))) return false; + return lmResponse.slice(8, 24).equals(Buffer.alloc(16, 0)); + } + return false; +} + +const ntV2ResponseMinLength = 32; +const ntV2ResponseStructureVersion = 0x01; + +const isNtV2Response = (ntResponse: Buffer) => ( + ntResponse.length >= 16 + ntV2ResponseMinLength && + ntResponse[16] === ntV2ResponseStructureVersion && + ntResponse[17] === ntV2ResponseStructureVersion +); + +const createMd4HashBuffer = (buffer: Buffer) => { + const md4Hash = crypto.createHash("md4"); + md4Hash.update(buffer); + return Buffer.from(md4Hash.digest("hex"), "hex"); +}; + +const createMd5HmacBuffer = (buffer: Buffer, key: Buffer) => { + const md5Hmac = crypto.createHmac("md5", key); + md5Hmac.update(buffer); + return Buffer.from(md5Hmac.digest("hex"), "hex"); +}; \ No newline at end of file diff --git a/src/protocol/smb/Header.ts b/src/protocols/smb/Header.ts similarity index 100% rename from src/protocol/smb/Header.ts rename to src/protocols/smb/Header.ts diff --git a/src/protocol/smb/Packet.ts b/src/protocols/smb/Packet.ts similarity index 100% rename from src/protocol/smb/Packet.ts rename to src/protocols/smb/Packet.ts diff --git a/src/protocol/smb/PacketType.ts b/src/protocols/smb/PacketType.ts similarity index 100% rename from src/protocol/smb/PacketType.ts rename to src/protocols/smb/PacketType.ts diff --git a/src/protocol/smb/Request.ts b/src/protocols/smb/Request.ts similarity index 100% rename from src/protocol/smb/Request.ts rename to src/protocols/smb/Request.ts diff --git a/src/protocol/smb/Response.ts b/src/protocols/smb/Response.ts similarity index 100% rename from src/protocol/smb/Response.ts rename to src/protocols/smb/Response.ts diff --git a/src/protocols/smb2/Capability.ts b/src/protocols/smb2/Capability.ts new file mode 100644 index 0000000..dca3fa5 --- /dev/null +++ b/src/protocols/smb2/Capability.ts @@ -0,0 +1,7 @@ +enum Capability { + DistributedFileSystem = 1 << 0, + Leasing = 1 << 1, + LargeMtu = 1 << 2 +} + +export default Capability; \ No newline at end of file diff --git a/src/protocols/smb2/ControlCode.ts b/src/protocols/smb2/ControlCode.ts new file mode 100644 index 0000000..7364299 --- /dev/null +++ b/src/protocols/smb2/ControlCode.ts @@ -0,0 +1,19 @@ +enum ControlCode { + DfsGetReferrals = 0x00060194, + PipeSeek = 0x0011400C, + PipeWait = 0x00110018, + PipeTransceive = 0x0011C017, + SrvCopyChunk = 0x001440F2, + SrvEnumerateSnapshots = 0x00144064, + SrvRequestResumeKey = 0x00140078, + SrvReadHash = 0x001441bb, + SrvCopyChunkWrite = 0x001480F2, + LmrRequestResiliency = 0x001401D4, + QueryNetworkInterfaceInfo = 0x001401FC, + SetReparsePoint = 0x000900A4, + DfsGetReferralsEx = 0x000601B0, + FileLevelTrim = 0x00098208, + ValidateNegotiateInfo = 0x00140204 +} + +export default ControlCode; \ No newline at end of file diff --git a/src/protocol/smb2/CreateDispositionType.ts b/src/protocols/smb2/CreateDispositionType.ts similarity index 100% rename from src/protocol/smb2/CreateDispositionType.ts rename to src/protocols/smb2/CreateDispositionType.ts diff --git a/src/protocol/smb2/Dialect.ts b/src/protocols/smb2/Dialect.ts similarity index 100% rename from src/protocol/smb2/Dialect.ts rename to src/protocols/smb2/Dialect.ts diff --git a/src/protocol/smb2/DirectoryAccess.ts b/src/protocols/smb2/DirectoryAccess.ts similarity index 100% rename from src/protocol/smb2/DirectoryAccess.ts rename to src/protocols/smb2/DirectoryAccess.ts diff --git a/src/protocol/smb2/FileAction.ts b/src/protocols/smb2/FileAction.ts similarity index 100% rename from src/protocol/smb2/FileAction.ts rename to src/protocols/smb2/FileAction.ts diff --git a/src/protocol/smb2/FileAttribute.ts b/src/protocols/smb2/FileAttribute.ts similarity index 100% rename from src/protocol/smb2/FileAttribute.ts rename to src/protocols/smb2/FileAttribute.ts diff --git a/src/protocol/smb2/FilePipePrinterAccess.ts b/src/protocols/smb2/FilePipePrinterAccess.ts similarity index 92% rename from src/protocol/smb2/FilePipePrinterAccess.ts rename to src/protocols/smb2/FilePipePrinterAccess.ts index 737c0cc..e027787 100644 --- a/src/protocol/smb2/FilePipePrinterAccess.ts +++ b/src/protocols/smb2/FilePipePrinterAccess.ts @@ -4,8 +4,8 @@ enum FilePipePrinterAccess { AppendData = 1 << 2, ReadEa = 1 << 3, WriteEa = 1 << 4, - DeleteChild = 1 << 5, - Execute = 1 << 6, + Execute = 1 << 5, + DeleteChild = 1 << 6, ReadAttributes = 1 << 7, WriteAttributes = 1 << 8, Delete = 1 << 16, diff --git a/src/protocol/smb2/Header.ts b/src/protocols/smb2/Header.ts similarity index 97% rename from src/protocol/smb2/Header.ts rename to src/protocols/smb2/Header.ts index cbcd981..ef798a4 100644 --- a/src/protocol/smb2/Header.ts +++ b/src/protocols/smb2/Header.ts @@ -85,7 +85,8 @@ export const headerStructure: HeaderStructure = { size: 4 }, treeId: { - type: Number, + type: String, + encoding: "hex", size: 4 }, sessionId: { diff --git a/src/protocol/smb2/HeaderFlag.ts b/src/protocols/smb2/HeaderFlag.ts similarity index 100% rename from src/protocol/smb2/HeaderFlag.ts rename to src/protocols/smb2/HeaderFlag.ts diff --git a/src/protocol/smb2/Packet.ts b/src/protocols/smb2/Packet.ts similarity index 98% rename from src/protocol/smb2/Packet.ts rename to src/protocols/smb2/Packet.ts index b98fe0f..bedaa61 100644 --- a/src/protocol/smb2/Packet.ts +++ b/src/protocols/smb2/Packet.ts @@ -51,7 +51,7 @@ export default class Packet { } = Packet.parseHeader(buffer); const structure = Packet.getStructure(header); - const body = structureUtil.parseStructure(bodyBuffer, structure); + const body = structureUtil.parseStructure(bodyBuffer, structure, { subtractOffset: headerSize }); return { header, diff --git a/src/protocol/smb2/PacketType.ts b/src/protocols/smb2/PacketType.ts similarity index 100% rename from src/protocol/smb2/PacketType.ts rename to src/protocols/smb2/PacketType.ts diff --git a/src/protocol/smb2/Request.ts b/src/protocols/smb2/Request.ts similarity index 100% rename from src/protocol/smb2/Request.ts rename to src/protocols/smb2/Request.ts diff --git a/src/protocol/smb2/Response.ts b/src/protocols/smb2/Response.ts similarity index 100% rename from src/protocol/smb2/Response.ts rename to src/protocols/smb2/Response.ts diff --git a/src/protocols/smb2/SecurityMode.ts b/src/protocols/smb2/SecurityMode.ts new file mode 100644 index 0000000..77fb73a --- /dev/null +++ b/src/protocols/smb2/SecurityMode.ts @@ -0,0 +1,6 @@ +enum SecurityMode { + SigningEnabled = 1 << 0, + SigningRequired = 1 << 1 +} + +export default SecurityMode; \ No newline at end of file diff --git a/src/protocols/smb2/SessionFlag.ts b/src/protocols/smb2/SessionFlag.ts new file mode 100644 index 0000000..7aec22e --- /dev/null +++ b/src/protocols/smb2/SessionFlag.ts @@ -0,0 +1,7 @@ +enum SessionFlag { + Guest = 1 << 0, + Null = 1 << 1, + Encrypt = 1 << 2 +} + +export default SessionFlag; \ No newline at end of file diff --git a/src/protocol/smb2/ShareAccessType.ts b/src/protocols/smb2/ShareAccessType.ts similarity index 100% rename from src/protocol/smb2/ShareAccessType.ts rename to src/protocols/smb2/ShareAccessType.ts diff --git a/src/protocol/smb2/StatusCode.ts b/src/protocols/smb2/StatusCode.ts similarity index 88% rename from src/protocol/smb2/StatusCode.ts rename to src/protocols/smb2/StatusCode.ts index e27d728..b3b3ff6 100644 --- a/src/protocol/smb2/StatusCode.ts +++ b/src/protocols/smb2/StatusCode.ts @@ -4,6 +4,7 @@ enum StatusCode { MoreProcessingRequired = 0xc0000016, FileNameNotFound = 0xc0000034, FilePathNotFound = 0xc000003a, + LogonFailure = 0xC000006D, FileClosed = 0xc0000128 } diff --git a/src/protocols/smb2/TreeConnectShareFlag.ts b/src/protocols/smb2/TreeConnectShareFlag.ts new file mode 100644 index 0000000..c398b84 --- /dev/null +++ b/src/protocols/smb2/TreeConnectShareFlag.ts @@ -0,0 +1,18 @@ +enum TreeConnectShareFlag { + ManualCaching = 0x00000000, + AutoCaching = 0x00000010, + VdoCaching = 0x00000020, + NoCaching = 0x00000030, + Dfs = 0x00000001, + DfsRoot = 0x00000002, + RestrictExclusiveOpens = 0x00000100, + ForceSharedDelete = 0x00000200, + AllowNamespaceCaching = 0x00000400, + AccessBasedDirectoryEnum = 0x00000800, + ForceLevel2Oplock = 0x00001000, + EnableHashV1 = 0x00002000, + EnableHashV2 = 0x00004000, + EncryptData = 0x00008000 +} + +export default TreeConnectShareFlag; \ No newline at end of file diff --git a/src/protocols/smb2/TreeConnectShareType.ts b/src/protocols/smb2/TreeConnectShareType.ts new file mode 100644 index 0000000..5903940 --- /dev/null +++ b/src/protocols/smb2/TreeConnectShareType.ts @@ -0,0 +1,7 @@ +enum TreeConnectShareType { + Disk = 0x01, + Pipe = 0x02, + Print = 0x03 +} + +export default TreeConnectShareType; \ No newline at end of file diff --git a/src/protocol/smb2/packets/ChangeNotify.ts b/src/protocols/smb2/packets/ChangeNotify.ts similarity index 97% rename from src/protocol/smb2/packets/ChangeNotify.ts rename to src/protocols/smb2/packets/ChangeNotify.ts index 4072b21..1ca611d 100644 --- a/src/protocol/smb2/packets/ChangeNotify.ts +++ b/src/protocols/smb2/packets/ChangeNotify.ts @@ -1,7 +1,7 @@ import * as util from "../../util"; import Structure from "../../Structure"; import FileAction from "../FileAction"; -import ChangeEntry from "../../models/ChangeEntry"; +import ChangeEntry from "../../../models/ChangeEntry"; import * as structureUtil from "../../structureUtil"; export enum Flags { diff --git a/src/protocol/smb2/packets/Close.ts b/src/protocols/smb2/packets/Close.ts similarity index 100% rename from src/protocol/smb2/packets/Close.ts rename to src/protocols/smb2/packets/Close.ts diff --git a/src/protocol/smb2/packets/Create.ts b/src/protocols/smb2/packets/Create.ts similarity index 100% rename from src/protocol/smb2/packets/Create.ts rename to src/protocols/smb2/packets/Create.ts diff --git a/src/protocol/smb2/packets/Echo.ts b/src/protocols/smb2/packets/Echo.ts similarity index 100% rename from src/protocol/smb2/packets/Echo.ts rename to src/protocols/smb2/packets/Echo.ts diff --git a/src/protocol/smb2/packets/Flush.ts b/src/protocols/smb2/packets/Flush.ts similarity index 100% rename from src/protocol/smb2/packets/Flush.ts rename to src/protocols/smb2/packets/Flush.ts diff --git a/src/protocols/smb2/packets/InputOutputControl.ts b/src/protocols/smb2/packets/InputOutputControl.ts new file mode 100644 index 0000000..cb7d0e6 --- /dev/null +++ b/src/protocols/smb2/packets/InputOutputControl.ts @@ -0,0 +1,124 @@ +import Structure from "../../Structure"; + +const requestStructure: Structure = { + structureSize: { + type: Number, + size: 2, + defaultValue: 57 + }, + reserved: { + type: Number, + size: 2 + }, + controlCode: { + type: Number, + size: 4 + }, + fileId: { + type: String, + encoding: "hex", + size: 16 + }, + inputOffset: { + type: Number, + size: 4 + }, + inputCount: { + type: Number, + size: 4 + }, + maxInputResponse: { + type: Number, + size: 4 + }, + outputOffset: { + type: Number, + size: 4 + }, + outputCount: { + type: Number, + size: 4 + }, + maxOutputResponse: { + type: Number, + size: 4 + }, + flags: { + type: Number, + size: 4 + }, + reserved2: { + type: Number, + size: 4 + }, + input: { + type: Buffer, + offsetFieldName: "inputOffset", + sizeFieldName: "inputCount" + }, + output: { + type: Buffer, + offsetFieldName: "outputOffset", + sizeFieldName: "outputCount" + } +}; + +const responseStructure: Structure = { + structureSize: { + type: Number, + size: 2, + defaultValue: 49 + }, + reserved: { + type: Number, + size: 2 + }, + controlCode: { + type: Number, + size: 4 + }, + fileId: { + type: String, + encoding: "hex", + size: 16 + }, + inputOffset: { + type: Number, + size: 4 + }, + inputCount: { + type: Number, + size: 4 + }, + outputOffset: { + type: Number, + size: 4 + }, + outputCount: { + type: Number, + size: 4 + }, + flags: { + type: Number, + size: 4 + }, + reserved2: { + type: Number, + size: 4 + }, + input: { + type: Buffer, + offsetFieldName: "inputOffset", + sizeFieldName: "inputCount" + }, + output: { + type: Buffer, + offsetFieldName: "outputOffset", + sizeFieldName: "outputCount" + } +}; + +export default { + requestStructure, + responseStructure +}; \ No newline at end of file diff --git a/src/protocol/smb2/packets/LogOff.ts b/src/protocols/smb2/packets/LogOff.ts similarity index 100% rename from src/protocol/smb2/packets/LogOff.ts rename to src/protocols/smb2/packets/LogOff.ts diff --git a/src/protocol/smb2/packets/Negotiate.ts b/src/protocols/smb2/packets/Negotiate.ts similarity index 100% rename from src/protocol/smb2/packets/Negotiate.ts rename to src/protocols/smb2/packets/Negotiate.ts diff --git a/src/protocol/smb2/packets/QueryDirectory.ts b/src/protocols/smb2/packets/QueryDirectory.ts similarity index 88% rename from src/protocol/smb2/packets/QueryDirectory.ts rename to src/protocols/smb2/packets/QueryDirectory.ts index 51ef91b..e12bdcd 100644 --- a/src/protocol/smb2/packets/QueryDirectory.ts +++ b/src/protocols/smb2/packets/QueryDirectory.ts @@ -1,8 +1,9 @@ import * as util from "../../util"; import Structure from "../../Structure"; +import * as dtypUtil from "../../dtyp/util"; import FileAttribute from "../FileAttribute"; import * as structureUtil from "../../structureUtil"; -import DirectoryEntry from "../../models/DirectoryEntry"; +import DirectoryEntry from "../../../models/DirectoryEntry"; const requestStructure: Structure = { structureSize: { @@ -72,16 +73,16 @@ const parseDirectoryEntry = (entryBuffer: Buffer): DirectoryEntry => { const index = entryBuffer.readUInt32LE(offset); offset += 4; - const creationTime = structureUtil.parseDate(entryBuffer.slice(offset, offset + 8)); + const creationTime = dtypUtil.parseFiletime(entryBuffer.slice(offset, offset + 8)); offset += 8; - const lastAccessTime = structureUtil.parseDate(entryBuffer.slice(offset, offset + 8)); + const lastAccessTime = dtypUtil.parseFiletime(entryBuffer.slice(offset, offset + 8)); offset += 8; - const lastWriteTime = structureUtil.parseDate(entryBuffer.slice(offset, offset + 8)); + const lastWriteTime = dtypUtil.parseFiletime(entryBuffer.slice(offset, offset + 8)); offset += 8; - const changeTime = structureUtil.parseDate(entryBuffer.slice(offset, offset + 8)); + const changeTime = dtypUtil.parseFiletime(entryBuffer.slice(offset, offset + 8)); offset += 8; const fileSize = entryBuffer.readBigUInt64LE(offset); diff --git a/src/protocol/smb2/packets/Read.ts b/src/protocols/smb2/packets/Read.ts similarity index 100% rename from src/protocol/smb2/packets/Read.ts rename to src/protocols/smb2/packets/Read.ts diff --git a/src/protocol/smb2/packets/SessionSetup.ts b/src/protocols/smb2/packets/SessionSetup.ts similarity index 100% rename from src/protocol/smb2/packets/SessionSetup.ts rename to src/protocols/smb2/packets/SessionSetup.ts diff --git a/src/protocol/smb2/packets/SetInfo.ts b/src/protocols/smb2/packets/SetInfo.ts similarity index 100% rename from src/protocol/smb2/packets/SetInfo.ts rename to src/protocols/smb2/packets/SetInfo.ts diff --git a/src/protocol/smb2/packets/TreeConnect.ts b/src/protocols/smb2/packets/TreeConnect.ts similarity index 83% rename from src/protocol/smb2/packets/TreeConnect.ts rename to src/protocols/smb2/packets/TreeConnect.ts index 92a5d27..af7f759 100644 --- a/src/protocol/smb2/packets/TreeConnect.ts +++ b/src/protocols/smb2/packets/TreeConnect.ts @@ -1,4 +1,5 @@ import Structure from "../../Structure"; +import TreeConnectShareType from "../TreeConnectShareType"; const requestStructure: Structure = { structureSize: { @@ -28,11 +29,13 @@ const requestStructure: Structure = { const responseStructure: Structure = { structureSize: { type: Number, - size: 2 + size: 2, + defaultValue: 16 }, shareType: { type: Number, - size: 1 + size: 1, + defaultValue: TreeConnectShareType.Disk }, reserved: { type: Number, diff --git a/src/protocol/smb2/packets/TreeDisconnect.ts b/src/protocols/smb2/packets/TreeDisconnect.ts similarity index 100% rename from src/protocol/smb2/packets/TreeDisconnect.ts rename to src/protocols/smb2/packets/TreeDisconnect.ts diff --git a/src/protocol/smb2/packets/Write.ts b/src/protocols/smb2/packets/Write.ts similarity index 100% rename from src/protocol/smb2/packets/Write.ts rename to src/protocols/smb2/packets/Write.ts diff --git a/src/protocol/smb2/packets/index.ts b/src/protocols/smb2/packets/index.ts similarity index 84% rename from src/protocol/smb2/packets/index.ts rename to src/protocols/smb2/packets/index.ts index 7a66967..f6bbb62 100644 --- a/src/protocol/smb2/packets/index.ts +++ b/src/protocols/smb2/packets/index.ts @@ -11,4 +11,5 @@ export { default as Write } from "./Write"; export { default as Echo } from "./Echo"; export { default as QueryDirectory } from "./QueryDirectory"; export { default as ChangeNotify } from "./ChangeNotify"; -export { default as SetInfo } from "./SetInfo"; \ No newline at end of file +export { default as SetInfo } from "./SetInfo"; +export { default as InputOutputControl } from "./InputOutputControl"; \ No newline at end of file diff --git a/src/protocol/structureUtil.ts b/src/protocols/structureUtil.ts similarity index 82% rename from src/protocol/structureUtil.ts rename to src/protocols/structureUtil.ts index 38bf087..b90cdd7 100644 --- a/src/protocol/structureUtil.ts +++ b/src/protocols/structureUtil.ts @@ -1,9 +1,8 @@ import Value from "./Value"; import Structure from "./Structure"; -import moment from "moment-timezone"; import StructureField from "./StructureField"; -export const parseStructure = (buffer: Buffer, structure: Structure) => { +export const parseStructure = (buffer: Buffer, structure: Structure, options: { subtractOffset?: number; } = {}) => { let offset = 0; const data: any = {}; const structureFieldNames = Object.keys(structure); @@ -26,7 +25,15 @@ export const parseStructure = (buffer: Buffer, structure: Structure) => { if (typeof structureField.count === "undefined") throw new Error(`invalid_count_field_name: ${structureField.countFieldName}`); } - const value = buffer.slice(offset, offset + (size * structureField.count)); + let currentOffset = offset; + if (structureField.offsetFieldName) { + currentOffset = data[structureField.offsetFieldName] as number; + if (typeof currentOffset === "undefined") throw new Error(`invalid_offset_field_name: ${structureField.offsetFieldName}`); + + if (typeof options.subtractOffset === "number") currentOffset -= options.subtractOffset; + } + + const value = buffer.slice(currentOffset, currentOffset + (size * structureField.count)); data[structureFieldName] = parseValue(value, structureField); offset += size * structureField.count; } @@ -78,11 +85,6 @@ export const parseString = (buffer: Buffer, structureField: StructureField) => { return buffer.slice(0, structureField.size).toString(structureField.encoding); }; -export const parseDate = (buffer: Buffer) => { - const milliseconds = Number(buffer.readBigUInt64LE(0) / 10000n); - return moment.utc("1601-01-01").add(milliseconds, "milliseconds").toDate(); -}; - export const parseEnumValue = (enumObject: any, value: number | string) => { return Object.keys(enumObject) .find(x => enumObject[x] === value); @@ -118,7 +120,7 @@ export const parseList = (buffer: Buffer, parser: (entryBuffer: }; export const serializeStructure = (structure: Structure, data: any) => { - const normalizedData: { [fieldName: string]: { value?: Buffer; size?: number; } } = {}; + const normalizedData: { [fieldName: string]: { value?: Buffer; size?: number; structureFieldName?: string; structureField?: StructureField; } } = {}; const structureFieldNames = Object.keys(structure); for (const structureFieldName of structureFieldNames) { const structureField = structure[structureFieldName]; @@ -160,13 +162,27 @@ export const serializeStructure = (structure: Structure, data: any) => { normalizedData[structureFieldName].size = structureField.size * structureField.count; } - const normalizedFields = structureFieldNames.map(x => normalizedData[x]); + const normalizedFields = structureFieldNames.map(structureFieldName => { + const structureField = structure[structureFieldName]; + const normalizedField = normalizedData[structureFieldName]; + normalizedField.structureFieldName = structureFieldName; + normalizedField.structureField = structureField; + + return normalizedField; + }); const bufferSize = normalizedFields.reduce((prev, current) => prev + current.size, 0); const buffer = Buffer.allocUnsafe(bufferSize); let offset = 0; for (const normalizedField of normalizedFields) { - normalizedField.value.copy(buffer, offset); + let currentOffset = offset; + if (normalizedField.structureField && normalizedField.structureField.offsetFieldName) { + currentOffset = parseNumber( + normalizedData[normalizedField.structureField.offsetFieldName].value, + structure[normalizedField.structureField.offsetFieldName] + ) as number; + } + normalizedField.value.copy(buffer, currentOffset); offset += normalizedField.size; } @@ -193,13 +209,6 @@ export const serializeValue = (value: Value, structureField: StructureField): Bu return result; }; -export const serializeDate = (date: Date) => { - const milliseconds = moment(date).diff(moment.utc("1601-01-01"), "milliseconds"); - const buffer = Buffer.allocUnsafe(8); - buffer.writeBigInt64LE(BigInt(milliseconds) * 10000n, 0); - return buffer; -}; - export const serializeString = (value: string, structureField: StructureField) => { return Buffer.from(value, structureField.encoding); }; \ No newline at end of file diff --git a/src/protocol/util.ts b/src/protocols/util.ts similarity index 68% rename from src/protocol/util.ts rename to src/protocols/util.ts index 3ac75d6..4b1d510 100644 --- a/src/protocol/util.ts +++ b/src/protocols/util.ts @@ -53,4 +53,32 @@ export const generateGuid = () => { node.copy(buffer, offset); return buffer; +}; + +export const generateUint = (bits = 32) => Math.floor(Math.random() * Math.pow(2, bits)); + +export const formatBuffer = (buffer: Buffer) => + Array.from(buffer) + .map(byte => { + const text = byte.toString(16); + return text.length === 1 ? "0" + text : text; + }) + .join(" "); + +export const stringify = (object: any) => { + const formattedObject: any = {}; + const keys = Object.keys(object); + + for (const key of keys) { + const value = object[key]; + formattedObject[key] = stringifyValue(value); + } + + return JSON.stringify(formattedObject, null, 2); +}; + +export const stringifyValue = (value: any) => { + if (Buffer.isBuffer(value)) return formatBuffer(value); + if (typeof value === "bigint") return value.toString(); + return value; }; \ No newline at end of file diff --git a/src/server/AuthenticationProvider.ts b/src/server/AuthenticationProvider.ts new file mode 100644 index 0000000..d2c7f0a --- /dev/null +++ b/src/server/AuthenticationProvider.ts @@ -0,0 +1,20 @@ +import User from "./User"; + +export default class AuthenticationProvider { + public domain: string; + public users: User[] = []; + + async init() { } + + matchDomain(domain: string) { + if (!this.domain) return true; + return this.domain === domain; + } + + getUser(domain: string, username: string) { + if (!this.matchDomain(domain)) return null; + + const user = this.users.find(x => x.name === username); + return user || null; + } +} \ No newline at end of file diff --git a/src/server/Client.ts b/src/server/Client.ts index 6c31d56..ae9719d 100644 --- a/src/server/Client.ts +++ b/src/server/Client.ts @@ -1,24 +1,30 @@ import { Socket } from "net"; import Server from "./Server"; +import Session from "./Session"; import { EventEmitter } from "events"; import SmbRequest from "./SmbRequest"; import Smb2Request from "./Smb2Request"; -import Packet from "../protocol/Packet"; -import Request from "../protocol/Request"; -import Response from "../protocol/Response"; -import Dialect from "../protocol/smb2/Dialect"; -import * as protocolIds from "../protocol/protocolIds"; +import Packet from "../protocols/Packet"; +import Request from "../protocols/Request"; +import Response from "../protocols/Response"; +import Dialect from "../protocols/smb2/Dialect"; +import * as protocolIds from "../protocols/ProtocolIds"; interface Client { on(event: "request", callback: (req: Request) => void): this; + on(event: "destroy", callback: () => void): this; once(event: "request", callback: (req: Request) => void): this; + once(event: "destroy", callback: () => void): this; } class Client extends EventEmitter { private restChunk: Buffer; public targetDialect: Dialect; public targetDialectName: string; + public serverChallenge: Buffer; + public useExtendedSessionSecurity: boolean = false; + public session: Session; constructor( private server: Server, @@ -27,10 +33,12 @@ class Client extends EventEmitter { super(); } - setup() { + init() { this.socket.setNoDelay(true); this.socket.addListener("data", this.onData); + this.socket.addListener("error", this.onError); + this.socket.addListener("close", this.onClose); } private onData = (buffer: Buffer) => { @@ -47,7 +55,7 @@ class Client extends EventEmitter { for (const chunk of chunks) { const protocolId = Packet.parseProtocolId(chunk); - if (protocolId === protocolIds.smb) { + if (protocolId === protocolIds.Smb) { const request = SmbRequest.parse(chunk); this.emit("request", request); } else { @@ -57,6 +65,22 @@ class Client extends EventEmitter { } } + private onError = (err: Error) => { + this.destroy(); + } + + private onClose = () => { + this.destroy(); + } + + destroy() { + this.socket.destroy(); + this.socket.removeAllListeners(); + delete this.socket; + + this.emit("destroy"); + } + send(response: Response) { const buffer = response.serialize(); this.socket.write(buffer); diff --git a/src/server/RemoteService.ts b/src/server/RemoteService.ts new file mode 100644 index 0000000..2255acf --- /dev/null +++ b/src/server/RemoteService.ts @@ -0,0 +1,3 @@ +export default abstract class RemoteService { + public abstract serialize(): Buffer; +} \ No newline at end of file diff --git a/src/server/Server.ts b/src/server/Server.ts index 3507b48..566db26 100644 --- a/src/server/Server.ts +++ b/src/server/Server.ts @@ -1,21 +1,25 @@ import Client from "./Client"; import Request from "./Request"; +import Session from "./Session"; import net, { Socket } from "net"; import Response from "./Response"; import moment from "moment-timezone"; import Middleware from "./Middleware"; import SmbResponse from "./SmbResponse"; -import * as util from "../protocol/util"; +import * as util from "../protocols/util"; import Smb2Response from "./Smb2Response"; -import SmbHeader from "../protocol/smb/Header"; -import Smb2Header from "../protocol/smb2/Header"; +import SmbHeader from "../protocols/smb/Header"; +import Smb2Header from "../protocols/smb2/Header"; import requestType from "./middlewares/requestType"; -import * as protocolIds from "../protocol/protocolIds"; -import SmbPacketType from "../protocol/smb/PacketType"; -import Smb2PacketType from "../protocol/smb2/PacketType"; +import * as protocolIds from "../protocols/ProtocolIds"; +import SmbPacketType from "../protocols/smb/PacketType"; +import Smb2PacketType from "../protocols/smb2/PacketType"; import * as smbRequestHandlers from "./requestHandlers/smb"; import * as smb2RequestHandlers from "./requestHandlers/smb2"; +import unhandledRequest from "./middlewares/unhandledRequest"; +import AuthenticationProvider from "./AuthenticationProvider"; import supportedProtocols from "./middlewares/supportedProtocols"; +import ShareProvider from "./ShareProvider"; export default class Server { public port: number; @@ -24,14 +28,17 @@ export default class Server { public startDate: Date; public guid = util.generateGuid(); private middlewares: Middleware[] = []; + private authenticationProviders: AuthenticationProvider[] = []; + private shareProviders: ShareProvider[] = []; + private nextSessionId: bigint = 0n; - constructor() { - this.use(supportedProtocols([protocolIds.smb, protocolIds.smb2])); + async init() { + this.use(supportedProtocols([protocolIds.Smb, protocolIds.Smb2])); const smb2RequestHandlerTypes = Object.keys(smb2RequestHandlers); for (const smb2RequestHandlerType of smb2RequestHandlerTypes) { const handler = requestType( - protocolIds.smb2, + protocolIds.Smb2, Smb2PacketType[smb2RequestHandlerType], smb2RequestHandlers[smb2RequestHandlerType] ); @@ -41,13 +48,23 @@ export default class Server { const smbRequestHandlerTypes = Object.keys(smbRequestHandlers); for (const smbRequestHandlerType of smbRequestHandlerTypes) { const handler = requestType( - protocolIds.smb, + protocolIds.Smb, SmbPacketType[smbRequestHandlerType], smbRequestHandlers[smbRequestHandlerType] ); this.use(handler); } + this.use(unhandledRequest); + + for (const authenticationProvider of this.authenticationProviders) { + await authenticationProvider.init(); + } + + for (const shareProvider of this.shareProviders) { + await shareProvider.init(); + } + this.server.addListener("connection", this.onConnection); } @@ -68,7 +85,11 @@ export default class Server { private onConnection = (socket: Socket) => { const client = new Client(this, socket); client.on("request", this.onRequest(client)); - client.setup(); + client.once("destroy", () => { + const index = this.clients.indexOf(client); + if (index !== -1) this.clients.splice(index, 1); + }); + client.init(); this.clients.push(client); } @@ -85,7 +106,7 @@ export default class Server { async handleRequest(req: Request) { let res: Response; - if (req.header.protocolId === protocolIds.smb) { + if (req.header.protocolId === protocolIds.Smb) { const header = req.header as SmbHeader; res = new SmbResponse({ protocolId: header.protocolId, @@ -108,10 +129,14 @@ export default class Server { req.response = res; - for (const middleware of this.middlewares) { - await middleware(req, res); - if (res.sent) return req.client.send(res); - if (res.redirectedReq) return await this.redirect(req, res.redirectedReq); + try { + for (const middleware of this.middlewares) { + await middleware(req, res); + if (res.sent) return req.client.send(res); + if (res.redirectedRequest) return await this.redirect(req, res.redirectedRequest); + } + } catch (err) { + console.error(err); } } @@ -120,7 +145,34 @@ export default class Server { await this.handleRequest(to); } - use(middleware: Middleware) { - this.middlewares.push(middleware); + use(element: Middleware | AuthenticationProvider | ShareProvider) { + if (element instanceof AuthenticationProvider) { + this.authenticationProviders.push(element); + } else if (element instanceof ShareProvider) { + this.shareProviders.push(element); + } else { + this.middlewares.push(element); + } + } + + getUser(domain: string, username: string) { + for (const authenticationProvider of this.authenticationProviders) { + const user = authenticationProvider.getUser(domain, username); + if (user) return user; + } + return null; + } + + getShare(name: string) { + for (const shareProvider of this.shareProviders) { + const share = shareProvider.getShare(name); + if (share) return share; + } + return null; + } + + createSession() { + const session = new Session(this.nextSessionId++); + return session; } } \ No newline at end of file diff --git a/src/server/Session.ts b/src/server/Session.ts new file mode 100644 index 0000000..f7cbca8 --- /dev/null +++ b/src/server/Session.ts @@ -0,0 +1,7 @@ +export default class Session { + constructor( + public id: bigint + ) { + + } +} \ No newline at end of file diff --git a/src/server/Share.ts b/src/server/Share.ts new file mode 100644 index 0000000..dd31927 --- /dev/null +++ b/src/server/Share.ts @@ -0,0 +1,3 @@ +export default class Share { + name: string; +} \ No newline at end of file diff --git a/src/server/ShareProvider.ts b/src/server/ShareProvider.ts new file mode 100644 index 0000000..f98581b --- /dev/null +++ b/src/server/ShareProvider.ts @@ -0,0 +1,12 @@ +import Share from "./Share"; + +export default abstract class ShareProvider { + public shares: Share[] = []; + + async init() { } + + getShare(name: string) { + const share = this.shares.find(x => x.name === name); + return share || null; + } +} \ No newline at end of file diff --git a/src/server/Smb2Request.ts b/src/server/Smb2Request.ts index 3370b09..b9b0a50 100644 --- a/src/server/Smb2Request.ts +++ b/src/server/Smb2Request.ts @@ -1,6 +1,6 @@ import Client from "./Client"; import Server from "./Server"; -import ProtocolSmb2Request from "../protocol/smb2/Request"; +import ProtocolSmb2Request from "../protocols/smb2/Request"; export default class Smb2Request extends ProtocolSmb2Request { server?: Server; diff --git a/src/server/Smb2Response.ts b/src/server/Smb2Response.ts index e08c056..8b068d5 100644 --- a/src/server/Smb2Response.ts +++ b/src/server/Smb2Response.ts @@ -1,10 +1,10 @@ import Request from "./Request"; -import Value from "../protocol/Value"; -import ProtocolSmb2Response from "../protocol/smb2/Response"; +import Value from "../protocols/Value"; +import ProtocolSmb2Response from "../protocols/smb2/Response"; export default class Smb2Response extends ProtocolSmb2Response { sent: boolean = false; - redirectedReq: Request; + redirectedRequest: Request; public status(status: number) { this.header.status = status; @@ -16,12 +16,15 @@ export default class Smb2Response extends ProtocolSmb2Response { this.header[name] = value; } - public send(data: any) { - this.body = data; + public send(data: any = {}) { + this.body = { + ...(this.body || {}), + ...data + }; this.sent = true; } public redirect(req: Request) { - this.redirectedReq = req; + this.redirectedRequest = req; } } \ No newline at end of file diff --git a/src/server/SmbRequest.ts b/src/server/SmbRequest.ts index a2c7ad7..1033628 100644 --- a/src/server/SmbRequest.ts +++ b/src/server/SmbRequest.ts @@ -1,6 +1,6 @@ import Client from "./Client"; import Server from "./Server"; -import ProtocolSmbRequest from "../protocol/smb/Request"; +import ProtocolSmbRequest from "../protocols/smb/Request"; export default class SmbRequest extends ProtocolSmbRequest { server?: Server; diff --git a/src/server/SmbResponse.ts b/src/server/SmbResponse.ts index c41a154..ccdfcc8 100644 --- a/src/server/SmbResponse.ts +++ b/src/server/SmbResponse.ts @@ -1,10 +1,10 @@ import Request from "./Request"; -import Value from "../protocol/Value"; -import ProtocolSmbResponse from "../protocol/smb/Response"; +import Value from "../protocols/Value"; +import ProtocolSmbResponse from "../protocols/smb/Response"; export default class SmbResponse extends ProtocolSmbResponse { sent: boolean = false; - redirectedReq: Request; + redirectedRequest: Request; public status(status: number) { this.header.status = status; @@ -22,6 +22,6 @@ export default class SmbResponse extends ProtocolSmbResponse { } public redirect(req: Request) { - this.redirectedReq = req; + this.redirectedRequest = req; } } \ No newline at end of file diff --git a/src/server/User.ts b/src/server/User.ts new file mode 100644 index 0000000..4d61474 --- /dev/null +++ b/src/server/User.ts @@ -0,0 +1,4 @@ +export default interface User { + name: string; + password: string; +} \ No newline at end of file diff --git a/src/server/authenticationProviders/FileAuthenticationProvider.ts b/src/server/authenticationProviders/FileAuthenticationProvider.ts new file mode 100644 index 0000000..394a0bc --- /dev/null +++ b/src/server/authenticationProviders/FileAuthenticationProvider.ts @@ -0,0 +1,14 @@ +import fs from "fs"; +import AuthenticationProvider from "../AuthenticationProvider"; + +export default class FileAuthenticationProvider extends AuthenticationProvider { + constructor( + private filename: string + ) { + super(); + } + + async init() { + this.users = JSON.parse(fs.readFileSync(this.filename, { encoding: "utf-8" })); + } +} \ No newline at end of file diff --git a/src/server/authenticationProviders/index.ts b/src/server/authenticationProviders/index.ts new file mode 100644 index 0000000..5f4d04c --- /dev/null +++ b/src/server/authenticationProviders/index.ts @@ -0,0 +1 @@ +export { default as FileAuthenticationProvider } from "./FileAuthenticationProvider"; \ No newline at end of file diff --git a/src/server/middlewares/requestType.ts b/src/server/middlewares/requestType.ts index bcb2c45..336cb6a 100644 --- a/src/server/middlewares/requestType.ts +++ b/src/server/middlewares/requestType.ts @@ -1,7 +1,7 @@ import Request from "../Smb2Request"; import Response from "../Smb2Response"; import Middleware from "../Middleware"; -import PacketType from "../../protocol/smb2/PacketType"; +import PacketType from "../../protocols/smb2/PacketType"; export default (protocolId: string, packetType: PacketType, middleware: Middleware) => async (req: Request, res: Response) => { if ( diff --git a/src/server/middlewares/unhandledRequest.ts b/src/server/middlewares/unhandledRequest.ts new file mode 100644 index 0000000..87ee02d --- /dev/null +++ b/src/server/middlewares/unhandledRequest.ts @@ -0,0 +1,7 @@ +import Request from "../Request"; +import Response from "../Response"; +import * as util from "../../protocols/util"; + +export default (req: Request, res: Response) => { + throw new Error(`unhandled request: ${util.stringify(req.header)} ${util.stringify(req.body)}`); +}; \ No newline at end of file diff --git a/src/server/remoteServices/WorkstationService.ts b/src/server/remoteServices/WorkstationService.ts new file mode 100644 index 0000000..045521b --- /dev/null +++ b/src/server/remoteServices/WorkstationService.ts @@ -0,0 +1,7 @@ +import RemoteService from "../RemoteService"; + +export default class WorkstationService extends RemoteService { + public serialize() { + return Buffer.allocUnsafe(0); + } +} \ No newline at end of file diff --git a/src/server/requestHandlers/smb/Negotiate.ts b/src/server/requestHandlers/smb/Negotiate.ts index 0722daa..4f588f6 100644 --- a/src/server/requestHandlers/smb/Negotiate.ts +++ b/src/server/requestHandlers/smb/Negotiate.ts @@ -1,9 +1,9 @@ import Request from "../../SmbRequest"; import Response from "../../SmbResponse"; import Smb2Request from "../../Smb2Request"; -import Dialect from "../../../protocol/smb2/Dialect"; -import * as protocolIds from "../../../protocol/protocolIds"; -import Smb2PacketType from "../../../protocol/smb2/PacketType"; +import Dialect from "../../../protocols/smb2/Dialect"; +import * as protocolIds from "../../../protocols/ProtocolIds"; +import Smb2PacketType from "../../../protocols/smb2/PacketType"; const supportedDialects = [ "NT LM 0.12", @@ -20,7 +20,7 @@ export default (req: Request, res: Response) => { if (matchingDialects.find(x => x.startsWith("SMB 2."))) { const newReq = new Smb2Request({ - protocolId: protocolIds.smb2, + protocolId: protocolIds.Smb2, type: Smb2PacketType.Negotiate }, { dialects: [ diff --git a/src/server/requestHandlers/smb2/InputOutputControl.ts b/src/server/requestHandlers/smb2/InputOutputControl.ts new file mode 100644 index 0000000..ab5ac8a --- /dev/null +++ b/src/server/requestHandlers/smb2/InputOutputControl.ts @@ -0,0 +1,21 @@ +import Request from "../../Smb2Request"; +import Response from "../../Smb2Response"; +import StatusCode from "../../../protocols/smb2/StatusCode"; +import ControlCode from "../../../protocols/smb2/ControlCode"; + +export default (req: Request, res: Response) => { + const controlCode = req.body.controlCode as number; + + if (controlCode === ControlCode.ValidateNegotiateInfo) { + if (req.body.fileId !== "ffffffffffffffffffffffffffffffff") throw new Error(`invalid_validate_negotiate_info_request`); + + res.status(StatusCode.Success); + res.send({ + controlCode, + fileId: req.body.fileId + }); + return; + } + + throw new Error(`not_yet_implemented`); +}; \ No newline at end of file diff --git a/src/server/requestHandlers/smb2/Negotiate.ts b/src/server/requestHandlers/smb2/Negotiate.ts index 333a58b..9755035 100644 --- a/src/server/requestHandlers/smb2/Negotiate.ts +++ b/src/server/requestHandlers/smb2/Negotiate.ts @@ -1,16 +1,17 @@ import moment from "moment-timezone"; import Request from "../../Smb2Request"; import Response from "../../Smb2Response"; -import StatusCode from "../../../protocol/smb2/StatusCode"; -import Capability from "../../../protocol/smb2/Capability"; -import Smb2Dialect from "../../../protocol/smb2/Dialect"; -import { headerSize } from "../../../protocol/smb2/Header"; -import * as structureUtil from "../../../protocol/structureUtil"; +import * as dtypUtil from "../../../protocols/dtyp/util"; +import Smb2Dialect from "../../../protocols/smb2/Dialect"; +import StatusCode from "../../../protocols/smb2/StatusCode"; +import Capability from "../../../protocols/smb2/Capability"; +import { headerSize } from "../../../protocols/smb2/Header"; +import SecurityMode from "../../../protocols/smb2/SecurityMode"; const supportedDialects = [ + Smb2Dialect.Smb2xx, Smb2Dialect.Smb210, - Smb2Dialect.Smb202, - Smb2Dialect.Smb2xx + Smb2Dialect.Smb202 ]; export default (req: Request, res: Response) => { @@ -18,28 +19,26 @@ export default (req: Request, res: Response) => { const targetDialect = supportedDialects.find(supportedDialect => dialects.find(dialect => dialect === supportedDialect) ); - const targetDialectName = structureUtil.parseEnumValue(Smb2Dialect, targetDialect); const securityBuffer = Buffer.alloc(0); res.status(StatusCode.Success); - res.set("clientId", req.header.clientId); res.send({ structureSize: 0x0041, - securityMode: 0, + securityMode: SecurityMode.SigningEnabled, dialectRevision: targetDialect, - reserved: 0, // NegotiateContextCount + reserved: 0, serverGuid: req.server.guid, - capabilities: Capability.DistributedFileSystem | Capability.MultiCreditSupport, + capabilities: Capability.DistributedFileSystem | Capability.Leasing | Capability.LargeMtu, maxTransactSize: 0x00100000, maxReadSize: 0x00100000, maxWriteSize: 0x00100000, - systemTime: structureUtil.serializeDate(moment().toDate()), - serverStartTime: structureUtil.serializeDate(req.server.startDate), + systemTime: dtypUtil.serializeFiletime(moment().toDate()), + serverStartTime: dtypUtil.serializeFiletime(req.server.startDate), securityBufferOffset: headerSize + 64, securityBufferLength: securityBuffer.length, - reserved2: 0, // NegotiateContextOffset + reserved2: 0, buffer: securityBuffer }); }; \ No newline at end of file diff --git a/src/server/requestHandlers/smb2/SessionSetup.ts b/src/server/requestHandlers/smb2/SessionSetup.ts index 8908c4a..8435457 100644 --- a/src/server/requestHandlers/smb2/SessionSetup.ts +++ b/src/server/requestHandlers/smb2/SessionSetup.ts @@ -1,31 +1,132 @@ +import os from "os"; +import moment from "moment-timezone"; import Request from "../../Smb2Request"; import Response from "../../Smb2Response"; -import * as ntlm from "../../../protocol/ntlm/util"; -import StatusCode from "../../../protocol/smb2/StatusCode"; -import NegotiateFlag from "../../../protocol/ntlm/NegotiateFlag"; +import * as ntlmUtil from "../../../protocols/ntlm/util"; +import StatusCode from "../../../protocols/smb2/StatusCode"; +import SessionFlag from "../../../protocols/smb2/SessionFlag"; +import NegotiateFlag from "../../../protocols/ntlm/NegotiateFlag"; +import NtlmMessageType from "../../../protocols/ntlm/MessageType"; +import AttributeValueId from "../../../protocols/ntlm/attributeValue/AttributeValueId"; export default (req: Request, res: Response) => { - // console.log(ntlm); - // console.log(req.body); - // console.log(); - console.log("SessionSetup"); + const buffer = req.body.buffer as Buffer; + const messageType = ntlmUtil.parseMessageType(buffer); + if (messageType === NtlmMessageType.Negotiation) { + handleNegotiationRequest(req, res); + } else if (messageType === NtlmMessageType.Authentication) { + handleAuthenticationRequest(req, res); + } +}; + +const handleNegotiationRequest = (req: Request, res: Response) => { + const buffer = req.body.buffer as Buffer; + const negotiationMessage = ntlmUtil.parseNegotiationMessage(buffer); + + if ((negotiationMessage.negotiateFlags & NegotiateFlag.ExtendedSessionSecurity) > 0) { + req.client.useExtendedSessionSecurity = true; + } + + const ntlmChallengeNegotiationFlags = syncNegotiationFlags(negotiationMessage.negotiateFlags); + + const hostname = os.hostname(); + const targetInfo = [{ + id: AttributeValueId.NetBiosDomainName, + value: hostname + }, { + id: AttributeValueId.NetBiosComputerName, + value: hostname + }, { + id: AttributeValueId.DnsDomainName, + value: hostname + }, { + id: AttributeValueId.DnsComputerName, + value: hostname + }, { + id: AttributeValueId.Timestamp, + value: moment().toDate() + }]; - const decodedNtlmNegotiation = ntlm.decodeNegotiationMessage(req.body.buffer as Buffer); - // console.log(ntlmData); + const serverChallenge = ntlmUtil.generateServerChallenge(); + req.client.serverChallenge = serverChallenge; + const challengeMessage = ntlmUtil.serializeChallengeMessage(hostname, targetInfo, ntlmChallengeNegotiationFlags, serverChallenge); - const ntlmChallengeNegotiationFlags = syncNegotiationFlags(decodedNtlmNegotiation.negotiateFlags); - const encodedNtlmChallenge = ntlm.encodeChallengeMessage(ntlmChallengeNegotiationFlags); + req.client.session = req.server.createSession(); - // console.log(encodedNtlmChallenge.toString("hex")); - res.status(StatusCode.MoreProcessingRequired); // first session setup request (second success) - res.set("clientId", req.header.clientId); + res.status(StatusCode.MoreProcessingRequired); res.send({ structureSize: 9, sessionFlags: 0, securityBufferOffset: 72, securityBufferLength: 178, - buffer: encodedNtlmChallenge + buffer: challengeMessage + }); +}; + +const handleAuthenticationRequest = (req: Request, res: Response) => { + const buffer = req.body.buffer as Buffer; + const authenticationMessage = ntlmUtil.parseAuthenticationMessage(buffer); + + let authenticated = false; + const isRequestingAnonymous = (authenticationMessage.negotiateFlags & NegotiateFlag.Anonymous) > 0; + + if (isRequestingAnonymous) { + authenticated = true; + + res.status(StatusCode.Success); + sendEmptyBody(res, { + sessionFlags: SessionFlag.Guest + }); + return; + } else { + const user = req.server.getUser(authenticationMessage.domain, authenticationMessage.username); + if (!user) { + res.status(StatusCode.LogonFailure); + sendEmptyBody(res); + return; + } + + if (req.client.useExtendedSessionSecurity) { + if (ntlmUtil.isExtendedSessionSecurityLmResponse(authenticationMessage.lmResponse)) { + throw new Error(`not_yet_implemented`); + } else { + authenticated = ntlmUtil.matchPasswordV2( + user.password, + req.client.serverChallenge, + authenticationMessage.lmResponse, + authenticationMessage.ntResponse, + authenticationMessage.domain, + authenticationMessage.username + ); + } + } else { + authenticated = ntlmUtil.matchPassword( + user.password, + req.client.serverChallenge, + authenticationMessage.lmResponse, + authenticationMessage.ntResponse + ); + } + } + + if (authenticated) { + res.status(StatusCode.Success); + } else { + res.status(StatusCode.LogonFailure); + } + + sendEmptyBody(res); +}; + +const sendEmptyBody = (res: Response, overwrite: any = {}) => { + res.send({ + structureSize: 9, + sessionFlags: 0, + securityBufferOffset: 72, + securityBufferLength: 0, + buffer: Buffer.allocUnsafe(0), + ...overwrite }); }; @@ -40,15 +141,13 @@ const syncNegotiationFlags = (negotiationFlags: number) => { if ((negotiationFlags & NegotiateFlag.UnicodeEncoding) > 0) { challengeNegotiateFlags |= NegotiateFlag.UnicodeEncoding; - } - else if ((negotiationFlags & NegotiateFlag.OemEncoding) > 0) { + } else if ((negotiationFlags & NegotiateFlag.OemEncoding) > 0) { challengeNegotiateFlags |= NegotiateFlag.OemEncoding; } if ((negotiationFlags & NegotiateFlag.ExtendedSessionSecurity) > 0) { challengeNegotiateFlags |= NegotiateFlag.ExtendedSessionSecurity; - } - else if ((negotiationFlags & NegotiateFlag.LanManagerSessionKey) > 0) { + } else if ((negotiationFlags & NegotiateFlag.LanManagerSessionKey) > 0) { challengeNegotiateFlags |= NegotiateFlag.LanManagerSessionKey; } diff --git a/src/server/requestHandlers/smb2/TreeConnect.ts b/src/server/requestHandlers/smb2/TreeConnect.ts new file mode 100644 index 0000000..a3c29d5 --- /dev/null +++ b/src/server/requestHandlers/smb2/TreeConnect.ts @@ -0,0 +1,43 @@ +import url from "url"; +import Request from "../../Smb2Request"; +import Response from "../../Smb2Response"; +import * as util from "../../../protocols/util"; +import StatusCode from "../../../protocols/smb2/StatusCode"; +import TreeConnectShareType from "../../../protocols/smb2/TreeConnectShareType"; +import TreeConnectShareFlag from "../../../protocols/smb2/TreeConnectShareFlag"; +import FilePipePrinterAccess from "../../../protocols/smb2/FilePipePrinterAccess"; + +export default (req: Request, res: Response) => { + const buffer = req.body.buffer as Buffer; + const fullUrl = util.toUnixPath(buffer.toString("ucs2")); + const parsedUrl = url.parse("smb:" + fullUrl); + const pathname = parsedUrl.pathname; + + const treeId = util.generateUint(32); + + if (pathname === "/IPC$") { + res.status(StatusCode.Success); + res.set("treeId", treeId); + res.send({ + shareType: TreeConnectShareType.Pipe, + shareFlags: TreeConnectShareFlag.NoCaching, + maximalAccess: FilePipePrinterAccess.ReadData | + FilePipePrinterAccess.ReadEa | + FilePipePrinterAccess.Execute | + FilePipePrinterAccess.ReadAttributes | + FilePipePrinterAccess.Delete | + FilePipePrinterAccess.ReadControl | + FilePipePrinterAccess.WriteDiscretionaryAccessControl | + FilePipePrinterAccess.WriteOwner | + FilePipePrinterAccess.Synchronize + }); + return; + } + + res.status(StatusCode.Success); + res.set("treeId", treeId); + res.send({ + shareType: TreeConnectShareType.Disk, + shareFlags: TreeConnectShareFlag.NoCaching + }); +}; \ No newline at end of file diff --git a/src/server/requestHandlers/smb2/index.ts b/src/server/requestHandlers/smb2/index.ts index f38e484..71b1e6d 100644 --- a/src/server/requestHandlers/smb2/index.ts +++ b/src/server/requestHandlers/smb2/index.ts @@ -1,2 +1,4 @@ export { default as Negotiate } from "./Negotiate"; -export { default as SessionSetup } from "./SessionSetup"; \ No newline at end of file +export { default as SessionSetup } from "./SessionSetup"; +export { default as TreeConnect } from "./TreeConnect"; +export { default as InputOutputControl } from "./InputOutputControl"; \ No newline at end of file diff --git a/src/server/shareProviders/FileShareProvider.ts b/src/server/shareProviders/FileShareProvider.ts new file mode 100644 index 0000000..0b7dce9 --- /dev/null +++ b/src/server/shareProviders/FileShareProvider.ts @@ -0,0 +1,14 @@ +import fs from "fs"; +import ShareProvider from "../ShareProvider"; + +export default class FileShareProvider extends ShareProvider { + constructor( + private filename: string + ) { + super(); + } + + async init() { + this.shares = JSON.parse(fs.readFileSync(this.filename, { encoding: "utf-8" })); + } +} \ No newline at end of file diff --git a/src/server/shareProviders/index.ts b/src/server/shareProviders/index.ts new file mode 100644 index 0000000..c0d64cd --- /dev/null +++ b/src/server/shareProviders/index.ts @@ -0,0 +1 @@ +export { default as FileShareProvider } from "./FileShareProvider"; \ No newline at end of file From f9819d7fe4b00ef8008c0ecf76854e01e83c09a4 Mon Sep 17 00:00:00 2001 From: ardean Date: Mon, 1 Jun 2020 19:15:19 +0200 Subject: [PATCH 02/11] extract logic into own libraries --- package-lock.json | 26 + package.json | 4 +- src/client/Session.ts | 8 +- src/protocols/dtyp/util.ts | 13 - src/protocols/ntlm/MessageType.ts | 7 - src/protocols/ntlm/NegotiateFlag.ts | 35 -- src/protocols/ntlm/Version.ts | 62 -- .../ntlm/attributeValue/AttributeValueId.ts | 15 - .../ntlm/attributeValue/AttributeValuePair.ts | 7 - src/protocols/ntlm/attributeValue/util.ts | 111 ---- src/protocols/ntlm/util.ts | 560 ------------------ src/protocols/smb2/packets/QueryDirectory.ts | 10 +- src/server/requestHandlers/smb2/Negotiate.ts | 6 +- .../requestHandlers/smb2/SessionSetup.ts | 26 +- 14 files changed, 55 insertions(+), 835 deletions(-) delete mode 100644 src/protocols/dtyp/util.ts delete mode 100644 src/protocols/ntlm/MessageType.ts delete mode 100644 src/protocols/ntlm/NegotiateFlag.ts delete mode 100644 src/protocols/ntlm/Version.ts delete mode 100644 src/protocols/ntlm/attributeValue/AttributeValueId.ts delete mode 100644 src/protocols/ntlm/attributeValue/AttributeValuePair.ts delete mode 100644 src/protocols/ntlm/attributeValue/util.ts delete mode 100644 src/protocols/ntlm/util.ts diff --git a/package-lock.json b/package-lock.json index a970e79..55e6709 100644 --- a/package-lock.json +++ b/package-lock.json @@ -306,6 +306,24 @@ "is-obj": "^1.0.0" } }, + "dtyp": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dtyp/-/dtyp-0.1.2.tgz", + "integrity": "sha512-0q5FJ7BPNW4W8V+IGdZqdgVWCXCTdUkunpXye4X7lQUZujzMQVA8PPOkHtkhk4mGnakfhHeixWX045mHfwfLQg==", + "requires": { + "moment-timezone": "^0.5.31" + }, + "dependencies": { + "moment-timezone": { + "version": "0.5.31", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", + "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", + "requires": { + "moment": ">= 2.9.0" + } + } + } + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -711,6 +729,14 @@ "path-key": "^2.0.0" } }, + "ntlmv2": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ntlmv2/-/ntlmv2-0.1.1.tgz", + "integrity": "sha512-DEtpapIZEWjWfTxWpMTYCjHVjicuNKBYZaVvsy3/BM8wz2uyQjn4uUwEC6FufLnJ9dB8dsIvE1gOBBZt6MgwyA==", + "requires": { + "dtyp": "^0.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/package.json b/package.json index 0fddcf8..3fdc76a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "file-sharing" ], "dependencies": { - "moment-timezone": "^0.5.28" + "dtyp": "^0.1.2", + "moment-timezone": "^0.5.28", + "ntlmv2": "^0.1.1" }, "devDependencies": { "@types/moment-timezone": "^0.5.12", diff --git a/src/client/Session.ts b/src/client/Session.ts index 3638e51..9fdbe10 100644 --- a/src/client/Session.ts +++ b/src/client/Session.ts @@ -1,9 +1,9 @@ import Tree from "./Tree"; +import ntlmv2 from "ntlmv2"; import Client from "./Client"; import { EventEmitter } from "events"; import Dialect from "../protocols/smb2/Dialect"; import Header from "../protocols/smb2/Header"; -import * as ntlmUtil from "../protocols/ntlm/util"; import PacketType from "../protocols/smb2/PacketType"; export interface AuthenticateOptions { @@ -68,16 +68,16 @@ class Session extends EventEmitter { }); const sessionSetupResponse = await this.request( { type: PacketType.SessionSetup }, - { buffer: ntlmUtil.serializeNegotiationMessage(this.client.host, options.domain) } + { buffer: ntlmv2.serializeNegotiationMessage(this.client.host, options.domain) } ); this._id = sessionSetupResponse.header.sessionId; - const challengeMessage = ntlmUtil.parseChallengeMessage(sessionSetupResponse.body.buffer as Buffer); + const challengeMessage = ntlmv2.parseChallengeMessage(sessionSetupResponse.body.buffer as Buffer); await this.request( { type: PacketType.SessionSetup }, { - buffer: ntlmUtil.serializeAuthenticationMessage( + buffer: ntlmv2.serializeAuthenticationMessage( options.username, this.client.host, options.domain, diff --git a/src/protocols/dtyp/util.ts b/src/protocols/dtyp/util.ts deleted file mode 100644 index 93d3357..0000000 --- a/src/protocols/dtyp/util.ts +++ /dev/null @@ -1,13 +0,0 @@ -import moment from "moment-timezone"; - -export const parseFiletime = (buffer: Buffer) => { - const milliseconds = Number(buffer.readBigUInt64LE(0) / 10000n); - return moment.utc("1601-01-01").add(milliseconds, "milliseconds").toDate(); -}; - -export const serializeFiletime = (date: Date) => { - const milliseconds = moment(date).diff(moment.utc("1601-01-01"), "milliseconds"); - const buffer = Buffer.allocUnsafe(8); - buffer.writeBigInt64LE(BigInt(milliseconds) * 10000n, 0); - return buffer; -}; \ No newline at end of file diff --git a/src/protocols/ntlm/MessageType.ts b/src/protocols/ntlm/MessageType.ts deleted file mode 100644 index 7dac97a..0000000 --- a/src/protocols/ntlm/MessageType.ts +++ /dev/null @@ -1,7 +0,0 @@ -enum MessageType { - Negotiation = 1, - Challenge = 2, - Authentication = 3 -} - -export default MessageType; \ No newline at end of file diff --git a/src/protocols/ntlm/NegotiateFlag.ts b/src/protocols/ntlm/NegotiateFlag.ts deleted file mode 100644 index fda3cde..0000000 --- a/src/protocols/ntlm/NegotiateFlag.ts +++ /dev/null @@ -1,35 +0,0 @@ -enum NegotiateFlag { - UnicodeEncoding = 1 << 0, - OemEncoding = 1 << 1, - TargetNameSupplied = 1 << 2, - - Sign = 1 << 4, - Seal = 1 << 5, - Diagram = 1 << 6, - - LanManagerSessionKey = 1 << 7, - NTLMSessionSecurity = 1 << 9, - - Anonymous = 1 << 11, - - DomainNameSupplied = 1 << 12, - WorkstationNameSupplied = 1 << 13, - - AlwaysSign = 1 << 15, - TargetTypeDomain = 1 << 16, - TargetTypeServer = 1 << 17, - - ExtendedSessionSecurity = 1 << 19, - Identify = 1 << 20, - - RequestNonNtSessionKey = 1 << 22, - TargetInfo = 1 << 23, - - Version = 1 << 25, - - Use128BitEncryption = 1 << 29, - KeyExchange = 1 << 30, - Use56BitEncryption = 1 << 31 -} - -export default NegotiateFlag; \ No newline at end of file diff --git a/src/protocols/ntlm/Version.ts b/src/protocols/ntlm/Version.ts deleted file mode 100644 index f25613e..0000000 --- a/src/protocols/ntlm/Version.ts +++ /dev/null @@ -1,62 +0,0 @@ -export const Server2003Revision = 0x0F; - -export default class Version { - public static WindowsXP = new Version(5, 1, 2600, Server2003Revision); - public static Server2003 = new Version(5, 2, 3790, Server2003Revision); - - constructor( - public majorVersion: number, - public minorVersion: number, - public buildNumber: number, - public revision: number - ) { } - - serialize() { - let offset = 0; - - const buffer = Buffer.allocUnsafe(8); - - buffer.writeInt8(this.majorVersion, offset); - offset += 1; - - buffer.writeInt8(this.minorVersion, offset); - offset += 1; - - buffer.writeUInt16LE(this.buildNumber, offset); - offset += 2; - - buffer.writeInt8(0, offset); - offset += 1; - - buffer.writeInt8(0, offset); - offset += 1; - - buffer.writeInt8(0, offset); - offset += 1; - - buffer.writeInt8(this.revision, offset); - offset += 1; - - return buffer; - } - - static parse(buffer: Buffer) { - let offset = 0; - - const majorVersion = buffer.readInt8(offset); - offset += 1; - - const minorVersion = buffer.readInt8(offset); - offset += 1; - - const buildNumber = buffer.readUInt16LE(offset); - offset += 2; - - offset += 3; - - const revision = buffer.readInt8(offset); - offset += 1; - - return new Version(majorVersion, minorVersion, buildNumber, revision); - } -} \ No newline at end of file diff --git a/src/protocols/ntlm/attributeValue/AttributeValueId.ts b/src/protocols/ntlm/attributeValue/AttributeValueId.ts deleted file mode 100644 index 5918d02..0000000 --- a/src/protocols/ntlm/attributeValue/AttributeValueId.ts +++ /dev/null @@ -1,15 +0,0 @@ -enum AttributeValueId { - EndOfLine = 0x0000, - NetBiosComputerName = 0x0001, - NetBiosDomainName = 0x0002, - DnsComputerName = 0x0003, - DnsDomainName = 0x0004, - DnsTreeName = 0x0005, - Flags = 0x0006, - Timestamp = 0x0007, - SingleHost = 0x0008, - TargetName = 0x0009, - ChannelBindings = 0x000A -} - -export default AttributeValueId; \ No newline at end of file diff --git a/src/protocols/ntlm/attributeValue/AttributeValuePair.ts b/src/protocols/ntlm/attributeValue/AttributeValuePair.ts deleted file mode 100644 index 6555eef..0000000 --- a/src/protocols/ntlm/attributeValue/AttributeValuePair.ts +++ /dev/null @@ -1,7 +0,0 @@ -import AttributeValueId from "./AttributeValueId"; - -export default interface AttributeValuePair { - id: AttributeValueId; - buffer?: Buffer; - value?: string | Date; -}; \ No newline at end of file diff --git a/src/protocols/ntlm/attributeValue/util.ts b/src/protocols/ntlm/attributeValue/util.ts deleted file mode 100644 index d78a254..0000000 --- a/src/protocols/ntlm/attributeValue/util.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as dtypUtil from "../../dtyp/util"; -import AttributeValueId from "./AttributeValueId"; -import AttributeValuePair from "./AttributeValuePair"; -import moment from "moment-timezone"; - -export const serializePairs = (pairs: AttributeValuePair[]) => { - for (const pair of pairs) { - serializePair(pair); - } - - const length = pairs.reduce((prev, current) => - prev + (current.buffer.length + 4), - 0 - ) + 4; - - const buffer = Buffer.allocUnsafe(length); - let offset = 0; - for (const pair of pairs) { - buffer.writeUInt16LE(pair.id, offset); - offset += 2; - - buffer.writeUInt16LE(pair.buffer.length, offset); - offset += 2; - - pair.buffer.copy(buffer, offset); - offset += pair.buffer.length; - } - - buffer.writeUInt16LE(AttributeValueId.EndOfLine, offset); - offset += 2; - - buffer.writeUInt16LE(0, offset); - offset += 2; - - return buffer; -}; - -export const serializePair = (pair: AttributeValuePair) => { - if (Buffer.isBuffer(pair.buffer)) return pair; - - if ( - pair.id === AttributeValueId.NetBiosComputerName || - pair.id === AttributeValueId.NetBiosDomainName || - pair.id === AttributeValueId.DnsComputerName || - pair.id === AttributeValueId.DnsDomainName || - pair.id === AttributeValueId.DnsTreeName - ) { - pair.buffer = Buffer.from(pair.value as string, "ucs2"); - delete pair.value; - } else if (pair.id === AttributeValueId.Timestamp) { - pair.buffer = dtypUtil.serializeFiletime(pair.value as Date); - delete pair.value; - } - - return pair; -}; - -export const parsePairs = (buffer: Buffer) => { - let offset = 0; - - let lastId: AttributeValueId; - - const pairs: AttributeValuePair[] = []; - while (AttributeValueId.EndOfLine !== lastId) { - lastId = buffer.readUInt16LE(offset); - offset += 2; - - const length = buffer.readUInt16LE(offset); - offset += 2; - - const data = buffer.slice(offset, offset + length); - offset += length; - - const pair = { - id: lastId, - buffer: data - }; - - parsePair(pair); - - pairs.push(pair); - } - - offset += 4; - - pairs.pop(); - - return pairs; -}; - -export const parsePair = (pair: AttributeValuePair) => { - if ( - pair.id === AttributeValueId.NetBiosComputerName || - pair.id === AttributeValueId.NetBiosDomainName || - pair.id === AttributeValueId.DnsComputerName || - pair.id === AttributeValueId.DnsDomainName || - pair.id === AttributeValueId.DnsTreeName - ) { - if (typeof pair.value === "string") return pair; - - pair.value = pair.buffer.toString("ucs2"); - delete pair.buffer; - } else if (pair.id === AttributeValueId.Timestamp) { - if (pair.value instanceof Date) return pair; - - pair.value = dtypUtil.parseFiletime(pair.buffer); - delete pair.buffer; - } - - return pair; -}; \ No newline at end of file diff --git a/src/protocols/ntlm/util.ts b/src/protocols/ntlm/util.ts deleted file mode 100644 index 28444db..0000000 --- a/src/protocols/ntlm/util.ts +++ /dev/null @@ -1,560 +0,0 @@ -import crypto from "crypto"; -import Version from "./Version"; -import MessageType from "./MessageType"; -import NegotiateFlag from "./NegotiateFlag"; -import * as attributeValueUtil from "./attributeValue/util"; -import AttributeValuePair from "./attributeValue/AttributeValuePair"; - -export const serializeNegotiationMessage = (hostname: string, domain: string) => { - hostname = hostname.toUpperCase(); - domain = domain.toUpperCase(); - - const hostnameLength = Buffer.byteLength(hostname, "ascii"); - const domainLength = Buffer.byteLength(domain, "ascii"); - - let offset = 0; - const buffer = Buffer.alloc(32 + hostnameLength + domainLength); - - buffer.write("NTLMSSP", offset, 7, "ascii"); - offset += 7; - buffer.writeUInt8(0, offset); - offset += 1; - - buffer.writeUInt32LE(MessageType.Negotiation, offset); - offset += 4; - - const negotiateFlags = NegotiateFlag.UnicodeEncoding | - NegotiateFlag.NTLMSessionSecurity | - NegotiateFlag.AlwaysSign; - buffer.writeUInt32LE(negotiateFlags, offset); - offset += 4; - - buffer.writeUInt16LE(domainLength, offset); - offset += 2; - buffer.writeUInt16LE(domainLength, offset); - offset += 2; - - const domainOffset = 0x20 + hostnameLength; - buffer.writeUInt32LE(domainOffset, offset); - offset += 4; - - buffer.writeUInt16LE(hostnameLength, offset); - offset += 2; - buffer.writeUInt16LE(hostnameLength, offset); - offset += 2; - - buffer.writeUInt32LE(0x20, offset); - offset += 4; - - buffer.write(hostname, 0x20, hostnameLength, "ascii"); - buffer.write(domain, domainOffset, domainLength, "ascii"); - - return buffer; -}; - -export const parseNegotiationMessage = (buffer: Buffer) => { - let offset = 0; - - const protocol = buffer.slice(0, 7).toString("ascii"); - if ( - protocol !== "NTLMSSP" || - buffer.readInt8(7) !== 0x00 - ) throw new Error("ntlmssp_header_not_found"); - offset += 8; - - const type = parseMessageType(buffer); - if (type !== MessageType.Negotiation) throw new Error("ntlm_message_type_is_not_negotiation"); - offset += 4; - - const negotiateFlags = buffer.readUInt32LE(offset); - offset += 4; - - const domainLength = buffer.readUInt16LE(offset); - offset += 2; - const domainMaxLength = buffer.readUInt16LE(offset); - offset += 2; - const domainOffset = buffer.readUInt32LE(offset); - offset += 4; - - const hostnameLength = buffer.readUInt16LE(offset); - offset += 2; - const hostnameMaxLength = buffer.readUInt16LE(offset); - offset += 2; - const hostnameOffset = buffer.readUInt32LE(offset); - offset += 4; - - const domain = buffer.slice(domainOffset, domainOffset + domainLength).toString("ascii"); - const hostname = buffer.slice(hostnameOffset, hostnameOffset + hostnameLength).toString("ascii"); - - return { - negotiateFlags, - domain, - hostname - }; -}; - -export const serializeChallengeMessage = ( - targetName: string, - targetInfo: AttributeValuePair[], - negotiateFlags: number, - serverChallenge: Buffer -) => { - let targetNameBuffer: Buffer; - if ((negotiateFlags & NegotiateFlag.TargetNameSupplied) === 0) { - targetNameBuffer = Buffer.from([]); - } else { - targetNameBuffer = Buffer.from(targetName, "ucs2"); - } - - let targetInfoBuffer: Buffer; - if ((negotiateFlags & NegotiateFlag.TargetInfo) === 0) { - targetInfoBuffer = Buffer.from([]); - } else { - targetInfoBuffer = attributeValueUtil.serializePairs(targetInfo); - } - - let bufferLength = 48; - const shouldSupplyVersion = (negotiateFlags & NegotiateFlag.Version) > 0; - if (shouldSupplyVersion) { - bufferLength += 8; - } - - let offset = 0; - const buffer = Buffer.alloc( - bufferLength + - targetNameBuffer.length + - targetInfoBuffer.length - ); - - buffer.write("NTLMSSP", offset, 7, "ascii"); - offset += 7; - buffer.writeUInt8(0, offset); - offset += 1; - - buffer.writeUInt32LE(MessageType.Challenge, offset); - offset += 4; - - const targetNameBufferPointerOffset = offset; - offset += 8; - - buffer.writeUInt32LE(negotiateFlags, offset); - offset += 4; - - serverChallenge.copy(buffer, offset); - offset += 8; - - buffer.fill(0, offset, offset + 8); - offset += 8; - - const targetInfoBufferPointerOffset = offset; - offset += 8; - - if (shouldSupplyVersion) { - Version.Server2003.serialize().copy(buffer, offset); - offset += 8; - } - - serializeBufferPointer(targetNameBuffer.length, offset).copy(buffer, targetNameBufferPointerOffset); - targetNameBuffer.copy(buffer, offset); - offset += targetNameBuffer.length; - - serializeBufferPointer(targetInfoBuffer.length, offset).copy(buffer, targetInfoBufferPointerOffset); - targetInfoBuffer.copy(buffer, offset); - offset += targetInfoBuffer.length; - - return buffer; -}; - -export const parseChallengeMessage = (buffer: Buffer) => { - let offset = 0; - - const protocol = buffer.slice(0, 7).toString("ascii"); - if ( - protocol !== "NTLMSSP" || - buffer.readInt8(7) !== 0x00 - ) throw new Error("ntlmssp_header_not_found"); - offset += 8; - - const type = parseMessageType(buffer); - if (type !== MessageType.Challenge) throw new Error("ntlm_message_type_is_not_challenge"); - offset += 4; - - const targetNameBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); - const targetName = buffer.slice( - targetNameBufferPointer.offset, - targetNameBufferPointer.offset + targetNameBufferPointer.length - ).toString("ucs2"); - offset += 8; - - const negotiateFlags = buffer.readUInt32LE(offset); - offset += 4; - - const serverChallenge = buffer.slice(offset, offset + 8); - offset += 8; - - offset += 8; // Reserved - - const targetInfoBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); - const targetInfo = attributeValueUtil.parsePairs( - buffer.slice( - targetInfoBufferPointer.offset, - targetInfoBufferPointer.offset + targetInfoBufferPointer.length - ) - ); - offset += 8; - - let version: Version; - if ((negotiateFlags & NegotiateFlag.Version) > 0) { - version = Version.parse(buffer.slice(offset, offset + 8)); - } - - return { - targetName, - negotiateFlags, - serverChallenge, - targetInfo, - version - }; -} - -export const serializeAuthenticationMessage = (username: string, hostname: string, domain: string, serverChallenge: Buffer, password: string) => { - hostname = hostname.toUpperCase(); - domain = domain.toUpperCase(); - - const lmResponse = createLmResponse(password, serverChallenge); - const ntResponse = createNtResponse(password, serverChallenge); - - const usernameLength = Buffer.byteLength(username, "ucs2"); - const hostnameLength = Buffer.byteLength(hostname, "ucs2"); - const domainLength = Buffer.byteLength(domain, "ucs2"); - - const lmResponseLength = 24; - const ntResponseLength = 24; - - const domainOffset = 64; - const usernameOffset = domainOffset + domainLength; - const hostnameOffset = usernameOffset + usernameLength; - const lmResponseOffset = hostnameOffset + hostnameLength; - const ntResponseOffset = lmResponseOffset + lmResponseLength; - - let offset = 0; - const messageLength = 64 + domainLength + usernameLength + hostnameLength + lmResponseLength + ntResponseLength; - const buffer = Buffer.alloc(messageLength); - - buffer.write("NTLMSSP", offset, 7, "ascii"); - offset += 7; - buffer.writeUInt8(0, offset); - offset += 1; - - buffer.writeUInt32LE(MessageType.Authentication, offset); - offset += 4; - - serializeBufferPointer(lmResponseLength, lmResponseOffset).copy(buffer, offset); - offset += 8; - - serializeBufferPointer(ntResponseLength, ntResponseOffset).copy(buffer, offset); - offset += 8; - - serializeBufferPointer(domainLength, domainOffset).copy(buffer, offset); - offset += 8; - - serializeBufferPointer(usernameLength, usernameOffset).copy(buffer, offset); - offset += 8; - - serializeBufferPointer(hostnameLength, hostnameOffset).copy(buffer, offset); - offset += 8; - - buffer.writeUInt32LE(messageLength, offset); - offset += 4; - - const negotiateFlags = NegotiateFlag.UnicodeEncoding | - NegotiateFlag.NTLMSessionSecurity | - NegotiateFlag.AlwaysSign; - buffer.writeUInt32LE(negotiateFlags, offset); - offset += 4; - - lmResponse.copy(buffer, lmResponseOffset, 0, lmResponseLength); - ntResponse.copy(buffer, ntResponseOffset, 0, ntResponseLength); - buffer.write(domain, domainOffset, domainLength, "ucs2"); - buffer.write(username, usernameOffset, usernameLength, "ucs2"); - buffer.write(hostname, hostnameOffset, hostnameLength, "ucs2"); - - return buffer; -}; - -export const parseAuthenticationMessage = (buffer: Buffer) => { - let offset = 0; - - const protocol = buffer.slice(0, 7).toString("ascii"); - if ( - protocol !== "NTLMSSP" || - buffer.readInt8(7) !== 0x00 - ) throw new Error("ntlmssp_header_not_found"); - offset += 8; - - const type = parseMessageType(buffer); - if (type !== MessageType.Authentication) throw new Error("ntlm_message_type_is_not_authentication"); - offset += 4; - - const lmResponseBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); - const lmResponse = buffer.slice( - lmResponseBufferPointer.offset, - lmResponseBufferPointer.offset + lmResponseBufferPointer.length - ); - offset += 8; - - const ntResponseBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); - const ntResponse = buffer.slice( - ntResponseBufferPointer.offset, - ntResponseBufferPointer.offset + ntResponseBufferPointer.length - ); - offset += 8; - - const domainBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); - const domain = buffer.slice( - domainBufferPointer.offset, - domainBufferPointer.offset + domainBufferPointer.length - ).toString("ucs2"); - offset += 8; - - const usernameBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); - const username = buffer.slice( - usernameBufferPointer.offset, - usernameBufferPointer.offset + usernameBufferPointer.length - ).toString("ucs2"); - offset += 8; - - const hostnameBufferPointer = parseBufferPointer(buffer.slice(offset, offset + 8)); - const hostname = buffer.slice( - hostnameBufferPointer.offset, - hostnameBufferPointer.offset + hostnameBufferPointer.length - ).toString("ucs2"); - offset += 8; - - const messageLength = buffer.readUInt32LE(offset); - offset += 4; - - const negotiateFlags = buffer.readUInt32LE(offset); - offset += 4; - - let version: Version; - if ((negotiateFlags & NegotiateFlag.Version) > 0) { - version = Version.parse(buffer.slice(offset, offset + 8)); - } - - return { - lmResponse, - ntResponse, - domain, - username, - hostname, - messageLength, - negotiateFlags, - version - }; -}; - -export const generateServerChallenge = () => { - return crypto.randomBytes(8); -} - -export const parseMessageType = (buffer: Buffer) => { - return buffer.readUInt32LE(8); -}; - -export const matchPasswordV2 = (password: string, serverChallenge: Buffer, lmResponse: Buffer, ntResponse: Buffer, domain: string, username: string) => { - if (lmResponse.length === 24) { - const clientChallenge = lmResponse.slice(16, 24); - const lmV2Response = createLmV2Response(password, serverChallenge, clientChallenge, domain, username); - if (lmV2Response.equals(lmResponse)) return true; - } - - if (isNtV2Response(ntResponse)) { - const clientProof = ntResponse.slice(0, 16); - const clientChallenge = ntResponse.slice(16); - - const validClientProof = createNtV2Proof(password, serverChallenge, clientChallenge, domain, username); - return clientProof.equals(validClientProof); - } - - return false; -}; - -export const matchPassword = (password: string, serverChallenge: Buffer, lmResponse: Buffer, ntResponse: Buffer) => { - const validLmResponse = createLmResponse(password, serverChallenge); - if (validLmResponse.equals(lmResponse)) return true; - - const validNtResponse = createNtResponse(password, serverChallenge); - return validNtResponse.equals(ntResponse); -}; - -const fixOddParity = (buffer: Buffer) => { - for (let index = 0; index < buffer.length; index++) { - let parity = 1; - for (let index2 = 1; index2 < 8; index2++) { - parity = (parity + ((buffer[index] >> index2) & 1)) % 2; - } - buffer[index] |= parity & 1; - } - return buffer; -}; - -const createDESKey = (key56: Buffer) => { - const key64 = Buffer.alloc(8); - - key64[0] = key56[0] & 0xFE; - key64[1] = ((key56[0] << 7) & 0xFF) | (key56[1] >> 1); - key64[2] = ((key56[1] << 6) & 0xFF) | (key56[2] >> 2); - key64[3] = ((key56[2] << 5) & 0xFF) | (key56[3] >> 3); - key64[4] = ((key56[3] << 4) & 0xFF) | (key56[4] >> 4); - key64[5] = ((key56[4] << 3) & 0xFF) | (key56[5] >> 5); - key64[6] = ((key56[5] << 2) & 0xFF) | (key56[6] >> 6); - key64[7] = (key56[6] << 1) & 0xFF; - - return key64; -}; - -const createLmHash = (text: string) => { - const upperCaseText = text.substring(0, 14).toUpperCase(); - const upperCaseTextLength = Buffer.byteLength(upperCaseText, "ascii"); - - const paddingBuffer = Buffer.alloc(14); - paddingBuffer.write(upperCaseText, 0, upperCaseTextLength, "ascii"); - paddingBuffer.fill(0, upperCaseTextLength); - - const halves = [ - fixOddParity(createDESKey(paddingBuffer.slice(0, 7))), - fixOddParity(createDESKey(paddingBuffer.slice(7, 14))) - ]; - - const buffer = Buffer.alloc(16); - let offset = 0; - for (const halve of halves) { - const cipher = crypto.createCipheriv("DES-ECB", halve, ""); - const string = cipher.update("KGS!@#$%", "binary", "binary"); - buffer.write(string, offset, offset + 8, "binary"); - offset += 8; - } - - return buffer; -}; - -const createNtHash = (value: string) => { - const buffer = Buffer.from(value, "ucs2"); - return createMd4HashBuffer(buffer); -}; - -const createResponse = (hash: Buffer, serverChallenge: Buffer) => { - const buffer = Buffer.alloc(24); - for (let index = 0; index < 3; index++) { - const keyBuffer = fixOddParity(createDESKey(hash.slice(index * 7, index * 7 + 7))); - const cipher = crypto.createCipheriv("DES-ECB", keyBuffer, ""); - const string = cipher.update(serverChallenge.toString("binary"), "binary", "binary"); - buffer.write(string, index * 8, index * 8 + 8, "binary"); - } - return buffer; -}; - -const serializeBufferPointer = (pointerLength: number, pointerOffset: number) => { - let offset = 0; - - const buffer = Buffer.allocUnsafe(8); - buffer.writeUInt16LE(pointerLength, offset); - offset += 2; - - buffer.writeUInt16LE(pointerLength, offset); - offset += 2; - - buffer.writeUInt32LE(pointerOffset, offset); - offset += 4; - - return buffer; -}; - -const parseBufferPointer = (buffer: Buffer) => { - let offset = 0; - - const pointerLength = buffer.readUInt16LE(offset); - offset += 2; - - offset += 2; - - const pointerOffset = buffer.readUInt32LE(offset); - offset += 4; - - return { - length: pointerLength, - offset: pointerOffset - }; -}; - -const createLmResponse = (password: string, serverChallenge: Buffer) => { - const lmHash = Buffer.alloc(21); - createLmHash(password).copy(lmHash); - lmHash.fill(0x00, 16); - - return createResponse(lmHash, serverChallenge); -}; - -const createV2Hash = (domain: string, username: string, password: string) => { - const passwordBuffer = Buffer.from(password, "ucs2"); - const md4Buffer = createMd4HashBuffer(passwordBuffer); - - const phrase = username.toUpperCase() + domain; - const phraseBuffer = Buffer.from(phrase, "ucs2"); - - return createMd5HmacBuffer(phraseBuffer, md4Buffer); -}; - -const createLmV2Response = (password: string, serverChallenge: Buffer, clientChallenge: Buffer, domain: string, username: string) => { - const v2Hash = createV2Hash(domain, username, password); - - const challengeBuffer = Buffer.concat([serverChallenge, clientChallenge]); - const md5Buffer = createMd5HmacBuffer(challengeBuffer, v2Hash); - - return Buffer.concat([md5Buffer, clientChallenge]); -}; - -const createNtV2Proof = (password: string, serverChallenge: Buffer, clientChallenge: Buffer, domain: string, username: string) => { - const v2Hash = createV2Hash(domain, username, password); - - const challengeBuffer = Buffer.concat([serverChallenge, clientChallenge]); - return createMd5HmacBuffer(challengeBuffer, v2Hash); -}; - -const createNtResponse = (password: string, serverChallenge: Buffer) => { - const ntHash = Buffer.alloc(21); - createNtHash(password).copy(ntHash); - ntHash.fill(0x00, 16); - - return createResponse(ntHash, serverChallenge); -}; - -export const isExtendedSessionSecurityLmResponse = (lmResponse: Buffer) => { - if (lmResponse.length === 24) { - if (lmResponse.slice(0, 8).equals(Buffer.alloc(8, 0))) return false; - return lmResponse.slice(8, 24).equals(Buffer.alloc(16, 0)); - } - return false; -} - -const ntV2ResponseMinLength = 32; -const ntV2ResponseStructureVersion = 0x01; - -const isNtV2Response = (ntResponse: Buffer) => ( - ntResponse.length >= 16 + ntV2ResponseMinLength && - ntResponse[16] === ntV2ResponseStructureVersion && - ntResponse[17] === ntV2ResponseStructureVersion -); - -const createMd4HashBuffer = (buffer: Buffer) => { - const md4Hash = crypto.createHash("md4"); - md4Hash.update(buffer); - return Buffer.from(md4Hash.digest("hex"), "hex"); -}; - -const createMd5HmacBuffer = (buffer: Buffer, key: Buffer) => { - const md5Hmac = crypto.createHmac("md5", key); - md5Hmac.update(buffer); - return Buffer.from(md5Hmac.digest("hex"), "hex"); -}; \ No newline at end of file diff --git a/src/protocols/smb2/packets/QueryDirectory.ts b/src/protocols/smb2/packets/QueryDirectory.ts index e12bdcd..024a743 100644 --- a/src/protocols/smb2/packets/QueryDirectory.ts +++ b/src/protocols/smb2/packets/QueryDirectory.ts @@ -1,6 +1,6 @@ +import dtyp from "dtyp"; import * as util from "../../util"; import Structure from "../../Structure"; -import * as dtypUtil from "../../dtyp/util"; import FileAttribute from "../FileAttribute"; import * as structureUtil from "../../structureUtil"; import DirectoryEntry from "../../../models/DirectoryEntry"; @@ -73,16 +73,16 @@ const parseDirectoryEntry = (entryBuffer: Buffer): DirectoryEntry => { const index = entryBuffer.readUInt32LE(offset); offset += 4; - const creationTime = dtypUtil.parseFiletime(entryBuffer.slice(offset, offset + 8)); + const creationTime = dtyp.parseFiletime(entryBuffer.slice(offset, offset + 8)); offset += 8; - const lastAccessTime = dtypUtil.parseFiletime(entryBuffer.slice(offset, offset + 8)); + const lastAccessTime = dtyp.parseFiletime(entryBuffer.slice(offset, offset + 8)); offset += 8; - const lastWriteTime = dtypUtil.parseFiletime(entryBuffer.slice(offset, offset + 8)); + const lastWriteTime = dtyp.parseFiletime(entryBuffer.slice(offset, offset + 8)); offset += 8; - const changeTime = dtypUtil.parseFiletime(entryBuffer.slice(offset, offset + 8)); + const changeTime = dtyp.parseFiletime(entryBuffer.slice(offset, offset + 8)); offset += 8; const fileSize = entryBuffer.readBigUInt64LE(offset); diff --git a/src/server/requestHandlers/smb2/Negotiate.ts b/src/server/requestHandlers/smb2/Negotiate.ts index 9755035..d5646ad 100644 --- a/src/server/requestHandlers/smb2/Negotiate.ts +++ b/src/server/requestHandlers/smb2/Negotiate.ts @@ -1,7 +1,7 @@ +import dtyp from "dtyp"; import moment from "moment-timezone"; import Request from "../../Smb2Request"; import Response from "../../Smb2Response"; -import * as dtypUtil from "../../../protocols/dtyp/util"; import Smb2Dialect from "../../../protocols/smb2/Dialect"; import StatusCode from "../../../protocols/smb2/StatusCode"; import Capability from "../../../protocols/smb2/Capability"; @@ -34,8 +34,8 @@ export default (req: Request, res: Response) => { maxTransactSize: 0x00100000, maxReadSize: 0x00100000, maxWriteSize: 0x00100000, - systemTime: dtypUtil.serializeFiletime(moment().toDate()), - serverStartTime: dtypUtil.serializeFiletime(req.server.startDate), + systemTime: dtyp.serializeFiletime(moment().toDate()), + serverStartTime: dtyp.serializeFiletime(req.server.startDate), securityBufferOffset: headerSize + 64, securityBufferLength: securityBuffer.length, reserved2: 0, diff --git a/src/server/requestHandlers/smb2/SessionSetup.ts b/src/server/requestHandlers/smb2/SessionSetup.ts index 8435457..e2cfdf4 100644 --- a/src/server/requestHandlers/smb2/SessionSetup.ts +++ b/src/server/requestHandlers/smb2/SessionSetup.ts @@ -2,16 +2,18 @@ import os from "os"; import moment from "moment-timezone"; import Request from "../../Smb2Request"; import Response from "../../Smb2Response"; -import * as ntlmUtil from "../../../protocols/ntlm/util"; import StatusCode from "../../../protocols/smb2/StatusCode"; import SessionFlag from "../../../protocols/smb2/SessionFlag"; -import NegotiateFlag from "../../../protocols/ntlm/NegotiateFlag"; -import NtlmMessageType from "../../../protocols/ntlm/MessageType"; -import AttributeValueId from "../../../protocols/ntlm/attributeValue/AttributeValueId"; + +import ntlmv2, { + MessageType as NtlmMessageType, + NegotiateFlag, + AttributeValueId +} from "ntlmv2"; export default (req: Request, res: Response) => { const buffer = req.body.buffer as Buffer; - const messageType = ntlmUtil.parseMessageType(buffer); + const messageType = ntlmv2.parseMessageType(buffer); if (messageType === NtlmMessageType.Negotiation) { handleNegotiationRequest(req, res); } else if (messageType === NtlmMessageType.Authentication) { @@ -21,7 +23,7 @@ export default (req: Request, res: Response) => { const handleNegotiationRequest = (req: Request, res: Response) => { const buffer = req.body.buffer as Buffer; - const negotiationMessage = ntlmUtil.parseNegotiationMessage(buffer); + const negotiationMessage = ntlmv2.parseNegotiationMessage(buffer); if ((negotiationMessage.negotiateFlags & NegotiateFlag.ExtendedSessionSecurity) > 0) { req.client.useExtendedSessionSecurity = true; @@ -47,9 +49,9 @@ const handleNegotiationRequest = (req: Request, res: Response) => { value: moment().toDate() }]; - const serverChallenge = ntlmUtil.generateServerChallenge(); + const serverChallenge = ntlmv2.generateServerChallenge(); req.client.serverChallenge = serverChallenge; - const challengeMessage = ntlmUtil.serializeChallengeMessage(hostname, targetInfo, ntlmChallengeNegotiationFlags, serverChallenge); + const challengeMessage = ntlmv2.serializeChallengeMessage(hostname, targetInfo, ntlmChallengeNegotiationFlags, serverChallenge); req.client.session = req.server.createSession(); @@ -66,7 +68,7 @@ const handleNegotiationRequest = (req: Request, res: Response) => { const handleAuthenticationRequest = (req: Request, res: Response) => { const buffer = req.body.buffer as Buffer; - const authenticationMessage = ntlmUtil.parseAuthenticationMessage(buffer); + const authenticationMessage = ntlmv2.parseAuthenticationMessage(buffer); let authenticated = false; const isRequestingAnonymous = (authenticationMessage.negotiateFlags & NegotiateFlag.Anonymous) > 0; @@ -88,10 +90,10 @@ const handleAuthenticationRequest = (req: Request, res: Response) => { } if (req.client.useExtendedSessionSecurity) { - if (ntlmUtil.isExtendedSessionSecurityLmResponse(authenticationMessage.lmResponse)) { + if (ntlmv2.isExtendedSessionSecurityLmResponse(authenticationMessage.lmResponse)) { throw new Error(`not_yet_implemented`); } else { - authenticated = ntlmUtil.matchPasswordV2( + authenticated = ntlmv2.matchExtendedSessionSecurityPasswordV2( user.password, req.client.serverChallenge, authenticationMessage.lmResponse, @@ -101,7 +103,7 @@ const handleAuthenticationRequest = (req: Request, res: Response) => { ); } } else { - authenticated = ntlmUtil.matchPassword( + authenticated = ntlmv2.matchPassword( user.password, req.client.serverChallenge, authenticationMessage.lmResponse, From b486483c7340d1b84e6e2063e6e0912e6676de4a Mon Sep 17 00:00:00 2001 From: ardean Date: Mon, 1 Jun 2020 19:37:21 +0200 Subject: [PATCH 03/11] cleanup & refactor --- src/protocols/Packet.ts | 4 ++-- src/protocols/smb/Request.ts | 1 - src/protocols/smb/Response.ts | 1 - src/server/Client.ts | 4 ++-- src/server/Server.ts | 10 +++++----- src/server/requestHandlers/smb/Negotiate.ts | 4 ++-- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/protocols/Packet.ts b/src/protocols/Packet.ts index 50f372c..0f9979d 100644 --- a/src/protocols/Packet.ts +++ b/src/protocols/Packet.ts @@ -1,4 +1,4 @@ -import * as protocolIds from "./ProtocolIds"; +import * as ProtocolIds from "./ProtocolIds"; import StructureField from "./StructureField"; import * as structureUtil from "./structureUtil"; @@ -6,7 +6,7 @@ export const protocolIdStructureField: StructureField = { type: String, encoding: "hex", size: 4, - defaultValue: protocolIds.Smb2 + defaultValue: ProtocolIds.Smb2 }; export default class Packet { diff --git a/src/protocols/smb/Request.ts b/src/protocols/smb/Request.ts index 022d3a5..425ea86 100644 --- a/src/protocols/smb/Request.ts +++ b/src/protocols/smb/Request.ts @@ -12,6 +12,5 @@ export default class Request extends ProtocolRequest { serialize() { return Buffer.from([]); - // return Packet.serialize(this.header, this.body); } } \ No newline at end of file diff --git a/src/protocols/smb/Response.ts b/src/protocols/smb/Response.ts index eee4c46..c180f68 100644 --- a/src/protocols/smb/Response.ts +++ b/src/protocols/smb/Response.ts @@ -12,6 +12,5 @@ export default class Response extends ProtocolResponse { serialize() { return Buffer.from([]); - // return Packet.serialize(this.header, this.body); } } \ No newline at end of file diff --git a/src/server/Client.ts b/src/server/Client.ts index ae9719d..3d8ed25 100644 --- a/src/server/Client.ts +++ b/src/server/Client.ts @@ -8,7 +8,7 @@ import Packet from "../protocols/Packet"; import Request from "../protocols/Request"; import Response from "../protocols/Response"; import Dialect from "../protocols/smb2/Dialect"; -import * as protocolIds from "../protocols/ProtocolIds"; +import * as ProtocolIds from "../protocols/ProtocolIds"; interface Client { on(event: "request", callback: (req: Request) => void): this; @@ -55,7 +55,7 @@ class Client extends EventEmitter { for (const chunk of chunks) { const protocolId = Packet.parseProtocolId(chunk); - if (protocolId === protocolIds.Smb) { + if (protocolId === ProtocolIds.Smb) { const request = SmbRequest.parse(chunk); this.emit("request", request); } else { diff --git a/src/server/Server.ts b/src/server/Server.ts index 566db26..5756110 100644 --- a/src/server/Server.ts +++ b/src/server/Server.ts @@ -11,7 +11,7 @@ import Smb2Response from "./Smb2Response"; import SmbHeader from "../protocols/smb/Header"; import Smb2Header from "../protocols/smb2/Header"; import requestType from "./middlewares/requestType"; -import * as protocolIds from "../protocols/ProtocolIds"; +import * as ProtocolIds from "../protocols/ProtocolIds"; import SmbPacketType from "../protocols/smb/PacketType"; import Smb2PacketType from "../protocols/smb2/PacketType"; import * as smbRequestHandlers from "./requestHandlers/smb"; @@ -33,12 +33,12 @@ export default class Server { private nextSessionId: bigint = 0n; async init() { - this.use(supportedProtocols([protocolIds.Smb, protocolIds.Smb2])); + this.use(supportedProtocols([ProtocolIds.Smb, ProtocolIds.Smb2])); const smb2RequestHandlerTypes = Object.keys(smb2RequestHandlers); for (const smb2RequestHandlerType of smb2RequestHandlerTypes) { const handler = requestType( - protocolIds.Smb2, + ProtocolIds.Smb2, Smb2PacketType[smb2RequestHandlerType], smb2RequestHandlers[smb2RequestHandlerType] ); @@ -48,7 +48,7 @@ export default class Server { const smbRequestHandlerTypes = Object.keys(smbRequestHandlers); for (const smbRequestHandlerType of smbRequestHandlerTypes) { const handler = requestType( - protocolIds.Smb, + ProtocolIds.Smb, SmbPacketType[smbRequestHandlerType], smbRequestHandlers[smbRequestHandlerType] ); @@ -106,7 +106,7 @@ export default class Server { async handleRequest(req: Request) { let res: Response; - if (req.header.protocolId === protocolIds.Smb) { + if (req.header.protocolId === ProtocolIds.Smb) { const header = req.header as SmbHeader; res = new SmbResponse({ protocolId: header.protocolId, diff --git a/src/server/requestHandlers/smb/Negotiate.ts b/src/server/requestHandlers/smb/Negotiate.ts index 4f588f6..2dda736 100644 --- a/src/server/requestHandlers/smb/Negotiate.ts +++ b/src/server/requestHandlers/smb/Negotiate.ts @@ -2,7 +2,7 @@ import Request from "../../SmbRequest"; import Response from "../../SmbResponse"; import Smb2Request from "../../Smb2Request"; import Dialect from "../../../protocols/smb2/Dialect"; -import * as protocolIds from "../../../protocols/ProtocolIds"; +import * as ProtocolIds from "../../../protocols/ProtocolIds"; import Smb2PacketType from "../../../protocols/smb2/PacketType"; const supportedDialects = [ @@ -20,7 +20,7 @@ export default (req: Request, res: Response) => { if (matchingDialects.find(x => x.startsWith("SMB 2."))) { const newReq = new Smb2Request({ - protocolId: protocolIds.Smb2, + protocolId: ProtocolIds.Smb2, type: Smb2PacketType.Negotiate }, { dialects: [ From 29c457b5464e292a7ee0c69df8f49c924fd989ed Mon Sep 17 00:00:00 2001 From: ardean Date: Mon, 1 Jun 2020 19:38:08 +0200 Subject: [PATCH 04/11] improve gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 198b5f4..ebf1813 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /dist .DS_Store *.log -*.json \ No newline at end of file +/shares.json +/users.json \ No newline at end of file From 0b79dd84e16d6955253e27f19aaf26a6b0754c14 Mon Sep 17 00:00:00 2001 From: ardean Date: Mon, 1 Jun 2020 21:48:02 +0200 Subject: [PATCH 05/11] small fixes --- src/client/Session.ts | 2 +- src/protocols/smb2/Packet.ts | 2 +- src/protocols/structureUtil.ts | 12 ++++++++++-- src/server/Server.ts | 2 +- .../requestHandlers/smb2/InputOutputControl.ts | 2 ++ 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/client/Session.ts b/src/client/Session.ts index 9fdbe10..c71cb7e 100644 --- a/src/client/Session.ts +++ b/src/client/Session.ts @@ -68,7 +68,7 @@ class Session extends EventEmitter { }); const sessionSetupResponse = await this.request( { type: PacketType.SessionSetup }, - { buffer: ntlmv2.serializeNegotiationMessage(this.client.host, options.domain) } + { buffer: ntlmv2.serializeNegotiationMessage(options.domain, this.client.host) } ); this._id = sessionSetupResponse.header.sessionId; diff --git a/src/protocols/smb2/Packet.ts b/src/protocols/smb2/Packet.ts index bedaa61..b80868e 100644 --- a/src/protocols/smb2/Packet.ts +++ b/src/protocols/smb2/Packet.ts @@ -32,7 +32,7 @@ export default class Packet { const structure = Packet.getStructure(header); const headerBuffer = Packet.serializeHeader(header); - const bodyBuffer = structureUtil.serializeStructure(structure, body); + const bodyBuffer = structureUtil.serializeStructure(structure, body, { addOffset: headerSize }); const buffer = Buffer.concat([headerBuffer, bodyBuffer]); const prefixedBuffer = Buffer.allocUnsafe(buffer.length + 4); diff --git a/src/protocols/structureUtil.ts b/src/protocols/structureUtil.ts index b90cdd7..62bd1a8 100644 --- a/src/protocols/structureUtil.ts +++ b/src/protocols/structureUtil.ts @@ -119,7 +119,7 @@ export const parseList = (buffer: Buffer, parser: (entryBuffer: return list; }; -export const serializeStructure = (structure: Structure, data: any) => { +export const serializeStructure = (structure: Structure, data: any, options: { addOffset?: number; } = {}) => { const normalizedData: { [fieldName: string]: { value?: Buffer; size?: number; structureFieldName?: string; structureField?: StructureField; } } = {}; const structureFieldNames = Object.keys(structure); for (const structureFieldName of structureFieldNames) { @@ -177,10 +177,18 @@ export const serializeStructure = (structure: Structure, data: any) => { for (const normalizedField of normalizedFields) { let currentOffset = offset; if (normalizedField.structureField && normalizedField.structureField.offsetFieldName) { - currentOffset = parseNumber( + const value = parseNumber( normalizedData[normalizedField.structureField.offsetFieldName].value, structure[normalizedField.structureField.offsetFieldName] ) as number; + // if (typeof options.addOffset === "number") { + // normalizedData[normalizedField.structureField.offsetFieldName].value = serializeValue( + // value + options.addOffset, + // structure[normalizedField.structureField.offsetFieldName] + // ); + // } + + currentOffset = value; } normalizedField.value.copy(buffer, currentOffset); diff --git a/src/server/Server.ts b/src/server/Server.ts index 5756110..de3a5ec 100644 --- a/src/server/Server.ts +++ b/src/server/Server.ts @@ -8,6 +8,7 @@ import Middleware from "./Middleware"; import SmbResponse from "./SmbResponse"; import * as util from "../protocols/util"; import Smb2Response from "./Smb2Response"; +import ShareProvider from "./ShareProvider"; import SmbHeader from "../protocols/smb/Header"; import Smb2Header from "../protocols/smb2/Header"; import requestType from "./middlewares/requestType"; @@ -19,7 +20,6 @@ import * as smb2RequestHandlers from "./requestHandlers/smb2"; import unhandledRequest from "./middlewares/unhandledRequest"; import AuthenticationProvider from "./AuthenticationProvider"; import supportedProtocols from "./middlewares/supportedProtocols"; -import ShareProvider from "./ShareProvider"; export default class Server { public port: number; diff --git a/src/server/requestHandlers/smb2/InputOutputControl.ts b/src/server/requestHandlers/smb2/InputOutputControl.ts index ab5ac8a..d27a84d 100644 --- a/src/server/requestHandlers/smb2/InputOutputControl.ts +++ b/src/server/requestHandlers/smb2/InputOutputControl.ts @@ -9,6 +9,8 @@ export default (req: Request, res: Response) => { if (controlCode === ControlCode.ValidateNegotiateInfo) { if (req.body.fileId !== "ffffffffffffffffffffffffffffffff") throw new Error(`invalid_validate_negotiate_info_request`); + console.log(req.body.input); + res.status(StatusCode.Success); res.send({ controlCode, From a15549e004680437e1d1ad863794f65b6d83c950 Mon Sep 17 00:00:00 2001 From: ardean Date: Wed, 7 Oct 2020 23:57:05 +0200 Subject: [PATCH 06/11] properly respond to ValidateNegotiateInfo --- src/protocols/smb2/Header.ts | 3 +- src/protocols/structureUtil.ts | 41 ++++++++----- .../smb2/InputOutputControl.ts | 59 ++++++++++++++++++- .../requestHandlers/smb2/TreeConnect.ts | 3 +- 4 files changed, 85 insertions(+), 21 deletions(-) diff --git a/src/protocols/smb2/Header.ts b/src/protocols/smb2/Header.ts index ef798a4..10c0ae8 100644 --- a/src/protocols/smb2/Header.ts +++ b/src/protocols/smb2/Header.ts @@ -95,7 +95,8 @@ export const headerStructure: HeaderStructure = { size: 8 }, signature: { - type: Number, + type: String, + encoding: "hex", size: 16 } }; \ No newline at end of file diff --git a/src/protocols/structureUtil.ts b/src/protocols/structureUtil.ts index 62bd1a8..360af7f 100644 --- a/src/protocols/structureUtil.ts +++ b/src/protocols/structureUtil.ts @@ -120,7 +120,15 @@ export const parseList = (buffer: Buffer, parser: (entryBuffer: }; export const serializeStructure = (structure: Structure, data: any, options: { addOffset?: number; } = {}) => { - const normalizedData: { [fieldName: string]: { value?: Buffer; size?: number; structureFieldName?: string; structureField?: StructureField; } } = {}; + const normalizedData: { + [fieldName: string]: { + value?: Buffer; + size?: number; + offset?: number; + structureFieldName?: string; + structureField?: StructureField; + } + } = {}; const structureFieldNames = Object.keys(structure); for (const structureFieldName of structureFieldNames) { const structureField = structure[structureFieldName]; @@ -175,26 +183,27 @@ export const serializeStructure = (structure: Structure, data: any, options: { a let offset = 0; for (const normalizedField of normalizedFields) { - let currentOffset = offset; - if (normalizedField.structureField && normalizedField.structureField.offsetFieldName) { - const value = parseNumber( - normalizedData[normalizedField.structureField.offsetFieldName].value, - structure[normalizedField.structureField.offsetFieldName] - ) as number; - // if (typeof options.addOffset === "number") { - // normalizedData[normalizedField.structureField.offsetFieldName].value = serializeValue( - // value + options.addOffset, - // structure[normalizedField.structureField.offsetFieldName] - // ); - // } - - currentOffset = value; + if ( + normalizedField.structureField && + normalizedField.structureField.offsetFieldName + ) { + let currentOffset = offset; + if (typeof options.addOffset === "number") { + currentOffset += options.addOffset; + } + + const offsetField = normalizedData[normalizedField.structureField.offsetFieldName]; + offsetField.value = serializeValue(currentOffset, offsetField.structureField); } - normalizedField.value.copy(buffer, currentOffset); + normalizedField.offset = offset; offset += normalizedField.size; } + for (const normalizedField of normalizedFields) { + normalizedField.value.copy(buffer, normalizedField.offset); + } + return buffer; }; diff --git a/src/server/requestHandlers/smb2/InputOutputControl.ts b/src/server/requestHandlers/smb2/InputOutputControl.ts index d27a84d..0f9d2d8 100644 --- a/src/server/requestHandlers/smb2/InputOutputControl.ts +++ b/src/server/requestHandlers/smb2/InputOutputControl.ts @@ -2,19 +2,72 @@ import Request from "../../Smb2Request"; import Response from "../../Smb2Response"; import StatusCode from "../../../protocols/smb2/StatusCode"; import ControlCode from "../../../protocols/smb2/ControlCode"; +import * as structureUtil from "../../../protocols/structureUtil"; export default (req: Request, res: Response) => { const controlCode = req.body.controlCode as number; if (controlCode === ControlCode.ValidateNegotiateInfo) { - if (req.body.fileId !== "ffffffffffffffffffffffffffffffff") throw new Error(`invalid_validate_negotiate_info_request`); + if (req.body.fileId !== "f".repeat(32)) throw new Error(`invalid_validate_negotiate_info_request`); - console.log(req.body.input); + const input = structureUtil.parseStructure(req.body.input, { + capabilities: { + type: Number, + size: 4 + }, + fileId: { + type: String, + encoding: "hex", + size: 16 + }, + securityMode: { + type: Number, + size: 2, + defaultValue: 1 + }, + dialectCount: { + type: Number, + signedness: "Unsigned", + size: 2 + }, + dialects: { + type: Number, + countFieldName: "dialectCount", + size: 2 + } + }); + + const output = structureUtil.serializeStructure({ + capabilities: { + type: Number, + size: 4 + }, + fileId: { + type: String, + encoding: "hex", + size: 16 + }, + securityMode: { + type: Number, + size: 2, + defaultValue: 1 + }, + dialect: { + type: Number, + signedness: "Unsigned", + size: 2 + } + }, { + ...input, + dialect: 528 // TODO: use Negotiate targetDialect! + }); res.status(StatusCode.Success); res.send({ controlCode, - fileId: req.body.fileId + fileId: req.body.fileId, + outputCount: output.length, + output }); return; } diff --git a/src/server/requestHandlers/smb2/TreeConnect.ts b/src/server/requestHandlers/smb2/TreeConnect.ts index a3c29d5..2e30e1b 100644 --- a/src/server/requestHandlers/smb2/TreeConnect.ts +++ b/src/server/requestHandlers/smb2/TreeConnect.ts @@ -38,6 +38,7 @@ export default (req: Request, res: Response) => { res.set("treeId", treeId); res.send({ shareType: TreeConnectShareType.Disk, - shareFlags: TreeConnectShareFlag.NoCaching + shareFlags: TreeConnectShareFlag.NoCaching, + maximalAccess: 0x001f01ff }); }; \ No newline at end of file From d1e730efd2039e7228a1ce5b0dd4fbf19ddba1f9 Mon Sep 17 00:00:00 2001 From: ardean Date: Thu, 8 Oct 2020 11:57:26 +0200 Subject: [PATCH 07/11] first step to smb3 support --- src/protocols/smb2/Dialect.ts | 2 ++ src/protocols/smb2/packets/Negotiate.ts | 2 +- src/server/Client.ts | 7 ++++++- src/server/Server.ts | 13 ++++++++++-- .../smb2/InputOutputControl.ts | 2 +- src/server/requestHandlers/smb2/Negotiate.ts | 20 +++++++++---------- 6 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/protocols/smb2/Dialect.ts b/src/protocols/smb2/Dialect.ts index 1c7e74c..daec5ac 100644 --- a/src/protocols/smb2/Dialect.ts +++ b/src/protocols/smb2/Dialect.ts @@ -1,3 +1,5 @@ +export const formatDialectName = (dialect: Dialect) => dialect.toString(16); + enum Dialect { Smb202 = 0x0202, Smb210 = 0x0210, diff --git a/src/protocols/smb2/packets/Negotiate.ts b/src/protocols/smb2/packets/Negotiate.ts index 448dfaf..936d08c 100644 --- a/src/protocols/smb2/packets/Negotiate.ts +++ b/src/protocols/smb2/packets/Negotiate.ts @@ -74,7 +74,7 @@ const responseStructure: Structure = { type: Number, size: 4 }, - maxTransactSize: { + maxTransactionSize: { type: Number, size: 4 }, diff --git a/src/server/Client.ts b/src/server/Client.ts index 3d8ed25..23d6293 100644 --- a/src/server/Client.ts +++ b/src/server/Client.ts @@ -7,8 +7,8 @@ import Smb2Request from "./Smb2Request"; import Packet from "../protocols/Packet"; import Request from "../protocols/Request"; import Response from "../protocols/Response"; -import Dialect from "../protocols/smb2/Dialect"; import * as ProtocolIds from "../protocols/ProtocolIds"; +import Dialect, { formatDialectName } from "../protocols/smb2/Dialect"; interface Client { on(event: "request", callback: (req: Request) => void): this; @@ -41,6 +41,11 @@ class Client extends EventEmitter { this.socket.addListener("close", this.onClose); } + setTargetDialect(dialect: Dialect) { + this.targetDialect = dialect; + this.targetDialectName = formatDialectName(dialect); + } + private onData = (buffer: Buffer) => { if (this.restChunk) { buffer = Buffer.concat([this.restChunk, buffer]); diff --git a/src/server/Server.ts b/src/server/Server.ts index de3a5ec..33ae826 100644 --- a/src/server/Server.ts +++ b/src/server/Server.ts @@ -12,6 +12,7 @@ import ShareProvider from "./ShareProvider"; import SmbHeader from "../protocols/smb/Header"; import Smb2Header from "../protocols/smb2/Header"; import requestType from "./middlewares/requestType"; +import Smb2Dialect from "../protocols/smb2/Dialect"; import * as ProtocolIds from "../protocols/ProtocolIds"; import SmbPacketType from "../protocols/smb/PacketType"; import Smb2PacketType from "../protocols/smb2/PacketType"; @@ -31,7 +32,15 @@ export default class Server { private authenticationProviders: AuthenticationProvider[] = []; private shareProviders: ShareProvider[] = []; private nextSessionId: bigint = 0n; - + public supportedSmb2Dialects = [ + Smb2Dialect.Smb3xx, + Smb2Dialect.Smb311, + Smb2Dialect.Smb302, + Smb2Dialect.Smb300, + Smb2Dialect.Smb2xx, + Smb2Dialect.Smb210, + Smb2Dialect.Smb202 + ]; async init() { this.use(supportedProtocols([ProtocolIds.Smb, ProtocolIds.Smb2])); @@ -71,7 +80,7 @@ export default class Server { async listen(port: number = 445) { this.port = port; - await new Promise((resolve) => { + await new Promise(resolve => { this.server.listen({ port }, () => { resolve(); }); diff --git a/src/server/requestHandlers/smb2/InputOutputControl.ts b/src/server/requestHandlers/smb2/InputOutputControl.ts index 0f9d2d8..542bd0d 100644 --- a/src/server/requestHandlers/smb2/InputOutputControl.ts +++ b/src/server/requestHandlers/smb2/InputOutputControl.ts @@ -59,7 +59,7 @@ export default (req: Request, res: Response) => { } }, { ...input, - dialect: 528 // TODO: use Negotiate targetDialect! + dialect: req.client.targetDialect }); res.status(StatusCode.Success); diff --git a/src/server/requestHandlers/smb2/Negotiate.ts b/src/server/requestHandlers/smb2/Negotiate.ts index d5646ad..d987eae 100644 --- a/src/server/requestHandlers/smb2/Negotiate.ts +++ b/src/server/requestHandlers/smb2/Negotiate.ts @@ -8,18 +8,16 @@ import Capability from "../../../protocols/smb2/Capability"; import { headerSize } from "../../../protocols/smb2/Header"; import SecurityMode from "../../../protocols/smb2/SecurityMode"; -const supportedDialects = [ - Smb2Dialect.Smb2xx, - Smb2Dialect.Smb210, - Smb2Dialect.Smb202 -]; - export default (req: Request, res: Response) => { const dialects = req.body.dialects as Smb2Dialect[]; - const targetDialect = supportedDialects.find(supportedDialect => - dialects.find(dialect => dialect === supportedDialect) + const targetDialect = req.server.supportedSmb2Dialects.find( + supportedDialect => dialects.find( + dialect => dialect === supportedDialect + ) ); + req.client.setTargetDialect(targetDialect); + const securityBuffer = Buffer.alloc(0); res.status(StatusCode.Success); @@ -31,9 +29,9 @@ export default (req: Request, res: Response) => { reserved: 0, serverGuid: req.server.guid, capabilities: Capability.DistributedFileSystem | Capability.Leasing | Capability.LargeMtu, - maxTransactSize: 0x00100000, - maxReadSize: 0x00100000, - maxWriteSize: 0x00100000, + maxTransactionSize: 0x00800000, + maxReadSize: 0x00800000, + maxWriteSize: 0x00800000, systemTime: dtyp.serializeFiletime(moment().toDate()), serverStartTime: dtyp.serializeFiletime(req.server.startDate), securityBufferOffset: headerSize + 64, From b6a9dce88a0bf1f485d4bbe5dd7a20babaa4f7b5 Mon Sep 17 00:00:00 2001 From: Aron Dean Stifani Date: Sun, 11 Oct 2020 13:33:26 +0200 Subject: [PATCH 08/11] prepare for v2.0.2 only --- src/client/Session.ts | 2 +- src/protocols/smb2/Capability.ts | 6 +++++- src/protocols/smb2/PacketType.ts | 2 +- .../smb2/TreeConnectShareCapability.ts | 8 ++++++++ src/protocols/smb2/TreeConnectShareFlag.ts | 4 ++-- .../packets/{LogOff.ts => SessionLogoff.ts} | 0 src/protocols/smb2/packets/index.ts | 2 +- src/server/Server.ts | 9 +-------- .../smb2/InputOutputControl.ts | 6 +++++- src/server/requestHandlers/smb2/Negotiate.ts | 10 +++++++++- .../requestHandlers/smb2/SessionLogoff.ts | 9 +++++++++ .../requestHandlers/smb2/TreeConnect.ts | 20 ++++++++++++++++--- src/server/requestHandlers/smb2/index.ts | 1 + 13 files changed, 60 insertions(+), 19 deletions(-) create mode 100644 src/protocols/smb2/TreeConnectShareCapability.ts rename src/protocols/smb2/packets/{LogOff.ts => SessionLogoff.ts} (100%) create mode 100644 src/server/requestHandlers/smb2/SessionLogoff.ts diff --git a/src/client/Session.ts b/src/client/Session.ts index c71cb7e..43c82bc 100644 --- a/src/client/Session.ts +++ b/src/client/Session.ts @@ -104,7 +104,7 @@ class Session extends EventEmitter { await Promise.all(this.connectedTrees.map(x => x.disconnect())); - await this.request({ type: PacketType.LogOff }); + await this.request({ type: PacketType.SessionLogoff }); delete this._id; this.emit("logoff", this); diff --git a/src/protocols/smb2/Capability.ts b/src/protocols/smb2/Capability.ts index dca3fa5..24c7f94 100644 --- a/src/protocols/smb2/Capability.ts +++ b/src/protocols/smb2/Capability.ts @@ -1,7 +1,11 @@ enum Capability { DistributedFileSystem = 1 << 0, Leasing = 1 << 1, - LargeMtu = 1 << 2 + LargeMtu = 1 << 2, + MultiChannel = 1 << 3, + PersistentHandles = 1 << 4, + DirectoryLeasing = 1 << 5, + Encryption = 1 << 6 } export default Capability; \ No newline at end of file diff --git a/src/protocols/smb2/PacketType.ts b/src/protocols/smb2/PacketType.ts index abe0eac..8be56d8 100644 --- a/src/protocols/smb2/PacketType.ts +++ b/src/protocols/smb2/PacketType.ts @@ -1,7 +1,7 @@ enum PacketType { Negotiate = 0x0000, SessionSetup = 0x0001, - LogOff = 0x0002, + SessionLogoff = 0x0002, TreeConnect = 0x0003, TreeDisconnect = 0x0004, Create = 0x0005, diff --git a/src/protocols/smb2/TreeConnectShareCapability.ts b/src/protocols/smb2/TreeConnectShareCapability.ts new file mode 100644 index 0000000..25042e5 --- /dev/null +++ b/src/protocols/smb2/TreeConnectShareCapability.ts @@ -0,0 +1,8 @@ +enum TreeConnectShareCapability { + DistributedFileSystem = 1 << 3, + ContinuousAvailability = 1 << 4, + Scaleout = 1 << 5, + Cluster = 1 << 6 +} + +export default TreeConnectShareCapability; \ No newline at end of file diff --git a/src/protocols/smb2/TreeConnectShareFlag.ts b/src/protocols/smb2/TreeConnectShareFlag.ts index c398b84..1f6991d 100644 --- a/src/protocols/smb2/TreeConnectShareFlag.ts +++ b/src/protocols/smb2/TreeConnectShareFlag.ts @@ -1,10 +1,10 @@ enum TreeConnectShareFlag { ManualCaching = 0x00000000, + DistributedFileSystem = 0x00000001, + DistributedFileSystemRoot = 0x00000002, AutoCaching = 0x00000010, VdoCaching = 0x00000020, NoCaching = 0x00000030, - Dfs = 0x00000001, - DfsRoot = 0x00000002, RestrictExclusiveOpens = 0x00000100, ForceSharedDelete = 0x00000200, AllowNamespaceCaching = 0x00000400, diff --git a/src/protocols/smb2/packets/LogOff.ts b/src/protocols/smb2/packets/SessionLogoff.ts similarity index 100% rename from src/protocols/smb2/packets/LogOff.ts rename to src/protocols/smb2/packets/SessionLogoff.ts diff --git a/src/protocols/smb2/packets/index.ts b/src/protocols/smb2/packets/index.ts index f6bbb62..9697abe 100644 --- a/src/protocols/smb2/packets/index.ts +++ b/src/protocols/smb2/packets/index.ts @@ -1,6 +1,6 @@ export { default as Negotiate } from "./Negotiate"; export { default as SessionSetup } from "./SessionSetup"; -export { default as LogOff } from "./LogOff"; +export { default as SessionLogoff } from "./SessionLogoff"; export { default as TreeConnect } from "./TreeConnect"; export { default as TreeDisconnect } from "./TreeDisconnect"; export { default as Create } from "./Create"; diff --git a/src/server/Server.ts b/src/server/Server.ts index 33ae826..e5ffc7c 100644 --- a/src/server/Server.ts +++ b/src/server/Server.ts @@ -33,12 +33,6 @@ export default class Server { private shareProviders: ShareProvider[] = []; private nextSessionId: bigint = 0n; public supportedSmb2Dialects = [ - Smb2Dialect.Smb3xx, - Smb2Dialect.Smb311, - Smb2Dialect.Smb302, - Smb2Dialect.Smb300, - Smb2Dialect.Smb2xx, - Smb2Dialect.Smb210, Smb2Dialect.Smb202 ]; async init() { @@ -131,8 +125,7 @@ export default class Server { messageId: header.messageId, clientId: header.clientId, treeId: header.treeId, - sessionId: header.sessionId, - signature: header.signature + sessionId: header.sessionId }); } diff --git a/src/server/requestHandlers/smb2/InputOutputControl.ts b/src/server/requestHandlers/smb2/InputOutputControl.ts index 542bd0d..2aed015 100644 --- a/src/server/requestHandlers/smb2/InputOutputControl.ts +++ b/src/server/requestHandlers/smb2/InputOutputControl.ts @@ -1,7 +1,9 @@ import Request from "../../Smb2Request"; import Response from "../../Smb2Response"; import StatusCode from "../../../protocols/smb2/StatusCode"; +import Capability from "../../../protocols/smb2/Capability"; import ControlCode from "../../../protocols/smb2/ControlCode"; +import SecurityMode from "../../../protocols/smb2/SecurityMode"; import * as structureUtil from "../../../protocols/structureUtil"; export default (req: Request, res: Response) => { @@ -58,7 +60,9 @@ export default (req: Request, res: Response) => { size: 2 } }, { - ...input, + capabilities: Capability.DistributedFileSystem, + fileId: req.server.guid, + securityMode: SecurityMode.SigningEnabled, dialect: req.client.targetDialect }); diff --git a/src/server/requestHandlers/smb2/Negotiate.ts b/src/server/requestHandlers/smb2/Negotiate.ts index d987eae..742c5a8 100644 --- a/src/server/requestHandlers/smb2/Negotiate.ts +++ b/src/server/requestHandlers/smb2/Negotiate.ts @@ -18,6 +18,14 @@ export default (req: Request, res: Response) => { req.client.setTargetDialect(targetDialect); + let capabilities = Capability.DistributedFileSystem; + if ( + targetDialect !== Smb2Dialect.Smb202 + ) { + capabilities |= Capability.Leasing; + capabilities |= Capability.LargeMtu; + } + const securityBuffer = Buffer.alloc(0); res.status(StatusCode.Success); @@ -28,7 +36,7 @@ export default (req: Request, res: Response) => { dialectRevision: targetDialect, reserved: 0, serverGuid: req.server.guid, - capabilities: Capability.DistributedFileSystem | Capability.Leasing | Capability.LargeMtu, + capabilities, maxTransactionSize: 0x00800000, maxReadSize: 0x00800000, maxWriteSize: 0x00800000, diff --git a/src/server/requestHandlers/smb2/SessionLogoff.ts b/src/server/requestHandlers/smb2/SessionLogoff.ts new file mode 100644 index 0000000..a7ee1b8 --- /dev/null +++ b/src/server/requestHandlers/smb2/SessionLogoff.ts @@ -0,0 +1,9 @@ +import Request from "../../Smb2Request"; +import Response from "../../Smb2Response"; +import StatusCode from "../../../protocols/smb2/StatusCode"; + +export default (req: Request, res: Response) => { + // TODO: proper logoff + res.status(StatusCode.Success); + res.send(); +}; \ No newline at end of file diff --git a/src/server/requestHandlers/smb2/TreeConnect.ts b/src/server/requestHandlers/smb2/TreeConnect.ts index 2e30e1b..df50238 100644 --- a/src/server/requestHandlers/smb2/TreeConnect.ts +++ b/src/server/requestHandlers/smb2/TreeConnect.ts @@ -6,6 +6,7 @@ import StatusCode from "../../../protocols/smb2/StatusCode"; import TreeConnectShareType from "../../../protocols/smb2/TreeConnectShareType"; import TreeConnectShareFlag from "../../../protocols/smb2/TreeConnectShareFlag"; import FilePipePrinterAccess from "../../../protocols/smb2/FilePipePrinterAccess"; +import TreeConnectShareCapability from "../../../protocols/smb2/TreeConnectShareCapability"; export default (req: Request, res: Response) => { const buffer = req.body.buffer as Buffer; @@ -15,13 +16,18 @@ export default (req: Request, res: Response) => { const treeId = util.generateUint(32); + const cachingMode = TreeConnectShareFlag.ManualCaching; + if (pathname === "/IPC$") { res.status(StatusCode.Success); res.set("treeId", treeId); res.send({ shareType: TreeConnectShareType.Pipe, - shareFlags: TreeConnectShareFlag.NoCaching, - maximalAccess: FilePipePrinterAccess.ReadData | + shareFlags: ( + cachingMode + ), + maximalAccess: ( + FilePipePrinterAccess.ReadData | FilePipePrinterAccess.ReadEa | FilePipePrinterAccess.Execute | FilePipePrinterAccess.ReadAttributes | @@ -30,6 +36,7 @@ export default (req: Request, res: Response) => { FilePipePrinterAccess.WriteDiscretionaryAccessControl | FilePipePrinterAccess.WriteOwner | FilePipePrinterAccess.Synchronize + ) }); return; } @@ -38,7 +45,14 @@ export default (req: Request, res: Response) => { res.set("treeId", treeId); res.send({ shareType: TreeConnectShareType.Disk, - shareFlags: TreeConnectShareFlag.NoCaching, + shareFlags: ( + cachingMode | + TreeConnectShareFlag.DistributedFileSystem | + TreeConnectShareFlag.DistributedFileSystemRoot + ), + capabilities: ( + TreeConnectShareCapability.DistributedFileSystem + ), maximalAccess: 0x001f01ff }); }; \ No newline at end of file diff --git a/src/server/requestHandlers/smb2/index.ts b/src/server/requestHandlers/smb2/index.ts index 71b1e6d..dbdf309 100644 --- a/src/server/requestHandlers/smb2/index.ts +++ b/src/server/requestHandlers/smb2/index.ts @@ -1,4 +1,5 @@ export { default as Negotiate } from "./Negotiate"; export { default as SessionSetup } from "./SessionSetup"; +export { default as SessionLogoff } from "./SessionLogoff"; export { default as TreeConnect } from "./TreeConnect"; export { default as InputOutputControl } from "./InputOutputControl"; \ No newline at end of file From 62c1ff700477f699bf42b98df3274b3bc27f2836 Mon Sep 17 00:00:00 2001 From: Aron Dean Stifani Date: Sun, 11 Oct 2020 13:40:10 +0200 Subject: [PATCH 09/11] cleanup --- src/server/Client.ts | 2 -- src/server/Server.ts | 6 ------ src/server/Session.ts | 7 ------- src/server/requestHandlers/smb2/SessionSetup.ts | 2 -- 4 files changed, 17 deletions(-) delete mode 100644 src/server/Session.ts diff --git a/src/server/Client.ts b/src/server/Client.ts index 23d6293..fc0d939 100644 --- a/src/server/Client.ts +++ b/src/server/Client.ts @@ -1,6 +1,5 @@ import { Socket } from "net"; import Server from "./Server"; -import Session from "./Session"; import { EventEmitter } from "events"; import SmbRequest from "./SmbRequest"; import Smb2Request from "./Smb2Request"; @@ -24,7 +23,6 @@ class Client extends EventEmitter { public targetDialectName: string; public serverChallenge: Buffer; public useExtendedSessionSecurity: boolean = false; - public session: Session; constructor( private server: Server, diff --git a/src/server/Server.ts b/src/server/Server.ts index e5ffc7c..fcf713d 100644 --- a/src/server/Server.ts +++ b/src/server/Server.ts @@ -1,6 +1,5 @@ import Client from "./Client"; import Request from "./Request"; -import Session from "./Session"; import net, { Socket } from "net"; import Response from "./Response"; import moment from "moment-timezone"; @@ -172,9 +171,4 @@ export default class Server { } return null; } - - createSession() { - const session = new Session(this.nextSessionId++); - return session; - } } \ No newline at end of file diff --git a/src/server/Session.ts b/src/server/Session.ts deleted file mode 100644 index f7cbca8..0000000 --- a/src/server/Session.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default class Session { - constructor( - public id: bigint - ) { - - } -} \ No newline at end of file diff --git a/src/server/requestHandlers/smb2/SessionSetup.ts b/src/server/requestHandlers/smb2/SessionSetup.ts index e2cfdf4..2ec235e 100644 --- a/src/server/requestHandlers/smb2/SessionSetup.ts +++ b/src/server/requestHandlers/smb2/SessionSetup.ts @@ -53,8 +53,6 @@ const handleNegotiationRequest = (req: Request, res: Response) => { req.client.serverChallenge = serverChallenge; const challengeMessage = ntlmv2.serializeChallengeMessage(hostname, targetInfo, ntlmChallengeNegotiationFlags, serverChallenge); - req.client.session = req.server.createSession(); - res.status(StatusCode.MoreProcessingRequired); res.send({ From 23c1e6bce2a28cf772665f8eb6531f49aa01437b Mon Sep 17 00:00:00 2001 From: Aron Dean Stifani Date: Mon, 12 Oct 2020 21:41:16 +0200 Subject: [PATCH 10/11] update ntlmv2 --- package-lock.json | 6 +++--- package.json | 2 +- src/server/requestHandlers/smb2/SessionSetup.ts | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a5a7f6..1e2363e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -845,9 +845,9 @@ "dev": true }, "ntlmv2": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ntlmv2/-/ntlmv2-0.1.1.tgz", - "integrity": "sha512-DEtpapIZEWjWfTxWpMTYCjHVjicuNKBYZaVvsy3/BM8wz2uyQjn4uUwEC6FufLnJ9dB8dsIvE1gOBBZt6MgwyA==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/ntlmv2/-/ntlmv2-0.1.4.tgz", + "integrity": "sha512-DiVmUsFt3LlFwEo6VY5pTTab6Jxs3P5HYGj1rk+2FAGEgVyfs0mJNdW4JjAkEuIWmG6S49qrc1aHkxyzw9zNuw==", "requires": { "dtyp": "^0.1.1" } diff --git a/package.json b/package.json index 35a8e8a..24883a2 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "dependencies": { "dtyp": "^0.1.2", "moment-timezone": "^0.5.31", - "ntlmv2": "^0.1.1" + "ntlmv2": "^0.1.4" }, "devDependencies": { "@types/node": "^13.7.4", diff --git a/src/server/requestHandlers/smb2/SessionSetup.ts b/src/server/requestHandlers/smb2/SessionSetup.ts index 2ec235e..39b1e49 100644 --- a/src/server/requestHandlers/smb2/SessionSetup.ts +++ b/src/server/requestHandlers/smb2/SessionSetup.ts @@ -163,8 +163,10 @@ const syncNegotiationFlags = (negotiationFlags: number) => { challengeNegotiateFlags |= NegotiateFlag.Seal; } - if ((negotiationFlags & NegotiateFlag.Sign) > 0 || - (negotiationFlags & NegotiateFlag.Seal) > 0) { + if ( + (negotiationFlags & NegotiateFlag.Sign) > 0 || + (negotiationFlags & NegotiateFlag.Seal) > 0 + ) { if ((negotiationFlags & NegotiateFlag.Use56BitEncryption) > 0) { // [MS-NLMP] If the client sends NTLMSSP_NEGOTIATE_SEAL or NTLMSSP_NEGOTIATE_SIGN with // NTLMSSP_NEGOTIATE_56 to the server in the NEGOTIATE_MESSAGE, the server MUST return From 0bc0433af874fbe34b9e518718faca9a2aeee825 Mon Sep 17 00:00:00 2001 From: ardean Date: Fri, 16 Oct 2020 10:50:21 +0200 Subject: [PATCH 11/11] refactor --- src/server/Server.ts | 4 ++-- src/server/requestHandlers/smb2/SessionSetup.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/Server.ts b/src/server/Server.ts index fcf713d..9c93b96 100644 --- a/src/server/Server.ts +++ b/src/server/Server.ts @@ -156,7 +156,7 @@ export default class Server { } } - getUser(domain: string, username: string) { + findUser(domain: string, username: string) { for (const authenticationProvider of this.authenticationProviders) { const user = authenticationProvider.getUser(domain, username); if (user) return user; @@ -164,7 +164,7 @@ export default class Server { return null; } - getShare(name: string) { + findShare(name: string) { for (const shareProvider of this.shareProviders) { const share = shareProvider.getShare(name); if (share) return share; diff --git a/src/server/requestHandlers/smb2/SessionSetup.ts b/src/server/requestHandlers/smb2/SessionSetup.ts index 39b1e49..ccd1c66 100644 --- a/src/server/requestHandlers/smb2/SessionSetup.ts +++ b/src/server/requestHandlers/smb2/SessionSetup.ts @@ -80,7 +80,7 @@ const handleAuthenticationRequest = (req: Request, res: Response) => { }); return; } else { - const user = req.server.getUser(authenticationMessage.domain, authenticationMessage.username); + const user = req.server.findUser(authenticationMessage.domain, authenticationMessage.username); if (!user) { res.status(StatusCode.LogonFailure); sendEmptyBody(res);