Данная статья направлена, прежде всего, на начинающего специалиста, который только
приступил к исследованию методов и способов обеспечения информационной безопасности исполняемого программного кода. С такой задачей рано или поздно сталкиваются все разработчики ПО и системные инженеры, что и произошло на одном из проектов компании Альтирикс системс, в рамках которого необходимо было реализовать защищенное исполнение программного кода в условно не защищенной среде. Для чего, помимо, уже известных и хорошо описанных методов и средств защиты информации, была выбрана, достаточно редко применяемая в российских проектах технология Trusted Execution Environment (TEE) или, говоря по-русски, технология доверенных сред исполнения. Конкретно в этой статье мы решили описать практический пример использования анклавов процессора Intel для доверенной среды исполнения кода (Intel Software Guard Extensions или SGX).

Доверенные среды исполнения поддерживаются далеко не только процессорами данного производителя. Также, TEE поддерживается рядом процессоров AMD (Secure Execution Environment, Secure Technology), процессорами с архитектурой ARM (TrustZone), процессорами с архитектурой RISC-V. Кроме того, TEE поддерживается современными мейнфреймами IBM Z. Мы же выбрали Intel SGX для своего примера поскольку считаем, что на момент написания статьи (лето 2020г.) процессоры Intel наиболее популярны и доступны для начинающих специалистов на постсоветском пространстве. С полным перечнем моделей процессоров Intel с поддержкой Intel SGX можно ознакомиться на сайте Intel в разделе Intel product specifications (ARK), выбрав для поиска соответствующую технологию. И да, возможно, воспользоваться эмуляциями Intel SGX для учебных или исследовательских целей. Работа с несколькими из таких эмуляций выявила ряд сложностей в их настройке. Также нужно понимать, что для реальных “боевых” проектов никакая эмуляция технологии основанной на аппаратом функционале, естественно, недопустима.

Любой ваш отзыв, особенно, с замечаниями и дополнениями от специалистов уже имеющих опыт применения TEE в своих проектах, или же с вопросами от тех, кто точно так же только начинает погружаться в эту технологию, поспособствуют более детальному раскрытию данной темы в следующих статьях. Заранее спасибо!

Введение


Основной вопрос, который мы задаем в начале пути изучения доверенных сред исполнения: можем ли мы доверять компонентам системы компьютера? А если можем, то каким? Разработчики, а в частности инженеры Intel, дают на этот вопрос однозначный ответ: никому, кроме самого Intel. Что под этим подразумевается? Предлагаю поподробнее в этом разобраться.

Кольца привилегий


В целях безопасности, компоненты системы любого компьютера разделены по уровням привилегий. Во всех современных системах, базирующихся на процессорах Intel и не только, реализована кольцевая система уровней привилегий. От внешнего к внутреннему идет расширение полномочий для кода, который обрабатывается в данный момент процессором.


Кольцо №3. На внешнем кольце располагаются все пользовательские приложения, которые мы используем на компьютере в повседневной жизни, они имеют низший уровень доступа.
Кольцо №2 и №1. На данных уровнях располагаются операционные системы и драйвера устройств.
Кольцо №0. Режим супервизора. Здесь расположено ядро операционной системы (управление периферий, распределение ресурсов между процессами), а также системные драйвера.
Кольцо №-1. Гипервизор. Отвечает за распределение ресурсов в случае, если на компьютере одновременно запущены несколько операционных систем, а также отвечает за их изоляцию.
Кольцо №-2. Режим системного управления (SMM – System Management Mode). Управляет энергообеспечением системы, управляет платами расширения.

Мы можем формировать всё новые и новые кольца для ограничения полномочий компонентов иерархии, создавая всё более сложную и нагруженную систему. Однако, таким образом работа для злоумышленника только облегчится: чем сложнее система, тем проще найти в ней уязвимость. Но каким же образом обеспечить дополнительный уровень безопасности там, где это требуется? Ответ состоит из одного слова.

Анклавы


