Как стать автором
Обновить

Формирование данных с помощью шаблонов С++

Время на прочтение 6 мин
Количество просмотров 1.7K
Однажды была поставлена задача написать некую небольшую программу, она должна была что-то делать, но при это требовалось максимально усложнить процесс анализа кода при дизассемблировании. Один из методов это убрать из exe все упоминания об используемых WinApi функциях, и было принято решение хранить не названия WinApi функций, а некие хэш-коды, вычисляемые каким-нить несложным алгоритмом. Потом при перечислений всех функций dll библиотеки находить нужные по этим хэш-кодам. Один из часто применяемых способов, это написать небольшую утилитку, подать ей на вход список названий нужных функций, она вычисляет их хэш-коды и на выходе выдает исходник в котором находится готовая таблица с кодами. Потом этот исходник подключается к проекту и дальше во всю используется. Но как всегда вмешалась лень. Лень было писать такую утилитку, да и еще прописывать необходимые действия в make файле. Хотелось чтобы все вычислялось во время компиляции. Вот тогда был брошен взгляд на шаблоны С++ …

Шаблоны С++ не позволяют работать со строками типа char*, в них нельзя писать, с них нельзя читать, можно использовать только указатель для передачи его куда-то дальше. Но лень упертая штука, а что если в параметрах шаблона указывать не саму строку, а отдельно символы? Это уже очень хорошо, да и шаблоны могут иметь параметры по умолчанию, а это как раз то что нам нужно. Теперь достаточно написать шаблонный класс параметрами которого будут символы (тип char) и функцию для расчета хэша на основе значений параметров, а вот и сам класс:

template<char c01 = 0, char c02 = 0, char c03 = 0, char c04 = 0,
         char c05 = 0, char c06 = 0, char c07 = 0, char c08 = 0,
         char c09 = 0, char c10 = 0, char c11 = 0, char c12 = 0,
         char c13 = 0, char c14 = 0, char c15 = 0, char c16 = 0,
         char c17 = 0, char c18 = 0, char c19 = 0, char c20 = 0,
         char c21 = 0, char c22 = 0, char c23 = 0, char c24 = 0,
         char c25 = 0, char c26 = 0, char c27 = 0, char c28 = 0,
         char c29 = 0, char c30 = 0, char c31 = 0, char c32 = 0 >

class hash_text {

  public:

    //функция вычисления хэша
    static unsigned int get_hash()
      { unsigned int hash = c01;
        if( c02 ) { hash ^= (hash << 8) | c02; }
        if( c03 ) { hash ^= (hash << 8) | c03; }
        if( c04 ) { hash ^= (hash << 8) | c04; }
        if( c05 ) { hash ^= (hash << 8) | c05; }
        if( c06 ) { hash ^= (hash << 8) | c06; }
        if( c07 ) { hash ^= (hash << 8) | c07; }
        if( c08 ) { hash ^= (hash << 8) | c08; }
        if( c09 ) { hash ^= (hash << 8) | c09; }
        if( c10 ) { hash ^= (hash << 8) | c10; }
        if( c11 ) { hash ^= (hash << 8) | c11; }
        if( c12 ) { hash ^= (hash << 8) | c12; }
        if( c13 ) { hash ^= (hash << 8) | c13; }
        if( c14 ) { hash ^= (hash << 8) | c14; }
        if( c15 ) { hash ^= (hash << 8) | c15; }
        if( c16 ) { hash ^= (hash << 8) | c16; }
        if( c17 ) { hash ^= (hash << 8) | c17; }
        if( c18 ) { hash ^= (hash << 8) | c18; }
        if( c19 ) { hash ^= (hash << 8) | c19; }
        if( c20 ) { hash ^= (hash << 8) | c20; }
        if( c21 ) { hash ^= (hash << 8) | c21; }
        if( c22 ) { hash ^= (hash << 8) | c22; }
        if( c23 ) { hash ^= (hash << 8) | c23; }
        if( c24 ) { hash ^= (hash << 8) | c24; }
        if( c25 ) { hash ^= (hash << 8) | c25; }
        if( c26 ) { hash ^= (hash << 8) | c26; }
        if( c27 ) { hash ^= (hash << 8) | c27; }
        if( c28 ) { hash ^= (hash << 8) | c28; }
        if( c29 ) { hash ^= (hash << 8) | c29; }
        if( c30 ) { hash ^= (hash << 8) | c30; }
        if( c31 ) { hash ^= (hash << 8) | c31; }
        if( c32 ) { hash ^= (hash << 8) | c32; }
        return hash;
      }

};

Как видите все не так сложно. Единственное ограничение для данного класса, так это возможность использования строк длиною не более 32-х символов, но если нужно больше, то легко расширить.
Как теперь использовать этот класс? Все достаточно просто, берем название функции и прописываем его посимвольно в параметрах шаблона:

