Генерация ключа для по хешу имени

Задача была такой, дизассемблировать прогу и написать генерацию пароля для произвольного имени. Имя и пароль могут содержать цифры и символы a-z, A-Z.

Программа с начало преобразует имя в хеш через CRC, потом берет введенный пароль и выполняет исключающее или с каждым байтом пароля на 0x99, сумма прибавляется к результату. После обоих операций к результатам применяется логическое и на 0xFF, оставшиеся 2 байта у обоих чисел должны совпадать.

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

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

Переведенный ассемблерный листинг на С++:

DWORD32 genNameHash(byte *szName)
{
    DWORD32 crc = -1;
    for (DWORD idx = 0; idx < strlen((char *)szName); idx++)
    {
        crc ^= szName[idx];
        for (int j = 0; j < 8; j++)
        {
            char converChar = (crc & 1);
            crc = -converChar & 0xEDB88320 ^ (crc >> 1);

            /*
                .text:00438076 mov     eax, [ebp+crc]
                .text:00438079 and     eax, 1
                .text:0043807C neg     eax                      // CHAR!
                .text:0043807E mov     [ebp+numBuff], eax
                .text:00438081 mov     eax, [ebp+crc]
                .text:00438084 shr     eax, 1
                .text:00438086 mov     ecx, [ebp+numBuff]
                .text:00438089 and     ecx, 0EDB88320h
                .text:0043808F xor     eax, ecx
                .text:00438091 mov     [ebp+crc], eax
            */
        }
    }

    //printf("name hash: 0x%X(0x%X)\n", ~crc, ~crc & 0xFF);
    return ~crc;
}

DWORD32 genPassHash(byte* pass)
{
    size_t idx = 0; DWORD32 hash = 0;
    while (pass[idx] != NULL)
    {
        hash += pass[idx++] ^ 0x99;

        /*
            mov     eax, [ebp+pass]
            add     eax, [ebp+i]
            movsx   ecx, byte ptr [eax]
            xor     ecx, 99h
            add     ecx, [ebp+hash]
            mov     [ebp+hash], ecx
        */
    }

    //printf("pass hash: 0x%X(0x%X)\n", hash, hash & 0xFF);
    return hash;
}

bool isValid(byte *str) 
{
    size_t idx = 0;
    while (str[idx] != NULL)
    {
        if (str[idx] >= 'a' && str[idx] <= 'z' || 
            str[idx] >= 'A' && str[idx] <= 'Z' || 
            str[idx] >= '0' && str[idx] <= '9') 
        {
            idx++; 
            continue;
        }

        return false;
    }

    return true;
}

bool isAccess(byte* name, byte* pass)
{
    if (!isValid(name)) {
        return MessageBox(NULL, "Name a-z, A-Z, 0-9", "Falsch Daten", 0);
    }

    if (!isValid(pass)) {
        return MessageBox(NULL, "Pass a-z, A-Z, 0-9", "Falsch Daten", 0);
    }

    return (genNameHash(name) & 0xFF) == (genPassHash(pass) & 0xFF);

    /*
        mov     [ebp+nameHash], 0
        mov     [ebp+passHash], 0
        mov     eax, [ebp+name]
        push    eax
        call    j_nameHash
        add     esp, 4
        and     eax, 0FFh
        mov     [ebp+nameHash], eax
        mov     eax, [ebp+pass]
        push    eax             ; Str
        call    j_genPassHash
        add     esp, 4
        and     eax, 0FFh
        mov     [ebp+passHash], eax
        mov     eax, [ebp+nameHash]
        cmp     eax, [ebp+passHash]
        jnz     short loc_437ED8
    */
}

Мой генератор пароля:

bool checkByte(byte b) {
    return  (
                (b >= '0' && b <= '9') ||
                (b >= 'a' && b <= 'z') ||
                (b >= 'A' && b <= 'Z')
            );
}

