1. 리눅스 커널 분석 환경 세팅

1.1. 리눅스 커널 & 파일 시스템 빌드

리눅스 커널 빌드

리눅스 커널 빌드 과정

  1. 빌드에 필요한 패키지 설치
  2. 리눅스 커널 소스 다운로드
  3. make defconfig
  4. make menuconfig
  5. make -j N

apt-get install 명령을 통해 패키지 설치

  • build-essential
  • libncurses5
  • libncurses5-dev
  • bin86
  • kernel-package
  • libssl-dev
  • bison
  • flex
  • libelf-dev

리눅스 커널 소스 다운로드

리눅스 커널 빌드

  • 커널 소스 파일을 다운받은 후, 압축을 해제하면 소스 파일들을 얻을 수 있음
  • make defconfig 명령을 통해 .config를 현재 아키텍쳐의 default 옵션으로 설정
  • make menuconfig명령을 통해 메뉴 기반 설정 화면으로 커널 옵션을 설정
    • 설정한 옵션들은 .config 파일에 추가됨
  • make -j N명령을 통해 커널 이미지 빌드 시작
    • -j 옵션을 추가하여 빌드 속도를 최적화
  • 커널 빌드가 끝나면 vmlinux, bzImage 두 개의 커널 이미지가 생성됨
    • vmlinux: 디버깅에 유용한 정보들이 살아 있는 이미지
    • bzImage: x86에서 부팅 가능한 압축 커널 이미지. 보통 QEMU나 부트로더로 부팅할 때 사용

파일 시스템 빌드

파일 시스템 빌드 과정

  1. busybox 다운로드
  2. make menuconfig
  3. target 디렉토리에 make 진행
  4. target 디렉토리에 각종 디렉토리 세트 생성 및 init 스크립트 작성
  5. cpio 확장자로 압축

busybox 다운로드

파일 시스템 빌드

  • busybox를 다운받은 후, 압축을 해제하면 소스 파일들을 얻을 수 있음
  • make menuconfig명령을 통해 메뉴 기반 설정 화면으로 커널 옵션을 설정
    • Settings -> Build Options -> Build static binary 옵션 체크
    • Networking Utilities -> inetd 옵션 해제
  • mkdir ../target 명령으로 빌드 결과물이 저장될 target 디렉토리를 생성
  • make CONFIG_PREFIX=../target install 명령을 통해 파일 시스템 빌드를 진행
  • mkdir dev etc lib proc tmp sys 명령을 통해 최소한의 디렉토리 세트를 생성
  • target 디렉토리에 init 스크립트 작성
    • init 스크립트가 커널이 부팅되고 가장 먼저 실행되는 1번 프로세스
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
 
setsid cttyhack setuidgid 1000 sh
 
umount /proc
umount /sys
poweroff -d 0 -f
  • chmod 755 init 명령을 통해 init 스크립트 권한을 수정
  • find . | cpio -o --format=newc > rootfs.cpio 명령을 통해 rootfs.cpio 파일 생성
    • rootfs.cpio 파일이 지금까지 빌드한 루트 파일 시스템

1.2. qemu 세팅

qemu 설치

  • ubuntu 18.04의 경우 기본적으로 qemu가 설치되어 있으며, qemu-kvm만 추가적으로 설치해주면 됨

qemu 세팅

  • -m 512M : 주 메모리 512mb 할당
  • -kernel ./bzImage : bzImage를 커널 이미지로 사용
  • -initrd ./rootfs.cpio : rootfs.cpio를 파일 시스템으로 사용
  • -append “nokaslr” : KASLR 보호기법 미적용
  • -cpu smep : SMEP 보호기법 적용
  • -s : gdb attach를 위한 1234번 포트를 오픈

1.3. CTF 환경 세팅

리얼 월드 세팅과의 차이점

  • 커널 이미지와 파일 시스템을 제공해주기 때문에 따로 빌드할 필요가 없음
  • 보통 bzImage는 제공해 주지만, 디버깅에 유용한 vmlinux는 제공해주지 않음
  • extract-vmlinux 스크립트를 이용해 bzImage에서 vmlinux를 추출해야함

CTF 환경 세팅

  • /usr/src/linux-headers-$(uname-r)/scripts/extract-vmlinux bzImage > vmlinux 명령을 통해 bzImage에서 vmlinux를 추출할 수 있음
    • 이렇게 생성된 vmlinux 이미지는 정상적으로 빌드된 vmlinux와 달리 심볼들이 stripped 된 상태임
    • 추출한 vmlinux는 심볼은 없지만, kenel ROP를 위한 각종 가젯들의 주소는 구할 수 있음
  • 익스플로잇 코드를 파일 시스템에 집어 넣는 과정이 필요
    • 사용할 익스플로잇 코드를 -static 옵션으로 컴파일
    • mkdir rootfs 명령어로 루트 파일 시스템을 압축 해제할 디렉토리를 생성
    • rootfs.cpio를 rootfs 디렉토리로 이동
    • rootfs 디렉토리에서 cpio -d -v < rootfs.cpio 명령을 통해 cpio 압축을 해제
    • 기존의 rootfs.cpio 삭제 후, 컴파일한 익스플로잇 바이너리를 rootfs 디렉토리로 이동
    • find . | cpio -o --format=newc > rootfs.cpio 명령을 통해 파일 시스템 재압축

2. 리눅스 커널 보호 기법 및 우회 방법

2.1. 리눅스 커널 보호 기법

리눅스 커널 보호 기법

  • KASLR : 커널의 메모리 주소를 랜덤화
  • SMEP : 커널 공간에서 유저 공간의 실행 권한을 제거
  • SMAP : 커널 공간에서 유저 공간의 읽기/쓰기 권한 제거
  • KADR : 로컬 유저가 커널의 심볼을 볼 수 없도록 제한
  • SSP : 유저 공간의 카나리와 동일
  • KPTI : 커널 공간과 유저 공간의 전환이 일어날 때, 각각의 페이지 테이블을 사용

보호 기법 체크

  • checksec —kernel=.config
  • /proc/cpuinfo와 /etc/default/grub 확인
  • qemu script 확인

2.2. KASLR 우회

KASLR

  • 커널이 무작위 주소에 매핑되도록 만들어, 커널 익스플로잇을 어렵게 하는 보호 기법

commit_creds(prepare_kernel_cred(0))

  • prepare_kernel_cred(0)은 root credentials를 준비하는 역할
  • commit_creds()는 현재 태스크의 credentials를 인자로 받은 credentials로 갱신하는 역할
  • 즉, 현재 태스크의 권한을 root로 바꾸는 코드

우회 방법

  • leak 취약점을 이용해 커널 코드 주소를 leak
  • leak한 주소와 offset을 기반으로 익스플로잇에 사용할 커널 함수들의 주소를 계산
  • 계산한 커널 함수 주소를 이용해 익스플로잇 코드 작성

2.3. SMEP 우회

SMEP

  • 커널 공간에서 유저 공간에 대한 실행 권한을 제한하는 보호 기법
  • 즉, ring 0 권한일 때 ring 3와 관련된 코드를 실행할 수 없음
  • 유저 공간의 NX bit 보호 기법과 유사
  • cr4 레지스터에 SMEP를 on/off 하는 제어 비트가 있음

우회 방법

  • kernel ROP를 이용해 우회
  • ROP 페이로드로 commit_creds(prepare_kernel_cred(0)) 코드를 구현
  • 유저 공간에서 가젯을 구하듯이 objdump, rp++ 등을 이용해 vmlinux 이미지에서 커널 가젯을 구하면 됨

2.4. SMAP

SMAP

  • 커널 공간에서 유저 공간에 대한 읽기/쓰기 권한도 제한하는 보호 기법
  • 유저 공간으로의 액세스 자체를 제한
  • SMEP와 마찬가지로 cr4 레지스터에 SMAP를 on/off 하는 제어 비트가 있음
  • 커널 ROP만으로 우회할 수 있는 SMEP와 달리 우회 방법이 다양하며 다소 복잡한 편

우회 방법

  • Stack based BOF가 발생하는 경우, 단순히 커널 ROP만 작성해도 우회 가능
  • 원하는 slab 객체 크기의 UAF control이 가능할 경우, 유저 공간에서 fork()를 호출 시 커널 힙에 할당되는 cred 구조체의 멤버를 수정할 수 있으며, 이를 통해 SMEP와 SMAP 우회 가능
  • 만약 커널에 kernel heap pointer(slab pointer)를 릭 할 수 있는 취약점이 있다면, kernel heap spray 기법과 연계해서 수 많은 공격 시나리오를 짤 수 있음

2.5. KADR

KADR

  • 커널 내부 정보와 관련된 파일들을 root 권한으로만 확인할 수 있도록 하는 보호 기법
  • 보통 우회하지는 않고 로컬 환경에서 root 권한으로 변경한 후 심볼들의 주소를 확인하여 디버깅에 참고함

root 권한으로 실행 방법

  • mkdir rootfs : 압축된 파일 시스템을 해제할 디렉토리 생성
  • cp rootfs.cpio rootfs : 파일 시스템을 rootfs 디렉토리로 복사
  • cd rootfs : rootfs 디렉토리로 이동
  • cpio -id -v < rootfs.cpio : rootfs.cpio을 압축 해제
  • 압축 해제하고 나온 파일 중 init의 setuidgid 1000 부분을 setuidguid 0으로 변경
  • rm rootfs.cpio : 복사했던 rootfs.cpio 파일 삭제
  • find . | cpio -o —format=newc > ../rootfs.cpio : cpio 명령으로 현재 디렉토리의 파일들을 상위 디렉토리에 rootfs.cpio로 압축
  • 변경된 root 권한으로 /proc/kallsyms를 확인하면 000000… 대신 커널 주소가 출력됨

2.6. SSP 우회

SSP

  • make 과정에서 사용되는 gcc 옵션으로써, stack based bof를 방지하는 보호 기법
  • 스택에 랜덤한 카나리 값을 배치 후, 해당 카타리가 오염될 경우 오버플로우가 발생한 것으로 판단하여 커널 패닉 발생
  • gcc 옵션으로써 자동으로 활성화 되기 때문에 유저 공간의 SSP와 동일한 보호 기법

우회 방법

  • OOB 등의 릭 취약점을 통해 카나리를 릭하면 됨
  • 실제로 kernel stack based bof 취약점은 비교적 드물기 때문에 실제로 우회할 일은 적은 편

2.7. KPTI 우회

KPTI

  • 커널 공간과 유저 공간의 전환이 일어날 때, 각각의 페이지 테이블을 사용하여 유저 공간 페이지 테이블에 최소한의 커널 주소만 포함하도록 하는 보호 기법
  • Meldown 취약점에 대한 보안 패치
  • 커널 공간 -> 유저 공간으로 전환할 때 호출되는 어셈블리 함수를 이용해서 우회할 수 있음

swapgs_restore_regs_and_return_to_usermode

  • 커널 공간 -> 유저 공간으로 전환될 때 호출되는 어셈블리 함수
  • 페이지 테이블을 분리하는 역할
  • 해당 함수의 주소는 KADR 우회 방식과 동일하게 구함

우회 방법

  • 커널 공간 -> 유저 공간으로 전환될 때 호출되는 swapgs_restore_regs_and_return_to_usermode+22 어셈블리 함수를 ROP 페이로드에 추가하는 것으로 우회 가능
  • 해당 보호 기법의 원리는 복잡하나, 우회 방법 자체는 간단한 편