yourtablecloth / TableCloth

식탁보 프로젝트
GNU Affero General Public License v3.0
886 stars 54 forks source link

TermService 서비스를 강제 종료하지 못하도록 방어 #55

Closed rkttu closed 2 years ago

rkttu commented 2 years ago

Windows Sandbox의 필수 서비스인 TermService를 강제 종료하는 보안 에이전트 (예: AhnLab Safe Transaction)로 인해 Sandbox가 강제 종료되는 일이 점점 많이 보이고 있어 대응책이 필요하여 이슈를 만듦. 그러나 해결이 가능할지는 미지수.

아이디어

rkttu commented 2 years ago

Process Explorer를 이용하여 NT Service의 Write 권한을 Administrators에서 빼앗으면 서비스를 중단시키는 방식으로는 프로세스를 종료할 수 없게 되는 것을 확인함.

rkttu commented 2 years ago

테스트해보려는 코드 - https://icodebroker.tistory.com/9158

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;

namespace TestProject
{
    /// <summary>
    /// 프로세스 헬퍼
    /// </summary>
    public static class ProcessHelper
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Enumeration
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 프로세스 접근 권한 - ProcessAccessRights

        /// <summary>
        /// 프로세스 접근 권한
        /// </summary>
        [Flags]
        public enum ProcessAccessRights
        {
            /// <summary>
            /// PROCESS_CREATE_PROCESS
            /// </summary>
            PROCESS_CREATE_PROCESS = 0x0080,

            /// <summary>
            /// PROCESS_CREATE_THREAD
            /// </summary>
            PROCESS_CREATE_THREAD = 0x0002,

            /// <summary>
            /// PROCESS_DUP_HANDLE
            /// </summary>
            PROCESS_DUP_HANDLE = 0x0040,

            /// <summary>
            /// PROCESS_QUERY_INFORMATION
            /// </summary>
            PROCESS_QUERY_INFORMATION = 0x0400,

            /// <summary>
            /// PROCESS_QUERY_LIMITED_INFORMATION
            /// </summary>
            PROCESS_QUERY_LIMITED_INFORMATION = 0x1000,

            /// <summary>
            /// PROCESS_SET_INFORMATION
            /// </summary>
            PROCESS_SET_INFORMATION = 0x0200,

            /// <summary>
            /// PROCESS_SET_QUOTA
            /// </summary>
            PROCESS_SET_QUOTA = 0x0100,

            /// <summary>
            /// PROCESS_SUSPEND_RESUME
            /// </summary>
            PROCESS_SUSPEND_RESUME = 0x0800,

            /// <summary>
            /// PROCESS_TERMINATE
            /// </summary>
            PROCESS_TERMINATE = 0x0001,

            /// <summary>
            /// PROCESS_VM_OPERATION
            /// </summary>
            PROCESS_VM_OPERATION = 0x0008,

            /// <summary>
            /// PROCESS_VM_READ
            /// </summary>
            PROCESS_VM_READ = 0x0010,

            /// <summary>
            /// PROCESS_VM_WRITE
            /// </summary>
            PROCESS_VM_WRITE = 0x0020,

            /// <summary>
            /// DELETE
            /// </summary>
            DELETE = 0x00010000,

            /// <summary>
            /// READ_CONTROL
            /// </summary>
            READ_CONTROL = 0x00020000,

            /// <summary>
            /// SYNCHRONIZE
            /// </summary>
            SYNCHRONIZE = 0x00100000,

            /// <summary>
            /// WRITE_DAC
            /// </summary>
            WRITE_DAC = 0x00040000,

            /// <summary>
            /// WRITE_OWNER
            /// </summary>
            WRITE_OWNER = 0x00080000,

            /// <summary>
            /// STANDARD_RIGHTS_REQUIRED
            /// </summary>
            STANDARD_RIGHTS_REQUIRED = 0x000f0000,

            /// <summary>
            /// PROCESS_ALL_ACCESS
            /// </summary>
            PROCESS_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF)
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Import
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 현재 프로세스 구하기 - GetCurrentProcess()

        /// <summary>
        /// 현재 프로세스 구하기
        /// </summary>
        /// <returns>현재 프로세스 핸들</returns>
        [DllImport("kernel32")]
        private static extern IntPtr GetCurrentProcess();

        #endregion
        #region 커널 객체 보안 구하기 - GetKernelObjectSecurity(handle, securityInformation, securityDescriptor, length, lengthNeeded)

        /// <summary>
        /// 커널 객체 보안 구하기
        /// </summary>
        /// <param name="handle">커널 객체 핸들</param>
        /// <param name="securityInformation">보안 정보</param>
        /// <param name="securityDescriptor">보안 설명자</param>
        /// <param name="length">길이</param>
        /// <param name="lengthNeeded">필요 길이</param>
        /// <returns>처리 결과</returns>
        [DllImport("advapi32", SetLastError = true)]
        private static extern bool GetKernelObjectSecurity
        (
            IntPtr       handle,
            int          securityInformation,
            [Out] byte[] securityDescriptor,
            uint         length,
            out uint     lengthNeeded
        );

        #endregion
        #region 커널 객체 보안 설정하기 - SetKernelObjectSecurity(handle, securityInformation, securityDescriptor)

        /// <summary>
        /// 커널 객체 보안 설정하기
        /// </summary>
        /// <param name="handle">커널 객체 핸들</param>
        /// <param name="securityInformation">보안 정보</param>
        /// <param name="securityDescriptor">보안 설명자</param>
        /// <returns>처리 결과</returns>
        [DllImport("advapi32", SetLastError = true)]
        private static extern bool SetKernelObjectSecurity
        (
            IntPtr      handle,
            int         securityInformation,
            [In] byte[] securityDescriptor
        );

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// DACL_SECURITY_INFORMATION
        /// </summary>
        private const int DACL_SECURITY_INFORMATION = 0x00000004;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 프로세스 보호하기 - ProtectProcess()

        /// <summary>
        /// 프로세스 보호하기
        /// </summary>
        public static void ProtectProcess()
        {
            IntPtr processHandle = GetCurrentProcess();

            RawSecurityDescriptor descriptor = GetProcessSecurityDescriptor(processHandle);

            for(int i = descriptor.DiscretionaryAcl.Count - 1; i > 0; i--)
            {
                descriptor.DiscretionaryAcl.RemoveAce(i);
            }

            descriptor.DiscretionaryAcl.InsertAce
            (
                0,
                new CommonAce
                (
                    AceFlags.None,
                    AceQualifier.AccessDenied,
                    (int)ProcessAccessRights.PROCESS_ALL_ACCESS,
                    new SecurityIdentifier(WellKnownSidType.WorldSid, null),
                    false,
                    null
                )
            );

            SetProcessSecurityDescriptor(processHandle, descriptor);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private

        #region 프로세스 보안 설명자 구하기 - GetProcessSecurityDescriptor(processHandle)

        /// <summary>
        /// 프로세스 보안 설명자 구하기
        /// </summary>
        /// <param name="processHandle">프로세스 핸들</param>
        /// <returns>RAW 보안 설명자</returns>
        private static RawSecurityDescriptor GetProcessSecurityDescriptor(IntPtr processHandle)
        {
            byte[] byteArray = new byte[0];
            uint   bufferSizeNeeded;

            GetKernelObjectSecurity
            (
                processHandle,
                DACL_SECURITY_INFORMATION,
                byteArray,
                0,
                out bufferSizeNeeded
            );

            if(bufferSizeNeeded < 0 || bufferSizeNeeded > short.MaxValue)
            {
                throw new Win32Exception();
            }

            if
            (
                !GetKernelObjectSecurity
                (
                    processHandle,
                    DACL_SECURITY_INFORMATION,
                    byteArray = new byte[bufferSizeNeeded],
                    bufferSizeNeeded,
                    out bufferSizeNeeded
                )
            )
            {
                throw new Win32Exception();
            }

            return new RawSecurityDescriptor(byteArray, 0);
        }

        #endregion
        #region 프로세스 보안 설명자 설정하기 - SetProcessSecurityDescriptor(processHandle, descriptor)

        /// <summary>
        /// 프로세스 보안 설명자 설정하기
        /// </summary>
        /// <param name="processHandle">프로세스 핸들</param>
        /// <param name="descriptor">RAW 보안 설명자</param>
        private static void SetProcessSecurityDescriptor(IntPtr processHandle, RawSecurityDescriptor descriptor)
        {
            byte[] byteArray = new byte[descriptor.BinaryLength];

            descriptor.GetBinaryForm(byteArray, 0);

            if(!SetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, byteArray))
            {
                throw new Win32Exception();
            }
        }

        #endregion
    }
}
rkttu commented 2 years ago

프로세스를 강제 종료하는 것을 막을 수 있는 궁극적인 방법은 없는 것으로 결론 내림. 다만, 적용할 수 있다고 생각한 솔루션들은 모도 적용하여 이 이슈를 닫음.