Разгребал папки с древними программами и обнаружил эти два шОдевра. Пройти мимо не смог, потому что удалять жалко, а выкладывать в репу не хочется. Так что будут в лежать в гистах.
Как это работает
Посмотрим на двоичное представление магического числа 7709179928849219.0
. Таким образом, экспонента нашего числа равна 10000110011
, что в десятичной системе счисления равно 1075. Теперь немного преобразуем код в более читабельный вид:
#include <stdio.h>
double m[]= {7709179928849219.0, 771};
int main() {
if (m[1]--) {
m[0] *= 2;
main();
} else {
printf((char *)m);
}
}
Отсюда становится видно, что мы просто удваиваем магическое число 771 раз. Но, напомню, что умножение double
/float
типов в си на 2 равносильно увеличению экспоненты на 1. То есть, в конце экспонента будет равняться 1075 + 771 = 1846
, что в двоичном представлении равно 11100110110
. Тогда итоговое число будет 001110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
, что уже соответствует строке C++Sucks
. Внимательный читатель может заметить, что операции производяться только с экспонентой, а большая часть числа остается неизменной. Если попробовать интерпретировать как строку изначальное число, то мы получим C++Suc;C
. Вот так вот просто и бесхитростно.
Как это работает
Тут, в общем-то, все достаточно тривиально и просто - нет никакого мозговыноса, как в первом случае. Но, для начала, немного про то, как выглядят бинари в памяти во время исполнения в Unix-подобных ОС.
Как видно на картинке выше, программа делится на несколько сегментов - куча, стек, данные и сам код программы. На каждый из сегментов существуют свои права доступа - RWX (Read-Write-Execution). Как логично предположить, на код нет прав записи, поэтому просто так изменить код программы во время выполнения нельзя. Чуть подробнее об этом я напишу в статье про системные вызовы и как они работают, в частности, что за зверь такой VDSO, что такое linux-gates и прочее. В текущий момент данных знаний нам хватит для понимания работы программы, показаной выше.
Так как по-умолчанию на код нет прав на запись, то первое что нужно сделать - соответствующие права получить, что и происходит в функции change_page_permissions
. Мы получаем адрес функции, которую хотим менять и меняем права на странице. Дальше вызываем оригинальную функцию, просто чтобы показать, что происходит, после чего меняем в ней число и вызываем еще раз. Таким образом, одна и та же функция из-за модификации во время исполнения работает совершенно по-разному.
Очень часто такой прием можно было увидеть в старых программах и в современных вирусах. Анализировать все программы, работающие на компьютере слишком затратно при исполнении, поэтому антивирус ограничевается проверкой самого файла. Но таким образом можно избежать обнаружения. Правда, если быть честным, большинство программ, которые пытаются сменить права на страницу сразу же считаются подозрительными, так что в данном случае это просто красивый трюк, а не способ писать вирусы :)