commit 70fb4e150a4fcc05c3487cdfdeea72db99f13b04 Author: Peter Deutsch Date: Tue Jan 31 15:19:02 2023 -0500 Initial cache attack lab commit diff --git a/Part1-Timing/Makefile b/Part1-Timing/Makefile new file mode 100644 index 0000000..5bc5f7a --- /dev/null +++ b/Part1-Timing/Makefile @@ -0,0 +1,17 @@ +include ../cpu.mk + +all: main + +run: main + @taskset -c $(SENDER_CPU) ./main + +run-reference: reference + @taskset -c $(SENDER_CPU) ./reference + +main: main.c Makefile + @gcc main.c -o main + +.PHONY: clean + +clean: + rm -f main diff --git a/Part1-Timing/graph.py b/Part1-Timing/graph.py new file mode 100644 index 0000000..9eb1b01 --- /dev/null +++ b/Part1-Timing/graph.py @@ -0,0 +1,49 @@ +import os +import json +import matplotlib.pyplot as plt +import numpy as np +from tqdm import tqdm +from datetime import datetime + +num_runs = 100 +dict_of_dict_of_lists = dict() + +graph_repo="data" +os.makedirs(graph_repo, exist_ok=True) + +fancy_num_runs = range(0, num_runs, 1) +for run_id in tqdm(fancy_num_runs): + filename = graph_repo+"/run"+str(run_id)+".json" + with open(filename) as f: + dict_of_dict_of_lists[run_id] = json.load(f) + +l1_all = [] +l2_all = [] +l3_all = [] +mem_all = [] + +for run_id in tqdm(fancy_num_runs): + l1_all += dict_of_dict_of_lists[run_id]['1'] + l2_all += dict_of_dict_of_lists[run_id]['2'] + l3_all += dict_of_dict_of_lists[run_id]['3'] + mem_all += dict_of_dict_of_lists[run_id]['4'] + +# +# MAX 300 +# +fig_all = plt.figure(figsize=(11.25, 7.5)) +ax_all = fig_all.add_subplot(1,1,1) +ax_all.set_xlabel("Access Time") +ax_all.set_ylabel("Number of Samples") + +ax_all.hist(l1_all, label="L1", bins=np.arange(0, 300 ), alpha=0.5) +ax_all.hist(l2_all, label="L2", bins=np.arange(0, 300 ), alpha=0.5) +ax_all.hist(l3_all, label="L3", bins=np.arange(0, 300 ), alpha=0.5) +ax_all.hist(mem_all, label="DRAM", bins=np.arange(0, 300 ), alpha=0.5) +fig_all.legend() + +os.makedirs("graphs", exist_ok=True) +now = datetime.now() +date_time = now.strftime("%m:%d:%Y_%H:%M:%S") +fig_all.savefig(str("graphs/"+date_time+".pdf")) +plt.close(fig_all) diff --git a/Part1-Timing/main.c b/Part1-Timing/main.c new file mode 100644 index 0000000..1b6fa97 --- /dev/null +++ b/Part1-Timing/main.c @@ -0,0 +1,72 @@ +#include "utility.h" + +// TODO: Uncomment the following lines and fill in the correct size +//#define L1_SIZE [TODO] +//#define L2_SIZE [TODO] +//#define L3_SIZE [TODO] + +int main (int ac, char **av) { + + // create 4 arrays to store the latency numbers + // the arrays are initialized to 0 + uint64_t dram_latency[SAMPLES] = {0}; + uint64_t l1_latency[SAMPLES] = {0}; + uint64_t l2_latency[SAMPLES] = {0}; + uint64_t l3_latency[SAMPLES] = {0}; + + // A temporary variable we can use to load addresses + // The volatile keyword tells the compiler to not put this variable into a + // register- it should always try to load from memory/ cache. + volatile char tmp; + + // Allocate a buffer of 64 Bytes + // the size of an unsigned integer (uint64_t) is 8 Bytes + // Therefore, we request 8 * 8 Bytes + uint64_t *target_buffer = (uint64_t *)malloc(8*sizeof(uint64_t)); + + if (NULL == target_buffer) { + perror("Unable to malloc"); + return EXIT_FAILURE; + } + + // [1.2] TODO: Uncomment the following line to allocate a buffer of a size + // of your chosing. This will help you measure the latencies at L2 and L3. + //uint64_t *eviction_buffer = (uint64_t)malloc(TODO); + + // Example: Measure L1 access latency, store results in l1_latency array + for (int i=0; i +#include +#include +#include +#include +#include +#include + +#ifndef __UTILITY_H__ +#define __UTILITY_H__ + +#define SAMPLES 10 + + +// Function to read the time stamp counter, which is called tsc for short +// "rdtscpp" returns a 32bit unsigned integer +// "rdtscpp64" return a 64 bit unsigned integer +// Details in https://www.felixcloutier.com/x86/rdtscpp +static inline uint32_t rdtscpp() { + uint32_t rv; + asm volatile ("rdtscpp": "=a" (rv) :: "edx", "ecx"); + return rv; +} + +static inline uint64_t rdtscpp64() { + uint32_t low, high; + asm volatile ("rdtscpp": "=a" (low), "=d" (high) :: "ecx"); + return (((uint64_t)high) << 32) | low; +} + +// Function "lfence" wrap the assembly instruction lfence +// This function performs a serializing operation which ensures that +// the instructions after "lfence" start execution after +// all the instructions before "lfence" complete +// Details in https://www.felixcloutier.com/x86/lfence +static inline void lfence() { + asm volatile("lfence"); +} + +// Here is an example of using "rdtscp" and "mfence" to +// measure the time it takes to access a block specified by its virtual address +// The corresponding pseudo code is +// ========= +// t1 = rdtscp +// load addr +// t2 = rdtscp +// cycles = t2 - t1 +// return cycles +// ========= +static inline uint64_t measure_one_block_access_time(uint64_t addr) +{ + uint64_t cycles; + + asm volatile("mov %1, %%r8\n\t" + "mfence\n\t" + "lfence\n\t" + "rdtscp\n\t" + "mov %%eax, %%edi\n\t" + "mov (%%r8), %%r8\n\t" + "rdtscp\n\t" + "sub %%edi, %%eax\n\t" + : "=a"(cycles) /*output*/ + : "r"(addr) /*input*/ + : "r8", "edi"); /*reserved register*/ + + return cycles; +} + +static inline uint64_t one_block_access(uint64_t addr) +{ + asm volatile("mov (%0), %%r8\n\t" + : /*output*/ + : "r"(addr) /*input*/ + : "r8"); /*reserved register*/ + +} + + +// A wrapper function of the clflush instruction +// The instruction evict the given address from the cache to DRAM +// so that the next time the line is accessed, it will be fetched from DRAM +// Details in https://www.felixcloutier.com/x86/clflush +static inline void clflush(void *v) { + asm volatile ("clflush 0(%0)": : "r" (v):); +} + + + + +// Supporting functions for printing results in different formats +// Function "compare" is used in the priting functions and you do not need it +int compare(const void *p1, const void *p2) { + uint64_t u1 = *(uint64_t *)p1; + uint64_t u2 = *(uint64_t *)p2; + + return (int)u1 - (int)u2; +} + + +// Print out the latencies you measured +void print_results(uint64_t* dram, uint64_t* l1, uint64_t* l2, uint64_t* l3) { + qsort(dram, SAMPLES, sizeof(uint64_t), compare); + qsort(l1, SAMPLES, sizeof(uint64_t), compare); + qsort(l2, SAMPLES, sizeof(uint64_t), compare); + qsort(l3, SAMPLES, sizeof(uint64_t), compare); + printf(" : L1 L2 L3 Mem \n"); + printf("Minimum : %5ld %5ld %5ld %5ld\n", l1[0], l2[0], l3[0], dram[0]); + + printf("Bottom decile: %5ld %5ld %5ld %5ld\n", l1[SAMPLES/10], l2[SAMPLES/10], + l3[SAMPLES/10], dram[SAMPLES/10]); + + printf("Median : %5ld %5ld %5ld %5ld\n", l1[SAMPLES/2], l2[SAMPLES/2], + l3[SAMPLES/2], dram[SAMPLES/2]); + + printf("Top decile : %5ld %5ld %5ld %5ld\n", l1[(SAMPLES * 9)/10], l2[(SAMPLES * 9)/10], + l3[(SAMPLES * 9)/10], dram[(SAMPLES * 9)/10]); + + printf("Maximum : %5ld %5ld %5ld %5ld\n", l1[SAMPLES-1], l2[SAMPLES-1], + l3[SAMPLES-1], dram[SAMPLES-1]); +} + +// Format the latencies for part 1.5 +void print_results_for_python(uint64_t* dram, uint64_t* l1, uint64_t* l2, uint64_t* l3) +{ + qsort(dram, SAMPLES, sizeof(uint64_t), compare); + qsort(l1, SAMPLES, sizeof(uint64_t), compare); + qsort(l2, SAMPLES, sizeof(uint64_t), compare); + qsort(l3, SAMPLES, sizeof(uint64_t), compare); + + for (int i = 0; i < SAMPLES; i++) { + printf("%ld ", l1[i]); + } + printf("\n"); + for (int i = 0; i < SAMPLES; i++) { + printf("%ld ", l2[i]); + } + printf("\n"); + for (int i = 0; i < SAMPLES; i++) { + printf("%ld ", l3[i]); + } + printf("\n"); + for (int i = 0; i < SAMPLES; i++) { + printf("%ld ", dram[i]); + } + printf("\n"); +} + +#endif // _UTILITY_H__ diff --git a/Part2-DeadDrop/Makefile b/Part2-DeadDrop/Makefile new file mode 100644 index 0000000..ae7f4c2 --- /dev/null +++ b/Part2-DeadDrop/Makefile @@ -0,0 +1,26 @@ +include ../cpu.mk + +TARGETS=receiver sender +UTILS=util.o + +all: $(TARGETS) + +$(UTILS): %.o: %.c %.h + $(CC) $(CFLAGS) -c $< + +%.o: %.c util.h + $(CC) $(CFLAGS) -c $< + +$(TARGETS): %:%.o util.o + $(CC) $(CFLAGS) $^ -o $@ + +run_sender: sender + taskset -c $(SENDER_CPU) ./sender + +run_receiver: receiver + taskset -c $(RECEIVER_CPU) ./receiver + +.PHONY: clean + +clean: + $(RM) *.o $(HELPERS) $(TARGETS) diff --git a/Part2-DeadDrop/receiver.c b/Part2-DeadDrop/receiver.c new file mode 100755 index 0000000..4a658e1 --- /dev/null +++ b/Part2-DeadDrop/receiver.c @@ -0,0 +1,29 @@ + +#include"util.h" +// mman library to be used for hugepage allocations (e.g. mmap or posix_memalign only) +#include + +int main(int argc, char **argv) +{ + // Put your covert channel setup code here + + printf("Please press enter.\n"); + + char text_buf[2]; + fgets(text_buf, sizeof(text_buf), stdin); + + printf("Receiver now listening.\n"); + + bool listening = true; + while (listening) { + + // Put your covert channel code here + + } + + printf("Receiver finished.\n"); + + return 0; +} + + diff --git a/Part2-DeadDrop/sender.c b/Part2-DeadDrop/sender.c new file mode 100755 index 0000000..d196515 --- /dev/null +++ b/Part2-DeadDrop/sender.c @@ -0,0 +1,45 @@ + +#include"util.h" +// mman library to be used for hugepage allocations (e.g. mmap or posix_memalign only) +#include + +// TODO: define your own buffer size +#define BUFF_SIZE (1<<21) +//#define BUFF_SIZE [TODO] + +int main(int argc, char **argv) +{ + // Allocate a buffer using huge page + // See the handout for details about hugepage management + void *buf= mmap(NULL, BUFF_SIZE, PROT_READ | PROT_WRITE, MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1, 0); + + if (buf == (void*) - 1) { + perror("mmap() error\n"); + exit(EXIT_FAILURE); + } + // The first access to a page triggers overhead associated with + // page allocation, TLB insertion, etc. + // Thus, we use a dummy write here to trigger page allocation + // so later access will not suffer from such overhead. + //*((char *)buf) = 1; // dummy write to trigger page allocation + + + // TODO: + // Put your covert channel setup code here + + printf("Please type a message.\n"); + + bool sending = true; + while (sending) { + char text_buf[128]; + fgets(text_buf, sizeof(text_buf), stdin); + + // TODO: + // Put your covert channel code here + } + + printf("Sender finished.\n"); + return 0; +} + + diff --git a/Part2-DeadDrop/util.c b/Part2-DeadDrop/util.c new file mode 100755 index 0000000..56bbe0b --- /dev/null +++ b/Part2-DeadDrop/util.c @@ -0,0 +1,102 @@ + +#include "util.h" + +/* Measure the time it takes to access a block with virtual address addr. */ +CYCLES measure_one_block_access_time(ADDR_PTR addr) +{ + CYCLES cycles; + + asm volatile("mov %1, %%r8\n\t" + "lfence\n\t" + "rdtsc\n\t" + "mov %%eax, %%edi\n\t" + "mov (%%r8), %%r8\n\t" + "lfence\n\t" + "rdtsc\n\t" + "sub %%edi, %%eax\n\t" + : "=a"(cycles) /*output*/ + : "r"(addr) + : "r8", "edi"); + + return cycles; +} + +/* + * CLFlushes the given address. + * + * Note: clflush is provided to help you debug and should not be used in your + * final submission + */ +void clflush(ADDR_PTR addr) +{ + asm volatile ("clflush (%0)"::"r"(addr)); +} + +/* + * Converts a string to its binary representation. + */ +char *string_to_binary(char *s) +{ + if (s == NULL) + return 0; /* no input string */ + + size_t len = strlen(s); + + // Each char is one byte (8 bits) and + 1 at the end for null terminator + char *binary = malloc(len * 8 + 1); + binary[len] = '\0'; + + for (size_t i = 0; i < len; ++i) + { + char ch = s[i]; + for (int j = 7; j >= 0; --j) + { + if (ch & (1 << j)) + { + strcat(binary, "1"); + } + else + { + strcat(binary, "0"); + } + } + } + + return binary; +} + +/* + * Converts a binary string to its ASCII representation. + */ +char *binary_to_string(char *data) +{ + // Each char is 8 bits + size_t msg_len = strlen(data) / 8; + + // Add one for null terminator at the end + char *msg = malloc(msg_len + 1); + msg[msg_len] = '\0'; + + for (int i = 0; i < msg_len; i++) + { + char tmp[8]; + int k = 0; + + for (int j = i * 8; j < ((i + 1) * 8); j++) + { + tmp[k++] = data[j]; + } + + msg[i] = strtol(tmp, 0, 2); + } + + return msg; +} + +/* + * Converts a string to integer + */ +int string_to_int(char* s) +{ + return atoi(s); +} diff --git a/Part2-DeadDrop/util.h b/Part2-DeadDrop/util.h new file mode 100755 index 0000000..79ea9b6 --- /dev/null +++ b/Part2-DeadDrop/util.h @@ -0,0 +1,33 @@ + +// You may only use fgets() to pull input from stdin +// You may use any print function to stdout to print +// out chat messages +#include + +// You may use memory allocators and helper functions +// (e.g., rand()). You may not use system(). +#include + +#include +#include +#include +#include + +#ifndef UTIL_H_ +#define UTIL_H_ + +#define ADDR_PTR uint64_t +#define CYCLES uint32_t + +CYCLES measure_one_block_access_time(ADDR_PTR addr); + +// You Should Not Use clflush in your final submission +// It is only used for debug +void clflush(ADDR_PTR addr); + +char *string_to_binary(char *s); +char *binary_to_string(char *data); + +int string_to_int(char* s); + +#endif diff --git a/Part3-CTF/Makefile b/Part3-CTF/Makefile new file mode 100644 index 0000000..39e47ad --- /dev/null +++ b/Part3-CTF/Makefile @@ -0,0 +1,32 @@ +include ../cpu.mk + +TARGETS=attacker +UTILS=util.o + +all: $(TARGETS) + +$(UTILS): %.o: %.c %.h + $(CC) $(CFLAGS) -c $< + +%.o: %.c util.h + $(CC) $(CFLAGS) -c $< + +$(TARGETS): %:%.o util.o + $(CC) $(CFLAGS) $^ -o $@ + +run_victim-2: + taskset -c $(SENDER_CPU) ./victim-2 + +run_victim-3: + taskset -c $(SENDER_CPU) ./victim-3 + +run_victim-4: + taskset -c $(SENDER_CPU) ./victim-4 + +run_attacker: attacker + taskset -c $(RECEIVER_CPU) ./attacker + +.PHONY: clean + +clean: + $(RM) *.o $(HELPERS) $(TARGETS) diff --git a/Part3-CTF/attacker.c b/Part3-CTF/attacker.c new file mode 100644 index 0000000..7679cea --- /dev/null +++ b/Part3-CTF/attacker.c @@ -0,0 +1,12 @@ +#include "util.h" +// mman library to be used for hugepage allocations (e.g. mmap or posix_memalign only) +#include + +int main(int argc, char const *argv[]) { + int flag = -1; + + // Put your capture-the-flag code here + + printf("Flag: %d\n", flag); + return 0; +} diff --git a/Part3-CTF/util.c b/Part3-CTF/util.c new file mode 100755 index 0000000..9bf191b --- /dev/null +++ b/Part3-CTF/util.c @@ -0,0 +1,33 @@ + +#include "util.h" + +/* Measure the time it takes to access a block with virtual address addr. */ +CYCLES measure_one_block_access_time(ADDR_PTR addr) +{ + CYCLES cycles; + + asm volatile("mov %1, %%r8\n\t" + "lfence\n\t" + "rdtsc\n\t" + "mov %%eax, %%edi\n\t" + "mov (%%r8), %%r8\n\t" + "lfence\n\t" + "rdtsc\n\t" + "sub %%edi, %%eax\n\t" + : "=a"(cycles) /*output*/ + : "r"(addr) + : "r8", "edi"); + + return cycles; +} + +/* + * CLFlushes the given address. + * + * Note: clflush is provided to help you debug and should not be used in your + * final submission + */ +void clflush(ADDR_PTR addr) +{ + asm volatile ("clflush (%0)"::"r"(addr)); +} diff --git a/Part3-CTF/util.h b/Part3-CTF/util.h new file mode 100755 index 0000000..847284f --- /dev/null +++ b/Part3-CTF/util.h @@ -0,0 +1,25 @@ + +// You may only use fgets() to pull input from stdin +// You may use any print function to stdout to print +// out chat messages +#include + +// You may use memory allocators and helper functions +// (e.g., rand()). You may not use system(). +#include + +#include +#include +#include + +#ifndef UTIL_H_ +#define UTIL_H_ + +#define ADDR_PTR uint64_t +#define CYCLES uint32_t + +CYCLES measure_one_block_access_time(ADDR_PTR addr); + +void clflush(ADDR_PTR addr); + +#endif diff --git a/Part3-CTF/victim-2 b/Part3-CTF/victim-2 new file mode 100755 index 0000000..61c6c93 Binary files /dev/null and b/Part3-CTF/victim-2 differ diff --git a/Part3-CTF/victim-3 b/Part3-CTF/victim-3 new file mode 100755 index 0000000..ed1e69c Binary files /dev/null and b/Part3-CTF/victim-3 differ diff --git a/Part3-CTF/victim-4 b/Part3-CTF/victim-4 new file mode 100755 index 0000000..05c8fab Binary files /dev/null and b/Part3-CTF/victim-4 differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a2481e --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Hands-On Hardware Side Channels Lab + +This repository contains all the starting code you will need for the lab. + +A randomly-generated password will be emailed to you when the lab is released. Please log in and change your password immediately to something secure using the `passwd` command. Please note that your account will be deleted at the end of this course. + +## Starting the Lab + +As stated on the lab handout, you must first change the `SENDER_CPU` and `RECEIVER_CPU` variables in the Makefile to your assigned CPUs. These will be emailed to you along with your lab machine password when the lab is released. **Double check that you have set these values correctly.** + +After completing these steps, you are now ready to start the lab. Good luck! diff --git a/cpu.mk b/cpu.mk new file mode 100644 index 0000000..fd08cf6 --- /dev/null +++ b/cpu.mk @@ -0,0 +1,6 @@ +CC=gcc +CFLAGS=-O0 -I /usr/local +$(error Please set SENDER_CPU and RECEIVER_CPU to your assigned values) +SENDER_CPU= +RECEIVER_CPU= + diff --git a/update.sh b/update.sh new file mode 100644 index 0000000..c5fe63d --- /dev/null +++ b/update.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Updates repository to latest starter code +# +# Adapted from Oliver Beckstein's ASU-CompMethodsPhysics-PHY494 course 2016-2020 placed into the public domain + +# With GitHub template repositories one needs to use --allow-unrelated-histories +# at least once. https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template + +progname="$0" +REMOTE_NAME="startercode" +REMOTE_URL="https://github.com/CSAIL-Arch-Sec/SHD-CacheAttackLab.git" + +# progname, from top dir +UPDATESH="./deploy/$(basename $progname)" + +CONTACT_MESSAGE="Contact the instructor and TA with a screen shot of ALL output from running $0." + +function die () { + local msg="$1" err=${2:-1} + echo "ERROR: ${msg}." + exit $err +} + +# ensure everything relative to top dir +topdir="$(git rev-parse --show-toplevel)" || die "Failed to get rootdir" +cd "${topdir}" || die "Failed to get to the git root dir ${rootdir}" + + +# first time +# 1. set remote repo +# 2. merge histories between student (template) and remote skeleton + +if ! git remote get-url ${REMOTE_NAME} >/dev/null 2>&1; then + echo "Adding remote repository '${REMOTE_NAME}'." + git remote add ${REMOTE_NAME} ${REMOTE_URL} + + echo "Merging histories for the first time..." + set -x + git pull --allow-unrelated-histories -s recursive -X theirs --no-edit ${REMOTE_NAME} main || \ + { git rev-list -1 MERGE_HEAD >/dev/null 2>&1 && git merge --abort ; \ + git remote rm ${REMOTE_NAME}; \ + die "Failed to merge histories. ${CONTACT_MESSAGE}" $?; } + + set +x +fi + +echo "updating repository... git pull from ${REMOTE_NAME}" +git pull --no-edit ${REMOTE_NAME} main || die "Failed to pull from ${REMOTE_NAME}. ${CONTACT_MESSAGE}"