Безопасный распределитель памяти в C++

Я хочу создать распределитель, который предоставляет память со следующими атрибутами:

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

Идея состоит в том, что он будет содержать конфиденциальную информацию (например, информацию о лицензии), которая должна быть недоступна для пользователя. Я провел обычное онлайн-исследование и спросил об этом еще несколько человек, но не могу найти подходящего места для начала рассмотрения этой проблемы.

Обновления

Джош упоминает использование VirtualAlloc для установки защиты области памяти. Я создал собственный распределитель (показан ниже). Я обнаружил, что с помощью VirtualLock функции он ограничивает объем памяти, который я могу выделить. Хотя, похоже, это сделано специально. Поскольку я использую его для небольших объектов, это не проблема.

//
template<class _Ty>
class LockedVirtualMemAllocator : public std::allocator<_Ty>
{
public:
    template<class _Other>
    LockedVirtualMemAllocator<_Ty>& operator=(const LockedVirtualMemAllocator<_Other>&)
    {   // assign from a related LockedVirtualMemAllocator (do nothing)
        return (*this);
    }

    template<class Other>
    struct rebind {
        typedef LockedVirtualMemAllocator<Other> other;
    };

    pointer allocate( size_type _n )
    {
        SIZE_T  allocLen = (_n * sizeof(_Ty));
        DWORD   allocType = MEM_COMMIT;
        DWORD   allocProtect = PAGE_READWRITE;
        LPVOID pMem = ::VirtualAlloc( NULL, allocLen, allocType, allocProtect );
        if ( pMem != NULL ) {
            ::VirtualLock( pMem, allocLen );
        }
        return reinterpret_cast<pointer>( pMem );
    }
    pointer allocate( size_type _n, const void* )
    {
        return allocate( _n );
    }

    void deallocate(void* _pPtr, size_type _n )
    {
        if ( _pPtr != NULL ) {
            SIZE_T  allocLen = (_n * sizeof(_Ty));
            ::SecureZeroMemory( _pPtr, allocLen );
            ::VirtualUnlock( _pPtr, allocLen );
            ::VirtualFree( _pPtr, 0, MEM_RELEASE );
        }
    }
};

и используется

 //a memory safe std::string
 typedef std::basic_string<char, std::char_traits<char>, 
                           LockedVirtualMemAllocato<char> > modulestring_t;

Тед Персиваль упоминает mlock, но у меня пока нет его реализации.

Мне также очень пригодилась «Практическая криптография» Нила Фургюсона и Брюса Шнайера .

Ответов (13)

Решение

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

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

Вы не можете защитить содержимое памяти от владельца системы. Голливуд и музыкальная индустрия страдали от этого годами. Если бы это было возможно, они бы это уже делали.

@Крис

О, но с надежными вычислениями вы можете использовать занавес памяти! :-П

Но тогда вы должны быть готовы платить за компьютер, которым владеет другой человек. :п

@roo

Я действительно надеялся, что это возможно, но еще не нашел этого. Ваш пример только что заставил меня понять, что это именно то, что мы пытаемся сделать - разрешить доступ к файлам только в контексте нашей программы и, таким образом, сохранить IP.

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

Это определенно проблема. Вы можете хранить что-то в безопасности до тех пор, пока вы никогда не предоставите доступ, но как только вы предоставите доступ, ваш контроль исчезнет. Вы можете немного усложнить задачу, но это все.

В системах Unix вы можете использовать mlock (2) для блокировки страниц памяти в ОЗУ, предотвращая их подкачку.

mlock () и mlockall () соответственно блокируют часть или все виртуальное адресное пространство вызывающего процесса в ОЗУ, предотвращая подкачку этой памяти в область подкачки.

Существует ограничение на объем памяти, который может блокировать каждый процесс, он может отображаться ulimit -l и измеряется в килобайтах. В моей системе ограничение по умолчанию составляет 32 КБ на процесс.

Лучше всего реализовать что-то похожее на класс SecureString .NET и быть очень осторожным, чтобы обнулить все копии ваших данных в виде открытого текста, как только вы закончите (не забудьте выполнить очистку, даже если выбрасываются исключения). Хороший способ сделать это с помощью std::string, например, использовать настраиваемый распределитель .

В Windows, если вы используете CryptProtectMemory (или RtlEncryptMemory для старых систем), пароль шифрования хранится в не выгружаемой на страницу памяти (ядро?). В моем тестировании эти функции чертовски быстрые, особенно. с учетом защиты, которую они вам предоставляют.

На других системах мне нравится использовать Blowfish, так как это хорошее сочетание скорости и силы. В последнем случае вам придется случайным образом сгенерировать собственный пароль (16+ байтов энтропии для Blowfish) при запуске программы. К сожалению, вы не так много можете сделать для защиты этого пароля без поддержки ОС, хотя вы можете использовать общие методы запутывания, чтобы встроить жестко запрограммированное значение соли в свой исполняемый файл, которое вы можете комбинировать с паролем (каждый немного помогает).

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

установить Libsodium, использовать механизмы распределения, #including <sodium.h>

Охраняемое выделение кучи

Медленнее, чем malloc () и его друзья, они требуют 3 или 4 дополнительных страницы виртуальной памяти.

void *sodium_malloc(size_t size);

Выделите память для хранения конфиденциальных данных с помощью sodium_malloc() и sodium_allocarray() . Вам нужно будет сначала позвонить, sodium_init() прежде чем использовать эти защитные устройства кучи.

void *sodium_allocarray(size_t count, size_t size);

