Сегодня мы займемся разбором алгоритма программы SSMS
Messenger версии 1.0 от 11.01.2004.
Взять ее можно по адресу: http://www.softbox.ru/?a=21&i=3628/.
Программа предназначена для отправки СМС сообщений на мобильные телефоны
через smtp сервер. Имеет достаточно простой интерфейс. Ведётся лог всех исходящих
сообщений. Имеется также возможность массовой рассылки сообщений по списку.
Поддерживает 70 мобильных операторов и пейджинговых компаний стран СНГ,
Канады, США, Чехии, Литвы, Эстонии, Венгрии и Польши.
И конечно же надо оговориться, что программа не создавалась автором специально для
массовых рассылок спама или другого вида сетевой рекламы.
Посмотрим на внешний вид программы. Для этого запустим ее. Сразу после старта
появится сообщение с напоминанием о использовании незарегистрированной версии,
пропустим его, согласившись с высказыванием и перед нами предстанет главная форма
программы. В левом нижнем углу окна, находится кнопка "Регистрация". Нажмем
на нее. Откроется форма регистрации программы, в которой нам предлагается ввести
имя, фамилию, адрес эл. почты и номер счета WebMoney. Ниже идет кнопка "Отправить",
предназначена для отправки, тех самых, вписанных нами данных автору для последующей
генерации ключа, разумеется после оплаты. Ниже идет очередное поле ввода, куда вводится
присланный автором ключ и кнопка "Зарегистрировать", для проверки этого самого ключа.
Все, что хотели узнали теперь можно переходить к непосредственно исследованию.
Инструменты, которыми мы будем пользоваться:
- отладчик SoftIce
- декомпилятор делфи форм DEDE by DaFixer
- hex-редактор HIEW
- идентификатор PAID
Прежде всего проверим программу на наличие упаковщиком/протекторов, с помощью
давно известной программы PEID, откроем ее загрузим туда главнй файл программы
"SSMS.exe", выберем "hardcore scan" в единственном меню программы и произведем
сканирование. Анализатор показал: "Borland Delphi 6.0 - 7.0".
Т.е. программа не запакована и написана на
чрезвычайно распространенном нынче Borland Delphi версии 6.0 - 7.0.
Соответственно для исследования кода программы нам лучше воспользоваться
специальным для этих целей дизассемблером delphi форм - DEDE by
DaFixer. Запустим DEDE откроем в нем файл "SSMS.exe" и нажмем кнопку "process",
после выполнения этого процесса нам предложат провести дополнительный анализ
для нахождения нераспознанных процедур, согласимся с этим и немного подождем.
После того как DEDE завершит анализ, перейдем во вкладку "Procedures", там присутствует
много форм. Так как мы не знаем какая именно форма относится к регистрации,
пройдемся по каждой форме в поисках многочисленных полей ввода.
Находим такую форму, это TFORM2:
Edit1Enter 48707C ; поле ввода имени.
Edit2Enter 4870EC ; поле ввода фамилии.
Edit3Enter 487160 ; поле ввода адреса эл. почты.
Edit4Enter 4871D4 ; поле ввода WebMoney идентификатора.
Button1Click 487248 ; кнопка "Отправить", заполненные в полях данные.
Button2Click 487644 ; проверка регистрационного ключа.
Edit5Enter 487980 ; поле ввода регистрационного ключа.
Button3Click 487990 ; выход из регистрационного окна
_PROC_00487999 487999 ;
_PROC_004879D4 4879D4 ;
Нам нужна именно процедура проверки регистрационного ключа, поэтому заходим
в событие "Button3Click", два раза жмем по нему левой кнопкой мыши. Открывается
ассемблерный листинг, смотрим на начало процедуры
* Possible String Reference to:
|
0048765C 68DC784800 push $004878DC
***** TRY
|
00487661 64FF30 push dword ptr fs:[eax]
00487664 648920 mov fs:[eax], esp
00487667 33DB xor ebx, ebx
00487669 8D45F8 lea eax, [ebp-$08]
* Reference to: System.@LStrClr(void;void);
|
0048766C E8CFD0F7FF call 00404740
00487671 8D55F0 lea edx, [ebp-$10]
* Reference to control TForm2.Edit2 : TEdit
|
00487674 8B86F8020000 mov eax, [esi+$02F8]
* Reference to: Controls.TControl.GetText(TControl):TCaption;
|
0048767A E82D8BFBFF call 004401AC ; читает поле edit2 (как мы помним это поле для фамилии)
0048767F 8B45F0 mov eax, [ebp-$10] ;
* Reference to: System.@LStrLen(String):Integer;
|
00487682 E871D3F7FF call 004049F8 ; получает длину фамилии
00487687 83F804 cmp eax, +$04 ; если длина менее 4 символов, прыжка не происходит, и выводится сообщение, указанное ниже, после чего происходит выход из процедуры.
0048768A 7D0A jnl 00487696
* Possible String Reference to: Все поля должны содержать не менее
| 4х символов
|
0048768C B8F4784800 mov eax, $004878F4
* Reference to: Dialogs.ShowMessage(AnsiString); выводит сообщение
|
00487691 E8EE22FBFF call 00439984
00487696 8D55EC lea edx, [ebp-$14]
* Reference to control TForm2.Edit1 : TEdit
|
00487699 8B86F4020000 mov eax, [esi+$02F4]
* Reference to: Controls.TControl.GetText(TControl):TCaption;
|
0048769F E8088BFBFF call 004401AC ; читает поле edit2 (как мы помним это поле для имени)
004876A4 8B45EC mov eax, [ebp-$14]
* Reference to: System.@LStrLen(String):Integer;
|
004876A7 E84CD3F7FF call 004049F8 ; получает длину имени
004876AC 83F804 cmp eax, +$04 ; если длина менее 4 символов, прыжка не происходит, и выводится сообщение, указанное ниже, после чего происходит выход из процедуры.
004876AF 7D0A jnl 004876BB
* Possible String Reference to: 'Все поля должны содержать не менее
| 4х символов'
Затем таким же образом проверяются остальные 2 поля, после чего программа складывает
содержимое всех полей в одну строку разделяя их знаком ",". Таким
образом вводим следующие данные:
Имя: 1234
Фамилия: 2345
Адрес эл. почты: 6789
WebMoney идентификатор: 9999
Строка будет выглядеть так: 1234,2345,6789,9999
Для удобства назовем ее: "SumStr".
Смотрим дальше по адресу 0048779D:
* Reference to control TForm2.Edit2 : TEdit
|
0048779D 8B86F8020000 mov eax, [esi+$02F8]
* Reference to: Controls.TControl.GetText(TControl):TCaption;
|
004877A3 E8048AFBFF call 004401AC ; читает поле edit2 (как мы помним, это поле для фамилии)
004877A8 8B45C8 mov eax, [ebp-$38]
004877AB 0FB600 movzx eax, byte ptr [eax] ; помещает
в eax, первый байт фамилии
004877AE 6BC00D imul eax, eax, $0D ; умножает этот байт на 0Dh
004877B1 B9FA000000 mov ecx, $000000FA ; помещает в ecx, 0FAh
004877B6 33D2 xor edx, edx ; очищает edx
004877B8 F7F1 div ecx ; делит eax на ecx, целые в eax, остаток edx (обозначим его
REMD)
004877BA 8955FC mov [ebp-$04], edx ; помещает остаток деления (REMD) по адресу [ebp-$04]
004877BD 8B45F8 mov eax, [ebp-$08] ; по адресу [ebp-$08] находится та самая строка SumStr, сформированная из всех полей ввода
* Reference to: System.@LStrLen(String):Integer;
|
004877C0 E833D2F7FF call 004049F8 ; получает ее длину
004877C5 8BF8 mov edi, eax ; помещает длину в edi
004877C7 4F dec edi ; уменьшает edi на 1
004877C8 85FF test edi, edi ;
004877CA 7E31 jle 004877FD ;
004877CC BB01000000 mov ebx, $00000001
Здесь происходит следующее: копируется первый байт фамилии, затем умножается
на 0Dh и делится на 0FAh, остаток от этого деления сохраняется по адресу [ebp-$04],
обозначим его REMD.
Далее идет цикл, подробно его рассмотрим:
004877D1 8B45F8 mov eax, [ebp-$08] ;
по адресу [ebp-$08] находится SumStr, сформированная из всех полей ввода
004877D4 0FB64418FF movzx eax, byte ptr [eax+ebx-$01] ; помещает первый байт этой строки в eax
004877D9 F76DFC imul dword ptr [ebp-$04] ; умножает на содержимое по адресу [ebp-$04], как мы помним там находится REMD
004877DC B9FA000000 mov ecx, $000000FA ; помещает в ecx 0FAh
004877E1 99 cdq ; расширяет двойного слова со знаком до размера учетверенного слова (64 бита) со знаком
004877E2 F7F9 idiv ecx ; делит, целые в eax, остаток в edx
004877E4 8BC2 mov eax, edx ; остаток
деления помещает в eax
004877E6 8D55C4 lea edx, [ebp-$3C]
* Reference to: Unit_00407CD0.Proc_00408FA4
|
004877E9 E8B617F8FF call 00408FA4 ; интересная нам процедуре!
004877EE 8B55C4 mov edx, [ebp-$3C]
004877F1 8D45F4 lea eax, [ebp-$0C]
* Reference to: System.@LStrCat;
|
004877F4 E807D2F7FF call 00404A00
004877F9 43 inc ebx
004877FA 4F dec edi
004877FB 75D4 jnz 004877D1 ; проверяет не закончилась ли строка, если нет - возвращается в цикл и производит те же действия со следующим символом
004877FD B8C82D4900 mov eax, $00492DC8
00487802 8B55F4 mov edx, [ebp-$0C]
* Reference to: System.@LStrAsg(void;void;void;void);
|
00487805 E88ACFF7FF call 00404794
0048780A 8D55C0 lea edx, [ebp-$40]
* Reference to control TForm2.Edit4 : TEdit
|
0048780D 8B8604030000 mov eax, [esi+$0304]
* Reference to: Controls.TControl.GetText(TControl):TCaption;
|
00487813 E89489FBFF call 004401AC
00487818 8B45C0 mov eax, [ebp-$40] ; читает поле edit5 (то
есть регистрационный ключ, который мы должны были ввести)
0048781B 8B15C82D4900 mov edx, [$00492DC8] ; в edx помещается только что сгенерированный
регистрационный ключ из введенных данных
* Reference to: System.@LStrCmp;
|
00487821 E816D3F7FF call 00404B3C ; здесь происходит сравнение этих двух ключей
00487826 7574 jnz 0048789C ; если они не совпадают прыгает на 0048789C и выходит
00487828 BB01000000 mov ebx, $00000001 ; иначе продолжает и записывает ключ в файл ssms.dll, для последующей проверки
0048782D 8D45BC lea eax, [ebp-$44]
00487830 8B15C82D4900 mov edx, [$00492DC8]
00487836 8A541AFF mov dl, byte ptr [edx+ebx-$01]
* Reference to: System.@LStrFromChar(String;String;Char);
|
0048783A E8E1D0F7FF call 00404920
0048783F 8B45BC mov eax, [ebp-$44]
* Reference to: Unit_00407CD0.Proc_00409044
|
00487842 E8FD17F8FF call 00409044
00487847 33C9 xor ecx, ecx
00487849 33D2 xor edx, edx
* Reference to: Unit_00406D24.Proc_004077F8
|
0048784B E8A8FFF7FF call 004077F8
00487850 50 push eax
А происходит в этом цикле следующее: каждый байт строки SumStr умножается на
REMD, затем делится 0FAh, остаток от этого деления, передается процедуре 00408FA4 по адресу
004877E9, которая в свою очередь возвращает цифры регистрационного колюча.
Т.о. наша задача найти как именно процедура преобразует остаток и получает цифры
регистрационного ключа. Глубоко в коде этой процедуры находим:
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:004098FE(C), :00409912(C)
|
:00409922 B90A000000 mov ecx, 0000000A ; в ecx помещает 0Ah
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0040990E(U), :00409A1A(U)
|
:00409927 8D759F lea esi, dword ptr [ebp-61] ; буфер, куда будут записываться числа рег. кода.
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040993E(C)
|
:0040992A 31D2 xor edx, edx ; очищает edx
:0040992C F7F1 div ecx ; делит eax на ecx, в eax остаток передаваемый процедуре!
:0040992E 80C230 add dl, 30 ; к остатку от деления прибавляется 30h, чтобы получить число.
:00409931 80FA3A cmp dl, 3A ; если dl меньше 3Ah прыгает на 00409939
:00409934 7203 jb 00409939
:00409936 80C207 add dl, 07 ; иначе прибавляет к dl 7 для того чтобы избежать символов от 3Ah до 40h, но это нам не важно
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00409934(C)
|
:00409939 4E dec esi ; уменьшает esi на 1
:0040993A 8816 mov byte ptr [esi], dl ; записывает в буфер dl
:0040993C 09C0 or eax, eax ; если eax=0 выходит из цикла, иначе
:0040993E 75EA jne 0040992A ; переходит на адрес 0040992A и повторяет процесс
:00409940 8D4D9F lea ecx, dword ptr [ebp-61]
:00409943 29F1 sub ecx, esi
:00409945 8B55DC mov edx, dword ptr [ebp-24]
:00409948 83FA10 cmp edx, 00000010
:0040994B 7601 jbe 0040994E
:0040994D C3 ret
Таким образом остаток, передаваемый процедуре, занимает место делимого и делится на
0Ah, после чего к результату прибавляется 30h, полученное число записывается в буфер,
причем в обратном порядке. Этот процесс повторяется, пока делимое не будет равным 0.
Подведем итог. Первым делом программа проверяет, чтобы во всех полях ответственных
за регистрационные данные было не не менее 4 символов, если условие соблюдено,
то по порядку складываются эти данные, разделяя их при этом запятой. Таким образом образуется
строка, которую мы обозначили как SumStr. Затем
программа берет первый байт фамилии, умножает его на 0Dh и делит на 0FAh,
остаток этого деления сохраняет, мы его обозначили как
REMD. После этого идет главный цикл, в котором каждый байт SumStr умножается на REMD и
делится на 0FAh. Остаток каждого такого деления, став делимым, делится на 0Ah
(пока не станет равным 0) и к остатку прибавляется 30h. Так формируется регистрационный
код.
Теперь, зная алгоритм формирования регистрационного ключа, мы легко можем написать
свой генератор ключей. Сделаем это на том же delphi. Поместим на форму 5 полей ввода и
одну кнопку, присвоим кнопке событие onlick и впишем в него следующий код:
var
s:string;
b,d,d2,i:integer;
ar:array[1..100] of byte;
ar2:array[1..4] of byte;
ar3:array[1..100] of byte;
begin
s:=edit1.text+','+edit2.text+','+edit3.text+','+edit4.text;
b:=(byte(edit2.text[1])*13) mod 250;
for i:=1 to 100 do
ar3[i]:=0;
for i:=1 to length(s) do
ar[i]:=(byte(s[i])*b) mod 250;
d2:=0;
for i:=1 to length(s)-1 do
begin
d:=1;
repeat
ar2[d]:=(ar[i] mod 10)+48;
ar[i]:=ar[i] div 10;
d:=d+1;
until ar[i]=0;
for d:=d-1 downto 1 do
begin
ar3[i+d2]:=ar2[d];
d2:=d2+1;
end;
d2:=d2-1;
end;
edit5.text:='';
for i:=1 to 100 do
edit5.text:=edit5.text+char(ar3[i]);
end;
Код далеко не оптимален. Тем не менее
искренне надеюсь, что тебе несложно будет в нем разобраться.
Что бы зарегистрировать программу необходимо заполнить все поля, затем нажать
кнопку "Отправить", после чего будет создан файл в директории программы содержащий
введенные данные. Затем можно генерировать пароль, вводить его в соответствующее
поле и регистрировать. Делается это потому, что если не нажать кнопку "Отправить",
новые данные не будут сохранены в файле и при последующей проверке не будут соответствовать
регистрационному коду.
Статья предоставлена исключительно в образовательных целях.