intel:spectre&Meltdown侧信道攻击(五)—— DRAM address mapping
前面介绍了row hammer,理论上很完美,实际操作的时候会面临很尴尬的问题:内存存储数据最小的单位是cell(就是个电容,充电是1,放电是0),无数个横着的cell组成row,无数个竖着的cell组成colume;数个row和colume组成bank,多个bank组成chip,然后是rank,接着是dimm、channel,总的逻辑包含顺序是:channel->dimm->rank->chip->bak->row/colume->cell;怎么把物理地址映射到具体的cell了?换句话说:比如知道了某个能提权数据位的虚拟地址,linux和windwos都能调用系统API查找到相应的物理地址,又怎么根据物理地址映射到bank/row/colume了? 否则怎么精准hammer?Intel 并未公开映射算法,怎么通过一些逆向的方式、方法猜测出物理地址到dram 的address mapping了?
1、百度的同学公开一种算法,https://cloud.tencent.com/developer/article/1620354 这里有算法的说明,但本人并未找到该工具(自称DRAMDig)的源代码,也未找到该工具下载的地址,未能验证证其效果;为方便理解,整理了一个导图,如下:

通过DRAMDig测试的结果如下,由此可见:不同cpu型号、不同内存大小,对应不同的row、colume、bank function,情况较为复杂;

2、国外有团队做了row hammer测试,根据测试结果猜测出了物理地址映射到DRAM的方法,具体参考:http://lackingrhoticity.blogspot.com/2015/05/how-physical-addresses-map-to-rows-and-banks.html
对应的测试代码:https://github.com/google/rowhammer-test/tree/master/extended_test 下面详细说明代码的思路和倒推映射算法的思路;
(1)整个流程大致如下: 先分配1GB内存,每个bit全部初始化为1;再盲选40个地址反复hammer,选出成功flip的地址后两两组合继续hammer,再取消两两组合后继续hammer;