byte* genPass(byte nameHash)
{
    static byte key[37] = { NULL };
    memset(key, 0, sizeof(key));

    // 0xD1 (209)
    /*
        209 ^ 153(0x99) = 48 ("H")
        209 - 209 = 0
        
        name "hallo"  (0xD1)    = pass "H"
        name "hallo1" (0xC4)    = error
        name "hallo2" (0x7E)    = error
        name "hallo3" (0xE8)    = pass "q"
    */

    for (size_t idx = 0; idx < 37; idx++)
    {
        for (byte c = 'z'; c > '0'; c--)
        {
            byte b = c ^ 0x99;
            if (b > nameHash) {
                continue;
            }

            // Проверка символа, подходит ли он и подойдет ли следующий, если вычесть текущий
            if (checkByte(c) && nameHash - b >= '0' || checkByte(c) && nameHash - b == NULL)
            {
                nameHash -= b;
                key[idx] = c;
            }

            // Выход из цикла, если весь хеш разложен на строку
            if (nameHash == 0) {
                return key;
            }
        }
    }
    
    return nullptr;
}

int main() {
    // Crachme Lektion 3
    //return printf("Name: hallo\nKey: %u", genKey("hallo")), true;


    // Crackme Lektion 8
    byte name[] = "hallo", pass[] = "v";
    printf("Access: %s\n", (isAccess(name, pass)) ? "true" : "false");
    for (size_t i = 0; i < 20; i++)
    {
        
        char buff[100]; 
        sprintf(buff, "%s%d", name, i);
        byte hash = genNameHash((byte *)buff);
        byte* pass = genPass(hash);

        printf("Name: '%s'\t(0x%X)\tpass: %s\n", buff, hash, pass);
    }
    
    getchar();

    return true;
}

Результат генерации (null это ошибки генерации, их около 80%):

Результат генератора Результат выполнения в учебной программе


Ответы (2 шт):

Автор решения: Владос

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

byte* brutePass(byte nameHash)
{
    static byte key[10] = { NULL }, tab['9' - '0' + 'z' - 'a' + 'Z' - 'A' + 3] = { NULL };
    memset(key, NULL, sizeof(key)); // because static var

    auto gni = [](byte b) -> byte // get next index
    {
        for (byte idx = 0; idx < sizeof(tab); idx++)
        {
            if (tab[idx] == b) {
                return (idx + 1 < sizeof(tab)) ? idx + 1 : 255;
            }
        }

        return 255; // trigger for [idx + 1] = '+'
    };

    if (tab[0] == NULL) // Table loading...
    {
        byte tidx = 0;
        for (byte b = '0'; b <= 'z'; b++)
        {
            if (checkByte(b)) { // 0-9, a-z, A-Z
                tab[tidx++] = b;
            }
        }
    }

    static byte hash_cache[256][4] = { NULL };
    if (hash_cache[nameHash][0] != NULL) {
        return printf("Password findet in table: %s\n", hash_cache[nameHash]), hash_cache[nameHash];
    }
    strcpy((char*)key, "aaa");

    while (true)
    {
        byte res = NULL;
        for (size_t idx = 0; idx < sizeof(tab); idx++)
        {
            key[0] = tab[idx]; // +XX
            byte hash = genPassHash(key);
            strcpy((char*)hash_cache[hash], (char *)key);

            if (hash == nameHash) {
                return key;
            }

            //printf("KEY: %s\t\tHash: 0x%X\t\tZiel: 0x%X\n", key, hash, nameHash);
        }

        for (byte idx = 0; idx < 3; idx++)
        {
            byte nidx = gni(key[idx]);
            if (nidx == 255) 
            {
                key[idx] = tab[0];
                if (idx == 2) {
                    return MessageBox(NULL, "Overflow", "Something going wrong...", NULL), key;
                }

                nidx = gni(key[idx + 1]);
                key[idx + 1] = (nidx == 255) ? '+' : tab[nidx];
            }
        }
    }

    return key;
}

Результат:

Name: hallo0            Password: 70b
Hash: 0x52:0x52         Access: true


Password findet in table: uya
Name: hallo1            Password: uya
Hash: 0xC4:0xC4         Access: true


Name: hallo2            Password: C0b
Hash: 0x7E:0x7E         Access: true


Password findet in table: coa
Name: hallo3            Password: coa
Hash: 0xE8:0xE8         Access: true


Name: hallo4            Password: 11b
Hash: 0x4B:0x4B         Access: true


Password findet in table: nwa
Name: hallo5            Password: nwa
Hash: 0xDD:0xDD         Access: true


