안녕하세요! 디펜스입니다.

이번 글에서는 하드링크란 무엇이며, 익스플로잇에서 활용되는 하드링크와 일반적인 하드링크와의 차이점이 무엇인지 알아보겠습니다.

또한, 실제 권한상승 익스플로잇(CVE-2018–8440) 사례를 통해 하드링크가 어떤 식으로 활용되고 있는지 알아봅니다.

마지막으로 하드링크를 활용한 권한상승 익스플로잇을 효과적으로 탐지할 수 있는 방안에 대해 소개하겠습니다.

하드링크?

(하드링크에 대해 알고 계신 분은 건너뛰세요!)

윈도우즈 하드링크는 동일한 파일을 참조하는 여러 개의 파일명을 만들 수 있는 기능입니다.

그림 하나면 쉽게 이해할 수 있습니다.

hardlink 출처: Wikipedia

위 그림을 보면 실제 파일이 NTFS 파일시스템 볼륨(C드라이브)내 어딘가에 존재하고 있고, 우리는 이 파일을 A.TXT라는 파일명으로 링크하여 참조하고 있음을 볼 수 있습니다. 하드링크를 이용하면 해당 파일을 참조하는 또 다른 파일명(B.TXT)을 만들 수 있습니다.

만약 하드링크를 통해 파일 내용을 수정하거나 삭제 혹은 권한을 변경하면 어떻게 될까요?

  • 하드링크 수정
    • A.TXT, B.TXT는 모두 같은 파일에 대한 링크라서 둘 중 어떤 링크를 수정하더라도 동일한 결과가 나옵니다.
    • A.TXT를 수정한 다음 B.TXT 내용을 보면 서로 내용이 동일한 것을 알 수 있습니다. 물론 역도 마찬가지입니다.
  • 하드링크 삭제
    • 파일은 링크 참조 카운트를 내부적으로 유지하고 있습니다. 예제의 경우 링크 참조 카운트는 2가 될 것입니다.
    • 하나의 링크를 삭제하면 그것이 가리키는 파일의 링크 참조카운트가 1씩 감소합니다. 참조카운트가 0이 되면 최종적으로 파일이 삭제됩니다.
    • 하나의 링크(A.TXT)를 삭제하더라도 다른 링크(B.TXT)가 살아있다면 해당 파일을 계속 참조할 수 있는 것이죠.
  • 파일의 권한을 변경하려는 경우
    • 기존 링크(A.TXT)나 새로 생성된 하드링크(B.TXT) 둘 중 어떤 링크를 통해 권한을 바꾸어도 둘의 결과는 동일합니다. 링크가 가리키는 파일의 권한이 변경되는 것이죠.
    • 탐색기를 열어 A.TXT 파일의 권한을 수정한 후 B.TXT 권한을 확인해보세요. 동일함을 확인할 수 있을 겁니다. 역도 마찬가지입니다.
    • 링크 자체의 권한은 어떻게 바꾸는지 궁금한 분들이 있을 것입니다. 하드링크 자체는 파일로서 존재하는 것이 아니라 디렉토리의 엔트리로 표현되는 것이라 권한이라는 것이 없습니다.

하드링크 생성에는 다음과 같은 제약이 있습니다.

  • 디렉토리간에는 하드링크를 만들 수 없습니다. (C:\A linked to C:\B)
  • 다른 볼륨간에 하드링크를 만들 수 없습니다. (C:\A.TXT linked to D:\B.TXT)

이제 하드링크를 생성하는 방법을 알아보겠습니다.

하드링크 생성 방법

커맨드라인에서 mklink 명령어를 이용하면 하드링크를 만들 수 있습니다.

다음은 기존파일(hello.txt)에 하드링크(bye.txt)를 생성하는 예제입니다.

C:\temp>echo hello > hello.txt              ; hello.txt 만듭니다.

C:\temp>type hello.txt                      ; hello.txt 내용을 확인합니다.
hello

C:\temp>mklink /H bye.txt hello.txt         ; 하드링크(bye.txt -> hello.txt) 만듭니다.
하드 링크 작성: bye.txt <<===>> hello.txt

