윈도우즈 룻킷 - 윈도우즈 디펜더 무력화

윈도우즈 10 시스템에서는 DSE로 인해서 사실상 룻킷을 설치하는 것이 어렵다라는 의견들이 많습니다. 하지만, 2018년도에 발견된 Detrahere와 같은 말웨어들은 여러 기법들을 조합해서 윈도우즈 10에서 효과적으로 여러 방어 장치를 무력화하는 방법을 사용합니다. 이러한 기법들의 디테일들은 저의 2018년 블루햇 토크인 BlueHat v18: Return of the kernel rootkit malware (on windows 10)에서 이미 논의된 바가 있습니다. 여러 기법들 중에서 이번에는 여러 보안 장치, 특히 윈도우즈 디펜더를 무력화하는 방법에 대해서 논의해 보고, 윈도우즈 커널 디버깅을 통해서 어떻게 이러한 룻킷 모듈들을 찾아 낼 수 있는지에 대해서 알아 보겠습니다.

일단 룻킷이란 무엇인지에 대해서 설명해 보겠습니다. 룻킷은 운영체제 시스템에 변형을 일으켜 자신의 존재를 숨기는 말웨어들을 통칭합니다. 마이크로소프트사의 설명에 의하면, 룻킷은 운영체제의 프로세스들을 중간에서 가로채고 변형시키는 방식으로 동작합니다. 이를 통해 자신의 존재를 숨기거나 컴퍼넌트들을 숨기기도 합니다. 한마디로 운영체제 (OS)의 코드 자체를 변형하거나 운영체제의 코드가 사용하는 메모리 상의 구조체 등을 변형하는 방식으로 사용자들에게 자신의 존재를 숨기고, 원래 의도한 오퍼레이션과는 별도의 숨겨진 행위를 하는 말웨어를 통칭하여 룻킷이라고 부를 수 있습니다.

룻킷은 2000년대 초반정도까지 실제 환경 (In-The-Wild)에서 간혹 여러 형태로 발견되기도 하고, 여러 언더그라운드 포럼을 통해서 논의되기도 하다가 2005년 Rootkits: Subverting the Windows Kernel: Subverting the Windows Kernel라는 책을 통해서 본격적으로 체계화되고 정리 되었습니다. 이 책에는 그 당시 가장 진보한 룻킷의 기법 중의 하나였던 DKOM(Direct Kernel Object Manipulation) 등이 체계적으로 논의 되기 시작한 거의 최초의 문서라고 볼 수 있습니다.

기존의 취약점을 이용하거나 아니면 커널의 메모리 상에 존재하는 여러 구조체들을 변형하는 식의 DKOM 기술등의 전통적인 기법은 패치 가드의 도입으로 사라진 반면, 최근 인증서의 헛점을 이용한 새로운 형태의 룻킷들이 많아지고 있습니다. 또한 패치가드에 디텍트 되지 않는 여러 커널 구조체들을 공격하는 방식으로 기존 보안 메커니즘들을 무력화하는 행위를 보여줍니다.

Delivery & Infection

이 룻킷은 다음과 같이 새로운 USB 드라이브가 시스템에서 발견될 경우 해당 드라이브의 기존 PE 파일들을 Detrahere로 감염된 PE 파일로 바꿔치는 방법으로 전파됩니다.

이러한 이유로 인해서 이 룻킷의 경우 감염된 숙주 파일에 따라서 크기와 메타 정보가 다른 여러 종류의 변형된 PE 파읻들이 다수 존재합니다.

Investigation

해당 룻킷은 굉장히 다양한 안티 룻킷 회피 기능 등을 가지고 있습니다. 그 중에서 오늘은 미니필터 우회를 통한 윈도우즈 디펜더 우회 기법에 대해서 연구해 봅니다.

윈도우즈 룻킷의 행위를 분석하기 위해서는 여러가지 접근 방법이 가능합니다. 단 한가지 이 룻킷은 커널 디버깅을 적극적으로 회피하고, 커널 디버깅 행위를 발견할 때에 시스템의 디스크를 파괴하는 기능을 가지고 있어서, 일반적인 커널 디버깅을 통한 분석이 굉장히 난해합니다. 이러한 경우 포렌직 분석 방법이 유용하게 쓰일 수 있습니다. 이러한 룻킷 리서치를 위해서는 rekall이나 volatility와 같은 포렌직 분석 도구들도 유용하게 사용될 수 있습니다. 이번 실험을 위해서는 VMWare를 통한 시스템 메모리 이미지 덤프와 함께, WinDbg를 사용하도록 하겠습니다.

Image Acquisition

WinDbg 등을 이용하여 커널 이미지를 덤프하는데에는 먼저 느린 덤프 속도 문제가 있습니다. 또한 이 룻킷과 같이 적극적으로 커널 디버거의 존재를 회피할 경우 문제가 생길 수 있습니다. 그래서 사용할 수 있는 여러 방법 중의 하나가 VMWare와 같은 가상화 소프트웨어를 사용하는 것입니다. 예를 들어 VMWare에서는 Vmss2core라는 툴을 제공합니다. 이 툴을 사용하면 VMWare의 스냅샷 이미지로부터 WinDbg에 로딩 가능한 형태의 메모리 덤프등을 얻어 내는 것이 가능해집니다.

vmss2core의 정확한 사용법은 Debugging Virtual Machines with the Checkpoint to Core Tool 문서를 참조하시기 바랍니다.

  • 다음과 같이 툴 -W8 옵션을 사용하여 해당 가상 머신의 vmss 파일 이름과 vmem 파일을 지정해 줍니다.
vmss2core-sb-8456865.exe -W8 <vmss filename> <vmem filename>
  • 다음은 tester 사용자 디렉토리 아래의 “Windows 10-1607”이라는 가상 머신의 vmss, vmem 파일을 지정한 예입니다.
vmss2core-sb-8456865.exe -W8 "C:\Users\tester\Documents\Virtual Machines\Windows 10-1607\Windows 10-1607-1864641b.vmss" "C:\Users\tester\Documents\Virtual Machines\Windows 10-1607\Windows 10-1607-Snapshot27.vmem"  

Minifilter Analysis

이렇게 얻어진 메모리 덤프를 통해서 많은 정보들을 분석하고 얻어낼 수 있습니다. 이 룻킷의 특징 중의 하나가 윈도우즈 디펜더의 기능을 무력화 시켜서 RTP (Real-Time Protection) 기능이 작동하지 않는다라는 것입니다. 이러한 사실에 기반해서 미니필터에 어떠한 의심스러운 모듈이 삽입 되었는지 등을 알아 볼 차례입니다. WinDbg를 사용하여 미니필터 모듈 정보를 알아 내기 위해서는 Debugging Minifilters: Using !fltkd.filter이나 Development and Testing Tools와 같은 문서들을 참고 하시기 바랍니다.

기본적으로 다음과 같이 !fltkd.filters 명령을 사용하여 시스템에 설치된 미니 필터들을 리스트 해 볼 수 있습니다.

kd> !fltkd.filters

Filter List: ffffba88f639a0c0 "Frame 0" 
   FLT_FILTER: ffffba88f7516be0 "wcnfs" "409900"
   FLT_FILTER: ffffba88f63a9c20 "WdFilter" "328010"
      FLT_INSTANCE: ffffba88f6560c50 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffffba88f66aac50 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffffba88f67d1690 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffffba88f6ac14c0 "WdFilter Instance" "328010"
   FLT_FILTER: ffffba88f751dc70 "storqosflt" "244000"
   FLT_FILTER: ffffba88f4ece010 "wcifs" "189900"
      FLT_INSTANCE: ffffba88f4eca010 "wcifs Instance" "189900"
   FLT_FILTER: ffffba88f516acd0 "FileCrypt" "141100"
   FLT_FILTER: ffffba88f751c010 "luafv" "135000"
      FLT_INSTANCE: ffffba88f751e010 "luafv" "135000"
   FLT_FILTER: ffffba88f5197c30 "npsvctrig" "46000"
      FLT_INSTANCE: ffffba88f7833d20 "npsvctrig" "46000"
   FLT_FILTER: ffffba88f9189620 "udiskMgr" "45888"
      FLT_INSTANCE: ffffba88f59a5d10 "udiskMgr Instance" "45888"
      FLT_INSTANCE: ffffba88f9f44d10 "udiskMgr Instance" "45888"
      FLT_INSTANCE: ffffba88f54f4d10 "udiskMgr Instance" "45888"
   FLT_FILTER: ffffba88f62bbbc0 "atdxu" "45666"
      FLT_INSTANCE: ffffba88f5581560 "atdxu Instance" "45666"
      FLT_INSTANCE: ffffba88f9717480 "atdxu Instance" "45666"
      FLT_INSTANCE: ffffba88f623fa70 "atdxu Instance" "45666"
      FLT_INSTANCE: ffffba88f93496d0 "atdxu Instance" "45666"
   FLT_FILTER: ffffba88f63a5010 "FileInfo" "45000"
      FLT_INSTANCE: ffffba88f6531b40 "FileInfo" "45000"
      FLT_INSTANCE: ffffba88f6599b40 "FileInfo" "45000"
      FLT_INSTANCE: ffffba88f67d1010 "FileInfo" "45000"
      FLT_INSTANCE: ffffba88f9279980 "FileInfo" "45000"
   FLT_FILTER: ffffba88f63a5820 "Wof" "40700"
      FLT_INSTANCE: ffffba88f6669010 "Wof Instance" "40700"
      FLT_INSTANCE: ffffba88f673f010 "Wof Instance" "40700"

여러 필터 중에 윈도우즈 디펜더에 의해서 사용되는 WdFilter의 정보를 살펴 보면 ffffba88f63a9c20 위치에 해당 필터가 로딩 되었다라는 것을 알 수 있습니다.

   FLT_FILTER: ffffba88f63a9c20 "WdFilter" "328010"
      FLT_INSTANCE: ffffba88f6560c50 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffffba88f66aac50 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffffba88f67d1690 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffffba88f6ac14c0 "WdFilter Instance" "328010"

!fltk.filter 명령을 사용하여 해당 위치 (ffffba88f63a9c20)에서 필터의 자세한 정보를 뽑아 낼 수 있습니다.

kd> !fltkd.filter ffffba88f63a9c20

FLT_FILTER: ffffba88f63a9c20 "WdFilter" "328010"
   FLT_OBJECT: ffffba88f63a9c20  [02000000] Filter
      RundownRef               : 0x0000000000013d32 (40601)
      PointerCount             : 0x00000006 
      PrimaryLink              : [ffffba88f751dc80-ffffba88f7516bf0] 
   Frame                    : ffffba88f639a010 "Frame 0" 
   Flags                    : [00000032] FilteringInitiated +30!!
   DriverObject             : ffffba88f63a7060 
   FilterLink               : [ffffba88f751dc80-ffffba88f7516bf0] *** ERROR: Module load completed but symbols could not be loaded for WdFilter.sys

   PreVolumeMount           : fffff801f43b5240  WdFilter+0x15240 
   PostVolumeMount          : fffff801f43a1db0  WdFilter+0x1db0 
   FilterUnload             : fffff801f43dadd0  WdFilter+0x3add0 
   InstanceSetup            : fffff801f43c4360  WdFilter+0x24360 
   InstanceQueryTeardown    : fffff801f43dad70  WdFilter+0x3ad70 
   InstanceTeardownStart    : 0000000000000000  (null) 
   InstanceTeardownComplete : fffff801f43ca3b0  WdFilter+0x2a3b0 
   ActiveOpens              : (ffffba88f63a9dd8)  mCount=0 
   Communication Port List  : (ffffba88f63a9e28)  mCount=5 
   Client Port List         : (ffffba88f63a9e78)  mCount=2 
   VerifierExtension        : 0000000000000000 
   Operations               : ffffba88f63a9ed0 
   OldDriverUnload          : 0000000000000000  (null) 
   SupportedContexts        : (ffffba88f63a9d50)
      VolumeContexts           : (ffffba88f63a9d50)
      InstanceContexts         : (ffffba88f63a9d50)
      FileContexts             : (ffffba88f63a9d50)
      StreamContexts           : (ffffba88f63a9d50)
      StreamHandleContexts     : (ffffba88f63a9d50)
      TransactionContext       : (ffffba88f63a9d50)
      (null)                   : (ffffba88f63a9d50)
   InstanceList             : (ffffba88f63a9c88)
      FLT_INSTANCE: ffffba88f6560c50 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffffba88f66aac50 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffffba88f67d1690 "WdFilter Instance" "328010"
      FLT_INSTANCE: ffffba88f6ac14c0 "WdFilter Instance" "328010"

이 명령을 통해서 여러 콜백들에 대한 콜백 주소들을 확인할 수 있습니다. 추측과는 다르게 해당 콜백들은 모두 WdFilter 이미지 내부의 주소를 향하고 있습니다.

   PreVolumeMount           : fffff801f43b5240  WdFilter+0x15240 
   PostVolumeMount          : fffff801f43a1db0  WdFilter+0x1db0 
   FilterUnload             : fffff801f43dadd0  WdFilter+0x3add0 
   InstanceSetup            : fffff801f43c4360  WdFilter+0x24360 
   InstanceQueryTeardown    : fffff801f43dad70  WdFilter+0x3ad70 

다음으로 FLT_OBJECT.Operations들을 확인해 봅니다. 이 어레이 구조체에는 여러 오퍼레이션에 대한 함수들의 주소가 저장되어 있습니다.

   Operations               : ffffba88f63a9ed0 

다음과 같이 dqs 명령을 통해서 64비트 포인터 크기 단위로 해당 메모리의 내용과 해당 주소가 가리키는 심볼을 덤프해 볼 수 있습니다.

kd>  dqs ffffba88f63a9ed0
ffffba88`f63a9ed0  00000000`00000000
ffffba88`f63a9ed8  fffff801`f43bb300 WdFilter!MpAmPreCreate
ffffba88`f63a9ee0  fffff801`f43bea20 WdFilter!MpPostCreate
ffffba88`f63a9ee8  00000000`00000000
ffffba88`f63a9ef0  00000000`00000012
ffffba88`f63a9ef8  fffff801`f43bd6c0 WdFilter!MpPreCleanup
ffffba88`f63a9f00  00000000`00000000
ffffba88`f63a9f08  00000000`00000000
ffffba88`f63a9f10  00000000`00000006
ffffba88`f63a9f18  fffff801`f43c4b70 WdFilter!MpPreSetInfo
ffffba88`f63a9f20  fffff801`f43b8280 WdFilter!MpPostSetInfo
ffffba88`f63a9f28  00000000`00000000
ffffba88`f63a9f30  00000000`00000004
ffffba88`f63a9f38  fffff801`f43a1260 WdFilter!MpPreWrite
ffffba88`f63a9f40  fffff801`f43a10e0 WdFilter!MpPostWrite
ffffba88`f63a9f48  00000000`00000000
kd> d
ffffba88`f63a9f50  00000000`000000ed
ffffba88`f63a9f58  fffff801`f43b5240 WdFilter!MpPreMountVolume
ffffba88`f63a9f60  fffff801`f43a1db0 WdFilter!MpPostMountVolume
ffffba88`f63a9f68  00000000`00000000
ffffba88`f63a9f70  00000000`0000000d
ffffba88`f63a9f78  fffff801`f43c4860 WdFilter!MpPreFsControl
ffffba88`f63a9f80  fffff801`f43df3f0 WdFilter!MpPostFsControl
ffffba88`f63a9f88  00000000`00000000
ffffba88`f63a9f90  00000000`000000ff
ffffba88`f63a9f98  fffff801`f43c4ae0 WdFilter!MpPreAcquireSectionSync
ffffba88`f63a9fa0  00000000`00000000
ffffba88`f63a9fa8  00000000`00000000
ffffba88`f63a9fb0  00000000`00000003
ffffba88`f63a9fb8  00000000`00000000
ffffba88`f63a9fc0  fffff801`f43a1ac0 WdFilter!MpPostRead
ffffba88`f63a9fc8  00000000`00000000

이제 이러한 오퍼레이션 자체에 대해서 조사해 봅니다. 예를 들어 첫번재 오퍼레이션인 WdFilter!MpAmPreCreate (fffff801`f43bb300) 주소의 인스트럭션들을 확인해 보면 다음과 같습니다.

kd> u fffff801`f43bb300
WdFilter!MpAmPreCreate:
fffff801`f43bb300 b801000000      mov     eax,1
fffff801`f43bb305 c3              ret
fffff801`f43bb306 184989          sbb     byte ptr [rcx-77h],cl
fffff801`f43bb309 53              push    rbx
fffff801`f43bb30a 104989          adc     byte ptr [rcx-77h],cl
fffff801`f43bb30d 4b085553        or      byte ptr [r13+53h],dl
fffff801`f43bb311 56              push    rsi
fffff801`f43bb312 57              push    rdi

첫번째 두 인스트럭션들은 mov eax, 1과 함께 바로 ret 인스트럭션으로 해당 함수를 실행하지 않고 바로 1을 리턴합니다. 이로서 이 룻킷은 윈도우즈 디펜더의 여러 오퍼레이션 함수들을 직접 수정하는 방법으로 무력화를 시도한 것으로 보입니다. 더 확신을 가지기 위해서 룻킷에 감염되지 않은 상태에서의 해당 함수의 인스트럭션을 리스팅해 보면 다음과 같습니다.

kd> u fffff801`f43bb300
WdFilter!MpAmPreCreate:
fffff801`f43bb300 4c8bdc          mov     r11,rsp
fffff801`f43bb303 4d894318        mov     qword ptr [r11+18h],r8
fffff801`f43bb307 49895310        mov     qword ptr [r11+10h],rdx
fffff801`f43bb30b 49894b08        mov     qword ptr [r11+8],rcx
fffff801`f43bb30f 55              push    rbp
fffff801`f43bb310 53              push    rbx
fffff801`f43bb311 56              push    rsi
fffff801`f43bb312 57              push    rdi

이로서 해당 함수가 확실히 변조 되었다라는 것을 확인할 수 있습니다. 흥미롭게도 이러한 코드 직접 수정의 경우 패치 가드가 제대로 디텍션하지 못한다라는 흥미로운 사실 또한 확인 가능합니다.

이러한 전체 과정은 다음과 같은 다이어그램으로 표현 가능합니다.

결론

이러한 새로운 형태의 룻킷은 기존에 기업 환경의 안전을 보장한다고 생각했던 많은 기술들, 특히 인증서(certificate)조차도 전혀 보안을 보장하는데에 완벽하지 않다라는 것을 다시 알려 줍니다. 또한 패치 가드와 같은 보안 장치에도 불구하고 커널의 모든 메모리를 보호하는 것은 불가능하고, 항상 헛점이 발견될 수 밖에 없다라는 것도 확인 가능합니다.

윈도우즈 디펜더등이나 어떠한 보안 보안 장치라도 결국 같은 레벨과 권한을 가진 말웨어나 룻킷과 경쟁하게 되는 경우 항상 위변조(tampering) 문제를 겪을 수 밖에 없습니다. 이러한 보안 장치와 공격툴 사이의 경쟁에서 보안 장치들도 계속 우위를 점하기 위해서 노력하고 있으며, 이는 곧 VBS(Virtualization-Based Secuiryt)와 같은 최근의 가상화를 이용한 보안 장치의 사용의 방향으로 움직이고 있습니다. 이에 대해서는 다음 아티클을 통해서 조금 더 설명하도록 하겠습니다.

관련 영상