본문 바로가기
VB.net

Windows 암호정책 확인하는 Class (VB 버전)

by 호야호잇 2020. 12. 1.
Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Linq
Imports System.Runtime.InteropServices
Imports System.Security.Principal
Imports System.Text
Imports System.Threading.Tasks

Public Class Sam_Class 

    Partial Public Class SamServer : Implements IDisposable

        Private _handle As IntPtr

        Public Sub New(ByVal name As String, ByVal access As SERVER_ACCESS_MASK)
            name = name
            Check(SamConnect(New UNICODE_STRING(name), _handle, access, IntPtr.Zero))
        End Sub

        Public ReadOnly Property Name As String

        Public Sub Dispose() Implements IDisposable.Dispose
            If _handle <> IntPtr.Zero Then
                SamCloseHandle(_handle)
                _handle = IntPtr.Zero
            End If
        End Sub

        Public Sub SetDomainPasswordInformation(ByVal domainSid As SecurityIdentifier, ByVal passwordInformation As DOMAIN_PASSWORD_INFORMATION)
            If domainSid Is Nothing Then Throw New ArgumentNullException(NameOf(domainSid))
            Dim sid = New Byte(domainSid.BinaryLength - 1) {}
            domainSid.GetBinaryForm(sid, 0)
            Dim domain As IntPtr = Nothing
            Check(SamOpenDomain(_handle, DOMAIN_ACCESS_MASK.DOMAIN_WRITE_PASSWORD_PARAMS, sid, domain))
            Dim info As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(passwordInformation))
            Marshal.StructureToPtr(passwordInformation, info, False)

            Try
                Check(SamSetInformationDomain(domain, DOMAIN_INFORMATION_CLASS.DomainPasswordInformation, info))
            Finally
                Marshal.FreeHGlobal(info)
                SamCloseHandle(domain)
            End Try
        End Sub

        Public Function GetDomainPasswordInformation(ByVal domainSid As SecurityIdentifier) As DOMAIN_PASSWORD_INFORMATION
            If domainSid Is Nothing Then Throw New ArgumentNullException(NameOf(domainSid))
            Dim sid = New Byte(domainSid.BinaryLength - 1) {}
            domainSid.GetBinaryForm(sid, 0)
            Dim domain As IntPtr = Nothing
            Check(SamOpenDomain(_handle, DOMAIN_ACCESS_MASK.DOMAIN_READ_PASSWORD_PARAMETERS, sid, domain))
            Dim info = IntPtr.Zero

            Try
                Check(SamQueryInformationDomain(domain, DOMAIN_INFORMATION_CLASS.DomainPasswordInformation, info))
                Return CType(Marshal.PtrToStructure(info, GetType(DOMAIN_PASSWORD_INFORMATION)), DOMAIN_PASSWORD_INFORMATION)
            Finally
                SamFreeMemory(info)
                SamCloseHandle(domain)
            End Try
        End Function

        Public Function GetDomainSid(ByVal domain As String) As SecurityIdentifier
            If domain Is Nothing Then Throw New ArgumentNullException(NameOf(domain))
            Dim sid As IntPtr = Nothing
            Check(SamLookupDomainInSamServer(_handle, New UNICODE_STRING(domain), sid))
            Return New SecurityIdentifier(sid)
        End Function

        Public Iterator Function EnumerateDomains() As IEnumerable(Of String)
            Dim cookie As Integer = 0
            Dim info As IntPtr = Nothing, count As Integer = Nothing

            While True
                Dim status = SamEnumerateDomainsInSamServer(_handle, cookie, info, 1, count)
                If status <> NTSTATUS.STATUS_SUCCESS AndAlso status <> NTSTATUS.STATUS_MORE_ENTRIES Then Check(status)
                If count = 0 Then Exit While
                Dim us = CType(Marshal.PtrToStructure(info + IntPtr.Size, GetType(UNICODE_STRING)), UNICODE_STRING)
                SamFreeMemory(info)
                Yield us.ToString()
                us.Buffer = IntPtr.Zero
            End While
        End Function

        Private Enum DOMAIN_INFORMATION_CLASS
            DomainPasswordInformation = 1
        End Enum

        <Flags>
        Public Enum PASSWORD_PROPERTIES
            DOMAIN_PASSWORD_COMPLEX = &H1
            DOMAIN_PASSWORD_NO_ANON_CHANGE = &H2
            DOMAIN_PASSWORD_NO_CLEAR_CHANGE = &H4
            DOMAIN_LOCKOUT_ADMINS = &H8
            DOMAIN_PASSWORD_STORE_CLEARTEXT = &H10
            DOMAIN_REFUSE_PASSWORD_CHANGE = &H20
        End Enum

        <Flags>
        Private Enum DOMAIN_ACCESS_MASK
            DOMAIN_READ_PASSWORD_PARAMETERS = &H1
            DOMAIN_WRITE_PASSWORD_PARAMS = &H2
            DOMAIN_READ_OTHER_PARAMETERS = &H4
            DOMAIN_WRITE_OTHER_PARAMETERS = &H8
            DOMAIN_CREATE_USER = &H10
            DOMAIN_CREATE_GROUP = &H20
            DOMAIN_CREATE_ALIAS = &H40
            DOMAIN_GET_ALIAS_MEMBERSHIP = &H80
            DOMAIN_LIST_ACCOUNTS = &H100
            DOMAIN_LOOKUP = &H200
            DOMAIN_ADMINISTER_SERVER = &H400
            DOMAIN_ALL_ACCESS = &HF07FF
            DOMAIN_READ = &H20084
            DOMAIN_WRITE = &H2047A
            DOMAIN_EXECUTE = &H20301
        End Enum

        <Flags>
        Public Enum SERVER_ACCESS_MASK
            SAM_SERVER_CONNECT = &H1
            SAM_SERVER_SHUTDOWN = &H2
            SAM_SERVER_INITIALIZE = &H4
            SAM_SERVER_CREATE_DOMAIN = &H8
            SAM_SERVER_ENUMERATE_DOMAINS = &H10
            SAM_SERVER_LOOKUP_DOMAIN = &H20
            SAM_SERVER_ALL_ACCESS = &HF003F
            SAM_SERVER_READ = &H20010
            SAM_SERVER_WRITE = &H2000E
            SAM_SERVER_EXECUTE = &H20021
        End Enum

        <StructLayout(LayoutKind.Sequential)>
        Public Structure DOMAIN_PASSWORD_INFORMATION
            Public MinPasswordLength As Short
            Public PasswordHistoryLength As Short
            Public PasswordProperties As PASSWORD_PROPERTIES
            Private _maxPasswordAge As Long
            Private _minPasswordAge As Long

            Public Property MaxPasswordAge As TimeSpan
                Get
                    Return -New TimeSpan(_maxPasswordAge)
                End Get
                Set(ByVal value As TimeSpan)
                    _maxPasswordAge = value.Ticks
                End Set
            End Property

            Public Property MinPasswordAge As TimeSpan
                Get
                    Return -New TimeSpan(_minPasswordAge)
                End Get
                Set(ByVal value As TimeSpan)
                    _minPasswordAge = value.Ticks
                End Set
            End Property
        End Structure

        <StructLayout(LayoutKind.Sequential)>
        Private Class UNICODE_STRING
            'Inherits IDisposable

            Public Length As UShort
            Public MaximumLength As UShort
            Public Buffer As IntPtr

            Public Sub New()
                Me.New(Nothing)
            End Sub

            Public Sub New(ByVal s As String)
                If s IsNot Nothing Then
                    Length = CUShort((s.Length * 2))
                    MaximumLength = CUShort((Length + 2))
                    Buffer = Marshal.StringToHGlobalUni(s)
                End If
            End Sub

            Public Overrides Function ToString() As String
                Return If(Buffer <> IntPtr.Zero, Marshal.PtrToStringUni(Buffer), Nothing)
            End Function

            Protected Overridable Sub Dispose(ByVal disposing As Boolean)
                If Buffer <> IntPtr.Zero Then
                    Marshal.FreeHGlobal(Buffer)
                    Buffer = IntPtr.Zero
                End If
            End Sub

            Protected Overrides Sub Finalize()
                Dispose(False)
            End Sub

            Public Sub Dispose()
                Dispose(True)
                GC.SuppressFinalize(Me)
            End Sub
        End Class

        Private Shared Sub Check(ByVal err As NTSTATUS)
            If err = NTSTATUS.STATUS_SUCCESS Then Return
            Throw New Win32Exception("Error " & err & " (0x" & (CInt(err)).ToString("X8") & ")")
        End Sub

        Private Enum NTSTATUS
            STATUS_SUCCESS = &H0
            STATUS_MORE_ENTRIES = &H105
            STATUS_INVALID_HANDLE = &HC0000008
            STATUS_INVALID_PARAMETER = &HC000000D
            STATUS_ACCESS_DENIED = &HC0000022
            STATUS_OBJECT_TYPE_MISMATCH = &HC0000024
            STATUS_NO_SUCH_DOMAIN = &HC00000DF
        End Enum


        <DllImport("samlib.dll", CharSet:=CharSet.Unicode)>
        Private Shared Function SamConnect(ByVal ServerName As UNICODE_STRING, <Out> ByRef ServerHandle As IntPtr, ByVal DesiredAccess As SERVER_ACCESS_MASK, ByVal ObjectAttributes As IntPtr) As NTSTATUS

        End Function
        <DllImport("samlib.dll", CharSet:=CharSet.Unicode)>
        Private Shared Function SamCloseHandle(ByVal ServerHandle As IntPtr) As NTSTATUS

        End Function
        <DllImport("samlib.dll", CharSet:=CharSet.Unicode)>
        Private Shared Function SamFreeMemory(ByVal Handle As IntPtr) As NTSTATUS

        End Function
        <DllImport("samlib.dll", CharSet:=CharSet.Unicode)>
        Private Shared Function SamOpenDomain(ByVal ServerHandle As IntPtr, ByVal DesiredAccess As DOMAIN_ACCESS_MASK, ByVal DomainId As Byte(), <Out> ByRef DomainHandle As IntPtr) As NTSTATUS

        End Function
        <DllImport("samlib.dll", CharSet:=CharSet.Unicode)>
        Private Shared Function SamLookupDomainInSamServer(ByVal ServerHandle As IntPtr, ByVal name As UNICODE_STRING, <Out> ByRef DomainId As IntPtr) As NTSTATUS

        End Function
        <DllImport("samlib.dll", CharSet:=CharSet.Unicode)>
        Private Shared Function SamQueryInformationDomain(ByVal DomainHandle As IntPtr, ByVal DomainInformationClass As DOMAIN_INFORMATION_CLASS, <Out> ByRef Buffer As IntPtr) As NTSTATUS

        End Function
        <DllImport("samlib.dll", CharSet:=CharSet.Unicode)>
        Private Shared Function SamSetInformationDomain(ByVal DomainHandle As IntPtr, ByVal DomainInformationClass As DOMAIN_INFORMATION_CLASS, ByVal Buffer As IntPtr) As NTSTATUS

        End Function
        <DllImport("samlib.dll", CharSet:=CharSet.Unicode)>
        Private Shared Function SamEnumerateDomainsInSamServer(ByVal ServerHandle As IntPtr, ByRef EnumerationContext As Integer, <Out> ByRef EnumerationBuffer As IntPtr, ByVal PreferedMaximumLength As Integer, <Out> ByRef CountReturned As Integer) As NTSTATUS

        End Function
    End Class

