Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
выпущен 12 ноября!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

Исследование CDCheck 3.1.5.0 и написание кейгена

Обсудить статью на форуме

Очень удобно, когда все крэкерские инструменты, книги и статьи в одном месте. Используйте сборник от EXELAB - вот тут.

Автор: Ra$cal <rascalspb@mail.ru>

Приветствую всех читателей этого документа. В этот раз меня заинтересовала программа CDCheck. Интерес вызван обыденной проблемой – после записи болванки файлы не читаются. Полез искать программы, которые могли бы проверить качество записанной информации.После поисков в сети и прочтения некоторых мнений на софт-сайтах решил попробовать именно эту программу. Итак, что именно заинтересовало – не очень большой размер, удобство использования (после пары минут боёв с GUI:), ну и сам способ проверки внушает доверие – сравнение хэшей исходных и записанных файлов. Теперь посмотрим, как выглядит способ защитны этой программы :)
По поводу тутора - расписал всё очень подробно (как мне кажется), даже исходник прилепил. Писал в ужасную жару, так что не серчайте, если что-нибудь непонятно, путанно или заумно написано:)

Инструменты:
OllyDebug
DeDe
PEiD
UPX
MicroSoft Visual Studio (я использовал 7)

Ну что, поехали, как говорил один хороший человек, но почему-то полетел :)
Сначала осмотримся...
Для начала смотрим, запакована ли программа и если да, то чем. PEiD уверяет, что UPX. Верим на слово и распаковываем самим UPX’ом. Смотрим компилятор – Borland C++. Теперь берём в руки Olly и начинаем копать…
Запускаем и начинаем искать, с чего начать, отправную, так сказать, точку. Жмём кнопочку “О программе”. Всего одно поле ввода для регистрации. Уж и не вспомнить, когда последний раз я видел программу с одним окошком. А может за кажущейся простотой кроется что-нить этакое - привязка к железу, к винде?! Ладно, что-то я тучи сильно сгущаю. Лучше один раз глянуть, чем много гадать. Ищем вызовы привычных функций, но кроме одного GeTWindowText ничего нет. Так как компилер борландовский, пихаем экзешник в DeDe, ищем форму about и реакцию на клик по кнопке “Регистрировать”. Находим, что вызывается метод по адресу:

0041899C   55                     push    ebp


Видим там ещё несколько интересных строк


004189DA   8B83EC020000           mov     eax, [ebx+$02EC]
 * Reference to: controls.TControl.GetText(TControl):TCaption;
 004189E0   E82B680F00             call    0050F210

Читаем введённый текст


004189EA   8B00                   mov     eax, [eax]
 * Reference to: sysutils.StrToIntDef(S:
 004189EC   E8CBD61100             call    005360BC

И приводим его к типу int

Ставим бряк и начинаем следить…
Итак, с функциями, описанными выше всё понятно, смотрим дальше:


00418A05   E8867A0700             call    00490490


Первая функция, следующая сразу за считыванием номера. Может здесь будет что-то интересное…
Но нет, здесь номер заносится в реестр, так что продолжаем искать…
Следом идёт 3 ещё менее интересных вызова, а вот далее вызов и следом проверка


00418A3D  |. E8 9EAF0100    CALL unpacked.004339E0


Заходим… И в DeDe тоже… Опять несколько интересных строчек:


00433ADA   68F7395700             push    $005739F7
 * Reference to: GetPrivateProfileStringA()
 00433ADF   E812911200             call    0055CBF6


и ещё ниже


00433B3A   8B00                   mov     eax, [eax]
 * Reference to: sysutils.StrToIntDef(S:
 00433B3C   E87B251000             call    005360BC


Давайте здесь остановимся (надеюсь Вы ввели хоть какой-нибудь рег. номер :) Ставим бряк, отпускаем программу и… пролетаем. Странно. Перед этим вызовом идёт вызов функции GetPrivateProfileStringA. Она считывает из ini файла параметр ***. Как можно заметить, параметр здесь говорит сам за себя - "RegCode". Ладно, смотрим в оле ниже.

00433B54  |> 8B4D BC        MOV ECX,DWORD PTR SS:[EBP-44]


Вот этот значок (“>”) говорит, что сюда направлен прыжок. Прыжок идёт сверху, пропуская приличный кусок кода, в том числе и чтение данных из файла настройки. Логически рассуждая ( + немного экспереминетов ;) можно предположить, что мы находимся в функции проверки номера, причём номер достаётся и из реестра, и из ini файла, правда не сразу отовсюду, а в зависимости от ситуации (сначала пытается считать из реестра, если записи о номере нет, пытается из ini-файла). Сама функция не получает параметров. Странно. Немного помозговав понимаем, что работает это следующим образом – когда мы вводим номер он записывается в реестр (это мы отметили в самом начале, сразу после преобразованию к типу int), а метод проверки всегда достаёт номер из registry. Не очень удобный способ конечно, но это не наши проблемы. Главное, что из этого следует - патчить (если придётся) нам будет не напряжно – всего-то в одном месте :) Продолжим осматриваться...

Любопытнейшее место:


00433B54  |> 8B4D BC        MOV ECX,DWORD PTR SS:[EBP-44]
 00433B57  |. 51             PUSH ECX                                 		; /Arg1
 00433B58  |. E8 FFCA0500    CALL unpacked.0049065C                  	 ; \unpacked.0049065C


Вызов функции, параметр которой наш номер. Зайдём :)