Password findet in table: Z0b
Name: hallo6            Password: Z0b
Hash: 0x67:0x67         Access: true


Password findet in table: bga
Name: hallo7            Password: bga
Hash: 0xF1:0xF1         Access: true


Name: hallo8            Password: Z9d
Hash: 0x60:0x60         Access: true


Password findet in table: ggc
Name: hallo9            Password: ggc
Hash: 0xF6:0xF6         Access: true


Password findet in table: fgc
Name: hallo10           Password: fgc
Hash: 0xF7:0xF7         Access: true


Password findet in table: Z8d
Name: hallo11           Password: Z8d
Hash: 0x61:0x61         Access: true


Password findet in table: jwc
Name: hallo12           Password: jwc
Hash: 0xDB:0xDB         Access: true


Password findet in table: 68d
Name: hallo13           Password: 68d
Hash: 0x4D:0x4D         Access: true


Password findet in table: goc
Name: hallo14           Password: goc
Hash: 0xEE:0xEE         Access: true


Password findet in table: B9d
Name: hallo15           Password: B9d
Hash: 0x78:0x78         Access: true


Password findet in table: qyc
Name: hallo16           Password: qyc
Hash: 0xC2:0xC2         Access: true


Password findet in table: 07d
Name: hallo17           Password: 07d
Hash: 0x54:0x54         Access: true


Password findet in table: ryc
Name: hallo18           Password: ryc
Hash: 0xC5:0xC5         Access: true


Password findet in table: 17d
Name: hallo19           Password: 17d
Hash: 0x53:0x53         Access: true
→ Ссылка
Автор решения: Stanislav Volodarskiy

Подбор пароля грубой силой. Всего 256 различных хешей. По коду, вычисляющему хеш пароля, можно догадаться, что длинные пароли не нужны. Я положил верхнюю границу - 16 символов, оказалось что достаточно паролей длины не более трёх. Алфавит - 10 + 26 + 26 = 62 символа. Для подбора одного пароля делается не более 242234 попыток. Подбор можно было сделать лучше, но и так не плохо.

#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

typedef unsigned DWORD;
typedef uint32_t DWORD32;
typedef unsigned char byte;

DWORD32 genNameHash(byte *szName)
{
    DWORD32 crc = (DWORD32)-1;
    for (DWORD idx = 0; idx < strlen((char *)szName); idx++)
    {
        crc ^= szName[idx];
        for (int j = 0; j < 8; j++)
        {
            DWORD32 converChar = (crc & 1);
            crc = (-converChar & 0xEDB88320) ^ (crc >> 1);

            /*
                .text:00438076 mov     eax, [ebp+crc]
                .text:00438079 and     eax, 1
                .text:0043807C neg     eax                      // CHAR!
                .text:0043807E mov     [ebp+numBuff], eax
                .text:00438081 mov     eax, [ebp+crc]
                .text:00438084 shr     eax, 1
                .text:00438086 mov     ecx, [ebp+numBuff]
                .text:00438089 and     ecx, 0EDB88320h
                .text:0043808F xor     eax, ecx
                .text:00438091 mov     [ebp+crc], eax
            */
        }
    }

    //printf("name hash: 0x%X(0x%X)\n", ~crc, ~crc & 0xFF);
    return ~crc;
}

DWORD32 genPassHash(byte* pass)
{
    size_t idx = 0; DWORD32 hash = 0;
    while (pass[idx] != '\0')
    {
        hash += pass[idx++] ^ 0x99;

        /*
            mov     eax, [ebp+pass]
            add     eax, [ebp+i]
            movsx   ecx, byte ptr [eax]
            xor     ecx, 99h
            add     ecx, [ebp+hash]
            mov     [ebp+hash], ecx
        */
    }

    //printf("pass hash: 0x%X(0x%X)\n", hash, hash & 0xFF);
    return hash;
}


bool guessPassRec(unsigned hash, int n, char pass[/* n + 1 */], int i) {
    if (i == n) {
        pass[n] = '\0';
        return (genPassHash((byte *)pass) & 0xFF) == hash;
    }

    const char *alphabet =
        "0123456789"
        "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    for (const char *p = alphabet; *p != '\0'; ++p) {
        pass[i] = *p;
        if (guessPassRec(hash, n, pass, i + 1)) {
            return true;
        }
    }
    return false;
}

