본문 바로가기
C#.Net

Windows 암호정책 확인하는 Class

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

namespace SAM_API_Test
{

    public  class SamServer : IDisposable
    {
        private IntPtr _handle;

        public SamServer(string name, SERVER_ACCESS_MASK access)
        {
            Name = name;
            Check(SamConnect(new UNICODE_STRING(name), out _handle, access, IntPtr.Zero));
        }

        public string Name { get; }

        public void Dispose()
        {
            if (_handle != IntPtr.Zero)
            {
                SamCloseHandle(_handle);
                _handle = IntPtr.Zero;
            }
        }

        public void SetDomainPasswordInformation(SecurityIdentifier domainSid, DOMAIN_PASSWORD_INFORMATION passwordInformation)
        {
            if (domainSid == null)
                throw new ArgumentNullException(nameof(domainSid));

            var sid = new byte[domainSid.BinaryLength];
            domainSid.GetBinaryForm(sid, 0);

            Check(SamOpenDomain(_handle, DOMAIN_ACCESS_MASK.DOMAIN_WRITE_PASSWORD_PARAMS, sid, out IntPtr domain));
            IntPtr info = 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);
            }
        }

        public DOMAIN_PASSWORD_INFORMATION GetDomainPasswordInformation(SecurityIdentifier domainSid)
        {
            if (domainSid == null)
                throw new ArgumentNullException(nameof(domainSid));

            var sid = new byte[domainSid.BinaryLength];
            domainSid.GetBinaryForm(sid, 0);

            Check(SamOpenDomain(_handle, DOMAIN_ACCESS_MASK.DOMAIN_READ_PASSWORD_PARAMETERS, sid, out IntPtr domain));
            var info = IntPtr.Zero;
            try
            {
                Check(SamQueryInformationDomain(domain, DOMAIN_INFORMATION_CLASS.DomainPasswordInformation, out info));
                return (DOMAIN_PASSWORD_INFORMATION)Marshal.PtrToStructure(info, typeof(DOMAIN_PASSWORD_INFORMATION));
            }
            finally
            {
                SamFreeMemory(info);
                SamCloseHandle(domain);
            }
        }

        public SecurityIdentifier GetDomainSid(string domain)
        {
            if (domain == null)
                throw new ArgumentNullException(nameof(domain));

            Check(SamLookupDomainInSamServer(_handle, new UNICODE_STRING(domain), out IntPtr sid));
            return new SecurityIdentifier(sid);
        }

        public IEnumerable<string> EnumerateDomains()
        {
            int cookie = 0;
            while (true)
            {
                var status = SamEnumerateDomainsInSamServer(_handle, ref cookie, out IntPtr info, 1, out int count);
                if (status != NTSTATUS.STATUS_SUCCESS && status != NTSTATUS.STATUS_MORE_ENTRIES)
                    Check(status);

                if (count == 0)
                    break;

                var us = (UNICODE_STRING)Marshal.PtrToStructure(info + IntPtr.Size, typeof(UNICODE_STRING));
                SamFreeMemory(info);
                yield return us.ToString();
                us.Buffer = IntPtr.Zero; // we don't own this one
            }
        }

        private enum DOMAIN_INFORMATION_CLASS
        {
            DomainPasswordInformation = 1,
        }

        [Flags]
        public enum PASSWORD_PROPERTIES
        {
            DOMAIN_PASSWORD_COMPLEX = 0x00000001,
            DOMAIN_PASSWORD_NO_ANON_CHANGE = 0x00000002,
            DOMAIN_PASSWORD_NO_CLEAR_CHANGE = 0x00000004,
            DOMAIN_LOCKOUT_ADMINS = 0x00000008,
            DOMAIN_PASSWORD_STORE_CLEARTEXT = 0x00000010,
            DOMAIN_REFUSE_PASSWORD_CHANGE = 0x00000020,
        }

        [Flags]
        private enum DOMAIN_ACCESS_MASK
        {
            DOMAIN_READ_PASSWORD_PARAMETERS = 0x00000001,
            DOMAIN_WRITE_PASSWORD_PARAMS = 0x00000002,
            DOMAIN_READ_OTHER_PARAMETERS = 0x00000004,
            DOMAIN_WRITE_OTHER_PARAMETERS = 0x00000008,
            DOMAIN_CREATE_USER = 0x00000010,
            DOMAIN_CREATE_GROUP = 0x00000020,
            DOMAIN_CREATE_ALIAS = 0x00000040,
            DOMAIN_GET_ALIAS_MEMBERSHIP = 0x00000080,
            DOMAIN_LIST_ACCOUNTS = 0x00000100,
            DOMAIN_LOOKUP = 0x00000200,
            DOMAIN_ADMINISTER_SERVER = 0x00000400,
            DOMAIN_ALL_ACCESS = 0x000F07FF,
            DOMAIN_READ = 0x00020084,
            DOMAIN_WRITE = 0x0002047A,
            DOMAIN_EXECUTE = 0x00020301
        }

        [Flags]
        public enum SERVER_ACCESS_MASK
        {
            SAM_SERVER_CONNECT = 0x00000001,
            SAM_SERVER_SHUTDOWN = 0x00000002,
            SAM_SERVER_INITIALIZE = 0x00000004,
            SAM_SERVER_CREATE_DOMAIN = 0x00000008,
            SAM_SERVER_ENUMERATE_DOMAINS = 0x00000010,
            SAM_SERVER_LOOKUP_DOMAIN = 0x00000020,
            SAM_SERVER_ALL_ACCESS = 0x000F003F,
            SAM_SERVER_READ = 0x00020010,
            SAM_SERVER_WRITE = 0x0002000E,
            SAM_SERVER_EXECUTE = 0x00020021
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct DOMAIN_PASSWORD_INFORMATION
        {
            public short MinPasswordLength;
            public short PasswordHistoryLength;
            public PASSWORD_PROPERTIES PasswordProperties;
            private long _maxPasswordAge;
            private long _minPasswordAge;

            public TimeSpan MaxPasswordAge
            {
                get
                {
                    return -new TimeSpan(_maxPasswordAge);
                }
                set
                {
                    _maxPasswordAge = value.Ticks;
                }
            }

            public TimeSpan MinPasswordAge
            {
                get
                {
                    return -new TimeSpan(_minPasswordAge);
                }
                set
                {
                    _minPasswordAge = value.Ticks;
                }
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        private class UNICODE_STRING : IDisposable
        {
            public ushort Length;
            public ushort MaximumLength;
            public IntPtr Buffer;

            public UNICODE_STRING()
                : this(null)
            {
            }

            public UNICODE_STRING(string s)
            {
                if (s != null)
                {
                    Length = (ushort)(s.Length * 2);
                    MaximumLength = (ushort)(Length + 2);
                    Buffer = Marshal.StringToHGlobalUni(s);
                }
            }

            public override string ToString() => Buffer != IntPtr.Zero ? Marshal.PtrToStringUni(Buffer) : null;

            protected virtual void Dispose(bool disposing)
            {
                if (Buffer != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(Buffer);
                    Buffer = IntPtr.Zero;
                }
            }

            ~UNICODE_STRING() => Dispose(false);

            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
        }

        private static void Check(NTSTATUS err)
        {
            if (err == NTSTATUS.STATUS_SUCCESS)
                return;

            throw new Win32Exception("Error " + err + " (0x" + ((int)err).ToString("X8") + ")");
        }

        private enum NTSTATUS
        {
            STATUS_SUCCESS = 0x0,
            STATUS_MORE_ENTRIES = 0x105,
            STATUS_INVALID_HANDLE = unchecked((int)0xC0000008),
            STATUS_INVALID_PARAMETER = unchecked((int)0xC000000D),
            STATUS_ACCESS_DENIED = unchecked((int)0xC0000022),
            STATUS_OBJECT_TYPE_MISMATCH = unchecked((int)0xC0000024),
            STATUS_NO_SUCH_DOMAIN = unchecked((int)0xC00000DF),
        }

        [DllImport("samlib.dll", CharSet = CharSet.Unicode)]
        private static extern NTSTATUS SamConnect(UNICODE_STRING ServerName, out IntPtr ServerHandle, SERVER_ACCESS_MASK DesiredAccess, IntPtr ObjectAttributes);

        [DllImport("samlib.dll", CharSet = CharSet.Unicode)]
        private static extern NTSTATUS SamCloseHandle(IntPtr ServerHandle);

        [DllImport("samlib.dll", CharSet = CharSet.Unicode)]
        private static extern NTSTATUS SamFreeMemory(IntPtr Handle);

        [DllImport("samlib.dll", CharSet = CharSet.Unicode)]
        private static extern NTSTATUS SamOpenDomain(IntPtr ServerHandle, DOMAIN_ACCESS_MASK DesiredAccess, byte[] DomainId, out IntPtr DomainHandle);

        [DllImport("samlib.dll", CharSet = CharSet.Unicode)]
        private static extern NTSTATUS SamLookupDomainInSamServer(IntPtr ServerHandle, UNICODE_STRING name, out IntPtr DomainId);

        [DllImport("samlib.dll", CharSet = CharSet.Unicode)]
        private static extern NTSTATUS SamQueryInformationDomain(IntPtr DomainHandle, DOMAIN_INFORMATION_CLASS DomainInformationClass, out IntPtr Buffer);

        [DllImport("samlib.dll", CharSet = CharSet.Unicode)]
        private static extern NTSTATUS SamSetInformationDomain(IntPtr DomainHandle, DOMAIN_INFORMATION_CLASS DomainInformationClass, IntPtr Buffer);

        [DllImport("samlib.dll", CharSet = CharSet.Unicode)]
        private static extern NTSTATUS SamEnumerateDomainsInSamServer(IntPtr ServerHandle, ref int EnumerationContext, out IntPtr EnumerationBuffer, int PreferedMaximumLength, out int CountReturned);
    }
}

 메인 Form에서 불러들이는 방법

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SAM_API_Test
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();


        }

        string match_account = "";
        string match_account_info = "";

        private void Form1_Load(object sender, EventArgs e)
        {
            match_account = Environment.UserName;
            Console.WriteLine("로그인 계정 : {0}", match_account);

            // Class1 class1 = new Class1();
            // class1.New();
            dmp_Sam();

             setPasswordPolicy();

            dmp_Sam();

        }

        private void dmp_Sam()
        {
            using (SamServer server = new SamServer(null, SamServer.SERVER_ACCESS_MASK.SAM_SERVER_ENUMERATE_DOMAINS | SamServer.SERVER_ACCESS_MASK.SAM_SERVER_LOOKUP_DOMAIN))
            {
                foreach (string domain in server.EnumerateDomains())
                {

                    //신규 조건문 추가
                    if (match_account != "")
                    {
                        //domain으로 불러들인 계정은 대문자임으로 UserName으로 불러들인 계정 치환
                        if (match_account.ToUpper() == domain)
                        {
                            Console.WriteLine("domain: " + domain);

                            var sid = server.GetDomainSid(domain);
                            Console.WriteLine(" sid: " + sid);

                            var pi = server.GetDomainPasswordInformation(sid);
                            Console.WriteLine(" PasswordProperties: " + pi.PasswordProperties);
                            Console.WriteLine(" PasswordHistoryLength: " + pi.PasswordHistoryLength);
                            Console.WriteLine(" MaxPasswordAge: " + pi.MaxPasswordAge);
                            Console.WriteLine(" MinPasswordLength: " + pi.MinPasswordLength);
                            Console.WriteLine(" MinPasswordAge: " + pi.MinPasswordAge);

                            match_account_info = " domain: " + domain + "\n";
                            match_account_info += " sid: " + sid + "\n";
                            match_account_info += " PasswordProperties: " + pi.PasswordProperties + "\n";
                            match_account_info += " PasswordHistoryLength: " + pi.PasswordHistoryLength + "\n";
                            match_account_info += " MaxPasswordAge: " + pi.MaxPasswordAge + "\n";
                            match_account_info += " MinPasswordLength: " + pi.MinPasswordLength + "\n";
                            match_account_info += " MinPasswordAge: " + pi.MinPasswordAge;

                            richTextBox1.Clear();
                            richTextBox1.Text = match_account_info;
                        }
                    }

                    //Console.WriteLine("domain: " + domain);

                    //var sid = server.GetDomainSid(domain);
                    //Console.WriteLine(" sid: " + sid);

                    //var pi = server.GetDomainPasswordInformation(sid);
                    //Console.WriteLine(" PasswordProperties: " + pi.PasswordProperties);
                    //Console.WriteLine(" PasswordHistoryLength: " + pi.PasswordHistoryLength);
                    //Console.WriteLine(" MaxPasswordAge: " + pi.MaxPasswordAge);
                    //Console.WriteLine(" MinPasswordLength: " + pi.MinPasswordLength);
                    //Console.WriteLine(" MinPasswordAge: " + pi.MinPasswordAge);                    
                }
            }
        }

        private void setPasswordPolicy()
        {

            using (SamServer server = new SamServer(null, SamServer.SERVER_ACCESS_MASK.SAM_SERVER_ALL_ACCESS))
                foreach (string domain in server.EnumerateDomains())
                {
                    //신규 조건문 추가
                    if (match_account != "")
                    {
                        //domain으로 불러들인 계정은 대문자임으로 UserName으로 불러들인 계정 치환
                        if (match_account.ToUpper() == domain)
                        {
                            var sid = server.GetDomainSid(domain);
                            var pi = server.GetDomainPasswordInformation(sid);

                            /* 
                             * [변경값 지정]
                             * MaxPasswordAge 값과, MinPasswordAge 값은 SAM에서 상대 시간을 음수 값으로 저장하고 절대 시간을 양수로 저장하기때문에
                             * 시스템 상에서 값을 지정해줄 때 음수 값으로 지정해줘야함
                             * PasswordHistoryLength과 MinPasswordLength Short Type인 Int16으로
                             */

                            //암호 복잡성 사용여부 = 사용 ( 미사용 시 ~SamServer.PASSWORD_PROPERTIES.DOMAIN_PASSWORD_COMPLEX;)
                            //pi.PasswordProperties = SamServer.PASSWORD_PROPERTIES.DOMAIN_PASSWORD_COMPLEX;
                            //pi.PasswordHistoryLength = 1;
                            //pi.MaxPasswordAge = TimeSpan.Parse("-30.00:00:00");                             
                            //pi.MinPasswordLength = 8;
                            //pi.MinPasswordAge = TimeSpan.Parse("-0.00:00:00");

                            // 임의 지정 테스트
                            pi.PasswordProperties = ~SamServer.PASSWORD_PROPERTIES.DOMAIN_PASSWORD_COMPLEX;
                            pi.PasswordHistoryLength = 5;
                            pi.MaxPasswordAge = TimeSpan.Parse("-40.00:00:00");
                            pi.MinPasswordLength = 10;
                            pi.MinPasswordAge = TimeSpan.Parse("-4.00:00:00");


                            //변경값 적용
                            server.SetDomainPasswordInformation(sid, pi);
                            
                            
                        }
                    }
                }
         }
  
     }
 }