what is ret2usr
드디어 처음으로, 커널 익스를 해보았다.
생각보다 신기했고 생각보다 어려웠다. 처음으로 배워본 커널 익스 방식은 바로
ret2usr 이다.
베이직한 아이디어는 상당히 쉽다. 단순히 현재 rip를 유저영역의 코드로 옮기겠다는 것
하지만 상당히 쉬운 공격이니만큼 막기도 쉽다.
주로 SMEP , SMAP 이 두가지로 mitigation이 가능하다.
링크
일단 당연히 kerenl buffer overflow, funtion pointer overwrite , callback table overwrite 같은 수법으로 rip를 돌리는 것이 시작이다.
그러면 이제 내가 임의로 작성한 바이너리의 code 영역 ( no-pie로 하면 주소를 알기도 쉬움 ) 으로 rip를 옮길 수 있는데 여기서 현재는 커널링이 아직 커널의 context로 되어 있기에 유저랜드의 영역을 건드리지 않게 조심해야 한다. ( ex. push ) 이제 우리가 할것은 정해져 있다.
먼저, 권한을 root로 바꾸고 유저랜드로 돌아온것처럼 흉내를 낸뒤에 system(”/bin/sh”)을 실행하면 된다.
root로 권한을 바꿀려면
commit_creds(prepare_kernel_cred(0)); 해당 코드를 실행시켜야 하는데
일단 그럴려면 KASLR이 켜져 있다면 KASLR 주소를 leak을 해서 base를 구하고
commit_creds 와 prepare_kernel_cred 오프셋을 구해서 함수 포인터로 실행을 하면 된다. leak이 주로 문제가 된다. 출력할 수 있는 취약점이 존재를 해야한다. 이후에 offset을 구하는 것은 gdb로도 쉽게 할 수 있다.
따라서 commit_creds(prepare_kernel_cred(0)); 가 실행이 되었으면,
유저랜드로 돌아온것처럼 흉내를 내면 되는데 방식이 여러가지가 있다.
주로 sysret 방식과 iret , retf , sysexit 등이 사용된다.
나는 그중 국룰인 iret 으로 소개하겠다.
volatile register uint64_t RSP asm("rsp");
RSP = (uint64_t)&tf;
asm volatile(
"cli\n\t"
"swapgs\n\t"
"iretq"
:: "r" (RSP)
);다음과 같은 asm을 사용하면 되는데
먼저 RSP는 tf 구조체의 주소로 지정하는데 이건 이따가 다룬다.
일단 cli ( Clear Interrupt Flag ) 명령어로 일반적인 하드웨어 인터럽트를 막고
swapgs 로 현재 커널 영역으로 되어 있는 GS.base를 전에 사용하던 유저 영역에 GS.base로 다시 swap한다.
iretq 이제 iretq로 스택에 저장된 값을 꺼내서 실행 흐름/권한/스택을 복구하고 돌아간다.
따라서 스택에 저장을 할 구조체를 만들어 놓는다.
static struct trap_frame {
void *rip;
uint64_t cs;
uint64_t rflags;
void *rsp;
uint64_t ss;
} tf = {
.rip = &shell,
.cs = 0x33,
.rflags = 0x202,
.rsp = dummy_stack + 512,
.ss = 0x2b
};다음과 같이 만들어놓고 이제 이걸 스택으로 사용하기 위해 위에서
RSP = (uint64_t)&tf; 으로 설정해논것이다. 여기서 보면
.rip = &shell, rip 도 쉽게 설정을 할 수 있기에 바로 system(”/bin/sh”)을 불러올 수 있다.
각각의 템블릿은 다음과 같다.