Появление последовательности 0x90 (NOP) в допустимом коде

Предыстория: я написал скрипт на Python для проверки IP-пакетов, в частности полезной нагрузки/данных пакета, чтобы определить, можно ли его использовать при переполнении буфера (стека). Теперь, насколько я понимаю, NOP-цепочка используется для заполнения стека, чтобы указатель инструкций в конечном итоге попадал в ваш код эксплойта, это я могу легко обнаружить, ища повторяющиеся вхождения 0x90. Я видел код с большим количеством команд NOP до 8 в случае SQL Slammer, поэтому я мог бы использовать как минимум 8.

Теперь мой вопрос: часто ли сани NOP используются в законном коде? Если ответ «да», есть ли какие-то конкретные случаи (что означает, что я могу найти эти случаи, а затем исключить пакет как потенциально безвредный) или этот подход просто нецелесообразен для выявления вредоносного кода?


person Trowalts    schedule 17.10.2011    source источник


Ответы (3)


Компилятор будет генерировать NOP для выравнивания кода — например, на некоторых итерациях x86 переходы выполняются быстрее, если назначение перехода выровнено по 4-, 8- или даже 16-байтовой границе. .

Некоторые компиляторы пытаются по возможности использовать «длинные NOP» — отдельные инструкции, занимающие более одного байта пространства и формально выполняющие какие-либо действия, но не влияющие на состояние процессора — как в некоторых итерации архитектуры x86 это быстрее. Например, 66 90 — это двухбайтовый NOP, а 8d 74 26 00 — четырехбайтовый NOP (технически lea 0(%esi,%eiz,1),%esi, но, как вы можете видеть, он просто копирует значение из %esi в себя, так что никакого эффекта). Однако их можно использовать не во всех случаях, и последовательности, которые являются самыми быстрыми на одних x86, удручающе часто действительно медленны на других. Я не читал текущие рекомендации по микрооптимизации, но не удивлюсь, если Intel и AMD работали над тем, чтобы сделать строку из 90 самым быстрым способом выполнения длинного NOP, и их компиляторы совпадали.

person zwol    schedule 17.10.2011
comment
Длинные nop всегда лучше. Оптимальные последовательности длинных NOP различаются для Intel и AMD, но несколько инструкций NOP всегда проходят конвейер по отдельности вплоть до вывода из эксплуатации. Нет необходимости оптимизировать обработку коротких NOP, потому что ассемблеры знают, как использовать длинные NOP, а компиляторы избегают помещать их во внутренние циклы (в худшем случае один длинный NOP внутри внешнего цикла для выравнивания внутреннего цикла). - person Peter Cordes; 13.11.2017

из википедии:

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

Кроме того, 0x90 предположительно может использоваться компиляторами в качестве наполнителя для неинициализированных массивов, если данные в массиве интерпретируются как коды операций, он ничего не делает. Вы видите аналогичный эффект с Visual Studio, которая заполняет неинициализированные массивы 0xCC, что равно int 3, что вызывает остановку точки останова.

Более того, любые данные в исполняемом файле могут содержать любое количество 0x90, и отличить их от кода может быть непросто.

person shoosh    schedule 17.10.2011

Я только что просмотрел последний скомпилированный двоичный файл (безопасный код x86 в Linux) и обнаружил:

016b5e0 458b c9ec 90c3 9090 9090 9090 9090 9090

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

person Greg Hewgill    schedule 17.10.2011
comment
C3 = RET, так что, вероятно, следующая функция выравнивается по 16 байтам. Я думаю, вы могли бы определить этот случай как что-то другое. - person Rup; 17.10.2011
comment
Ничто не говорит о том, что скомпилированный код всегда должен заканчиваться RET (он может JMP где-то еще). - person Greg Hewgill; 17.10.2011
comment
На всякий случай, если другие нубы, такие как я, задаются вопросом о комментарии @GregHewgill - - код вроде int funfunfunction(...){ for(...) { do Things; если (что-то) вернуть 3; делать больше дел; } } закончилось бы переходом вместо возврата - person Cedar; 15.10.2019