Генерация ключа для по хешу имени
Задача была такой, дизассемблировать прогу и написать генерацию пароля для произвольного имени. Имя и пароль могут содержать цифры и символы 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
Подбор пароля грубой силой. Всего 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

