SMB1Client.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. /* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Diagnostics;
  10. using System.Net;
  11. using System.Net.Sockets;
  12. using System.Threading;
  13. using SMBLibrary.Authentication.NTLM;
  14. using SMBLibrary.NetBios;
  15. using SMBLibrary.Services;
  16. using SMBLibrary.SMB1;
  17. using Utilities;
  18. namespace SMBLibrary.Client
  19. {
  20. public class SMB1Client
  21. {
  22. public const int NetBiosOverTCPPort = 139;
  23. public const int DirectTCPPort = 445;
  24. public const string NTLanManagerDialect = "NT LM 0.12";
  25. public const int MaxBufferSize = 65535; // Valid range: 512 - 65535
  26. public const int MaxMpxCount = 1;
  27. private SMBTransportType m_transport;
  28. private bool m_isConnected;
  29. private bool m_isLoggedIn;
  30. private Socket m_clientSocket;
  31. private IAsyncResult m_currentAsyncResult;
  32. private bool m_forceExtendedSecurity;
  33. private object m_incomingQueueLock = new object();
  34. private List<SMB1Message> m_incomingQueue = new List<SMB1Message>();
  35. private EventWaitHandle m_incomingQueueEventHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
  36. private ushort m_userID;
  37. private byte[] m_serverChallenge;
  38. private byte[] m_securityBlob;
  39. private byte[] m_sessionKey;
  40. public SMB1Client()
  41. {
  42. }
  43. public bool Connect(IPAddress serverAddress, SMBTransportType transport)
  44. {
  45. return Connect(serverAddress, transport, true);
  46. }
  47. public bool Connect(IPAddress serverAddress, SMBTransportType transport, bool forceExtendedSecurity)
  48. {
  49. m_transport = transport;
  50. if (!m_isConnected)
  51. {
  52. m_forceExtendedSecurity = forceExtendedSecurity;
  53. m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  54. int port;
  55. if (transport == SMBTransportType.DirectTCPTransport)
  56. {
  57. port = DirectTCPPort;
  58. }
  59. else
  60. {
  61. port = NetBiosOverTCPPort;
  62. }
  63. try
  64. {
  65. m_clientSocket.Connect(serverAddress, port);
  66. }
  67. catch (SocketException)
  68. {
  69. return false;
  70. }
  71. ConnectionState state = new ConnectionState();
  72. NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer;
  73. m_currentAsyncResult = m_clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnServerSocketReceive), state);
  74. bool supportsCIFS = NegotiateNTLanManagerDialect(m_forceExtendedSecurity);
  75. if (!supportsCIFS)
  76. {
  77. m_clientSocket.Close();
  78. }
  79. else
  80. {
  81. m_isConnected = true;
  82. }
  83. }
  84. return m_isConnected;
  85. }
  86. public void Disconnect()
  87. {
  88. if (m_isConnected)
  89. {
  90. m_clientSocket.Disconnect(false);
  91. m_isConnected = false;
  92. }
  93. }
  94. private bool NegotiateNTLanManagerDialect(bool forceExtendedSecurity)
  95. {
  96. if (m_transport == SMBTransportType.NetBiosOverTCP)
  97. {
  98. SessionRequestPacket sessionRequest = new SessionRequestPacket();
  99. sessionRequest.CalledName = NetBiosUtils.GetMSNetBiosName("*SMBSERVER", NetBiosSuffix.FileServiceService); ;
  100. sessionRequest.CallingName = NetBiosUtils.GetMSNetBiosName(Environment.MachineName, NetBiosSuffix.WorkstationService);
  101. TrySendPacket(m_clientSocket, sessionRequest);
  102. }
  103. NegotiateRequest request = new NegotiateRequest();
  104. request.Dialects.Add(NTLanManagerDialect);
  105. TrySendMessage(request);
  106. SMB1Message reply = WaitForMessage(CommandName.SMB_COM_NEGOTIATE);
  107. if (reply == null)
  108. {
  109. return false;
  110. }
  111. if (reply.Commands[0] is NegotiateResponse && !forceExtendedSecurity)
  112. {
  113. NegotiateResponse response = (NegotiateResponse)reply.Commands[0];
  114. m_serverChallenge = response.Challenge;
  115. return true;
  116. }
  117. else if (reply.Commands[0] is NegotiateResponseExtended)
  118. {
  119. NegotiateResponseExtended response = (NegotiateResponseExtended)reply.Commands[0];
  120. m_securityBlob = response.SecurityBlob;
  121. return true;
  122. }
  123. else
  124. {
  125. return false;
  126. }
  127. }
  128. public NTStatus Login(string domainName, string userName, string password)
  129. {
  130. return Login(domainName, userName, password, AuthenticationMethod.NTLMv2);
  131. }
  132. public NTStatus Login(string domainName, string userName, string password, AuthenticationMethod authenticationMethod)
  133. {
  134. if (!m_isConnected)
  135. {
  136. throw new InvalidOperationException("A connection must be successfully established before attempting login");
  137. }
  138. if (m_serverChallenge != null)
  139. {
  140. SessionSetupAndXRequest request = new SessionSetupAndXRequest();
  141. request.MaxBufferSize = MaxBufferSize;
  142. request.MaxMpxCount = MaxMpxCount;
  143. request.Capabilities = Capabilities.Unicode | Capabilities.NTStatusCode;
  144. request.AccountName = userName;
  145. request.PrimaryDomain = domainName;
  146. byte[] clientChallenge = new byte[8];
  147. new Random().NextBytes(clientChallenge);
  148. if (authenticationMethod == AuthenticationMethod.NTLMv1)
  149. {
  150. request.OEMPassword = NTLMCryptography.ComputeLMv1Response(m_serverChallenge, password);
  151. request.UnicodePassword = NTLMCryptography.ComputeNTLMv1Response(m_serverChallenge, password);
  152. }
  153. else if (authenticationMethod == AuthenticationMethod.NTLMv1ExtendedSessionSecurity)
  154. {
  155. // [MS-CIFS] CIFS does not support Extended Session Security because there is no mechanism in CIFS to negotiate Extended Session Security
  156. throw new ArgumentException("SMB Extended Security must be negotiated in order for NTLMv1 Extended Session Security to be used");
  157. }
  158. else // NTLMv2
  159. {
  160. // Note: NTLMv2 over non-extended security session setup is not supported under Windows Vista and later which will return STATUS_INVALID_PARAMETER.
  161. // https://msdn.microsoft.com/en-us/library/ee441701.aspx
  162. // https://msdn.microsoft.com/en-us/library/cc236700.aspx
  163. request.OEMPassword = NTLMCryptography.ComputeLMv2Response(m_serverChallenge, clientChallenge, password, userName, domainName);
  164. NTLMv2ClientChallenge clientChallengeStructure = new NTLMv2ClientChallenge(DateTime.UtcNow, clientChallenge, AVPairUtils.GetAVPairSequence(domainName, Environment.MachineName));
  165. byte[] temp = clientChallengeStructure.GetBytesPadded();
  166. byte[] proofStr = NTLMCryptography.ComputeNTLMv2Proof(m_serverChallenge, temp, password, userName, domainName);
  167. request.UnicodePassword = ByteUtils.Concatenate(proofStr, temp);
  168. }
  169. TrySendMessage(request);
  170. SMB1Message reply = WaitForMessage(CommandName.SMB_COM_SESSION_SETUP_ANDX);
  171. if (reply != null)
  172. {
  173. m_isLoggedIn = (reply.Header.Status == NTStatus.STATUS_SUCCESS);
  174. return reply.Header.Status;
  175. }
  176. return NTStatus.STATUS_INVALID_SMB;
  177. }
  178. else // m_securityBlob != null
  179. {
  180. SessionSetupAndXRequestExtended request = new SessionSetupAndXRequestExtended();
  181. request.MaxBufferSize = MaxBufferSize;
  182. request.MaxMpxCount = MaxMpxCount;
  183. request.Capabilities = Capabilities.Unicode | Capabilities.NTStatusCode | Capabilities.LargeRead | Capabilities.LargeWrite;
  184. request.SecurityBlob = NTLMAuthenticationHelper.GetNegotiateMessage(m_securityBlob, domainName, authenticationMethod);
  185. TrySendMessage(request);
  186. SMB1Message reply = WaitForMessage(CommandName.SMB_COM_SESSION_SETUP_ANDX);
  187. if (reply != null)
  188. {
  189. if (reply.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED && reply.Commands[0] is SessionSetupAndXResponseExtended)
  190. {
  191. SessionSetupAndXResponseExtended response = (SessionSetupAndXResponseExtended)reply.Commands[0];
  192. m_userID = reply.Header.UID;
  193. request = new SessionSetupAndXRequestExtended();
  194. request.MaxBufferSize = MaxBufferSize;
  195. request.MaxMpxCount = MaxMpxCount;
  196. request.Capabilities = Capabilities.Unicode | Capabilities.NTStatusCode | Capabilities.LargeRead | Capabilities.LargeWrite | Capabilities.ExtendedSecurity;
  197. request.SecurityBlob = NTLMAuthenticationHelper.GetAuthenticateMessage(response.SecurityBlob, domainName, userName, password, authenticationMethod, out m_sessionKey);
  198. TrySendMessage(request);
  199. reply = WaitForMessage(CommandName.SMB_COM_SESSION_SETUP_ANDX);
  200. if (reply != null)
  201. {
  202. m_isLoggedIn = (reply.Header.Status == NTStatus.STATUS_SUCCESS);
  203. return reply.Header.Status;
  204. }
  205. }
  206. else
  207. {
  208. return reply.Header.Status;
  209. }
  210. }
  211. return NTStatus.STATUS_INVALID_SMB;
  212. }
  213. }
  214. public NTStatus Logoff()
  215. {
  216. LogoffAndXRequest request = new LogoffAndXRequest();
  217. TrySendMessage(request);
  218. SMB1Message reply = WaitForMessage(CommandName.SMB_COM_LOGOFF_ANDX);
  219. if (reply != null)
  220. {
  221. m_isLoggedIn = (reply.Header.Status != NTStatus.STATUS_SUCCESS);
  222. return reply.Header.Status;
  223. }
  224. return NTStatus.STATUS_INVALID_SMB;
  225. }
  226. public List<string> ListShares(out NTStatus status)
  227. {
  228. if (!m_isConnected || !m_isLoggedIn)
  229. {
  230. throw new InvalidOperationException("A login session must be successfully established before retrieving share list");
  231. }
  232. SMB1FileStore namedPipeShare = TreeConnect("IPC$", ServiceName.NamedPipe, out status);
  233. if (namedPipeShare == null)
  234. {
  235. return null;
  236. }
  237. return ServerServiceHelper.ListShares(namedPipeShare, ShareType.DiskDrive, out status);
  238. }
  239. public SMB1FileStore TreeConnect(string shareName, ServiceName serviceName, out NTStatus status)
  240. {
  241. if (!m_isConnected || !m_isLoggedIn)
  242. {
  243. throw new InvalidOperationException("A login session must be successfully established before connecting to a share");
  244. }
  245. TreeConnectAndXRequest request = new TreeConnectAndXRequest();
  246. request.Path = shareName;
  247. request.Service = serviceName;
  248. TrySendMessage(request);
  249. SMB1Message reply = WaitForMessage(CommandName.SMB_COM_TREE_CONNECT_ANDX);
  250. if (reply != null)
  251. {
  252. status = reply.Header.Status;
  253. if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is TreeConnectAndXResponse)
  254. {
  255. TreeConnectAndXResponse response = (TreeConnectAndXResponse)reply.Commands[0];
  256. return new SMB1FileStore(this, reply.Header.TID);
  257. }
  258. }
  259. else
  260. {
  261. status = NTStatus.STATUS_INVALID_SMB;
  262. }
  263. return null;
  264. }
  265. private void OnServerSocketReceive(IAsyncResult ar)
  266. {
  267. if (ar != m_currentAsyncResult)
  268. {
  269. // We ignore calls for old sockets which we no longer use
  270. // See: http://rajputyh.blogspot.co.il/2010/04/solve-exception-message-iasyncresult.html
  271. return;
  272. }
  273. ConnectionState state = (ConnectionState)ar.AsyncState;
  274. if (!m_clientSocket.Connected)
  275. {
  276. return;
  277. }
  278. int numberOfBytesReceived = 0;
  279. try
  280. {
  281. numberOfBytesReceived = m_clientSocket.EndReceive(ar);
  282. }
  283. catch (ObjectDisposedException)
  284. {
  285. Log("[ReceiveCallback] EndReceive ObjectDisposedException");
  286. return;
  287. }
  288. catch (SocketException ex)
  289. {
  290. Log("[ReceiveCallback] EndReceive SocketException: " + ex.Message);
  291. return;
  292. }
  293. if (numberOfBytesReceived == 0)
  294. {
  295. m_isConnected = false;
  296. }
  297. else
  298. {
  299. NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer;
  300. buffer.SetNumberOfBytesReceived(numberOfBytesReceived);
  301. ProcessConnectionBuffer(state);
  302. try
  303. {
  304. m_currentAsyncResult = m_clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnServerSocketReceive), state);
  305. }
  306. catch (ObjectDisposedException)
  307. {
  308. m_isConnected = false;
  309. Log("[ReceiveCallback] BeginReceive ObjectDisposedException");
  310. }
  311. catch (SocketException ex)
  312. {
  313. m_isConnected = false;
  314. Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message);
  315. }
  316. }
  317. }
  318. private void ProcessConnectionBuffer(ConnectionState state)
  319. {
  320. NBTConnectionReceiveBuffer receiveBuffer = state.ReceiveBuffer;
  321. while (receiveBuffer.HasCompletePacket())
  322. {
  323. SessionPacket packet = null;
  324. try
  325. {
  326. packet = receiveBuffer.DequeuePacket();
  327. }
  328. catch (Exception)
  329. {
  330. m_clientSocket.Close();
  331. break;
  332. }
  333. if (packet != null)
  334. {
  335. ProcessPacket(packet, state);
  336. }
  337. }
  338. }
  339. private void ProcessPacket(SessionPacket packet, ConnectionState state)
  340. {
  341. if (packet is SessionKeepAlivePacket && m_transport == SMBTransportType.NetBiosOverTCP)
  342. {
  343. // [RFC 1001] NetBIOS session keep alives do not require a response from the NetBIOS peer
  344. }
  345. else if (packet is PositiveSessionResponsePacket && m_transport == SMBTransportType.NetBiosOverTCP)
  346. {
  347. }
  348. else if (packet is NegativeSessionResponsePacket && m_transport == SMBTransportType.NetBiosOverTCP)
  349. {
  350. m_clientSocket.Close();
  351. m_isConnected = false;
  352. }
  353. else if (packet is SessionMessagePacket)
  354. {
  355. SMB1Message message;
  356. try
  357. {
  358. message = SMB1Message.GetSMB1Message(packet.Trailer);
  359. }
  360. catch (Exception ex)
  361. {
  362. Log("Invalid SMB1 message: " + ex.Message);
  363. m_clientSocket.Close();
  364. m_isConnected = false;
  365. return;
  366. }
  367. lock (m_incomingQueueLock)
  368. {
  369. m_incomingQueue.Add(message);
  370. m_incomingQueueEventHandle.Set();
  371. }
  372. }
  373. }
  374. internal SMB1Message WaitForMessage(CommandName commandName)
  375. {
  376. const int TimeOut = 5000;
  377. Stopwatch stopwatch = new Stopwatch();
  378. stopwatch.Start();
  379. while (stopwatch.ElapsedMilliseconds < TimeOut)
  380. {
  381. lock (m_incomingQueueLock)
  382. {
  383. for (int index = 0; index < m_incomingQueue.Count; index++)
  384. {
  385. SMB1Message message = m_incomingQueue[index];
  386. if (message.Commands[0].CommandName == commandName)
  387. {
  388. m_incomingQueue.RemoveAt(index);
  389. return message;
  390. }
  391. }
  392. }
  393. m_incomingQueueEventHandle.WaitOne(100);
  394. }
  395. return null;
  396. }
  397. private void Log(string message)
  398. {
  399. System.Diagnostics.Debug.Print(message);
  400. }
  401. internal void TrySendMessage(SMB1Command request)
  402. {
  403. TrySendMessage(request, 0);
  404. }
  405. internal void TrySendMessage(SMB1Command request, ushort treeID)
  406. {
  407. SMB1Message message = new SMB1Message();
  408. message.Header.UnicodeFlag = true;
  409. message.Header.ExtendedSecurityFlag = m_forceExtendedSecurity;
  410. message.Header.Flags2 |= HeaderFlags2.LongNamesAllowed | HeaderFlags2.LongNameUsed | HeaderFlags2.NTStatusCode;
  411. message.Header.UID = m_userID;
  412. message.Header.TID = treeID;
  413. message.Commands.Add(request);
  414. TrySendMessage(m_clientSocket, message);
  415. }
  416. public static void TrySendMessage(Socket socket, SMB1Message message)
  417. {
  418. SessionMessagePacket packet = new SessionMessagePacket();
  419. packet.Trailer = message.GetBytes();
  420. TrySendPacket(socket, packet);
  421. }
  422. public static void TrySendPacket(Socket socket, SessionPacket response)
  423. {
  424. try
  425. {
  426. socket.Send(response.GetBytes());
  427. }
  428. catch (SocketException)
  429. {
  430. }
  431. catch (ObjectDisposedException)
  432. {
  433. }
  434. }
  435. }
  436. }