00490665  |. 85F6           TEST ESI,ESI                             ;  Проверяем, есть ли вообще номер
 00490667  |. 7F 04          JG SHORT unpacked.0049066D               ;  если есть, работаем
 00490669  |. 33C0           XOR EAX,EAX                              ;  иначе обнулимся
 0049066B  |. EB 34          JMP SHORT unpacked.004906A1              ;  и на выход
 0049066D  |> BB 02000000    MOV EBX,2                                ;  цикл начинается с 2
 00490672  |. EB 0E          JMP SHORT unpacked.00490682
 00490674  |> 8BC6           /MOV EAX,ESI                             ;  введённый номер
 00490676  |. 99             |CDQ
 00490677  |. F7FB           |IDIV EBX                                ;  делим на EBX (счётчик цикла)
 00490679  |. 85D2           |TEST EDX,EDX                            ;  Проверяем остаток
 0049067B     75 04          JNZ SHORT unpacked.00490681              ;  Если не ноль, продолжаем
 0049067D  |. 33C0           |XOR EAX,EAX                             ;  Иначе обнуляемся
 0049067F     EB 20          JMP SHORT unpacked.004906A1              ;  И валим
 00490681  |> 43             |INC EBX                                 ;  Прибавим EBX
 00490682  |> 8975 FC         MOV DWORD PTR SS:[EBP-4],ESI            ;  В стек номер
 00490685  |. DB45 FC        |FILD DWORD PTR SS:[EBP-4]               ;  Из стека в регистр FPU
 00490688  |. 83C4 F8        |ADD ESP,-8                              ; /
 0049068B  |. DD1C24         |FSTP QWORD PTR SS:[ESP]                 ; |Arg1 (8-byte)
 0049068E  |. E8 598C0B00    |CALL unpacked.005492EC                  ; \Извлечь корень помещённого числа
 00490693  |. 83C4 08        |ADD ESP,8
 00490696  |. E8 05780B00    |CALL unpacked.00547EA0                  ;  Корень в EAX
 0049069B  |. 3BD8           |CMP EBX,EAX                             ;  Сравниваем корень и счётчик
 0049069D  |.^7E D5          \JLE SHORT unpacked.00490674



Вроде довольно подробно прокомментировал, теперь надо разобраться, что же здесь делается? Это проверка нашего номера. Бросается в глаза, что цикл начинается не с 0, и даже не 1, а с 2, и при этом увеличивается. Цель – видимо избежать деления на единицу. Зачем? Чтобы не выйти сразу, т.к. условие для выхода – деление без остатка, а на единицу делится любое число. Алгоритм реализован как-то очень корявенько – зачем же каждый раз извлекать корень из номера (ну не удержался я от критики :). Но это, повторюсь, не наши проблемы. Теперь, собсно, сам алгоритм – из номера извлекается корень – это предел счётчика. Введённый номер делится на значение счётчика, если нацело – выходим с ошибкой. Остроумно. Таким свойствами обладают лишь простые числа (точнее такие числа называются простыми (они делятся лишь на себя и единицу)). Значит наш номер должен являться простым числом. Это, например, число 13. Пишем его и смотрим, что будет... Да, мы благополучно дошли до конца и в AL у нас теперь 1. Смотрим дальше:


