Исследование алгоритма
защиты программы Gif2swf v2.1.
Gif2Swf v2.5
[458 KB-ZIP] http://biocyborg.narod.ru/soft/Gif2Swf25.zip OllyDbg
v1.10
[1087
KB-ZIP] http://biocyborg.narod.ru/soft/OllyDbg110.zip Версия
статьи для печати
[44 KB-ZIP] http://biocyborg.narod.ru/rev/srcGif2Swf.zip
С распространением
интернета все больше программ для
своей регистрации требуют выхода в
Сеть и, порой, даже не
предусматривают альтернативного
способа регистрации. Т.е. чтобы
зарегистрировать программу,
необходимо подключиться к сайту
разработчика, заполнить специальные
формы регистрации, после чего, в
случае ввода корректной информации, в системе
создастся запись о том, что программа
зарегистрирована. Примером такой
программы может служить Gif2swf-утилита для преобразования
gif-анимации в формат swf (сайт
разработчика - http://www.gfx2swf.com).
Как вы уже, наверное,
догадались, эта программа не
бесплатная и в демонстрационной
версии существует ряд ограничений. В
меню "Help" имеется пункт "Registration",
при выборе которого появляется окно
с предложением перейти на сайт
разработчика для регистрации. И хотя
с помощью редактора ресурсов в самой
программе можно найти диалоговые
окна ввода регистрационных данных,
вызвать эти окна без подключения к
сайту стандартными способами нельзя.
Но мы подключаться к
вражескому сайту не собираемся, ведь
так?:). Тогда поищем обходные пути. С
помощью утилиты RegMon мне удалось
выяснить, что при запуске программа
ищет в реестре в разделе
HKEY_LOCAL_MACHINE\SOFTWARE\GIF2SWF ключи RegisteredUserName и
RegisteredUserKey. Но этих ключей там нет,
поэтому мы их создадим сами. Идем в
реестр в указанный раздел и создаём
два строковых параметра:
RegisteredUserName и RegisteredUserKey. Почему именно
строковых? Ну, имя юзера обычно
записывается именно в строковый
параметр, а тип параметра для рег.
кода пришлось выбрать наугад (если
будут возникать ошибки, мы попробуем
выбрать другой тип для этого
параметра). Параметру RegisteredUserName присвоим
значение "BioCyborG", а параметру
RegisteredUserKey - "123456789".
Теперь нам надо
подумать над тем, как поймать
программу при считывании этой
информации из реестра. Поступим
следующим оразом. Откроем gif2swf.exe в
отладчике OllyDbg. После того, как
отладчик покажет нам ассемблерный
листинг программы, щелкнем на нем
правой кнопкой мыши и в контекстном
меню выберем "Search for->All referenced text
strings".

Так-так... Здесь мы видим, что
программа обращается к интересующим
нас ключам реестра в трех местах (адреса
403242, 4037С1, 403DFB). Перейдем по каждой из
этих сылок (двойной щелочек левой
кнопкой по выделенной ссылке) и
установим точки останова на каждый
из этих адресов (двойной щелчек по
опкоду команды напротив выбранного
адреса). Запускаем программу (F9) и
прерываемся здесь:
00403DFB PUSH gif2swf.0042A5C0 ; ASCII "RegisteredUserName"
00403E00 >MOV DWORD PTR DS:[434B1C],0
00403E0A >MOV DWORD PTR DS:[434B18],0
00403E14 CALL gif2swf.004038C0
00403E19 PUSH gif2swf.0042C090
00403E1E PUSH gif2swf.0042A5F8 ; ASCII "RegisteredUserKey"
00403E23 CALL gif2swf.004038C0
00403E28 ADD ESP,0C
00403E2B MOV ECX,ESP
00403E2D MOV DWORD PTR SS:[ESP+14],ESP
00403E31 PUSH gif2swf.0042C090
00403E36 CALL gif2swf.0041D214
00403E3B PUSH ECX
00403E3C MOV DWORD PTR SS:[ESP+230],0
00403E47 MOV ECX,ESP
00403E49 MOV DWORD PTR SS:[ESP+20],ESP
00403E4D PUSH gif2swf.0042C094
00403E52 CALL gif2swf.0041D214
00403E57 >MOV DWORD PTR SS:[ESP+230],-1
00403E62 CALL gif2swf.004035E0 ;возвращает в ЕАХ единицу, если код неверный
00403E67 ADD ESP,8
00403E6A XOR ECX,ECX
00403E6C TEST EAX,EAX ;собственно, проверка и...
00403E6E SETE CL
00403E71 MOV DWORD PTR DS:[434B1C],ECX
00403E77 MOV EAX,DWORD PTR SS:[EBP+70]
00403E7A CMP BYTE PTR DS:[EAX],0 ;...вынесение приговора ;)
00403E7D JNZ SHORT gif2swf.00403EE7
00403E7F MOV EDX,DWORD PTR DS:[42A1C8] ; gif2swf.0042A1CC
Все видно из листинга.
Нужно исследовать процедуру,
вызываемую по адресу 403E62:
004035E0 PUSH -1
004035E2 PUSH gif2swf.00421B50 ; SE handler installation
004035E7 MOV EAX,DWORD PTR FS:[0]
004035ED PUSH EAX
004035EE >MOV DWORD PTR FS:[0],ESP
004035F5 SUB ESP,68
004035F8 PUSH EBX
004035F9 PUSH ESI
004035FA PUSH EDI
004035FB >LEA ECX,DWORD PTR SS:[ESP+84]
00403602 >MOV DWORD PTR SS:[ESP+7C],1
0040360A CALL gif2swf.0041D8E2 ;строчные символы имени преобразуем в прописные
0040360F >LEA ECX,DWORD PTR SS:[ESP+84]
00403616 CALL gif2swf.0041D8BB
0040361B >LEA ECX,DWORD PTR SS:[ESP+88]
00403622 MOV ESI,EAX
00403624 CALL gif2swf.0041D8BB ;достаем из памяти введенный нами код
00403629 MOV EBX,EAX
0040362B MOV EDI,ESI
0040362D OR ECX,FFFFFFFF
00403630 XOR EAX,EAX
;;;;;;;;;;;;;;;;;вырезано 16 строк неинтересного кода;;;;;;;;;;;;;;;;;
0040365B NOT ECX
0040365D DEC ECX
0040365E CMP ECX,20 ;сравниваем длину имени с числом 3210
00403661 JLE SHORT gif2swf.00403668 ;если длина имени больше 32 символов
00403663 MOV ECX,20 ;то будем обрабатывать только 32
00403668 XOR ESI,ESI ;ESI будет счетчиком обработанных символов
0040366A TEST ECX,ECX
0040366C JLE SHORT gif2swf.00403696
0040366E >/MOV EAX,DWORD PTR SS:[ESP+88];в ЕАХ помещает имя юзера
00403675 |MOV BL,BYTE PTR DS:[ESI+EAX] ;берем символ из имени юзера
00403678 |MOV EAX,163EF7 ;в ЕАХ помещаем константу 163EF7h
0040367D |MOVSX EDI,BL ;пересылаем код символа имени в EDI
00403680 |CDQ ;копирование значения старшего бита EAX на все биты EDX
00403681 |IDIV EDI ;делим EAX на EDI, целую часть от деления в EAX
00403683 |MOVSX EDX,BL ;пересылаем код символа имени в EDX
00403686 |ADD EAX,ESI ;складываем результат деления с номером
00403688 |IMUL EAX,EDX ;умножаем получ. сумму на код символа имени
0040368B |ADD EBP,EAX ;и прибавляем к EBP; ЕВР-это будет верный код
0040368D |INC ESI ;увеличиваем ESI на 1
0040368E |CMP ESI,ECX ;если не все символы обработаны, то берем следующий
00403690 \JL SHORT gif2swf.0040366E
00403692 MOV EBX,DWORD PTR SS:[ESP+10] ;помещаем в ЕВХ наш код
00403696 ADD EBP,85D9ED ;к ЕВР прибавляем 85D9EDh и получаем верный код
0040369C LEA EAX,DWORD PTR SS:[ESP+14]
004036A0 PUSH EBP ;сохраняем верный код в стек
004036A1 PUSH gif2swf.0042A5F4
;;;;;;;;;;далее идет хитробайтое :) сравнение нашего кода с верным кодом и если
;;;;;;;;;;они не равны, то в ЕАХ помещается 1, и по этому результату программа
;;;;;;;;;;определяет, зарегистрирована она или нет
Итак, подведем итоги: мы
правильно угадали тип параметра
RegisteredUserKey. Программа генерирует
правильный регистрационный номер на
основе введенного имени
пользователя и сравнивает его с тем,
что записан в реестре. Правильный
номер генерируется по следующему
алгоритму: берем ASCII-код символа из
UserName, делим на него число 163EF7h. К целой
части от деления прибавляем
порядковый номер символа в имени
юзера (считаем номера символов начиная
с 0). Затем
результат сложения умножаем на ASCII-код
символа и прибавляем к числу X,
которое вначале равно 0. Так
обрабатываем все символы имени
пользователя, после чего к X
прибавляем число 85D9EDh и получаем
правильный регистрационный код. Да,
чуть не забыл: если длина имени
пользователя больше 32 символов, то в
процедуре генерации правильного
кода используются только первые 32.
А теперь приведу код
процедуры генерации правильного
регистрационного кода. Нам нужно
учесть тот факт, что пользователь не
сможет обычным способом ввести свое имя и код, поэтому наш кейген
будет автоматически добавлять эту
информацию в реестр. Для написания
примера я взял Delphi+KOL, но
использовал ассемблерные вставки,
чтобы не заморачиваться с обработкой символов
национальных алфавитов (да и вообще
так прикольнее смотрится :).
На форме поместим 2 EditBox’a: первый для ввода имени пользователя, второй – для отображения сгенерированного регистрационного кода (он юзеру не нужен, ведь мы сразу пишем рег. инфу в реестр, но все равно код покажем для контроля ;).
В опциях EditBox1 обязательно установите
eoUpperCase:=true, чтобы введенные символы
имени автоматически
преобразовывались в верхний регистр.
Конечно, можно было бы использовать
команду upcase, но она работает только с
ASCII-символами a..z и не поддерживает
символы национальных алфавитов. Еще на форме нужно поместить кнопку, при нажатии которой будет генерироваться рег. код и полученная информация будет записываться в реестр операционной системы.
procedure TForm1.Button1Click(Sender: PObj);
var
ddd:integer; //ddd - переменная для промежуточного результата
i,y: integer; //i-номер симвора имени,y-слагаемое (y=i-1)
SerNum:integer; //В этой переменной окажется правильный номер
bukva:char; //bukva - переменная для символов из UserName
UserInfo:HKey; //UserInfo - переменная для работы с реестром
begin
if Editbox1.Text='' then ShowMessage('Input User Name!') else
begin
i:=1; //i - номер символа из имени юзера
y:=0; //y - слагаемое, y=i-1
SerNum:=$85D9ED; //значек "$" показывает, что число в шестнадцатеричном формате
while (i<=Length(Editbox1.Text)) and (y<32) do
begin
bukva:=editbox1.text[i]; //считываем очередной символ
{не забудьте установить в опциях EditBox1 eoUpperCase:=true}
asm
pushad
mov eax,163EF7h
mov bl, bukva
movsx edi,bl
idiv edi
add eax,y
cdq
movsx edx,bl
imul eax,edx
mov ddd,eax
inc y
inc i
popad
end;
SerNum:=SerNum+ddd;
end;
{теперь записываем в реестр полученную инфу}
UserInfo:=RegKeyOpenWrite(HKEY_LOCAL_MACHINE, 'SOFTWARE\GIF2SWF');
RegKeySetStr(UserInfo, 'RegisteredUserName', editbox1.text);
RegKeySetStr(UserInfo, 'RegisteredUserKey', int2str(SerNum));
RegKeyClose(UserInfo);
editbox2.Text:=int2str(SerNum); //выводим результат
label1.Text:='Cracked ;)';
end;
end;
Исходники можно взять здесь.
Информация
приведена в образовательных целях. Повторение
описанных в статье действий
запрещается.
© BioCyborG, 2005
|