Скрытие строк

Хеширование - это техника, используемая для создания фиксированного представления куска данных, называемого хеш-значением или хеш-кодом. Хеш-алгоритмы разработаны как односторонние функции, что означает, что вычислительно невозможно определить исходные входные данные, используя хеш-значение. Хеш-код обычно имеет более короткий размер и быстрее обрабатывается. При сравнении строк хеширование может использоваться для быстрого определения равенства двух строк, в сравнении с сравнением самих строк, особенно если строки длинные.

В контексте разработки вредоносных программ хеширование строк полезно для скрытия строк, используемых в реализации, так как строки могут использоваться как сигнатуры для обнаружения вредоносных бинарных файлов.

Хеширование строк

Давайте рассмотрим некоторые алгоритмы хеширования строк. Важно понимать, что результатом этих алгоритмов является число, выраженное в шестнадцатеричном формате, так как это более аккуратно и компактно.

В этой статье рассматриваются следующие алгоритмы хеширования строк:

Dbj2
JenkinsOneAtATime32Bit
LoseLose Rotr32

Существует гораздо больше алгоритмов хеширования строк, чем те, которые обсуждаются в этой сьтатье, и некоторые из них можно найти в репозитории
Чтобы увидеть нужно авторизоваться или зарегистрироваться.


Рекомендую присмотреться к этому репозиторию, там очень много полезных модулей на С++ для разработки малвари и пентестинга.)

Dbj2


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

hash = ((hash << 5) + hash) + c

hash - это текущее хеш-значение, c - текущий символ во входной строке, и << - оператор битового сдвига влево.

Результирующее хеш-значение - это положительное целое число, уникальное для входной строки. Известно, что Djb2 производит хорошее распределение хеш-значений, что приводит к низкой вероятности коллизий между разными строками и их соответствующими хеш-значениями.

Реализация Djb2, представленная ниже, взята из репозитория VX-API на GitHub.

C:
#define INITIAL_HASH    3731  // добавлено для рандомизации хеширования
#define INITIAL_SEED    7     // генерация хешей Djb2 из строк ASCII
DWORD HashStringDjb2A(_In_ PCHAR String)
{
    ULONG Hash = INITIAL_HASH;
    INT c;

    while (c = *String++)
        Hash = ((Hash << INITIAL_SEED) + Hash) + c;

    return Hash;
}

// генерация хешей Djb2 из строк в широких символах
DWORD HashStringDjb2W(_In_ PWCHAR String)
{
    ULONG Hash = INITIAL_HASH;
    INT c;

    while (c = *String++)
        Hash = ((Hash << INITIAL_SEED) + Hash) + c;

    return Hash;
}

JenkinsOneAtATime32Bit

Алгоритм JenkinsOneAtATime32Bit работает, итерируясь по символам во входной строке и инкрементально обновляя текущее хеш-значение в соответствии с значением каждого символа. Алгоритм обновления хеш-значения продемонстрирован в следующем фрагменте.

hash += c; hash += (hash << 10); hash ^= (hash >> 6); hash - текущее хеш-значение, а c - текущий символ во входной строке.

Результирующее хеш-значение - это 32-битное целое число, уникальное для входной строки. Известно, что алгоритм JenkinsOneAtATime32Bit производит относительно хорошее распределение хеш-значений, что приводит к низкой вероятности коллизий между разными строками и их соответствующими хеш-значениями.

Реализация JenkinsOneAtATime32Bit, представленная ниже, взята из репозитория VX-API на GitHub.

C:
#define INITIAL_SEED    7    // Генерация хешей JenkinsOneAtATime32Bit из строк ASCII
UINT32 HashStringJenkinsOneAtATime32BitA(_In_ PCHAR String)
{
    SIZE_T Index = 0;
    UINT32 Hash = 0;
    SIZE_T Length = lstrlenA(String);

    while (Index != Length)
    {
        Hash += String[Index++];
        Hash += Hash << INITIAL_SEED;
        Hash ^= Hash >> 6;
    }

    Hash += Hash << 3;
    Hash ^= Hash >> 11;
    Hash += Hash << 15;

    return Hash;
}

// Генерация хешей JenkinsOneAtATime32Bit из строк в широких символах
UINT32 HashStringJenkinsOneAtATime32BitW(_In_ PWCHAR String)
{
    SIZE_T Index = 0;
    UINT32 Hash = 0;
    SIZE_T Length = lstrlenW(String);

    while (Index != Length)
    {
        Hash += String[Index++];
        Hash += Hash << INITIAL_SEED;
        Hash ^= Hash >> 6;
    }

    Hash += Hash << 3;
    Hash ^= Hash >> 11;
    Hash += Hash << 15;

    return Hash;
}

Rotr32

Алгоритм хеширования строк Rotr32 использует итерируемые символы во входной строке для суммирования их ASCII-значений, а затем применяет битовое вращение к текущему значению хеша. Входное значение и счет (счет равен INITIAL_SEED) используются для выполнения сдвига вправо на это значение, затем применяется операция ИЛИ с исходным значением, сдвинутым влево на отрицание счета.

Результирующее хеш-значение - это 32-битное целое число, уникальное для входной строки. Известно, что Rotr32 производит относительно хорошее распределение хеш-значений, что приводит к низкой вероятности коллизий между разными строками и их соответствующими хеш-значениями.

Реализация Rotr32, представленная ниже, взята из репозитория VX-API на GitHub.

C:
#define INITIAL_SEED    5 // Вспомогательная функция, которая применяет битовое вращение
UINT32 HashStringRotr32Sub(UINT32 Value, UINT Count)
{
    DWORD Mask = (CHAR_BIT * sizeof(Value) - 1);
    Count &= Mask;
#pragma warning( push )
#pragma warning( disable : 4146)
    return (Value >> Count) | (Value << ((-Count) & Mask));
#pragma warning( pop )
}

// Генерация хешей Rotr32 из строк ASCII
INT HashStringRotr32A(_In_ PCHAR String)
{
    INT Value = 0;

    for (INT Index = 0; Index < lstrlenA(String); Index++)
        Value = String[Index] + HashStringRotr32Sub(Value, INITIAL_SEED);

    return Value;
}

// Генерация хешей Rotr32 из строк в широких символах
INT HashStringRotr32W(_In_ PWCHAR String)
{
    INT Value = 0;

    for (INT Index = 0; Index < lstrlenW(String); Index++)
        Value = String[Index] + HashStringRotr32Sub(Value, INITIAL_SEED);

    return Value;
}

Стек строки

В языках программирования C/C++ строку можно представить как массив символов, разделяя символы друг от друга, что помогает избегать обнаружения на основе строк. Например, строку "hello world" можно представить следующим образом.

C:
char string[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0' };

1746784427195.png


Поиск строки "hello world" с использованием бинарного редактора HxD ничего не вернет.

Однако стека строки недостаточно, чтобы скрыть строку от некоторых отладчиков и инструментов обратной разработки, так как они могут содержать плагины для их обнаружения.

Рекомендуется также шифровать строки, если они используется в программе.)
Следующая страница цикла: Изучаем кунгфу-1. Скрытие таблицы импорта
Предыдущая статья цикла: Изучаем технику Spoofing