Основной задачей злоумышленника является получение уровня привилегий, который обеспечил бы ему доступ к необходимым ресурсам системы. Если это секрет приложения-жертвы, то злонамеренному приложению необходим именно тот уровень доступа, который отвечает за хранение секретов в системе. Отсюда напрашивается вывод, что управление секретами приложений стоит доверить самому внутреннему кольцу, ведь доступ туда получить сложнее всего. Однако, данный подход был несколько переосмыслен. Теперь все секреты хранятся на одном уровне с пользовательскими приложениями, как и код, который этими секретами управляет за одним условием: никто, абсолютно никто, кроме процессора, не может получить туда доступ. Программа и данные как бы упакованы в хранилище, в данном случае это хранилище называется анклавом (Enclave – закрытый, запертый), ключ от которого есть только у процессора.


Приложения, работающие с доверенной средой


Чем проще система, чем меньше кода в ней прописано, тем сложнее её вскрыть на основе дыр в защите (мы не говорим о принципиально незащищенных системах), получаем некоторую аксиому: код, работающий с секретом, должен быть максимально простым и коротким. Упаковывать в анклав весь код программы нецелесообразно, поэтому приложение, использующее анклавы, должно быть разделено на две части: «доверенную» и «недоверенную». В доверенной хранятся анклавы (их может быть несколько), а в недоверенной – основной код программы.

Доверенная часть представляет из себя набор функций и процедур, называемых ECALL (Enclave Call). Сигнатура таких функций должна быть прописана в специальном header-файле, а их реализация в файле с исходным кодом. В целом, подход схож с тем, что мы используем при обычном прописывании хедеров, однако, в данном контексте используется специальный C-подобный язык EDL (Enclave Definition Language). Также необходимо прописать прототипы тех функций, которые можно будет вызвать изнутри анклава, такие функции называются OCALL (Outside Call). Прототипы прописываются в том же хедере, где и ECALL-функции, а реализация, в отличие от ECALL, прописывается соответственно в недоверенной части приложения.
Доверенный и недоверенный код жестко связываются между собой сертификацией с использованием протокола Диффи-Хеллмана. За процедуру подписи отвечает процессор, где и хранится ключ обмена информации, обновляющийся каждый раз при перезагрузке системы. Содержимое анклавов хранится в общей памяти, используемой пользовательскими приложениями, однако хранение происходит в зашифрованном виде. Расшифровать содержимое может только процессор. В идеализированном мире, где код анклавов прописан без единого бага, а все железо работает точно так, как это задумал производитель и никак иначе, мы бы получили универсальную, полностью защищенную систему. Основным достоинством данной системы является исполнение секретной части на том же процессоре, где исполняются все остальные программы, в том числе и пользовательские.

Однако, в последние несколько лет перед широкой аудиторий предстало большое количество микроархитектурных уязвимостей современных процессоров, позволяющих получить доступ внутрь анклава: Foreshadow (уязвимость класса Spectre), SGAxe, Zombieload, CacheOut и многие другие. Нет никакой гарантии того, что это список не пополнится очередной серьезной аппаратной уязвимостью, программное исправление которой не иначе как программной «заплаткой» назвать будет нельзя. Возможно, мы доживем до того времени, когда миру будет представлена абсолютно новая архитектура процессоров, в которой будут исправлены все недостатки, а пока, стоит говорить о том, что у нас есть под рукой. А под рукой у нас есть универсальный, мощный инструмент, серьезно повышающий безопасность современных систем. Повышающий настолько, что он реализован в той или иной интерпретации в миллиардах устройств по всему миру: от умных часов, смартфонов до огромных вычислительных кластеров.

Hello world!


Перейдем от теории к практике. Напишем небольшую программу, которая реализует ставшую уже каноничной задачу: вывести строку «Hello world!». В данной интерпретации укажем ещё и место, откуда будет отправлено послание.

Для начала необходимо скачать и установить SDK для работы с SGX с официального сайта. Для скачивания необходимо пройти простую процедуру регистрации. На этапе установки будет предложено интегрировать пакет разработки в имеющуюся на компьютере версию VS, сделайте это. Всё готово для успешной реализации первого проекта с использованием SGX.


Запускаем VS и создаем проект Intel SGX.


Выбираем имя для проекта и для решения и ждем «далее».

