Kernel Mitigations

이동화·2025년 7월 27일

Introduction

커널에 존재하는 보호 기법은 /proc/cmdline/proc/cpuinfo 파일을 확인하는 것으로 알 수 있다. 커널이 부팅될 때 전달된 매개 변수 정보가 /proc/cmdline에 저장이 되는데, nokaslr 명령이 없다면 KASLR이 적용되어 있는 것이고, /proc/cpuinfo 파일에 smep smap pti 등의 플래그가 설정되어 있다면 각각 SMEP, SMAP, KPTI가 적용되어 있는 상태이다.

간단하게 커널의 보호 기법을 정리하면 다음과 같이 정리할 수 있다.

  • Kernel Address Space Layout Randomization (KASLR)
    가상 메모리 상의 커널 영역인 text, vmalloc 등의 위치를 무작위로 변경하여 공격자가 커널 영역의 주소를 예측하고 활용하는 것을 어렵게 만든다.
  • Supervisor Mode Execution Prevention (SMEP)
    kernel mode (ring 0)일 때 user mode (ring 3)에서 할당된 페이지의 코드가 실행되는 것을 막는 보호 기법이다. (NX와 비슷)
  • Supervisor Mode Access Prevention (SMAP)
    kernel mode일 때 user mode에서 할당된 메모리에 대한 읽기와 쓰기를 막는 보호 기법으로, SMEP을 강화한 보호 기법이다.
  • SSP (Stack Smashing Protector)
    Stack BOF 공격을 방지하기 위해 사용되는 보호 기법으로, canary 기법을 통해 memory corruption을 탐지한다. 커널에 기본적으로 활성화된 이 기법은 컴파일러가 제공하는 기능으로 gdb를 통한 커널 디버깅을 통해 canary 값을 설정하고 검사하는지 확인하면 적용 여부를 알 수 있다.
  • Kernel Page Table Isolation (KPTI)
    Meltdown 취약점에 대응하기 위한 보호 기법으로, 커널 영역과 유저 영역의 페이지 테이블을 분리한다.

kernel exploit의 최종 목표는, 두 가지 레이어를 차례대로 달상하는 것이다. Ring 0 코드 실행 등 커널 모드 탈취를 통해 커널 주소 공간을 임의로 읽고 쓸 수 있는 상태로 바꾸고, user mode로 복귀했을 때 프로세스가 root가 되도록 root credential을 탈취하는 것이 목적이다. 오직 user mode (ring 3) 내에서 setuid 프로그램 등을 활용하여 권한 상승 및 root 프로세스 생성을 노리는 system("/bin/sh")와는 다르다.

prepare_kernel_cred()

struct cred *prepare_kernel_cred(struct task_struct *daemon)

해당 함수는 공격자가 root 권한을 탈취하할 때 활용하는 함수이다. 해당 함수는 프로세스의 credential 정보를 한데 모아 관리하는 구조체인 cred를 return한다.

prepare_kernel_cred() 함수는 커널 서비스 프로세스(daemon)나 권한 상승용 context를 위해 새로운 cred 객체를 생성하거나 초기화하는데 사용되는 함수로, 인자로 전달된 daemon task의 security 정보들을 기반으로 새 cred를 생성하고, daemon이 NULL 또는 &init_cred일 경우 root 권한으로 설정된 완전한 권한의 cred를 반환한다. 현재 프로세스의 credential을 반환하거나 변경하지는 않는다. 즉 커널 모듈이나 system call 내부에서 안전하게 root 권한을 획득하는데 사용하는 함수이다. (최신 kernel에서는 NULL을 넘길 수 없음)

인자로 사용된 daemon은 새로 생성할 cred 객체의 기준이 되는 프로세스로, 관용적으로 쓰이는 daemon과는 뜻이 다르다. 관용적으로 Linux에서의 daemon은 백그라운드에서 동작하는 서비스 프로세스를 의미하는데, 유저가 사용하는 터미널과는 완전히 분리된 상태에서 상시 대기 상태를 유지하여 네트워크 요청, 예약, logging 작업 등 외부 이벤트 요청이 들어오면 대응하는 형태로 동작한다. thread daemon들은 이름 끝에 d를 붙이고, kernel 내부의 daemon들은 이름 앞에 k를 붙이기 때문에 구분할 수 있다. (sshd, kswapd 등)