(2)新问题来了:作者一开始盲选地址hammer,成功flip后为啥要narrow to pair down后再hammer了?——为了更准确地确认物理地址和反转flip的关系,看看到底是哪个地址导致了哪些cell flip,借此更精确的地逆向address mapping;下面会打印hammer的地址和被flip的地址,然后根据这些信息逆向address mapping;
if (check(&bit_flip_info)) {
found = true;
printf("RESULT PAIR,0x%" PRIx64 ",0x%" PRIx64 ",0x%" PRIx64 ",%i,%i\n",
get_physical_addr((uintptr_t) addr1),
get_physical_addr((uintptr_t) addr2),
get_physical_addr((uintptr_t) bit_flip_info.victim_virtual_addr),
bit_flip_info.bit_number,
bit_flip_info.flips_to);
}
作者耗费6小时,反转了22个地址,分别如下:
RESULT PAIR,0x6ccc1000,0x6cd59000,0x6cd1f680,,
RESULT PAIR,0x708f1000,0x70969000,0x7092ef08,,
RESULT PAIR,0x1a1d57000,0x1a1ddc000,0x1a1d9b718,,
RESULT PAIR,0x1a14de000,0x72367000,0x72321c20,,
RESULT PAIR,0x194d63000,0x194cf8000,0x194d27b30,,
RESULT PAIR,0x7b664000,0x7b6ed000,0x7b622d30,,
RESULT PAIR,0x72366000,0x61503000,0x72321c20,,
RESULT PAIR,0x72366000,0x5e9cf000,0x72321c20,,
RESULT PAIR,0x193606000,0x193825000,0x193643c10,,
RESULT PAIR,0x171417000,0x171236000,0x171272980,,
RESULT PAIR,0x17a644000,0x17a865000,0x17a822f00,,
RESULT PAIR,0x80af9000,0x17ebaf000,0x80a34310,,
RESULT PAIR,0x1961ec000,0x196165000,0x1961abd10,,
RESULT PAIR,0x7248f000,0x72515000,0x724c8d88,,
RESULT PAIR,0x1716b7000,0x7eb69000,0x1716f1ea0,,
RESULT PAIR,0x16f3d6000,0x16f1f6000,0x16f3930b0,,
RESULT PAIR,0x72901000,0x177232000,0x1772775a0,,
RESULT PAIR,0x772fc000,0x77277000,0x77231830,,
RESULT PAIR,0x7bcf3000,0x7bd69000,0x7bd2ef10,,
RESULT PAIR,0x7e275000,0x7e456000,0x7e412a30,,
RESULT PAIR,0x1730d7000,0x17305d000,0x1730910a8,,
RESULT PAIR,0x80afb000,0x78671000,0x80a34310,,
怎么根据这些flip的位反推物理地址和row、colume、bank了?作者cpu是sandy brige,ubuntu系统,4GB内存,先用decode-dimms初步查看了内存信息,如下:
Size MB
Banks x Rows x Columns x Bits x x x
Ranks
这里有2个rank,8个bank;每个bank包含了2^15 = 32768 rows;每个row的容量 2^10*64=8KB;总容量 = 8 kbytes per row * 32768 rows * 2 ranks * 8 banks = 4GB;通过该命令,初步确认了row、colume和bank的位数;结合上述被filp的地址,连带着各种猜测和不停的尝试,作者把地址做出了以下分解:
result:
diff=-
addr=0x06cd1f680 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x06cd59000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x06ccc1000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=-3a0f8
addr=0x07092ef08 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x070969000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x0708f1000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=-408e8
addr=0x1a1d9b718 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x1a1ddc000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x1a1d57000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=-453e0
addr=0x072321c20 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x072367000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x1a14de000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=2fb30
addr=0x194d27b30 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x194cf8000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x194d63000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
unusual?
result:
diff=-412d0
addr=0x07b622d30 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x07b664000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x07b6ed000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=-443e0
addr=0x072321c20 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x072366000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x061503000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=-443e0
addr=0x072321c20 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x072366000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x05e9cf000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=3dc10
addr=0x193643c10 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x193606000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x193825000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=3c980
addr=0x171272980 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x171236000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x171417000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=-
addr=0x17a822f00 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x17a865000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x17a644000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=-c4cf0
addr=0x080a34310 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x080af9000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x17ebaf000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=False
unusual?
result:
diff=-402f0
addr=0x1961abd10 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x1961ec000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x196165000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=39d88
addr=0x0724c8d88 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x07248f000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x072515000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=3aea0
addr=0x1716f1ea0 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x1716b7000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x07eb69000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=-42f50
addr=0x16f3930b0 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x16f3d6000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x16f1f6000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=455a0
addr=0x1772775a0 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x177232000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x072901000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=-457d0
addr=0x077231830 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x077277000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x0772fc000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=-3a0f0
addr=0x07bd2ef10 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x07bd69000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x07bcf3000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=-435d0
addr=0x07e412a30 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x07e456000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x07e275000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
result:
diff=340a8
addr=0x1730910a8 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x17305d000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x1730d7000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=True
unusual?
result:
diff=-c6cf0
addr=0x080a34310 -> row= rank= bank= col_hi= channel= col_lo= (victim)
addr=0x080afb000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor1)
addr=0x078671000 -> row= rank= bank= col_hi= channel= col_lo= (aggressor2)
fits=False
unusual?
仔细观察:(1)aggressor1距离victim更近(22个样本中,20个样本的位数只差1,两个样本差3),大概率是影响victim 的地址 (2)victims和两个aggressor都在同一个bank
进而得出了以下address mapping的结论:
- Bits 0-5: These are the lower 6 bits of the byte index within a row (i.e. the 6-bit index into a 64-byte cache line).
- Bit 6: This is a 1-bit channel number, which selects between the 2 DIMMs.
- Bits 7-13: These are the upper 7 bits of the index within a row (i.e. the upper bits of the column number).
- Bits 14-16: These are XOR'd with the bottom 3 bits of the row number to give the 3-bit bank number.
- Bit 17: This is a 1-bit rank number, which selects between the 2 ranks of a DIMM (which are typically the two sides of the DIMM's circuit board).
- Bits 18-32: These are the 15-bit row number.
- Bits 33+: These may be set because physical memory starts at physical addresses greater than 0.
内存管理器这么映射物理地址,有啥好处了?
- 0~5bit一共有6位,刚好是2^6=64byte 一个cache line的大小,这么做可以让两个channel同时并行访问不同的cache line,提升速度;
- bank 并行:8个bank可以同时并行读写,提升速度
- bank function: bank bit之间的XOR,可以在大范围读取数据时让地址映射到不同的bank,减少thrashing碰撞的概率
(3)最核心的hammer代码如下: 对特定地址读54万次;每次读后cflush清空cache line,强迫cpu每次都去DRAM读取,使得对应地址的row反复充放电,达到hammer的效果;
//inner的每个地址分别读数据,再清除缓存,如此重复54万次;
static void row_hammer_inner(struct InnerSet inner) {
if (TEST_MODE &&
inner.addrs[] == g_inject_addr1 &&
inner.addrs[] == g_inject_addr2) {
printf("Test mode: Injecting bit flip...\n");
g_mem[] ^= ;
} uint32_t sum = ;
for (int i = ; i < toggles; i++) {//重复54万次
for (int a = ; a < ADDR_COUNT; a++)
sum += *inner.addrs[a] + ;//分别从4个内层地址读数据,可以把这4个地址存储的数据放进rowbuffer,原地址的cell读一次会充放电一次,影响其周边的cell;
if (!TEST_MODE) {
for (int a = ; a < ADDR_COUNT; a++)
//上面4个地址的内容从cache line清除,确保下次cpu还是从内存去读,才能保证row hammer的效果
asm volatile("clflush (%0)" : : "r" (inner.addrs[a]) : "memory");
}
} // Sanity check. We don't expect this to fail, because reading
// these rows refreshes them.
if (sum != ) {
printf("error: sum=%x\n", sum);
exit();
}
}
完整代码如下:精华都在注释(英文是原作者,中文是我加的)
// Copyright 2015, Google, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. // This is required on Mac OS X for getting PRI* macros #defined.
#define __STDC_FORMAT_MACROS #include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h> #if !defined(TEST_MODE)
# define TEST_MODE
#endif const size_t mem_size = << ;//1GB
const int toggles = ; char *g_mem;
void *g_inject_addr1;
void *g_inject_addr2; uint64_t g_address_sets_tried;
int g_errors_found; /*从g_mem开始随机选个物理地址,偏移是页的整数倍,大小不超过1GB
http://lackingrhoticity.blogspot.com/2015/05/how-physical-addresses-map-to-rows-and-banks.html 原作者自己的硬件环境:
Bits 0-5: These are the lower 6 bits of the byte index within a row (i.e. the 6-bit index into a 64-byte cache line).
Bit 6: This is a 1-bit channel number, which selects between the 2 DIMMs.
Bits 7-13: These are the upper 7 bits of the index within a row (i.e. the upper bits of the column number).
物理地址依次增加0x1000,也就是不同的物理地址低12位是相同的,那么这些物理地址的:1、channel是一样的 2、colume是一样的 3、bank和row可能不同
*/
char *pick_addr() {
//offset是页(1<<12=4096)的整数倍
size_t offset = (rand() << ) % mem_size;
return g_mem + offset;
}
//虚拟地址转成物理地址,http://0x4c43.cn/2018/0508/linux-dynamic-link/ 有详细说明
uint64_t get_physical_addr(uintptr_t virtual_addr) {
int fd = open("/proc/self/pagemap", O_RDONLY);
assert(fd >= ); int kPageSize = 0x1000;
off_t pos = lseek(fd, (virtual_addr / kPageSize) * , SEEK_SET);
assert(pos >= );
uint64_t value;
int got = read(fd, &value, );
assert(got == );
int rc = close(fd);
assert(rc == ); uint64_t frame_num = value & ((1ULL << ) - );
return (frame_num * kPageSize) | (virtual_addr & (kPageSize - ));
} class Timer {
struct timeval start_time_; public:
Timer() {
// Note that we use gettimeofday() (with microsecond resolution)
// rather than clock_gettime() (with nanosecond resolution) so
// that this works on Mac OS X, because OS X doesn't provide
// clock_gettime() and we don't really need nanosecond resolution.
int rc = gettimeofday(&start_time_, NULL);
assert(rc == );
} double get_diff() {
struct timeval end_time;
int rc = gettimeofday(&end_time, NULL);
assert(rc == );
return (end_time.tv_sec - start_time_.tv_sec
+ (double) (end_time.tv_usec - start_time_.tv_usec) / 1e6);
}
}; #define ADDR_COUNT 4
#define ITERATIONS 10 struct InnerSet {
uint32_t *addrs[ADDR_COUNT];//4个32位的地址
};
struct OuterSet {
struct InnerSet inner[ITERATIONS];//10个内层结构,每个内层结构4个32位地址
}; /*g_mem每bit都置1,后续按照一定频率反复读写某些地址的数据。如果发生flip,
会导致其内容不再是1,后续会在check函数检查是否发生了bit flip*/
static void reset_mem() {
memset(g_mem, 0xff, mem_size);
} //从g_mem开始随机选择40个物理地址保存在set;物理地址的偏移是页的整数倍,大小超过1GB;
static void pick_addrs(struct OuterSet *set) {
for (int j = ; j < ITERATIONS; j++) {
for (int a = ; a < ADDR_COUNT; a++) {
set->inner[j].addrs[a] = (uint32_t *) pick_addr();
}
}
} //inner的每个地址分别读数据,再清除缓存,如此重复54万次;
static void row_hammer_inner(struct InnerSet inner) {
if (TEST_MODE &&
inner.addrs[] == g_inject_addr1 &&
inner.addrs[] == g_inject_addr2) {
printf("Test mode: Injecting bit flip...\n");
g_mem[] ^= ;
} uint32_t sum = ;
for (int i = ; i < toggles; i++) {//重复54万次
for (int a = ; a < ADDR_COUNT; a++)
sum += *inner.addrs[a] + ;//分别从4个内层地址读数据,可以把这4个地址存储的数据放进rowbuffer,原地址的cell读一次会充放电一次,影响其周边的cell;
if (!TEST_MODE) {
for (int a = ; a < ADDR_COUNT; a++)
//上面4个地址的内容从cache line清除,确保下次cpu还是从内存去读,才能保证row hammer的效果
asm volatile("clflush (%0)" : : "r" (inner.addrs[a]) : "memory");
}
} // Sanity check. We don't expect this to fail, because reading
// these rows refreshes them.
if (sum != ) {
printf("error: sum=%x\n", sum);
exit();
}
} static void row_hammer(struct OuterSet *set) {
Timer timer;
for (int j = ; j < ITERATIONS; j++) {
//读取inner的地址、清空缓存,重复54万次;
row_hammer_inner(set->inner[j]);
g_address_sets_tried++;
} // Print statistics derived from the time and number of accesses.
double time_taken = timer.get_diff();
printf(" Took %.1f ms per address set\n",//1个set = 1个1inner(一共4个地址),耗时58~59ms,平局每个地址的读取耗时14.5~15ms(每个地址重复了54万次,并且上次读取后清空了缓存);
time_taken / ITERATIONS * 1e3);
printf(" Took %g sec in total for %i address sets\n",
time_taken, ITERATIONS);
int memory_accesses = ITERATIONS * ADDR_COUNT * toggles;
printf(" Took %.3f nanosec per memory access (for %i memory accesses)\n",
time_taken / memory_accesses * 1e9,//每个地址的读取时间在27~29ns之间
memory_accesses);
int refresh_period_ms = ;//内存控制器每64ms刷新一次;每个刷新周期内,每个地址访问53~58万次;
printf(" This gives %i accesses per address per %i ms refresh period\n",
(int) (refresh_period_ms * 1e- * ITERATIONS * toggles / time_taken),
refresh_period_ms);
} struct BitFlipInfo {
uintptr_t victim_virtual_addr;
int bit_number;
uint8_t flips_to; // 1 if this is a 0 -> 1 bit flip, 0 otherwise.
}; static bool check(struct BitFlipInfo *result) {
uint64_t *end = (uint64_t *) (g_mem + mem_size);
uint64_t *ptr;
bool found_error = false;
for (ptr = (uint64_t *) g_mem; ptr < end; ptr++) {
uint64_t got = *ptr;
uint64_t expected = ~(uint64_t) ;//g_mem每个bit初始都置1
if (got != expected) {
printf("error at %p (phys 0x%" PRIx64 "): got 0x%" PRIx64 "\n",
ptr, get_physical_addr((uintptr_t) ptr), got);
found_error = true;
g_errors_found++; if (result) {
result->victim_virtual_addr = (uintptr_t) ptr;//保存flip的地址
result->bit_number = -;//0xff,初始值;
for (int bit = ; bit < ; bit++) {//上面每次比对取64bit,这里继续看看到底是哪个bit被flip了
if (((got >> bit) & ) != ((expected >> bit) && )) {
result->bit_number = bit;//找到了flip的位,最终flip的位=victim_virtual_addr+bit_number;
result->flips_to = (got >> bit) & ;
}
}
assert(result->bit_number != -);
}
}
}
return found_error;
}
/*
用发生flip的地址两两组合形成新inner地址,缩小范围后继续hammer
*/
bool narrow_to_pair(struct InnerSet *inner) {
bool found = false;
for (int idx1 = ; idx1 < ADDR_COUNT; idx1++) {
for (int idx2 = idx1 + ; idx2 < ADDR_COUNT; idx2++) {
//0+1、1+2、2+3组合发生反转的地址
uint32_t *addr1 = inner->addrs[idx1];
uint32_t *addr2 = inner->addrs[idx2];
struct InnerSet new_set;
// This is slightly hacky: We reuse row_hammer_inner(), which
// always expects to hammer ADDR_COUNT addresses. Rather than
// making another version that takes a pair of addresses, we
// just pass our 2 addresses to row_hammer_inner() multiple
// times. 新的inner分别放这两个发生过flip的地址组合
for (int a = ; a < ADDR_COUNT; a++) {
new_set.addrs[a] = a % == ? addr1 : addr2;
}
printf("Trying pair: 0x%" PRIx64 ", 0x%" PRIx64 "\n",
get_physical_addr((uintptr_t) addr1),
get_physical_addr((uintptr_t) addr2));
reset_mem();
row_hammer_inner(new_set);
struct BitFlipInfo bit_flip_info;
if (check(&bit_flip_info)) {
found = true;
printf("RESULT PAIR,0x%" PRIx64 ",0x%" PRIx64 ",0x%" PRIx64 ",%i,%i\n",
get_physical_addr((uintptr_t) addr1),
get_physical_addr((uintptr_t) addr2),
get_physical_addr((uintptr_t) bit_flip_info.victim_virtual_addr),
bit_flip_info.bit_number,
bit_flip_info.flips_to);
}
}
}
return found;
}
//继续hammer,把发生flip的inner地址保存后
bool narrow_down(struct OuterSet *outer) {
bool found = false;
for (int j = ; j < ITERATIONS; j++) {
reset_mem();
row_hammer_inner(outer->inner[j]);
if (check(NULL)) {
printf("hammered addresses:\n");
struct InnerSet *inner = &outer->inner[j];//把发生flip的地址保存在inner结构
for (int a = ; a < ADDR_COUNT; a++) {
printf(" logical=%p, physical=0x%" PRIx64 "\n",
inner->addrs[a],
get_physical_addr((uintptr_t) inner->addrs[a]));
}
found = true; printf("Narrowing down to a specific pair...\n");
int tries = ;
while (!narrow_to_pair(inner)) {
if (++tries >= ) {
printf("Narrowing to pair: Giving up after %i tries\n", tries);
break;
}
}
}
}
return found;
} void main_prog() {
printf("RESULT START_TIME,%" PRId64 "\n", time(NULL)); g_mem = (char *) mmap(NULL, mem_size, PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE, -, );
assert(g_mem != MAP_FAILED); printf("Clearing memory...\n");
reset_mem();//分配的1G内存全部值0xFF Timer t;
int iter = ;
for (;;) {
printf("Iteration %i (after %.2fs)\n", iter++, t.get_diff());
struct OuterSet addr_set;
pick_addrs(&addr_set);
if (TEST_MODE && iter == ) {
printf("Test mode: Will inject a bit flip...\n");
g_inject_addr1 = addr_set.inner[].addrs[];
g_inject_addr2 = addr_set.inner[].addrs[];
}
row_hammer(&addr_set); Timer check_timer;
bool found_error = check(NULL);
printf(" Checking for bit flips took %f sec\n", check_timer.get_diff()); if (iter % == || found_error) {
// Report general progress stats:
// - Time since start, in seconds
// - Current Unix time (seconds since epoch)
// - Number of address sets tried
// - Number of bit flips found (not necessarily unique ones)
printf("RESULT STAT,%.2f,%" PRId64 ",%" PRId64 ",%i\n",
t.get_diff(),
(uint64_t) time(NULL),
g_address_sets_tried,
g_errors_found);
} if (found_error) {
printf("\nNarrowing down to set of %i addresses...\n", ADDR_COUNT);
int tries = ;
while (!narrow_down(&addr_set)) {
if (++tries >= ) {
printf("Narrowing to address set: Giving up after %i tries\n", tries);
break;
}
} printf("\nRunning retries...\n");
for (int i = ; i < ; i++) {
printf("Retry %i\n", i);
reset_mem();
row_hammer(&addr_set);
check(NULL);
}
if (TEST_MODE)
exit();
}
}
} int main() {
// Turn off unwanted buffering for when stdout is a pipe.
setvbuf(stdout, NULL, _IONBF, ); // Start with an empty line in case previous output was truncated
// mid-line.
printf("\n"); if (TEST_MODE) {
printf("Running in safe test mode...\n");
} // Fork a subprocess so that we can print the test process's exit
// status, and to prevent reboots or kernel panics if we are running
// as PID 1.
int pid = fork();
if (pid == ) {
main_prog();
_exit();
} int status;
if (waitpid(pid, &status, ) == pid) {
printf("** exited with status %i (0x%x)\n", status, status);
} if (getpid() == ) {
// We're the "init" process. Avoid exiting because that would
// cause a kernel panic, which can cause a reboot or just obscure
// log output and prevent console scrollback from working.
for (;;) {
sleep();
}
}
return ;
}
参考:
1、 http://lackingrhoticity.blogspot.com/2015/05/how-physical-addresses-map-to-rows-and-banks.html How physical addresses map to rows and banks in DRAM
2、 https://cloud.tencent.com/developer/article/1620354 逆向DRAM地址映射
3、 DRAMDig: A Knowledge-assisted Tool to Uncover DRAM Address Mapping
intel:spectre&Meltdown侧信道攻击(五)—— DRAM address mapping的更多相关文章
- intel:spectre&Meltdown侧信道攻击(一)
只要平时对安全领域感兴趣的读者肯定都听过spectre&Meltdown侧信道攻击,今天简单介绍一下这种攻击的原理( https://www.bilibili.com/video/av1814 ...
- intel:spectre&Meltdown侧信道攻击(三)—— raw hammer
今天介绍raw hammer攻击的原理:这次有点“标题党”了.事实上,raw hammer是基于DRAM内存的攻击:所以理论上,只要是用了DRAM内存的设备,不论是什么cpu(intel.amd,或则 ...
- intel:spectre&Meltdown侧信道攻击(二)
上面一篇介绍了spectre&meltdown基本原理和简单的demo方案,今天继续学习一下该漏洞发现团队原始的POC:https://spectreattack.com/spectre.pd ...
- intel:spectre&Meltdown侧信道攻击(四)—— cache mapping
前面简单介绍了row hammer攻击的原理和方法,为了更好理解这种底层硬件类攻击,今天介绍一下cpu的cache mapping: 众所周知,cpu从内存读数据,最开始用的是虚拟地址,需要通过分页机 ...
- 第四十五个知识点:描述一些对抗RSA侧信道攻击的基础防御方法
第四十五个知识点:描述一些对抗RSA侧信道攻击的基础防御方法 原文地址:http://bristolcrypto.blogspot.com/2015/08/52-things-number-45-de ...
- 第四十三个知识点:为AES描述一些基础的(可能无效)的对抗侧信道攻击的防御
第四十三个知识点:为AES描述一些基础的(可能无效)的对抗侧信道攻击的防御 原文地址:http://bristolcrypto.blogspot.com/2015/07/52-things-numbe ...
- 侧信道攻击,从喊666到入门之——Unicorn的环境构建
作者:backahasten 发表于小米安全中心微信公众号 0x00 前言 Unicorn可以模拟多种指令集的代码,在很多安全研究领域有很强大的作用,但是由于需要从头自己布置栈空间,代码段等虚拟执行环 ...
- 嵌入式 -- WINKHUB 边信道攻击 (NAND Glitch)
0x00 前言 随着物联网IOT的飞速发展,各类嵌入式设备, 路由器安全研究也越来越火. 但因为跟以往纯软件安全研究的要求不同, 这类研究往往需要结合相应的硬件知识. 很多朋友困惑如何开始, 甚至卡在 ...
- ORW-测信道攻击
做SCTF时碰到一个没看过的题型,比赛结束之后才知道是orw的一个玩法,测信道攻击.主要特点就是只给使用open,read,但是不给write,即无法把flag输出到终端.这里可以通过把flag读到栈 ...
随机推荐
- Flask 上下文机制和线程隔离
1. 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决, 上下文机制就是这句话的体现. 2. 如果一次封装解决不了问题,那就再来一次 上下文:相当于一个容器,保存了Flask程序运行过程中 ...
- HDFS读写流程(重点)
@ 目录 一.写数据流程 举例: 二.异常写流程 读数据流程 一.写数据流程 ①服务端启动HDFS中的NN和DN进程 ②客户端创建一个分布式文件系统客户端,由客户端向NN发送请求,请求上传文件 ③NN ...
- AutoJS 实现QQ小游戏胡莱三国爬塔
AutoJS 开发文档参考 环境 安卓QQ 胡莱三国小游戏 AutoJS APP 使用方法 安装AutoJs,打开无障碍模式,进入到胡莱三国小游戏,在Autojs中执行脚本 代码 "auto ...
- Python 字符串改变
在Python中,字符串是不可变类型,即无法直接修改字符串的某一位字符. 因此改变一个字符串的元素需要新建一个新的字符串. 常见的修改方法有以下4种. 方法1:将字符串转换成列表后修改值,然后用joi ...
- 数据可视化之PowerQuery篇(三)学会使用PowrQuery的自定义函数
https://zhuanlan.zhihu.com/p/64415763 使用Power Query进行复杂一些的数据处理,离不开M函数,目前已经有超过700个函数了,基本上各式各样的数据处理需求都 ...
- Django 【基础篇】
前戏 python Web程序 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. #!/usr/bin/env python #cod ...
- Git操作(二)
很久以前写的git入门,最近又巩固了一下Git分支操作,下面是我的一些整理. 1.分支与合并 #创建并切换到该分支 git checkout -b xxx #查看当前分支 git branch #进行 ...
- Babel:下一代Javascript语法编译器
定义 Babel是一个Javascript的编译器,通过它你可以将一些新版本的ECMAScript语法转换成低版本的语法.以便能够在低版本的浏览器或者其它环境平稳运行. 截至目前笔者写这篇文章的时候, ...
- 一个简单的Maven小案例
Maven是一个很好的软件项目管理工具,有了Maven我们不用再费劲的去官网上下载Jar包. Maven的官网地址:http://maven.apache.org/download.cgi 要建立一个 ...
- oracle终止用户会话
1.创建两个测试用户进行实验 执行命令如下: create user test1 identified by 1; create user test2 identified by 1; grant d ...