본문 바로가기
C | C++ | VC++

[Tip] UAC 와 CreateProcess의 ERROR_ELEVATION_REQUIRED,SendMessage의 ERROR_ACCESS_DENIED

by 두루물 2010. 9. 29.

UAC(사용자 계정 컨트롤) 와 CreateProcess의  ERROR_ELEVATION_REQUIRED,SendMessage의 ERROR_ACCESS_DENIED

UAC(사용자 계정 컨트롤) 보안기능은 사용자에겐 불편함이,또 개발자에게는 참으로 골치덩이이자,손볼게 많은 것임에 틀림없다.
더군다나 PC업계 MS-Windows 에서는 (이제야) 32비트와 64비트의 과도기에 접어들면서 WOW에 따른 접근제한등과 맞물려서 개발자가 신경써야 할 부분이 늘어나게 되어 더욱 혼잡하게 된 상황인듯하다.

UAC는 VC++ 2005,2008 이상 빌드시 메니페스트에 쉽게 FIX가능한데, 여기서 asInvoker 로 하자니,프로그램 파일폴더쓰기 금지,HKEY_LOCAL_MACHINE 레지스트리 쓰기금지,파일 쓰기 금지 등 관리자 권한과 관련된 어떠한 접근도 불가능하다. 그렇다면 manifest에 관리자 권한 상승이 필요한(requireAdministrator) 으로 빌드하면 이런 문제는 쉽게 해결이 된다.
하지만 이것도 마냥 편할수 가 없는 문제가 발생한다.
일례로 탐색기의 Shell Extension에서 해당 프로그램에 File Path를 넘겨 해당 작업을 수행하고자 할 경우,
100% 실패할 것이다.(물론,사용자가 UAC 기능 끄기를 하지 않는한)

그렇다고 이렇게 UAC 실행 수준 권한 요구 를 리소스에 FIX 하면, 이 권한중에(정확하게는 highestAvailable 포함 3개) 무언가 하나를 선택해서 고정시켜야 하기 때문에,응용프로그램 생산자 입장에서는 왠지 매끄럽지가 못하다.
이것은 프로그램이 우선 현재 실행중인 일반적인 사용자 권한 (asInvoker)로 프로그램을 빌드하고 프로그램내에서 권리자 권한이 요구되는 레지스트리 쓰기,파일 쓰기등이 시점에서 COM Moniker 나 별도의 Service로 EXE를 따로 만들어 올려놓고,프로그램에서 유동적으로 필요할 때만,관리자 권한을 획득하고 반환하면 된다는 등의 우회방법이 공공연 하게 돌고 있다.

어쨋튼 이제서야 보안에 눈뜬 MS측에서 새롭게 내놓은 이 보안정책이 예전엔 EXE 하나가 모두 해결할 일을 MS에서는 불편하더라도 대세니 각 DLL이나 COM,별도의 EXE 서비스등으로 쪼개서 개발하고 관리하라는 식의 지침(?)을 내리는 것 같은 느낌이다.

각설하고,그렇다면 실제 UAC 관련하여 CreateProcess 와 SendMessage등의 처리시 어떻게 되는 건지 테스트 결과를 보도록 하자.이걸 실제로 확인해 보려면 테스트 프로그램을 만들어 보아야 한다.
먼저 호출되는 대상 EXE는 requireAdministrator 로 빌드 되었다.
결론부터 말하자면, UAC로 인해 내권한 보다 더 높은 관리자 권한 상승이 필요한(requireAdministrator) EXE 실행시 나 메모리 공유 관련등의 접근은 원칙적으로 100% 실패한다.(어차피 나혼자 쓰는 PC용 OS인데 이렇게 복잡할 필요있나라고 할수 있지만,단순한 1 User 용 이었던 MS의 Windows 였지만 이제는 완벽하게 Single User 를 탈피하고  Home PC 급으로 안정화된지 수년, Multi User 를 지향하는 Windows 로서 이런 보안을 강화하는 메카니즘상으로 보면 당연한 결과일 것이다.)