따라서 prepare_kernel_cred()는 처음에는 kernel daemon의 credential을 복사하는 함수였으나, 나중에는 임의의 프로세스도 인자로 넣을 수 있게 되어 범용적인 task의 credential을 복사하는 함수로 확장되었다.

해당 함수가 가지는 취약점은, 인자로 들어오는 daemon이 &init_task일 경우 root 권한을 가지는 credential을 반환받을 수 있어, 현재 task의 권한을 바꿀 수 있는 함수인 commit_creds() 함수를 활용하여 현재 task 권한을 root 권한으로 상승시킬 수 있게 된다. 즉 임의 코드를 실행시킬 수 있는 상황에서 commit_creds() 함수와 prepare_kernel_cred() 함수의 주소, init_task의 주소를 가지고 있다면 commit_creds(prepare_kernel_cred(&init_task)) 호출을 통해 권한 상승을 노릴 수 있다.

Mitigations

KASLR

커널 메모리 주소의 예측을 어렵게 하기 위한 기법으로, win, iOS, linux 등 상당수의 OS에서 사용중이다. 커널 부팅 과정에서 인자로 nokaslr 명령을 넣어주면 된다. KASLR이 적용되면 부팅 시 커널 코드 및 데이터가 임의 주소에 적재된다.

ASLR과 동작 방식이 같은 만큼 한 번 부팅되면 커널이 재부팅 되기 전에는 base_addr이 변하지 않는다. 커널의 base 주소의 크기도 8~9bit라서 bruteforce 공격에도 취약하다.

SMEP

커널 모드일 때 유저 모드에서 할당된 페이지의 코드가 실행되는 것을 막는 기법으로, CPU의 흐름이 커널 모드와 유저 모드 사이에서 전환될 때 특정한 규칙을 따르도록 한다.

커널 모드에서 유저 모드 주소 공간의 코드를 실행하려고 하면 page fault가 발생하여 실행을 중지한다. CPU의 cr4 레지스터에서 SMEP 관련 bit를 설정하는 것으로 SMEP을 활성화 할 수 있다.

커널이 중지되면 kernel panic이 발생하게 되며, 유저 영역의 소프트웨어에서 crash가 일어나는 것과 똑같다. windows에서 뜨는 블루스크린도 커널 패닉을 현상이다.

SMAP

커널 모드에서 유저 모드에 할당된 메모리에 대한 읽기와 쓰기를 막는 보호기법이다. SMEP과 똑같이 cr4 레지스터에서 SMAP 관련 bit를 설정하는 것으로 활성화가 가능하다.

유저 영역에 대한 주소 접근, 읽기 등의 작업이 발생되면 page fault를 발생시킨다.

SMEP/SMAP이 적용되지 않은 경우, 위에서 kernel mode 진입 후 prepare_kernel_cred() 함수를 통해 쉽게 root credential을 획득할 수 있다.

SSP

커널의 BOF 취약점을 방지하기 위한 보호 기법으로, 커널 스택에 canary 값을 추가하여 버퍼 오버플로우를 방지한다. SSP가 트리거되면 kernel panic이 발생한다.
이를 우회하기 위해서는 canary 값을 leak하고 payload에 추가해야 한다.

KPTI

https://velog.io/@marchen/Meltdown

Meltdown 공격의 영향력을 완화하는 보호 기법으로, user mode의 page table과 kernel mode의 page table을 완전히 분리하는 방식으로 작동한다.

KPTI가 활성화되면 cache, ROB 등에서 커널 주소와의 매핑 관계가 모두 삭제된다. kernel mode와 user mode 간의 전환에서 page table 또한 전환되어야 하며, kernel mode의 page table은 모든 주소 공간을 포괄하지만 user mode의 page table은 사용자가 사용할 주소 공간 및 kernel mode 전환에 필요한 최소한의 영역만을 포함하도록 설정하는 방법으로 적용된다.

user mode만의 page table을 trampoline이라고 하며, user mode에서는 trampoline을 제외한 주소 공간이 가상 메모리에 존재하지 않게 되기 때문에 meltdown 공격을 수행해도 커널 메모리에는 접근 자체를 할 수 없게 된다.

