Комментарии 16
Картинка не правильная. В мясорубку должна входить сосиска, а выходить барашек.
Спасибо за замечание :) Там так и нарисовано, только стрелки в обратном порядке поставлены с идей, что процесс в две стороны рассматривается.
Заменила картинку :) Спасибо.
Я как-то неловко отклонила Ваше предложение поменять барана и сосиску местами. Но в этот раз все верно: ДЕкомпилятор — это «мясорубка наооборот», которая именно из фарша (бинарника) восстанавливает подобие исходника, то есть барана :)
Все оптимизации компилятора должны сохранять наблюдаемое поведение.
До оптимизации функция либо падала при доступе к нулевому указателю либо записывала значение в память. После оптимизаций она делает то же самое.
Более эпичный случай: компилятор может иногда выкинуть бесконечный цикл:
int infinte(){
int counter = 0;
while(true){
counter++;
if(PureVeryComplicatedCondition(counter))
return 1;
}
return 0;
}
Низкоуровневое программирование — оно такое, чем дальше двигаешься, тем больше сюрпризов. «Я знаю, что ничего не знаю» — Сократ сказал очень верно :)
Мы делаем вручную с помощью инструментальных средств, помогающих анализировать низкоуровневые программы. Undefined behavior можно находить в исходнике на С/C++, но в бинарнике искать надежнее. Я сама использую IdaPro для низкоуровневого анализа. Для анализа C/C++ и других программ по исходникам использую InCode. Конечно, большинство работы для анализа бинарников делается руками.
О каком undefined behavior в бинарниках вы говорите?
Компилятор переводит C++ программу с UB в опкоды x64, поведение которых специфицировано производителем CPU. UB исчезает.
Или вы ищете недокументированные опкоды процессора? Откуда они после компилятора?
Компилятор переводит C++ программу с UB в опкоды x64, поведение которых специфицировано производителем CPU. UB исчезает.
Или вы ищете недокументированные опкоды процессора? Откуда они после компилятора?
Я говорю о том, что UB в исходной C/C++ программе может привести к различным непредсказуемым defined behavior в низкоуровневой программе. Именно поэтому проверить исходник может быть недостаточно, а для более точного анализа надо проверять и исходник, и бинарник.
Ага, то есть нужно скомилировать C++ в код, затем декомпилировать обратно в C++, проанализировать результат (вручную?). Если никакой странной лажи не появилось, то, наверное, в исходной программе нет UB. Не слишком ли дорого выходит?
Нет, совсем не так. В статье же написано, что декомпиляцию стоит выполнять, если надо понять содержательный аспект бинарника. В декомпилированном коде уязвимости искать несравненно сложнее, нежели в исходнике, так как «артефакты» восстановления мешают работе статических анализаторов.
Анализировать и исходник, и бинарник, но именно бинарник в бинарном виде (по ассемблеру, например) нужно, если у вас критичный по надежности фрагмент кода и надо точно понимать, что будет выполняться.
В статье приведены 2 примера, когда именно по ассемблеру отлавливается уязвимость, которая «спряталась» в исходнике.
Анализировать и исходник, и бинарник, но именно бинарник в бинарном виде (по ассемблеру, например) нужно, если у вас критичный по надежности фрагмент кода и надо точно понимать, что будет выполняться.
В статье приведены 2 примера, когда именно по ассемблеру отлавливается уязвимость, которая «спряталась» в исходнике.
Интересная статья. Скажите, у вас используются какие-то специальные инструменты для обнаружения undefined behavior или это выполняется «врукопашную»? Не пробовали ли учесть опыт обнаружения подобных косяков в каком-нибудь средстве автоматизации/плагине и т.д.?
Мы делаем вручную с помощью инструментальных средств, помогающих анализировать низкоуровневые программы. Undefined behavior можно находить в исходнике на С/C++, но в бинарнике искать надежнее. Я сама использую IdaPro для низкоуровневого анализа. Для анализа C/C++ и других программ по исходникам использую InCode. Конечно, большинство работы для анализа бинарников делается руками.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Анализ унаследованного кода, когда исходный код утрачен: делать или не делать?