소개
Kernel Exploit을 공부하다보니 커널 영역의 UAF나 Heap Overflow 같은 공격이 매우 유용하다고 느꼈다. 하지만 취약한 드라이버에 있는 기능 외에 커널이나 다른 드라이버가 제공하는 기능을 이용하는 경우가 많기 때문에 폭넓은 지식이 많지 않으면 Exploit Chain을 쉽게 짜기 힘들다고 생각한다. 예를 들어 kUAF는 매우 유용하지만, UAF에서 할당되는 크기가 고정된 경우 kmalloc 크기가 일치하고 사용 가능한 오브젝트를 찾아야 한다. 이번 글에서는 그런 쓸만한 오프젝트를 알아보고 공부해보려고 한다. 하지만 Kernel Exploit을 공부한지 얼마 되지 않았기 때문에 틀린 부분이나 놓친 부분이 있을 수도 있기 때문에 찾으면 댓글로 알려주시면 매우 감사하겠습니다…(_ _)
실험 환경과 작성한 코드는 아래 리포지토리에서 받았습니다.
리눅스 커널 버전은 Linux Kernel 4.19.98을 사용했습니다.
Leak/AAR/AAW/RIP 제어에 활용할 수 있는 구조체 목록
구조체는 정말 많지만, 우선 크기별로 사용할 수 있는 구조체를 최대한 정리해보려 한다.
shm_file_data
struct shm_file_data {
int id;
struct ipc_namespace *ns;
struct file *file;
const struct vm_operations_struct *vm_ops;
};Size : 0x20 (kmalloc-32)
Leakable
- ☑ base:
ns,vm_ops가 커널 데이터 영역을 가리키므로 커널 주소를 Leak 가능. - ☑ heap:
file이 힙 영역을 가리키므로 힙 주소를 Leak 가능. - ☒ stack: 스택 주소는 Leak 불가능.
RIP Control
- ☒ 불가능: vm_ops를 조작해도, shmget 등에서 가짜 vtable의 함수 포인터가 호출되는 동작은 확인되지 않는다.
할당 :shmat로 공유 메모리를 맵핑하면 shm_file_data 구조체가 할당된다.
해제 :shmctl 등의 함수로 공유 메모리를 제거하면 구조체가 해제될 가능성이 있다.
참고 코드: https://elixir.bootlin.com/linux/v4.19.98/source/ipc/shm.c#L74

seq_operations
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};Size:0x20 (kmalloc-32)
Leakable
- ☑ base : 4개의 함수 포인터 중 원하는 것을 사용하여 누출 가능.
- ☒ heap : 힙 영역 주소는 이 구조체를 통해 직접적으로 Leak 불가능.
- ☒ stack : 스택 주소는 이 구조체를 통해 직접적으로 Leak 불가능.
RIP Control
- ☑ 가능 : start 함수 포인터를 원하는 주소로 덮어쓴 후, read 시스템 호출을 트리거하면 RIP Control 가능.
할당 : single_open을 사용하는 파일을 열면 할당 된다(ex: /proc/self/stat).
해제 : close 하기
참고 코드 : https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/seq_file.h#L32

msg_msg (+user-supplied data)
struct msg_msg {
struct list_head m_list;
long m_type;
size_t m_ts; /* message text size */
struct msg_msgseg *next;
void *security;
/* the actual message follows immediately */
};Size : 0x31〜0x1000 (kmalloc-64)
Leakable
- ☒ base : 불가능
- ☑ heap : *next가 전에 msgsnd된 메시지를 가리키기 때문에 Leak 가능
- ☒ stack : 불가능
RIP Control
- ☒ 불가능
할당 : msgget → msgsnd
해제 : msgrcv
기타
- 동적 할당으로 임의의 데이터를 쓸 수 있어 편리하지만, 처음 48바이트는 구조체로 사용되기 때문에 다시 쓸 수 없으며, UAF에서 읽기만 가능한 경우, 힙의 주소를 유출한 후 힙에 데이터를 준비하는 등의 용도로 사용 가능함.
참고 코드 : https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/msg.h#L9

subprocess_info
struct subprocess_info {
struct work_struct work;
struct completion *complete;
const char *path;
char **argv;
char **envp;
struct file *file;
int wait;
int retval;
pid_t pid;
int (*init)(struct subprocess_info *info, struct cred *new);
void (*cleanup)(struct subprocess_info *info);
void *data;
} __randomize_layout;Size : 0x60 (kmalloc-128)
Leakable
- ☑ base :
work.func가call_usermodehelper_exec_work를 가리키고 있어서 가능 - ☑ heap : Leak이 가능은 하지만 어느 Slub의 데이터인지는 미검증
- ☒ stack : 불가능
RIP Control
- ☑ 가능 : race condition으로 할당 작업 중에
cleanup을 조작하면 control 가능
할당 : 확인된 것은 socket(22, AF_INET,0);으로 알 수 없는 프로토콜을 지정
해제 : 할당과 같은 방법으로 해제
참고 코드 : https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/umh.h#L19


cred
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
/* RCU deletion */
union {
int non_rcu; /* Can we skip RCU deletion? */
struct rcu_head rcu; /* RCU deletion hook */
};
} __randomize_layout;Size : 0xa8 (kmalloc-192)
Leakable
- ☒ base : 불가능
- ☑ heap :
session_keyring으로 유출 가능 - ☒ stack : 불가능
RIP Control
- ☒ 불가능
할당 : fork() 호출
해제 : 생성한 프로세스 종료
참고 코드 : https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/cred.h#L116
file
Size : ? (kmalloc-256)
Leakable
- ☑ base :
f_op이 커널의 데이터 영역을 가리키고 있기 때문에 leak 가능 - ☒ heap : 미검증
- ☒ stack : 미검증
RIP Control
- ☑☒
f_op을 다시 조작하고shmctl등을 호출하면 RIP 제어 가능. 하지만 UAF 후 file 구조체가 왜인지 overlap 되지 않아서 검증에는 실패.
할당 : shmget으로 공유 메모리 생성
해제 : shmctl로 삭제
참고 코드 : https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/fs.h#L891
timerfd_ctx
struct timerfd_ctx {
union {
struct hrtimer tmr;
struct alarm alarm;
} t;
ktime_t tintv;
ktime_t moffs;
wait_queue_head_t wqh;
u64 ticks;
int clockid;
short unsigned expired;
short unsigned settime_flags; /* to show in fdinfo */
struct rcu_head rcu;
struct list_head clist;
spinlock_t cancel_lock;
bool might_cancel;
};크기: ? (kmalloc-256) base : tmr.function이 timerfd_tmrproc를 가리키고 있어 유출 가능.heap: tmr.base 등에서 유출 가능.stack: 누출되지 않음.RIP: 걸릴 것 같고 안 걸릴 것 같다.확보: timerfd_create를 호출한다.해제: tfd를 close?참고 : https://elixir.bootlin.com/linux/v4.19.98/source/fs/timerfd.c#L30
Size : ? (kmalloc-256)
Leakable
- ☑ base :
tmr.function이timerfd_tmrproc을 가리치고 있어 leak 가능 - ☑ heap :
tmr.base에서 leak 가능 - ☒ stack : 불가능
RIP Control
- 미검증
할당 : timerfd_create를 호출
해제 : tfd를 close
참고 코드 : https://elixir.bootlin.com/linux/v4.19.98/source/fs/timerfd.c#L30

tty_struct
Size : 0x2e0 (kmalloc-1024)
Leakable
- ☑ base :
ops가ptm_unix98_ops를 가리켜서 leak 가능 - ☑ heap :
dev,driver등 많은 오브젝트가 힙이나 자신의 멤버를 가리켜서 leak 가능 - ☒ stack : 불가능
RIP Control
- ☑ 가능 :
ops를 다시 조작해서 Control rksmd
할당 : /dev/ptmx 열기
해제 : 열린 ptmx 닫기
참고 코드 : https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/tty.h#L283
0x0000: 0x0000000100005401
0x0008: 0x0000000000000000
0x0010: 0xffff88800f1ed840
0x0018: 0xffffffff81e65900
0x0020: 0x0000000000000000
...
[+] kbase = 0xffffffff81000000
[+] kheap = 0xffff88800f1ed840
Press enter to continue...
[ 5.413411] BUG: unable to handle kernel paging request at 00000000deadbeefAAW/Heap Spray에 사용할 수 있는 구조체
setxattr
Size : ⇐ 65536
할당 : setxattr을 호출 후 value에 사용하고 싶은 값의 포인터를 입력 후 size 지정
해제 : 할당과 같은 방법으로 해제
userfaultfd와 함께 사용하며,msgsnd에서는 맨 앞 48byte를 다시 덮을 수 없기 때문에 이에 대응할 방법으로 유용하다. Heap Spray에도 사용 가능
sendmsg
크기: 임의(>=2) 확보: sendmsg를 호출하여 msg.msg_control에 포인터, msg.msg_controllen에 크기를 넣는다.해제: 확보와 동일한 경로로 해제한다.비고: setxattr과 동일하게 userfaultfd와 결합한다.
Size : >=2
할당 : sendmsg를 호출 후 msg.msg_control에 원하는 값 포인터, msg.msg_controllen에 size 지정
해제 : 할당과 같은 방법으로 해제
setxattr과 동일하게userdefaultfd와 퓨전