Technologies

InfinityHook

PatchGuard를 우회할 수 있는 윈도우즈 커널 후킹 라이브러리가 최근 공개되었습니다.

커널 후킹 기술은 보안 제품이나 루트킷에서 거의 필수적으로 사용되어 왔습니다. 과거에는 SSDT, IDT 등의 시스템 테이블 변조를 통해 구현하는 방식이 일반적이었는데, MS가 PatchGuard를 도입하고 나서부터 기존의 커널 후킹 방식이 불가능해졌습니다.

이러한 상황에서 공개된 InfinityHookPatchGuard를 우회할 수 있는 윈도우즈 커널 후킹 라이브러리로서, Win7~Win10을 지원하고 시스템콜,컨텍스트스위치,페이지폴트 핸들러 등을 후킹할 수 있습니다.

사용하기 쉽게 잘 만들어져 있어 해당 기술을 해커들이 차용할 가능성이 있습니다. 방어자 입장에선 어떻게 탐지할 것인지 고민해야 할 것으로 보입니다..

테스트

InfinityHook은 커널 라이브러리이므로, 이것을 테스트해보려면 커널드라이버를 만들어야 합니다. 커널 드라이버의 큰 골격은 다음과 같습니다.

/* 드라이버 메인 함수입니다. */
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
    // 드라이버 언로드 핸들러 등록
    DriverObject->DriverUnload = DriverUnload;  

    // NtCreateFile 주소
    OriginalNtCreateFile = (NtCreateFile_t)MmGetSystemRoutineAddress(&StringNtCreateFile);

    // InfinityHook을 시작합니다.
    IfhInitialize(SyscallStub);
}

/* 드라이버 언로드시 */
void DriverUnload(_In_ PDRIVER_OBJECT DriverObject) {
    // InfinityHook을 종료합니다.
    IfhRelease();       
}

// 시스템콜호출 -> 시스템콜디스패처(KiSystemCall64) -> ETW 로깅(PerfInfoLogSysCallEntry)-> GetCpuClock ->
// InfinityHook 라이브러리(IfhpInternalGetCpuClock) -> SyscallStub
void __fastcall SyscallStub(_In_ unsigned int SystemCallIndex, _Inout_ void** SystemCallFunction) {

    // 원하는 시스템콜(예: NtCreateFile)이 호출되면
    if (*SystemCallFunction == OriginalNtCreateFile) {
        *SystemCallFunction = DetourNtCreateFile;   // 후킹함수로 교체
    }
}

// 후킹 함수
NTSTATUS DetourNtCreateFile(
	_Out_ PHANDLE FileHandle,
	_In_ ACCESS_MASK DesiredAccess,
	_In_ POBJECT_ATTRIBUTES ObjectAttributes,
	_Out_ PIO_STATUS_BLOCK IoStatusBlock,
	_In_opt_ PLARGE_INTEGER AllocationSize,
	_In_ ULONG FileAttributes,
	_In_ ULONG ShareAccess,
	_In_ ULONG CreateDisposition,
	_In_ ULONG CreateOptions,
	_In_reads_bytes_opt_(EaLength) PVOID EaBuffer,
	_In_ ULONG EaLength)
{
    /*
        여기서 원하는 작업 수행
    */

    // 원본 함수 호출
    return OriginalNtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, AllocationSize, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength);
}

InfinityHook공식 홈페이지에서 다운로드 받습니다.

WDK 위치: https://docs.microsoft.com/ko-kr/windows-hardware/drivers/download-the-wdk

후킹방식

InfinityHook의 후킹 방식을 좀 살펴볼까요? 후킹은 ETW(Event Tracing For Windows)와 관련이 있습니다. ETW는 윈도우즈 시스템에서 발생하는 이벤트를 수집해주는 기능으로, 이벤트 추적을 위해 시스템 전역에 이벤트 수집 코드를 정적으로 삽입해 놓았습니다.

ETW는 시스템콜 호출 이벤트 역시 수집할 수 있습니다. 이를 위해 모든 시스템콜의 커널 진입점이 되는 시스템콜 디스패처(KiSystemCall64)에다 이벤트 수집 함수(PerfInfoLogSysCallEntry)를 아래 그림처럼 삽입해 놓습니다.

[출처: InfinityHook github]

PerfInfoLogSysCallEntry 내부에선 _WMI_LOGGER_CONTEXT 구조체의 GetCpuClock 함수포인터를 호출하는데, InfinityHook은 바로 이 함수 포인터를 덮어쓰는 방식으로 제어권을 가로챕니다. 결국 모든 시스템콜이 호출될 때마다 InfinityHook이 제어권을 가로챌 수 있도록 한 것이죠. PatchGuard는 정적으로 생성된 주요 시스템 테이블이나 코드 영역에 대해서 Coarse-grained하게 변조 행위를 감시할 수 있지만, 동적으로 생성되는 이런 종류의 데이터스트럭쳐들 하나하나를 Fine-grained하게 보호하지는 못하는 것으로 보입니다. 성능 문제 때문이겠지요.

제어권을 가로챈 후 InfinityHook이 하는 일은, 커널스레드 스택을 조사해서 원본 시스템콜핸들러 주소가 저장된 스택위치(그림에서 보면 첫번째 빨간색 화살표)를 찾아낸 후 해당 스택의 값을 후킹함수의 주소로 덮어씁니다. 이후 call r10(세번째 빨간색)이 실행되면, 원본시스템콜핸들러 대신 후킹함수가 최종적으로 호출되도록 구현되어 있습니다.

InfinityHook의 단점은 시스템콜이 불릴 때마다 후킹을 위해 스택 조사 작업(Stack Walking)을 동반하기 때문에 예전 후킹방식에 비해 시스템 성능을 떨어뜨릴 수 있다는 점입니다.

감히(?) 단점을 언급하였지만, 커널 후킹 기술을 공개하였다는 사실만으로도 리서쳐들에겐 감사한 일인 것은 분명합니다.

참고자료

Vulnerability

Dell LPE 취약점(CVE-2019-3735)

Bill Demirkapi라는 17살의 보안 연구원이 윈도우즈가 설치된 Dell PC에서의 권한상승 취약점에 대한 내용을 지난 6월말에 캐나다에서 개최된 Recon 컨퍼런스와 본인의 블로그를 통해 공개했습니다.

[출처: d4stiny.github.io]

Dell PC에는 SupportAssist라는 프로그램이 미리 설치가 되어 있습니다. SupportAssist는 주기적으로 시스템의 상태를 체크하고 사용자에게 업데이트 알람도 주며 버전에 맞는 드라이버도 자동으로 설치해주는 프로그램입니다.

Bill은 바로 이 프로그램에서 권한상승 취약점을 발견했습니다. Bill은 지난 5월에 동일한 프로그램에서 RCE 취약점을 발견하여 공개하기도 했습니다.

취약점

SupportAssistAgent.exe는 시스템 서비스로 실행되며, 자식 프로세스로 SupportAssistAppWire.exe를 실행시킵니다.

SupportAssistAgentProcess

[출처: d4stiny.github.io]

권한상승 취약점의 원인은 자식 프로세스가 생성될 때 상속받은 부모프로세스(시스템프로세스)의 스레드 핸들에 모든 권한(full control)이 주어졌다는 데 있습니다.

thread_handle_properties

[출처: d4stiny.github.io]

익스플로잇 절차

시스템 프로세스의 스레드에 대한 모든 권한(full control)을 가진 취약점을 악용하여 다음과 같은 절차를 통해 최종적으로 권한상승 할 수 있습니다.

  1. 자식 프로세스(SupportAssistAppWire.exe)의 실행을 감시합니다.
  2. 자식 프로세스가 실행되면, Dll Injection을 통해 자식 프로세스에 코드를 삽입합니다.
  3. 삽입된 코드는 상속받은 부모프로세스(SupportAssistAgent.exe)의 스레드 핸들을 찾습니다.
  4. 부모프로세스의 스레드 핸들을 이용해, SetThreadContext를 호출하여 부모프로세스의 스레드 실행 컨텍스트를 다음과 같이 변경합니다.
    • RIP값을 NtTestAlert로 변경
    • 해당 스레드는 현재 Wait 상태라 RIP값을 바꾼다고 바로 실행되지는 않음
  5. 부모프로세스의 스레드 핸들을 이용해, LoadLibrary를 엔트리로 하는 APC를 등록합니다.
  6. 부모프로세스에 시그널을 보내 스레드의 Wait 상태를 깨웁니다.
    • 자식 프로세스가 시그널을 보낼 수 있었던 것은 부모프로세스가 Interactive 그룹에 속해 있어, 자식 프로세스도 시그널을 보낼 수 있는 권한이 있었기 때문입니다.
  7. 스레드가 깨어나면 NtTestAlert이 실행되고, NtTestAlert는 큐에 등록되어 있던 APC를 꺼내 실행합니다.
  8. APC로 LoadLibrary가 실행되어 페이로드 DLL이 최종적으로 부모프로세스상에서 실행됩니다.
    • Bill은 부모프로세스의 sqlite3.dll이 사용하는 파일 경로 중에서 사용자가 쓸 수 있는 경로를 찾아냈고, 해당 경로를 페이로드 DLL로 덮어씁니다.