00433B66  |. 817D BC 40420F>CMP DWORD PTR SS:[EBP-44],0F4240
 00433B6D  |. 0F82 A6000000  JB unpacked.00433C19


Итак, номер должен быть больше F4240 (1000000). Ради любопытства подменим флажок C и посмотрим, что нас ждёт дальше


00433B73  |. FF4D BC        DEC DWORD PTR SS:[EBP-44]


Номер уменьшается на единицу.


00433B76  |. B9 DF030000    MOV ECX,3DF
 00433B7B  |. 8B45 BC        MOV EAX,DWORD PTR SS:[EBP-44]
 00433B7E  |. 33D2           XOR EDX,EDX
 00433B80  |. F7F1           DIV ECX
 00433B82  |. 85D2           TEST EDX,EDX
 00433B84  |. 75 13          JNZ SHORT unpacked.00433B99


Как можно заметить теперь номер делят на константу 3DF (991), дальше на 3E5, потом на 3F1, следом на 3F5, и наконец на 2665. Попробуйте изменяя флажки посмотреть, что будет, если наш номер будет делиться на какое-нибудь из этих чисел без остатка... Поздравления и... тип регистрации. Код типа лицензии, кстати, кладётся перед проверкой в EAX. Вот мы и осмотрелись, итог:
1) Номер должен быть простым числом
2) Номер должен быть больше F4240 (1000000)
3) Номер – 1 должен делится без остатка на одну из констант (желательно на 2665 – корпоративная лицензия :)
4) Ну и номер не должен выходить за пределы типа int


Активные действия
Итак, что делать вроде понятно, но вот как – я уже не соображаю (за окном +28,камень 48С, так ещё и кот рядом с вентиляционными отверстиями прикемарил, охлаждается в выдуваемом кулером воздухе – не отогнать :). Пока олю, DeDe и прочий инструментарий можно закрывать. Теперь надо думать, как найти номерок. Первое, что приходит в голову:
чтобы получить номер надо проделать обратные операции – то есть взять число, умножить его на 2665h и прибавить единицу. Остаётся одна проблема – получившееся число должно быть простым, а это уже сложнее. Кстати, вспомнил одну вещь – человеку, который получит простое число, длиной более не_помню_сольких (вроде в десятках измеряется) знаков вручат не_помню_сколько_много долларов :) но чё-то в районе миллиона. Я это к чему – нет алгоритма получения простых чисел, нет чёткой зависимости, последовательности (как это ещё назвать), а у нас именно такая задача и стоит, да ёще чтоб при вычитании 1 оно делилось без остатка. Мне видится один путь – написать программку, которая будет перебирать числа в поисках нужного. Итак, программа должна сделать следующее – создать массив чисел, полученных обратным алгоритмом, а потом удостовериться, что хоть одно из них простое. Я реализовал это следующим способом:
1) Вводится число, с которого начинается поиск ключа (обязательно должно делиться на 9829)
2) Вводится кол-во раз, сколько мы к нему будет плюсовать 9829
3) Выполняется обратный алгоритм
4) И проверяется, простое число получилось или нет
В конце приложен сорец на С++

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

Послесловие
После геморрических усилий мы разобрали алгоритм, сделали ключеделалку и попутно вспомнили математику :) . Кстати, можно было обойтись и патчем. Решение, что именно патчить оставляется Вам как творческое задание для самостоятельной работы :) На счёт генератора - необязательно создавать массив (тем более толку от него в этом коде нулл), можно просто в цикле увеличивать номер на константу и сразу его проверять. Но тут вдруг получится, что валидных ключей в ближайшем диапозоне (то есть до предела типа int :) нету, и получится лёгкое подтормаживание (а может и не лёгкое :) , это если nSize убрать нах), плюс я вообще-то планировал выводить ВСЕ найденные ключи в этом диапозоне, но передумал (уж очень непостояяный я человек - мыслей слишком много :).
Короче, читайте, изучайте, ломайте (есесно всё это только в образовательных целях) . Спасибо за внимание и до встречи.
Все вопросы, замечания и пожелания в комменты. Если что-то очень важное приспичит - мыло тоже присутствует.

АвторRa$cal
Mail: Rascalspb [dog] mail [dot] ru
Дата14.07.2005

-------->Cut<--------

#include <iostream>
#include <math.h>
using namespace std;

// Переменные
const long Type = 9829;
long nStart = 0;
long nSize = 0;
long* nArray = NULL;

// Функции
void CreateArray ( );
long TestArray ( );
bool IsSimple ( long _Number );
void Exit ( );

void main ()
{
char Answer;
do
{
cout << "1.Generate key\n";
cout << "0.Exit\n";
cin >> Answer;

switch (Answer)
{
case ’1’: CreateArray ( ); break;
case ’0’: Exit ( ); break;
}


} while(Answer !=’0’);

}

void CreateArray ()
{
if(nArray!=NULL)
delete [] nArray;

cout << "\nCDCheck keygen\nEnter start number ->";
cin >> nStart;
cout << "\nEnter upper limit ->";
cin >> nSize;

nArray = new long [nSize];

for(long i=0; i < nSize; i++)
nArray[i]= nStart + Type * i + 1;

long Key = TestArray ();

if(Key < 0)
cout << "\nKey not found :( Try increase search range\n";
else
cout << "\nCongratulations! License number is " << Key << endl;

}

long TestArray ()
{
for(long i = 0; i<nSize;i++)
if(IsSimple(nArray[i]))
return nArray[i];
return -1;
}

bool IsSimple (long _Number)
{
long SQRT_NUMBER = sqrt ((long double)_Number);
for (long i = 2; i < SQRT_NUMBER; i++)
if(!(_Number%i))
return false;
return true;
}

void Exit ()
{
if(nArray!=NULL)
delete [] nArray;
exit(0);

}

-------->Cut<--------



Обсуждение статьи: Исследование CDCheck 3.1.5.0 и написание кейгена >>>


Комментарии к статье: Исследование CDCheck 3.1.5.0 и написание кейгена

Devastator 15.07.2005 13:32:35
0049068E |. E8 598C0B00 |CALL unpacked.005492EC ; \\Извлечь корень помещённого числа
00490693 |. 83C4 08 |ADD ESP,8
00490696 |. E8 05780B00 |CALL unpacked.00547EA0 ; Корень в EAX
То что функция извлекает корень - это откуда такое утверждение? Обосновать надо было. Я кстати реверсировал когда-то давно алгоритм генерации ключей, основанный на fpu. Там была огромная процедура и трудность была в том, чтобы следить за стеком fpu, помимо самих вычислений. Хотя алгоритм был обратим.
А статья довольно ничего. Вроде всё понятно.

---
Ra$cal 16.07.2005 02:14:12
Обосновать? Зайди в этот метод и посмотри вниз
00549315 |. 68 B8465A00 PUSH unpacked.005A46B8 ; |Arg2 = 005A46B8 ASCII \"sqrt\"

А теперь 2 попытки - что такое sqrt ;)
Просто подробнее разжёвывать - это уже перегиб. Задача не рассказать, где нажать, а объяснить, почему. Вот...
---
Ra$cal 16.07.2005 02:16:00
Кстати, парсер этого сайта воспринял в цикле оператор индексирования ([i]) как указание, что писать надо курсивом :) Примите к сведению
---
Mario555 16.07.2005 12:30:15
хорошая статья, молодец.
---
Ra$cal 16.07.2005 16:42:30
Mario555>> thank\’s :)
---
Devastator 19.07.2005 11:29:38
Задача не рассказать, где нажать, а объяснить, почему. Вот...(ц)
Золотые слова...


---
Alexey 20.07.2005 00:54:48
Прятная статья, спасибо:)
---
ValdiS 21.07.2005 17:31:48
Интересная статья. Есть прикольные моменты. Спасибо
---
gloom_undead 23.09.2009 21:26:21
"Алгоритм реализован как-то очень корявенько – зачем же каждый раз извлекать корень из номера (ну не удержался я от критики :)."

Потому что наименьший простой делитель числа n не превышает sqrt(n). Для проверки простоты числа достаточно проверить его делимость на 2 и все нечетные не превосходящие sqrt(n).
---

Материалы находятся на сайте https://exelab.ru



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS