Windows Mitigations - EAF (Export Address Filtering)

WDEG (Widows Defender Exploit Guard)는 기존 EMET (Enhanced Mitigation Experience Toolkit)으로 알려진 익스플로잇 방어 도구를 Windows 10 시스템에 통합하여 제공하는 서비스입니다. 기존 EMET에서 제공하던 여러 mitigation들을 윈도우즈의 통합된 환경에서 설정할 수 있는 편리성을 가지고 있습니다. 특히 기존 Shim 형태로 제공되던 서비스를 시스템 바이너리로 통합시켜서 안정성을 높였습니다. 이번 화에서는 먼저 WDEG의 가장 유용한 기능 중의 하나인 EAF (Export Address Filtering)에 대해서 알아 보겠습니다.

EAF란

Shellcode는 익스플로잇의 역사와 함께 해 왔으며, 최근에는 여러 APT 등을 비롯한 말웨어들이 차용하여 즐겨 쓰는 기법이 되었습니다. EAF는 이러한 쉘코드를 휴리스틱한 방법으로 탐지하기 위해서 개발된 기법입니다.

EAF의 세팅

EAF는 WDEG 의 Exploit Protection 기능에 속합니다. 이 기능을 활성화하고 사용하기 위한 방법은 Enable exploit protection에 자세히 기술되어 있습니다. 여기에서는 사용자 UI와 PowerShell을 이용하는 방법에 대해서 설명하겠습니다.

Settings를 사용하는 방법

윈도우즈 10에서 Update & Security의 “Windows Security” 메뉴의 “App & browser control” 항목을 통해서 해당 기능에 접근 가능합니다.

  • 다음과 같이 “Exploit protection settings” 메뉴를 선택합니다.

  • 시스템 전체에 대해서 아니면 개개의 프로그램등에 대해서 개별 세팅이 가능합니다.

  • 예를 들어 다음 화면은 특정 프로그램에 대해서 EAF를 활성화 한 모습입니다. “Audit only” 체크박스를 체크할 경우 쉘코드를 디텍션할 시에 로깅만을 하고, 실제로 블락킹을 하지는 않습니다. 이러한 기능을 통해서 기존 EDR 환경으로 이벤트를 포워딩하는 방식으로 여러 익스플로잇이나 말웨어 디텍션에 도움을 받을 수 있습니다.

PowerShell을 사용하는 방법

편리성을 위해서 윈도우즈 10에서부터는 PowerShell을 사용하여 EAF를 관리할 수 있습니다. 윈도우즈 10에서부터는 mitigation 세팅을 위해서 Set-ProcessMitigationGet-ProcessMitigation 명령을 제공합니다. 더 자세한 설명은 Customize exploit protection 문서에 잘 설명 되어 있습니다.

예를 들어 notepad.exe에 대해서 EAF를 활성화 하기 위해서는 어드민 권한으로 다음과 같은 명령을 실행할 수 있습니다.

Set-ProcessMitigation -Name notepad.exe -Enable EnableExportAddressFilter

notepad.exe에 적용된 mitigation 들을 알아 보기 위해서는 다음과 같은 명령을 사용할 수 있습니다.

Get-ProcessMitigation -Name notepad.exe

여러 항목에 대한 mitigation 세팅이 나오는데, EAF에 대해서는 다음과 같이 EnableExportAddressFilter이 ON으로 세팅 되어 있음을 확인할 수 있습니다. 물론 notepad.exe 자체에 EAF를 세팅하는 것은 예제의 일부로서 실제 시스템 보안 향상을 위해서는 큰 효과는 없습니다. 대부분의 경우 자신의 시스템 환경과 취약점이 많을 제삼자 어플리케이션에 대해서 이러한 mitigation 세팅을 강화하는 전략을 수립하는 것이 바람직합니다.

Payload:
    EnableExportAddressFilter          : ON
    AuditEnableExportAddressFilter     : ON
    Override ExportAddressFilter       : False
    EnableExportAddressFilterPlus      : NOTSET
    AuditEnableExportAddressFilterPlus : NOTSET
    Override ExportAddressFilterPlus   : False
    EAFModules                         : {kernerl32.dll}
    EnableImportAddressFilter          : OFF
    AuditEnableImportAddressFilter     : ON
    Override ImportAddressFilter       : False
    EnableRopStackPivot                : NOTSET
    AuditEnableRopStackPivot           : NOTSET
    Override EnableRopStackPivot       : False
    EnableRopCallerCheck               : NOTSET
    AuditEnableRopCallerCheck          : NOTSET
    Override EnableRopCallerCheck      : False
    EnableRopSimExec                   : NOTSET
    AuditEnableRopSimExec              : NOTSET
    Override EnableRopSimExec          : False

EAF의 작동 방식

일반적으로 윈도우즈 플랫폼하에서의 쉘코드는 다음과 같은 형태로 작동합니다. EAF는 이러한 쉘코드의 Export Table에 대한 접근 행위를 체크하는 방법으로 이러한 악성 행위들을 탐지해 냅니다.

1) 대부분의 윈도우즈 쉘코드는 32비트의 경우 fs:30을 64비트의 경우 gs:60 메모리 주소를 통해서 PEB 구조체의 주소를 먼저 알아 냅니다.

2) 이 구조체의 Ldr 멤버는 _PEB_LDR_DATA 구조체로서 InLoadOrderModuleList 멤버에 _LIST_ENTRY 타입의 프로세스내에 로딩된 모듈들의 리스트를 가지고 있습니다.

3) 이 구조체를 통해서 kernel32.dll (또는 kernelbase.dll, ntdll.dll) 등의 중요 모듈의 베이스 주소를 알아 냅니다.

4) 이 PE 파일 구조체를 파싱하여 해당 메모리에서 export table 의 위치를 알아 내고 원하는 테이블을 룩업해서, 함수의 주소를 찾습니다.

5) EAF의 경우 IMAGE_DIRECTORY_ENTRY_EXPORT에 지정된 Export Table의 위치에 PAGE_GUARD 메모리 퍼미션을 걸어, 해당 메모리 접근을 모니터링하고, 어떠한 인스트럭션들이 어느 메모리 위치에서 접근했는지에 따라서 디텍션을 수행합니다.

Guard Page

EAF를 디텍션을 기술적으로 이해하기 위해서는 Guard Page에 대해서 이해하여야 합니다. Creating Guard Pages와 같은 문서에 기술되어 있듯이, PAGE_GUARD 플래그를 VirtualAlloc이나 VirtualProtect 등의 메모리 관리 함수를 통해서 메모리 페이지에 세팅할 경우, 해당 메모리에 대한 접근이 일어날 경우 STATUS_GUARD_PAGE_VIOLATION (0x80000001) 익셉션이 생성됩니다. EAF의 경우 이러한 익셉션 핸들러로 자신의 콜백을 등록하여 이 메모리 주소를 접근한 주체가 정당한 모듈인지 아니면 의심스러운 메모리 영역 (특히, 로딩된 모듈 이미지에 맵핑되지 않은 영역)에 속하는지를 체크합니다. 만약 의심스러운 것으로 판단될 경우, 이벤트를 발생 시킵니다.

EAF 인터널스

실제로 이렇게 EAF가 세팅된 프로세스를 확인해 보면, 다음과 같이 WinDbg와 같은 디버거를 어태치 하여 확인할 경우, kernelbase.dll 이미지가 로딩된 여러 위치 중에 7ffd`e4422000 페이지에 Protect 컬럼이 “unknown”으로 마킹된 페이지가 발견 됩니다.

0:000> !address
        BaseAddress      EndAddress+1        RegionSize     Type       State                 Protect             Usage
--------------------------------------------------------------------------------------------------------------------------

...

 +     7ffd`e41d0000     7ffd`e41d1000        0`00001000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [kernelbase; "C:\Windows\System32\kernelbase.dll"]
       7ffd`e41d1000     7ffd`e42d6000        0`00105000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [kernelbase; "C:\Windows\System32\kernelbase.dll"]
       7ffd`e42d6000     7ffd`e4422000        0`0014c000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [kernelbase; "C:\Windows\System32\kernelbase.dll"]
       7ffd`e4422000     7ffd`e4423000        0`00001000 MEM_IMAGE   MEM_COMMIT  <unknown>                          Image      [kernelbase; "C:\Windows\System32\kernelbase.dll"]
       7ffd`e4423000     7ffd`e4438000        0`00015000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [kernelbase; "C:\Windows\System32\kernelbase.dll"]
       7ffd`e4438000     7ffd`e443c000        0`00004000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [kernelbase; "C:\Windows\System32\kernelbase.dll"]
       7ffd`e443c000     7ffd`e443d000        0`00001000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [kernelbase; "C:\Windows\System32\kernelbase.dll"]
       7ffd`e443d000     7ffd`e4473000        0`00036000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [kernelbase; "C:\Windows\System32\kernelbase.dll"]

이러한 위치는 kernel32나 ntdll 등에서도 비슷하게 발견됩니다.

       7ffd`e4422000     7ffd`e4423000        0`00001000 MEM_IMAGE   MEM_COMMIT  <unknown>                          Image      [kernelbase; "C:\Windows\System32\kernelbase.dll"]
       7ffd`e594e000     7ffd`e594f000        0`00001000 MEM_IMAGE   MEM_COMMIT  <unknown>                          Image      [kernel32; "C:\Windows\System32\kernel32.dll"]
 +     7ffd`e7220000     7ffd`e7221000        0`00001000 MEM_IMAGE   MEM_COMMIT  <unknown>                          Image      [ntdll; "C:\Windows\System32\ntdll.dll"]