C:\temp>type bye.txt                        ; 하드링크 내용을 확인합니다. 
hello

C:\temp>echo bye > bye.txt                  ; 하드링크의 내용을 변경합니다.

C:\temp>type hello.txt                      ; 기존파일(hello.txt)의 내용이 변경된 것을 확인할 수 있습니다.
bye

mklink 명령어는 CreateHardLink API를 사용하여 하드링크를 생성합니다.

윈도우즈에서 하드링크를 만드는데 사용되는 API가 CreateHardLink1입니다.

CreateHardLink 사용법은 다음과 같습니다.

BOOL CreateHardLinkA(
  LPCSTR                lpFileName,
  LPCSTR                lpExistingFileName,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

첫번째 인자에 생성될 파일명(하드링크)을 넣어주고, 두번째 인자에 기존(대상) 파일명을 입력해주면 하드링크를 생성할 수 있습니다.

CreateHardLink가 실패를 리턴하는 경우는 다음과 같습니다.

  • 대상 파일이 존재하지 않는 경우
  • 1023개 이상 하드링크를 만들 경우
  • MAX_PATH 길이보다 긴 패스이름이 사용될 경우
  • 다른 볼륨에 있는 파일에 하드링크를 거는 경우 (앞서 언급)
  • 디렉토리간에 하드링크를 거는 경우 (앞서 언급)
  • 또 있을까요?

하드링크의 재미난 사실

윈도우즈 보안 연구로 굉장히 유명한 James Forshaw(구글 프로젝트 제로)는 하드링크에 대한 흥미로운 사실2을 발표하였습니다.

먼저 CreateHardLink의 구현 내부를 살펴보겠습니다.

BOOL CreateHardLinkW(LPCWSTR lpFileName, LPCWSTR lpExistingFileName) {
    // 생략

    // 대상 파일을 Open한다.(FILE_WRITE_ATTRIBUTES 권한을 요구)
    status = NtOpenFile(&ExistingFileHandle, SYNCHRONIZE | FILE_WRITE_ATTRIBUTES, &ObjectAttributes, ...);
    if (status < 0) return FALSE;   // Open이 실패하면 하드링크 생성 실패를 리턴
 
    // 생략

    // 하드링크 생성요청
    status = NtSetInformationFile(ExistingFileHandle, LinkInfo, target.Length + 16, FileLinkInformation);
    if ( status < 0 ) return FALSE;      
    
    return TRUE;
}

위 코드를 보면, 하드링크를 생성하기 위해 NtOpenFileNtSetInformationFile을 사용하고 있음을 알 수 있습니다. NtOpenFile로 대상 파일을 Open하고나서, NtSetInformationFile을 호출하는 2단계로 이루어져 있는 것이죠.

CreateHardLink

여기서 주목할 부분은 NtOpenFile에 사용된 인자 FILE_WRITE_ATTRIBUTES입니다. 이것의 의미는 “파일 속성을 변경할 수 있게 파일을 열고 싶어”라는 의미입니다. 호출자에게 해당 권한이 주어지지 않으면 NtOpenFile 호출은 실패하게 되고 결국 하드링크 생성을 할 수 없습니다. 대상 파일에 속성쓰기 권한이 없으면 하드링크 생성을 못한다? 일견 타당해보이기도 하는데요.

다음 예는 방금 언급한 NtOpenFile 호출이 실패하여 하드링크 생성에 실패하는 것을 보여주고 있습니다. 일반사용자는 kernel32.dll의 속성을 쓸 수 있는 권한이 없기 때문에 하드링크 생성에 실패한 것이죠.

C:\Test>mklink /H hardlink.txt c:\windows\system32\kernel32.dll
Access is denied.

James Forshaw는 이 지점에서 읽기 권한만 가진 대상 파일을 Open(NtOpenFile)해도 하드링크를 생성할 수 있을까?라는 의문을 가지고 테스트를 해봅니다. 그 결과 다음과 같은 사실을 발견하게 됩니다.

대상 파일에 대한 Write 권한이 없어도 하드링크를 만들 수 있다

CreateHardLink를 이용하면 권한 문제로 NtOpenFile 호출에 실패하여 하드링크를 만들 수 없는 경우(예: kernel32.dll)가 있었는데, James Forshaw가 발견한 사실을 이용하면 그 경우에도 하드링크를 만들 수 있게 된 것이죠. 이를 증명하기 위해 다음과 같은 POC3를 작성하여 공개하였습니다.

// 주요부분만 추려냈습니다.
bool CreateNativeHardlink(LPCWSTR linkname, LPCWSTR targetname)
{
    // ...

    // 대상 파일을 Open합니다.(DesiredAccess=MAXIMUM_ALLOWED)
    NtOpenFile(&ExistingFileHandle, MAXIMUM_ALLOWED/*이것이 바뀌었습니다*/,,,,);

    // ...

    // 하드링크 생성을 요청합니다.
    NtSetInformationFile(ExistingFileHandle, &io_status, link_info, link_info.size(), FileLinkInformation);
}

CreateHardLink는 인자로 FILE_WRITE_ATTRIBUTES인 반면, James Forshaw가 작성한 함수 CreateNativeHardlinkMAXIMUM_ALLOWED를 사용한다는 것이 가장 큰 차이점입니다.

CreateNativeHardLink

MAXIMUM_ALLOWED는 어떻게든 파일을 열어달라는 의미로 보면 됩니다. 즉 어떻게든 대상 파일을 열기만 하면 하드링크 생성이 가능하다는 점을 코드를 통해 얘기해주고 있는 것이죠.

James Forshaw가 만든 CreateHardLink.exe을 이용하여 지금까지 설명한 내용을 테스트해 볼 수 있습니다.

C:\test>mklink /H hardlink.dll c:\windows\system32\kernel32.dll
액세스가 거부되었습니다.

C:\test>CreateHardlink.exe hardlink.dll c:\windows\system32\kernel32.dll
Done

C:\test>dir
 C 드라이브의 볼륨에는 이름이 없습니다.
 볼륨 일련 번호: 5621-FE76

 C:\test 디렉터리

2019-02-26  오후 12:29    <DIR>          .
2019-02-26  오후 12:29    <DIR>          ..
2019-02-04  오전 12:01           117,760 CreateHardlink.exe
2019-01-09  오전 11:55         1,114,112 hardlink.dll
               2개 파일           1,231,872 바이트
               2개 디렉터리  161,787,883,520 바이트 남음
C:\test>

위 테스트를 보면 기본 명령어인 mklink(CreateHardLink)를 이용하면 하드링크 생성이 불가능했는데, 그러한 경우에도 하드링크를 만들 수 있음을 보여준 것이죠.

James Forshaw가 발견한 하드링크 문제는 단순 흥미거리로 그치지 않고, 최근까지도 권한상승 익스플로잇에 활용되고 있습니다.

하드링크가 어떤 문제가 될 수 있을까요?

쓰기 권한이 없는 대상 파일(예:kernel32.dll)에 하드링크를 걸었다고 생각해봅시다. 하드링크를 통해서 대상 파일을 쓸 수 있을까요? 그렇게 된다면 권한상승 취약점으로 이어질 수 있겠지만 윈도우즈가 그렇게 쉬운 상대는 아닙니다. 윈도우즈는 하드링크가 가리키는 파일에 대한 접근 검사(Access Check)를 통해서 쓰기 시도를 막습니다. 쓰기 권한이 없는 대상 파일에 하드링크를 걸 수는 있으나, 하드링크를 통해서 대상 파일을 쓸 수는 없는 것이죠.

hardlink write

쓰기 권한이 없는 대상 파일에 하드링크를 걸 수는 있으나, 하드링크를 통해서 대상 파일을 쓸 수는 없는 것이죠.

다른 케이스를 생각해봐야겠네요.

test 폴더가 있습니다. 일반사용자와 시스템관리자 모두 해당 폴더 밑에 파일을 생성하고 삭제할 수 있는 권한이 주어져 있는 상태입니다. 시스템서비스는 일반사용자도 test\log.txt을 쓸 수 있도록 해당 파일의 퍼미션을 바꾸는 작업을 주기적으로 수행한다고 생각해보죠.

case_1

만약 시스템서비스가 test\log.txt 퍼미션을 바꾸려 하기 전에, 일반사용자가 test\log.txt를 삭제하고 하드링크(test\log.txt -> system32\kernel32.dll)를 생성하면 어떻게 될까요? 시스템서비스는 하드링크를 열어 퍼미션을 바꿀 것이고, 결국 하드링크가 가리키던 kernel32.dll 퍼미션이 일반사용자도 파일쓰기 권한을 가지도록 바뀌게 될 것입니다. 파일쓰기 권한이 없던 일반사용자가 시스템의 중요 파일을 쓸 수 있는 상황이 만들어지는 것이죠.

이와 비슷한 실제 취약점 사례를 바로 살펴보겠습니다.

CVE-2018–8440

실제 권한 상승 익스플로잇에서는 하드링크를 어떻게 활용하는지 알아보겠습니다.

살펴볼 취약점은 2018년 말에 발표된 태스크 스케쥴러 서비스의 권한상승(EoP) 취약점입니다. 이 취약점은 많은 주목을 받았는데요. 그 이유는 제작자인 sandboxescaper가 제로데이를 그대로 공개해 버렸기 때문입니다.

sandboxescaper Source: welivesecurity.com

또한 APT 그룹들이 실제로 오퍼레이션에 활용하였다는 사실4이 밝혀지면서 더 이슈가 되었죠.

태스크 스케쥴러 서비스는 윈도우즈에서 가장 높은 권한인 시스템 권한으로 실행됩니다. 이 서비스는 ALPC 포트라는 일종의 IPC를 통해 외부 프로세스와 통신할 수 있도록 인터페이스를 열어두었습니다.

열어둔 API 중에서 SchRpcSetSecurity에서 취약점이 발생했는데요. 이 API의 역할은 c:\windows\tasks 폴더에 있는 파일의 DACL을 조작하는 것입니다. 예를 들어 “일반사용자가 c:\windows\tasks\hello.job 파일을 READ/WRITE하도록 DACL을 변경해주세요”와 같은 메시지를 서비스에 요청할 수 있습니다.

Task Scheduler Service

tasks 폴더에 위치한 파일의 DACL을 변경할 수 있는 문제와 더불어 “일반 사용자도 tasks 폴더에 파일을 생성(쓰기)할 수 있다”는 사실이 합쳐져 권한상승 익스플로잇이 탄생할 수 있었습니다. 이 과정에서 하드링크가 활용되는데요.

익스플로잇 과정을 살펴보면서 하드링크의 활용을 이해해보겠습니다.

sandboxescaper exploit

  1. tasks 폴더에 하드링크(c:\windows\tasks\UpdateTask.job -> c:\windows\…\PrintConfig.dll)를 하나 생성합니다. 일반사용자가 tasks 폴더에 파일을 생성할 수 있고, 대상 파일(PrintConfig.dll)에 하드링크도 걸 수 있으니 1번 작업이 가능해집니다.
  2. SchRpcSetSecurity를 호출하여, “일반사용자가 c:\windows\tasks\UpdateTask.job의 파일쓰기권한을 가지도록” DACL 변경을 요청합니다.
  3. 태스크 스케쥴러 서비스는 DACL 변경을 수행합니다. 하드링크 대상인 PrintConfig.dll의 DACL을 변경될 것입니다. 일반사용자가 PrintConfig.dll에 대한 쓰기 권한을 갖게 됩니다.
  4. 익스플로잇이 가진 페이로드로 PrintConfig.dll을 덮어씁니다.
  5. 스풀러(Spooler) 서비스를 호출하여 PrintConfig.dll을 로드하도록 합니다.
  6. 스풀러 프로세스 컨텍스트(시스템권한)에서 페이로드가 실행되어 권한상승을 완료합니다.

PrintConfig.dll 외에도 앞서 설명드렸듯이 Open할 수 있는 파일이면 모두 하드링크 대상으로 삼을 수 있습니다.

이번엔 sandboxescaper가 공개한 익스플로잇 코드5에서 하드링크를 생성하는 부분만 살펴보겠습니다.

//Create a hardlink with UpdateTask.job to our target, this is the file the task scheduler will write the DACL of
CreateNativeHardlink(L"c:\\windows\\tasks\\UpdateTask.job", BeginPath);

James Forshaw가 만든 CreateNativeHardlink를 이용해 하드링크를 만들고 있음을 알 수 있습니다. CreateHardLink(표준 API)를 사용하면 하드링크를 걸 수 없기 때문이죠!

James Forshaw가 만든 CreateNativeHardlink를 그대로 재사용하고 있음을 알 수 있습니다. CreateHardLink(표준API)를 사용하면 하드링크를 걸 수 없기 때문이죠!

대응 방안

James Forshaw가 발견한 하드링크 이슈는 2015년 말에 발표된 내용이지만, 최근까지도 권한상승 익스플로잇에 활용되고 있다는 사실을 실제 사례를 통해 알아보았습니다.

sandboxescaper의 제로데이 공개 이후 MS가 패치를 릴리즈하기 전까지, 보안 회사와 보안리서처들이 몇 가지 대응방안을 제시했었습니다.

(a) 스풀러 프로세스 감시6

스풀러 프로세스(spoolsv.exe)의 자식프로세스 생성 행위를 감시하는 방법입니다. 이 방법의 단점은 다음과 같습니다.

  • 스풀러가 아닌 다른 시스템프로세스를 통해서 페이로드를 실행시킬 수 있습니다.
  • 스풀러를 통해서 실행되는 페이로드에서 자식프로세스를 생성하지 않는다면 제안된 방법을 쉽게 우회할 수 있습니다.

(b) tasks 폴더에서 하드링크 생성을 감시하는 방법6

하드링크 생성 로그를 확인하는 방법입니다. 하드링크가 tasks 폴더에서 생성된 사실이 있다면 비정상 행위로 보는 것입니다. 이 방법의 단점은 다음과 같습니다.

  • sandboxescaper의 익스플로잇을 탐지할 수 있으나, 익스플로잇(취약점)이 다른 폴더에 하드링크를 생성할 수 있는 경우라면 탐지하기 어렵습니다. 그렇다고 모든 하드링크 생성 행위를 비정상적으로 판단할 수도 없는 노릇이죠.
  • 행위(공격)가 발생했다는 이벤트만 알려줄 뿐, 공격을 차단하지 못합니다.

(c) 익스플로잇 바이트시퀀스(시그니처) 탐지 방법6

대부분의 AV에서 택하는 방식일 것이라 생각합니다. 공개된 YARA 룰은 다음과 같습니다.

rule TaskSched
{
 strings:
 $a = “c:\\windows\\tasks” wide ascii nocase
 $b = “.job” wide ascii nocase
 $c = “ncalrpc” wide ascii nocase
 $d = “MZ” wide ascii
 
 condition:
 $a and $b and $c and $d
}

윈도우 실행파일(PE)이고 “.job” “c:\windows\tasks” “ncalrpc” 문자열이 포함되어 있으면 익스플로잇이라고 판단하는 것입니다. 이 방법의 단점은 다음과 같습니다.

  • 문자열을 암호화하는 방법으로 탐지 우회가 가능합니다.
  • 오탐(False Positive)의 가능성이 있습니다. 익스플로잇이 아닌 정상실행파일 중에서 저 문자열을 포함하고 있을 가능성도 있는 것이죠. 물론 AV가 사용하는 매칭룰이 저렇게 간단하리라 생각하진 않습니다.
  • 패턴매칭 방식은 알려지지 않은 제로데이 취약점에 대해서 탐지하기 어렵다는 단점도 가지고 있습니다.

(d) 태스크 스케쥴러 권한을 낮추는 방법7(Impersonation)

SchRpcSetSecurity의 내부 동작 중에서 취약점과 가장 관련이 있었던 부분은 바로 DACL을 변경하는 부분입니다. 여기서 제안한 방법은 태스크 스케쥴러가 DACL을 변경하기 전에 현재 스레드 액세스 토큰 대신 사용자(클라이언트)의 토큰(Impersonation token)을 사용하여 자원에 접근하도록 하는 것입니다. 이러한 작업을 Impersonation이라고 하는데요. DACL을 변경하는 작업 전에 태스크 스케쥴러 스레드의 권한을 사용자(클라이언트) 권한으로 잠시 변경하는 것이라 생각하면 됩니다. 이렇게 되면 하드링크대상(예: PrintConfig.dll)의 DACL을 변경할 수 있는 권한이 사용자(클라이언트)에게 없으므로 취약점은 동작하지 않게 됩니다.

impersonation

이 방법이 취약점의 원인(Root Cause)을 정확히 이해하고 내린 적절한 해결책이라 생각합니다. 이 방법을 제안한 0patch라는 제품은 태스크 스케쥴러의 SchRpcSetSecurity 함수 코드 부분을 바이너리 패치하는 방식으로 Impersonation을 구현했습니다. 아마 마이크로소프트가 릴리즈한 패치도 Impersonation을 이용하지 않았을까 추측해봅니다.

하지만 제안된 방식은 해당 취약점만 패치한 것이라서, 새로 나올 권한상승 취약점 대응과는 무관하다는 단점이 있습니다.

X-DIFFENSE

하드링크를 활용하는 권한 상승 익스플로잇에 대한 효과적인 탐지 방안에 대해 소개합니다.

하드링크가 필수적으로 동반되는 종류의 권한 상승 익스플로잇의 공통된 특징은 하드링크 대상 파일에 쓰기 권한이 없다는 점입니다. 익스플로잇이 이미 하드링크 대상 파일에 대해 쓰기 권한이 있다면 하드링크를 걸 필요가 없는 것이죠. 하드링크를 걸어둔다는 것은 취약점을 이용해서 하드링크대상의 쓰기 제한을 풀겠다는 의도니까요. 하드링크가 생성되는 과정에서 그것을 만드는 주체가 하드링크 대상 파일에 쓰기 권한이 없는 경우는 굉장히 의심스러운 행위로 보아야 합니다.

하드링크가 생성되는 과정에서 그것을 만드는 주체가 하드링크 대상 파일에 쓰기 권한이 없는 경우는 굉장히 의심스러운 행위로 보아야 합니다.

표준 API인 CreateHardLink에선 이러한 이상(abnormal) 패턴이 나타날 수 없습니다. CreateHardLink에선 애초에 파일이 쓰기 가능한 파일에 대해서만 하드링크 생성을 요청하거든요.

따라서 하드링크를 요청하는 과정에서 하드링크 대상 파일을 “쓰기접근”으로 열지 않은 경우를 탐지할 수 있으면, 하드링크를 활용하는 권한상승 익스플로잇(기존 익스플로잇 및 앞으로 나올 제로데이를 포함하여)을 효과적으로 탐지하고 차단할 수 있습니다.

X-DIFFENSE(출시예정)는 커널레벨(파일시스템필터)에서 방금 언급한 방법을 적용해 하드링크 를 활용하는 권한상승 시도 행위를 효과적으로 차단할 수 있습니다.

Reference

  1. MSDN, https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createhardlinka 

  2. James Forshaw, Project Zero Blog, https://googleprojectzero.blogspot.com/2015/12/between-rock-and-hard-link.html 

  3. James Forshaw, SymbolicLink Testing Tools, https://github.com/googleprojectzero/symboliclink-testing-tools 

  4. PowerPool malware exploits ALPC LPE zero-day vulnerability, https://www.welivesecurity.com/2018/09/05/powerpool-malware-exploits-zero-day-vulnerability/ 

  5. Task Scheduler LPE from SandboxEscaper, https://github.com/jackson5-sec/TaskSchedLPE 

  6. Task Scheduler ALPC exploit high level analysis – CVE-2018–8440, https://doublepulsar.com/task-scheduler-alpc-exploit-high-level-analysis-ff08cda6ad4f  2 3

  7. How We Micropatched a Publicly Dropped 0day in Task Scheduler (CVE-2018-8440), https://blog.0patch.com/2018/08/how-we-micropatched-publicly-dropped.html