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 으로 소개하겠다.

C
	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로 스택에 저장된 값을 꺼내서 실행 흐름/권한/스택을 복구하고 돌아간다.

따라서 스택에 저장을 할 구조체를 만들어 놓는다.

C
	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”)을 불러올 수 있다.

각각의 템블릿은 다음과 같다.