bool guessPass(unsigned hash, int n, char pass[/* n */]) {
    for (int i = 1; i < n; ++i) {
        if (guessPassRec(hash, i, pass, 0)) {
            return true;
        }
    }
    assert(n >= 2);
    strcpy(pass, "?");
    return false;
}

#define MAX_PASS_SIZE 17

int main(int argc, char **argv) {
    if (argc == 1) {
        char pass[256][MAX_PASS_SIZE];
        for (unsigned h = 0; h < sizeof(pass) / sizeof(pass[0]); ++h) {
            guessPass(h, sizeof(pass[h]), pass[h]);
        }
        int max_w = 0;
        for (unsigned h = 0; h < sizeof(pass) / sizeof(pass[0]); ++h) {
            int w = (int)strlen(pass[h]);
            max_w = (w > max_w) ? w : max_w;
        }
        max_w = (2 > max_w) ? 2 : max_w;

        printf("  ");
        for (int j = 0; j < 16; ++j) {
            char buffer[3];
            sprintf(buffer, "_%x", j);
            printf(" %*s", max_w, buffer);
        }
        printf("\n");
        for (int i = 0; i < 16; ++i) {
            printf("%x_", i);
            for (int j = 0; j < 16; ++j) {
                printf(" %*s", max_w, pass[16 * i + j]);
            }
            printf("\n");
        }
        printf("\n");
    }
    {
        char pass[MAX_PASS_SIZE];
        for (int i = 1; i < argc; ++i) {
            unsigned hash = genNameHash((byte *)argv[i]) & 0xFF;
            guessPass(hash, sizeof(pass), pass);
            printf("%s 0x%02x %s\n", argv[i], hash, pass);
        }
    }
}
$ gcc -std=c11 -pedantic -Wall -Wextra -Werror -Wwrite-strings -Wconversion guess.c

"Радужная" таблица:

$ ./a.out
    _0  _1  _2  _3  _4  _5  _6  _7  _8  _9  _a  _b  _c  _d  _e  _f
0_ 007 006 027 026 047 046 067 066 19Y 09Y 08Y 08X 09Z 08Z 29Z 28Z
1_ 11Y 01Y 00Y 00X 01Z 00Z 03Z 02Z 05Z 01Q 00Q 00P 00S 00R 00U 00T
2_ 00W 00V 00I 00H 00K 00J 00M 00L 00O 00N 00A 01C 00C 00B 00E 00D
3_ 00G 00F 00y 00x 01z 00z 03z 02z 05z 01q 00q 00p 00s 00r 00u 00t
4_  99  89  88 00h 00k 00j 00m 00l  19  09  08  29  28  49  48  69
5_  11  01  00  03  02  05  04  07  06  27  26  47  46  67  66 0jZ
6_  9Y  8Y  8X  9Z  8Z 0bX 0cZ 0bZ  1Y  0Y  0X  1Z  0Z  3Z  2Z  5Z
7_  1Q  0Q  0P  0S  0R  0U  0T  0W  0V  0I  0H  0K  0J  0M  0L  0O
8_  0N  0A  1C  0C  0B  0E  0D  0G  0F  0y  0x  1z  0z  3z  2z  5z
9_  1q  0q  0p  0s  0r  0u  0t  0w  0v  0i  0h  0k  0j  0m  0l  0o
a_   9   8  1c  0c  0b  0e  0d  0g   1   0   3   2   5   4   7   6
b_  iY  hY  hX  iZ  hZ  kZ  jZ  mZ  aY  aX  cY  aZ  bX  cZ  bZ  eZ
c_   Y   X  aS   Z  aU  aT  aW  aV   Q   P   S   R   U   T   W   V
d_   I   H   K   J   M   L   O   N   A  ax   C   B   E   D   G   F
e_   y   x  as   z  au  at  aw  av   q   p   s   r   u   t   w   v
f_   i   h   k   j   m   l   o   n   a  bg   c   b   e   d   g   f

Подбор по именам:

$ ./a.out hello0 hello1 hello2 hello3
hello0 0x92 0p
hello1 0x04 047
hello2 0xbe bZ
hello3 0x28 00O
→ Ссылка