В sodium_allocarray() функции возвращает указатель из которого подсчета объектов , которые являются размером байт памяти каждым могут быть доступно. Он предоставляет те же гарантии, что и, sodium_malloc() но также защищает от арифметических переполнений при count * size превышении SIZE_MAX .

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

Кроме того, защита областей памяти выделяется таким образом можно изменить с помощью операции блокировки памяти: sodium_mprotect_noaccess(), sodium_mprotect_readonly() и sodium_mprotect_readwrite() .

После sodium_malloc можно использовать sodium_free() для разблокировки и освобождения памяти. На этом этапе реализации рассмотрите возможность обнуления памяти после использования.

обнулить память после использования

void sodium_memzero(void * const pnt, const size_t len);

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

Функция натрия_memzero () пытается эффективно обнулить len байтов, начиная с pnt, даже если к коду применяется оптимизация.

блокировка выделения памяти

int sodium_mlock(void * const addr, const size_t len);

В sodium_mlock() функции блокировки по крайней мере LEN байт памяти , начиная с адреса. Это может помочь избежать подкачки конфиденциальных данных на диск.

int sodium_mprotect_noaccess(void *ptr);

Функция натрия_mprotect_noaccess () делает область, выделенную с помощью натрия_malloc () или натрия_allocarray (), недоступной. Его нельзя прочитать или записать, но данные сохраняются. Эта функция может использоваться, чтобы сделать конфиденциальные данные недоступными, за исключением случаев, когда это действительно необходимо для конкретной операции.

int sodium_mprotect_readonly(void *ptr);

Функция натрия_mprotect_readonly () отмечает область, выделенную с помощью натрия_malloc () или натрия_allocarray (), как доступную только для чтения. Попытка изменить данные приведет к завершению процесса.

int sodium_mprotect_readwrite(void *ptr);

В sodium_mprotect_readwrite() Функция отмечает область , выделенные с использованием sodium_malloc() или , sodium_allocarray() как для чтения и записи, после того , как были защищены с помощью sodium_mprotect_readonly() или sodium_mprotect_noaccess() .

То, что вы просите, обрабатывается на уровне ОС. Как только данные попадают в вашу программу, они могут быть выгружены.

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

@ Дерек: О, но с надежными вычислениями вы можете использовать занавес памяти ! :-P </devils-advocate>

Давайте рассмотрим это понемногу:

Я хочу создать распределитель, который предоставляет память со следующими атрибутами:

Это достаточно честно.

* cannot be paged to disk.

Это будет сложно. Насколько мне известно, вы не можете отключить виртуальную подкачку, поскольку она обрабатывается ОС. Если есть способ, то вы будете копаться в недрах ОС.

* is incredibly hard to access through an attached debugger

Вы можете запустить его через PGP и сохранить в зашифрованном виде в памяти и при необходимости расшифровать. Огромный удар по производительности.

Идея состоит в том, что он будет содержать конфиденциальную информацию (например, информацию о лицензии), которая должна быть недоступна для пользователя. Я провел обычное исследование в Интернете и спросил об этом еще несколько человек, но я не могу найти подходящего места для начала рассмотрения этой проблемы.

Храните всю конфиденциальную информацию вне машины. Шутки в сторону. Не храните конфиденциальную информацию в памяти. Напишите специальную процедуру удаления, которая будет автоматически удалять все данные из любых выполняемых вами распределений. Никогда не допускайте общего доступа к машине с чувствительными материалами. Если вы выполняете доступ к базе данных, убедитесь, что весь доступ очищен перед запуском. Доступ разрешен только людям с определенными учетными записями. Нет доступа к общей группе.

Кстати, какие существуют другие методы доступа к памяти процесса, кроме подключения отладчика?

Делаем дамп памяти.

@graham

Вы можете запустить его через PGP и сохранить в зашифрованном виде в памяти и при необходимости расшифровать. Огромный удар по производительности.

Тогда вам придется держать ключ в памяти. Это сделало бы это немного сложнее, но определенно не невозможно. Любой мотивированный человек все равно сможет получить данные из памяти.

@ Дерек Парк

Он только сказал сильнее, не невозможно. PGP сделает это сложнее, а не невозможным.

Если вы разрабатываете для Windows, есть способы ограничить доступ к памяти, но полностью заблокировать другие невозможно. Если вы надеетесь сохранить секрет в секрете, прочтите « Написание безопасного кода», в котором подробно рассматривается эта проблема, но имейте в виду, что у вас нет возможности узнать, выполняется ли ваш код на реальной или виртуальной машине. Для работы с криптовалютой существует множество API-интерфейсов Win32, которые обрабатывают такие вещи, включая безопасное хранение секретов - об этом говорится в книге. Подробности можно найти в Microsoft CyproAPI в Интернете ; Разработчики ОС осознают эту проблему и необходимость защиты открытого текста (опять же, прочтите Написание кода безопасности ).

Функция Win32 API VirtualAlloc- это распределитель памяти на уровне ОС. Позволяет установить защиту доступа; что вы могли бы сделать, так это установить доступ к PAGE_GUARD или PAGE_NOACCESS и переключить доступ к чему-то более дружелюбному, пока ваша программа читает, и затем сбросить его, но это просто горб, если кто-то очень сильно пытается подсмотреть ваш секрет.

Таким образом, взгляните на криптографические API-интерфейсы на своей платформе, они решат проблему лучше, чем то, что вы взламываете сами.

Вы не можете защитить содержимое памяти от владельца системы. Голливуд и музыкальная индустрия страдали от этого годами. Если бы это было возможно, они бы уже это делали.

Вы когда- нибудь видели Защищенные процессы в Vista (и выше) (прямая загрузка .doc ). Я считаю, что усиленная защита операционной системы - это любезность индустрии развлечений.