최근 많은 연구들이 Windows 운영체제에 초점이 맞추어져 있지만, 보안적인 측면에서 아이폰과 맥북에 탑재되는 iOS와 macOS를 빼놓을 수 없다. 이번 블로그 시리즈를 통해서 OS X 커널에서의 버그 분석 및 익스플로잇 기법 등을 알아본다.
(1) 커널 버그 찾기
BSD, Mach와 IOKit 등 과 같은 커널 레벨에서 실행 가능한 버그를 퍼징, 소스코드 오디팅과 같은 방법을 통해 발견함.
BSD : 커널의 BSD 부분은 대부분의 시스템 호출, 네트워킹 및 파일 시스템 기능을 제공함. FreeBSD 5에서 가져온 소스.
Mach : Carnegie Mellon University에서 개발 된 Mach 3.0 마이크로 커널에서 파생되어짐. 메모리 맵 및 IPC 와 같은 기본 서비스를 구현함. 사용자 공간 프로그램은 마하 트랩을 통해 마하 서비스에 액세스 가능.
IOKit : IOKit은 XNU용 드라이버를 작성하는 C++로 작성된 프레임워크이며, Apple은 libkern이라는 자체 런타임 시스템을 제공함.
(2) Exploit Primitive(s)
Arbitrary Read / Write를 이용해 취약점을 공략하는데에 있어서 필요한 데이터를 획득 하거나, 커널 영역에 임의의 데이터를 작성함.
이는 버그 별로 상이한 데이터이기 때문에, Read / Write 로 공격시 이용할 수 있는 데이터를 사용해야함.
(3) 커널 권한 획득 & AAR / AAW in Kernel
kernel_task(pid=0)의 권한을 위해 필요한 값 ipc object와 kernel task를 획득하기 위해 커널에 존재하는 모든 프로세스를 트레버싱 한 후 유저 영역에 데이터를 덤프 한다.
이를 이용해, 커널 레벨에서 Read / Write를 할 수 있다.
(4) 루트 권한 획득
각각의 프로세스가 커널 메모리에 적재되어 있기 때문에, 타겟 프로세스를 잡아서 아래의 프로세스 권한 구조체의 CR_RUID(Credential Real UID)를 0으로 변경 [bsd/sys/ucred.h]
/*
* In-kernel credential structure.
*
* Note that this structure should not be used outside the kernel, nor should
* it or copies of it be exported outside.
*/
struct ucred {
TAILQ_ENTRY(ucred) cr_link; /* never modify this without KAUTH_CRED_HASH_LOCK */
u_long cr_ref; /* reference count */
struct posix_cred {
/*
* The credential hash depends on everything from this point on
* (see kauth_cred_get_hashkey)
*/
uid_t cr_uid; /* effective user id */
uid_t cr_ruid; /* real user id */
uid_t cr_svuid; /* saved user id */
short cr_ngroups; /* number of groups in advisory list */
gid_t cr_groups[NGROUPS]; /* advisory group list */
gid_t cr_rgid; /* real group id */
gid_t cr_svgid; /* saved group id */
uid_t cr_gmuid; /* UID for group membership purposes */
int cr_flags; /* flags on credential */
} cr_posix;
struct label *cr_label; /* MAC label */
/*
* NOTE: If anything else (besides the flags)
* added after the label, you must change
* kauth_cred_find().
*/
struct au_session cr_audit; /* user auditing data */
};
변경이 완료되면, 해당 프로세스는 root 권한으로 돌아가는 상태가 되어짐.
따라서, system(“/bin/bash”); 를 실행해 준다면 루트 권한의 쉘을 획득할 수 있음. (Local Privilege Escalation)
배경 지식
Kernel Zone
OS X 의 커널에서는 힙이 할당되는 Zone이라는 구조를 사용하고 있음.
Zone은 zalloc(zone), kalloc(size)를 이용해 할당되어지며, zfree(zone, ptr), kfree(ptr,size)를 이용해 해제 되어짐.
kalloc 호출시, zalloc이 내부 호출 됨. 또한 kalloc zone은 sudo zprint kalloc 를 통해 확인 가능함.
zone metadata : 페이지 첫번째에 들어가있는 zone의 정보로 size, page_count, alloc_element, free_element 등을 가지고 있다.
OOL(Out-Of-Line) Port
IPC 통신에서 발생하는 인라인이 아닌 패킷을 적재
OOL 데이터를 수신하기 전까지 커널에서 보존되어짐.
주로 OS X Kernel Exploit을 할 때, fakeport를 만들기 위해 OOL을 Leak 시킴으로써 공격을 함
따라서, OOL Leak을 완화시키기 위한 보안 패치가 자주 이루어짐.
OS X Kernel Mitigation
kASLR
부팅에 따른 커널 메모리 주소 랜덤화
Kext(Kernel Extension)와 커널은 같은 슬라이드를 공유함
우회 : kslide 주소 계산 (kslide = kernel_base - kernel text base)
kslide : 바이너리 상의 주소와 실제 주소의 차이
DEP
커널에서의 RWX 권한 방지
우회 : ROP
SMEP / SMAP
인텔 CPU에서 제공하는 커널 메모리 보호 기법
SMEP(Supervisor Mode Execution Protection) : 유저 영역 주소 공간에서 커널 코드 실행 불가
SMAP(Supervisor Mode Access Protection) : 유저 영역 주소 공간에서 메모리 액세스를 허용하지 않음. 단, 지원되는 CPU 아키텍처에서만 사용 가능
우회 : 커널에서 우회 가능한 ROP 시행
vm_map_copy() 변화
OS X 10.11 El Capitan 이전의 커널 익스플로잇은, 오버플로우 취약점을 이용해 vm_map_copy의 kdata 포인터 및 size를 공략하여 임의 데이터 읽기를 시도했으나, 이는 구조가 변경됨으로써 보완 되었음. (osfmk/vm/vm_map.h)
// 변경 전
struct vm_map_copy{
int type;
#define VM_MAP_COPY_ENTRY_LIST 1
#define VM_MAP_COPY_OBJECT 2
#define VMMAP_COPY_KERNEL_BUFFER 3
vm_object_offset_t offset;
vm_map_size_t size;
union {
struct vm_map_header hdr;
vm_object_t object;
struct { // <<= Before Change
void *kdata;
vm_size_t kalloc_size;
}c_k;
}c_u;
};
// 변경 후
struct vm_map_copy{
int type;
#define VM_MAP_COPY_ENTRY_LIST 1
#define VM_MAP_COPY_OBJECT 2
#define VM_MAP_COPY_KERNEL_BUFFER 3
vm_object_offset_t offset;
vm_map_size_t size;
union{
struct vm_map_header hdr;
vm_object_t object;
uint8_t kdata[0]; // <<= Changed
}
}
단, OS X 10.11 El capitan의 ipc_kmsg_copyout_ool_descriptor()에서 레이스 컨디션 기법을 이용해 우회한 사례가 있음. (현재 구조 패치 되었음.)
기타
iOS와 macOS의 구조 및 보안에 대한 패치가 상당히 비슷하므로, 보통 함께 공략 가능.
완벽한 오버플로우 버그가 아니라면 공격하기 까다로움.
많은 버그들이 리모트 공격으로부터 도달하기 어려워짐. (e.g., 사파리 취약점을 넘어서 커널 권한 획득 까지 가는데 있어서 도달 과정)
이전 버전의 OS X에서는 널 포인터에 데이터를 작성함으로써, 상대적으로 간단하게 LPE가 가능하였으나 (아래 소스 참조), 현재는 불가한 상태임.
void null_page(){
sync();
vm_address_t addr=0;
vm_deallocate(mach_task_self(),0x0,0x1000);
vm_allocate(mach_task_self(),&addr,0x1000,0);
uint64_t * np=0;
for(int i=1;i<0x100;i++)
{
np[i] = 0x4141414141414141;
}
}