End Class

 

메인 Form

  Public Sub chk_SAM()
        Dim chk_LoginAccount As String
        chk_LoginAccount = System.Environment.UserName
        Using server As SamServer = New SamServer(Nothing,
                                                  SamServer.SERVER_ACCESS_MASK.SAM_SERVER_ENUMERATE_DOMAINS _
                                                  Or SamServer.SERVER_ACCESS_MASK.SAM_SERVER_LOOKUP_DOMAIN)

            For Each domain As String In server.EnumerateDomains

                If chk_LoginAccount <> Nothing Then
                    If chk_LoginAccount.ToUpper() = domain Then

                        Dim sid = server.GetDomainSid(domain)
                        Dim pi = server.GetDomainPasswordInformation(sid)

                        Dim msg As String
                        msg = $"계정 : {chk_LoginAccount}{vbNewLine}"
                        msg += $"패스워드 속성 : {pi.PasswordProperties}{vbNewLine}"
                        msg += $"최근 암호 기억 : {pi.PasswordHistoryLength}{vbNewLine}"
                        msg += $"최대 암호 사용기간  : {pi.MaxPasswordAge}{vbNewLine}"
                        msg += $"최소 암호 길이 : {pi.MinPasswordLength}{vbNewLine}"
                        msg += $"최소 암호 사용기간 : {pi.MinPasswordAge}{vbNewLine}"

                        MsgBox(msg)
                    End If
                End If

            Next

        End Using



    End Sub