16 Jun
2015
Posted in: 코드
By    No Comments

윈도우8 취약점 완화 관련 API


윈도우8 취약점 완화 관련 API
by 신영진(YoungJin Shin), codewiz at gmail.com, @codemaru, http://www.jiniya.net

윈도우 8하면 보통 현란하게 바뀐 시작 화면을 떠올린다. 인터넷을 접한 사람들은 시작 버튼이 없어졌다는 사실 또한 떠올릴 것이다. 그만큼 윈도우 8의 UI 변화는 많은 사람들에게 충격적이었다. 그 UI 변화를 두고 호불호가 많이 나뉘기도 한다. 하지만 윈도우 8의 모든 변화가 단지 표면적으로 드러나는 앱 월드와 데스크톱 월드의 통합 내지는 UI 표현의 변화, 또는 사라진 시작 버튼에만 있는 것은 아니다. 우리에게 직접 나타나지 않는 내부에도 상당히 많은 변화가 있었다. 이런 내부의 경우 굉장히 혁신적인 변화임에도 관심을 가지고 살펴보지 않으면 잘 알기 힘든 부분이라 크게 평가되고 있지 못한 점이 안타깝다.

윈도우 8은 내부에도 엄청난 변화가 있었다. 하지만 윈도우 8을 만든 개발자들은 비스타에서 충분히 많은 교훈을 얻었기 때문에 그 엄청난 변화를 우리가 직접 느낄 수 있도록 만들지는 않았다. 즉, 내부 구조의 변화도 겉으로 드러나는 표면적인 부분의 변화는 극히 제한적이라는 의미다. 하지만 상당히 많은 부분이 개선되었고 또 기존 것들과 충돌을 일으키지 않는 범위 내에서 실험적인 다양한 기능들이 추가되었다. 그런 변화 중에서도 보안과 관련되어 있으면서도 데스크톱 응용 프로그래머들이 알아두면 도움이 될 만한 UAC 정책 변화와 새롭게 추가된 프로세스 보안 정책에 대해서 살펴보도록 하자.

사용자 계정 컨트롤(UAC) 정책 변경

사용자 계정 컨트롤은 윈도우 비스타부터 도입된 개념으로 프로그램이 관리자 권한이 필요한 작업을 수행할 때에 사용자에게 그 행위를 보고함으로써 위험한 작업이 은밀하게 발생하는 것을 방지하는 것을 의미한다. 원래 권한 개념이 크게 부각되지 않은 윈도우에서 그나마 택할 수 있는 정책이었지만 권한을 줬다가 다시 가져오는 작업은 그리 수월하지 않았다. UAC는 비스타를 가로막는 가장 강한 허들이 되었고, 그로 인해서 시장에서 비스타는 실패한 운영체제로 낙인 찍히게 되었다. 윈도우 7은 그런 교훈을 바탕으로 UAC를 좀 더 다듬어서 사용자를 덜 귀찮게 만들어서 내놓는 형태로 사용자와 타협했다.

하지만 사실상 타협했다는 표현은 좀 어폐가 있다. 그도 그런 것이 거의 대부분의 윈도우 7 사용자는 UAC가 기본적으로 켜져 있음에도 굳이 옵션을 조정해서 UAC를 끈 상태로 운영체제를 사용하기 때문이다. 그래서 윈도우 8에서는 UAC 기능을 끄는 것과 관련해서 아주 미세하게 설정을 조율했다.

기존의 윈도우 비스타와 윈도우 7에서는 사용자 계정 컨트롤을 가장 낮은 단계로 설정하는 경우에는 UAC가 없는 것과 동일하게 되었다. 즉, 가장 낮은 단계는 UAC를 끄는 것과 같은 맥락이었다. 하지만 윈도우 8부터는 더 이상 그런 상식이 통용되지 않는다.

윈도우 8에서 사용자 계정 컨트롤을 가장 낮은 선택할 경우에 경고창은 출력되자 않지만 프로그램의 권한 컨트롤은 여전히 적용된다. 이 말이 무슨 의미냐 하면 기존의 윈도우의 경우 UAC가 가장 낮은 단계인 경우에는 오른쪽 버튼을 눌러서 관리자 권한으로 실행 메뉴를 전혀 사용할 필요가 없었다. 왜냐하면 매니페스트에 상관 없이 UAC가 꺼지면 모든 프로그램이 자동으로 관리자 권한으로 실행되었기 때문이다. 하지만 윈도우 8부터는 UAC가 가장 낮은 단계로 설정하더라도 여전히 관리자 권한으로 실행 메뉴를 사용해야 한다.

