Задумался я тут над смыслом подписания компоновочных блоков .Net. Наверняка,
ты тоже подписывал свои библиотеки, чтобы установить их в GAC. В ходе
сегодняшнего расследования мы научимся изменять подписанные сборки, не обладая
исходниками и секретными ключами.

 

Приватные сборки

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

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

Проведем эксперимент, создадим небольшую библиотечку:

Библиотека signedLib.dll

namespace signedLib
{
public class sLib
{
public static int GetNumber() { return 1; }
}
}

Подпишем ее и добавим к проекту консольного приложения:

Консольное приложение changeKey.exe

namespace changeKey
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(signedLib.sLib.GetNumber());
Console.ReadLine();
}
}
}

Затем скомпилируем релиз проекта. С помощью .NET Reflector [1] и плагина
Reflexil [2] отредактируем IL-код подписанной библиотеки (signedLib.dll), так
что GetNumber() будет возвращать не "1", а "2". Консольное приложение не
заметило подмены и вывело "2". Вывод: подменить/изменить приватную сборку со
строгим именем очень просто. Другие сборки, ссылающиеся на измененную, никак на
это не реагируют, несмотря на то, что они были скомпилированы с оригинальной.
Обращаю внимание, что речь идет именно о приватных сборках, со сборками в GAC
дело обстоит иначе.

 

Сборки в GAC

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

Вносить изменения в сборки, установленные в GAC, не многим сложнее. В случае
с приватными сборками их "подписанность" никакой роли не играет. Подпись не
проверяется, а "полный идентификатор приватного компоновочного блока состоит из
имени компоновочного блока и числового номера его версии" (из книги Э.
Троелсена).

Сборки, устанавливаемые в GAC, должны иметь так называемое строгое имя.
Сборка получает строгое имя в момент ее подписания. Идентификаторы сборок в GAC
дополняются параметрами публичного ключа, подписи проверяются. Замуровали,
демоны! Что же это получается? Окружили со всех сторон:

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

Тем не менее, не нужно быть криптографом, чтобы все же изменить библиотеку в
GAC. Требуется всего лишь обладать правами администратора и знать параметры
утилиты sn.exe (страдальцы, не имеющие Студии, вручную используют стандартную
утилиту sn.exe для подписания компоновочных блоков).

Итак, возьмем проект уже знакомой библиотеки signedLib.dll. Подпишем ее и
установим в GAC.

gacutil.exe /i
D:\projects\changeKey\signedLib\bin\Release\signedLib.dll

Добавим референс к консольному приложению changeKey.exe. Компилируем релиз,
убеждаемся, что в папке с программой нет файла signedLib.dll (это значит, что
сборка будет загружена из GAC). Запускаем changeKey.exe - приложение показывает
"1".

С этого момента воображаем себя атакующими - у нас нет исходников, нет
секретного ключа. Но надо, чтобы метод GetNumber() возвращал не 1, а 2.

Структуру файлов ниже C:\Windows\assembly проводник Windows не показывает.
Создадим псевдодиск, на который будет проецироваться нужный каталог:

subst b: C:\Windows\assembly

В проводнике появился диск B.

.Net сборки попадают в папку GAC_MSIL; находим нужную папку (ее название
совпадает с названием .dll файла). Внутри будет еще одна папка, а в ней,
наконец, signedLib.dll. Копируем signedLib.dll на рабочий стол.

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

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

Нажимаем "Remove Strong Name" - удалить цифровую подпись. Закрываем сборку
(теоретически закрывать сборку нет необходимости и нам должен подойти вариант
"Register it for verification skipping", однако у меня эта операция
заканчивается ошибкой; к тому же, в обучающих целях лучше проделать все операции
вручную).

Теперь у нас есть:

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

Осталось установить ее в GAC. Для этого воспользуемся механизмом отложенной
подписи. Если сборка содержит информацию о публичном ключе, но не имеет цифровой
подписи – говорят, что она имеет отложенную подпись (придумал это какой-то
надмозг из Майкрософт "с целью тестирования").

Сделать такую сборку с помощью .NET Reflector не составляет никакой сложности
- нужно заполнить соответствующие поля, они выделены желтым на рисунке
"Параметры публичного ключа" (помнишь, мы копировали их значения в блокнот?). И
не забудь поставить галочку "HasPublicKey" (в теории публичный ключ нужно
извлекать из секретного с помощью утилиты sn.exe и потом с помощью нее же
создавать отложенную подпись).

Итак, мы получили сборку, которая называется так же, как оригинальная, имеет
такую же версию и такой же публичный ключ. Получается, если ее установить в GAC,
она получит точно такой же идентификатор, что и оригинальная (смотри начало
статьи). Как я писал выше, по умолчанию у сборок в GAC проверяется подпись,
однако проверку подписи можно отключить - опять же "для тестирования".

Чтобы отключить проверку подписи dll на данном компьютере, нужно
воспользоваться sn.exe:

sn -Vr C:\Users\Alex\Desktop\signedLib.dll

Удаляем оригинальную сборку из GAC:

gacutil /u
signedLib,Version=1.0.0.0,Culture=neutral,PublicKeyToken=2b1b71846e76146e

И устанавливаем измененную:

gacutil /i C:\Users\Alex\Desktop\signedLib.dll

Радуемся, глядя на выведенную gacutil.exe надпись:

Assembly successfully added to the cache

Вот мы и добились желаемого - изменили библиотеку, установленную в GAC. Чтобы
еще раз порадоваться (и проверить результат), запускаем наше приложение
changeKey.exe, которое в начале статьи выводило 1. Ура, теперь он покажет 2!

Подведем итог

Самое время подвести итог нашим сегодняшним свершениям. Сделаем это по
пунктам:

  • Публичный ключ записан в самой сборке (точнее, в манифесте);
  • в случае с приватными сборками подписи не проверяются.

Чтобы изменить сборку в CAG, нужно:

  1. Сделать копию нужного dll-файла из C:\Windows\assembly (воспользовавшись
    командой subst).
  2. Извлечь из сборки публичный ключ.
  3. Модифицировать IL-код сборки и удалить цифровую подпись.
  4. Добавить к измененному файлу публичный ключ, полученный на шаге 2
    (создадим отложенную подпись).
  5. Отменить проверку цифровой подписи для модифицированной сборки на данном
    компьютере.
  6. Удалить оригинальную сборку из GAC.
  7. Установить модифицированную сборку.

Для реализации шагов 5-7 нужно обладать правами администратора. Вот и все! На
этом позволь откланяться и пожелать тебе огромных творческих узбеков на ниве
исследований программного обеспечения.

 

INFO

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

Оставить мнение