Делаем кейген на примере
Из предыдущей статьи у нас уже есть шестнадцатеричный редактор и дизассемблер.
К этому прибавиться еще один инструмент - отладчик.
Качаем OllyDebuger
Расспаковываем в папку и запускаем OLLYDBG.EXE
File - Open - выбираем SMS_BOMBER.EXE
Итак из предыдущей статьи знаем то место, где нужно было править:
:004BE08F E82CABF4FF call 00408BC0
:004BE094 3BD8 cmp ebx, eax
:004BE096 754C jne 004BE0E4 - вот он тот переход, что выполняется если ключ неправильный.
Здесь столбцы по аналогии с WinDasm.
Первый это адресс команды.
Второй ее шестнадцатеричное представление.
Третий ее асемблерное представление.
Четвертый - дополнительная, часто очень важная информация.
Жмем Ctrl+G и вводим 004BE096.
Итак мы находимся в процедуре. Почему? Посмотрите левее в окно отладчика. Сразу же после адресов команд идет скобка. Вот эта скобка и охватывает всю процедуру. Итак продвигаемся вверх туда где она начинаеться
(пользоваться я буду листингом с WinDasm'a). Вам рекоммендую паралельно смотреть и в него):
:004BDF48 55 push ebp
:004BDF49 8BEC mov ebp, esp
:004BDF4B 33C9 xor ecx, ecx
:004BDF4D 51 push ecx
:004BDF4E 51 push ecx
:004BDF4F 51 push ecx
:004BDF50 51 push ecx
:004BDF51 51 push ecx
:004BDF52 51 push ecx
:004BDF53 51 push ecx
:004BDF54 53 push ebx
:004BDF55 56 push esi
и далее...
Делаем предположение что именно здесь и будет содержаться генератор серийного номера и процедура проверки
(ну уж процеда проверки точно, это мы выяснили в предыдущей статье).
Обычно сразу же (или через несколько шагов) после процедуры генерации ключа идет процедура проверки введенного ключа с сгенерированным. Обычно сразу же после проверки идет прыжок в зависимости от результатов этой проверки. И даже обычно сразу после прыжка (или того места куда прыгнули) идет ругательство что ключ неверный. Короче обычно так и есть и этим мы будем пользоваться.
Ставим брикпоинт на начало процедуры, т.е. на адресс 004BDF48 (жмем F2 на нем).
Запускаем программу по F9 и появляеться окно ввода ключа.
Вводим что-то чтобы запомнилось (я вводил 12345).
Так же рекоммендую запомнить или записать ID что нам выдало в окне, т.к. именно этот айди и введенный нами номер играют ключевую роль! (а кому ведь еще ее играть )) )
Жмем ОК.
Стали в начале процедуры!
Теперь будем идти по F8 и смотреть на окно регистров.
А так же на небольшое окошечко "комментария" чуть ниже окна дизассемлерного листинга (дизасма) нашей программы в OllyDbg.
Идем и идем а ничего интересного. Все какие-то числа мелькают в комментарии.
Когда стоп!
:004BDF6F E8901BF9FF call 0044FB04
:004BDF74 8B45F8 mov eax, dword ptr [ebp-08]
После этой процедуры когда мы стали на адресе 004BDF74 в комментарии было такое:
Stack SS:[0012Fc4C]=00A7D60C, (ASCII "12345")
Это значит, что по этому адресу лежит строка "12345". А это ведь введенная нами строка в окошке.
Что ж становиться интереснее. Нажмеме F8 раз и как и ожидалось от команды mov eax, dword ptr [ebp-08] "наша строка зашла" в регистр eax. Ну лежит там и хорошо. Будем за ней наблюдать.
(К стати о том что значение в регистре только что изменилось можно судить по тому что оно стало красным).
Итак стоим на команде:
:004BDF77 E84C64F4FF call 004043C8
Тут вызываеться какая-то процедура.
После нее:
:004BDF7C 83F805 cmp eax, 00000005
:004BDF7F 7D1D jge 004BDF9E
:004BDF81 6A00 push 00000000
:004BDF83 B92CE14B00 mov ecx, 004BE12C
* Possible StringData Ref from Code Obj ->"Ви ввели не вірний ключ"
|
:004BDF88 BA34E14B00 mov edx, 004BE134
:004BDF8D A1C85E4C00 mov eax, dword ptr [004C5EC8]
:004BDF92 8B00 mov eax, dword ptr [eax]
:004BDF94 E88B17FBFF call 0046F724
:004BDF99 E95E010000 jmp 004BE0FC
Итак видим, что после процедуры то, что будет в eax сравниваеться с 5 (cmp eax, 00000005)
Если оно больше или равно, то идет прыжок на 004BDF9E (jge 004BDF9E). Если мы так и не прыгнули, то через пару команд вступает в игру строка "Ви ввели не вірний ключ" ("Вы ввели не верный ключ"), видим процедуру которая наверное что-то с этой строкой будет делать и идет прыжок в конец нашей процедуры (той где мы сейчас ходим).
Почему конец? Да потому что вот оно по тому адресу куда ведет (jmp 004BE0FC):
:004BE0FC 33C0 xor eax, eax
:004BE0FE 5A pop edx
:004BE0FF 59 pop ecx
:004BE100 59 pop ecx
:004BE101 648910 mov dword ptr fs:[eax], edx
:004BE104 6826E14B00 push 004BE126
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004BE124(U)
|
:004BE109 8D45E4 lea eax, dword ptr [ebp-1C]
:004BE10C BA06000000 mov edx, 00000006
:004BE111 E81660F4FF call 0040412C
:004BE116 8D45FC lea eax, dword ptr [ebp-04]
:004BE119 E8EA5FF4FF call 00404108
:004BE11E C3 ret
ret значит конец процедуры, возврат.
Все на адресс 004BE096 где мы патчили проверку на верный ключ мы не попадем. Это означает что программе все и так с нами ясно и она не будет туда идти. Это плохо, очень плохо. Не верите? А давайте посмотрим.
Заменим переход по адресу 004BDF7F - jge 004BDF9E на nop (т.е. нет операции, пустая команда).
Нажимаем пробел находясь на этом адресе, стираем и пишем nop (обязательно отмечаем птичку Fill with NOP's)
все идем по F8 до нашего места, проходим его... доходим до:
* Possible StringData Ref from Code Obj ->"Ви ввели не вірний ключ"
|
:004BDF88 BA34E14B00 mov edx, 004BE134
Далее процедура и прыжок в конец.
Проходим процедуру по F8 и... что куда-то пропало что-то? Нет, просто мы эту процедуру не прошли еще.
Не прошли потому что она вызвала окно, которое ругаеться на нас: "Ви ввели не вірний ключ" орет. Жмем Ок и мы стоим на прыжке.
Ну далее нечего интересного. Главное мы узнали. Узнали что по адресу 004BDF7F нам желательно чтобы прыжок выполнялся. Но результат ведь зависит от того что будет после выполнения по адресу 004BDF77, а именно после вызова процедуры с адресса 004043C8 (CALL 004043C8). Давайте зайдем в нее (идем просто по F7 пока не пройдем ее) и посмотрим что там:
:004043C8 85C0 test eax, eax
:004043CA 7403 je 004043CF
:004043CC 8B40FC mov eax, dword ptr [eax-04] здесь у меня 5
:004043CF C3 ret
Как видим сравниваеться то что в регистре eax c нульом. Если это так, то процедура заканчиваеться (прыжок на ret и мы выходим из нее). Если же это НЕ так, то в eax помещаеться в моем случае 5. Это как вы догадались длина введенной строки. Попробуйте ее удлиннить и увидите что число пропорционально увеличиться.
И так после процедуры то, что в eax сравниваеться с 5 и если больше или равно то все ок. Иначе на нас опять орать будут. Стает понятно, что делает иследуемый нами кусок кода. А именно он сравнивает не пустая ли строка и сколько в ней символов. Если она пустая или символов меньше 5, то кричит, что код неправильный (и даже проверять его не будет).
ОК. Нам извесна минимальная длина кода который можно вводить. Если посмотрите на предыдущую статью, то увидите, что эту проверку мы там рубили (во всяком случае должны были). Идем далее.
Прыгнули стоим в начале этого:
:004BDF9E 8D55F4 lea edx, dword ptr [ebp-0C]
:004BDFA1 8B86FC020000 mov eax, dword ptr [esi+000002FC]
:004BDFA7 E8581BF9FF call 0044FB04
:004BDFAC 837DF400 cmp dword ptr [ebp-0C], 00000000 - проверяеться "12345"
:004BDFB0 751D jne 004BDFCF
:004BDFB2 6A00 push 00000000
:004BDFB4 B92CE14B00 mov ecx, 004BE12C
* Possible StringData Ref from Code Obj ->"Ви ввели не вірний ключ"
|
:004BDFB9 BA34E14B00 mov edx, 004BE134
:004BDFBE A1C85E4C00 mov eax, dword ptr [004C5EC8]
:004BDFC3 8B00 mov eax, dword ptr [eax]
:004BDFC5 E85A17FBFF call 0046F724
:004BDFCA E92D010000 jmp 004BE0FC
Итак по аналогии с предыдущим случаем видим, что здесь тоже идет сравнение.
Это еще одна проверка введенного нами кода. Пытаемся пройти ее, чтобы все было нормально.
Если этого не произойдет, то можно поставить брикпоинт на 004BDFB0, перезапустить программу по Ctrl+F2
и запустить по F9, введя тот же "12345", а когда остановимся, пропатчить jne 004BDFCF на jmp 004BDFCF
(просто жмем пробел и пишем что надо). Но у меня все нормально, я прыгнул и ничего не пришлось патчить.
Идем далее... (по F8)
Стоп. Интересное место. Очень даже:
:004BDFDC E8231BF9FF call 0044FB04
:004BDFE1 8B45F0 mov eax, dword ptr [ebp-10]
Здесь оля говорит, что в eax кладеться наш ID! (я просил его записать).
Вот здесь и начинаеться все самое интересное, а именно операции с ним.
Итак первый вызов процедуры после этого (call 00404628), вот она (заходим в нее по F7):
:00404628 53 push ebx
:00404629 85C0 test eax, eax
:0040462B 742D je 0040465A
:0040462D 8B58FC mov ebx, dword ptr [eax-04]
:00404630 85DB test ebx, ebx
:00404632 7426 je 0040465A
:00404634 4A dec edx
:00404635 7C1B jl 00404652
:00404637 39DA cmp edx, ebx
:00404639 7D1F jge 0040465A
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00404654(U)
|
:0040463B 29D3 sub ebx, edx
:0040463D 85C9 test ecx, ecx
:0040463F 7C19 jl 0040465A
:00404641 39D9 cmp ecx, ebx
:00404643 7F11 jg 00404656
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00404658(U)
|
:00404645 01C2 add edx, eax
:00404647 8B442408 mov eax, dword ptr [esp+08]
:0040464B E8A8FBFFFF call 004041F8
:00404650 EB11 jmp 00404663
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00404635(C)
|
:00404652 31D2 xor edx, edx
:00404654 EBE5 jmp 0040463B
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00404643(C)
|
:00404656 89D9 mov ecx, ebx
:00404658 EBEB jmp 00404645
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0040462B(C), :00404632(C), :00404639(C), :0040463F(C)
|
:0040465A 8B442408 mov eax, dword ptr [esp+08]
:0040465E E8A5FAFFFF call 00404108
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00404650(U)
|
:00404663 5B pop ebx
:00404664 C20400 ret 0004
Идем теперь тут и видим, что вобщем то вроде то, что ожидалось.
А именно проверки строки ID не пустая она и т.п.
Ну это нам не важно. Вышли с процедуры.
:004BDFF3 8B45FC mov eax, dword ptr [ebp-04]
Здесь в eax ложиться тот же ID, но уже обрезанный (без последнего символа). К стати об этом можно было сделать вывод и на основе анализа вложенных процедур, но я предпочел способ попроще. Тем более что защита в этой программе очень простая.
Итак вот он и начался, алгоритм генерации кода!
Дело в том что в нашей главной процедуре я уже не первый раз прохожу, и знаю, что по адресу 004BE094 сравниваеться введенный нами код с сгенерированным. А сгенерированный делаеться на основе ID (ну это и без дизасма понятно вобщем то). И посколько ID уже начали перемалывать, то стало быть это и началась генерация.
Следующая процедура, которая ест обрезааный ID это 00408BC0.
Давайте здесь аналогично попробуем догадаться чем будет то что получится после нее (не заходим в нее):
:004BDFFB 8BD8 mov ebx, eax
:004BDFFD 81FB10270000 cmp ebx, 00002710
:004BE003 7C1D jl 004BE022
Итак видим что после проходе через процедуру изменились регистры ecx, edx, eax. В частности в ecx все тот же обрезанный ID. В остальный двух какие-то числа. Давайте возьмем калькулятор и переведем эти значения в 10-ную систему с 16-ной. Из того, что в edx Вы скорее всего ничего знакомого не узнаете, а вот то, что в eax это наш обрезанный ID!
УРааа!
Значит теперь понятно что сделала процедура: она перевела 10-ное значение обрезанного ID в его 16-ное представление. (Рекоммендую запомнить адрес этой процедуры)
Итак алгоритм генерации кода продолжаеться...
В више обозначенных трех инструкциях делаеться вот что: 16-ный айди перебрасываеться в ebx и сравниваеться с 2710. В нашем случае он больше этого числа и чесно говоря я не видел на нескольких компьютерах чтобы это было не так. Так что вариант когда он меньше его мы просто напросто отбросим.
Идем далее по F8.
Проходим это и видим:
:004BE013 8B45EC mov eax, dword ptr [ebp-14]
:004BE016 E8A5ABF4FF call 00408BC0
:004BE01B 3D10270000 cmp eax, 00002710
:004BE020 7D1D jge 004BE03F
Что здесь проверяеться "12345" переработанный все той же процедурой которую мы проигноривали пройтись по ней. А значить здесь тоже сравниваеться шестнадцатеричное представление "12345" с 2710. Если оно больше то все хорошо. Идем далее, т.к. в этой еще одной проверке введенного кода ничего интересного не вижу.
Прыгнули сюда:
:004BE03F 8BC3 mov eax, ebx
Здесь видим, что в первый регистр кладеться со второго 16-ное представление обрезанного айди.
ОК, далее MOV ECX,6
Еще одна инструкция. Смотрим далее:
:004BE046 99 cdq
:004BE047 F7F9 idiv ecx
:004BE049 8BD8 mov ebx, eax
:004BE04B 8BC3 mov eax, ebx
:004BE04D 03C0 add eax, eax
:004BE04F 8BD8 mov ebx, eax
:004BE051 8BC3 mov eax, ebx
:004BE053 B903000000 mov ecx, 00000003
:004BE058 99 cdq
:004BE059 F7F9 idiv ecx
:004BE05B 8BD8 mov ebx, eax
:004BE05D 8D049B lea eax, dword ptr [ebx+4*ebx]
:004BE060 8BD8 mov ebx, eax
:004BE062 D1FB sar ebx, 1
Здесь преобразовываеться далее 16-ное представление обрезанного айди (далее я просто буду звать его нашим айди). К примеру это add eax, eaxозначает, такое x=x+x или x=x*2
Значит вот наш код генерации номера вплоть до проверки с введенным:
:004BE03F 8BC3 mov eax, ebx
:004BE041 B906000000 mov ecx, 00000006
:004BE046 99 cdq
:004BE047 F7F9 idiv ecx
:004BE049 8BD8 mov ebx, eax
:004BE04B 8BC3 mov eax, ebx
:004BE04D 03C0 add eax, eax
:004BE04F 8BD8 mov ebx, eax
:004BE051 8BC3 mov eax, ebx
:004BE053 B903000000 mov ecx, 00000003
:004BE058 99 cdq
:004BE059 F7F9 idiv ecx
:004BE05B 8BD8 mov ebx, eax
:004BE05D 8D049B lea eax, dword ptr [ebx+4*ebx]
:004BE060 8BD8 mov ebx, eax
:004BE062 D1FB sar ebx, 1
:004BE064 7903 jns 004BE069
:004BE066 83D300 adc ebx, 00000000
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004BE064(C)
|
:004BE069 8BC3 mov eax, ebx
:004BE06B C1E003 shl eax, 03
:004BE06E 2BC3 sub eax, ebx
:004BE070 8BD8 mov ebx, eax
:004BE072 8BC3 mov eax, ebx
:004BE074 B905000000 mov ecx, 00000005
:004BE079 99 cdq
:004BE07A F7F9 idiv ecx
:004BE07C 8BD8 mov ebx, eax
:004BE07E 8D55E8 lea edx, dword ptr [ebp-18]
:004BE081 8B86FC020000 mov eax, dword ptr [esi+000002FC]
:004BE087 E8781AF9FF call 0044FB04
:004BE08C 8B45E8 mov eax, dword ptr [ebp-18]
:004BE08F E82CABF4FF call 00408BC0
:004BE094 3BD8 cmp ebx, eax
:004BE096 754C jne 004BE0E4
Если еще присмотреться, то видно, что он заканчиваеться на:
:004BE07C 8BD8 mov ebx, eax
Так как ключевым звеном являеться проверка:
:004BE094 3BD8 cmp ebx, eax
А поскольку межу ними ebx не меняеться (можно пройти по F8 и убедиться)
Важно только:
:004BE08C 8B45E8 mov eax, dword ptr [ebp-18]
:004BE08F E82CABF4FF call 00408BC0
Но не для генерации кода а потому, что 00408BC0 нам уже знакома. Она переводила айди в шестнадцатеричное представление. А здесь переводит "12345". Итак в ключевом сравнении сравниваеться переведенный в шестнадцатеричное представление введенный нами код с обрезанным без последнего символа ID, переведенным в шестнадцатеричный формат тоже.
Теперь когда мы локализовали код генерации можно приступать к написанию кейгена!
Можно проанализировать ассемблерные инструкции и вывести форумулу генерации, но я предлагаю сделать проще.
А именно просто вырезать эти инструкции и вставить в наш кейген в таком виде как есть.
Это буде и проще и и меньше времени займет.
Писать будем с помощью Dev C++
Качаем с wwwbloodshed.net, ставим.
File - New - Project - Console Application
Текст будет в целом такой:
#include <cstdlib>
#include <iostream>
#include <windows.h>
using namespace std;
DWORD vIn=1;
DWORD vOut=1;
int main(int argc, char *argv[])
{
// Выводим строку информации
cout<<"SMS_BOMBER v.069 Keygen by BRe@K\r\n\r\nEnter ID: ";
// Запрашиваем ввод ID
cin>>vIn;
// Делим полученной число на 10 обрезая тем самым последнюю цыфру
vIn=vIn/10;
// Ассемблерная вставка с программы:
__asm(
".intel_syntax noprefix\n"
"MOV EBX,[_vIn]\n"
"MOV EAX,EBX\n"
"MOV ECX,6\n"
"CDQ\n"
"IDIV ECX\n"
"MOV EBX,EAX\n"
"MOV EAX,EBX\n"
"ADD EAX,EAX\n"
"MOV EBX,EAX\n"
"MOV EAX,EBX\n"
"MOV ECX,3\n"
"CDQ\n"
"IDIV ECX\n"
"MOV EBX,EAX\n"
// А вот таким образом делаем аналог инструкции LEA EAX,DWORD PTR DS:[EBX+EBX*4]
//---start--- LEA EAX,DWORD PTR DS:[EBX+EBX*4]
"ADD EBX, EBX\n"
"ADD EBX, EBX\n"
"ADD EBX, EAX\n"
//---end--- LEA EAX,DWORD PTR DS:[EBX+EBX*4]
"SAR EBX,1\n"
"JNS c1\n"
"ADC EBX,0\n"
"c1:\n"
"MOV EAX,EBX\n"
"SHL EAX,3\n"
"SUB EAX,EBX\n"
"MOV EBX,EAX\n"
"MOV EAX,EBX\n"
"MOV ECX,5\n"
"CDQ\n"
"IDIV ECX\n"
"MOV EBX,EAX\n"
"MOV [_vOut], EBX\n"
".att_syntax \n"
);
// Выводим код для регистрации программы
cout<<"\r\nHere is you code: "<< vOut<<"\r\n\r\n";
// Для продолжения нажмите любую клавишу
system("PAUSE");
return EXIT_SUCCESS;
}
Компилируем по Ctrl+F9 , проверяем работоспособность и правильность генерации кода.
Хочеться заметить что преимущество описанного мной способа никак не безошибочность, а простота.
Мы просто делаем допущение. Если оно оказываеться не верно то только тогда уже копаем глубже и анализируем код детальней. Но это работа намного более кропотливая.
Надеюсь статья пришлась по душе. Принимаються замечания, предложения.
Статья писалась исключительно в ознакомительных целях!
Copyright(с) BRe@K