이를 직접 확인해 보기 위해서는 UAC를 가장 낮은 단계로 설정한 다음 실행 메뉴를 통해서 명령창을 실행하고 관리자 권한을 필요로 하는 명령어를 입력해보면 알 수 있다. <화면 1>나타난 것과 같이 권한이 없어서 명령어가 실패하는 것을 볼 수 있다. 이런 문제 때문에 윈도우 8의 탐색기에는 <화면 2>에 나타난 것과 같이 관리자 권한으로 명령창을 실행하는 메뉴가 추가돼 있다. 이 메뉴를 통해서 관리자 권한으로 실행된 명령창에서 동일한 명령어를 입력하면 정상적으로 수행되는 것을 볼 수 있다.


image001

화면 1 UAC가 꺼진 상태에서 관리자 권한이 필요한 작업을 수행하면 실패한다.


image002

화면 2 탐색기에는 관리자 권한으로 명령창을 실행하는 메뉴가 생겼다.

UAC를 끄더라도 권한 컨트롤이 여전히 수행된다는 것은 프로그램을 그냥 실행시켰을 때 매니페스트에 관리자 권한을 요구하는 표시가 되어 있다면 경고창 없이 그 프로그램을 관리자 권한으로 실행시켜 주지만 그렇지 않은 경우에는 UAC가 가장 낮은 단계로 설정돼 있더라도 일반 권한으로 해당 프로그램을 실행시킨다는 것을 의미한다.

UAC 설정은 HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System 경로에 있는 일부 키 값을 통해서 조정된다. 윈도우 8에서는 UAC에서 가장 낮은 단계를 선택하더라도 <화면 3>에 나타난 것과 같이 EnableLUA 값이 0이 아닌 1로 설정돼 있는 것을 볼 수 있다. <표 1>에는 사용자 계정 컨트롤 설정 값에 따른 UAC 관련 레지스트리 값의 변화가 나와 있다. 표를 살펴보면 다른 부분은 모두 같지만 가장 낮은 단계를 선택한 경우에는 EnableLUA 값이 달라진 것을 볼 수 있다. 이 값을 윈도우 7과 똑같이 조작하면 UAC가 완전히 꺼지도록 만들 수 있다.


image003

화면 3 UAC 설정 관련 레지스트리 키

표 1 UAC 설정에 따른 레지스트리 키 값

설정 윈도우 7 윈도우 8
항상 알림 ConsentPromptBehaviorAdmin: 2
PromptOnSecureDesktop: 1
EnableLUA: 1
ConsentPromptBehaviorAdmin: 2
PromptOnSecureDesktop: 1
EnableLUA: 1
설정 변경 시에만 알림 ConsentPromptBehaviorAdmin: 5
PromptOnSecureDesktop: 1
EnableLUA: 1
ConsentPromptBehaviorAdmin: 5
PromptOnSecureDesktop: 1
EnableLUA: 1
설정 변경 시에 알림
화면 흐리게 하지 않음
ConsentPromptBehaviorAdmin: 5
PromptOnSecureDesktop: 0
EnableLUA: 1
ConsentPromptBehaviorAdmin: 5
PromptOnSecureDesktop: 0
EnableLUA: 1
알리지 않음 ConsentPromptBehaviorAdmin: 0
PromptOnSecureDesktop: 0
EnableLUA: 0
ConsentPromptBehaviorAdmin: 0
PromptOnSecureDesktop: 0
EnableLUA: 1

이 사소한 변화를 통해서 시작 버튼도 없애버린 마이크로소프트가 우리에게 얼마나 UAC를 강요하고 싶어하는지를 엿볼 수 있다고 생각한다. 보안을 생각했을 때 이런 변화가 결코 나쁜 변화는 아니다. 하지만 관리자 권한 매니페스트가 없는 프로그램을 자주 관리자 권한으로 실행해야 하는 사용자에게는 분명 예전보다는 불편해진 점이기도 하다. 이 때에는 프로그램의 호환성 속성을 사용하면 도움이 된다. <화면 4>에 나타난 것과 같이 호환성 설정 부분에서 항상 관리자 권한으로 실행하도록 만들어주는 옵션을 선택하면 해당 프로그램은 매니페스트에 관리자 권한 요구 속성이 없어도 관리자 권한으로 실행시켜 준다.


image004

화면 4 항상 관리자 권한으로 실행하도록 설정

