Pull to refresh

Числовые подписи

Reading time 3 min
Views 2.2K
Меня всегда завораживала магия программирования — маленькие фокусы, в которых бессмысленный на первый взгляд код делает что-то интересное. Самые известные из них — «подписи», которые выводят на печать короткий текст (обычно имя автора). В прошлый раз я показала несколько таких фокусов, основанных на эзотерических языках программирования, и некоторым читателям они даже пригодились при подготовке новогоднего поздравления :-) Настоящая же магия — это создание таких вещиц на совершенно нормальном языке, который вы используете каждый день, например, на C++ или Java. В этой статье я покажу несколько способов вывести короткий текст с использованием в качестве исходных данных только числовых констант.

Disclaimer: большинство приведенных фокусов основаны на низкоуровневых действиях с памятью, поэтому результаты могут варьироваться в зависимости от архитектуры компьютера и используемого компилятора (я пользуюсь gcc).


C++ спокойно относится к маргинальным манипуляциям с памятью и указателями, поэтому обфускации типа задания строки числом — почти обычное дело :-) Самый простой пример:

#include <stdio.h>
int main()
{   int A = 2037539149;
    printf((char *)&A);
}


Как это работает? Первый и единственный обязательный аргумент функции printf — char * format, задающий формат вывода. Обычно в него передается постоянная строка, а переменные части берутся из следующих аргументов; использование переменной char * в качестве аргумента тоже работает, хотя и с предупреждением компилятора «format not a string literal and no format arguments».

(char *)&A трактует адрес переменной A как указатель на массив символов (вне зависимости от того, что хранится в этой переменной на самом деле). Дело за малым: разместить в A байты, которые составят нужное слово, например, Mary -> 0x4D 0x61 0x72 0x79 -> 0x7972614D (на печать символы выводятся в обратном порядке, от младших разрядов к старшим) -> 2037539149.

Ограничение этого метода — он выводит слова длиной до 4 букв, больше в int не поместится. 8 букв можно получить, если заменить int на unsigned long long:

#include <stdio.h>
int main()
{   unsigned long long A = 8751164009814452552ULL;
    printf((char *)&A);
}


Усложним задачу и перейдем с целых чисел на числа с плавающей точкой. В их внутреннем представлении разбирается меньше народу, поэтому это повысит степень обфускации :-)

Самый простой пример:

#include <stdio.h>
int main()
{   double A = 2.222663600523023e-313;
    printf((char*)&A);
}


Как это работает? Да точно так же, только константу для вывода конкретной строки получить немного сложнее. Чтобы вывести «Mary» с переводом строки после имени, нужно иметь в памяти набор байтов 0x4D 0x61 0x72 0x79 0x0A. Для поиска double-константы, которая записывается именно этими байтами, можно использовать следующий хак:
1. Записать в строку нужные байты как шестнадцатеричное число (аналогично первому методу): A7972614D.
2. Из этой строки прочитать число как unsigned long long, но записать его в переменную типа double.
3. Полученную переменную вывести на печать с максимально возможной точностью, например, средствами STL.

#include <stdio.h>
#include <iostream>
#include <iomanip>
#include <limits>

using namespace std;

int main()
{   double a;
    sscanf("A7972614D", "%llx", (unsigned long long *)&a);
    cout << setprecision (numeric_limits<double>::digits10 + 1) << a << endl;
}


При желании можно усложнить код еще, например, задавать числовую константу не непосредственно, а как результат некоторых вычислений. Отлично смотрелось бы получение текста из магических констант pi, e, золотого сечения и т.д. Эта статья была вдохновлена чудной рекурсивной подписью следующего вида (константа изменена):

#include <stdio.h>
double x = 0.003609087829883, y;
int main() { return(*(char*)&x?x*=y=x,main():printf((char*)&y)); }


Переформатированием и добавлением отладочных выводов можно убедиться, что этот код рекурсивно возводит в квадрат число x и выводит на печать последнюю ненулевую степень (опять же, как строку). Поскольку текст из 4 символов представляется константой порядка 10-300, квадрат этой константы действительно становится нулем. Исходная константа в коде рассчитывается как корень степени двух из константы текста.

Многие языки не позволяют программисту таких вольностей с памятью и типами данных, но можно обойтись более классическим способом — представлением числа в системе счисления, отличной от десятичной. Если бы меня звали Ada, можно было бы ограничиться шестнадцатеричными числами, но Mary требует основания минимум 35, а лучше 36.

public class Magic {
    public static void main(String[] args) {
        System.out.println(Integer.toString(1040398,36));
    }
}


С уважением,
370288658856287000618250P
Tags:
Hubs:
+75
Comments 48
Comments Comments 48

Articles