typedef hash_text<'C','r','e','a','t','e','F','i','l','e'> hashCreateFile; //объявляем новый тип

Как вам?

А чтобы получить сам хэш:

hashCreateFile::get_hash();

и все! Теперь достаточно объявить нужное количество типов-функций и использовать хэши в где вам нужно.
Если вы думаете, что компилятор сгенерирует кучу ненужного кода, то ошибаетесь, будет только одно число и больше ничего (попробуйте сами скомпилить в ассемблерный вид).
Но это еще не предел, таким способом еще можно шифровать строки прямо налету, во время компиляции.

Шифрование строк

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

template<unsigned int code,
         char c01 = 0, char c02 = 0, char c03 = 0, char c04 = 0,
         char c05 = 0, char c06 = 0, char c07 = 0, char c08 = 0,
         char c09 = 0, char c10 = 0, char c11 = 0, char c12 = 0,
         char c13 = 0, char c14 = 0, char c15 = 0, char c16 = 0,
         char c17 = 0, char c18 = 0, char c19 = 0, char c20 = 0,
         char c21 = 0, char c22 = 0, char c23 = 0, char c24 = 0,
         char c25 = 0, char c26 = 0, char c27 = 0, char c28 = 0,
         char c29 = 0, char c30 = 0, char c31 = 0, char c32 = 0 >

class code_text {

    //вычисляем количество символов в строке
    static const char count = 
      (c01 ? 1 : 0) + (c02 ? 1 : 0) + (c03 ? 1 : 0) + (c04 ? 1 : 0) +
      (c05 ? 1 : 0) + (c06 ? 1 : 0) + (c07 ? 1 : 0) + (c08 ? 1 : 0) +
      (c09 ? 1 : 0) + (c10 ? 1 : 0) + (c11 ? 1 : 0) + (c12 ? 1 : 0) +
      (c13 ? 1 : 0) + (c14 ? 1 : 0) + (c15 ? 1 : 0) + (c16 ? 1 : 0) +
      (c17 ? 1 : 0) + (c18 ? 1 : 0) + (c19 ? 1 : 0) + (c20 ? 1 : 0) +
      (c21 ? 1 : 0) + (c22 ? 1 : 0) + (c23 ? 1 : 0) + (c24 ? 1 : 0) +
      (c25 ? 1 : 0) + (c26 ? 1 : 0) + (c27 ? 1 : 0) + (c28 ? 1 : 0) +
      (c29 ? 1 : 0) + (c30 ? 1 : 0) + (c31 ? 1 : 0) + (c32 ? 1 : 0);

    char buf[count + 1]; //выделяем место под нашу строку

    char encode_char( bool first, int bit, char b1, char b2 )
           { if( b2 == 0 ) //конец строки
               return b1;
             unsigned int change = code & (1 << bit); //делать обмен или нет
             if( first )
               return change ? b2 : b1; //кодирование 1-го символа
             else
               return change ? b1 : b2; //кодирование 2-го символа
           }

    //помещает кодированный символ в буфер, n - начальный индекс для двух символов
    bool put_char( int n, char c1, char c2 ) 
           { if( c1 == 0 ) return false; //конец строки
             buf[n] = encode_char( true, n / 2, c1, c2 );
             if( c2 == 0 ) return false; //конец строки
             buf[n + 1] = encode_char( false, n / 2, c1, c2 );
             return true;
           }

    //кодирование строки целиком
    void encode()
           { int v = 0;
             //символы ложим в буфер парами, конструкции if не делал лесенкой, так как в столбик красивее )
             if( put_char( 0,  c01, c02 ) )
             if( put_char( 2,  c03, c04 ) )
             if( put_char( 4,  c05, c06 ) )
             if( put_char( 6,  c07, c08 ) )
             if( put_char( 8,  c09, c10 ) )
             if( put_char( 10, c11, c12 ) )
             if( put_char( 12, c13, c14 ) )
             if( put_char( 14, c15, c16 ) )
             if( put_char( 16, c17, c18 ) )
             if( put_char( 18, c19, c20 ) )
             if( put_char( 20, c21, c22 ) )
             if( put_char( 22, c23, c24 ) )
             if( put_char( 24, c25, c26 ) )
             if( put_char( 26, c27, c28 ) )
             if( put_char( 28, c29, c30 ) )
             if( put_char( 30, c31, c32 ) )
               v = 0; //в последней конструкции if должен быть какой-нить оператор
             buf[count] = 0;
           }

    public:
      
                  code_text()
                    { encode();
                    }
      const char* ptr() const //указатель на кодированную строку
                    { return buf;
                    }
             int  len()
                    { return count;
                    }
            char* str( char* to )
                    { return decode( buf, count, to ); //декодирование в to
                    } 
};

Как видите тут немного сложнее, но смысл тот же. Использовать нужно несколько по другому:

