[Kernel] Linux kernel exploit basic
Linux kernel exploit basic
그렇다. 이것은 Linux kernel exploit에 관한 기본적인 지식들을 까먹지않게 적어놓는 공간인 것이다.
Mitigation
그렇다. 보호기법이다.
KASLR
: ASLR과 똑같이 Kernel단 address를 randomization함.SMEP
: Ring3에서 Ring0 영역의 명령어를 실행할 수 없도록 만듦.SMAP
: Ring3에서 Ring0 영역을 접근할 수 없도록 만듦.KADR
: /proc/kallsyms을 읽었을 때 보이는 주소를 다 0으로 바꿔버림.KPTI
: Ring3에서 Ring0의 정보를 읽어올 수 없게 아예 새로 페이징 하는 보호기법.canary
: 그렇다. 그 canary인 것이다.mmap_min_addr
: mmap을 할 수 있는 최소 주소를 정해 null pointer dereference 공격 방지
Function that we need to know to exploit
익스를 하기 위해 알아야 할 함수들이다.
prepare_kernel_cred(struct task_struct *daemon)
위 함수는 인자로 넘겨받은 값에 따라 cred
구조체를 생성하고, 그 주소를 반환한다.
NULL을 인자로 넘겨주면, root
권한의 cred
구조체를 새로 생성한 뒤 반환한다.
commit_creds(struct cred *new)
입력받은 cred
구조체로 유저의 권한을 변환시킨다. return값은 항상 0인듯?
copy_from_user(void *to, const void __user *from, unsigned long n)
void __user
은 그냥 char
나 void
로 봐도 무방할듯하다.
from -> to
로 n bytes
만큼 memcpy
하는 것과 유사하게 작동한다.
이름값 하듯이 user space
에서 kernel space
로 값을 적는 것임.
copy_to_user(void *to, const void *from, unsigned long n)
이것도 아까와 같이 from -> to
로 n bytes
만큼 memcpy
하는 것과 유사하게 작동한다.
다만 반대로 kernel space -> user space
로 복사한다.
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 *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;
cred
구조체의 구현은 이렇게 되어있다.
딱히 설명할 것은 없는듯.
Etc..
커널을 익스할 때는 보통, 커널 유저 쉘에 접근한 후 C
언어로 작성한 ELF
를 실행시켜서 권한을 획득한다.
하지만 당연하게도 부팅되는 커널에는 gcc
가 존재하지 않는다.
그러므로 host의 쉘에서 미리 gcc
로 static compile
을 진행한 후 exploit
할 커널에 넘겨주어야한다.
방법은 여러가지가 있겠지만 나 같은 경우는 ELF
를 base64 encoding -> kernel -> base64 decoding -> execute
순서로 진행하는 것을 선호한다.
저 밑에 스켈레톤 참조.
x86_64
같은 경우에는 상관 없는 이야기겠지만, arm
혹은 mips
아키텍쳐 쪽은 호환성 때문인지 pwndbg
, peda
보다 gef
를 가장 많이 사용하는 것 같다.
커널 보호기법 확인하는 법.
mmap_min_addr : sysctl -a | grep vm.mmap
smep : cat /proc/cpuinfo | grep smep
kaslr : cat /proc/kallsyms | grep _text | head -n 1
fork
함수로 자식 프로세스를 생성하면 현재 사용자의 권한에 맞는 cred
구조체를 힙에 새로 생성한다.
skeleton code.
compile.sh
#!/bin/bash
musl-gcc -o solve solve.c -static
base64 ./solve > solve.b64
cat solve.b64
rm solve solve.b64
solve.c
#include <stdio.h>
int main() {
puts("Hello, World!");
return 0;
}
solve.py
from pwn import *
import subprocess as sub
p = process(argv=['/bin/bash', '-c', './boot.sh'])
def main() :
res = sub.check_output('./compile.sh').strip()
p.sendlineafter('$', 'cd /home/ctf')
p.sendline('echo "{}" > solve.b64'.format(res))
p.sendline('base64 -d solve.b64 > solve')
p.sendline('chmod +x solve')
p.interactive()
if __name__ == '__main__' :
main()