반대로 kernel mode에서는 모든 주소 공간을 포함하고, user mode의 page table에 대해 접근 권한을 따로 지정할 수 있게 된다. Linux에서는 이를 활용하여 trampoline 전체를 NX로 지정하여 커널 모드에서 유저 page table의 코드를 실행할 수 없게 한다. 이를 우회하려면 현재 가리키는 page table을 user page table을 가리키도록 바꾸어 커널 권한은 유지한 채로 권한 상승 작업을 하도록 하는 것이다. 이는 CR3을 복구하는 gadget을 추가적으로 chain에 넣어주어야 한다.

Kernel ROP

커널 영역에 위치한 가젯들을 활용하면 유저 모드에서 NX를 우회했을 때 처럼 ROP 기법을 통해 프로세스 권한 변경이 가능하다. pwndbg의 ROPgadget 도구를 이용하면 vmlinux image의 크기가 크고 python으로 작성되었기 때문에 가젯을 추출하는데 시간이 오래 걸린다. 따라서 cpp로 작성된 rp++를 사용하여 빠르게 가젯을 추출하면 좋다.

또한 chaining에 필요한 명령어로 swapgsiretq가 추가로 필요하다.

  • swapgs : GS segment register에는 base_addr을 가리키는 부분이 있는데, user mode의 base_addr과 kernel mode의 base_addr을 swap해주는 명령어이다. syscall이나 interrupt 호출 등에 의해 kernel mode에 진입한 직후 GS register가 kernel mode의 perCPU를 가리키고 복귀할 때 user mode TLS를 빠르게 복원한다. kernel mode에서만 유효한 명령어이다.

  • iretq : interrupt return을 빠르게 하기 위해 stack 위에 trap frame을 쌓아, 한꺼번에 pop하면서 CPU 상태를 빠르게 복원하는 명령어이다. frame은 위에서부터 RIP-CS-RFLAGS-RSP-SS 레지스터 값으로 이루어진다 (pop 순서랑 같음). 원래는 안전하게 kernel mode에서 user mode로 전환할 때 사용하는 명령어이지만, 공격자가 kernel mode에서 프로세스에 root 권한을 부여한 후 chaining으로 iretq을 호출하여 안전하게 user mode로 복귀할 때 사용한다.

ROP payload는 SMEP만 적용되어 있는 경우와 SMAP까지 적용된 경우 다르게 구성된다. SMEP만 적용되어 있는 경우, 유저 영역에 대한 read/write는 막지 않기 때문에 유저 영역 페이지를 커널의 스택으로 사용할 수 있다. 즉 커널 모드로 진입했을 때 CPU가 커널 스택이 아닌 공격자가 구성한 가짜 스택을 가리키도록 rsp를 변경하는 Kernel Stack Pivoting이 가능하다.

Kernel stack pivoting은 크기가 작은 커널 스택에 ROP chaining을 쌓지 않고도 유저 영역에서 제한 없이 ROP chain을 배치하고 실행할 수 있게 한다. 사용자 공간에 mmap 함수 등으로 rwx가 가능한 버퍼를 할당하고, mov rsp <addr>을 통해 stack pointer를 전환, 이후 이어서 prepare_kernel_cred()commit_creds의 인자들을 넣어 커널에서의 ret 이후 가짜 스택을 참조하여 함수를 호출하도록 가젯들을 넣어주면 된다. 마지막으로 swapgs, iretq를 chaining하여 정상적으로 user mode로 복귀하도록 할 수 있다.

주의할 점은 chain 중간에 add byte ptr [rcx], bh 와 같이 함수 인자 전달과 무관한 레지스터를 사용하는 가젯이 존재할 수 있어, rcx와 같은 레지스터를 커널 메모리 내의 안전한 주소로 세팅해두면 오류나 충돌 발생 없이 안전한 chain을 구성할 수 있다. NULL같은게 들어 있다가는 오류가 발생할 수 있기에 레지스터에 넣을 주소는nfsd_debug, __this_cpu_ptr()와 같이 쓰기 작업이 가능하고 시스템에 별다른 영향을 주지 않는 심볼이 적합하다.

SMAP이 적용된 경우에는, 커널 영역에 직접 ROP payload를 넣어야 한다. 커널 내의 BOF 취약점을 통한 커널 영역으로의 pivoting이 필요하다.

profile
notion이 나은듯

0개의 댓글