code_text<44717397, 'E','n','c','o','d','e','S','t','r','i','n','g'> EncodeString;

тут создается объект, а не как выше – тип. Ну и применять можно так:

printf( "%s\n", EncodeString.ptr() );

на экране будет зашифрованная строка, или

char to[33];
printf( "%s\n", EncodeString.str(to) );

на экране будет расшифрованная строка. Функция расшифровки (decode), естественно должна быть где-то описана.
При создании объекта, а не типа, есть одно но, полученные данные не помещаются таким способом в секцию .data (как обычные строки), компилятор формирует код который сохраняет вычисленные данные посредством инструкций mov. Это так называемый инициализируемый код, и запускается перед вызовом функции main(), то есть для нас все прозрачно и мы можем пользоваться результатом как обычными строками. Также возможно придется поработать над опциями оптимизации компилятора, так как такие огромные инлайн функции могут остаться в вашем конечном exe, т. е. нужно добиться того чтобы остался только инициализируемый код (переброс данных) и больше ничего.

Закуска

Ну и на закуску еще такой примерчик. С++ не имеет встроенных средств написания чисел в двоичном виде. Но мы можем это обойти используя данный метод. Ниже показан класс который может преобразовывать 32-х разрядные числа:

template<int c01 = -1, int c02 = -1, int c03 = -1, int c04 = -1,
         int c05 = -1, int c06 = -1, int c07 = -1, int c08 = -1,
         int c09 = -1, int c10 = -1, int c11 = -1, int c12 = -1,
         int c13 = -1, int c14 = -1, int c15 = -1, int c16 = -1,
         int c17 = -1, int c18 = -1, int c19 = -1, int c20 = -1,
         int c21 = -1, int c22 = -1, int c23 = -1, int c24 = -1,
         int c25 = -1, int c26 = -1, int c27 = -1, int c28 = -1,
         int c29 = -1, int c30 = -1, int c31 = -1, int c32 = -1 >

class bin_to_dec {

    static unsigned int get_bit( int res, int c )
      {  
         return (res << 1) | (c ? 1 : 0); //c не обязательно может быть равен 0 или 1
      }

  public:

    static unsigned int dec()
      { 
        unsigned int res = 0;
        if( c01 >= 0 ) res = get_bit( res, c01 );
        if( c02 >= 0 ) res = get_bit( res, c02 );
        if( c03 >= 0 ) res = get_bit( res, c03 );
        if( c04 >= 0 ) res = get_bit( res, c04 );
        if( c05 >= 0 ) res = get_bit( res, c05 );
        if( c06 >= 0 ) res = get_bit( res, c06 );
        if( c07 >= 0 ) res = get_bit( res, c07 );
        if( c08 >= 0 ) res = get_bit( res, c08 );
        if( c09 >= 0 ) res = get_bit( res, c09 );
        if( c10 >= 0 ) res = get_bit( res, c10 );
        if( c11 >= 0 ) res = get_bit( res, c11 );
        if( c12 >= 0 ) res = get_bit( res, c12 );
        if( c13 >= 0 ) res = get_bit( res, c13 );
        if( c14 >= 0 ) res = get_bit( res, c14 );
        if( c15 >= 0 ) res = get_bit( res, c15 );
        if( c16 >= 0 ) res = get_bit( res, c16 );
        if( c17 >= 0 ) res = get_bit( res, c17 );
        if( c18 >= 0 ) res = get_bit( res, c18 );
        if( c19 >= 0 ) res = get_bit( res, c19 );
        if( c20 >= 0 ) res = get_bit( res, c20 );
        if( c21 >= 0 ) res = get_bit( res, c21 );
        if( c22 >= 0 ) res = get_bit( res, c22 );
        if( c23 >= 0 ) res = get_bit( res, c23 );
        if( c24 >= 0 ) res = get_bit( res, c24 );
        if( c25 >= 0 ) res = get_bit( res, c25 );
        if( c26 >= 0 ) res = get_bit( res, c26 );
        if( c27 >= 0 ) res = get_bit( res, c27 );
        if( c28 >= 0 ) res = get_bit( res, c28 );
        if( c29 >= 0 ) res = get_bit( res, c29 );
        if( c30 >= 0 ) res = get_bit( res, c30 );
        if( c31 >= 0 ) res = get_bit( res, c31 );
        if( c32 >= 0 ) res = get_bit( res, c32 );
        return res;
      }
};

Использовать так:

typedef bin_to_dec<1,1,1,0,0,0,1,1,1> flags;
printf( "%u", flags::dec() );


Послесловие


Я использую в одном своем проекте данный метод для формирования хэшей и закодированных строк и пока все замечательно, но вам особо не рекомендую, лучше пишите отдельные утилитки, хотя … кто знает Вас и вашу лень )
Теги:
Хабы:
+35
Комментарии 47
Комментарии Комментарии 47

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн