1. 리눅스 커널 분석 환경 세팅
1.1. 리눅스 커널 & 파일 시스템 빌드
리눅스 커널 빌드
리눅스 커널 빌드 과정
- 빌드에 필요한 패키지 설치
- 리눅스 커널 소스 다운로드
- make defconfig
- make menuconfig
- 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나 부트로더로 부팅할 때 사용
파일 시스템 빌드
파일 시스템 빌드 과정
- busybox 다운로드
- make menuconfig
- target 디렉토리에 make 진행
- target 디렉토리에 각종 디렉토리 세트 생성 및 init 스크립트 작성
- 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 -fchmod 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 페이로드에 추가하는 것으로 우회 가능- 해당 보호 기법의 원리는 복잡하나, 우회 방법 자체는 간단한 편