우리가 만드는 프로그램도 사용자가 알아서 저렇게 설정해 주기를 원할 수 있지만 사용자 친화적인 프로그램이라면 이러한 설정을 메뉴를 통해서 옵션으로 제공하는 것도 나쁘지 않다. 프로그램으로 해당 옵션을 조정하는 방법은 간단한 레지스트리 조작으로 할 수 있다. 호환성 플래그를 설정하는 레지스트리 키는 아래 경로를 살펴보면 된다. 해당 경로를 이동하면 <화면 5>에 나타난 것과 같이 호환성이 설정된 프로그램의 목록이 나타난다. 키 값은 해당 프로그램의 경로가 되고, 데이터는 설정된 값을 나타낸다. 관리자 권한으로 실행을 하기 위해서는 RUNASADMIN이라는 문자열을 추가해 주면 된다.

HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
HKLM\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers


image005

화면 5 항상 관리자 권한으로 실행 기능이 켜진 프로그램의 AppCompatFlags 레지스트리 값

취약점 완화 방안

예전에 스타크래프트란 게임을 한창 즐기던 시절 게임 잡지에 “스타크래프트 상에서 최고의 수비는 상대를 공격하는 것”이란 기사가 실린 것을 본 적이 있다. 그 당시만 해도 우주방어테란만 고집했던 터라 내심 그 기사를 좀 무시했었다. 하지만 이후 임요환 선수가 등장하면서 모든 것이 달라졌다. 고질적으로 공격이 느리던 테란의 단점을 드랍쉽으로 커버하면서 정말 최고의 수비는 방어가 아닌 공격에서 나온다는 것을 온몸으로 보여줬기 때문이었다. 시시한 게임 이야기라고 생각하면 오산이다. 이 전략은 게임 속뿐만 아니라 현실 세계에서도 얼마든지 통용되는 전략이기 때문이다.

모든 프로그래머들은 버그가 없는 프로그램, 보안 취약성이 없는 프로그램을 만들고 싶어한다. 하지만 안타깝게도 현대 프로그램은 그 규모와 복잡도가 너무나 커져 버려서 사실상 그런 프로그램을 만든다는 것은 거의 불가능에 가까워졌다고 할 수 있다. 매일같이 보고되는 수많은 취약점, 제로데이 공격들이 그 근거라고 할 수 있겠다. 이러한 이슈가 발생할 때 마다 프로그래머는 빠르게 관련 문제를 수정하지만 수정한 것을 배포할 때쯤엔 또 다른 버그와 취약점이 기다리고 있다. 이미 속도에서 경쟁이 안 되는 상태가 돼 버린 것이다.

그래서 임요환 선수같이 똑똑한 프로그래머들이 조금 다른 생각을 하기 시작했다. “왜 우리만 항상 이렇게 당해야 하지? 우리도 똑같이 해커를 공격할 방법은 없는 것일까?”라는 생각에서 출발한 전략이 바로 취약점 완화 방안이다. 이 방법의 전략은 단순하다. 어차피 늘 상 있는 버그, 취약점은 인정하겠다는 것이다. 하지만 그런 것들을 알고 있는 해커라고 하더라도 그걸 공격하는 코드를 만드는 것은 어렵도록 만들겠다는 취지다. 그래서 모든 취약점 완화 방안은 근본적인 대책은 아니다. 하지만 이런 사소한 것들이 여러 개 모이면 공격자 입장에서는 다양한 환경에서 항상 정상적으로 동작하는 코드를 만드는 일이 예전보다 훨씬 어려워지기 때문에 공격의 속도가 늦어지고 악성 코드를 만드는 데에 더 많은 비용이 투입되도록 만드는 장점이 있다.

시스템 프로그래머가 아니라면 이러한 방법이 있었다는 것을 모르는 경우가 많지만 사실 윈도우가 취약점 완화 기능을 만들기 시작한지는 제법 오래됐다. 많이 알려진 기능으로는 2002년에 처음 소개된 VIsual C++의 /GS 기능이 있다. 이 기능을 사용하면 스택 오버플로 공격을 통해서 임의의 코드를 실행하는 것을 억제할 수 있다. 2003년에는 SafeSEH라는 기능이 개발됐다. 이미지에 기록된 예외처리 핸들러만 실행하도록 운영체제에서 제한한 것이다. 2004년에는 데이터 실행 방지(DEP) 개념이 소개됐다. 이 기능은 프로그램 내의 스택과 힙과 같은 영역에서 코드가 실행될 수 있는 것을 제한한 기능이다. 윈도우 비스타가 출시되던 2006년에는 주소 공간 배치를 랜덤화 하는 ASLR이라는 기술이 소개됐다. 이 기술은 /DYNAMICBASE 플래그가 지정된 DLL의 로드 주소를 랜덤하게 결정하는 기술이다. 이 기술과 함께 윈도우 핵심 DLL들은 모두 이 플래그를 사용하도록 변경됐기 때문에 윈도우 비스타 이후에는 주소가 하드 코딩된 형태로 시스템을 공격하는 코드는 더 이상 동작하지 않게 되었다. 윈도우 8에는 이런 기존의 취약점 완화 기능들을 보다 향상시키는 한편 새로운 취약점 완화 기능들이 대거 추가했다. 새롭게 추가된 기능에 대해서 하나씩 살펴보도록 하자.

PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON
이 정책이 적용된 프로세스는 로드되는 모든 DLL에 대해서 강제로 주소 공간 배치 랜덤화(ASLR) 기능을 적용한다. /DYNAMICBASE가 지정되지 않은 구형 DLL 또한 강제로 랜덤한 주소에 로드하도록 만든다는 의미다.

PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON_REQ_RELOCS
이 정책은 앞서 설명한 강제로 ASLR을 적용하는 것에 더불어 재배치 정보가 없어서 랜덤한 주소에 로드할 수 없는 모듈의 경우에는 DLL의 로드를 실패 처리한다. 이 정책이 지정된 프로세스에는 재배치 정보가 포함되지 않은 DLL은 아예 로드 자체가 불가능해진다는 의미다.

PROCESS_CREATION_MITIGATION_POLICY_HEAP_TERMINATE_ALWAYS_ON
이 정책이 지정되면 힙이 손상되는 경우에는 자동으로 해당 힙을 파괴한다. 힙 오버플로나 힙의 메타 정보를 사용한 공격이 발생하는 과정에서 공격자 코드의 결함으로 힙의 정보가 손상되는 경우에는 해당 힙을 바로 파괴해 버리도록 만든다.

PROCESS_CREATION_MITIGATION_POLICY_BOTTOM_UP_ASLR_ALWAYS_ON
스택을 포함한 bottom-up 할당기가 적용되는 메모리 할당에 ASLR을 적용한다.

PROCESS_CREATION_MITIGATION_POLICY_HIGH_ENTROPY_ASLR_ALWAYS_ON
bottom-up 할당기의 할당 범위를 1테라 바이트 주소 공간까지 확장한다. 네이티브 64비트 프로그램에만 적용된다.

PROCESS_CREATION_MITIGATION_POLICY_STRICT_HANDLE_CHECKS_ALWAYS_ON
잘못된 핸들을 사용하는 경우에 즉시 예외가 발생한다. 이 플래그가 설정되지 않은 프로세스에서는 실패 값이 반환된다.

PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON
win32 서브시스템과 관련된 코드는 유저 모드와 커널 모드에 모두 코드가 존재하면서 상호 정보를 교환하기 때문에 윈도우 구조상 가장 취약한 부분이라고 할 수 있다. 이 정책이 적용된 프로세스는 win32 서브시스템과 관련된 API 호출이 원천적으로 봉쇄된다. 이 말은 쉽게 말하면 user32.dll에 있는 API를 호출할 수 없도록 만드는 옵션이라고 생각하면 된다.

PROCESS_CREATION_MITIGATION_POLICY_EXTENSION_POINT_DISABLE_ALWAYS_ON
높은 수준의 보안성을 유지해야 하는 프로그램에 있어서 AppInit이나 윈도우 훅과 같은 알려진 시스템 루트를 통한 침투는 굉장히 취약한 부분이다. 이러한 기능은 일반적인 프로그램에서도 많이 사용하기 때문에 악의적인 목적의 공격인지를 판단하는 것이 더욱 어렵다. 이 정책이 적용된 프로세스는 이러한 알려진 시스템 루트를 통해서 DLL이 로딩되는 것을 원천적으로 차단된다.

프로세스 취약점 완화 정책 설정

윈도우 8에서 추가된 다양한 취약점 완화 정책들을 살펴보았다. 그렇다면 도대체 이 정책들은 어떻게 설정할 수 있는 것일까? 윈도우 8에서는 그런 목적을 위해서 2개의 함수가 추가됐다. 취약점 완화 정책을 설정하기 위해서 SetProcessMitigationPolicy, 취약점 완화 정책을 조회하기 위해서 GetProcessMitigationPolicy 함수가 추가됐다. 각 함수의 원형은 <리스트 1>에 나와 있는 것과 같다.

