In developing cryptographic applications one must always be aware of side-channel attacks. The most common kind of side-channel attack is timing attack, where an attacker learns information about secret inputs by measuring the amount of time taken by a computation. Fortunately it is also relatively easy to defend against timing attacks. In most cases, making the control flow and memory access pattern of the cryptographic algorithm independent of its secret inputs is sufficient to eliminate timing side-channels.
Here I describe how to trace the control flow and memory access pattern of an AArch64 application using DynamoRIO. It can be considered a port of my previous x86 tracer (https://github.com/CharlieQiu2017/doit-enforcer).
We start from a simple Ubuntu Base environment. I previously described how to construct such an environment (https://gist.github.com/CharlieQiu2017/8a12ab79bd7f50586a0e36fb33a55997).
The latest release of DynamoRIO can be obtained from https://dynamorio.org/page_releases.html. We also need to install CMake since we will be building custom plugins for DynamoRIO.
Let's assume that DynamoRIO is extracted to /root/drio.
To test whether it is working correctly, we execute
/root/drio/bin64/drrun -t drmemtrace -offline -- progwhere prog can be any program of your choice, e.g. hello-world written in C.
Note: DynamoRIO currently has a bug which prevents it from handling ELF executables with empty segments. See DynamoRIO/dynamorio#7522.
A plugin for the DynamoRIO tracer contains three components:
- A plugin descriptor file;
- The plugin itself, as a dynamic library;
- A "plugin creator", invoked by drmemtrace to instantiate the plugin.
The descriptor file is simple. It contains only two lines:
TOOL_NAME=CryptoTracer
CREATOR_BIN64=../cryptotrace_build/libcrypto_trace_creator.so
This file should be named CryptoTracer.drcachesim and placed under /root/drio/tools.
The second line specifies the relative path (relative to /root/drio) to the plugin creator library.
We now describe how to build the plugin and plugin creator.
First create a directory for the plugin source code (let's call it cryptotrace).
We also need a separate directory (cryptotrace_build) for building the plugin. This is a requirement of CMake.
In the source tree, create a file CMakeLists.txt with the following content:
cmake_minimum_required(VERSION 3.22)
project(CryptoTracer)
set(DynamoRIO_DIR "/root/drio/cmake")
find_package(DynamoRIO)
if(NOT DynamoRIO_FOUND)
message(FATAL_ERROR "DynamoRIO not found")
endif(NOT DynamoRIO_FOUND)
add_library(crypto_trace SHARED crypto_trace.cpp)
use_DynamoRIO_drmemtrace(crypto_trace)
configure_DynamoRIO_client(crypto_trace)
add_library(crypto_trace_creator SHARED crypto_trace_creator.cpp)
use_DynamoRIO_drmemtrace(crypto_trace_creator)
target_link_libraries(crypto_trace_creator crypto_trace)This file specifies that we want to build two shared libraries called libcrypto_trace and libcrypto_trace_creator.
The line use_DynamoRIO_drmemtrace adds include paths of the DynamoRIO API.
Create a header file crypto_trace.hpp with the following content:
#ifndef CRYPTOTRACE_H
#define CRYPTOTRACE_H
#include "drmemtrace/analysis_tool.h"
using dynamorio::drmemtrace::analysis_tool_t;
using dynamorio::drmemtrace::memref_t;
analysis_tool_t * crypto_trace_tool_create (unsigned int verbosity);
class crypto_trace_t : public analysis_tool_t {
public:
explicit crypto_trace_t (unsigned int verbose);
virtual ~crypto_trace_t ();
std::string initialize () override;
bool process_memref (const memref_t &memref) override;
bool print_results () override;
bool parallel_shard_supported () override;
protected:
const static std::string TOOL_NAME;
};
#endifAll drmemtrace plugins should subclass analysis_tool_t.
The most important function is process_memref which is called when each instruction executes and when each memory access occurs.
The plugin creator (crypto_trace_creator.cpp) merely instantiates the plugin class and returns a pointer:
#include "crypto_trace.hpp"
#define EXPORT __attribute__((visibility("default")))
extern "C" EXPORT const char * get_tool_name () { return "CryptoTracer"; }
extern "C" EXPORT analysis_tool_t * analysis_tool_create () {
return crypto_trace_tool_create (0);
}Finally, the magic occurs inside crypto_trace.cpp:
#include <cstring>
#include <iostream>
#include <iomanip>
#include "dr_api.h"
#include "crypto_trace.hpp"
using std::cout;
using std::endl;
using dynamorio::drmemtrace::trace_type_t;
using dynamorio::drmemtrace::addr_t;
using dynamorio::drmemtrace::type_is_instr;
analysis_tool_t * crypto_trace_tool_create (unsigned int verbose) {
return new crypto_trace_t (verbose);
}
const std::string crypto_trace_t::TOOL_NAME = "CryptoTracer";
crypto_trace_t::crypto_trace_t (unsigned int verbose) {
cout << "CryptoTracer started" << endl;
}
std::string crypto_trace_t::initialize () { return std::string (""); }
crypto_trace_t::~crypto_trace_t () { }
bool crypto_trace_t::parallel_shard_supported () { return false; }
bool crypto_trace_t::print_results () { return true; }
bool crypto_trace_t::process_memref (const memref_t &memref) {
trace_type_t type;
memcpy (&type, &memref, sizeof (trace_type_t));
if (type_is_instr (type)) { // If an instruction execution event is reported
std::ios_base::fmtflags f(cout.flags());
// Then we print out the address of that instruction
cout << std::hex << std::setw(16) << std::setfill('0') << memref.instr.addr << std::endl;
cout.flags (f);
}
return true;
}Inside the build directory, invoke cmake ../cryptotrace to generate the Makefile.
Invoke make to build the plugin.
To run the plugin, execute:
~/drio/bin64/drrun -t drmemtrace -tool CryptoTracer -- prog [args]