Как разработчики, мы часто имеем дело с конфиденциальными данными; кредитные карты, пароли, может быть, даже медицинские записи. Мы считываем такие данные с диска, или пользователь дает их нам через пользовательский интерфейс, и они остаются в памяти.

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

  1. Память процесса может быть записана незашифрованной в файл гибернации, и, возможно, файл подкачки не помечен как не подкачиваемый.
  2. Если ваше приложение когда-либо выйдет из строя, цепочка инструментов, используемая вашим клиентом, может автоматически загрузить файл дампа памяти на центральный сервер, чтобы инженеры могли выяснить причину сбоя.
  3. Вредоносное ПО может создать файл дампа, а затем провести атаку по словарю на память вашего процесса.

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

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

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

Во-первых, давайте взглянем на строки Delphi и посмотрим, как они могут нам помочь.

Хотя строки Delphi выглядят и воспринимаются как типы значений, на самом деле они являются указателями на динамически размещаемые данные; их значение содержит фактическое расположение строки в памяти.

Строки Delphi подсчитываются по ссылкам. Этот механизм отслеживает, сколько строковых переменных ссылаются на одну и ту же строку в памяти. Предположим, у вас есть переменная строкового типа с именем A. Когда переменная A присваивается переменной B, обе переменные «указывают» на одни и те же байты в памяти.

Этот механизм подсчета ссылок также используется для освобождения памяти, когда строка больше не используется. Когда обе наши переменные A и B выходят за пределы области видимости, счетчик ссылок падает до нуля, и связанная с ними память автоматически освобождается. Круто, похоже, у нас здесь хорошо.

К сожалению, это не так просто. Хотя библиотека времени выполнения Delphi освобождает память, на которую указывали ваши переменные, эти байты не обнуляются, и это может быть проблемой, если эти строки были кредитными картами или паролями.

Вот как вы можете воспроизвести эту проблему:

  1. Создайте новое приложение Delphi VCL, затем добавьте диалоговое окно пароля.
  2. Когда пользователь войдет в наше приложение, мы обработаем пароль, который он нам дал (надеюсь, мы сольем, а затем хэшируем пароль, но это история для другого дня).
  3. Мы позаботимся о том, чтобы строковая переменная Delphi, которая использовалась для хранения простого текстового пароля, вышла за рамки.
  4. Мы создадим файл дампа и
  5. Попробуем найти в дампе памяти текстовый пароль, воспользовавшись прекрасными Строками Марка Руссиновича.

Шаг 1. Создайте новое приложение Delphi VCL.

  1. Создайте новое приложение Delphi VCL.
  2. Перетащите кнопку из палитры инструментов на главную форму
  3. Нажмите: Файл › Создать › Другое › Файлы Delphi › Диалоговое окно пароля
  4. Дважды щелкните Button1 и введите этот код:
uses
  PASSWORD,
  System.StrUtils;
procedure TForm1.Button1Click(Sender: TObject);
var
  S: string;
begin
  with TPasswordDlg.Create(Self) do
  try
    if ShowModal <> mrOk then
      EXIT;
    S := Password.Text;
    if S <> '' then
      ShowMessage(ReverseString(S));
  finally
    Free;
  end;
end;

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

Шаг 4: Создайте файл дампа

  1. Запустите свое приложение
  2. Введите этот пароль: MyStupidPass123, а затем закройте диалоговое окно.
  3. Откройте диспетчер задач (Ctrl+Shift+Esc)
  4. Щелкните правой кнопкой мыши: Project1.exe (32-разрядная версия).
  5. Нажмите: Создать файл дампа.

Шаг 5. Найдите дамп памяти

  1. Скачайте превосходную Струны Марка Руссиновича.
  2. Разархивируйте strings.zip в %temp%\strings.exe.
  3. Откройте командную строку в %temp%.
  4. Предполагая, что файл дампа называется Project1.DMP, введите следующее:
strings "Project1.DMP" | findstr /i MyStupidPass123

Результат: вы найдете MyStupidPass123 в памяти, вероятно, несколько раз.

Как мы можем убедиться, что наши чувствительные строки обнуляются, предпочтительно автоматически (например, когда они выходят за рамки)? К сожалению, строковый тип Delphi не имеет деструктора и не отправляет нам ни события, ни сообщения.

Во Части 2 этой записи в блоге мы рассмотрим Интерфейсы и посмотрим, сможем ли мы совместить наши строки Delphi с этой другой концепцией подсчета ссылок.

Перейти к части 2 →