Далее будет предложено выбрать конфигурацию проекта, ничего не меняйте, оставьте те значения, которые предложены изначально.



Затем добавьте к созданному решение ещё один проект: обычное консольное приложение C++.
В результате в диалоговом окне проектов должна получиться следующая картина:



Затем необходимо связать анклав с недоверенной частью. Жмем правой кнопкой на проект «Untrusted part».



Далее необходимо изменить некоторые свойства проектов.

image



Это необходимо проделать для корректной работы программы. Повторяем действия для обоих проектов.

Также необходимо обозначить в свойствах решения главный проект.


Всё, наша программа готова к реализации.

В данной программе будет 3 файла, с которыми мы будем работать: Enclave.edl (тот самый хедер), Enclave.cpp (прописана реализация ECALL’ов), Untrusted Part.cpp (главный файл проекта – недоверенная часть).

Поместим в файлы следующий код:

Untusted Part.cpp:

#define ENCLAVE_FILE "Enclave.signed.dll" //библиотека, через которую осуществляется подпись анклава

#include "sgx_urts.h" //базовый хедер, в котором упакованы функции создания и удаления анклава и многие другие
#include "Enclave_u.h" //подключение автоматически сгенерированного файла
#include "stdio.h"

void print_string(char* buf) //OCALL для вывода текстовой строки - секрета из анклава
{
	printf("ocall output: %s\n", buf);
}

int main()
{
	sgx_enclave_id_t eid; // id анклава, в проекте может быть несколько анклавов, каждый со своим id
	sgx_status_t ret = SGX_SUCCESS; //необходимо для отлавливания ошибок на этапе доступа к анклаву
	sgx_launch_token_t token = { 0 }; //инициализация токена запуска для анклава
	int updated = 0; // токен запуска не был изменен 
	const int BUF_LEN = 30; //размер буфера данных, которые мы отправляем в анклав

	ret = sgx_create_enclave(ENCLAVE_FILE, SGX_DEBUG_FLAG, &token, &updated, &eid, NULL); //функция создания анклава

	if (ret != SGX_SUCCESS)
	{
		printf("Failed to create enclave with error number: %#x\n", ret); //отлавливаем ошибки 
		return 0;
	}
	char buf[BUF_LEN]; //создаем пустую переменную, в которую запишем секрет из анклава

	enclaveChat(eid, buf, BUF_LEN); //вызов ECALL функции 

	printf("\noutput form main(): %s\n", buf); //вывод полученного секрета
}

Enclave.edl:

enclave {
    from "sgx_tstdc.edl" import *;

    trusted {
        /* define ECALLs here. */
        public void enclaveChat([out, size=len] char* str, size_t len);
        /* прописываем сигнатуру функции, которая работает с секретом. OUT - указывает на то, что данные 
           отправляются из анклава в основную программу, out по отношению к местоположению функции.
           Также, для строковых и символьных переменных необходимо прописывать размер буфера информации,
           который мы отправляем или получаем из анклава.
        */
    };

    untrusted {
        /* define OCALLs here. */
        void print_string([in, string] char* buf); //прописываем сигнатуру функции, которая будет осуществлять вывод секрета
    };
};

Enclave.cpp:

#include "Enclave_t.h"

#include "sgx_trts.h"
#include <cstring>

void enclaveChat(char* str, size_t len)
{
	char* secret = "Hello from better place"; // Наша секретная фраза

	memcpy(str, secret, len); //копируем секрет по ссылке, которую получили

	print_string(secret); //вызов OCALL-функции вывода строки
}

Жмем f7 – собираем решение, а затем ctrl+f5 для запуска.

Если у вас выдало ошибку следующего содержания:


убедитесь, что в BIOS активирован Intel SGX: Bios: Security/IntelSGX/Enabled.

В случае, если никаких ошибок не последовало, а перед экраном на консоли вы увидели следующие строки:


… то могу поздравить, ваша первая программа с использованием технологии Intel SGX готова. Надеюсь, комментарии в коде были исчерпывающими для понимания, в противном случае, вы всегда можете задать вопросы здесь в комментариях или в личных сообщениях.