리스트 1 SetProcessMitigationPolicy/GetProcessMitigationPolicy 함수 원형

BOOL WINAPI SetProcessMitigationPolicy(
PROCESS_MITIGATION_POLICY MitigationPolicy
, PVOID lpBuffer
, SIZE_T dwLength
);

BOOL WINAPI GetProcessMitigationPolicy(
    HANDLE hProcess
    , PROCESS_MITIGATION_POLICY MitigationPolicy
    , PVOID lpBuffer
    , SIZE_T dwLength
);

함수 사용방법은 간단하다. SetProcessMitigationPolicy의 경우 현재 프로세스에 대해서만 적용되며, GetProcessMitigationPolicy 함수의 경우에는 첫 번째 인자로 넘어간 프로세스의 정책을 구한다. MitigationPolicy에는 <표 2>에 나타난 것과 같은 상수 각을 사용할 수 있다. buffer에는 MitigationPolicy에 지정한 상수에 맞는 구조체의 포인터를, dwLength에는 해당 구조체의 크기를 넘겨주면 된다.

표 2 MitigationPolicy 값

MitigationPolicy 값 설명
ProcessDEPPolicy 데이터 실행 방지(DEP) 정책을 설정한다.
buffer에는 PROCESS_MITIGATION_DEP_POLICY 구조체 포인터를 전달한다.
ProcessASLRPolicy 주소 공간 배치 랜덤화 정책을 설정한다.
buffer에는 PROCESS_MITIGATION_ASLR_POLICY 구조체 포인터를 전달한다.
ProcessStrictHandleCheckPolicy 엄격한 핸들 체크 정책을 설정한다.
buffer에는 PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY 구조체 포인터를 전달한다.
ProcessSystemCallDisablePolicy 시스템 호출 금지 정책을 설정한다.
buffer에는 PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY 구조체 포인터를 전달한다.
ProcessExtensionPointDisablePolicy 프로세스 확장 포인트 금지 정책을 설정한다.
buffer에는 PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY 구조체 포인터를 전달한다.

<리스트 2>에는 ProcessStrictHandleCheckPolicy 정책을 구하고 설정하는 예제가 나와 있다. 프로그램을 살펴보면 첫 번째 VirtualAllocEx는 ProcessStrictHandleCheckPolicy 정책이 설정되기 전에 호출되었기 때문에 일반적인 호출과 같이 함수 실패로 처리된다. 하지만 두 번째 VirtualAllocEx의 경우에는 해당 정책이 설정된 다음에 잘못된 핸들을 사용해서 호출하고 있기 때문에 예외가 발생해서 프로그램이 크래시 되도록 만든다.

리스트 2 PROCESS_CREATION_MITIGATION_POLICY_STRICT_HANDLE_CHECKS_ALWAYS_ON 테스트

소스 코드 다운로드

#define _WIN32_WINNT 0x0602
#include "Windows.h"

int main()
{
    HANDLE process = (HANDLE) 0x4444;

    VirtualAllocEx(process, NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    printf("First VirtualAllocEx Call\n");

    PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY splc1;
    if(GetProcessMitigationPolicy(GetCurrentProcess()
                                    , ProcessStrictHandleCheckPolicy
                                    , &splc1
                                    , sizeof(splc1)))
    {
        printf("DisallowWin32kSystemCalls: %d\n"
                 , splc1.RaiseExceptionOnInvalidHandleReference);
    }

    PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY splc2 = {0,};

    splc2.RaiseExceptionOnInvalidHandleReference = TRUE;
    splc2.HandleExceptionsPermanentlyEnabled = TRUE;
    if(!SetProcessMitigationPolicy(ProcessStrictHandleCheckPolicy
                                     , &splc2
                                     , sizeof(splc2)))
        printf("SetProcessMitigationPolicy fail %d\n", GetLastError());

    PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY splc3;
    if(GetProcessMitigationPolicy(GetCurrentProcess()
                                    , ProcessStrictHandleCheckPolicy
                                    , &splc3
                                    , sizeof(splc3)))
    {
        printf("DisallowWin32kSystemCalls: %d\n"
                , splc3.RaiseExceptionOnInvalidHandleReference);
    }

    VirtualAllocEx(process, NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    printf("Second VirtualAllocEx Call\n");
    return 0;
}

<리스트 2>의 예를 살펴보면 프로그램이 정책을 설정하기 전에는 여전히 해당 정책으로 금지하려는 동작이 수행 가능한 것을 알 수 있다. 이를 보면 당연히 공격자 입장에서 정책이 설정되기 직전에 공격을 한다면 타이밍 공격이 가능하다는 것을 알 수 있다. 더불어 앞서 살펴보았던 정책 중에 ASLR과 같은 것들은 주소 공간의 변화에 대한 것들이라 프로그램이 실행된 이후에 설정하는 것은 아무 의미가 없다. 왜냐하면 이미 프로그램이 실행됐다는 것은 프로그램 실행에 필요한 DLL이 거의 다 로드됐다는 의미이기 때문이다. 따라서 사실상 이런 정책은 앞서 소개한 API를 통해서 설정하는 것 보다는 프로세스 생성시에 지정하는 것이 가장 큰 효과를 가질 수 있다.

<리스트 3>에는 프로세스 생성 시에 정책을 설정하는 것을 보여주고 있다. 실행하면 PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON 정책을 설정한 다음 파라미터를 추가해서 자기 자신을 다시 실행한다. 다시 실행되면 if문의 코드는 수행되지 않고 뒤쪽의 현재 정책을 출력하고 user32.dll을 로드하는 코드가 수행된다. 이 경우에는 시작 시점에 이미 win32k 시스템 호출 금지 정책이 적용됐기 때문에 user32.dll의 로드가 실패해서 모듈 주소가 0으로 출력되는 것을 확인할 수 있다.

리스트 3 PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON 정책 테스트

소스 코드 다운로드

int _tmain(int argc, _TCHAR* argv[])
{
    if(argc == 1)
    {
        STARTUPINFOEXW si = {0,};
        PROCESS_INFORMATION pi;
        WCHAR cmd[MAX_PATH];

        StringCbPrintfW(cmd, sizeof(cmd), L"\"%s\" dummy_args", argv[0]);

        UCHAR buffer[4096];
        LPPROC_THREAD_ATTRIBUTE_LIST attr = (LPPROC_THREAD_ATTRIBUTE_LIST) buffer;
        SIZE_T size = 0;
        if(!InitializeProcThreadAttributeList(NULL, 1, 0, &size))
        {
            if(GetLastError() != ERROR_INSUFFICIENT_BUFFER)
                return 0;
        }

        attr = (LPPROC_THREAD_ATTRIBUTE_LIST)  new UCHAR[size];
        if(!InitializeProcThreadAttributeList(attr, 1, 0, &size))
            return 0;

        ULONG policy 
        = PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON;
        if(!UpdateProcThreadAttribute(attr
                                        , 0
                                        , PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY
                                        , &policy
                                        , sizeof(policy)
                                        , NULL
                                        , NULL))
        {
            DeleteProcThreadAttributeList(attr);
            return 0;
        }

        si.StartupInfo.cb = sizeof(si);
        si.lpAttributeList = attr;

        if(!CreateProcessW(NULL
                            , cmd
                            , NULL
                            , NULL
                            , FALSE
                            , EXTENDED_STARTUPINFO_PRESENT
                            , NULL
                            , NULL
                            , &si.StartupInfo
                            , &pi))
        {
            DeleteProcThreadAttributeList(attr);
            return 0;
        }

        WaitForSingleObject(pi.hProcess, INFINITE);
        DeleteProcThreadAttributeList(attr);
        return 0;
    }

    PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY plc;
    if(GetProcessMitigationPolicy(GetCurrentProcess()
                                    , ProcessSystemCallDisablePolicy
                                    , &plc
                                    , sizeof(plc)))
    {
        printf("DisallowWin32kSystemCalls: %d\n"
                    , plc.DisallowWin32kSystemCalls);
    }

    printf("user32.dll => %p\n", LoadLibraryW(L"user32.dll"));
    getchar();

    return 0;
}

<리스트 3>의 코드에 MessageBoxW와 같은 win32 서브시스템을 사용하는 함수를 정적으로 호출하는 코드를 추가한 다음 프로그램을 다시 실행하면 <화면 6>에 나타난 것과 같이 정상적으로 응용 프로그램을 시작할 수 없다는 메시지가 출력되고 프로그램이 실행되지 않는다. 이는 해당 함수가 정적으로 바인딩 됐기 때문에 운영체제 로더가 user32.dll을 로드해야 하는데 그 또한 정상적으로 수행할 수 없기 때문이다.


image006

화면 6 응용 프로그램 시작 실패 메시지


  • 트랙백 주소: http://www.jiniya.net/wp/archives/10414/trackback

관련 글