위 CreateProcess는 관리자권한이 요구되는 레벨의 EXE를 일반사용자 권한으로 실행된 EXE에서 호출시,
ERROR_ELEVATION_REQUIRED에러가 발생한다. 이 해결법은,ShellExecute나 ShellExecuteEx 함수로 해결할 수 있다.

일반적으로는 SendMessage는 무결성 수준(Integrity Level)이 낮은 프로세스에서 높은 프로세스로의 SendMessage는 UAC가 WIM(Windows Integrity Mechanism)를 이용해 차단해 버리기 때문에 메세지 전달자체가 안되고 ERROR_ACCESS_DENIED 에러가 발생한다.
이를 위한 해결 방법은 IPC의 다른 방법은 얼마든지 있다.실제로 UAC는 레지스트리,특정폴더 쓰기작업등의 물리적인 접근제한 이외에도 WIM을 사용하여 무결성 수준이 다른 process간에도 Message를 보내지 못하도록 차단하는 것이다.
(Shared Memory나 단순한 인스턴스 형태의 1대1 공유시 파일 매핑이 자주 쓰이지만 UAC Elevation 에 관련된 메시징은 아래 링크를 참고하거나 더 검색해보시길.. 대략 보면 권한높은쪽에서 ChangeWindowMessageFilter() 호출하면 된다고 하는듯....)

The Windows Vista and Windows Server 2008 Developer Story: Windows Vista Application Development Requirements for User Account Control (UAC)

사용자 삽입 이미지


http://msdn.microsoft.com/en-us/library/aa905330.aspx


http://msdn.microsoft.com/library/en-us/IETechCol/dnwebgen/ProtectedMode.asp?frame=true

http://msdn.microsoft.com/en-us/library/Aa480152
http://msdn.microsoft.com/en-us/library/Aa480152#appcomp_topic21


Low Rights 의 대표적인 보호모드 Explorer 가 접근할 수 있는 영역에 대한 문서
Understanding and Working in Protected Mode Internet Explorer
http://msdn.microsoft.com/en-us/library/Bb250462
현재 사용자 권한으로 실행중인 탐색기에서 Shell Extension DLL 을 작성하고 관리자 권한 EXE 에 대해 CreateProcess , SendMessage 등은 이처럼 오류가 날것 이니 참조 하십시오.
(DLL의 manifest에 아무리 권한 수준을 준다고 해도 소유자인 EXE의 권한으로 로드되어 실행되므로 무용지물임)

호출하고자 하는 본체 EXE를 asInvoker로 작성하고 레지스트리 HKEY_LOCAL_MACHINE등에 쓰기 접근하지 말거나,프로그램 파일폴더등에 접근을 하지 못하게 하면 호출은 성공합니다만 ..

파일쓰기,프로그램 폴더 접근,레지스트리 쓰기등의 나머지 로직은 애초에 관리자권한으로 빌드하거나,
앞서 말한 다른 두가지 방법을 찾아봐도 될것이다.

아래는 kxLibrary(자작,구 KFC 윈도우즈 SDK 플랫폼 개발 라이브러리 클래스-KFC1.0이름으로 풀소스 공개) 로 작성된 테스트 소스이다.

 
/* 
  $Id: DECmd.cpp 3 2009-12-20 19:12:53Z krkim $ $Revision: 3 $
  $HeadURL: https://krkim-laptop/svn/KFC/trunk/Demo/Dialog/DECmd.cpp $
 */

#include "stdafx.h"
#include "DlgCmd.h"

#include "resource.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
//
#include "KFCApp.h"
#include "kxRegUtil.h"
#include <tchar.h>

#define WOW64API __fastcall
#define GetProc(m, n) ((PVOID)GetProcAddress(GetModuleHandleA(m), n))
#ifndef _WIN64
//#define GetProc(m, n) ((PVOID)GetProcAddress(GetModuleHandleA(m), n))
#endif
class CApp : public KFCApp  
{
public:
 virtual BOOL InitInstance();
 virtual BOOL ExitInstance();
 CApp();
 virtual ~CApp();
};
/*for dll
KFC_DLL_ENTRYPOINT(CApp)
*/

/*for exe*/

KFC_ENTRYPOINT(CApp)

//int APIENTRY WinMain(HINSTANCE hInstance,
//                     HINSTANCE hPrevInstance,
//                     LPSTR     lpCmdLine,
//                     int       nCmdShow)
//{
// int a = 0;
// //CExampleDlg *demodlg = new CExampleDlg(IDD_MAIN,0);
// MSG msg;
// //ghWnd = CreateDialogParam(hInstance,MAKEINTRESOURCE
(IDD_MAKESFX_DIALOG),NULL,(DLGPROC)MainDlgProc,(LPARAM)0);
// //ShowWindow(ghWnd,SW_SHOW);
// // Main message loop:
// while (GetMessage(&msg, NULL, 0, 0)){
//  TranslateMessage(&msg);
//  DispatchMessage(&msg);
// }
//
// return 0;
//}

void __stdcall TRACELOG(LPCTSTR pszFormat,...)
{
 va_list arglist;
 va_start(arglist,pszFormat);
 //int nMin = _vscprintf((LPCSTR)pszFormat, arglist );
 static const int nCount = 1024;
 TCHAR szBuf[nCount] = {'\0',};

 //vsprintf_s(szBuf,nNeed,pszFormat,arglist);
 //_vsnprintf_s((LPSTR)szBuf, nCount, nCount - 1, 
(LPCSTR)pszFormat, arglist);
 vsnprintf((LPSTR)szBuf, nCount, (LPCSTR)pszFormat, arglist);
 va_end(arglist);
 FILE *fp = fopen("c:\\DEShellx.log","a");
 OutputDebugString((LPCTSTR)szBuf);
 if(fp){
  fprintf(fp,szBuf);
  MessageBox(0,szBuf,"msg",MB_OK);
  fclose(fp);
 }
 else{
  CHAR mess[80];
  DWORD dwerr = GetLastError();
  wsprintf(mess,"error=%d\r\n",dwerr);
  MessageBox(0,szBuf,mess,MB_OK);
 }
}

CApp::CApp()
{

}

CApp::~CApp()
{

}

BOOL WOW64API Wow64CheckProcess( )
{
 typedef BOOL (WINAPI *PFNISWOW64PROCESS)( HANDLE
 hProcess, PBOOL Wow64Process );

 PFNISWOW64PROCESS pfnIsWow64Process =(PFNISWOW64PROCESS)
  GetProc("KERNEL32.dll", "IsWow64Process");
 //fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(
 // GetModuleHandle(TEXT("kernel32")),"IsWow64Process");

 BOOL bIsWow64;

 if (pfnIsWow64Process && pfnIsWow64Process(
GetCurrentProcess(), &bIsWow64))
  return(bIsWow64);

 return(FALSE);
}

BOOL CApp::InitInstance()
{
 LPTSTR strArg = GetCommandLine();
 HWND hBrood;
 ATOM hAtom;
 STARTUPINFO si={0,};
 PROCESS_INFORMATION pi={0,};
 TCHAR EXEPath[MAX_PATH];
 HANDLE hFile = INVALID_HANDLE_VALUE;
 TCHAR szProgram[MAX_PATH * 2 + 1]={0,};

 si.cb = sizeof(si);

 TRACELOG("IsWOW64Process: %d\r\n",Wow64CheckProcess());
 kxReg::LoadString(HKEY_CURRENT_USER,REG_DURUEDIT,
"InstallDir","",EXEPath,MAX_PATH);
 if(lstrlen(InstPath) <= 0)
  return FALSE;

 MessageBox(0,strArg,"test",MB_OK);
 BOOL error= 0;
 hBrood = FindWindow(DURUEDIT_CLASSNAME,NULL);
 // 실행중이지 않으면 직접 실행시킨다.
 int i = 1;
 LPTSTR lpFile = 0;
 TCHAR szTest[MAX_PATH]="c:\\vfo.log";
 if(__argc > 1){
  lpFile = __argv[1];
 }
 else
  lpFile = szTest;
 if(hBrood == NULL) {
  wsprintf(szProgram,"%s \"%s\"",EXEPath,lpFile);
  //관리자권한이 요구되는 레벨의 EXE를 일반사용자 권한으로
  //실행된 EXE에서 호출시,
  //ERROR_ELEVATION_REQUIRED에러가 발생한다.
  //따라서,같은 권한으로 실행된 EXE에서(탐색기포함) 호출해야 성공한다.
  //다른 방법은 ShellExecute나 ShellExecuteEx로 호출하여야 한다.
  //관리자 권한이 아니면 c:\Program Files 나 HKEY_
  //LOCAL_MACHINE 레지스트리,파일쓰기등
  //의 에러도 난다.
  if (CreateProcess(NULL,szProgram,NULL,NULL,
   FALSE,0,NULL,NULL,&si,&pi)) {
   WaitForInputIdle(pi.hProcess,5000);
   CloseHandle(pi.hProcess);
   CloseHandle(pi.hThread);
  }
  else{
   //UAC로 인해 내권한 보다 더 높은 관리자 권한 상승이
   //필요한(requireAdministrator) EXE 실행시 오류난다.
   //하지만,대상 EXE가 asInvoker 권한이면 성공한다.
   //다만,그 EXE에서 권한에 따른 제약이 따르게 된다.
   //(c:\Program Files 나 HKEY_LOCAL_MACHINE 레지스트리,파일쓰기)
   error= 1;
   DWORD dwError = GetLastError();
   if(dwError == ERROR_ELEVATION_REQUIRED){
   }
   TRACELOG("CreateProcess failed! dwError=[%d],
    szProgram=[%s]\r\n",dwError,szProgram);

  }
  // 실행중이면 파일을 열도록 한다.
 } else {
  //관리자권한이 요구되는 레벨의 EXE를 일반사용자 
  //권한으로 실행된 EXE에서 호출시,
  //ERROR_ACCESS_DENIED 에러가 발생한다.
  //권한이 같거나  SendMessage 메세지를 받는 대상
  //윈도우를 소유한 EXE가 높은 무결성 수준 권한으로
  //실행중 이여야 SendMessage가 성공한다.
  hAtom = GlobalAddAtom(lpFile);
  if(hAtom){
   UINT n = SendMessage(hBrood,WM_OPEN_ATOM,(WPARAM)hAtom,0);
   DWORD dwError = GetLastError();
  if(dwError == ERROR_ACCESS_DENIED){

   }
   TRACELOG("SendMessage n = %d(%x) dwError=[%d]\r\n",n,n,dwError);
   GlobalDeleteAtom(hAtom);
  }
  else{
   error= 1;
   DWORD dwError = GetLastError();
   if(dwError == ERROR_ACCESS_DENIED){

   }
   TRACELOG("GlobalAddAtom failed! dwError=[%d]\r\n",dwError);
  }
  SetForegroundWindow(hBrood);
  }
 if(error){
#if 0   //권한상승 효과주는 콜
  //ShellExecute 를 사용 하는 방법
  if(IsUserAnAdmin() == FALSE)
  //프로그램이 관리자 권한인지 알 수 있는 함수
  {
   //관리자 권한으로 실행 시킨다.
   SHELLEXECUTEINFO si;
   ZeroMemory(&si, sizeof(SHELLEXECUTEINFO));
   si.cbSize = sizeof(SHELLEXECUTEINFO);
   si.hwnd = NULL;
   si.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI;
   si.lpVerb = _T("runas");
   si.lpFile = _T("프로그램명");
   si.lpParameters = _T("파라미터");
   si.nShow = SW_SHOWNORMAL;
   si.lpDirectory = NULL;
   ShellExecuteEx(&si);
   /*si.hProcess로 제어*/
  }
#endif
#if 0  //권한상승 효과주는 콜
  wsprintf(szProgram,"\"%s\"",lpFile);
  HINSTANCE hInstance = (HINSTANCE)
   ShellExecute(NULL,_T("open"),InstPath,szProgram,NULL,SW_SHOWNORMAL);
  if(hInstance <= (HINSTANCE)HINSTANCE_ERROR){
   TRACELOG("ShellExecute error!\r\n");
  }
#endif
 }

 return TRUE;
}

BOOL CApp::ExitInstance()
{
 return TRUE;
}

int __stdcall StartUp(int * mask)
{
 DlgCmd dlg(IDD_STARTUP,0);
 int f =  dlg.DoModal();
 *mask = dlg.m_mask;
 return f;
}

참고할 만한 함수:
ChangeWindowMessageFilterEx Function
Modifies the User Interface Privilege Isolation (UIPI) message filter for a specified window.

Syntax

BOOL WINAPI ChangeWindowMessageFilterEx(
  __in         HWND hWnd,
  __in         UINT message,
  __in         DWORD action,
  __inout_opt  PCHANGEFILTERSTRUCT pChangeFilterStruct
);

Parameters

hWnd [in]
HWND

A handle to the window whose UIPI message filter is to be modified.

message [in]
UINT

The message that the message filter allows through or blocks.

action [in]
DWORD

The action to be performed, and can take one of the following values:

Value Meaning
MSGFLT_ALLOW
1

Allows the message through the filter. This enables the message to be received by hWnd, regardless of the source of the message, even it comes from a lower privileged process.

MSGFLT_DISALLOW
2

Blocks the message to be delivered to hWnd if it comes from a lower privileged process, unless the message is allowed process-wide by using the ChangeWindowMessageFilter function or globally.

MSGFLT_RESET
0

Resets the window message filter for hWnd to the default. Any message allowed globally or process-wide will get through, but any message not included in those two categories, and which comes from a lower privileged process, will be blocked.


pChangeFilterStruct [in, out, optional]
PCHANGEFILTERSTRUCT

Optional pointer to a CHANGEFILTERSTRUCT structure.

Return Value

BOOL

If the function succeeds, it returns TRUE; otherwise, it returns FALSE. To get extended error information, call GetLastError.

Remarks

UIPI is a security feature that prevents messages from being received from a lower-integrity-level sender. You can use this function to allow specific messages to be delivered to a window even if the message originates from a process at a lower integrity level. Unlike the ChangeWindowMessageFilter function, which controls the process message filter, the ChangeWindowMessageFilterEx function controls the window message filter.

An application may use the ChangeWindowMessageFilter function to allow or block a message in a process-wide manner. If the message is allowed by either the process message filter or the window message filter, it will be delivered to the window.

Note that processes at or below SECURITY_MANDATORY_LOW_RID are not allowed to change the message filter. If those processes call this function, it will fail and generate the extended error code, ERROR_ACCESS_DENIED.

Certain messages whose value is smaller than WM_USER are required to be passed through the filter, regardless of the filter setting. There will be no effect when you attempt to use this function to allow or block such messages.

Examples

For an example, see MSDN Code Gallery.

Requirements

Minimum supported client

Windows 7

Minimum supported server

Windows Server 2008 R2

Header

Winuser.h (include Windows.h)

Library

User32.lib

DLL

User32.dll

See Also

Reference
ChangeWindowMessageFilter

Conceptual

Windows 

Send comments about this topic to Microsoft

Build date: 9/7/2010


출처: http://krkim.net/39