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

    Меня всегда завораживала магия программирования — маленькие фокусы, в которых бессмысленный на первый взгляд код делает что-то интересное. Самые известные из них — «подписи», которые выводят на печать короткий текст (обычно имя автора). В прошлый раз я показала несколько таких фокусов, основанных на эзотерических языках программирования, и некоторым читателям они даже пригодились при подготовке новогоднего поздравления :-) Настоящая же магия — это создание таких вещиц на совершенно нормальном языке, который вы используете каждый день, например, на 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
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 48

      0
      потрясающе! использование таких методов хотя бы в части функционала (например, кодирование импортируемых функций при динамическом импорте) уже приведет к путанице реверсера (:

      спасибо за статью, очень интересно!
        +24
        Думаю, реверсер даже не заметит подмены. Ему-то в отладчике всё равно смотреть в эти байтики во всех возможных представлениях. Увидев что-то похожее на текст, он моментально переключится в режим отображения ASCII-символами и будет прав.
        • UFO just landed and posted this here
            0
            Вы не учли возможность операций с числами. Синус там, косинус, интеграл…
          0
          Я предлагаю так писать финансовое ПО (щютка).
            –3
            Вас уже заждались в команде разработчиков ProGuard :D
              0
              вот что бывает, когда программистам скучно :-D
                +2
                C#
                Console.WriteLine(
                (new List {279544143478566788, 8437057322050937631})
                .Aggregate("", (s, x) =>
                {while(x>0){s=((x%34==33)?' ':(char)(x%34-1+'а'))+s;x/=34;}return s;}));
                  +1
                  Патч Бармина?
                    0
                    Отлично) только List<long>
                      0
                      да правильно, там дженерик хабрапарсер съел
                    0
                    C#, для разнообразия на лямбде
                    Func〈ulong, string〉 f = null;
                    f=(n)=>""+((n==0)?"":f(n/34)+((n%34==33)?' ':(char)(n%34-1+'а')));
                    ulong[]code={814315,1196087757209127883,1402407386743198646,8253365425410800148};
                    Console.WriteLine(code.Aggregate("", (s, x) => f(x) + s));
                    –2
                    Один раз действительно использовал похожее в курсовой работе, только там строчка была побольше, поэтому пришлось использовать массив чисел, к элементам которого применялись дополнительные преобразования
                      –7
                      int A = 2037539149;
                      Бил бы ногами за несоблюдение диапазона…
                        –3
                        long A должно быть, верно?
                          +3
                          На *некоторых* платформах диапазон соблюдается. :P
                            0
                            Пусть соблюдаются, я не против. Но использование стандартов (наличие return'ов в функции не исключение) очень желательно, когда не идёт привязка к конкретному компилятору.
                              +7
                              gcc — не конкретный компилятор?
                            +5
                            Специально, чтобы сохранить свою драгоценную шкурку, написала disclaimer; у меня int такого размера.
                              0
                              Извиняюсь, забылся. Вопрос исчерпан.
                            +11
                            По первому варианту: нет гарантии, что в АП процесса сразу после переменной A в стеке будут нули. Поэтому, в более общем случае в long (32-битный) можно поместить до 3 символов (включительно) плюс завершающий NULL. В 64-битный — до 7 (также включительно).
                              +20
                              Так можно и до незаконных чисел докатиться.
                                0
                                Вот уж не знал)))) Прикольно так над копирастами издеваются))))
                                  0
                                  Monolith (в статье «Незаконное число» упоминается, monolith.sourceforge.net/) — «коза» в адрес правообладателей.
                                  –1
                                  Никогда и в мыслях не было, так подписываться :)
                                    0
                                    int A = 2037539149;
                                    printf((char *)&A);
                                    


                                    А откуда берется нолик в конце? O_O
                                      0
                                      У меня там вообще «Maryx (».
                                        0
                                        Соглашусь с более ранним комментарием — нет символа конца строки, поэтому выводится что-то из-за пределов переменной.
                                          0
                                          Ограничение этого метода — он выводит слова длиной до 4 букв, больше в int не поместится. 8 букв можно получить, если заменить int на unsigned long long:


                                          Тогда, видимо, это утверждение неверно?
                                            0
                                            Ну почему же? Заявленные 4 и 8 букв выводятся. Иногда больше, но уж никак не меньше :-)
                                              0
                                              Тоесть access violation вас не смущает? :)
                                                +3
                                                Не очень. Эти приемы заведомо нельзя использовать в реальной разработке (помните о читающим ваш код маньяке, который знает, где вы живете?), поэтому их побочные эффекты принципиального значения не имеют.
                                          0
                                          Это уже от машины зависит — если 64-битная платформа с соответствующим компилятором и little-endian, то int это 8 байт, и нолики там найдутся в старших разрядах
                                          А вот с переносимостью такого кода — фигня-с™
                                          –1
                                          В общем на самом деле уровень чисто школьный))))) мы как раз этим в школе баловались чтобы лучше понять суть строк ;)
                                          • UFO just landed and posted this here
                                              0
                                              Объясните, пожалуйста, непросвещенному, как из 0x7972614D получается 2037539149.
                                              Спасибо!
                                                0
                                                Первое — шестнадцатеричная запись, вторая — десятичная.
                                                  +2
                                                  Эх… Мне стыдно…
                                                +1
                                                пользователи ARM'а и SPARC v9 не одобряют этот пост!
                                                  +1
                                                  пользователи MIPS одобряют.
                                                    +1
                                                    у ARM endianness настраивается. У моего арма так вообще с интеловским порядком совпадает.
                                                    0
                                                    Напомнило преобразование ip адреса в число, тоже можно вставить в подпись.
                                                      0
                                                      ссылка в догонку www.allredroster.com/iptodec.htm
                                                        0
                                                        А ещё есть преобразование IP-адреса в смайлик.

                                                        Лёгким движением shift'a 127.0.0.1 превращается… в !@&>)>)>!
                                                        +6
                                                        вот-вот.
                                                          0
                                                          Ну да, ник тоже подбирать удобно :-)
                                                          +2
                                                          Нулевой байт в конце строки и правда как-то упущен. зато при некоторых компиляторах и их параметрах можно использовать ситуацию когда переменные располагаются в памяти подряд:
                                                          int A = 2037539149;
                                                          int B = 2037539149;
                                                          int C = 0;
                                                          printf((char *)&A);
                                                          например такая махинация у меня (gcc 4.4.5, x86, опции по-умолчанию) вывела «MaryMary»
                                                            0
                                                            ну или просто массив определить, но это уже как-то слишком банально

                                                          Only users with full accounts can post comments. Log in, please.