Подтвердить что ты не робот

Как всегда создавать байт-байт-идентичный .exe при перестройке приложения С#?

Сначала я расскажу вам немного о том, почему я задаю этот вопрос:

В настоящее время я работаю в строго регулируемой отрасли, и поэтому наш код довольно тщательно просматривается официальными тестовыми домами. Эти тестовые дома ожидают, что смогут построить код и сгенерировать файл .exe или .dll, который ТОЧНО один и тот же каждый раз (без изменения кода явно!). Они проверяют MD5 и SHA1 исполняемых файлов, которые они создают для обеспечения этого.

До этого момента я преимущественно кодировал на С++, где (после нескольких настроек настройки проекта) мне удалось заставить проекты последовательно перестраиваться на один и тот же MD5/SHA1. Я теперь использую С# в проекте, и мне очень трудно получить MD5 для соответствия после восстановления. Я знаю, что в PE-заголовке файла есть "метки времени", и они были очищены до 0. Я также знаю, что есть GUID для .exe, который снова был очищен до 00 00 00... и т.д. Однако файлы все еще не совпадают.

Я использую CFF Explorer для просмотра и редактирования заголовка PE, чтобы удалить отметки времени и даты. После использования двоичного инструмента сравнения в файле .exe есть только 2 блока байтов, которые отличаются (оба очень маленькие).

Один из непоследовательных блоков появляется непосредственно перед некоторым двоичным кодом, который в ASCII указывает путь к файлу *Project*\obj\Release\xxx.pdb.

EDIT: Теперь известно, что это GUID файла *.pdb, однако я до сих пор не знаю, могу ли я изменить его, не вызывая никаких ошибок!?

Другой блок появляется в середине того, что выглядит как имена функций, т.е. (типичный раздел) AssemblyName.GetName.Version.get_Version.System.IO.Ports.SerialPort.Parity.Byte.<PrivateImplementationDetails>{

то разный кодовый блок:

4A134ACE-D6A0-461B-A47C-3A4232D90816

а затем:

"}. ValueType.__ StaticArrayInitTypeSize = 7.. $$ method0x60000ab-1.RuntimeFieldHandle.InitializeArray`... и т.д.

Любые идеи или предложения были бы наиболее желанными!

4b9b3361

Ответ 1

Обновление: у Roslyn есть флаг компилятора /feature:deterministic для воспроизводимых сборок, хотя он не работает на 100%.


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

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

Я бы сделал это, пройдя поток метаданных #Strings и заменив все строки формы "<PrivateImplementationDetails> {GUID}" на "<PrivateImplementationDetails> {номер запуска, дополненный той же длиной, что и GUID)".

поток метаданных #Strings - это просто список строк, используемых метаданными, закодированных в UTF-8 и разделенных символом \0; поэтому поиск и замена имен должны быть легкими, если вы знаете, где поток строк #String находится внутри исполняемого файла.

К сожалению, "заголовки потоков метаданных", содержащие эту информацию, довольно хороши в формате файла. Вам нужно будет начать с NT Head Header, найти указатель на заголовок CLI Runtime Header, разрешить его в позиции файла, используя таблицу раздела PE (это RVA, но вам нужна позиция внутри файла), затем перейдите к корень метаданных и чтение заголовков потоков.

Ответ 2

Я не уверен в этом, но просто подумал: используете ли вы какие-либо анонимные типы, для которых компилятор может генерировать имена за кулисами, которые могут быть разными при каждом запуске компилятора? Просто возможность, которая произошла со мной. Вероятно, один для Джона Скита; -)

Обновление:. Для сравнения и разбора вы также можете использовать Reflector addins.

Ответ 3

Что касается проблемы с GUID PDB, если вы указываете, что PDB не должен генерироваться при компиляции для релизных сборок, то содержит ли двоичный файл GUID файловой системы PDB?

Чтобы отключить генерацию PDB:

  • Щелкните правой кнопкой мыши свой проект в обозревателе решений и выберите "Свойства".
  • В меню слева выберите "Построить".
  • Убедитесь, что выбор конфигурации - Release (вам все равно нужен PDB для отладки).
  • Нажмите кнопку "Дополнительно" в правом нижнем углу.
  • В разделе "Информация о выходе/отладки" выберите "Нет".

Если вы создаете консоль, используйте /debug -, чтобы получить тот же результат.

Ответ 4

Взгляните на ответы этого вопроса. Особенно на внешнем звене, представленном в третьем.

EDIT:

Я действительно хочу, чтобы ссылка на эту статью.

Ответ 5

Вы сказали, что после нескольких трюков проекта вы смогли получить приложения на С++ для повторного сложения к тем же значениям SHA1/MD5. Я нахожусь в той же лодке, что и вы, находясь в индустрии с третьей тестовой лабораторией, которая требует повторного восстановления точно таких же исполняемых файлов.

В исследовании, как это произошло в VS2005, я столкнулся с вашим сообщением здесь. Не могли бы вы поделиться изменениями в проекте, которые вы сделали, чтобы последовательно создавать С++-приложения с одинаковыми значениями SHA1/MD5? Это было бы очень полезно для меня и, возможно, для других, которые разделяют это требование.

Ответ 6

Используйте ildasm.exe, чтобы полностью разобрать обе программы и сравнить IL. Затем вы можете "очистить" код с помощью текстовых методов и (предсказуемо) перекомпилировать его снова.