2024 release

This commit is contained in:
Yuheng Yang 2024-02-07 09:23:56 -05:00
parent 3e503fa7b2
commit 8544adc054
35 changed files with 463 additions and 256 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
._*

3
Bonus-DeadDrop/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/*.o
/sender
/receiver

29
Bonus-DeadDrop/receiver.c Executable file
View File

@ -0,0 +1,29 @@
#include"util.h"
// mman library to be used for hugepage allocations (e.g. mmap or posix_memalign only)
#include <sys/mman.h>
int main(int argc, char **argv)
{
// [Bonus] TODO: 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) {
// [Bonus] TODO: Put your covert channel code here
}
printf("Receiver finished.\n");
return 0;
}

47
Bonus-DeadDrop/sender.c Executable file
View File

@ -0,0 +1,47 @@
#include"util.h"
// mman library to be used for hugepage allocations (e.g. mmap or posix_memalign only)
#include <sys/mman.h>
// [Bonus] 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
// [Bonus] 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);
// [Bonus] TODO:
// Put your covert channel code here
}
printf("Sender finished.\n");
return 0;
}

102
Bonus-DeadDrop/util.c Executable file
View File

@ -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);
}

3
Part1-Timing/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/main
/main-visual
/data

View File

@ -1,16 +1,19 @@
include ../cpu.mk include ../cpu.mk
all: main all: main main-visual
run: main run: main
@taskset -c $(SENDER_CPU) ./main @taskset -c $(SENDER_CPU) ./main
run-reference: reference run-visual: main-visual
@taskset -c $(SENDER_CPU) ./reference @taskset -c $(SENDER_CPU) ./main-visual
main: main.c Makefile main: main.c Makefile
@gcc main.c -o main @gcc main.c -o main
main-visual: main.c Makefile
@gcc main.c -DVISUAL -o main-visual
.PHONY: clean .PHONY: clean
clean: clean:

View File

@ -1,9 +1,17 @@
#include "utility.h" #include "utility.h"
// TODO: Uncomment the following lines and fill in the correct size #ifndef VISUAL
//#define L1_SIZE [TODO] #define PRINT_FUNC print_results_plaintext
//#define L2_SIZE [TODO] #else
//#define L3_SIZE [TODO] #define PRINT_FUNC print_results_for_visualization
#endif
#define LINE_SIZE 64
// [1.2] TODO: Uncomment the following lines and fill in the correct size
//#define L1_SIZE TODO
//#define L2_SIZE TODO
//#define L3_SIZE TODO
//#define BUFF_SIZE TODO
int main (int ac, char **av) { int main (int ac, char **av) {
@ -15,14 +23,12 @@ int main (int ac, char **av) {
uint64_t l3_latency[SAMPLES] = {0}; uint64_t l3_latency[SAMPLES] = {0};
// A temporary variable we can use to load addresses // A temporary variable we can use to load addresses
// The volatile keyword tells the compiler to not put this variable into a uint8_t tmp;
// register- it should always try to load from memory/ cache.
volatile char tmp;
// Allocate a buffer of 64 Bytes // Allocate a buffer of LINE_SIZE Bytes
// the size of an unsigned integer (uint64_t) is 8 Bytes // The volatile keyword tells the compiler to not put this variable into a
// Therefore, we request 8 * 8 Bytes // register -- it should always try to be loaded from memory / cache.
uint64_t *target_buffer = (uint64_t *)malloc(8*sizeof(uint64_t)); volatile uint8_t *target_buffer = (uint8_t *)malloc(LINE_SIZE);
if (NULL == target_buffer) { if (NULL == target_buffer) {
perror("Unable to malloc"); perror("Unable to malloc");
@ -31,11 +37,12 @@ int main (int ac, char **av) {
// [1.2] TODO: Uncomment the following line to allocate a buffer of a size // [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. // of your chosing. This will help you measure the latencies at L2 and L3.
//uint64_t *eviction_buffer = (uint64_t)malloc(TODO); //volatile uint8_t *eviction_buffer = (uint8_t *)malloc(BUFF_SIZE);
// Example: Measure L1 access latency, store results in l1_latency array // Example: Measure L1 access latency, store results in l1_latency array
for (int i=0; i<SAMPLES; i++){ for (int i=0; i<SAMPLES; i++){
// Step 1: bring the target cache line into L1 by simply accessing the line // Step 1: bring the target cache line into L1 by simply accessing
// the line
tmp = target_buffer[0]; tmp = target_buffer[0];
// Step 2: measure the access latency // Step 2: measure the access latency
@ -59,14 +66,17 @@ int main (int ac, char **av) {
// Print the results to the screen // Print the results to the screen
// [1.5] Change print_results to print_results_for_python so that your code will work // When compile to main and used by `make run`,
// with the python plotter software // it uses print_results_plaintext
print_results(dram_latency, l1_latency, l2_latency, l3_latency); // When compile to main-visual and used by `run.py`,
// it uses print_results_for_visualization
PRINT_FUNC(dram_latency, l1_latency, l2_latency, l3_latency);
free(target_buffer); free((uint8_t *)target_buffer);
// [1.2] TODO: Uncomment this line once you uncomment the eviction_buffer creation line // [1.2] TODO: Uncomment this line once you uncomment the eviction_buffer
//free(eviction_buffer); // creation line
//free((uint8_t *)eviction_buffer);
return 0; return 0;
} }

Binary file not shown.

View File

@ -7,11 +7,8 @@ from tqdm import tqdm
# Run your code: # Run your code:
# Make sure that your code is using print_results_for_python # Make sure that your code is using print_results_for_visualization
#executable_filename = ['make', 'run'] executable_filename = ['make', 'run-visual']
# Run reference code:
executable_filename = ['make', 'run-reference']
num_runs = 100 num_runs = 100

View File

@ -15,7 +15,7 @@
// Function to read the time stamp counter, which is called tsc for short // Function to read the time stamp counter, which is called tsc for short
// "rdtscpp" returns a 32bit unsigned integer // "rdtscpp" returns a 32bit unsigned integer
// "rdtscpp64" return a 64 bit unsigned integer // "rdtscpp64" return a 64 bit unsigned integer
// Details in https://www.felixcloutier.com/x86/rdtscpp // Details in https://www.felixcloutier.com/x86/rdtscp
static inline uint32_t rdtscpp() { static inline uint32_t rdtscpp() {
uint32_t rv; uint32_t rv;
asm volatile ("rdtscpp": "=a" (rv) :: "edx", "ecx"); asm volatile ("rdtscpp": "=a" (rv) :: "edx", "ecx");
@ -98,7 +98,7 @@ int compare(const void *p1, const void *p2) {
// Print out the latencies you measured // Print out the latencies you measured
void print_results(uint64_t* dram, uint64_t* l1, uint64_t* l2, uint64_t* l3) { void print_results_plaintext(uint64_t* dram, uint64_t* l1, uint64_t* l2, uint64_t* l3) {
qsort(dram, SAMPLES, sizeof(uint64_t), compare); qsort(dram, SAMPLES, sizeof(uint64_t), compare);
qsort(l1, SAMPLES, sizeof(uint64_t), compare); qsort(l1, SAMPLES, sizeof(uint64_t), compare);
qsort(l2, SAMPLES, sizeof(uint64_t), compare); qsort(l2, SAMPLES, sizeof(uint64_t), compare);
@ -120,7 +120,7 @@ void print_results(uint64_t* dram, uint64_t* l1, uint64_t* l2, uint64_t* l3) {
} }
// Format the latencies for part 1.5 // Format the latencies for part 1.5
void print_results_for_python(uint64_t* dram, uint64_t* l1, uint64_t* l2, uint64_t* l3) void print_results_for_visualization(uint64_t* dram, uint64_t* l1, uint64_t* l2, uint64_t* l3)
{ {
qsort(dram, SAMPLES, sizeof(uint64_t), compare); qsort(dram, SAMPLES, sizeof(uint64_t), compare);
qsort(l1, SAMPLES, sizeof(uint64_t), compare); qsort(l1, SAMPLES, sizeof(uint64_t), compare);

View File

@ -1,29 +0,0 @@
#include"util.h"
// mman library to be used for hugepage allocations (e.g. mmap or posix_memalign only)
#include <sys/mman.h>
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;
}

View File

@ -1,45 +0,0 @@
#include"util.h"
// mman library to be used for hugepage allocations (e.g. mmap or posix_memalign only)
#include <sys/mman.h>
// 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;
}

View File

@ -1,102 +0,0 @@
#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);
}

3
Part2-FlushReload/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/shared_file
/*.o
/attacker

View File

@ -0,0 +1,26 @@
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:
taskset -c $(SENDER_CPU) ./victim
run_attacker: attacker
taskset -c $(RECEIVER_CPU) ./attacker
.PHONY: clean
clean:
$(RM) *.o $(HELPERS) $(TARGETS)

View File

@ -0,0 +1,24 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include "util.h"
int main() {
int flag = -1;
// buf is shared between the attacker and the victim
char *buf = allocate_shared_buffer();
// [2.1] TODO: Put your capture-the-flag code here
printf("Flag: %d\n", flag);
deallocate_shared_buffer(buf);
return 0;
}

View File

@ -0,0 +1,11 @@
from pathlib import Path
def main():
out_path = Path("shared_file")
with out_path.open(mode="wb"):
out_path.write_bytes(bytearray([0] * 1024 * 128))
if __name__ == '__main__':
main()

98
Part2-FlushReload/util.c Executable file
View File

@ -0,0 +1,98 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#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));
}
int fd;
size_t file_size;
char *allocate_shared_buffer() {
const char *filepath = "shared_file";
int fd = open(filepath, O_RDONLY, (mode_t)0600);
if (fd == -1)
{
perror("Error opening file for writing, please run `python3 gen_file.py` to get the file for shared buf");
exit(EXIT_FAILURE);
}
struct stat fileInfo = {0};
if (fstat(fd, &fileInfo) == -1)
{
perror("Error getting the file size");
exit(EXIT_FAILURE);
}
if (fileInfo.st_size == 0)
{
fprintf(stderr, "Error: File is empty, nothing to do\n");
exit(EXIT_FAILURE);
}
if (fileInfo.st_size < SEC_RANGE * ALIGN) {
close(fd);
perror("File is too small\n");
exit(EXIT_FAILURE);
}
char *buf = mmap(0, fileInfo.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (buf == MAP_FAILED)
{
close(fd);
perror("Error mmapping the file");
exit(EXIT_FAILURE);
}
file_size = fileInfo.st_size;
return buf;
}
void deallocate_shared_buffer(char *buf) {
// Don't forget to free the mmapped memory
if (munmap(buf, file_size) == -1)
{
close(fd);
perror("Error un-mmapping the file");
exit(EXIT_FAILURE);
}
// Un-mmaping doesn't close the file, so we still need to do that.
close(fd);
}

31
Part2-FlushReload/util.h Executable file
View File

@ -0,0 +1,31 @@
// You may only use fgets() to pull input from stdin
// You may use any print function to stdout to print
// out chat messages
#include <stdio.h>
// You may use memory allocators and helper functions
// (e.g., rand()). You may not use system().
#include <stdlib.h>
#include <inttypes.h>
#include <time.h>
#include <stdbool.h>
#ifndef UTIL_H_
#define UTIL_H_
#define SEC_RANGE 1024
#define ALIGN 128
#define ADDR_PTR uint64_t
#define CYCLES uint32_t
CYCLES measure_one_block_access_time(ADDR_PTR addr);
void clflush(ADDR_PTR addr);
char *allocate_shared_buffer();
void deallocate_shared_buffer(char *buf);
#endif

BIN
Part2-FlushReload/victim Executable file

Binary file not shown.

2
Part3-PrimeProbe/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/*.o
/attacker

View File

@ -23,6 +23,9 @@ run_victim-3:
run_victim-4: run_victim-4:
taskset -c $(SENDER_CPU) ./victim-4 taskset -c $(SENDER_CPU) ./victim-4
run_victim-16:
taskset -c $(SENDER_CPU) ./victim-16
run_attacker: attacker run_attacker: attacker
taskset -c $(RECEIVER_CPU) ./attacker taskset -c $(RECEIVER_CPU) ./attacker

View File

@ -5,7 +5,7 @@
int main(int argc, char const *argv[]) { int main(int argc, char const *argv[]) {
int flag = -1; int flag = -1;
// Put your capture-the-flag code here // [3.2] TODO: Put your capture-the-flag code here
printf("Flag: %d\n", flag); printf("Flag: %d\n", flag);
return 0; return 0;

BIN
Part3-PrimeProbe/victim-16 Executable file

Binary file not shown.

View File

@ -4,6 +4,6 @@ This repository contains all the starting code you will need for the lab.
## Starting the Lab ## 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.** As stated on the lab handout, you must first change the `SENDER_CPU` and `RECEIVER_CPU` variables in the `cpu.mk` to your assigned CPUs. **Double check that you have set these values correctly.**
After completing these steps, you are now ready to start the lab. Good luck! After completing these steps, you are now ready to start the lab. Good luck!

38
report.md Normal file
View File

@ -0,0 +1,38 @@
## 1-1
**Fill in the blanks in the following table using the information you gathered about the cache configuration of the lab machine.**
| Cache | Cache Line Size | Total Size | Associativity | Number of Sets | Raw Latency |
| ----- | --------------- | ---------- | ------------- | -------------- | ----------- |
| L1-D | 64 | | | | |
| L2 | | | | | |
| L3 | | | | | |
## 1-3
**After completing your code, generate the histogram pdf file and include it in the lab report.**
<!-- ![Histogram](./Part1-Timing/Histogram.pdf) -->
## 1-4
**Based on the generated histogram, report two thresholds, one to distinguish between L2 and L3 latency and the other to distinguish between L3 and DRAM latency.**
L2-L3 threshold:
L3-DRAM threshold:
## 2-2
**If the victim want to read the kth byte of a file, where k is a secret, how can he/she avoid leaking the secret to the attacker?**
## 3-1
**Given a 64-bit virtual address, fill in the table below.**
| Page Size | 4KB | 2MB |
| ------------------------------------- | ------- | ------- |
| Page Offset Bits | | |
| Page Number Bits | | |
| L2 Set Index Bits | | |
| L2 Set Index Bits Fully Under Control | | |

View File

@ -1,48 +0,0 @@
#!/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}"