Альтернативный Метод написания КейГена на примере Professional Minesweeper v1.2 Shareware version
Оформил: DeeCo
Автор: http://www.cracklab.narod.ru
Инструменты:
1) SoftIce
2) С++
3) Знание С++&&Asm (и опыт работы :)
На примере этой простой (в плане защиты) программки я покажу, как просто
научится писать КейГены. Конечно же эта статья не распространяется на
защиты, в которых быстрый перебор невозможен, однако...
Моё мнение таково: в каждом отдельно взятом случае своя ситуация, по-своему
неповторимая и интересная. Так что применять какие-то алгоритмы или правила
в данных ситуациях просто глупо. Однако, из таких вот пустяшных туториалов
можно почерпнуть много общих идей, при помощи которых позже вы сможете
сами анализировать защитный механизм и сами писать КейГен.
Итак, в данной статье мы:
- Найдём процедуру проверки РН (рег.номер).
- Проанализируем все действия этой процедуры
- Напишем на С++ КейГен, который будет емулировать эту процедуру.
- Лирические отступления.
(1) Локализовываем защиту.
Итак, найдя окно регистрации, введём в него "vallkor" и "123321"
нажмём "ОК" и увидим MessageBox с инфой о том, что РН неправильный.
Вываливаем Айс и ставим бряк:
bpx MessageBoxA do "p ret"
и выйдя из айса, опять нажмём "ОК", при этом окажемся тут:
015F:00404FE3 PUSH 0041EDC8
015F:00404FE8 PUSH 65
015F:00404FEA PUSH EBX
015F:00404FEB CALL USER32!GetDlgItemTextA <--Считали имя
015F:00404FF0 PUSH 0A
015F:00404FF2 PUSH 0041EE18
015F:00404FF7 PUSH 66
015F:00404FF9 PUSH EBX
015F:00404FFA CALL USER32!GetDlgItemTextA <--Считали РН
015F:00404FFF PUSH 0041EE18 <--сохранили адрес РН
015F:00405004 PUSH 0041EDC8 <--сохранили адрес имени
015F:00405009 CALL 00405657 <--!!!Процедура проверки
015F:0040500E ADD ESP,08
015F:00405011 MOV [0041EE22],AL
015F:00405016 TEST AL,AL
015F:00405018 JZ 0040502B <--Если её результаты плохи - прыжок на плохое сообщение
015F:0040501A MOV BYTE PTR [0041C074],00
015F:00405021 PUSH 00
015F:00405023 PUSH EBX
015F:00405024 CALL USER32!EndDialog <--иначе - закроем окно
015F:00405029 JMP 0040503E
015F:0040502B PUSH 30
015F:0040502D PUSH DWORD PTR [0041C078]
015F:00405033 PUSH 0041EF13
015F:00405038 PUSH EBX
015F:00405039 CALL USER32!MessageBoxA
Как вы уже поняли, процедура считывания Имени и РН да и сам вызов Функции
проверки РН на валидность очень прозрачны и сразу можно догадаться, что
CALL 00405657 - вызов необходимой нам процедуры.
Теперь необходимо понять логику этой процедуры...
(2) Анализ защитного механизма.
Зайдём в функцию, которую мы обнаружили и увидим:
015F:00405657 PUSH EBP
015F:00405658 MOV EBP,ESP
015F:0040565A ADD ESP,-1C
015F:0040565D PUSH EBX
015F:0040565E PUSH ESI
015F:0040565F PUSH EDI
015F:00405660 MOV EBX,[EBP+0C]
015F:00405663 MOV ECX,[EBP+08]
015F:00405666 XOR EAX,EAX
015F:00405668 MOVSX EDX,BYTE PTR [EAX+EBX] <--Загружаем первую цифру РН
015F:0040566C ADD EDX,-30 <--Переводим её из символьного в цифровой вид
015F:0040566F MOV [EAX*4+EBP-1C],EDX <--сохраняем по адресу EBP-1C
015F:00405673 CMP DWORD PTR [EAX*4+EBP-1C],00 <--сравниваем, цифра ли это
015F:00405678 JL 00405681 <--если
015F:0040567A CMP DWORD PTR [EAX*4+EBP-1C],09 <-- нет
015F:0040567F JLE 004056A4 <-- то выходим из функции проверки
015F:00405681 PUSH 0041C42D
015F:00405686 PUSH ECX
015F:00405687 CALL 00414780
015F:0040568C ADD ESP,08
015F:0040568F PUSH 0041C435
015F:00405694 PUSH EBX
015F:00405695 CALL 00414780
015F:0040569A ADD ESP,08
015F:0040569D XOR EAX,EAX
015F:0040569F JMP 00405749
015F:004056A4 INC EAX
015F:004056A5 CMP EAX,07 <--Символов, судя по счётчику, должно быть 7
015F:004056A8 JL 00405668
В этом куске кода программа проверяет, чтобы РН состоял только из Цифр и
чтобы их было ровно 7.
Смотрим дальше:
015F:004056AA IMUL EAX,[EBP-0C],000003E8
015F:004056B1 IMUL EDX,[EBP-14],64
015F:004056B5 ADD EAX,EDX
015F:004056B7 MOV EDX,[EBP-04]
015F:004056BA ADD EDX,EDX
015F:004056BC LEA EDX,[EDX*4+EDX]
015F:004056BF ADD EAX,EDX
015F:004056C1 ADD EAX,[EBP-1C]
015F:004056C4 IMUL ESI,EAX,0D
015F:004056C7 MOV EAX,ESI
015F:004056C9 MOV ESI,000000C5
015F:004056CE CDQ
015F:004056CF IDIV ESI
015F:004056D1 MOV ESI,EDX
===================
015F:004056D3 XOR EDI,EDI
015F:004056D5 XOR EAX,EAX
015F:004056D7 CMP BYTE PTR [EAX+ECX],00
015F:004056DB JZ 004056E9
015F:004056DD MOVSX EDX,BYTE PTR [EAX+ECX]
015F:004056E1 ADD EDI,EDX
015F:004056E3 INC EAX
015F:004056E4 CMP EAX,50
015F:004056E7 JL 004056D7
=====================
015F:004056E9 MOV EAX,ESI
015F:004056EB PUSH ECX
015F:004056EC MOV ECX,0000000A
015F:004056F1 CDQ
015F:004056F2 IDIV ECX
015F:004056F4 POP ECX
015F:004056F5 ADD EAX,EDI
015F:004056F7 MOV EDI,00000064
015F:004056FC CDQ
015F:004056FD IDIV EDI
015F:004056FF MOV EDI,EDX
015F:00405701 MOV EAX,ESI
015F:00405703 MOV ESI,0000000A
015F:00405708 CDQ
015F:00405709 IDIV ESI
015F:0040570B MOV EAX,EDI
015F:0040570D ADD EAX,EAX
015F:0040570F LEA EAX,[EAX*4+EAX]
015F:00405712 ADD EDX,EAX
015F:00405714 MOV ESI,EDX
015F:00405716 IMUL EAX,[EBP-10],64
015F:0040571A MOV EDX,[EBP-18]
015F:0040571D ADD EDX,EDX
015F:0040571F LEA EDX,[EDX*4+EDX]
015F:00405722 ADD EAX,EDX
015F:00405724 ADD EAX,[EBP-08]
015F:00405727 CMP EAX,ESI
015F:00405729 JZ 00405749
Здесь можно было бы начать анализировать алгоритм, но я решил поступить
оригинально, я выяснил, что скрывается под [EBP-xx]:
[EBP-04]=7цифра РН
[EBP-08]=6цифра РН
[EBP-0C]=5цифра РН
[EBP-10]=4цифра РН
[EBP-14]=3цифра РН
[EBP-18]=2цифра РН
[EBP-1С]=1цифра РН
Потом я выяснил, что делается в этом цикле:
015F:004056D3 XOR EDI,EDI
015F:004056D5 XOR EAX,EAX
015F:004056D7 CMP BYTE PTR [EAX+ECX],00
015F:004056DB JZ 004056E9
015F:004056DD MOVSX EDX,BYTE PTR [EAX+ECX]
015F:004056E1 ADD EDI,EDX
015F:004056E3 INC EAX
015F:004056E4 CMP EAX,50
015F:004056E7 JL 004056D7
А именно сумируются все сиволы введённого имени и результат записывается
в EDI
Заня это я решил не упрощать задачу, а просто "эмулировать" процедуру
проверки. Т.е. я взял C++ и там создал переменные
unsigned long eax,ebx,edx,ecx,edi,esi,SUM;
Т.е. все необходимые регистры процессора и переменная SUM, в которой будет
хранится сумма всех символов введённого имени.
Завёл переменную
char number[7]="\0\0\0\0\0\0\0";
это будет число, которое мы будем наращивать в цикле и смотреть, является ли
оно правильным РН или нет.
Вобщем сделал все так, как в программе (Люди, которые мало-мальски знакомы
с программированием поймут меня, а люди, которые нет - им в писании кейгена
места нету).
(3) Сам КейГен.
#include <iostream.h>
#include <conio.h>
void inc(char *number) //Функция увеличения на 1 нашего перебираемого числа
{
int i;
number[6]++;
if (number[6]>9)
{
for (i=6;i>=0;i--)
{
number[i]=0;
number[i-1]++;
if (number[i-1]<10) break;
}
}
}
void main()
{
char name[50]; //наше имя
char number[7]="\0\0\0\0\0\0\0"; //перебираемое число
unsigned long eax,ebx,edx,ecx,edi,esi,SUM,i,passcount=0;
int k,l;
cout<<"KeyGen For Professional Minesweeper v1.2 Shareware version \n";
cout<<"cracked by vallkor (vallkor@chat.ru) or http://vallkor/chat.ru \n\n";
cout<<"Enter your name:";
cin>>name;
SUM=0;
for (i=0;name[i]!=0;i++) //в этом цикле считаем сумму всех букв имени
SUM=SUM+name[i];
for (i=0;i<9999999;i++) //главный цикл перебора
//в котором мы переписываем всю логическую
//часть функции проверки
//преобразовывая ассемблерный код в C++
{
eax=number[4]*0x3E8;
edx=number[2]*0x64;
eax+=edx;
edx=10*number[6];
eax+=edx;
eax+=number[0];
esi=eax*0x0D;
eax=esi;
esi=0xC5;
edx=eax%esi;
eax=eax/esi;
esi=edx;
edi=SUM;
eax=esi;
ecx=0x0A;
edx=eax%ecx;
eax=eax/ecx;
eax+=edi;
edi=0x64;
edx=eax%edi;
eax=eax/edi;
edi=edx;
eax=esi;
esi=0x0A;
edx=eax%esi;
eax=eax/esi;
eax=edi*10;
edx+=eax;
esi=edx;
eax=number[3]*0x64;
edx=number[1]*10;
eax+=edx;
eax+=number[5];
if (eax==esi)
{
for(k=0;k<7;k++) cout<<number[k]+0;
cout<<"\n";
passcount++;
}
inc(number); //наращиваем перебираемое число и на следующее прохождение
}
cout<<"\n Password's Found:"<<passcount; //вывели количество найденых правильных РН для нашего имени
getch();
}
(4)
Как оказалось, для любого имени всегда можно найти 10000 правильных РН.
Т.е. даже простой чудак, перебрав сотню-другую паролей смог бы найти
правильный для себя, но в том-то и прикол, что простой чувак не знает об
этом.
Вообще приведеный метод анализа и построения перебора без детального разбора
алгоритма хорош только в таких вот "слабых" защитах, где время, затраченное
на перебор в 100 раз меньше, чем нужно для детального анализа.
К тому же если оптимизировать цикл (а там много чего можно оптимизировать,
т.к. он просто сконвертирован в С++ из ассемблерного вида безо всякого
изменения), то все пароли переберутся ещё в 10-ки раз быстрее.
Вобщем задача выполнена, программа закейгенена без особых усилий. А остальное
уже флейм, а значит позвольте мне откланяться и идти переписывать конспекты
закошенных по вине написания этого туториала лекций :)
Извращался, исследовал и наваял туториал:
vallkor //PTDS
E-mail: vallkor@chat.ru
Page : http://vallkor.chat.ru
|