이러한 메모리 위치의 속성을 확인해 보면 다음과 같습니다. 해당 위치는 다음의 Type 항목에 보이듯이 0x00000102 (PAGE_READONLY + PAGE_GUARD) 퍼미션을 가지고 있는 것을 알 수 있습니다.

0:000> !address 7ffd`e4422000
...

Usage:                  Image
Base Address:           00007ffd`e4422000
End Address:            00007ffd`e4423000
Region Size:            00000000`00001000 (   4.000 kB)
State:                  00001000          MEM_COMMIT
Protect:                00000102          <unknown>
Type:                   01000000          MEM_IMAGE
Allocation Base:        00007ffd`e41d0000
Allocation Protect:     00000080          PAGE_EXECUTE_WRITECOPY
Image Path:             C:\Windows\System32\kernelbase.dll
Module Name:            kernelbase
Loaded Image Name:      
Mapped Image Name:      
More info:              lmv m kernelbase
More info:              !lmi kernelbase
More info:              ln 0x7ffde4422000
More info:              !dh 0x7ffde41d0000


Content source: 0 (invalid), length: 1000

다음으로 해당 메모리가 속하는 PE에서의 영역을 확인해 볼 차례입니다. 예를 들어 kernelbase.dll의 이미지 헤더는 첫번째 페이지인 7ffd`e41d0000에 존재합니다.

 +     7ffd`e41d0000     7ffd`e41d1000        0`00001000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [kernelbase; "C:\Windows\System32\kernelbase.dll"]

다음과 같이 .writemem 명령을 사용하여 해당 메모리를 파일로 저장해 줍니다.

0:000> .writemem 7ffde41d0000.bin 7ffde41d0000 L00001000
Writing 1000 bytes..

해당 메모리에는 PE 파일 전체가 아닌, kernelbase.dll의 PE 파일 헤더 부분만이 저장됩니다. 이제 이 헤더를 파싱하여 PAGE_GUARD가 걸린 7ffd`e4422000 주소가 포함하고 있는 레코드를 확인합니다. 이 과정은 pefile 파이썬 모듈을 사용하여 쉽게 실행 가능합니다. 먼저 시스템에 pefile 모듈이 설치 되지 않았을 경우 다음과 같이 pefile 파이썬 모듈을 설치해 줍니다.

pip install pefile

그리고, 위에서 .writemem으로 저장한 7ffde41d0000.bin 파일을 파싱해 줍니다. pe.print_info() 함수를 통해서 PE 헤더의 구조를 프린트 해 줄 수 있습니다.

import pefile
pe = pefile.PE("7ffde41d0000.bin")
pe.print_info()

프린트 되어 나온 아웃풋에서 다음과 같은 VirtualAddress 항목을 확인할 수 있습니다.

...
[IMAGE_DIRECTORY_ENTRY_EXPORT]
0x178      0x0   VirtualAddress:                0x252D70  
0x17C      0x4   Size:                          0xEBB0    
...

PE 헤더에서의 VirtualAddress들은 모두 이미지 베이스를 기준으로 하기 때문에 이미지 베이스인 7ffde41d0000을 VirtualAddress이 0x252D70와 더해주면, Export Table의 위치는 7FFDE4422D70가 됩니다. 이 위치는 PAGE_GUARD가 걸린 7ffde4422000와 7ffde442300 주소 사이에 존재함을 확인할 수 있습니다. 이로써 가드 페이지가 걸린 주소 범위 안에 Export Table이 포함됨을 알 수 있습니다. 메모리 퍼미션은 페이지 단위로 세팅 가능하기 때문에 다른 PE파일의 영역이 불가피하게 포함되는 경우가 많습니다. 이러한 특성은 특정 케이스에 성능 저하나 호환성 이슈를 일으킬 가능성이 있습니다.

결론

WDEG의 EAF의 개념과 작동 방식에 대해서 간단히 알아 보았습니다. WDEG는 사실 완전한 mitigation을 제공하지는 않습니다. 대부분 시스템에 디폴트로 적용되지 않았지만, 시스템 관리자의 역량에 따라서 시스템의 보안 레벨을 올릴 수 있는 여러 기능 들을 모아 놓았습니다. EAF의 경우 쉘코드 디텍션 등에 큰 효과를 볼 수 있으며, 특히 레거시 모듈을 시스템에서 구동해야 할 경우 등, 해당 프로그램의 보안성을 높이긴 힘든 경우 등에 이러한 mitigation을 적용해 주는 방법으로 어느 정도의 보안성 확보 효과를 기대할 수 있습니다. 다음 아티클에서는 EAF에 대한 실제 쉘코드를 통한 실험과 함께 우회 방법 등에 대해서 논의해 보겠습니다.

Updated: