플랫폼 보안을 위한 제어 흐름 보호
제어 흐름 보호란?
CFG(Control Flow Guard)는 메모리 손상 취약성을 방지하기 위해 만들어진 고도로 최적화된 플랫폼 보안 기능입니다. 애플리케이션이 코드를 실행할 수 있는 위치에 엄격한 제한을 두면 버퍼 오버플로와 같은 취약점을 통해 익스플로잇이 임의의 코드를 실행하기가 훨씬 어려워집니다. CFG는 /GS(버퍼 보안 검사), DEP(데이터 실행 방지) 및 ASLR(주소 공간 레이아웃 임의화)과 같은 이전 악용 완화 기술을 확장합니다.
CFG를 사용하면 다음을 수행할 수 있습니다.
- 메모리 손상 및 랜섬웨어 공격을 방지합니다.
- 서버의 기능을 특정 시점에 필요한 기능으로만 제한하여 공격 표면을 줄입니다.
- 버퍼 오버플로와 같은 취약성을 통해 임의 코드를 악용하기 어렵게 만듭니다.
이 기능은 Microsoft Visual Studio에서 사용할 수 있으며 CFG 인식 버전의 Windows에서 실행됩니다. 클라이언트의 Windows 10 및 Windows 11 및 서버 쪽의 Windows Server 2019 이상
개발자는 애플리케이션에 CFG를 사용하도록 설정하는 것이 좋습니다. CFG 사용 및 비 CFG 사용 코드가 잘 실행되므로 코드의 모든 부분에 대해 CFG를 활성화할 필요가 없습니다. 그러나 모든 코드에 CFG를 사용하도록 설정하지 않으면 보호에 차이가 생기게 될 수 있습니다. 또한 CFG 사용 코드는 CFG를 인식하지 못하는 Windows 버전에서 잘 작동하므로 완전히 호환됩니다.
CFG를 사용하도록 설정하려면 어떻게 해야 하나요?
대부분의 경우 소스 코드를 변경할 필요가 없습니다. Visual Studio 프로젝트에 옵션을 추가하기만 하면 컴파일러와 링커에서 CFG를 사용하도록 설정합니다.
가장 간단한 방법은 Project |로 이동하는 것입니다. 속성 | 구성 속성 | C/C++ | 코드를 생성하고 제어 흐름 보호에 대해 예(/guard:cf)를 선택합니다.
또는 Project |에 /guard:cf를 추가합니다. 속성 | 구성 속성 | C/C++ | 명령줄 | 추가 옵션(컴파일러용) 및 /guard:cf to Project | 속성 | 구성 속성 | 링커 | 명령줄 | 추가 옵션(링커용).
자세한 내용은 /guard(Control Flow Guard 사용)를 참조하세요.
명령줄에서 프로젝트를 빌드하는 경우 동일한 옵션을 추가할 수 있습니다. 예를 들어 test.cpp 프로젝트를 컴파일하는 경우 cl /guard:cf test.cpp /link /guard:cf를 사용합니다.
또한 메모리 관리 API의 SetProcessValidCallTargets를 사용하여 CFG에서 유효한 것으로 간주되는 icall 대상 주소 집합을 동적으로 제어할 수도 있습니다. 동일한 API를 사용하여 페이지가 CFG의 유효하지 않은 대상인지 유효한지 여부를 지정할 수 있습니다. VirtualProtect 및 VirtualAlloc 함수는 기본적으로 실행 파일 및 커밋된 페이지의 지정된 영역을 유효한 간접 호출 대상으로 처리합니다. 메모리 보호 상수 아래에 자세히 설명된 대로 VirtualProtect를 호출할 때 VirtualAlloc 또는 PAGE_TARGETS_NO_UPDATE 호출할 때 PAGE_TARGETS_INVALID 지정하여 Just-In-Time 컴파일러를 구현하는 경우와 같은 이 동작을 재정의할 수 있습니다.
이진 파일이 제어 흐름 보호 아래에 있음을 어떻게 알 수 있나요?
/headers 및 /loadconfig 옵션(dumpbin /headers /loadconfig test.exe)을 사용하여 Visual Studio 명령 프롬프트에서 덤프빈 도구(Visual Studio 설치에 포함)를 실행합니다. CFG 아래의 이진 파일에 대한 출력은 헤더 값에 "Guard"가 포함되고 부하 구성 값에 "CF Instrumented" 및 "FID table present"가 포함됨을 보여 줘야 합니다.
CFG는 실제로 어떻게 작동합니까?
소프트웨어 취약성은 실행 중인 프로그램에 가능성, 비정상적 또는 극단적인 데이터를 제공하여 악용되는 경우가 많습니다. 예를 들어 공격자는 예상보다 프로그램에 더 많은 입력을 제공하여 버퍼 오버플로 취약성을 악용하여 프로그램에서 예약한 영역을 과도하게 실행하여 응답을 보유할 수 있습니다. 이로 인해 함수 포인터를 보유할 수 있는 인접한 메모리가 손상될 수 있습니다. 프로그램이 이 함수를 통해 호출하면 공격자가 지정한 의도하지 않은 위치로 이동할 수 있습니다.
그러나 CFG의 컴파일 및 런타임 지원의 강력한 조합은 간접 호출 명령을 실행할 수 있는 위치를 엄격하게 제한하는 제어 흐름 무결성을 구현합니다.
컴파일러는 다음을 수행합니다.
- 컴파일된 코드에 간단한 보안 검사를 추가합니다.
- 간접 호출에 유효한 대상인 애플리케이션의 함수 집합을 식별합니다.
Windows 커널에서 제공하는 런타임 지원:
- 유효한 간접 호출 대상을 식별하는 상태를 효율적으로 유지 관리합니다.
- 간접 호출 대상이 유효한지 확인하는 논리를 구현합니다.
설명하기 위해 다음을 수행합니다.
런타임에 CFG 검사가 실패하면 Windows는 프로그램을 즉시 종료하므로 잘못된 주소를 간접적으로 호출하려는 모든 악용이 중단됩니다.