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

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


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

БОЛЬШОЙ FAQ ПО DELPHI



Перечислить сетевые соединения

Речь сисадмина к девушке, пришедшей регистрироваться на сервере:
- С точки зрения сетевой безопасности вы представляете собой потенциальную дырку.


 {
   From the MS-DOS prompt, you can enumerate the network
   connections (drives) by using the following command:
 
    net use
 
   Programmatically, you would call WNetOpenEnum() to start
   the enumeration of connected resources and
   WNetEnumResources() to continue the enumeration.
   The following sample code enumerates the network connections:
 }
 
 
 procedure TForm1.Button1Click(Sender: TObject);
 var
   i, dwResult: DWORD;
   hEnum: THandle;
   lpnrDrv: PNETRESOURCE;
   s: string;
 const
   cbBuffer: DWORD = 16384;
   cEntries: DWORD = $FFFFFFFF;
 begin
   dwResult := WNetOpenEnum(RESOURCE_CONNECTED,
     RESOURCETYPE_ANY,
     0,
     nil,
     hEnum);
 
   if (dwResult <> NO_ERROR) then
   begin
     ShowMessage('Cannot enumerate network drives.');
     Exit;
   end;
   s := '';
   repeat
     lpnrDrv  := PNETRESOURCE(GlobalAlloc(GPTR, cbBuffer));
     dwResult := WNetEnumResource(hEnum, cEntries, lpnrDrv, cbBuffer);
     if (dwResult = NO_ERROR) then
     begin
       s := 'Network drives:'#13#10;
       for i := 0 to cEntries - 1 do
       begin
         if lpnrDrv^.lpLocalName <> nil then
           s := s + lpnrDrv^.lpLocalName + #9 + lpnrDrv^.lpRemoteName;
         Inc(lpnrDrv);
       end;
     end
     else if dwResult <> ERROR_NO_MORE_ITEMS then
     begin
       s := s + 'Cannot complete network drive enumeration';
       GlobalFree(HGLOBAL(lpnrDrv));
       break;
     end;
     GlobalFree(HGLOBAL(lpnrDrv));
   until (dwResult = ERROR_NO_MORE_ITEMS);
   WNetCloseEnum(hEnum);
   if s = '' then s := 'No network connections.';
   ShowMessage(s);
 end;
 
 
 {***********************************************************************
  FindComp Unit from
  Fatih Olcer
  fatiholcer@altavista.com
 ***********************************************************************}
 unit FindComp;
 
 interface
 
 uses
   Windows, Classes;
 
 function FindComputers: DWORD;
 
 var
   Computers: TStringList;
 
 implementation
 
 uses
   SysUtils;
 
 const
   MaxEntries = 250;
 
 function FindComputers: DWORD;
 var
   EnumWorkGroupHandle, EnumComputerHandle: THandle;
   EnumError: DWORD;
   Network: TNetResource;
   WorkGroupEntries, ComputerEntries: DWORD;
   EnumWorkGroupBuffer, EnumComputerBuffer: array[1..MaxEntries] of TNetResource;
   EnumBufferLength: DWORD;
   I, J: DWORD;
 begin
   Computers.Clear;
 
   FillChar(Network, SizeOf(Network), 0);
   with Network do
   begin
     dwScope := RESOURCE_GLOBALNET;
     dwType  := RESOURCETYPE_ANY;
     dwUsage := RESOURCEUSAGE_CONTAINER;
   end;
 
   EnumError := WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0,
     @Network, EnumWorkGroupHandle);
 
   if EnumError = NO_ERROR then
   begin
     WorkGroupEntries := MaxEntries;
     EnumBufferLength := SizeOf(EnumWorkGroupBuffer);
     EnumError        := WNetEnumResource(EnumWorkGroupHandle, WorkGroupEntries,
       @EnumWorkGroupBuffer, EnumBufferLength);
 
     if EnumError = NO_ERROR then
     begin
       for I := 1 to WorkGroupEntries do
       begin
         EnumError := WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0,
           @EnumWorkGroupBuffer[I], EnumComputerHandle);
         if EnumError = NO_ERROR then
         begin
           ComputerEntries  := MaxEntries;
           EnumBufferLength := SizeOf(EnumComputerBuffer);
           EnumError        := WNetEnumResource(EnumComputerHandle, ComputerEntries,
             @EnumComputerBuffer, EnumBufferLength);
           if EnumError = NO_ERROR then
             for J := 1 to ComputerEntries do
               Computers.Add(Copy(EnumComputerBuffer[J].lpRemoteName,
                 3, Length(EnumComputerBuffer[J].lpRemoteName) - 2));
           WNetCloseEnum(EnumComputerHandle);
         end;
       end;
     end;
     WNetCloseEnum(EnumWorkGroupHandle);
   end;
 
   if EnumError = ERROR_NO_MORE_ITEMS then
     EnumError := NO_ERROR;
   Result := EnumError;
 end;
 
 initialization
 
   Computers := TStringList.Create;
 
 finalization
 
   Computers.Free;
 end.
 




Как узнать переменные окружения

- Говорят, что русским было очень сложно опустить "Мир" обратно на землю... - Да чего уж там. Самой сложной частью всей операции была инсталляция Windows 95 на бортовые компьютеры "Мира". А упал он сам...


 procedure TForm1.Button5Click(Sender: TObject);
 var
   p: pChar;
 begin
   Memo1.Lines.Clear;
   Memo1.WordWrap := false;
   {$IFDEF WIN32}
   p := GetEnvironmentStrings;
   {$ELSE}
   p := GetDOSEnvironment;
   {$ENDIF}
   while p^ <> #0 do begin
     Memo1.Lines.Add(StrPas(p));
     inc(p, lStrLen(p) + 1);
   end;
   {$IFDEF WIN32}
   FreeEnvironmentStrings(p);
   {$ENDIF}
 end;
 




Получение переменных среды

Интересно, у владельцев компьютера, выражение "мать его..." это ругательное?


 procedure GetEnvironmentStrings(ss:TStrings);
 {Переменные среды}
 var
   ptr: PChar;
   s: string;
   Done: boolean;
 begin
   ss.Clear;
   s:='';
   Done:=FALSE;
   ptr:=windows.GetEnvironmentStrings;
   while Done=false do begin
     if ptr^=#0 then begin
       inc(ptr);
       if ptr^=#0 then Done:=TRUE
       else ss.Add(s);
       s:=ptr^;
     end else s:=s+ptr^;
     inc(ptr);
   end;
 end;
 




Тpансляция ошибок

Автор: Nomadic

- Исправил ли ты ошибку в программе?
- В разумных пределах...

Делаем ApplyUpdates. Если пpи insert(update) пpоизошла ошибка (поле null, сpаботал check, etc.), то BDE всегда говоpит "General SQL Error" вместо ноpмального сообщения об ошибке :-( Без CU все ноpмально, pазумеется. Как боpоть этот баг?

Использyй ноpмальнyю тpансляцию ошибок в Application.OnException. Вpоде это.


 procedure DBExceptionTranslate(E: EDBEngineError);
 


 function OriginalMessage: string;
 var
   I: Integer;
   DBErr: TDBError;
   S: string;
 begin
   Result := '';
   for I := 0 to E.ErrorCount - 1 do
   begin
     DBErr := E.Errors[I];
     case DBErr.NativeError of
       -836: { Intebase exception }
         begin
           S := DBErr.Message;
           Result := #13#10 + Copy(S, Pos(#10, S) + 1, Length(S));
           Exit;
         end;
     end;
     S := Trim(DBErr.Message);
     if S <> '' then
       Result := Result + #13#10 + S;
   end;
 end;
 
 begin
   case E.Errors[0].ErrorCode of
     $2204:
       E.Message := LoadStr(SKeyDeleted);
     $271E, $2734:
       E.Message := LoadStr(SInvalidUserName);
     $2815:
       E.Message := LoadStr(SDeadlock);
     $2601:
       E.Message := LoadStr(SKeyViol);
     $2604:
       E.Message := LoadStr(SFKViolation) + OriginalMessage;
   else
     begin
       E.Message := Format(LoadStr(SErrorCodeFmt), [E.Errors[0].ErrorCode]) +
         OriginalMessage;
     end;
   end;
 end;
 




Некорректность реализации свойства BorderWidth у ProgressBar

Автор: VS

При использовании в компонентах свойства BorderWidth будте внимательны. В большинстве компонентов (ControlBar, ProgressBar, StatusBar, ToolBar, TrackBar и т.д.), это свойство реализовано некорректно. Можно получить забавные результаты или большие неприятности.

На рисунке показаны возможные варианты при использовании различных значений BorderWidth в компоненте ProgressBar. Высота компонента ProgressBar.Height = 16.

В последних двух вариантах вместо индикатора – изображение под активным окном. Не надейтесь, что компонент стал "прозрачным". Это "моментальный снимок" при создании окна.

Если есть желание, то некорректность можно исправить в ComCtrls.pas, переопределив BorderWidth.


 TProgressBar = class(TWinControl)
 private
   FBorderWidth: TBorderWidth;
   procedure SetBorderWidth(Value: TBorderWidth);
 published
   property BorderWidth: TBorderWidth read FBorderWidth write SetBorderWidth;
 
 ...
 
 constructor TProgressBar.Create(AOwner: TComponent);
 begin
     FBorderWidth := inherited BorderWidth;
 end;
 
 procedure TProgressBar.SetBorderWidth(Value: TBorderWidth);
 begin
   if Value > (Height div 2) - 3 then
     Exit
       if Value <> inherited BorderWidth then
     begin
       inherited BorderWidth := Value;
       FBorderWidth := inherited BorderWidth;
     end;
 end;
 

Аналогичный подход можно использовать в других компонентах. В процедуре SetBorderWidth вместо Exit можно создать исключение, но это уже на любителя.




Ошибка создания дескриптора курсора

Билл Гейтс, когда стал самым богатым человеком в мире, подумал: "Hа все воля Божья. Я никогда бы не разбогател, если бы Он этого не хотел. Hадо как-то отблагодарить." Билли построил огромный храм, зажег в нем миллион свечей, вошел и молится:
- Господи, спасибо Тебе за все. Hе сочти за дерзость, Господи, но я хочу выразить Тебе свою признательность и приглашаю Тебя на игру в гольф в это воскресенье.
Увидел Иисус это фейерверк, услышал молитву, понял, что уважают, и решил прийти. Играю они, значит, в гольф. Иисус бьет по мячу, мяч летит и зависает прямо над центром лунки. Иисус бьет по второму - тот летит и зависает прямо над центром лунки. Иисус бьет по третьему - тоже самое. Он оборачивается к Гейтсу и говорит:
- Билли, а другого бета-тестера для своих глючных мячей ты подыскать не мог?

Вы должны использовать ExecSql вместо Open. К примеру, если имя вашего запроса UpdateStudent, то при необходимости обновления STUDENT.DB вы должны использовать следующий код:


 Begin
 .....
 UpdateStudent.ExecSql;
 .....
 End;
 

Ваш запрос является Passtrough-запросом, который не может возвратить установленный результат, так что это не может быть открыто, а должно быть 'ВЫПОЛНЕНО'.




Получение констант с определением ошибки функцией LoadStr

В семье программиста несчастье. Жена прибегает в слезах:
- У меня случился выкидыш! Программист задумчиво чешет репу:
- Мда-а... Баг в бета-версии...

Возбуждение исключения и передача строковой константы ошибки из CONSTS.PAS (как это делалось в Delphi 1 и Delphi 2) в Delphi3 невозможна. Например:


 raise SomeException.Create(LoadStr(SInsertLineError));
 

в Delphi3 теперь не работает. Я предлагаю использовать выражение


 raise SomeException.Create(
 {$IFNDEF VER100}LoadStr{$ENDIF}
 (SInsertLineError));
 

для вызова функции LoadStr "по нужде", устаревшей почему-то для этих целей в Delphi 3.

При попытке вызвать данную функцию в Delphi 3 (для получения типа ошибки, декларированной в модуле CONSTS(.PAS/.DCU)), мы получаем следующую ошибку компилятора: "Incompatible types: 'Integer' and 'String'"

Однако в Delphi 4 данная оплошность исправлена и вроде как документирована. Почитайте в хелпе на тему "resourcestrings", посмотрите сырцы и все сразу станет понятно и просто.




Номера ошибок

Странное какое-то у немцев отношение к компьютерам: на их языке слово "Gluck" означает счастье.

Номера ошибок
Ошибка
Сообщение об ошибке
1 Неверный номер функции (Invalid function number)
2 Файл не найден (File not found)
3 Путь не найден (Path not found)
4 Слишком много открытых файлов (Too many open files)
5 Доступ к файлу закрыт (File access denied)
6 Неверный дескриптор файла (Invalid file handle)
12 Неверный код доступа к файлу (Invalid file access code)
15 Неверный номер устройства (Invalid drive number)
16 Невозможно удалить текущий каталог (Cannot remove current directory)
17 Невозможно переименовать устройство (Cannot rename across drives)
100 Ошибка чтения диска (Disk read error)
101 Ошибка записи на диск (Disk write error)
102 Файл не назначен (File not assigned)
103 Файл не открыт (File not open)
104 Файл не открыт для ввода (File not open for input)
105 Файл не открыт для вывода (File not open for output)
106 Неверный числовой формат (Invalid numeric format)
200 Деление на ноль (Division by zero)
201 Превышение допустимого диапазона (Range check error)
202 Переполнение стека (Stack overflow error)
203 Переполнение кучи (Heap overflow error)
204 Неверная операция с указателем (Invalid pointer operation)
205 Переполнение плавающей точки (Floating point overflow)
206 Потеря значимости плавающей точкой (Floating point underflow)
207 Неверная операция с плавающей точкой (Invalid floating point operation)
210 Объект не инициализирован (Object not initialized)
211 Вызов абстрактного метода (Call to abstract method)
212 Ошибка регистрации потока (Stream registration error)
213 Индекс коллекции вышел за пределы допустимого диапазона (Collection index out of range)
214 Ошибка переполнения коллекции (Collection overflow error)
215 Ошибка арифметического переполнения (Arithmetic overflow error)
216 Общая ошибка защиты (General protection fault)




Error reading symbol file

Время от времени при открытии проекта возникает сообщение: "Error reading symbol file".

ТИПОВЫЕ РЕШЕНИЯ

Удалить файлы *.sym. Они могут остаться от другой версии Delphi, при том что формат файла с символьной информацией изменился.




Escape код PASSTHROUGH

Мой папа хакер и мама хакер, сестричка тоже хакер хоть куда. А кто не хакер? Ну кто не хакер? А тот кто кода и не видел никогда.

Кто-нибудь знает почему некоторые видеодрайверы не осуществляют транзитную пересылку данных (passthrough) в Escape функции?

Как узнать, поддерживает ли драйвер принтера код PASSTROUGH? Dos-приложения и в Windows 95 используют эту функцию с командой "copy file > lpt1" для передачи текста в буфер принтера.

Хотя Delphi модуль TPrinter облегчает доступ к принтеру, есть случаи, когда вам необходимо спуститься до транспортного уровня общения системы с принтером и передать специфические для устройства управляющие коды. Под 16-битной операционной системой Windows это было так же легко, как открыть порт принтера, но сейчас, к примеру, под Windows NT, непосредственный доступ к аппаратному обеспечению невозможен. Одно из решений проблемы состоит в использовании Windows кода "PASSTHROUGH" для посылки кода непосредственно в принтер. Тем не менее, для использования кода "PASSTHROUGH" необходимо, чтобы это поддерживалось самим драйвером принтера. К сожалению, далеко не все принтеры поддерживают данную характеристику.

Необходимо отметить, что "PASSTHROUGH" для 32-битных приложений считается устаревшим кодом. Но должно пройти немало лет, прежде чем это умрет окончательно, поскольку это до сих пор используется во многих коммерческих приложениях.

Приведенный ниже пример не привязан к каким-либо специфическим моделям принтеров. Вам необходимо лишь знать правильную последовательность передачи escape кодов на управляемый вами принтер. Имейте в виду, что вы все еще должны вызывать методы BeginDoc и EndDoc объекта TPrinter. При вызове метода BeginDoc драйвер принтера инициализирует принтер как объект управления, EndDoc - деинициализацирует и извлекает бумагу. При escape вызове принтер может установить текущий режим метрики экрана, если он поддерживает внутреннее масштабирование. Технически вы ничего не должны делать, что могло бы вызвать обнуление памяти принтера или удаление из него бумаги с помощью escape кодов. Другими словами, попытайтесь оставить принтер в том же состоянии, в котором он остался после окончания печати. В основном это касается технически совершенных принтеров, поддерживающих режим Postscript, в стандартных же моделях (TTY) все это не столь существенно, и вы свободны в своих действиях, включая удаление страницы из принтера.

Пример кода:

Прежде всего вам необходимо описать структуру буфера, который вы собираетесь посылать. Структура буфера определена как слово, содержащее размер буфера и сам буфер, содержащий данные.

Прежде всего, с помощью escape вызова "QUERYESCSUPPORT" необходимо убедиться, что "PASSTHROUGH" поддерживается драйвером печати.

И, наконец, ваши данные будут переданы в поток данных принтера. Необходимо также помнить, что в некоторых моделях принтеров (Postscript), вам возможно понадобиться добавить пробелы в начале и в конце передаваемых данных, чтобы отделить ваши данные от данных драйвера печати.

(Postscript - зарегистрированная торговая марка Adobe Systems Incorporated)


 unit Esc1;
 
 interface
 
 uses
 
   SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
   Forms, Dialogs, StdCtrls;
 
 type
 
   TForm1 = class(TForm)
     Button1: TButton;
     procedure Button1Click(Sender: TObject);
   private
     { Private declarations }
   public
     { Public declarations }
   end;
 
 var
 
   Form1: TForm1;
 
 implementation
 
 { добавляем модуль printers }
 uses
 
   Printers;
 
 {$R *.DFM}
 
 { описываем структуру "PASSTHROUGH" }
 type
   TPrnBuffRec = record
 
     BuffLength: word;
     Buffer: array[0..255] of char;
   end;
 
 procedure TForm1.Button1Click(Sender: TObject);
 var
 
   Buff: TPrnBuffRec;
   TestInt: integer;
   s: string;
 begin
 
   { Тестируем на предмет поддержки escape кода "PASSTHROUGH" }
 
   TestInt := PASSTHROUGH;
   if Escape(Printer.Handle,
     QUERYESCSUPPORT,
     sizeof(TestInt),
     @TestInt,
     nil) > 0 then
   begin
 
     { Начинаем вывод на печать }
     Printer.BeginDoc;
 
     { Создаем строку для транзитной пересылки }
     s := ' Текстовая строка ';
 
     { Копируем строчку в буфер }
     StrPCopy(Buff.Buffer, s);
 
     { Устанавливаем размер буфера }
     Buff.BuffLength := StrLen(Buff.Buffer);
 
     { Даем команду на транзитную пересылку буфера }
     Escape(Printer.Canvas.Handle,
       PASSTHROUGH,
       0,
       @Buff,
       nil);
 
     { Заканчиваем вывод на печать }
     Printer.EndDoc;
   end;
 end;
 
 end.
 




Возможность отмены вставки нового узла в TTreeView по нажатию кнопки Esc


 unit BetterTreeView;
 
 interface
 
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   ComCtrls, CommCtrl;
 
 type
   TTVNewEditCancelEvent = procedure( Sender: TObject; Node: TTreeNode; var Delete: Boolean) of object;
   TBetterTreeView = class(TTreeView)
   protected
     FIsEditingNew: Boolean;
     FOnEditCancel: TTVChangedEvent;
     FOnNewEditCancel: TTVNewEditCancelEvent;
     procedure Edit(const Item: TTVItem); override;
   public
     function NewChildAndEdit(Node: TTreeNode; const S: string): TTreeNode;
   published
     property IsEditingNew: Boolean read FIsEditingNew;
     property OnEditCancel: TTVChangedEvent read FOnEditCancel write FOnEditCancel;
     property OnNewEditCancel: TTVNewEditCancelEvent read FOnNewEditCancel write FOnNewEditCancel;
 end;
 
 implementation
 
 procedure TBetterTreeView.Edit(const Item: TTVItem);
 var
   Node: TTreeNode;
   Action: Boolean;
 begin
   with Item do
   begin
     { Get the node }
     if (state and TVIF_PARAM) <> 0 then
       Node := Pointer(lParam)
     else
       Node := Items.GetNode(hItem);
 
     if pszText = nil then
     begin
       if FIsEditingNew then
       begin
         Action := True;
         if Assigned(FOnNewEditCancel) then
           FOnNewEditCancel(Self, Node, Action);
         if Action then
           Node.Destroy
       end
       else
         if Assigned(FOnEditCancel) then
           FOnEditCancel(Self, Node);
     end
     else
       inherited;
   end;
   FIsEditingNew := False;
 end;
 
 function TBetterTreeView.NewChildAndEdit(Node: TTreeNode; const S: string): TTreeNode;
 begin
   SetFocus;
   Result := Items.AddChild(Node, S);
   FIsEditingNew := True;
   Node.Expand(False);
   Result.EditText;
   SetFocus;
 end;
 
 end.
 




Делегирование события

Автор: Steve

...я вижу что событие делегировано, но почему вы считаете, что это отход от ООП? Тем не менее, Delphi это использует при каждом определении обработчика события.

Вот как поступить в вашей ситуации:

  1. Определите тип процедуры, использующейся в качестве обработчика события. Допустим, ваш обработчик OnCalculate имеет один параметр типа Integer (присутствующий лишь для демонстрации идеи).

  2.  type
       TCalculateEvent = procedure(I: Integer) of object;
     

  3. Теперь объявите ваш класс:

  4.  type
     TSomeClass = class(TObject)
     private
     FOnCalculate: TCalculateEvent;
     procedure DoCalculate(I: Integer);
     public
     property OnCalculate: TCalculateEvent read FOnCalculate
     write FOnCalculate;
     end;
     

  5. Метод DoCalculate совсем простой:

  6.  procedure TSomeClass.DoCalculate(I: Integer);
     begin
       if Assigned(FOnCalculate) then
         FOnCalculate(I);
     end;
     

Теперь вы можете присваивать значение объекту TSomeClass, и назначать любую процедуру событию OnCalculate (естественно, при условии, что ее объявление соответствует объявлению TCalculateEvent).




Синтаксис ссылки на событие

Автор: Kurt

Я хочу проверить значение события, чтобы убедиться, что оно указывает в настоящий момент на определенную функцию.

По всей видимости вам придется использовать две временные переменные. Попытайтесь сделать следующее:


 var
   ClickMethod: TNotifyEvent;
   B1Click: TNotifyEvent;
 ...
 ClickMethod := Button1.OnClick;
 B1Click := Button1Click;
 if @ClickMethod = @B1Click then
   messagebeep(0);
 




Генерация еженедельных списков задач

Автор: Mike Orriss

Мне необходима программа, которая генерировала бы еженедельные списки задач. Программа должна просто показывать количество недель в списке задач и организовывать мероприятия, не совпадающие по времени. В моем текущем планировщике у меня имеется 12 групп и планы на 11 недель.

Мне нужен простой алгоритм, чтобы решить эту проблему. Какие идеи?

Вот рабочий код (но вы должны просто понять алгоритм работы):


 unit Unit1;
 
 interface
 
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   StdCtrls;
 
 type
   TForm1 = class(TForm)
     ListBox1: TListBox;
     Edit1: TEdit;
     Button1: TButton;
     procedure Button1Click(Sender: TObject);
   private
     { Private declarations }
   public
     { Public declarations }
   end;
 
 var
   Form1: TForm1;
 
 implementation
 
 {$R *.DFM}
 
 const
   maxTeams = 100;
 var
   Teams: array[1..maxTeams] of integer;
   nTeams, ix, week, savix: integer;
 
 function WriteBox(week: integer): string;
 var
   str: string;
   ix: integer;
 begin
   Result := Format('Неделя=%d ', [week]);
   for ix := 1 to nTeams do
   begin
     if odd(ix) then
       Result := Result + ' '
     else
       Result := Result + 'v';
     Result := Result + IntToStr(Teams[ix]);
   end;
 end;
 
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   nTeams := StrToInt(Edit1.Text);
   if Odd(nTeams) then
     inc(nTeams); {должны иметь номера каждой группы}
   ListBox1.Clear;
   for ix := 1 to nTeams do
     Teams[ix] := ix;
   ListBox1.Items.Add(WriteBox(1));
 
   for week := 2 to nTeams - 1 do
   begin
     Teams[1] := Teams[nTeams - 1];
       {используем Teams[1] в качестве временного хранилища}
     for ix := nTeams downto 2 do
       if not Odd(ix) then
       begin
         savix := Teams[ix];
         Teams[ix] := Teams[1];
         Teams[1] := savix;
       end;
     for ix := 3 to nTeams - 1 do
       if Odd(ix) then
       begin
         savix := Teams[ix];
         Teams[ix] := Teams[1];
         Teams[1] := savix;
       end;
     Teams[1] := 1; {восстанавливаем известное значение}
     ListBox1.Items.Add(WriteBox(week));
   end;
 end;
 
 end.
 




Высокоточный таймер

Автор: John Mertus


 {
 
 Имя файла: HRTimer.PAS V1.00
 Создан: Апр 17 1997, 06:40, автор John Mertus
 Обновлен #6: Окт 12 1997, 10:56 John Mertus
 
 Оболочка для таймера высокой точности при создании приложений под
 Win95/WinNT
 
 Var
 HRT : THRTimer
 
 HRT := THRTimer.Create;
 HRT.StartTimer;  Сброс таймера в ноль
 HRT.ReadTimer;   Возвращает отсчитанное время в миллисекундах
 начиная со времени старта
 
 HRT.Free;
 
 Список изменений
 Версия  1.00 - Первый выпуск
 }
 
 {-----------------Модуль HRTimer-------------------John Mertus Апрель 97---}
 
 unit HRTimer;
 
 {-------------------Объявления-------------------------------}
 
 interface
 
 uses Windows;
 
 type
 
   THRTimer = class(TObject)
     constructor Create;
     function StartTimer: Boolean;
     function ReadTimer: Double;
 
   private
     StartTime: Double;
     ClockRate: Double;
 
   public
     Exists: Boolean;
 
   end;
 
   {--------------------------Реализация-----------------------------------}
 
 implementation
 
 {------------------Create-------------------------John Mertus----Мар 97-}
 
 constructor THRTimer.Create;
 
 { Получаем точное системное время и сохраняем его для дальнейшего       }
 { использования.                                                        }
 {                                                                       }
 {***********************************************************************}
 var
 
   QW: TLargeInteger;
 
 begin
 
   inherited Create;
   Exists := QueryPerformanceFrequency(QW);
   ClockRate := QW.QuadPart;
 end;
 
 {------------------StartTimer---------------------John Mertus----Мар 97-}
 
 function THRTimer.StartTimer: Boolean;
 
 { Получаем точное системное время и сохраняем его для дальнейшего       }
 { использования.                                                        }
 {                                                                       }
 {***********************************************************************}
 var
 
   QW: TLargeInteger;
 
 begin
 
   Result := QueryPerformanceCounter(QW);
   StartTime := QW.QuadPart;
 end;
 
 {-------------------ReadTimer---------------------John Mertus----Мар 97---}
 
 function THRTimer.ReadTimer: Double;
 
 { Получаем точное системное время и сохраняем его для дальнейшего       }
 { использования.                                                        }
 {                                                                       }
 {***********************************************************************}
 var
 
   ET: TLargeInteger;
 
 begin
 
   QueryPerformanceCounter(ET);
   Result := 1000.0 * (ET.QuadPart - StartTime) / ClockRate;
 end;
 
 end.
 




Как отчитывать промежутки времени с точностью

Автор: Nomadic

Для начала описываешь процедуру, которая будет вызываться по сообщению от таймера :


 procedure FNTimeCallBack(uTimerID, uMessage: UINT;
 dwUser, dw1, dw2: DWORD); stdcall;
 begin
   // Тело процедуры.
 end;
 

а дальше в программе (например по нажатию кнопки) создаешь Таймер и вешаешь на него созданную процедуру


 uTimerID:=timeSetEvent(10,500,@FNTimeCallBack,100,TIME_PERIODIC);
 

Подробности смотри в Help. Hу и в конце убиваешь таймер


 timeKillEvent(uTimerID);
 

И все. Точность этого способа до 1 мсек. минимальный интервал времени можно задавать 1 мсек.

Обратите внимание на то, что все CALLBACK-функции, вызываемые Windows, должны использовать соглашение о вызовах stdcall.




Как работать с буфером обмена (Clipboard)

Собираются программисты встречать новый год. Уже разлили пиво (по бокалам - а вы что подумали?), и тут один говорит:
- Я хочу произнести тост. Давайте выпьем за моего злейшего врага. И пусть в новом году у него денег будет в десять раз больше, чем у меня! И, обведя взглядом недоуменные лица окружающих, добавляет:
- За Билла Гейтса!

Этот пример использует картинку, кнопку и компонент shape на форме. Когда пользователь кликает по кнопке, то изображение формы сохраняется в в переменной FormImage и копируется в буфер обмена (Clipboard). Затем изображение формы копируется обратно в компонент картинки, тем самым создавая интересный эффект, особенно, если кнопку понажимать несколько раз.


 procedure TForm1.Button1Click(Sender: TObject);
 var
   FormImage: TBitmap;
 begin
   FormImage := GetFormImage;
   try
     Clipboard.Assign(FormImage);
     Image1.Picture.Assign(Clipboard);
   finally
     FormImage.Free;
   end;
 end;
 
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   Shape1.Shape := stEllipse;
   Shape1.Brush.Color := clLime;
   Image1.Stretch := True;
 end;
 

Следующий пример копирует содержимое экрана в буфер обмена:


 procedure CopyScreenToClipboard;
 var
   dx,dy: integer;
   hSourcDC, hDestDC, hBM, hbmOld: THandle;
 begin
   dx := screen.width;
   dy := screen.height;
   hSourcDC := CreateDC('DISPLAY', nil, nil, nil);
   hDestDC := CreateCompatibleDC(hSourcDC);
   hBM := CreateCompatibleBitmap(hSourcDC, dx, dy);
   hbmold:= SelectObject(hDestDC, hBM);
   BitBlt(hDestDC, 0, 0, dx, dy, hSourcDC, 0, 0, SRCCopy);
   OpenClipBoard(form1.handle);
   EmptyClipBoard;
   SetClipBoardData(CF_Bitmap, hBM);
   CloseClipBoard;
   SelectObject(hDestDC,hbmold);
   DeleteObject(hbm);
   DeleteDC(hDestDC);
   DeleteDC(hSourcDC);
 end;
 




Проход по ячейкам Excel макросом

Новости от Microsoft: "... новая операционная система имеет удобный дружественный интерфейс и множество функций. облегчающих жизнь пользователю. Сразу после запуска она форматирует диск, уничтожает содержимое BIOS и выводит из строя процессор, после чего на экране появляется надпись - "Теперь питание компьютера можно отключить".


 Private Sub CommandButton1_Click()
 Dim i As Integer
 Dim index As Integer
   For Each cell_in_loop In Range("A2:A8")
     For i = 3 To 21
       index = 24 - i
       If cell_in_loop.Value < Лист2.Cells(index, 1) Then
         If cell_in_loop.Offset(0, 1).Value = "Да" Then
           cell_in_loop.Offset(0, 2).Value = Лист2.Cells(index, 3)
         Else
           cell_in_loop.Offset(0, 2).Value = Лист2.Cells(index, 2)
         End If
       End If
     Next
   Next
 End Sub
 




Пример объединения ячеек и выравнивания текста в Excel


Встречаются двое юзеров. Один говорит:
- Я тут свежий антивирус достал, не хочешь себе установить?
- Нет, мне это не нужно.
- Почему?
- Да мой комп так глючит, что на нем ни один вирус не запустится.

Вызывать так:


 MerCen('A4:J4');
 

Исходный код:


 var
   xls: Variant;
 const
   xlCenter = -4108;
 
 procedure MerCen(ran: string);
 begin
   xls.Range[ran].Select;
   xls.Selection.HorizontalAlignment := xlCenter;
   xls.Selection.VerticalAlignment := xlCenter;
   xls.Selection.WrapText:= false;
   xls.Selection.Orientation := 0;
   xls.Selection.ShrinkToFit := False;
   xls.Selection.MergeCells := False;
   xls.Selection.Merge;
 end;
 




Управление Microsoft Excel

Генеральный спонсор апокалипсиса: компания Microsoft.


 uses AciveX;        // для Delphi 3 и 4
 
 uses Ole2, OleAuto; // для Delphi 2
 
 procedure TForm1.Button1Click(Sender: TObject);
 var
   MyExcel: Variant;
 begin
   MyExcel:=CreateOleObject('Excel.Application');
   MyExcel.Visible:=true;
   MyExcel.WorkBooks.Add;
   MyExcel.Cells[1,1].value:='1';
   MyExcel.ActiveWorkbook.SaveAs(PATHNAME);
   MyExcel.Quit;
 end;
 




Обмен данными с Excel


Самое часто произносимое в мире имя:
- Билл Гейтс!?
- Нет, его матушка!

В Delphi 5, для обмена данными между Вашим приложением и Excel можно использовать компонент TExcelApplication, доступный на Servers Page в Component Palette.

На форме находится TStringGrid, заполненный некоторыми данными и две кнопки, с названиями To Excel и From Excel. Так же на форме находится компонент TExcelApplication со свойством Name, содержащим XLApp и свойством ConnectKind, содержащим ckNewInstance.

Когда нам необходимо работать с Excel, то обычно мы открываем ExcelApplication, затем открываем WorkBook и в конце используем WorkSheet.

Итак, несомненный интерес представляет для нас листы (WorkSheets) в книге (WorkBook). Давайте посмотрим как всё это работает.

Посылка данных в Excel

Это можно сделать с помощью следующей процедуры :


 procedure TForm1.BitBtnToExcelOnClick(Sender: TObject);
 var
   WorkBk: _WorkBook; //  определяем WorkBook
   WorkSheet: _WorkSheet; //  определяем WorkSheet
   I, J, K, R, C: Integer;
   IIndex: OleVariant;
   TabGrid: Variant;
 begin
   if GenericStringGrid.Cells[0,1] <> '' then
   begin
     IIndex := 1;
     R := GenericStringGrid.RowCount;
     C := GenericStringGrid.ColCount;
     // Создаём массив-матрицу
     TabGrid := VarArrayCreate([0,(R - 1),0,(C - 1)],VarOleStr);
     I := 0;
     //  Определяем цикл для заполнения массива-матрицы
     repeat
     for J := 0 to (C - 1) do
       TabGrid[I,J] := GenericStringGrid.Cells[J,I];
     Inc(I,1);
     until
     I > (R - 1);
 
     // Соединяемся с сервером TExcelApplication
     XLApp.Connect;
     // Добавляем WorkBooks в ExcelApplication
     XLApp.WorkBooks.Add(xlWBatWorkSheet,0);
     // Выбираем первую WorkBook
     WorkBk := XLApp.WorkBooks.Item[IIndex];
     // Определяем первый WorkSheet
     WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet;
     // Сопоставляем Delphi массив-матрицу с матрицей в WorkSheet
     Worksheet.Range['A1',Worksheet.Cells.Item[R,C]].Value := TabGrid;
     // Заполняем свойства WorkSheet
     WorkSheet.name := 'Customers';
     Worksheet.Columns.Font.Bold := True;
     Worksheet.Columns.HorizontalAlignment := xlRight;
     WorkSheet.Columns.ColumnWidth := 14;
     // Заполняем всю первую колонку
     WorkSheet.Range['A' + IntToStr(1),'A' + IntToStr(R)].Font.Color := clBlue;
     WorkSheet.Range['A' + IntToStr(1),'A' + IntToStr(R)].HorizontalAlignment := xlHAlignLeft;
     WorkSheet.Range['A' + IntToStr(1),'A' + IntToStr(R)].ColumnWidth := 31;
     // Показываем Excel
     XLApp.Visible[0] := True;
     // Разрываем связь с сервером
     XLApp.Disconnect;
     // Unassign the Delphi Variant Matrix
     TabGrid := Unassigned;
   end;
 end;
 

Получение данных из Excel

Это можно сделать с помощью следующей процедуры


 procedure TForm1.BitBtnFromExcelOnClick(Sender: TObject);
 var
   WorkBk: _WorkBook;
   WorkSheet: _WorkSheet;
   K, R, X, Y: Integer;
   IIndex: OleVariant;
   RangeMatrix: Variant;
   NomFich: WideString;
 begin
   NomFich := 'C:\MyDirectory\NameOfFile.xls';
   IIndex := 1;
   XLApp.Connect;
   // Открываем файл Excel
   XLApp.WorkBooks.Open(NomFich, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam,
   EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam,EmptyParam,0);
   WorkBk := XLApp.WorkBooks.Item[IIndex];
   WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet;
   // Чтобы знать размер листа (WorkSheet), т.е. количество строк и количество
   // столбцов, мы активируем его последнюю непустую ячейку
   WorkSheet.Cells.SpecialCells(xlCellTypeLastCell,EmptyParam).Activate;
   // Получаем значение последней строки
   X := XLApp.ActiveCell.Row;
   // Получаем значение последней колонки
   Y := XLApp.ActiveCell.Column;
   // Определяем количество колонок в TStringGrid
   GenericStringGrid.ColCount := Y;
   // Сопоставляем матрицу WorkSheet с нашей Delphi матрицей
   RangeMatrix := XLApp.Range['A1',XLApp.Cells.Item[X,Y]].Value;
   // Выходим из Excel и отсоединяемся от сервера
   XLApp.Quit;
   XLApp.Disconnect;
   //  Определяем цикл для заполнения TStringGrid
   K := 1;
   repeat
     for R := 1 to Y do
       GenericStringGrid.Cells[(R - 1),(K - 1)] := RangeMatrix[K,R];
     Inc(K,1);
     GenericStringGrid.RowCount := K + 1;
   until
     K > X;
   // Unassign the Delphi Variant Matrix
   RangeMatrix := Unassigned;
 end;
 




OLE Automation для работы с Excel

Автор: Konstantin Khripkov

Фирма Microsoft выпустила новую электронную таблицу Sexcel 1.1. На первый взгляд она ничем не отличается от Excel 5.0, но удовольствия от работы гораздо больше.

Подскажите мне основные функции OLE Automation для работы с Excel из Delphi.

Есть множество статей по этому вопросу. Смотри также этот материал .

Я не могу привести полный код моего проекта, здесь я поместил лишь его небольшую часть, создающую и форматирующую таблицу Excel на основе содержимого DBGrid, сгенеренного с помощью SQL запроса. Код содержит некоторое форматирование ячеек. Код проверен в работе с Delphi 3 и Excel 97:


 procedure TfrmBlank.btnExcelClick(Sender: TObject);
 var
   XL, XArr: Variant;
   i: Integer;
   j: Integer;
 begin
   {не забудьте включить ComObj в список используемых модулей}
   // Создаем массив элементов, полученных в результате запроса
   XArr := VarArrayCreate([1, EmailQuery.FieldCount], varVariant);
   XL := CreateOLEObject('Excel.Application'); // Создание OLE объекта
   XL.WorkBooks.add;
   XL.visible := true;
 
   j := 1;
   EmailQuery.First;
   while not EmailQuery.Eof do
   begin
     i := 1;
     while i <= EmailQuery.FieldCount do
     begin
       XArr[i] := EmailQuery.Fields[i - 1].Value;
       i := i + 1;
     end;
     XL.Range['A' + IntToStr(j),
       CHR(64 + EmailQuery.FieldCount) + IntToStr(j)].Value := XArr;
     EmailQuery.Next;
     j := j + 1;
   end;
   XL.Range['A1', CHR(64 + EmailQuery.FieldCount) + IntToStr(j)].select;
   // XL.cells.select;                     // Выбираем все
   XL.Selection.Font.Name := 'Garamond';
   XL.Selection.Font.Size := 10;
   XL.selection.Columns.AutoFit;
   XL.Range['A1', 'A1'].select;
 end;
 

Дополнение

При однопроходной передачи полной страницы гораздо удобнее использовать двумерный Variant Array, чем запихивать данные туда построчно. Функция VarArrayCreate позволяет создать многомерные массивы при определении 2N (где N - кол-во измерений) границ массива в аргументе Bounds.




Передать данные из Excel в Word


 { Add these global variables/constants:
   als Globale Variablen/Konstanten deklarieren: }
 
 var
   XLApp: Variant;
   WordApp: Variant;
 
 const
   xlWBATWorksheet = -4167;
   wdDoNotSaveChanges = 0;
 
 {For creating data in Excel we shall start it first:
 Excel starten:}
 
   //Starting Excel application: 
   XLApp := CreateOleObject('Excel.Application');
   // Making it visible: 
   XLApp.Visible := True;
   // Adding workbook: 
   XLApp.Workbooks.Add[XLWBatWorksheet];
   // Specifying name of worksheet: 
   XLApp.Workbooks[1].Worksheets[1].Name := 'Delphi Data';
 
 {Now inserting data to Excel:
 Daten in Excel einfugen:}
 
 procedure TForm1.InsertData2Excel;
 var
   Sheet: Variant;
   i: Integer;
 begin
   Sheet := XLApp.Workbooks[1].Worksheets['Delphi Data'];
   for i := 1 to 10 do
     Sheet.Cells[i, 1] := i;
 end;
 
 {And copying data from Excel to Word.
 Daten von Excel nach Word kopieren. }
 
 
 {This process consists of two phrases:
 1) Data should be copied from Excel into Windows clipboard.
 2) Data should be pasted from Windows clipboard into the Word.
 For successful completion both Excel and Word should be running.
 
 Copying data from Excel into Windows clipboard:
 Daten von Excel in die Zwischenablage kopieren: }
 
 procedure TForm1.CopyData;
 var
   Sheets: Variant;
 begin
   SetFocus;
   Sheets := XLApp.Sheets;
   // Selecting our worksheet: 
   Sheets.Item['Delphi Data'].Activate;
   // Selecting our cells: 
   Sheets.Item['Delphi Data'].Range['A1:A10'].Select;
   // Copying selected cells into clipboard: 
   Sheets.Item['Delphi Data'].UsedRange.Copy;
   // Inserting copied data into Word 
   InserData2Word;
 end;
 
 procedure TForm1.InsertData2Word;
 var
   Range: Variant;
   i: Integer;
 begin
   // Starting Word: 
   WordApp := CreateOleObject('Word.Application');
   // Making it visible: 
   WordApp.Visible := True;
   // Adding new document: 
   WordApp.Documents.Add;
   // Inserting description text into new document: 
   Range      := WordApp.Documents.Item(1).Range;
   Range.Text := 'This is a column from a spreadsheet: ';
   for i := 1 to 3 do WordApp.Documents.Item(1).Paragraphs.Add;
   // Inserting data from clipboard 
   Range := WordApp.Documents.Item(1).Range(WordApp.Documents.Item
     (1).Paragraphs.Item(3).Range.Start);
   Range.Paste;
   for i := 1 to 3 do WordApp.Documents.Item(1).Paragraphs.Add;
 end;
 
 {Don't forget to close Excel and Word by your program termination:
 Excel und Word anschliessend wieder schliessen:}
 
 procedure TForm1.FormDestroy(Sender: TObject);
 begin
   if not VarIsEmpty(XLApp) then
   begin
     XLApp.DisplayAlerts := False;  // Discard unsaved files... 
     XLApp.Quit;
   end;
   if not VarIsEmpty(WordApp) then
   begin
     WordApp.Documents.Item(1).Close(wdDoNotSaveChanges);
     WordApp.Quit;
   end;
 end;
 




Как выполнить какой-то процесс тогда, когда пользователь не работает с моим приложением


Кафедра вычислительной техники Урюпинского университета объявляет прием студентов для обучения двум специальностям:
1. Юзер.
2. Хакер.

Создайте процедуру, которая будет вызываться при событии Application.OnIdle.

Обьявим процедуру:


 {Private declarations}
 procedure IdleEventHandler(Sender: TObject; var Done: Boolean);
 

В разделе implementation опишем поцедуру:


 procedure TForm1.IdleEventHandler(Sender: TObject; var Done: Boolean);
 begin
   {Здесь нужно указать, что именно будем делать}
   Done := false;
 end;
 

В методе Form'ы OnCreate - укажем что наша процедура вызывается на событии


 Application.OnIdle.Application.OnIdle := IdleEventHandler;
 

Событие OnIdle возникает один раз - когда приложение переходит в режим "безделья" (idle). Если в обработчике переменной Done присвоить False событие будет вызываться вновь и вновь, до тех пор пока приложение "бездельничает" и переменной Done не присвоенно значение True.




Как, зная Handle окна программы, определить имя EXE


 // Для начала определяешь какому процессу принадлежит окно:
 
 Var pProcID : ^DWORD;
 begin
  GetMem (pProcID, SizeOf (DWORD));
  GetWindowThreadProcessId (WinHandle, pProcID);
 end;
 
 // а после этого используешь TProcessEntry32 примерно так:
 
 function GetExeNameByProcID (ProcID : DWord) : String;
 var
  ContinueLoop   : BOOL;
  FSnapshotHandle : THandle;
  FProcessEntry32 : TProcessEntry32;
 begin
  FSnapshotHandle := CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0);
 
  FProcessEntry32.dwSize := Sizeof(FProcessEntry32);
 
  ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
 
  Result := '';
  while (Integer (ContinueLoop) <> 0) and (Result='') do
  begin
   if FProcessEntry32.th32ProcessID = ProcID then
    Result := FProcessEntry32.szExeFile;
   ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
  end;
 end;
 
 // Не забудь в uses добавить Tlhelp32
 




Вставить программу внутрь EXE файла

ДОРОГОЙ МОЙ МУЖ, Отправляю тебе это письмо по электронной почте, чтобы быть уверенной, что ты его прочтешь. Думаю, ты простишь мне эту небольшую хитрость, но просто я хотела держать тебя в курсе событий за два года, прошедших с того момента, как компьютер вошел в наш дом. С детьми все хорошо. Пете в прошлом месяце исполнилось 8 лет. Учится он хорошо, все считают его красивым и общительным мальчиком. У него, несомненно, есть художественные способности. На прошлой неделе он в школе на уроке рисования изобразил всю семью. У него очень хорошо получились все лица, а особенно реалистично - твой затылок. Ты должен гордиться своим сыном. Леночке в сентябре исполнилось три годика. Она очень похожа на тебя, каким ты был в ее возрасте. Леночка - очень умная девочка. Она даже помнит, что ты провел с нами весь вечер ее дня рождения. Она его с радостью вспоминает, хотя в тот день шел дождь, а ближе к вечеру отключили свет. Со мной тоже все в порядке. В прошлом году я перекрасилась в блондинку и сейчас я думаю, что этот цвет волос мне очень даже идет. Коля (я имею в виду Николая Ивановича - менеджера по персоналу) стал очень интересоваться моей карьерой. А потом он стал моим очень близким другом. Я поняла, что заниматься домашними делами не так уж и сложно. Сначала, когда я подметала пол веником, ты чихал от пыли, но сейчас я делаю это пылесосом, который тебе совершенно не мешает. В доме теперь чистота и уют. Прошлой весной мы переклеили обои, за исключением той части комнаты, где ты поставил свой компьютер, там мы не стали обои переклеивать (не хотели тебе мешать). На этом я заканчиваю письмо, так как мне пора идти: Коля, то есть Николай Иванович, пригласил всех нас на горнолыжный курорт, и мне нужно собирать чемоданы. На время нашего отъезда я наняла домработницу. Она будет готовить тебе кофе и приносить тебе еду прямо за компьютер. Думаю, что вы хорошо проведете время с компьютером, пока мы будем отсутствовать. Петенька, Леночка и я будем думать о тебе. Постарайся и ты подумать о нас, хотя бы во время перезагрузки компьютера.
Обнимаю. Твоя Маша.

1. Пишем в блокноте RC-файл, куда прописываем все нужные нам программы, например:

 ARJ EXEFILE C:\ARHIVERS\ARJ.EXE
 

2. Компилируем его в ресурс при помощи Brcc32.exe. Получаем RES- файл.

3. Далее в тексте нашей программы:


 implementation
 {$R *.DFM}
 {$R test.res} //Это наш RES-файл
 
 // Процедура для извлечения ресурса в указанный файл
 procedure ExtractRes(ResType, ResName, ResNewName : String);
 var
   Res : TResourceStream;
 begin
   Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
   Res.SavetoFile(ResNewName);
   Res.Free;
 end;
 
 procedure TForm1.BitBtn1Click(Sender: TObject);
 begin
  // Записывает в текущую папку arj.exe
  ExtractRes('EXEFILE', 'ARJ', 'ARJ.EXE');
 end;
 




Как раскрыть строки переменных окружения

Автор: Nomadic

Компьютерщик приходит к своему другу (такому же), видит - тот коврик мышовый в ванной стирает. Офигевает.
- Ты чего?
- Да вот, стал Windows 2000 ставить...
- Hу и что???
- Мышь проблевалась....

Используй вызов


 ExpandEnvironmentStrings( LPCTSTR lpSrc, LPTSTR lpDst, DWORD nSize );
 




Раскрытие пути к элементу TOutline по его индексу

Когда я писал этот код, у меня была цель по индексу TOutlineNode (который являлся результатом поиска) раскрыть его путь (т.е. раскрыть дочерние узлы, ведующие к нему), не затрагивая при это остальные узлы.

Следующая процедура в качестве параметра принимает индекс, после чего раскрывает путь к элементу с этим индексом.

Процедура подразумевает работу с объектом TOutline, имеющим имя Outline.


 var
   Outline: TOutline;
 
 procedure TSearchDlg.ExpandPathToFoundItem(const FoundItemIndex: Longint);
 {----------------------------------------------------------------------------
  Открываем путь к данному элементу (элемент определяется номером индекса).
  До корневого элемента необходимо раскрывать только родителей.
  ----------------------------------------------------------------------------}
 var
   ItemIndex: Longint;
   Found: Boolean;
   LastCh: Longint;
   Path: string;
   ItemText: string;
   SepPos: Integer;
   OldSep: string;
 begin
   {Сохраняем старый ItemSpearator}
   OldSep := Outline.ItemSeparator;
   {Устанавливаем новый ItemSeparator}
   Outline.ItemSeparator := '\';
   {Получаем полный путь к TOutlineNode и добавляем '\'.
   Это делается для упрощения последующего алгоритма}
   Path := Outline.Items[FoundItemIndex].FullPath + '\';
   {Зацикливаемся до тех пор, пока не будет достигнут конец пути}
   while Length(Path) > 0 do
   begin
     {Определяем в пути позицию первого '\'}
     SepPos := Pos('\', Path);
     {Изолируем элемент TOutlineNode}
     ItemText := Copy(Path, 1, SepPos - 1);
     {Определяем индекс TOutlineNode}
     ItemIndex := Outline.GetTextItem(ItemText);
     {Раскрываем его}
     Outline.Items[ItemIndex].Expand;
     {Вырезаем из строки раскрытый TOutlineNode}
     Path := Copy(Path, SepPos + 1, Length(Path) - SepPos + 1);
   end;
   {Восстанавливаем оригинальный ItemSeparator}
   Outline.ItemSeparator := OldSep;
 end;
 

Детали

Давайте присвоим элементу желаемый путь:

        "My Computer\Hardware\SoundCard\Base Adress"
На первом шаге возвращается приведенный выше путь. Затем изолируется подстрока "My Computer". Затем с помощью метода "GetTextItem" определяется индекс TOutlineNode "My Computer". Метод "Expand" раскрывает это дерево. Впоследствие "My Computer" вырезается из пути, и новым путем становится "Hardware\SoundCard\Base Adress".

Затем определяется индекс "Hardware", раскрывается, и снова выразается. Данная процедура повторяется до тех пор, пока не останется пути, который можно раскрыть. После чего полностью раскрывается путь передаваемой TOutlineNode.




Новые шуточки от дяди Солодовникова... в Actual Startup Pro 2.3

Автор: Hex

В последнее время начали попадаться мне проги с аспротектом, да какие-то не понятные. OEP и импорт все как обычно, а дамп зависает на перевом же от OEP call... Хрень прям какая-то...

Чтоб не долбаться OEP: 48F2FC IATRVA: 00093164 IATSize: 00000708 Думал импорт кривой - вроде правильный. Начал рассматривать где ж виснет. Виснет в глубинах инициализации при обращении к памяти типа вот так mov ecx,[edx+eax*2] Посмотрел как оно в проге упакованой обращается - обращается туда же но память там уже забита какими-то данными. Я уж начал думать что аспротект инициализацию памяти на себя забирает... Но не в этом то дело :)

Все проги, которые я смотрел с таким глюком, были на делфи и решил посмотреть, а как выглядят секции у обычных делфийских прог... И вот что сразу бросилось в глаза: третья секция(BBS) имеет Physical Size = 0! А в полученых дампах секция BBS уже существовала физически(т.е. Physical Size>0) и была заполнена всяким бредом от аспротекта! Вот вам и Rebuild PE... Как же дампить чтоб на такие грабли не наступать? В принципе можно каждый раз тупо менять размер этой секции, но это чо-то не то... Дампить надо сразу после создания импорта, не доходя к OEP. Как это место найти? bpx mapviewoffile после его срабатывания в протекторе bpx getprocaddress. Есть мазохисты которые делают сразу bpx getprocaddress и жмут F5 раз 20-30... не знаю к чему так извращаться если проще отловить аспротект через mapviewoffile, т.к. внутри протектора оно сработает только 1 раз, ну и еще максимум 2 раза перед этим, если какие-нить извратные DLL догрузит из импорта. Тем более первое срабатываение bpx getprocaddress после bpx mapviewoffile - это начало создания импорта. В общем после срабатывания bpx getprocaddress жмем F12 пока не увидим вот такое:


 Call HZ - мы выйдем отсюда
 Pop ebx
 Jmp hz_UP - это джамп цикла
 Popad - сюда ставим брейкпоинт чтоб цикл пропустить
 Call XXXXXXX
 Push hz
 Inc Dword Ptr[Esp]
 Ret
 

Дальше входим в Call XXXXXXX и идем по ней пока не увидим :


 Pushad
 Push xxxxxx
 Lea EAX,[ebp-c]
 Push Dword ptr[hz]
 Call [eax] - В этом Call "эмулируется" GetVersion, GetCommandlineA и т.д.
 Popad
 

После этого Call [eax] можно брать дамп так как нравится. Дамп будет нормальный и BBS тоже будет впорядке. Импорт тоже можно восстанавливать прямо тут, не дожидаясь пока до оер дойдет. С импортом тут тоже 1 прикол оказался. Теперь даже плагин для определения эмуляций не помагает, т.к. 1 функция все равно не верно определяется. Это функция GetCommandlineA, плагин ее путает с GetVersion. Найти такую неверно определенную функцию очень просто. Восстанавливаем импорт как обычно, а потом ищем в дереве импорта Imprec функцию GetCurrentDirectoryA. Будет типа так:


 1 000931D0 kernel32.dll 0158 GetCurrentDirectoryA
 1 000931D4 kernel32.dll 01DC GetVersion
 1 000931D8 kernel32.dll 0133 FreeLibrary
 

Так вот этот GetVersion который между GetCurrentDirectoryA и FreeLibrary на самом деле GetCommandLineA. Сколько я прог смотрел везде так было.

Теперь про эмуляцию апи. Много шуму вокруг этого поднимали. Апи эмулировать нужно далеко не всегда, если проге есть закриптованные участки то обычно эти апи - это функции чтения ключевого файла. Эмулировать надо если получили дамп, а прога при заходе к примеру в About матерится о том что у нее чо-то не так по такому-то адресу... Как искать их адреса? Многи предлагаю ципляться на bpx Getmodulehandlea if (*(esp+4)==0) и потом листать куда-то... Я так ничего найти не смог поэтому нашел свой способ. При запущеной проге заходим в айс и делаем addr имя_проги, чтоб в ее адресное пространство попасть. Потом делаем поиск в памяти протектора значения imagebase нашей проги. Ну типа если imagebase = 400000 и протектор лежит в районе C00000 - D00000 делаем поиск: s C00000 l 100000 00 00 40 00 00 00 40 00 Там просто рядом с этими апи лежит 2 раза подряд Imagebase. Все. Там ниже под тем местом где найдет будут лежать адреса апи. Как эмулировать расписано в статье "Исследование защиты на основе ASProtect 1.2x" by Kola на www.reversing.net. В Actual Startup Pro 2.3 есть всего 2 апи. Одна читает ключевой файл, а другая - записывает какое-то число в секцию DATA которое потом не юзается. Т.е. тут эмулировать нечего.

P.S. Спасибо Kola за наставление на путь истинный :)




Новая фишка для любителей Asprotect

Автор: Hex

Ковырял я когда-то PowerStrip. Распаковал, все ок, только одна мелочь - не вылазит popup menu на иконки в трэе. Я и так и эдак - не нашел в чем глюк. Но вот попалась мне в руки прога Advanced Administrative tools которая пролила свет на эту тайну...

Если распаковать Aatools(там новый аспр который кусок кода в себе зажал), прога будет работать, тока где не нажмешь "Start" будет вылетать то "Invalid Pointer", то еще какой-то другой мат. Сел я разбираться и нашел вот такое интересное место:


 seg000:006FFF03 push ebp
 seg000:006FFF04 push offset loc_0_6FFF25
 seg000:006FFF09 push dword ptr fs:[eax]
 seg000:006FFF0C mov fs:[eax], esp
 seg000:006FFF0F push offset loc_0_4079F8 - Это адрес jmp GetStartupInfoA
 seg000:006FFF14 mov eax, ds:dword_0_713048
 seg000:006FFF19 call eax ; sub_0_70BD88
 ...............
 
 seg001:0070BD88 pop ebx
 seg001:0070BD89 pop eax - Это адрес jmp GetStartupInfoA
 seg001:0070BD8A mov eax, [eax+2] - сдвигаемся на 2 байта чтоб пропустить опкод jmp
 seg001:0070BD8D mov eax, [eax] - читаем адрес операнда jmp (т.е. адрес в таблице импорта)
 seg001:0070BD8F push dword ptr [eax] - сохраняем в стэк 4 байта
 seg001:0070BD91 pop dword ptr [eax] - пишем эти байты туда откуда взяли
 seg001:0070BD93 jmp ebx
 

И чо оно такое творит? Если аспротект есть то push dword ptr [eax] сохраняет в стэк первые 4 байта переходника к апи GetStartupInfoA. Дальше по команде pop dword ptr [eax] ничего страшного не произойдет, т.к. в область памяти где лежат переходники аспротекта можно писать. Но что произойдет если аспротекта нет? Команда push dword ptr [eax] сохранит в стэк первые 4 байта апи GetStartupInfoA. А потом при pop dword ptr [eax] попытается записать данные в kernel32.dll... ОС такое не прощает :) Вот так то. Как бороться? Занопить push dword ptr [eax] и pop dword ptr [eax]. Это еще не все! Для того чтобы, мы немножко еще мозги понапрягали таких мест несколько и они динамически раскриптовываются по хитрющему алгоритму "-x +x" :) Вот к примеру "раскриптовка":


 seg000:00664245 mov eax, [ebp+var_C]
 seg000:00664248 sub byte ptr ds:unk_0_711BAF[eax], 2 (побайтно -2)
 seg000:0066424F inc [ebp+var_C]
 seg000:00664252 cmp [ebp+var_C], 0Eh
 seg000:00664256 jnz short loc_0_664245
 seg000:00664258 xor eax, eax
 

Ну это понятно:


 seg000:0066425A push ebp
 seg000:0066425B push offset loc_0_66427C
 seg000:00664260 push dword ptr fs:[eax]
 seg000:00664263 mov fs:[eax], esp
 seg000:00664266 push offset loc_0_407A60
 seg000:0066426B mov eax, offset unk_0_711BB0
 seg000:00664270 call eax ; unk_0_711BB0 - тут опять проверяет импорт
 

Закриптовка:


 seg000:006642A3 mov eax, [ebp+var_C]
 seg000:006642A6 add byte ptr ds:unk_0_711BAF[eax], 2 (побайтно +2)
 seg000:006642AD inc [ebp+var_C]
 seg000:006642B0 cmp [ebp+var_C], 0Eh
 seg000:006642B4 jnz short loc_0_6642A3
 

Ну тут придется в закриптованном куске сделать не 90909090 а 92929292 :)

В PowerStrip 3.29 имеется вот такое


 seg000:0050D1A7 mov eax, offset loc_0_53F1D4
 seg000:0050D1AC
 seg000:0050D1AC loc_0_50D1AC: ; CODE XREF: seg000:0050D1B0j
 seg000:0050D1AC dec byte ptr [eax]
 seg000:0050D1AE inc eax
 seg000:0050D1AF dec ebx
 seg000:0050D1B0 jnz short loc_0_50D1AC
 

А тут - 91919191 :) Как только тут подправим - сразу и popup вылазит и "Полезные советы"...

Дыр-дыр-дыр, занавес и аплодисменты.




Исследование программы Audio Mp3 Maker v 1.10-1.13 by Wersion

Автор: Wersion

Программист коллегам:
- Сегодня на работе опять весь день @нанизмом занимался.
- Что, винды переставлял?
- Да нет... просто др#чил...

Итак, как это было. Когда-то, очень давно, я скачал эту довольно неплохую вещь для того, чтобы наделать Mp3-файлов с одного Audio-диска. У меня тогда не было ни времени, ни знаний, поэтому я решил воспользоваться Crack-сайтами. Нашедши около 7 серийных номеров к версии 1.10, я убедился, что ни один из них не работает. Позже я кое-что делал с этой программой, поменял кучу байтов,она даже стала похожей на зарегистрированную, но в конце концов выкинула мне сообщение о истечении *0-дневного срока.

И я закинул сию вещь на год. После, набравшись ума, вновь принялся за исследование.

Вот оно:

Что используем:

  • Softice 4.x & Icedump 6.x
  • Win32 Disassembler v X.X.
  • Delphi 4-6.

Начнём. Запускаем программу. Жмём Register, вводим имя и любой код. Видим сообщение "Invalid...", запоминаем. Запускаем W32Dasm. Ищем строку в String Data References. Я нашёл следующий код:


 :0040D844 53 push ebx (1)
 :0040D845 51 push ecx (2)
 :0040D846 E8AE9E0000 call 004176F9   (3)
 :0040D84B 83C408 add esp, 00000008
 :0040D84E 85C0 test eax, eax (4)
 :0040D850 6A00 push 00000000
 
 * Possible StringData Ref from Data Obj ->"Message"
 |
 :0040D852 68BC7B4400 push 00447BBC
 :0040D857 7518 jne 0040D871  (5)
 
 * Possible StringData Ref from Data Obj ->"Thank you, please restart programs"
 |
 :0040D859 68E47D4400 push 00447DE4
 :0040D85E 8BCE mov ecx, esi
 :0040D860 E889CE0100 call 0042A6EE
 :0040D865 8B16 mov edx, dword ptr [esi]
 :0040D867 8BCE mov ecx, esi
 :0040D869 FF92C4000000 call dword ptr [edx+000000C4]
 :0040D86F EB37 jmp 0040D8A8
 
 * Referenced by a (U)nconditional or (C)onditional Jump at Address:
 |:0040D857(C)
 |
 
 * Possible StringData Ref from Data Obj ->"Invalid reg code"
 |
 :0040D871 68D07D4400 push 00447DD0  (6)
 :0040D876 8BCE mov ecx, esi
 :0040D878 E871CE0100 call 0042A6EE
 :0040D87D EB29 jmp 0040D8A8
 

Итак, что же мы видим? В (1) и (2) функции передаются 2 параметра, в (3) она вызывается. В (4) мы проверяем значение EAX на равенство 0, потом в (5) прыгаем на (6) если это не так. Соответственно, начиная с (6) идёт подготовка и выдача сообщения о неверности РИ. Что мы делаем? Ставим брейкпоинт на (1), начинаем регистрироваться. Попадая в в Softice, делаем:


 d ebx
 d ecx
 

В одном из них адрес строки с нашим кодом, в другом - с реальным. Берём Icedump и копируем реальный код на диск. Вводим их там где надо и получаем зарегистрированный Audio Mp3 Maker®.

0Днак0!

Всё это только начало. Меня всё время мучали следующие вопросы: при такой защите Coxsoft (теперь они расцвели и стали MjSoft) загнулись бы на корню. Кроме того, мой код работал не только для версии 1.10, но и для 1.13. Следовательно, алгоритм его генерации не меняли никогда. Тогда трудно объяснить существование 7 неверных серийных номеров(см. начало).

Это всё значит, что код зависит от конфигурации компьютера или чего-нибудь ещё. На сем факте, не заметив оного, proudly обломались 4 команды и 3 крэкера(и все ост., кот. я не нашёл). MjSoft специально сделали такую ламерскую проверку(ост. на уровне), чтобы к ним скорее нашли с/н и оставили бы их в покое.

Наш кейген должен будет проделать всю нашу работу самостоятельно. Он станет интеллектуальным отладчиком. Берем Delphi 6 и пишем. Вот исходники:


 program main;
 
 {$APPTYPE CONSOLE} //мне так больше нравиться
 {$R KeyGen.res} //иконка
 uses
   SysUtils, Windows; //необходимый минимум
 var
   BW, BREAD: Cardinal;
   Event: DEBUG_EVENT; //отладочное событие
   XCode: array[1..255] of byte; //массив с нашим кодом
   Answer: char; //ответ пользователя
   WriteAddress_0, cAddress_0: Longint; //нужные нам адреса
   WriteAddress_1, cAddress_1: Pointer;
   NewByte: byte; //его будем писать в тело программы
   CodeString: string; //строка с кодом
   StartupInfo: TStartupInfo; //информация для запуска
   ProcessInfo: TProcessInformation; //информация о процессе
   Context: _CONTEXT; //контекст нашего процесса -- сод. значения регистров и т.д.
   i: integer; //не помню что
 
 function SeekInBuffer(Buffer: array of byte): Longint;
   //надо сначала найти нужный адрес
 var
   posit: Integer;
 begin
   Result := 0;
   for posit := 1 to 1024 do
   begin
     if Buffer[Posit] = $8B then
       //ищем наши инструкции в 16-м виде(см. начало(асм. листинг))
       if Buffer[Posit + 1] = $1B then //это инструкции до push ebx, push ecx
         if Buffer[Posit + 2] = $8B then
           if Buffer[Posit + 3] = $4D then
             if Buffer[Posit + 4] = $E8 then
               if Buffer[Posit + 5] = $53 then //$53=push ebx
                 if Buffer[Posit + 6] = $51 then //$51=push ecx
                 begin //тут, надеюсь, вс¸ ясно.
                   Result := Posit;
                   Exit;
                 end
                 else
                   Result := 0;
   end;
 
 end;
 
 function SeekBpPlace(ProcessHandle: Cardinal): Longint; //главная функция поиска
 var
   lpBaseAdr: Pointer;
   BR: Cardinal;
   StartAddr, SeekResult: Longint;
   Buffer: array[1..1030] of byte; //для прочитанных кусков программы.
 begin
   StartAddr := $401000;
   repeat
     asm //си¸ делается так, чтобы присвоить lpBaseAdr значение StartAddr
       mov eax, StartAddr //что обычными средствами Delphi сделать проблематично.
       mov lpBaseAdr, eax;
     end;
     ReadProcessMemory(ProcessHandle, lpBaseAdr, @Buffer, 1030, BR);
       //читаем кусок
     SeekResult := SeekInBuffer(Buffer); //ищем в н¸м наши инструкции
     if SeekResult = 0 then
     begin
       Inc(StartAddr, 1024); //если ничего не нашли, ид¸м дальше.
     end;
   until SeekResult <> 0; //пока поиск не закончится
   Result := SeekResult + StartAddr; //добавляем StartAddr и получаем искомое.
 end;
 //--------------------------------------------//
 begin
   Writeln('THIS PROGRAM IS FOR EDUCATIONAL USE ONLY!');
   Writeln('YOU CAN USE IT ONLY IF YOU HAVE REGISTERED COPY OF AUDIO MP3 MAKER!');
   Writeln('OTHERWISE, IT IS ILLEGAL TO USE THIS PROGRAM!');
   Writeln;
   Write('Hit <Y>,<Enter> to agree or <N>,<Enter> to disagree/exit==>');
   Read(Answer);
   if UpCase(Answer) <> 'Y' then
     ExitProcess(0);
   if not FileExists('keyinfo.key') then //чтоб нас не использовали как крэк
   begin
     Writeln('You aren''t registered user!');
     Writeln('Hit <Enter> to exit...');
     Readln;
     ExitProcess(0);
   end;
   RenameFile('keyinfo.key', 'keyinfo.key.org'); //делаем резервную копию
   Writeln('Welcome.'); //тут, надеюсь, тоже вс¸ ясно
   Writeln(#4 + ' This is keygen for Audio Mp3 Maker v. 1.10/1.11/1.12/1.13!');
   Writeln(#4 + ' Tested on Audio Mp3 Maker v. 1.10/1.13');
   Writeln(#4 + ' Author: Wersion');
   Writeln(#4 + ' E-mail: wcrkgroup2002@mail.ru');
   Writeln('What do you need to do:');
   Writeln(#4 +
     'When program will be loaded, press ''Try it'' on the nag-screen');
   Writeln(#4 + 'After that, press ''Register'' in the program''s window!');
   Writeln(#4 + 'Enter your name.');
   Writeln(#4 + 'Enter any code, for example ''any code'' :-).');
   Writeln(#4 + 'Press''OK''');
   Writeln(#4 + 'Wait.');
   Sleep(10000);
   FillChar(StartupInfo, Sizeof(StartupInfo), #0); //заполняем структуру
   StartupInfo.cb := Sizeof(StartupInfo);
   StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
   StartupInfo.wShowWindow := SW_SHOWDEFAULT;
   if not FileExists('amm.exe') then //он, конечно, нам нужен.
   begin
     Writeln(#4 +
       ' File not found (''Amm.exe'') ! Please run me in program''s directory!');
     Readln;
     ExitProcess(0);
   end;
   {Созда¸м процесс и становимся отладчиком}
   if CreateProcess(nil, PChar('Amm.exe'), nil, nil, false,
     DEBUG_ONLY_THIS_PROCESS, nil, nil, StartupInfo, ProcessInfo) then
     Writeln(#4 + 'Process created successfully...')
   else
     ExitProcess(0);
   Writeln(#4 + 'Seeking target...');
   WriteAddress_0 := SeekBpPlace(ProcessInfo.hProcess) + 6;
     //сюда надо поставить брейкпоинт
   Writeln(#4 + ' Target found at 0x' + IntToHex(WriteAddress_0, 6));
   asm //вы это уже видели
     mov eax,WriteAddress_0
     mov WriteAddress_1,eax
   end;
   //--------------------------------------------//
   repeat
     WaitForDebugEvent(Event, 0);
     if event.dwDebugEventCode = EXIT_PROCESS_DEBUG_EVENT then
     begin
       Writeln(#4 + ' Process terminated by user! Unable to get code!');
       Readln;
       ExitProcess(0);
     end;
     if FindWindow(Pchar('#32770'), Pchar('Register')) <> 0 then
       //дожд¸мся начала регистрации
     begin
       ContinueDebugEvent(Event.dwProcessId, Event.dwThreadId, DBG_CONTINUE);
       //продолжаем отладку и выходим из цикла(бесконечного, кстати). Break;
     end;
     ContinueDebugEvent(Event.dwProcessId, Event.dwThreadId, DBG_CONTINUE);
   until false;
   NewByte := $CC;
     //инструкция INT 03, воспринимается нами(отладчиком) как брейкпоинт.
   WriteProcessMemory(ProcessInfo.hProcess, WriteAddress_1, @NewByte, 1, BW);
     //устанавливаем его
   Writeln(#4 + ' Target patched...');
   Writeln(#4 + ' Breakpoint enabled...');
   repeat //ждем появления INT 03
     WaitForDebugEvent(Event, Infinite);
     Writeln(#4 + ' Waiting...');
     if event.dwDebugEventCode = EXCEPTION_DEBUG_EVENT then
       if event.Exception.ExceptionRecord.ExceptionCode = EXCEPTION_BREAKPOINT
         then
       begin
         //дождались! Writeln(#04+' Breakpoint reached...');
         NewByte := $51; //восстанавливаем старую инструкцию push ecx
         WriteProcessMemory(ProcessInfo.hProcess, WriteAddress_1, @NewByte, 1,
           BW);
         Writeln(#4 + ' Target patched again (original restored)...');
         Context.ContextFlags := CONTEXT_INTEGER;
         GetThreadContext(ProcessInfo.hThread, Context); //получаем контекст
         cAddress_0 := Context.Ecx; //нам надо значение регистра Ecx
         asm
           mov eax,cAddress_0
           mov CAddress_1,eax
         end;
         Writeln(#4 + 'Reading code...');
         ReadProcessMemory(ProcessInfo.hProcess, cAddress_1, @Xcode, 255, BREAD);
           //читаем код с мусором в буфер
         TerminateProcess(ProcessInfo.hProcess, 0); //завершаем программу
         Writeln(#4 + 'Terminating process...');
         Break; //выходим из бесконечного цикла
       end;
     ContinueDebugEvent(Event.dwProcessId, Event.dwThreadId, DBG_CONTINUE);
   until false;
   //-----------------------------------------------------//
   CodeString := '';
   for i := 1 to BREAD do //очищаем код от мусора и су¸м в строку
   begin
     if Xcode[i] <> 0 then
       CodeString := CodeString + Chr(Xcode[i])
     else
       Break;
   end;
   Writeln(#4 + ' Your code: ' + CodeString);
   Writeln(#4 + ' Your have 1 minute to copy it to clipboard.');
   Writeln(#4 + ' Enjoy!');
   Sleep(60000);
   //собственно, это вс¸.
 end.
 

Что сказать в заключение?

Что сделать кейген с чистого листа при пратически полном отсутствии документации по функциям отладки совсем не просто. А ещё, все исходники являются моей интеллектуальной собственностью и защищаются законами об авторских правах.

Created by Wersion. E-mail: wcrkgroup2002@mail.ru
Комментарии/вопросы - приветствуются.
Greats to: Iczelion, Dr. Golova. <- наставили меня на путь истинный.




Hasp защита в Базис Конструктор Мебельщик Demo

Два уровня защиты: High и... и нехай...

Это хоть и демо но в ней остался блок для работы с HASP. Естественно даже если мы тут сэмулируем Hasp - фичи не активируются, потому что их тут нет :) Но зато можно потренироваться в эмуляции HASP.

Взять это дело можно вот тут

Инструменты: Softice, Windasm, Hiew.

Сначала откроем BazMebel50.exe в windasm. Вызовы ключа тут отключены т.к. это демо, но нам для обучения нужно чтобы ключ таки вызывался. Как это сделать? Сначала найдем процедуру вызова HASP ключа. Искать ее очень просто, либо по вызову FreeEnvironmentStringsA либо по команде cmp bh, 32 (этой командой проверяется номер функции перед вызовом HASP ключа). Итак сделаем поиск строки "cmp bh, 32" и окажемся вот в таком месте:


 :004C68D0 55 push ebp
 :004C68D1 8BEC mov ebp, esp
 :004C68D3 50 push eax
 :004C68D4 53 push ebx
 :004C68D5 51 push ecx
 :004C68D6 52 push edx
 :004C68D7 57 push edi
 :004C68D8 56 push esi
 :004C68D9 8B7514 mov esi, dword ptr [ebp+14]
 :004C68DC 8B3E mov edi, dword ptr [esi]
 :004C68DE BB00000000 mov ebx, 00000000
 :004C68E3 8BD8 mov ebx, eax
 :004C68E5 8AFB mov bh, bl
 :004C68E7 B300 mov bl, 00
 :004C68E9 03D9 add ebx, ecx
 :004C68EB 8BC2 mov eax, edx
 :004C68ED 8B4D1C mov ecx, dword ptr [ebp+1C]
 :004C68F0 8B5518 mov edx, dword ptr [ebp+18]
 :004C68F3 80FF32 cmp bh, 32 - проверяем чтобы номер функции был < 50
 :004C68F6 7205 jb 004C68FD
 :004C68F8 8B7508 mov esi, dword ptr [ebp+08]
 :004C68FB 8B06 mov eax, dword ptr [esi]
 :004C68FD 8B7510 mov esi, dword ptr [ebp+10]
 :004C6900 8B36 mov esi, dword ptr [esi]
 :004C6902 55 push ebp
 :004C6903 E82E090000 call 004C7236 - Тут вызов HASP
 :004C6908 5D pop ebp
 :004C6909 8B7D14 mov edi, dword ptr [ebp+14]
 :004C690C 8907 mov dword ptr [edi], eax - первое возвращаемое значение
 :004C690E 8B7D10 mov edi, dword ptr [ebp+10]
 :004C6911 891F mov dword ptr [edi], ebx - второе возвращаемое значение
 :004C6913 8B7D0C mov edi, dword ptr [ebp+0C]
 :004C6916 890F mov dword ptr [edi], ecx - третье возвращаемое значение
 :004C6918 8B7D08 mov edi, dword ptr [ebp+08]
 :004C691B 8917 mov dword ptr [edi], edx - четвертое возвращаемое значение
 :004C691D 5E pop esi
 :004C691E 5F pop edi
 :004C691F 5A pop edx
 :004C6920 59 pop ecx
 :004C6921 5B pop ebx
 :004C6922 58 pop eax
 :004C6923 5D pop ebp
 :004C6924 C21800 ret 0018
 

Это стандартная процедура вызова HASP ключа. Теперь надо сделать чтобы она таки вызывалась. Смотрим откуда вызывается эта процедура:


 * Referenced by a CALL at Address:
 :004B2C40
 А там:
 * Referenced by a CALL at Addresses:
 |:004B2D48 , :004BF452 , :004BF4D9 , :004BF568 , :004BF5E2
 |:004BF643 , :005846C8 , :00584703 , :00584781
 

ну глянем чо там к примеру по адресу 4B2D48:


 :004B2D07 803D3D90650000 cmp byte ptr [0065903D], 00
 :004B2D0E 753D jne 004B2D4D - тут перепрыгивает вызов HASP
 .........
 
 :004B2D43 B802000000 mov eax, 00000002
 :004B2D48 E8C3FEFFFF call 004B2C10 - отсюда мы вышли
 
 * Referenced by a (U)nconditional or (C)onditional Jump at Address:
 |:004B2D0E(C)
 

Вот, это уже хорошо. Теперь делаем запускаем прогу через symbol loader и bpm 0065903D. Оно сработает вот тут:


 :004BE99B C6053C90650000 mov byte ptr [0065903C], 00
 :004BE9A2 C6053D90650001 mov byte ptr [0065903D], 01 - Вот оно!
 :004BE9A9 C605F468650001 mov byte ptr [006568F4], 01
 :004BE9B0 C6054D7E650001 mov byte ptr [00657E4D], 01
 :004BE9B7 C6054E7E650000 mov byte ptr [00657E4E], 00
 

Если сделать чтоб по адресу 0065903D записалось 0, то все вызовы HASP ключа оживут. Оживляем :)

Ставим bpx 4C6903(процедура вызова HASP) Теперь берем бумажку и записываем все вызовы и ихние параметры. Перед тем как записывать параметры, не плохо бы почитать вот тут описалово функций Hasp.

Будут вызваны 1, 5 и 3. Функции 1 и 5 и идут без параметров, а вот третьей передается di=0e. Больше вызовов естественно нету т.к. вызов хаспа не дал правильных результатов. Давайте ка глянем а чо ж ему надо чтоб он поверил что ключ присутствует. Вопервых функция 1 должна вернуть 1 в ax, потом после 5-й функции у нас тут проверяется только чтоб в ax не было 0. А вот с третьей функцией сложнее. Поставим bpm на все четыре возвращаемые значения. Bpm сработает тут:


 :004BF576 8B55F4 mov edx, dword ptr [ebp-0C]
 :004BF579 89500C mov dword ptr [eax+0C], edx
 :004BF57C 817DF44D350000 cmp dword ptr [ebp-0C], 0000354D
 :004BF583 7407 je 004BF58C
 

Хе-хе, третий возвращаемый параметр должен быть равен 354D. Т.е. в [ebp-0C] должно быть 4D 35 00 00 (354D). Правим память и смотрим дальше. Теперь, когда мы сэмулировали правильный ответ, прога будет пробовать снова обращаться к HASP.

Опять функция 3 с параметром di=10 Проверка возвращаемого значения тут:


 :004BF5E7 817DF452350000 cmp dword ptr [ebp-0C], 00003552
 :004BF5EE 750A jne 004BF5FA
 

И еще раз функция 3 с параметром di=11 Проверка возвращаемого значения тут:


 :004BF648 817DF449350000 cmp dword ptr [ebp-0C], 00003549
 :004BF64F 750A jne 004BF65B
 

Дальше идет вызов функции 2 с параметром ax=1B

Но тут чуть извратнее. Параметры копируются:


 :004B2D4D A194846500 mov eax, dword ptr [00658494]
 :004B2D52 8B80C4070000 mov eax, dword ptr [eax+000007C4]
 :004B2D58 8B55F0 mov edx, dword ptr [ebp-10]
 :004B2D5B 89500C mov dword ptr [eax+0C], edx - первый
 :004B2D5E A194846500 mov eax, dword ptr [00658494]
 :004B2D63 8B80C8070000 mov eax, dword ptr [eax+000007C8]
 :004B2D69 8B55EC mov edx, dword ptr [ebp-14]
 :004B2D6C 89500C mov dword ptr [eax+0C], edx - второй
 :004B2D6F A194846500 mov eax, dword ptr [00658494]
 :004B2D74 8B80CC070000 mov eax, dword ptr [eax+000007CC]
 :004B2D7A 8B55E8 mov edx, dword ptr [ebp-18]
 :004B2D7D 89500C mov dword ptr [eax+0C], edx - третий
 :004B2D80 A194846500 mov eax, dword ptr [00658494]
 :004B2D85 8B80D0070000 mov eax, dword ptr [eax+000007D0]
 :004B2D8B 8B55E4 mov edx, dword ptr [ebp-1C]
 :004B2D8E 89500C mov dword ptr [eax+0C], edx - четвертый
 

Ну естественно нужно ставить bpm на dword ptr [eax+0C]. Только вот один облом, они почему то не срабатывают :( А глюк такой вылазит вот тут:


 :004B4552 803DF468650000 cmp byte ptr [006568F4], 00 - тут у нас 1
 :004B4559 7416 je 004B4571 - тут нет прыжка
 :004B455B C6053D90650001 mov byte ptr [0065903D], 01 - единица означает что hasp нету.
 

А как жеж в [006568F4] появляется единица? А очень просто! Поставив bpm 006568F4 мы увидим:


 :004BE99B C6053C90650000 mov byte ptr [0065903C], 00
 :004BE9A2 C6053D90650000 mov byte ptr [0065903D], 00 - это мы Hasp включили
 :004BE9A9 C605F468650001 mov byte ptr [006568F4], 01 - Вот оно!
 :004BE9B0 C6054D7E650001 mov byte ptr [00657E4D], 01
 :004BE9B7 C6054E7E650000 mov byte ptr [00657E4E], 00
 

Упс :) Оказывается для активации ключа надо еще 1 байт поправить. Поправив вот это место возвращаемся к нашим брейкпоинтам, которые мы ставили чтобы понять куда уходят значения после функции 2 с параметром ax=1B. Теперь они таки сработают. Вот тут:


 :004B459B A194846500 mov eax, dword ptr [00658494]
 :004B45A0 8B80C4070000 mov eax, dword ptr [eax+000007C4]
 :004B45A6 81780C76F00000 cmp dword ptr [eax+0C], 0000F076 - правильное значение 1
 :004B45AD 753C jne 004B45EB
 :004B45AF A194846500 mov eax, dword ptr [00658494]
 :004B45B4 8B80C8070000 mov eax, dword ptr [eax+000007C8]
 :004B45BA 81780C77DF0000 cmp dword ptr [eax+0C], 0000DF77 - правильное значение 2
 :004B45C1 7528 jne 004B45EB
 :004B45C3 A194846500 mov eax, dword ptr [00658494]
 :004B45C8 8B80CC070000 mov eax, dword ptr [eax+000007CC]
 :004B45CE 81780CFFC50000 cmp dword ptr [eax+0C], 0000C5FF - правильное значение 3
 :004B45D5 7514 jne 004B45EB
 :004B45D7 A194846500 mov eax, dword ptr [00658494]
 :004B45DC 8B80D0070000 mov eax, dword ptr [eax+000007D0]
 :004B45E2 81780CADEA0000 cmp dword ptr [eax+0C], 0000EAAD - правильное значение 4
 :004B45E9 7404 je 004B45EF
 

Вот они все 4 параметра которые нужно вернуть. Правим их в памяти. Вооот ... и прога запускается без надписей "Demo". Активируется кнопка сохранить на панели инструментов и в About уже горит что полная. Но если зайти в меню "файл" или пощелкать по вкладкам выбора примитвовов то происходит еще 1 вызов функции 2 на этот раз с параметром ax=25. Опять ставим брейкпоинты и видим что проверка происходит тут:


 :004B4654 A194846500 mov eax, dword ptr [00658494]
 :004B4659 8B80C4070000 mov eax, dword ptr [eax+000007C4]
 :004B465F 81780C1ACC0000 cmp dword ptr [eax+0C], 0000CC1A
 :004B4666 753C jne 004B46A4
 :004B4668 A194846500 mov eax, dword ptr [00658494]
 :004B466D 8B80C8070000 mov eax, dword ptr [eax+000007C8]
 :004B4673 81780C8EF10000 cmp dword ptr [eax+0C], 0000F18E
 :004B467A 7528 jne 004B46A4
 :004B467C A194846500 mov eax, dword ptr [00658494]
 :004B4681 8B80CC070000 mov eax, dword ptr [eax+000007CC]
 :004B4687 81780C6AA30000 cmp dword ptr [eax+0C], 0000A36A
 :004B468E 7514 jne 004B46A4
 :004B4690 A194846500 mov eax, dword ptr [00658494]
 :004B4695 8B80D0070000 mov eax, dword ptr [eax+000007D0]
 :004B469B 81780CFBD30000 cmp dword ptr [eax+0C], 0000D3FB
 :004B46A2 7404 je 004B46A8
 

Ну вроде все. Можно писать эмулятор. Данные есть. Т.е. надо написать программу которая обрабатывает 3 регистра и возвращает правильные цифры в регистры ax, bx, cx и dx

Т.е. будет чо-то типа


 cmp bh,1 - вызвали функцию 1?
 jnz дальше
 mov ax,1
 jmp выход
 
 дальше:
 cmp bh,2 - вызвали функцию 2?
 jnz еще_дальше
 cmp ax, 1b
 jnz не1b
 mov ax,F076
 mov bx,DF77
 mov cx,C5FF
 mov dx,EAAD
 jmp выход
 
 не1b:
 cmp ax, 25
 jnz выход
 mov ax,CC1A
 mov bx,F18E
 mov cx,A36A
 mov dx,D3FB
 jmp выход
 
 еще_дальше:
 cmp bh,3 - вызвали функцию 3?
 jnz даль_голубая
 cmp di, 0e
 jnz не_0е
 mov cx,354d
 jmp выход
 
 не_0е:
 cmp di, 10
 jnz не_10
 mov cx,3552
 jmp выход
 
 не_10:
 cmp di, 11
 jnz выход
 mov cx,3549
 jmp выход
 
 даль_голубая:
 cmp bh,5 - вызвали функцию 5?
 jnz выход
 mov ax,1
 
 выход:
 ret
 

И вот такую процедурку мы подсовываем вместо вызова HASP ключа. Это называется громким словом "Эмулятор" :)

Эх, жалко что демка :( Но я не думаю что в полноценной версии защита сильно отличается :)

P.S. Напоминаю! Это тока демка т.е. в ней нет кусков кода для сохранения и т.д. поэтому после эмуляции будет тока свтетится что она полноценная. Я эту прогу брал чисто для примера, чтоб показать процесс эмуляции.




Blowfish Advanced простая защита - простой взлом

Автор: Zet

Едут бизнecмeн и пpoгpaммиcт в такси. Bдpyг мaшинa лoмaeтcя. Бизнecмeн xвaтaeт тeлeфoн, нaчинaeт звoнить, pвёт вoлcы, кpичит, чтo вcё пpoпaлo... Boдилa гoвopит:
- Лaднo, нe opи сейчас пoчиним...
И идёт чинить. Пpoгpaммиcт:
- Cлyшaйтe, мyжики, мoжeт выйдeм-вoйдём, oнa и пoeдeт?

В этой статье дан общий подход к исследованию целого ряда программ, примитивно защищенных, но, почему-то создающий трудность для начинающих. Относится это к таким программам, как WinZip, WinRar и т.д. (Комментарий Bad_guy: насчёт WinRAR старше 2.5 не соглашусь)

Введение

Довольно давно я достал программу Blowfish Advanced 97. Мне она кажется достаточно интересной, т.к. предлагает пять различных алгоритмов шифрования, возожность подключения модулей, реализующих другие методы шифрования, а также возможность удалени конфиденциальной информации с винчестера различными методами, обеспечивающими невозможность восстановления.

Необходимые инструменты

  • SoftICE 3.23 for Win95
  • Любой шестнадцатиричный редактор файлов
  • WDasm 8.x

Статья

Перед тем как начать исследовать программу, следует поставить себе цель. Надо знать, на что обратить внимание при исследовании, попытаться представить возможный в данном случае метод защиты. Лично я, перед тем как начать взлом программы, всегда читаю файлы типа REGISTER.TXT, или соответствующий раздел хэлпа. Там можно узнать много интересного - сколько программа стоит (цена позволяет оценить качество защиты - хорошая защита бывает в основном у очень дорогих программ, и у программ дешевых, но написанных умельцами, которые о защите кое-что знают). Далее, можно узнать срок, в течении которого программа будет работать нормально, и что произойдет, если срок истечет. Можно найти там и последовательность действий, которые необходимо совершить для активизации процедуры регистрации(если это не очевидно). Вообще, перед тем как взламывать, надо собрать побольше информации об обьекте взлома. В большинстве случаев эта информация может оказаться полезной на начальном этапе взлома.

Теперь о нашей программе. В хэлпе к ней сказано, что при оплате вы получите ключ, "отпирающий" вашу программу, позволяя вводить пароли, длинной более пяти символов. Ключ. Наверное, не очень то просто вычислить ключ. А может и просто. Не знаю. Как известно, я пишу статьи для кракеров. Так что достаточно будет найти нужное место в программе и поменять в ней пару бит. Этого достаточно.

Теперь главное - найти место в программе, где надо менять. Если кто хочет, может попробовать взломат эту программу с помощью SoftICE. Я решил, что этот подход безперспективный, и дизассемблировал файл BFA97.EXE. Прежде чем читать дальше, сделайте тоже самое. Пока прочитаете - может и получите листинг.

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

Если этот ценный артефакт уже у вас, то найдите в нем код, ссылающийся на строку типа 'Registered Release' или, что равнозначно, 'Unregistered Evaluation copy'. За несколько байтов до этого места, стоит пресловутое


 call 12345678
 test al,al
 jz UNREGISTERED!!!!
 

Вот этот самый call мы и будем анализировать! Для этого запомните адрес(в данном случае 12345678), и загрузив программу, в SoftICE поставьте Bpx на этот адрес. При некоторых ваших действиях, будет всплывать SoftICE. Посмотрите! Вы в процедуре проверки (правда, неизвестно чего, но это не важно)! Теперь ваша задача - найти код, который некоторым образом обнуляет/не обнуляет регистр AL. Хорошо, если вы знаете ассемблер. Очень, очень хорошо... Для тех, кто не знает, скажу сразу - ищите (в данном случае, в других программах может быть по другому) команду SETZ AL (ее код 0F 94 C0). Эта команда обнуляет AL при установленном флаге ZF. Разбирайтесь сами в том, что происходит в процедуре. Скажу, что я решил задачу, поменяв 0F 94 C0 на 0F 95 C0. Итого - изменили 1 бит в программе.

Обратите внимание на занятную метаморфозу. До взлома (по крайней мере у меня) программа сообщала, что она Registered Release и не давала создавать паролей и ключей длиннее пяти байт. После взлома обнаружил, что лишился моего Registered Release и стал обладателем Unregistered Evaluation copy, но с возможностью шифровать с ключом в 16 Кбит и длиннющими паролями. Странно? Может не все так просто, как кажется, и автор программы, не так плох в защите, как кажется? Это еще предстоит выяснить. Вам...

Примечание

Чем проще взлом, тем лучше. Для связи с автором пишите ze_tty@yahoo.com. Если вы с помощью этой статьи не смогли сделать для себя работающую версию, то пишите, посмотрю, что можно сделать еще.

Легально ли это?

Я даже не хочу напоминать (вы должны знать и помнить это сами), что данный материал публикуется только в образовательных целях. Если вы хотите использовать этот програмный продукт, то вы обязаны купить его. Если же вы не желаете покупать его, то для этого вам достаточно поискать серверах распространяющих пиратские программы. Для этого вовсе не надо понимать механизм работы программы и схему ее защиты. Ответсвенность за ваши действия несете только вы сами.




Клиент-сервер в ClubAdmin 2.3

Разговаривают два системных администратора:
-Прикинь, вчера мой знакомый сервак уронил!
-Он что - хакер?
-Нет. Он кретин!

Очередная чисто "клубная" прога :) Т.е. для управления клубом компутерным. Как обычно ничего особенно, но тут заюзан принцип "клиент-сервер" для процесса регистрации и это интересно поглядеть...

Инструменты: Softice, Ida Pro 4.x, Hex редактор.

Как я определил что тут принцип клиент-сервер? Да просто прога сетевая и ClubAdmin.exe при запуске конектится к ClubServer. И еще после того как имя и регистрационный введен он только раз копируется и больше с ним ничего не происходит, что тоже вызвало у меня подозрение. Сделав ради интереса bpx send я убедился, что код таки пересылается на ClubServer и обрабатывается там. Если посмотреть что передается в буфере send то можно видеть что-то типа:


 xx..Testinfo..nnnnnn..uuuu
 xx - какие-то 2 байта
 nnnn - введенный серийник
 uuuu - введенное имя
 

Значит "TestInfo"... посмотрим где встречается эта строка в ClubServer.exe Грузим в IDA. И видим вот такое(я тут немного от себя подписал):


 _text:00405423 push 8
 _text:00405425 push offset aTestinfo ; "TestInfo"
 _text:0040542A push edi
 _text:0040542B call sub_0_412F74
 _text:00405430 add esp, 0Ch
 _text:00405433 test eax, eax
 _text:00405435 jnz loc_0_405538
 _text:0040543B push 80h
 _text:00405440 add edi, 8
 _text:00405443 push edi
 _text:00405444 lea eax, [ebp+var_7594]
 _text:0040544A push eax
 _text:0040544B call GetDataFromPacket
 _text:00405450 add esp, 0Ch
 _text:00405453 mov esi, eax
 _text:00405455 push 80h
 _text:0040545A push esi
 _text:0040545B lea eax, [ebp+var_74CC]
 _text:00405461 push eax
 _text:00405462 call GetDataFromPacket
 _text:00405467 add esp, 0Ch
 _text:0040546A lea edx, [ebp+var_74CC]
 _text:00405470 push edx ; SN
 _text:00405471 lea ecx, [ebp+var_7594]
 _text:00405477 push ecx ; UserName
 _text:00405478 push ebx
 _text:00405479 call TestReg
 

То что я назвал TestReg:


 _text:00404164 push esi
 _text:00404165 push ebx
 _text:00404166 call sub_0_40B7BC ; CheckSerial
 _text:0040416B add esp, 8
 _text:0040416E cmp eax, 4
 _text:00404171 jg short loc_0_40417A
 _text:00404173 xor eax, eax
 _text:00404175 jmp loc_0_40423B
 

CheckSerial:


 _text:0040B7BC push ebp
 _text:0040B7BD mov ebp, esp
 _text:0040B7BF add esp, 0FFFFFF60h
 _text:0040B7C5 push ebx
 _text:0040B7C6 push esi
 _text:0040B7C7 mov esi, offset aNone ; "none"
 _text:0040B7CC lea eax, [ebp+var_18]
 _text:0040B7CF push eax ; int
 _text:0040B7D0 mov edx, [ebp+lpString]
 _text:0040B7D3 push edx ; lpString
 _text:0040B7D4 call CheckLength - тут проверяет длину серийного номера. Длина кода = 20 символов.
 _text:0040B7D9 add esp, 8
 _text:0040B7DC test al, al
 _text:0040B7DE jnz short loc_0_40B7EA - если ок то прыгнет
 _text:0040B7E0 mov eax, 4 - это число компов в демо режиме.
 _text:0040B7E5 jmp loc_0_40BA19
 

Дальше оно шифрует серийник и идет по кускам кода, которые определяют тип лицензии. Там идет типа:


 _text:0040B877 call sub_0_412F74 - проверка зашифрованного серийника
 _text:0040B87C add esp, 0Ch
 _text:0040B87F test eax, eax
 _text:0040B881 jz short loc_0_40B88D - если тут не прыгнет то дальше сразу демо режим.
 _text:0040B883 mov eax, 4 - количество компов...
 _text:0040B888 jmp loc_0_40BA19
 ..............
 _text:0040B8CF call sub_0_412F74
 _text:0040B8D4 add esp, 0Ch
 _text:0040B8D7 test eax, eax
 _text:0040B8D9 jnz short loc_0_40B8E5
 _text:0040B8DB mov eax, 0Ah - количество компов...
 _text:0040B8E0 jmp loc_0_40BA19
 
 и т.д. мне понравился вот этот вариант:
 _text:0040B97E call sub_0_412F74
 _text:0040B983 add esp, 0Ch
 _text:0040B986 test eax, eax
 _text:0040B988 jnz short loc_0_40B994
 _text:0040B98A mov eax, 64h - 100 компов :)
 _text:0040B98F jmp loc_0_40BA19
 

Итак что нужно чтобы управлять 100 компами? Нужно сделать чтоб jnz по адресу 40B988 не выполнялся, а также jz по адресу 40B881 выполнялся всегда. После таких модификаций можно заходить ClubAdmin.exe и регить себя на любое имя, главное чтоб номер имел длину 20 символов.




Корсары проклятье дальних морей - Пример взлома CD

Автор: Fess

Target: Корсары проклятье дальних морей

Tools:

  • Немного мозгов
  • Win32Dasm 8.93
  • Любой файловый менеджер
  • Hex-редактор

Пролог

Вступление:

Наконец-то заработал CD-ROM!!! Дали мне тут гамес Корсары от известной в России фирмы 1С. Насколько мне известно игры от этой фирмы за- щитой не блещут. Установилась хорошо, вот только без кампакта работать не хотела. Что ж придется помочь. А заодно вот решил накатать тьюториал, как иллюстрация к другому моему тьюториалу от 31.05.2002 с названием "Практи- чески все о взломе CD". Найти можно на vallkor.chat.ru.

Что за прога:

Вполне приличный гамес типа RPG. Минимальные системные требования: PII-233МГц, 64 Мб оперативки, 3D-видео карта, 800 Метров на ЖД, Win9x.

Начало

Первое, что надо сделать это проверить, каким методом гамес проверяет наличие CD. Вариантов 3: использует функцию GetDriveTypeA, пишет букву CD в файл, другим способом. Первые два наиболее популярны. Проверяется это так, заходим в "Система" Панели управления и изменяем имя вашего CD-ROM'a на любое другое. У меня был K: я заменил на Z:. Перезагружаем. И запуска- ем гамес, компакт с игрой, оставив в лотке CD-ROM'a. Заработала. Значит тип защиты (скорее всего номер 1).

Взлом

Засовываем запускающийся файл engine.exe в Win32Dasm и ищем там, в разделе Импорта строку KERNEL32.DLL GetDriveTypeA. К сожалению, ее там не оказалось. Стукните рукой по CD-ROM'у может заработает. И тут нам приходит в голову, что если ее тут нет, значит она может быть где- нибудь еще, например, в динамической библиотеке (dll). Запускаем любой фаловый менеджер (я предпочитаю клоны DosNavigator'a, в частности NDN). Заходим в каталог куда мы установили Корсары нажимаем Alt-F7. Выбираем такие опции: Поиск в текущем каталоге, Рекруссивный поиск. В Имя файлов пишем *.dll в Содержащий текст пишем GetDriveTypeA. И запускаем, если все сделано правильно находится один файл core.dll, его и дизассемблируем.

Опять смотрим в таблицу импорта. Нашлась строка, нажимаем на нее два раза и вываливаемся тут.


 :1000C86A 8D6C1C18      lea ebp, dword ptr [esp+ebx+18]
 :1000C86E 55            push ebp
 
 * Reference To: KERNEL32.GetDriveTypeA, Ord:0104h
                           |
 :1000C86F FF1534700110  Call dword ptr [10017034]
 :1000C875 83F805        cmp eax, 00000005
 :1000C878 7579          jne 1000C8F3
 :1000C87A 8BFD          mov edi, ebp
 

Ага, это почти 100% то, что нам нужно, если посмотреть в моей общей статье (см. выше) вид функции GetDriveTypeA, то можно узнать, что она во- звращает в eax 3, если имя ЖД и 5, если имя CD. То есть здесь нам надо просто заменить по адресу в файле C877 число 05 на 03.

Посмотрим дальше, может еще что-нибудь есть.


 :1000C8DC 895704        mov dword ptr [edi+04], edx
 :1000C8DF 894708        mov dword ptr [edi+08], eax
 
 * Reference To: KERNEL32.CreateFileA, Ord:0034h
                          |
 :1000C8E2 FF150C700110  Call dword ptr [1001700C]
 :1000C8E8 8BF0          mov esi, eax
 :1000C8EA 83FEFF        cmp esi, FFFFFFFF
 :1000C8ED 752C          jne 1000C91B
 

Да, вот еще один трюк, попытка создать файл на CD, если бы вы это про- пустили, она бы не заработала, потому что на ЖД можно создать файл. И вы- деленное сравнение, есть ни что иное как проверка создался файл или нет, т.е. если в eax (потои перешедшее в esi) возвратилось FFFFFFFF, вроде как файл не смог создаться, то все нормально. Значит здесь надо заметить в файле по адресу C8ED число 75h (jne,jnz) на EBh(jmp), т.е. на безусловный переход.

Посмотрим может еще что-нибудь есть. Да вроде нет , дальше выход из функции обеспечивающей проверку. Если вы хорошо разбираетесь в этом, то можете увидеть, что функция проверки возвращает eax=1, если проверка уда- лась и eax=0, если не удалась. Вывод: можно в самом начале функции напи- сать mov eax,1 , а затем ret. Это попробуйте сами.

Послесловие

Товарищи программисты. Это не защита это отмазка, любой начинающий сломает ее не трудясь за 3 минуты. Хотите получать деньги, делайте лучше.

Все ругательства отправлять в null
Все остальное на lomovskih@yandex.ru

P.S. Запомните все материалы публикуются только в учебных целях и автор за их использование ответственности не несет!!

P.P.S. Возможно имеют место опечатки, заранее извините!

With best wishes Fess

И да пребудет с вами великий дух bad-сектора.




Crazy Cow очень простой взлом

Автор: Fess

Российские хакеры взломали бортовой компьютер российского истребителя СУ-27. Теперь боекомплект самолёта пополняется автоматически и у летчика есть шанс выйти на следующий уровень.

Target: Crazy Cow

Tools:

  • Немного мозгов
  • Win32Dasm 8.93
  • Hex-редактор

Пролог

Вступление:

Побудила к взлому слезная прозьба моего друга Satan'a. Мол, чертов скринсавер перестал работать, помоги. Ну что ж не помочь, делать было не- чего и я взялся!

Что за прога:

Скринсавер бешеная корова. Минимальные системные требования: 486-60МГц, 8 Мб оперативки, Win9x.

Предупреждение:

Люди уже умеющие ломать: не читайте этот тьюториал, он предназначен для начинающих.

Начало

Первое, что стоит отметить везде написано, что это фривара и никакой регистрации не нужно?!!! А почему тогда пишет срок вышел? Инфы, что это бета-версия я тоже не нашел. Дизассемблируем файл Crazy Cow. scr и прис- тупим.

Взлом

Ищем в секции строк строку с надписью типа "expired", нашлась. Взлом обещает быть легким.

Смотрим этот участок кода


 * Referenced by a (U)nconditional or (C)onditional Jump at Address:
 |:00407343(C)
 |
 :004073DB 8B0D28F94000 mov ecx, dword ptr [0040F928]
 :004073E1 83E101       and ecx, 00000001
 :004073E4 85C9         test ecx, ecx
 :004073E6 0F84B3000000 je 0040749F
 ...
 :00407457 8945EC       mov dword ptr [ebp-14], eax
 :0040745A 6A1D         push 0000001D
 
 * Possible StringData Ref from Data Obj ->"Your screensaver has expired."
                         |
 :0040745C 6870F94000   push 0040F970
 

Здесь видно, что все зависит от значения в 40F928. Посмотрим, где при- сваивают значение этой переменной. Для этого перемещаемся к началу текста, заходим в поиск и набираем 40F928 и вываливаемся здесь.


 :004072AA 56             push esi
 :004072AB E8B0EEFFFF     call 00406160
 :004072B0 85C0           test eax, eax
 :004072B2 0F85FD010000   jne 004074B5
 :004072B8 833D3005410000 cmp dword ptr [00410530], 00000000
 :004072BF 0F85F0010000   jne 004074B5
 :004072C5 A128F94000     mov eax, dword ptr [0040F928]
 :004072CA 83C001         add eax, 00000001
 :004072CD A328F94000     mov dword ptr [0040F928], eax
 

Так, так, так меня интересует процедура по адресу 406160, ведь если в eax возвращается один, то процедура не выполняется. Заходим в процедуру


 * Referenced by a CALL at Addresses:
 |:0040646E   , :004066A9   , :004069E9   , :00406EBB   , :00406FF7
 |:004072AB   , :00407782
 |
 :00406160 55             push ebp
 :00406161 8BEC           mov ebp, esp
 :00406163 833D90F8400000 cmp dword ptr [0040F890], 00000000
 :0040616A 7419           je 00406185
 :0040616C E8A3210000     call 00408314
 :00406171 F7D8           neg eax
 :00406173 1BC0           sbb eax, eax
 :00406175 40             inc eax
 :00406176 A3BC034100     mov dword ptr [004103BC], eax
 :0040617B C70590F8400000000000  mov dword ptr [0040F890], 00000000
 
 * Referenced by a (U)nconditional or (C)onditional Jump at Address:
 |:0040616A(C)
 |
 :00406185 A1BC034100     mov eax, dword ptr [004103BC]
 :0040618A 5D             pop ebp
 :0040618B C3             ret
 

Эта процедура вызывается из многих мест, возможно, что она основная, здесь и будем править. Выделенный переход, едиственное что может помешать, чтобы в eax была 1. Значит надо переход убрать или перенаправить, я пред- почел второе. Перенаправляем переход на 406171. Для этого высчитываем, что должно стоять вместо 19h, смотрите у процедуры длинна байт = 5, а эта строка сразу после процедуры. Значит вместо 19h будет 05h. Заходим в файл и исправляем по адресу 616B 19h на 05h. Все.

Послесловие

Товарищи программисты. Я так и не понял смысл этого временного огра- ничения, если кто знает прошу сообщить.

Господа начинающие крэкеры. Это программа проста для взлома, так что можете смело на ней тренироваться.

Все ругательства отправлять в null
Все остальное на lomovskih@yandex.ru

P.S. Запомните все материалы публикуются только в учебных целях и автор за их использование ответственности не несет!!

P.P.S. Возможно имеют место опечатки, заранее извините!

With best wishes Fess

И да пребудет с вами великий дух bad-сектора.




Исследование кода, генерируемого Delphi

Автор: Den is Com

Сидит девочка за MAC'ом, а рядом грузин за IBM. Вдруг девочка подскакивает и говорит грузину:
- Скажи MAC.
- Ну мак.
- У меня компьютер MAC, а ты - голубой маньяк! Ла-ла-ла-ла ла-ла-ла.
Прошло еще 5 минут. Девочка говорит грузину:
- Дяденька, скажи Система 7.
- Ну Сыстэма сэм.
- У меня система семь, а ты - п#дераст совсем! Ла-ла-ла-ла ла-ла-ла.
И сказал грузин тогда девочке:
- Дэвачка, скажы АйБиЭм савмэстымый компутэр.
- Ну IBM-совместимый компьютер.
- В рот мой х@й тебя е#ал. Ла-ла-ла-ла ла-ла-ла.

Введение

На этот раз я представляю Вам сугубо теоретическое исследование, и все рассматриваемые программы написал сам. Кроме них нам понадобятся Delphi и исходный код VCL (я использовал Delphi 4.0 Client/Server Edition), а также дизассемблер IDA Pro (я пользуюсь v3.8b). Полагаю Вы понимаете Ассемблер и имеете опыт в написании программ на Delphi с применением VCL.

Delphi генерирует огромное количество мёртвого и практически одинакового кода для любого приложения, использующего VCL. Тем не менее множество приложений относительно успешно создаются на Delphi, как же бедным исследователям отделять зёрна от плевел?

Я набросал в несистематическом порядке несколько элементов управления (TEdit, TButton и TBitBtn - именно они чаще всего применяются в диалогах регистрации), и написал примерно такой непритязательный код:


 type
   TForm1 = class(TForm)
     Edit1: TEdit;
     Edit2: TEdit;
     Button1: TButton;
     Button2: TButton;
     Button3: TButton;
     BitBtn1: TBitBtn;
     BitBtn2: TBitBtn;
     BitBtn3: TBitBtn;
     procedure BitBtn1Click(Sender: TObject);
     procedure FormShow(Sender: TObject);
     procedure Button1Click(Sender: TObject);
     procedure Button2Click(Sender: TObject);
   private
     { Private declarations }
     procedure MyClickHandler(Sender: TObject);
   public
     { Public declarations }
   end;
 
 var
   Form1: TForm1;
 
 implementation
 
 {$R *.DFM}
 
 procedure TForm1.BitBtn1Click(Sender: TObject);
 begin
  MessageDlg('BitBtn1Click',mtConfirmation, [mbOk], 0);
  ModalResult := mrOk;
 end;
 
 procedure TForm1.MyClickHandler(Sender: TObject);
 begin
  MessageDlg('MyClickHandler',mtConfirmation, [mbOk], 0);
  ModalResult := mrCancel;
 end;
 
 procedure TForm1.FormShow(Sender: TObject);
 begin
  MessageDlg('FormShow',mtConfirmation, [mbOk], 0);
  BitBtn2.OnClick := MyClickHandler;
 end;
 
 procedure TForm1.Button1Click(Sender: TObject);
 var
  S: String;
 begin
  S := Trim(Edit1.Text) + Trim(Edit2.Text);
  Application.MessageBox(PChar(S),'Button1Click',IDOk);
 end;
 
 procedure TForm1.Button2Click(Sender: TObject);
 begin
  MessageDlg('Button2Click',mtConfirmation, [mbOk], 0);
  Edit1.Enabled := not Edit1.Enabled;
  Button3.Enabled := not Button3.Enabled;
 end;

Чтобы мне было легко идентифицировать мой же собственный код, я поместил в каждой функции вызов MessageDlg(). Также здесь не все обработчики назначаются во время проектирования - функция MyClickHandler() назначается обработчиком динамически при показе формы (в методе FormShow()). Компилируем, запускаем - безделица, конечно, но работает... Размер EXE-файла 329728 байт! И это буквально за пять минут! Да я - серьезный программист!

Далее неплохо было бы дизассемблировать полученный файл.

Общее замечание: строки в Delphi в бинарном виде выглядят не как во всех прочих языках - т.е. не оканчиваются нулевым символом, отчего IDA Pro не опознаёт их как строки. Вначале идёт один байт - длина, а далее - сама строка, причём её конец никак более не обозначен. Это верно для так называемых коротких строк, длина которых меньше 256 байт. К несчастью, именно такими строками пользуется механизм поддержки классов.

Надо заметить, что, несмотря на все свои достоинства, IDA Pro не справляется со всеми тонкостями программ, написанных на Delphi - утверждает, что на месте VTBL находится код, не распознаёт строк в стиле Pascal'я и прочие мелочи - так что нас выручит только её интерактивность. И, кстати, не забудьте применить файл сигнатур для VCL 4 - для моего файла IDA Pro опознала аж 2297 библиотечных функций!

Для начала посмотрим, как выглядит стартовая процедура Start() (004444A8h):


 push    ebp
  mov     ebp, esp
  add     esp, 0FFFFFFF4h
  mov     eax, offset dword_0_444398
  call    @@InitExe       ; ::`intcls'::InitExe
  mov     eax, ds:off_0_445CDC
  mov     eax, [eax]
  call    @TApplication@Initialize ; TApplication::Initialize
  mov     ecx, ds:off_0_445DAC
  mov     eax, ds:off_0_445CDC
  mov     eax, [eax]
  mov     edx, ds:off_0_443F30
  call    @TApplication@CreateForm ; TApplication::CreateForm
  mov     eax, ds:off_0_445CDC
  mov     eax, [eax]
  call    @TApplication@Run ; TApplication::Run
  call    @@Halt0         ; ::`intcls'::Halt0
 

Самым многообещающим здесь выглядит вызов метода TApplication::CreateForm(), аргументом ему передаётся некий указатель - на структуру RTTI (Run-Time Type Information, информация о типе времени исполнения) класса нашей формы TForm1. Исследуем ее.

По смещению DWORD от начала структуры RTTI расположен указатель на VTBL. Далее идут 12 нулей (возможно выравнивание по границе, а возможно эти три DWORDа тоже что-нибудь означают). А по смещению 10h в расположен указатель (DWORD) на некую рекурсивную структуру, которую я назвал список наследственности:

смещениетипописание
0BYTEзначение не выяснено
1BYTEдлина N Pascal-строки
2Stringимя класса
N+2DWORDещё один указатель на VTBL
N+6DWORDуказатель на указатель (!) предка этого класса; обычно он указывает на 4 байта дальше себя, но я не берусь этого гарантировать
N+10WORDзначение не выяснено
N+12BYTEдлина Pascal-строки
N+13Stringимя модуля, где определяется этот класс

Путешествуя по этому списку, можно с лёгкостью выяснить генеалогическое дерево класса TForm1:

TForm, файл Forms
TCustomForm, файл Forms
TScrollingWinControl, файл Forms
TWinControl, файл Controls
TControl, файл Controls
TComponent, файл Classes
TPersistent, файл Classes
TObject, файл System

У последнего указатель на предка содержит нулевое значение - видимо, означая конец списка.

Вернёмся к структуре RTTI класса TForm1. По смещению 14h находится указатель на компоненты, которыми владеет данный класс. Это все элементы списка Components во время разработки. Эта структура имеет довольно простой вид:

смещениетипописание
0WORDчисло CompCount различных классов компонентов
2DWORDуказатель на массив указателей на структуры RTTI этих классов. Первым элементом этого массива является WORD - число его элементов, далее расположены указатели на структуры RTTI.

Сразу вслед за ней идут CompCount структур, описывающих эти компоненты:

смещениетипописание
0WORDсмещение в классе, по которому находится указатель на компонент
1WORDзначение не выяснено
2WORDиндекс в массиве структур RTTI - по нему определяется класс компонента
N+2WORDдлина Pascal-строки
N+6Stringимя компонента (например, Edit1)

Самым важным здесь являются смещение на компонент во включающем классе и его тип. Запомним их для компонентов в форме TForm1:

имя компонентаcмещение в классетип компонента
Edit102C4h0 - TEdit
Edit202C8h0 - TEdit
Button102CCh1 - TButton
Button202D0h1 - TButton
Button302D4h1 - TButton
BitBtn102D8h2 - TBitBtn
BitBtn202DCh2 - TBitBtn
BitBtn302E0h2 - TBitBtn

Снова вернёмся к структуре RTTI класса TForm1. По смещению 18h находится указатель на одну из самых полезных структур - на массив обработчиков событий (но только тех, которые заданы во время проектирования!). Первым элементом этого массива идёт WORD, определяющий длину этого массива, а его элементы имеют такие поля:

смещениетипописание
0WORDтип обработчика
2DWORDуказатель на функцию-обработчик
6BYTEдлина Pascal-строки
7Stringимя функции-обработчика

Тип определяет количество и размерность аргументов. Для обработчиков OnClick он равен 13h, для OnShow 0Fh.

Не прошло и получаса, а я уже нашёл свой код. Мы рассмотрим его чуть позже (пока Вы можете назвать найденные функции как в оригинале), а сейчас продолжим рассмотрение структуры RTTI класса. По смещению 24h записывается размер класса (DWORD) - для TForm1 он составляет 02E4h байт. Сравните его с таблицей смещений компонентов. По смещению 28h находится указатель на структуру RTTI класса-предка. У объекта TObject он равен нулю. По смещению 20h находится указатель на Pascal-строку - имя класса. Я повторю всю вышеизложенную информацию в следующей таблице:

смещениетипописание
0DWORDуказатель на VTBL
412 байтзначение не выяснено
10hDWORDуказатель на список наследований
14hDWORDуказатель на компоненты, которыми владеет данный класс
18hDWORDуказатель на массив обработчиков событий
1ChDWORDзначение не выяснено
20hDWORDуказатель на Pascal-строку - имя класса
24hDWORDразмер класса
28hDWORDуказатель на структуру RTTI класса-предка данного класса

По смещению 2Ch идёт таблица методов. Порядок следования методов в ней мне не до конца ясен, однако я уверен, что в ней должны содержаться конструктор и деструктор данного класса.

Настало время рассмотреть обнаруженные нами методы подробнее. Я рассмотрю их в том порядке, в каком их расположила Delphi в массиве обработчиков событий.

BitBtn1Click


 BitBtn1Click    proc near
                 push    ebx
                 mov     ebx, eax
                 push    0
 loc_0_444149:
                 mov     cx, ds:word_0_444168
                 mov     dl, 3
                 mov     eax, offset aBitbtn1click
                 call    @MessageDlg
 loc_0_44415C:
                 mov     dword ptr [ebx+22Ch], 1
                 pop     ebx
                 retn
 BitBtn1Click    endp
 

Простой и понятный код. Подспудно выясняется, что закрытие формы осуществляется записью DWORD'а (ModalResult) по смещению 022Ch в экземпляре классе. Обратите внимание на механизм передачи параметров - по умолчанию Delphi использует соглашение вызова register - параметры передаются слева-направо, используя регистры EAX, EDX и ECX, очистку стека производит вызываемая функция. Соответственно, первый (неявный) аргумент для этой функции, представляющий собой указатель на класс, передаётся в регистре EAX.

OnFormShow


 OnFormShow      proc near
                 push    ebx
                 mov     ebx, eax
                 push    0
                 mov     cx, ds:word_0_4441F4
                 mov     dl, 3
                 mov     eax, offset aFormshow
                 call    @MessageDlg
                 mov     eax, [ebx+2DCh]
                 mov     [eax+108h], ebx
                 mov     dword ptr [eax+104h], offset MyClickHandler
                 pop     ebx
                 retn
 OnFormShow      endp
 

Здесь тоже можно увидеть кое-что интересное. Во-первых, смещение 02DCh не напоминает Вам о компоненте BitBtn2? Во-вторых, обратите внимание, что здесь присваиваются два указателя. Почему? Потому что мы присваиваем не просто указатель на функцию. Все обработчики являются "of object" - т.е. методами классов. Соответственно, присваивается сначала указатель на экземпляр класса (в данном случае Self) по смещению 0108h, а затем - указатель на нашу функцию MyClickHandler(). Замечу, что больше указатель на эту функцию не встречается. Это сильно затрудняет поиск динамически назначенных обработчиков событий. Нам может помочь только ещё одно обстоятельство - все строковые константы, используемые в функции, Delphi располагает следом за самой функцией.

Button1Click


 Button1Click    proc near
 var_10          = dword ptr -10h
 var_C           = dword ptr -0Ch
 var_8           = dword ptr -8
 var_4           = dword ptr -4
                 push    ebp
                 mov     ebp, esp	; фрейм стека для локальных переменных
                 xor     ecx, ecx
                 push    ecx
                 push    ecx
                 push    ecx
                 push    ecx   ; 4 нуля в стек
                 push    ebx
                 mov     ebx, eax	; в eax - указатель на экземпляр класса
                 xor     eax, eax
                 push    ebp
                 push    offset loc_0_4442B0
 		push 	dword ptr fs:[eax]
                 mov     fs:[eax], esp
 ...
 loc_0_4442B0:
 		jmp     @@HandleFinally
 

IDA Pro неправильно опознала аргументы функций - ведь они передаются в регистрах, а не через стек. Кроме того, здесь задействуется механизм обработки исключений. Для передачи управления при исключениях Delphi использует сегментный регистр FS - в FS:[0] помещается текущий указатель стека ESP, предыдущее же значение перед этим помещается в стек. Кроме того, в стек также помещается адрес функции - обработчика блока finally. Также обратите внимание на инициализацию четырёх локальных переменных типа DWORD нулями.


      lea     edx, [ebp+var_C]
      mov     eax, [ebx+2C8h]	; смещение 02C8h не напоминает Вам о Edit2?
      call    @TControl@GetText ; TControl::GetText
      mov     eax, [ebp+var_C]
      lea     edx, [ebp+var_8]
      call    @Trim
      mov     eax, [ebp+var_8]
      push    eax
      lea     edx, [ebp+var_C]
      mov     eax, [ebx+2C4h]	; а 02C4h - о Edit1?
      call    @TControl@GetText ; TControl::GetText
      mov     eax, [ebp+var_C]
      lea     edx, [ebp+var_10]
      call    @Trim
      mov     edx, [ebp+var_10]
      lea     eax, [ebp+var_4]
      pop     ecx
      call    @@LStrCat3      ; ::'intcls'::LStrCat3
      push    1
      mov     eax, [ebp+var_4]
      call    @@LStrToPChar   ; ::'intcls'::LStrToPChar
      mov     edx, eax
      mov     ecx, offset aButton1click
      mov     eax, ds:off_0_445CDC
      mov     eax, [eax]
      call    @TApplication@MessageBox ; TApplication::MessageBox
 

В общем-то, в этом коде нет ничего примечательного, но можно выяснить, что по адресу 00445CDCh находится указатель на экземпляр класса Application.


                 xor     eax, eax
                 pop     edx
                 pop     ecx
                 pop     ecx
                 mov     fs:[eax], edx
                 push    offset loc_0_4442B7
 
 loc_0_444292:                           ; CODE XREF: CODE:004442B5.j
                 lea     eax, [ebp+var_10]
                 call    @@LStrClr       ; ::`intcls'::LStrClr
                 lea     eax, [ebp+var_C]
                 call    @@LStrClr       ; ::`intcls'::LStrClr
                 lea     eax, [ebp+var_8]
                 mov     edx, 2
                 call    @@LStrArrayClr  ; ::`intcls'::LStrArrayClr
                 retn
 ...
 offset loc_0_4442B7:
                 pop     ebx
                 mov     esp, ebp
                 pop     ebp
                 retn
 
 

Рассмотрим восстановление стека подробнее. В стеке в настоящий момент содержится:

  • указатель на finally-функцию
  • EBP - прежнее значение стека
  • EBX
  • ECX = 0
  • ECX = 0
  • ECX = 0
  • ECX = 0
  • оригинальное значение EBP
  • адрес возврата из функции

Хотя перед этим в стек была помещена 1 - её нет в стеке. Почему? Потому что она является последним аргументом функции TApplication::MessageBox(). Но ведь у этой функции всего три аргумента, и они все передаются в регистрах - скажете Вы! Ничего подобного, Вы забыли, что всем методам классов передаётся неявно ещё один аргумент (под номером ноль) - указатель на экземпляр класса. При возврате же вызываемая функция сама производит очистку стека.

Итак, сначала извлекается предыдущее значение FS:[0], указатель на finally-функцию и прежнее значение стека, и восстанавливается значение FS:[0]. Дальше в стек помещается адрес процедуры очистки стека. После инструкции retn стек будет выглядеть так:

  • EBX
  • ECX = 0
  • ECX = 0
  • ECX = 0
  • ECX = 0
  • оригинальное значение EBP
  • адрес возврата из функции

Далее снимается оригинальное значение регистра EBX, стек восстанавливается в первоначальное состояние (которое хранилось всё время выполнения процедуры в регистре EBP). Стек сейчас выглядит так:

  • оригинальное значение EBP
  • адрес возврата функции

Восстанавливается предыдущее значение регистра EBP (указатель стека для вызывающей процедуры) и после инструкции retn мы возвращаемся в вызывающую функцию с полностью восстановленным стеком.

Button2Click


 Button2Click    proc near
                 push    ebx
                 push    esi
                 mov     ebx, eax ; в eax - указатель на экземпляр класса
                 push    0
                 mov     cx, ds:word_0_44431C
                 mov     dl, 3
                 mov     eax, offset aButton2click_0
                 call    @MessageDlg
                 mov     esi, [ebx+2C4h] ; смещение на Edit1
                 mov     eax, esi
                 mov     edx, [eax]
                 call    dword ptr [edx+50h] ; вызов TEdit::GetEnabled
                 mov     edx, eax	; результат в eax
                 xor     dl, 1		; xor boolean с 1 - его же not
                 mov     eax, esi
                 mov     ecx, [eax]
                 call    dword ptr [ecx+60h] ; вызов TEdit::SetEnabled
                 mov     esi, [ebx+2D4h] ; смещение на Button3
                 mov     eax, esi
                 mov     edx, [eax]
                 call    dword ptr [edx+50h]
                 mov     edx, eax
                 xor     dl, 1
                 mov     eax, esi
                 mov     ecx, [eax]
                 call    dword ptr [ecx+60h]
                 pop     esi
                 pop     ebx
                 retn
 Button2Click    endp
 

Эта функция инвертирует свойство Enabled поля ввода и кнопки. Свойство Enabled определено для класса TComponent (общий предок для TEdit и TButton) так:


 property Enabled: Boolean read GetEnabled write SetEnabled
   stored IsEnabledStored default True;
 

Доступ к этому свойству осуществляется через методы GetEnabled & SetEnabled, что мы и видим здесь - через индекс в VTBL.




Компьютерный зал 1.81 Demo2Full

Чем больше узнаю людей, тем больше люблю компьютеpы...

Опять демка.

Ограничение: управление не более чем 4 компутерами. Старо как мир...

Инструменты: Softice, Hiew, upx, filemon, windasm.

Автор страшно любит модульное программирование и поэтому у него вышло не одна прога, а целых 6. Да-да 6 exe файлов...

  • Computerzal.exe - прога управления клиентами
  • Setup.exe - прога настроек для Computerzal.exe
  • czclient.exe - клиент
  • clientsetup.exe - прога для настройки клиента
  • LogViewPrint.exe - прога для печати логов
  • PassClean.exe - прога для сброса забытых паролей

Две последние нас не интересуют т.к. их подозревать не в чем. А остальные могут нас кидать по-полной.

О "защите": Упакована UPX. Файлы конфигурации криптует. Данные конфигурации клиента тупо пишет в реестр не скрывая ничего. Номер машины и число клиентов заблокировано через ограниечение в элементе Spinedit (edit+updown).

Приступим...

В файле Computerzal.exe искать пока нечего, т.к. н берет данные из файла конфигурации, который генерится из setup.exe. Поэтому начнем с Setup.exe. Распаковываем. Теперь не плохо бы узнать как же задается диапозон значений для spinedit. Так как spinedit состоит из edit и updown то нужно искать разгадку именно в элементе updown, т.е. чтобы изменить число мы кликаем именно на него. Берем win32.hlp и ищем Up-Down Control Messages... Там их не много. И вот что есть интересного:


 The UDM_SETPOS message sets the current position for an up-down control
 UDM_SETPOS
 wParam = 0;
 lParam = (LPARAM) MAKELONG((short) nPos, 0);
 
 New position for the up-down control. This parameter must be in the
 range specified by the upper and lower limits for the control.
 

и вот еще:


 The UDM_SETRANGE message sets the minimum and maximum
 positions (range) for an up-down control.
 UDM_SETRANGE
 wParam = 0;
 lParam = (LPARAM) MAKELONG((short) nUpper, (short) nLower);
 
 Maximum position and minimum position for the up-down control.
 Neither position can be greater than the UD_MAXVAL value or
 less than the UD_MINVAL value. In addition, the difference
 between the two positions cannot exceed UD_MAXVAL.
 

Сообщения это хорошо... Но где жеж взять их численные значения чтоб найти, где происходит ограничивание области значений? Естественно в WWW (World Wide Wreck - всемирная свалка :).


 UDM_SETPOS 467
 UDM_SETRANGE 465
 
 Теперь дизасмим и ищем "00000465". B вот что я вижу:
 * Reference To: USER32.SendMessageA, Ord:0000h
 |
 :00402C19 8B3DDCD34100 mov edi, dword ptr [0041D3DC]
 .........
 :00402D10 8B9658030000 mov edx, dword ptr [esi+00000358]
 :00402D16 6804000100 push 00010004 ; 1 - минимум 4- максимум...
 :00402D1B 6A00 push 00000000
 :00402D1D 6865040000 push 00000465 ; UDM_SETRANGE
 :00402D22 52 push edx ; Handle
 :00402D23 FFD7 call edi ; SendMessageA
 

и еще интересный кусок:


 :00402D25 8B0D907B4200 mov ecx, dword ptr [00427B90]
 :00402D2B 6A04 push 00000004 ; хм.....
 :00402D2D 8D442448 lea eax, dword ptr [esp+48]
 :00402D31 6A01 push 00000001 ; хм.....
 :00402D33 50 push eax
 :00402D34 6A04 push 00000004 ; хм.....
 :00402D36 C744245424000000 mov [esp+54], 00000024
 :00402D3E C744245807020000 mov [esp+58], 00000207
 :00402D46 E895ECFFFF call 004019E0 - А это чо за CALL такой?
 :00402D4B 8B8E58030000 mov ecx, dword ptr [esi+00000358]
 :00402D51 25FFFF0000 and eax, 0000FFFF
 :00402D56 50 push eax
 :00402D57 6A00 push 00000000
 :00402D59 6867040000 push 00000467 ; UDM_SETPOS
 :00402D5E 51 push ecx
 :00402D5F FFD7 call edi ; SendMessageA
 

Если взять айс и посмотреть что же делает call 004019E0, то можно довольно быстро понять, что в нем происходит чтение из файла конфигурации "Числа машин в сетке". А эти числа что пушатся перед ним - это ограничения! А то мало ли ... вдруг мы чужой файл конфигурации подсунем.

Для упокоения совести ищем "00010004" ... B вот что видим:


 :00403305 8B8658030000 mov eax, dword ptr [esi+00000358]
 :0040330B 6804000100 push 00010004 - опять!
 :00403310 6A00 push 00000000
 :00403312 6865040000 push 00000465
 :00403317 50 push eax
 :00403318 FFD7 call edi
 :0040331A 8B8EE0050000 mov ecx, dword ptr [esi+000005E0]
 :00403320 B804000000 mov eax, 00000004 - еще одно ограничение
 :00403325 3BC8 cmp ecx, eax
 :00403327 7606 jbe 0040332F
 

Подправив эти push 00010004 можно выставить нунжый максимум числа машин. Осталось проверить еще одну подлость: как бы оно не проверяло перед сохранением файла конфигураци... Поставив bpx на getwindowtexta, а потом bpm на адрес прочтенной строки, можно легко определить, что идет еще одна проверка вот тут:


 :00401FC1 8B8424D8060000 mov eax, dword ptr [esp+000006D8]
 :00401FC8 C744241824000000 mov [esp+18], 00000024
 :00401FD0 83F804 cmp eax, 00000004
 :00401FD3 C744241C07020000 mov [esp+1C], 00000207
 :00401FDB C78424D800000004000000 mov dword ptr [esp+000000D8], 00000004
 :00401FE6 C78424DC0000000A000000 mov dword ptr [esp+000000DC], 0000000A
 :00401FF1 C78424E000000076000000 mov dword ptr [esp+000000E0], 00000076
 :00401FFC C78424E400000002020000 mov dword ptr [esp+000000E4], 00000202
 :00402007 C78424E80000009E020000 mov dword ptr [esp+000000E8], 0000029E
 :00402012 C78424EC0000000C030000 mov dword ptr [esp+000000EC], 0000030C
 :0040201D C78424F000000078030000 mov dword ptr [esp+000000F0], 00000378
 :00402028 C78424F4000000E7030000 mov dword ptr [esp+000000F4], 000003E7
 :00402033 760C jbe 00402041
 :00402035 B804000000 mov eax, 00000004
 

С Setup.exe все.

Теперь будет повеселее ... Computerzal.exe

Поставив bpx на createfilea нужно найти место где открывается файл "cz.cfg". Потом bpx readfile и после первого срабатывания, понажимав F12 мы окажемся тут:


 :004128C7 6A04 push 00000004 ; Где-то я это уже видел...
 :004128C9 8D442464 lea eax, dword ptr [esp+64]
 :004128CD 6A01 push 00000001 ; Где-то я это уже видел...
 :004128CF 8B8E64040000 mov ecx, dword ptr [esi+00000464]
 :004128D5 50 push eax
 :004128D6 6A04 push 00000004 ; Где-то я это уже видел...
 :004128D8 C744247024000000 mov [esp+70], 00000024
 :004128E0 C744247407020000 mov [esp+74], 00000207
 :004128E8 E833440000 call 00416D20 ; После этого call в eax лежит число машин
 :004128ED 33FF xor edi, edi
 :004128EF 68FFFF0000 push 0000FFFF
 :004128F4 8D8C24A4000000 lea ecx, dword ptr [esp+000000A4]
 :004128FB 57 push edi
 :004128FC 51 push ecx
 :004128FD 8B8E64040000 mov ecx, dword ptr [esi+00000464]
 :00412903 6852030000 push 00000352
 :00412908 8986D8010000 mov dword ptr [esi+000001D8], eax - а тут запоминаем число машин...
 

Дальше возникает умная мысль: "А что если bpm esi+000001D8 ?". Эта мысль приводит нас к тому что мы очень быстро увидим следующие ограничения:


 :004121E5 8B9ED8010000 mov ebx, dword ptr [esi+000001D8]
 :004121EB 83FB04 cmp ebx, 00000004
 :004121EE 0F83FC000000 jnb 004122F0
 

потом:


 :0040CA1F 8B2DF0534400 mov ebp, dword ptr [004453F0]
 :0040CA25 8B542414 mov edx, dword ptr [esp+14]
 :0040CA29 83FD04 cmp ebp, 00000004
 :0040CA2C 8915C4534400 mov dword ptr [004453C4], edx
 :0040CA32 7E05 jle 0040CA39
 

Прога запустилась... появились дополнительные машины! но они неактивны :((( Вот блин... Ну так это ... bpx enablewindow. После этого меня просто достало жать F5 (т.к. очень много раз срабатывало) и я решил глянуть чо ж оно там стока блокирует... и вылез тут:


 :0041212B 8B8FC8000000 mov ecx, dword ptr [edi+000000C8]
 :00412131 6A00 push 00000000 ; disable
 :00412133 03CE add ecx, esi
 :00412135 E8EC710100 call 00429326 ; enablewindow
 :0041213A 8B8FCC000000 mov ecx, dword ptr [edi+000000CC]
 :00412140 6A00 push 00000000 ; disable
 :00412142 03CE add ecx, esi
 :00412144 E8DD710100 call 00429326 ; enablewindow
 :00412149 8B8FD8000000 mov ecx, dword ptr [edi+000000D8]
 :0041214F 6A00 push 00000000 ; disable
 :00412151 03CE add ecx, esi
 :00412153 E8CE710100 call 00429326 ; enablewindow
 

и т.д.

Я поднялся на уровень вверх:


 :00411A84 6A00 push 00000000
 :00411A86 8BCE mov ecx, esi
 :00411A88 E883060000 call 00412110
 :00411A8D 8D4C241C lea ecx, dword ptr [esp+1C]
 

Это call который блокирует новые машины! Я просто перепрыгнул с адреса 411A84 (push не делал) на

411A8D и все машины стали активными. Тока вот еще два глюка... почему-то не горит "Free" и почему-то горят на некоторых машинах зеленые квадратитики как-будто они уже подключены. Закраска делается через Setbkcolor. Поэтому легко можно определить что надпись "Free" блокирует ограничение:


 :0040BF62 8B08 mov ecx, dword ptr [eax]
 :0040BF64 56 push esi
 :0040BF65 83F903 cmp ecx, 00000003 ; А тут они с 0 считают :)
 :0040BF68 57 push edi
 :0040BF69 0F8FF0020000 jg 0040C25F
 

И вот остался последний огромный глюк + последнее ограничение. Глюк с подключением клиента... Состоит он в том, что прога падает при попытке подключения клиента с номером больше 4, квадратики подключения клиента ваще никак не реагируют, прога падает при выходе... А причина всего этого, скажу я вам, это называется глюк из-за статических массивов. При попытке содния более чем 4 машин мы просто вылазим запределы выделенного пространнства и поганим другие переменные. Ну и прога в том числе тоже поганит наши данные... Будем исправлять...

Прога падает тут:


 :0040DE1D 8B4668 mov eax, dword ptr [esi+68]
 :0040DE20 53 push ebx
 :0040DE21 8B0CB8 mov ecx, dword ptr [eax+4*edi] - в edi идет номер машины
 :0040DE24 83C170 add ecx, 00000070
 :0040DE27 51 push ecx
 :0040DE28 8D4C2424 lea ecx, dword ptr [esp+24]
 :0040DE2C E8C79B0000 call 004179F8
 

в ecx попадает не то значение... и потом внутри call 004179F8 оно слетает окончательно...

[eax+4*edi]... Пляшут относительно eax. Eax не меняется... меняется тока индекс - это 100% работа с одномерным массивом! Чтоже у нас в EAX там у нас 4453F8. Делаем d 4453F8 и н смотрим чо там делается... будет цикл который заполнит DWORD числами массив. Длинна массива равна коичеству машин. Делаем bpr 445408 xxxxxx RW (откуда я знаю скока вы себе там машин сделаете). Я сделал именно 445408, а не 4453f8 т.к. первые 4 dword числа нас не интересуют они и так в пезопасности. А теперь будем следить, где начнутся попытки читать или писать. Начнутся они совсем рядом:


 :0040E097 8B869C000000 mov eax, dword ptr [esi+0000009C]
 :0040E09D 8818 mov byte ptr [eax], bl
 

в esi+0000009C будет лежать адрес 6-го элемента нашего массива машин... Будем разбираться откду он там берется. Вот тут он берется:


 :0040F4F8 8D95E0FEFFFF lea edx, dword ptr [ebp-120] - Вот!
 :0040F4FE 898898000000 mov dword ptr [eax+00000098], ecx
 :0040F504 8B07 mov eax, dword ptr [edi]
 :0040F506 89909C000000 mov dword ptr [eax+0000009C], edx - а тут сохраняем.
 

ebp меняется но вот вычитаемое остается константой. Все что нам нужно, это заставить программу использовать данные где-нить подальше от нашего массива. Я сделал это очень просто:


 :0040F4F8 lea edx, dword ptr [ebp-60]
 :0040F4FB nop
 :0040F4FC nop
 :0040F4FD nop
 

Теперь у меня еще аж 120h-60h=C0h места. C0h /4h = 30h = 48 машин. Прям как в зарегеной :)

После такого изменения прога больше не падает и можно управлять машинами! Остался тока глюк с индикацией подключения. Ну тут все тоже связано с EBP :) Поставив bpx на SetBkColor я нашел в чем глюк:


 :0040CDBD 8A8508544400 mov al, byte ptr [ebp+00445408]
 :0040CDC3 8A8D38544400 mov cl, byte ptr [ebp+00445438]
 

и тут:


 :0040D01C 8A8508544400 mov al, byte ptr [ebp+00445408]
 

Это естественно. Т.к. мы же сдвинули адреса переменных на C0h, а прога обращается по старым адресам.

Нужно сделать:


 :0040CDBD 8A85C8544400 mov al, byte ptr [ebp+004454С8]
 :0040CDC3 8A8DF8544400 mov cl, byte ptr [ebp+004454F8]
 :0040D01C 8A85C8544400 mov al, byte ptr [ebp+004454C8]
 

Теперь и мигает как надо :)

Клинта и сетап к нему ломайте сами. Я задолбался уже писать этот туториал...




Взлом игры под DOS - MagicLand Dizzy (Dizzy 4)

Автор: Fess

Жизнь-игра. Задумана хреново, но графика обалденная.

Target: Dizzy 4 File: magic.exe Type: DOS Game Difficulity: Easy

Tools:

  • Some Brain
  • DeGlucker( dg.com )
  • UNPack 4.11
  • Hex-editor (QView, Hiew, в DosNavigator)

Ну так приступим, сломать досовый гамес, это в основном легче легкого (95 % гамесов).

Сделаем все по плану (а не накурившись плана).

1) Проверим не запакован ли файл чем-то, для этого запускаем из коммандной строки UNPack. Строка такая: unp.exe magic.exe И мы видим...


 UNP 4.12с Executable file restore utility, written by Ben Castricum, 08/27/95
 
 processing file : MAGIC.EXE
 DOS file size   : 155382
 file-structure  : executable (EXE)
 EXE part sizes  : header 96 bytes, image 155286 bytes, overlay 0 bytes
 processed with  : PKLITE V1.15 -e
 action          : decompressing... done
 new file size   : 446215
 writing to file : MAGIC.EXE
 

О-о-о, да он запакован PKLITE V1.15. Но теперь все Ок - он распакован в файл magic.exe. Запускаем эту строку еще раз и видим, что больше паковщиков на файле не висит.

2) Теперь все просто. Выходим в ДОС, запускаем dg.com с параметром magic.exe. И вываливаемся в окне ДеГлюкера нажимаем кнопочку F9, т.е запустим программу.

3) Найдем где можно запороть жизнь, но не запорем ее (в этой игре это можно сделать справа, у привидений). Нажимаем Pause - по этой кнопке вызывается ДеГлюкер. Смотрим состояние регистра DS, запоминаем. У меня DS=1C7A. Переходим с помощью Tab к окну кодов (в самом внизу). Нажимаем Alt-S. Нас просят указать сегмент и смещение для начала дампа. Пишем DS:0, нажимаем Enter. Вводим длинну дампа FFFF, нажимаем Enter. Вводим имя файла: кол-во_оставшихся_жизней.dmp (3.dmp). Нажимаем F9.

4) Повторяем пункт 3, раза три четыре, только запарывая жизни. И состояние регистра DS должно быть одинаковым, как в 1 раз.

5) Нажимаем Pause. Нажимаем Alt-X, т.е выходим из программы. Теперь у нас появились файлы 3.dmp, 2.dmp, 1.dmp.

6) Запускаем ДОСовую программу fc.exe такими строками:


 fc 3.dmp 2.dmp /b>32.res
 fc 2.dmp 1.dmp /b>21.res
 

У нас появились два файла 32.res и 21.res, заглянув в них мы увидим, что это побитовое сравнение.

7) Значит в первом файле у нас кол-во жизней изменялось с 3 на 2. Ищем строку "03 02" или, если такой не найдено можно попробовать "04 03".

У меня строка "03 02" нашлась на таких строках


 0000338B: 03 02
 00003F61: 03 02
 00003F79: 03 02
 

Теперь ищем во втором файле строку "02 01".

У меня она нашлась только на строке

Уау, рулез. Мне жутко кажется, что номера 338B одинаковы, значит по этому адресу памяти храниться запись о жизнях.

8) Опять запускаем dg.com magic.exe. Нажимаем F9. Идем туда где можно запороть жизнь. Нажимаем Pause. Переходим в окно данных (где уже были). Нажимаем Ctrl+G. Набираем ds:338B, если все хорошо, т.к. DS - тот же, и мы угадали, то по этому адресу должно находиться текущее кол-во жизней. У нас этот так и есть. Нажимаем F2 (ставим бряк на этот адрес). Затем нажимаем 1, т.к. нам надо только на запись. Запарываем жизнь, и вываливаемя в ДеГлюкере. Переходим в окно кода (основное большое окно), поднимаемся на одну команду вверх и мы видим строку.


 CS:0F40:  01068B33    add [338B],ax
 

Т.е к нашему байту с жизнями добавляется число, если взглянуть на ax увидим FFFF можно понять, что это -1. Значит надо, чтоб эта строка никогда не выполнялась, т.е. забить nop'ами (код 90).

9) Запускаем любой hex-редактор, я обычно пользуюсь встроенным в DosNavigator, там нужно нажать F3, на файле magic.exe, пару раз F4. Нажать F7. И в окне ввода hex-строки вписать 01068B33, нажать Enter. Строка найдена по адресу 1140, нажимаем Ctrl+L(повтор поиска) чтобы удостовериться, что такая строка единственная. У нас это так. Тогда вместо этих чисел вписываем 90909090 (если была бы не одна пришлось бы исправлять несколько раз). Сохраняем файл.

10) Запускаем файл magic.exe. Теряем жизнь, но ее не убрали, работает!!!!!

Вот и все, что сегодня хотел рассказать.

Статья написана исключительно в учебных целях. За любое ее использование автор ответственности не несет.

Все ругательства отправлять в null
Все остальное на lomovskih@yandex.ru

З.Ы. Возможны ошибки. Взлом игры 3 минуты. Написание статьи 50 минут, видите как старался.

With best wishes Fess




Новый ASProtect в EldoS TimelyWeb

Дождались... Радуйтесь! Вот оно :)

Новые фичи:

  • Изначально IAT загажена мусором, а помере создания импорта некоторые части этого мусора заменяются на нормальные адреса.
  • Переходники к апи теперь имеют вид похожий на Starforce.
  • Статические АПИ(ну типа у которых возвращаемое значение одинаковое) теперь имеют другой вид.
  • Часть кода первоначальной проги(от OEP до первого CALL) забирается в код аспротекта. Т.е. в дампе там 00 00 ...

Как вывод из 1) - "Iat autosearch" даст неправильные результаты; вывод из 2) и 3) - апи не определяются. Ну и фиг с ним :)

Чтоб не было мусора нужно просто забить нолями секцию импорта перед тем как аспротект начнет туда писать адреса апи. Чтобы не долбаться с восстановлением апи сделаем похитрому :) Если посидеть и подолбаться то можно поставив bpr на секцию импорта увидеть место, где аспротект подменяет адрес реальной апи на адрес своего переходника, а потом пишет адрес переходника в импорт. Вот расскрою военную тайну о том как найти такое место :) Все как обычно: bpx mapviewoffile и потом bpx getprocaddress. После первого срабатывания bpx getprocaddress жмем 3 раза F12. Видим вот такое дело:


 Mov EAX, [EBP+C]
 Push EAX
 Call xxxxxx - мы выйдем отсюда, в EAX будет адрес реального апи
 Call yyyyyy - Тут идет создание переходника и в EAX возвращается адрес переходника.
 Mov EDX, [EDI]
 Mov [EDX], EAX - записываем в IAT адрес переходника
 

Таким образом если занопить Call yyyyyy то Mov [EDX], EAX будет заполнять IAT адресами реальных апи :) Но если все вот так и бросить, то аспротект рухнет после создания импорта т.к. он блюдет целостность своего кода :) Поэтому после создания импорта надо все вернуть наместо(т.е. нопы вернуть на родину). Таким образом мы получим практически полный импорт. Будет не хватать 9 функций 2 повторяются итого 7. Эти функции можно легко определить вручную(также как Kola делал), А.С. не сильно их изменил.

Вот они:


 1 0022D244 kernel32.dll 01A3 GetProcAddress *
 1 0022D248 kernel32.dll 018D GetModuleHandleA
 1 0022D258 kernel32.dll 0149 GetCommandLineA
 1 0022D3B0 kernel32.dll 023E LockResource
 1 0022D3F8 kernel32.dll 01DC GetVersion
 1 0022D418 kernel32.dll 01A3 GetProcAddress *
 1 0022D420 kernel32.dll 018D GetModuleHandleA
 1 0022D44C kernel32.dll 015B GetCurrentProcessId
 1 0022D45C kernel32.dll 0135 FreeResource
 

* - ну почти, тут как обычно есть добавка для получении имени юзера и дней триала. OEP находим как обычно через BPR, тока тут оно сработает не перед прыжком на oep, а на тех байтах которые аспр спер у проги :)

Типа так:


 015F:00EA66A3 CALL 00EA66A8 - тут bpr сработает.
 015F:00EA66A8 POP EBP
 015F:00EA66A9 SUB EBP,00496FBD
 015F:00EA66AF LEA ECX,[EBP+00496FD1]
 015F:00EA66B5 ADD ECX,EBX
 015F:00EA66B7 MOV [ECX+01],EAX
 015F:00EA66BA JMP 00EA66BC
 015F:00EA66BC PUSH EBP *
 015F:00EA66BD MOV EBP,ESP *
 015F:00EA66BF ADD ESP,-0C *
 015F:00EA66C2 PUSH EBX *
 015F:00EA66C3 PUSH ESI *
 015F:00EA66C4 PUSH EDI *
 015F:00EA66C5 MOV EAX,0062167C *
 015F:00EA66CA PUSH 90909090 - это выход к OEP, пока что тут NOP
 015F:00EA66CF RET
 

* - то что должно быть в начале проги.

Входим в CALL 00EA66A8 и у JMP 00EA66BC команда PUSH 90909090 превратится в PUSH 00621E22. Т.е. OEP почти 621E22 :) Нужно только дописать байты из аспра в дамп с адреса 621E14. Там как раз ноли идут и ждут когда их вернут на родину. Т.е. OEP = 621E14. Дамп брать нужно также как я говорил в предидущих статьях.

Вот в принципе и весь новый аспр.




Взлом FineReader Pro 5.0 Try and Buy - Красота в минимуме

Автор: Fess

Вчера в США хакер российского происхождения был осужден на 99 лет за взлом всех национальных компьютерных сетей. Сегодня пришло сообщение, что по данным Центрального компьютера исполнения наказаний, он отбыл весь срок и выходит на свободу.

Target: FineReader Pro 5.0

Tools:

  • Some brains
  • Win32Dasm 8.93
  • Любой hex-редактор (я использую QView)

Пролог

Вступление:

Как-то ко мне прибегает друг истошно ругаясь и проклиная крякеров за их кривые руки. Я попросил его подробно разъяснить мне, что случилось. А вот что: Пользовался он взломанной версией FineReader Pro 5.0 с пират- ского диска "Новый реаниматор : Куча левого софта" и вдруг он прекратил работу, такое уже бывало после переустановки Windows все заработало опять. На его гневную отповедь я ему говорю: "Купил бы у Аббая и все бы работало нормально". А он мне: "Какой купил, откуды баблосы? Сделай, млин, не охо- та Windows переустанавливать, а после завтра реферат сдавать.". Ладно грю, посмотрю, но ничего не гарантирую. Дал он мне диск с этим ридером, посмот- рел... А патч то занимает почти 2 метра это архив, там три файла для него. Не будем на него опираться сделаем свой, поскольку у нас не кривые руки, и растут они у нас из нужного места. Я считаю, что Красота в минимуме, если вы считаете, что это не так, то дальше можете не читать

Что за прога:

Очень хороший (лучший) распозновальщик текста после сканирования при этом русский интерфейс и все что нужно. Размер в архиве 24 Метра.

Примечание:

Хотел бы я увидеть в чьей же команде этот саморощеный крякер, но не увидивлся когда не увидел ни имени, ни группы. Да, я бы тоже такого стыдился.

Начало

Хочу сразу сказать, что тьютор будет очень коротким, потому что взлом ЭЛЕМЕНТАРНЫЙ, чего там наизменял ломавший до меня этот продукт крякер не хочется даже смотреть.

Приступим.

Не будем откладывать в долгий ящик дизассемблируем основной файл про- граммы FineReader.exe (1,511,424 байт, если у вас размер не такой, то и смещения внутри файла будут отличаться). Я использую Win32Dasm он быстрее, чем IDA, при таком размере файла IDA будет долго торможить.

Взлом

Теперь поищем в разделе строк что-нибудь напоминающее нам о регистра- ции, первое на что натолкнулся мой взгляд была стока IDD_SH_EVALCOPY, яс- ен кот, что речь идет о Evaluation Copy. Посмотрим откуда вызывается де- лаем двойной клик на этой строке и вываливаемся здесь.


 * Referenced by a CALL at Address:
 |:004042D2
 |
 :00502D90 6AFF                    push FFFFFFFF
 :00502D92 68C5E45100              push 0051E4C5
 :00502D97 64A100000000            mov eax, dword ptr fs:[00000000]
 :00502D9D 50                      push eax
 :00502D9E 64892500000000          mov dword ptr fs:[00000000], esp
 :00502DA5 51                      push ecx
 :00502DA6 53                      push ebx
 :00502DA7 55                      push ebp
 :00502DA8 56                      push esi
 :00502DA9 57                      push edi
 :00502DAA 8BF1                    mov esi, ecx
 :00502DAC 6A00                    push 00000000
 
 * Possible StringData Ref from Data Obj ->"IDD_SH_EVALCOPY"
                                   |
 :00502DAE 6838A85600              push 0056A838
 :00502DB3 89742418                mov dword ptr [esp+18], esi
 

Видим, что это скорее всего процедура напоминающая о том, что мы не зареганы. Идем по адресу вызова процедуры, т.е. на 4042D2. И видим там следующий код...


 :004042A6 E815FD0100              call 00423FC0
 :004042AB A138E25500              mov eax, dword ptr [0055E238] <- чтение переменной
 :004042B0 33ED                    xor ebp, ebp
 :004042B2 3BC5                    cmp eax, ebp  <- сравнение чего-то
 :004042B4 0F84A2000000            je 0040435C   <- истинный переход
 :004042BA E8A1760200              call 0042B960 <- ложная процедура
 :004042BF 85C0                    test eax, eax
 :004042C1 0F8484000000            je 0040434B   <- ложный переход
 :004042C7 8D86C8000000            lea eax, dword ptr [esi+000000C8]
 :004042CD 8D4C2430                lea ecx, dword ptr [esp+30]
 :004042D1 50                      push eax
 :004042D2 E8B9EA0F00              call 00502D90 <- наша процедура
 :004042D7 8D4C2430                lea ecx, dword ptr [esp+30]
 

Смотрим. Выше нашей процедуры какой-то переход зависящий от значения в eax (если eax<>0 то переходим). Заходим в процедуру, чтобы посмотреть че- му будет равен eax и видим следующий код:


 :0042B960 33C0                    xor eax, eax
 :0042B962 C3                      ret
 

Выходит eax всегда 0 и переход никогда не выполняется. Ха-ха-ха хотели обмануть нас ложной процедурой. Значит посмотрим еще выше, на сравнение cmp eax, ebp; если посмотреть еще выше, то будет видно, что ebp равен 0. Значит переход будет осуществляться, если eax равен 0. А eax у нас берет- ся из mov eax, dword ptr [0055E238]. Теперь посмотрим где в [0055E238] пишется что нибудь. Набираем в поиске [0055E238] и ждем, прокрутим пару раз, пока не окажемся у следующего кода.


 :0040A09D 3BC6                    cmp eax, esi <- сравниваем
 :0040A09F 680080CF00              push 00CF8000
 :0040A0A4 0F95C1                  setne cl     <-если было не равно cl=1
 :0040A0A7 890D38E25500            mov dword ptr [0055E238], ecx
 

Какое-то сравнение, если не равно в ecx=1, затем пишем в нашу перемен- ную значение из ecx. А нам бы надо, чтобы ecx=0. Можно было бы, конечно, написать mov ecx,0 забив сравнение и установку, но я люблю поступать кра- сиво. Вы же не будете исправлять картину малярной кистью, лучше это сде- лать исправлять тонкой кисточкой. Вот и тут так же мне нравится исправлять минимальное число байтов. Смотрите нам надо, чтобы было равно, значит бу- дем сравнивать ecx с ecx или esi и esi.

Смотрите:

Команда Ее hex-код
cmp eax, esi 3BC6
cmp eax, eax 3BC9
cmp esi, esi 3BF6

Патч (Patch)

Программа написана на C++, значит смещение в файле равно смещению в па- мяти минус 400000. Т.е. если в памяти адрес был 40A09D, то в файле будет 40A09D-400000=0A09D.

Теперь переходим по адресу 0A09E и изменяем C6 на C9 или F6. Сохраняем. Запускаем и видим, что все ограничения исчезли. Слезы радости бегут из наших глаз и умиленное выражение не сходит с нашего лица. Делаем вывод 1 байт > 2 Мбайт. Значит мы рульные крэкеры и ценим минимум изменений для большой цели.

Послесловие

Граждане, соотечественники, если у Вас есть деньги купите этот продукт поддержите отечественного производителя. Тем более программа действительно хороша.

Крэкеры, крякеры и кракерята не выкладывайте кряки к этой программы в Инете, так мы сами в большинстве своем программисты, и ваш поступок будет выглядеть, как стрельба из пистолета себе в ногу.

Программисты фирмы Аббай. Ну что это такое, как это назвать. Не забы- вайте, что надежность защиты определяется стойкостью ее не максимально за- щищенного элемента, а минимального.

"Патч - это единство с программой в своих интересах, максимально близкое у оригиналу, но изменяющее форму и внешность содержания." (Fess)

Все ругательства отправлять в null
Все остальное на lomovskih@yandex.ru

P.S. Запомните все материалы публикуются только в учебных целях и автор за их использование ответственности не несет!!

P.P.S. Возможно имеют место опечатки, заранее извините!

With best wishes Fess

И да пребудет с вами великий дух bad-сектора.




Описание метода взлома игры FlashPoint

Автор: FreeExec

Проанализировав содержимое файла OperationFlashpoint.exe, я обнаружил, что названия сегментов не содержат не одного символа и экспортируется минимальный набор функций из модулей KERNEL32.DLL, USER32.dll: LoadLibraryA, GetProcAddress, VirtualProtect, ExitProcess, MessageBoxA. Так обычно поступают, чтобы скрыть содержимое файла от всяких дисассемблеров, т.е. когда файл запакован.

Далее запускаю SoftICE и ставлю брекпоинт на функцию определения типа устройства (bpx GetDriveTypeA). Вставляем CD и запускаем игру. Убеждаемся, что функцию вызвала игра, а не explorer.exe. Дальше жму F5 пока эту функцию не вызовут с параметром указывающий путь к CD.


 00011E2: lea eax,[ebp][-000C]
 00011E5: push eax
 00011E6: call d,[000407018] ; GetDriveTypeA
 00011EC: mov dl,al ; al=05 тут мы оказались после выхода из функ.
 00011EE: lea edi,[ebp][0FFFFF254] ; ссылка на буфер с меткой тома диска
 00011F4: or ecx,-001
 00011F7: xor eax,eax
 00011F9: repne scasb
 00011FB: not ecx
 00011FD: dec ecx ; ecx равен длине метки
 00011FE: add ecx,esi ; esi соответствует адресу на 4 байта меньше чем адрес буфера, т..е обработке подлежат последние 4 символа
 0001200: mov bl,[eax][ecx]
 0001203: add bl,dl
 0001205: mov [eax][ecx],bl ;увеличивают символы на 5(вернула функ.) позиций дальше
 0001208: inc eax
 0001209: cmp eax,004 ; всего 4 символа
 000120C: jl 000001200 -------- (2)
 000120E: mov ecx,[ebp][-0010]
 0001211: lea edx,[ebp][0FFFFFD1F]
 0001217: push ecx
 0001218: lea eax,[ebp][0FFFFF254]
 000121E: push edx
 000121F: push eax
 0001220: call 0000014C0 -------- (3) ; эта функция сравнивает два буфера переданные ей в параметрах
 0001225: add esp,00C
 0001228: test eax,eax
 000122A: jne 0000012D5 -------- (4)
 Функция (3) возвращает 0, если буфера одинаковые, и значит (4)-ый переход не осуществляется.
 :00401230 lea ecx, dword ptr [ebp-6C]
 :00401233 push ecx
 :00401234 call dword ptr [00407014] ; GetStartupInfoA
 :0040123A xor eax, eax
 :0040123C mov cl, byte ptr [ebp+eax-00000179] ; mrc32.dll
 :00401243 mov byte ptr [ebp+eax-00000DAC], cl
 :0040124A inc eax
 :0040124B test cl, cl
 :0040124D jne 0040123C
 :0040124F lea edi, dword ptr [ebp+FFFFF254]
 :00401255 or ecx, FFFFFFFF
 :00401258 xor eax, eax
 :0040125A mov dx, word ptr [004080C0]
 :00401261 repnz
 :00401262 scasb
 :00401263 or ecx, FFFFFFFF
 :00401266 mov word ptr [edi-01], dx
 :0040126A mov edi, dword ptr [ebp+10]
 :0040126D repnz
 :0040126E scasb
 :0040126F not ecx
 :00401271 sub edi, ecx
 :00401273 lea edx, dword ptr [ebp+FFFFF254]
 :00401279 mov esi, edi
 :0040127B mov ebx, ecx
 :0040127D mov edi, edx
 :0040127F or ecx, FFFFFFFF
 :00401282 repnz
 :00401283 scasb
 :00401284 mov ecx, ebx
 :00401286 dec edi
 :00401287 shr ecx, 02
 :0040128A repz
 :0040128B movsd
 :0040128C mov ecx, ebx
 :0040128E lea eax, dword ptr [ebp-28]
 :00401291 and ecx, 00000003
 :00401294 push eax ; struct _PROCESS_INFORMATION
 :00401295 repz
 :00401296 movsb
 :00401297 lea ecx, dword ptr [ebp-6C]
 :0040129A lea edx, dword ptr [ebp+FFFFF254]
 :004012A0 push ecx ; struct _STARTUPINFO
 :004012A1 push 00000000
 :004012A3 push 00000000
 :004012A5 push 00000004
 :004012A7 push 00000000
 :004012A9 push 00000000
 :004012AB push 00000000
 :004012AD push edx ; mrc32.dll
 :004012AE push 00000000
 :004012B0 call dword ptr [00407010] ; CreateProcess
 :004012B6 test eax, eax
 :004012B8 jne 00401307
 ...
 :00401307 mov ecx, dword ptr [ebp+FFFFFCF8]
 :0040130D mov edx, dword ptr [ebp-28]
 :00401310 lea eax, dword ptr [ebp-08]
 :00401313 add ecx, 00400000
 :00401319 push eax ; буфер, сюда вернут старые права
 :0040131A push 00000040 ; новый код доступа, 40h=PAGE_EXECUTE_READWRITE
 :0040131C push 00000100 ; размер
 :00401321 push ecx ; адрес региона для изменения доступа к странице памяти
 :00401322 push edx ; HANDLE hProcess из struct _PROCESS_INFORMATION
 :00401323 call dword ptr [0040700C] ; VirtualProtectEx
 :00401329 test eax, eax
 :0040132B jne 00401348
 …
 :00401348 mov edx, dword ptr [ebp+FFFFFCF8]
 :0040134E lea eax, dword ptr [ebp-08]
 :00401351 push eax ; буфер, сюда вернут количество записанных байт
 :00401352 mov eax, dword ptr [ebp-28]
 :00401355 lea ecx, dword ptr [ebp+FFFFFD70]
 :0040135B push 00000100 ; размер буфера
 :00401360 add edx, 00400000
 :00401366 push ecx ; адрес – откуда скопировать (12FCA4)
 :00401367 push edx ; адрес – куда писать (44CCBF)
 :00401368 push eax ; HANDLE hProcess
 :00401369 call dword ptr [00407008] ; WriteProccessMemory
 :0040136F test eax, eax
 :00401371 je 0040137C
 :00401373 cmp dword ptr [ebp-08], 00000100
 :0040137A je 00401398 ; осуществляется прыжок
 …
 :00401398 mov ecx, dword ptr [ebp-24]
 :0040139B push ecx ; HANDLE hThread
 :0040139C call dword ptr [00407004] ; ResumeThread
 :004013A2 mov edx, dword ptr [ebp-28]
 :004013A5 push FFFFFFFF ; количество миллисикунд
 :004013A7 push edx ; HANDLE hHandle
 :004013A8 call dword ptr [00407000] ; WaitForSingleObject
 

Теперь разберемся во всех вызывающихся функциях. GetStartupInfo нужна для получения одноименной структуры, требующейся для функции создания процесса - CreateProcess. Создаем процесс используя файл mrc32.dll, он то и есть ядро игры. Затем функцией VirtualProtectEx назначаем новые права коду загруженного модуля, а именно разрешаем выполнять этот код. Потом туда копируем код из адреса 12FCA4, и передаем управление созданному процессу, функцией WaitForSingleObject. Единственная трудность это получить код который мы копируем для исполнения. Можно было конечно переписать эти 256 байт и руками, но я сделал по другому.

Вставляю CD, ставлю брекпоинт на функцию GetDriveTypeA (bpx GetDriveTypeA), запускаю игру. После вылета в функцию, ставлю еще один брекпоинт (bpx 401355),жму F5. Теперь мне нужны адреса трех функций CreateFileA, WriteFile, ExitProcess. Получаю их поможью команды EXP (exp CreateFileA), в ответ должно быть что-то такое:


 exp CreateFileA
 KERNEL32
 001B:77E7A837 CreateFileA
 exp WriteFile
 KERNEL32
 001B:77E79D8C WriteFile
 exp ExitProcess
 KERNEL32
 001B:77E75CB5 ExitProcess
 

Затем задаю параметры первой функции, прямо в стек.


 d esp ## показать стек
 e ## начать редактировать его
 

Должно это выглядеть как-то так. Функция на языке Си выгледит так: CreateFileA(&filename, 0x40000000, 0, 0, 2, 0x80, 0); 8C 9D E7 77 – это адрес возврата (на функцию WriteFile = 77E79D8C), туда попадаем после завершения функции CreateFileA. По адресу 0012E198 (98 E1 12 00) находится имя файла завершающееся нулевым символом. Этот файл должен сущуствовать. Теперь ставим указатель на начало функции CreateFileA.


 r eip=77E7A837 # изменяем значение регистра EIP
 bpx WriteFile # что отловить момент когда завершится первая функция
 

Жму F5. Если все нормально, то должен остановится на первой команде функции WriteFile, а EAX не равняться FFFFFFFF, потому что это код ошибки. У меня EAX=000007CC, это хэндл на только что созданный файл. Задаю параметры второй функции, прямо в стек.


 d esp ## показать стек
 e ## начать редактировать его
 

Должно это выглядеть как-то так. Функция на языке Си выгледит так: WriteFile(hHandle, 0x0012FCA4, 100, 0x0012E1C8, 0); где hHandle=0x7CC, 0x0012FCA4 – адрес буфера ради которого все и затеял, 100 его размер, 0x0012E1C8 – сюда напишут сколько реально записали байт. 5B 5C E7 77 – это адрес возврата (на функцию ExitProcess = 77E75CB5), туда попадаем после завершения функции CreateFileA. Теперь у меня есть все чтобы с имитировать запуск игры, т.е. написать загрузчик самому без всяких выкрутасов.

Приведен пример загрузчика:
File calldll.asm


 .386
 .model flat, stdcall
 option casemap :none ; case sensitive
 include \masm32\include\windows.inc
 include \masm32\include\user32.inc
 include \masm32\include\kernel32.inc
 includelib \masm32\lib\user32.lib
 includelib \masm32\lib\kernel32.lib
 .data?
 buff db 100h dup (?)
 prin db 100h dup (?)
 .code
 start:
 jmp @F
 libName db "mrc32.dll",0
 include patch.inc
 @@:
 
 push offset buff
 call GetStartupInfo
 
 push offset prin
 push offset buff
 push 0
 push 0
 push 4
 push 0
 push 0
 push 0
 push offset libName
 push 0
 call CreateProcess
 mov eax, offset [prin+20h]
 push eax
 push 000040h
 push 000100h
 mov ecx, 44CCBFh
 push ecx
 mov eax, dword ptr [prin]
 push eax
 call VirtualProtectEx
 mov eax, offset [prin+20h]
 push eax
 push 000100h
 push offset patch
 push 44CCBFh
 mov eax, dword ptr [prin]
 push eax
 call WriteProcessMemory
 mov eax, dword ptr [prin+4]
 push eax
 call ResumeThread
 push 0000FFh
 mov eax, dword ptr [prin]
 push eax
 call WaitForSingleObject
 invoke ExitProcess,eax
 end start
 

file patch.ini


 patch:
 db 055h,08Bh,0ECh,06Ah,0FFh,068h,018h,024h,06Eh,000h,068h,0ECh,020h,06Ch,000h,064h
 db 0A1h,000h,000h,000h,000h,050h,064h,089h,025h,000h,000h,000h,000h,083h,0ECh,058h
 db 053h,056h,057h,089h,065h,0E8h,0FFh,015h,00Ch,052h,06Ch,000h,033h,0D2h,08Ah,0D4h
 db 089h,015h,0C8h,070h,077h,000h,08Bh,0C8h,081h,0E1h,0FFh,000h,000h,000h,089h,00Dh
 db 0C4h,070h,077h,000h,0C1h,0E1h,008h,003h,0CAh,089h,00Dh,0C0h,070h,077h,000h,0C1h
 db 0E8h,010h,0A3h,0BCh,070h,077h,000h,033h,0F6h,056h,0E8h,0CAh,052h,0FFh,0FFh,059h
 db 085h,0C0h,075h,008h,06Ah,01Ch,0E8h,06Fh,04Eh,027h,000h,059h,089h,075h,0FCh,0E8h
 db 0B9h,062h,0FFh,0FFh,0FFh,015h,0D8h,051h,06Ch,000h,0A3h,064h,08Bh,077h,000h,0E8h
 db 08Fh,02Ah,0FFh,0FFh,0A3h,0FCh,070h,077h,000h,0E8h,0A3h,0A0h,0FFh,0FFh,0E8h,037h
 db 0A1h,0FFh,0FFh,0E8h,0F4h,036h,0FFh,0FFh,089h,075h,0D0h,08Dh,045h,0A4h,050h,0FFh
 db 015h,010h,052h,06Ch,000h,0E8h,0FEh,0FEh,0FFh,0FFh,089h,045h,09Ch,0F6h,045h,0D0h
 db 001h,074h,006h,00Fh,0B7h,045h,0D4h,0EBh,003h,06Ah,00Ah,058h,050h,0FFh,075h,09Ch
 db 056h,056h,0FFh,015h,0E8h,050h,06Ch,000h,050h,0E8h,099h,087h,0FDh,0FFh,089h,045h
 db 0A0h,050h,0E8h,0F3h,036h,0FFh,0FFh,08Bh,045h,0ECh,08Bh,008h,08Bh,009h,089h,04Dh
 db 098h,050h,051h,0E8h,060h,001h,000h,000h,059h,059h,0C3h,08Bh,065h,0E8h,0FFh,075h
 db 098h,0E8h,0C3h,036h,0FFh,0FFh,083h,03Dh,004h,071h,077h,000h,001h,075h,005h,0E8h
 

file compl.bat


 @echo off
 e:\masm32\bin\ml /Zf /Zi /c /coff /w calldll.asm
 if errorlevel 1 goto exit
 echo _
 echo MASM32 OK *************************************************
 echo _
 e:\masm32\bin\Link /DEBUG /SUBSYSTEM:WINDOWS calldll.obj
 :exit
 pause
 

Весь архив можно скачать (и он вроде даже работает :)




Исследование защиты FTPControl

Финальная версия Windows: "Windows ХаРэ"!!!

Введение

Данная программа - хороший FTP-клиент, который позволяет соединяться с FTP-серверами и обеспечивает полный набор услуг такого рода. Получить shareware версию можно на узле компании-производителя http://www.ftpcontrol.com/. Для проведения исследования использовалась версия 2.9 build 5.

Инструменты

  • SoftICE
  • QuickVeiw
  • WinDasm

Исследование

При каждом запуске программа выдает окно с сообщением о том, что она незарегистрированная и осталось определенное число дней до окончания срока действия evaluation copy. Тут же можно нажать на кнопку Register и ввести имя и код. Следует отметить, что программа не докучает своими сообщениями о регистрации, и сообщение при запуске можно отключить. Однако после 30 дней использования программы со дня установки, она "сворачивается" в версию light, и целый ряд функций данной программы становится недоступным. Использование нормальной (не "light" версии) более 30 дней возможно достичь двумя путями:

  • сделать evluation период вместо 30 дней вечным;
  • попытаться "зарегистрировать" программу.

Первый из вышеуказанных способов описан в сборнике Cracking Tutorial (статья CpuIdle - временная защита), поэтому останавливаться на нем не будем. Отмечу только, что описание вышеуказанной статьи полностью применимо в данном случае, только дату нужно поставить свою(кто так и не понял о чем речь, отсылаю к сборнику Cracking Tutorial ). Далее я рассмотрю второй способ, а именно "регистрацию".

Итак, регистрацию, как выясняется из меню HELP, можно осуществить получив после оплаты регистрационный номер, который надо вводить в меню HELP/SHAREWARE NOTES/REGISTER. Обращаю внимание на то, что здесь есть еще некоторые пункты меню посвященные регистрации, где надо, в частности, вводить номер кредитной карточки и прочие личные данные, но речь идет не о них. Итак, при выборе вышеуказанного пункта появляется окно с предложением ввести Имя и Номер, а ниже уже стоит адрес Вашей электронной почты, который программа просит указать еще при инсталляции, т.к. это требуется при подключении к FTP серверам в анонимном режиме. После введения имени и номера появляется типичный MessageBox со словами IDKeyInvalid.

Ставим в SoftIce контрольную точку на вызов MessageBox():


 bpx MessageBoxA
 

и после выхода в отладчик, нажимаем F12 и видим код, где вызывается данная функция. Я не привожу здесь этот код, так как он бесполезен. Просмотр данного места в WinDasm говорит о том, что это код общей процедуры вызова любого MessageBox(), о чем в частности видно по строке:


 * Referenced by a CALL at Addresses:
 |:00435BCD   , :0049315C   , :004931A5   , :0049C51B   , :004E31BD
 |:004E3206   , :004E323F   , :005439DD   , :00543A18
 |
 :00435AC4 55                   		   push ebp
 

далее будет MessageBoxA()

Зададим в WinDasm поиск по слову "IDKeyInvalid" и находим следующее место:


 * Referenced by a (U)nconditional or (C)onditional Jump at Address:
 |:004E2B28(C)
 |
 :004E320D 6A00                    	push 00000000
 :004E320F 8D4DE4                  	lea ecx, dword ptr [ebp-1C]
 :004E3212 A13C445500              	mov eax, dword ptr [0055443C]
 :004E3217 8B00                    	mov eax, dword ptr [eax]
 :004E3219 8B80C4020000            	mov eax, dword ptr [eax+000002C4]
 
 * Possible StringData Ref from Code Obj ->"idKeyInvalid"
                                    	|
 :004E321F BAE0354E00              	mov edx, 004E35E0
 :004E3224 E86B89FAFF              	call 0048BB94
 :004E3229 8B45E4                  	mov eax, dword ptr [ebp-1C]
 :004E322C E8FB0FF2FF              	call 0040422C
 :004E3231 8BD0                    	mov edx, eax
 
 * Possible StringData Ref from Code Obj ->"Registration"
                                    	|
 :004E3233 B9C8354E00              	mov ecx, 004E35C8
 :004E3238 A1DC455500              	mov eax, dword ptr [005545DC]
 :004E323D 8B00                    	mov eax, dword ptr [eax]
 :004E323F E88028F5FF              	call 00435AC4 ;вызов функции с MessageBoxA()
 

Здесь уже более интересно, просмотрев код от адреса 4E2320D вверх, видим идентичный кусок программы, но уже со строкой:


 Possible StringData Ref from Code Obj ->"idNotConnected"
 

Очевидно, что мы находимся в месте где заканчиваются все неудачные регистрации, и попадаем мы сюда из адреса 4E2B28. Т.е. отсюда:


 :004E2B15 E8F225F4FF              	call 0042510C
 :004E2B1A 8B55E8                  	mov edx, dword ptr [ebp-18]
 :004E2B1D 8B45FC                  	mov eax, dword ptr [ebp-04]
 :004E2B20 59                      	pop ecx
 :004E2B21 E86AFAFFFF              	call 004E2590
 :004E2B26 84C0                    	test al, al
 :004E2B28 0F84DF060000            	je 004E320D ;переход на "неудачную
                                                  регистрацию"
 :004E2B2E 6A00                    	push 00000000
 :004E2B30 A178455500              	mov eax, dword ptr [00554578]
 :004E2B35 8B00                    	mov eax, dword ptr [eax]
 :004E2B37 33C9                    	xor ecx, ecx
 :004E2B39 33D2                    	xor edx, edx
 :004E2B3B E8BC180000              	call 004E43FC
 :004E2B40 84C0                    	test al, al
 

Забегая вперед, скажу, что у меня было несколько неудачных попыток исследования защиты данной программы и забивание Nop'ами данного перехода входило в одну из них. Данная программа изобилует конструкциями типа try……………except, что видно из часто встречающегося кода типа:


 push dword ptr fs:[eax]
 mov dword ptr fs:[eax], esp
 

В Win32 регистр FS указывает на структуру, где хранятся адреса обработчиков исключений и если программа модифицирует адрес fs:[eax], значит она добавляет свой обработчик исключений, который может делать все что угодно и, в конце концов, не возвращаться в процедуру, где произошло исключение. В частности был такой момент, когда программа выдавала сообщение об удачной регистрации, но при следующем запуске все возвращалось "на круги своя". Поиски выхода из данной ситуации завели меня внутрь процедры по адресу: 004E2590, которая вызывается непосредственно перед переходом на неудачную регистрацию. Внутри данной процедуры есть такое место:


 :004E2604 43                      	inc ebx
 :004E2605 83FB0B                  	cmp ebx, 0000000B
 :004E2608 75C1                    	jne 004E25CB
 :004E260A 8D55F4                  	lea edx, dword ptr [ebp-0C]
 :004E260D 8B45FC                  	mov eax, dword ptr [ebp-04]
 :004E2610 E86FFDFFFF           call 004E2384
 :004E2615 8B45F4                  	mov eax, dword ptr [ebp-0C]
 :004E2618 8B55F8                  	mov edx, dword ptr [ebp-08]
 :004E261B E8581BF2FF           call 00404178
 :004E2620 0F94C3                  	sete bl
 

При прохождении отладчиком данного участка, можно увидеть, что [ebp-04] указывает на введенное Имя, а [ebp-08] указывает на введенный Номер. Причем указание идет неявное а через адрес, значение которого лежит по адресам, указанным [ebp-04] и [ebp-08]. Соответственно [ebp-0C] всегда указывает на двойное слово, состоящее из 4 нулей. Значит можно предположить, что там тоже должен быть какой-то адрес, который должен указывать на то с чем сравнивать Имя и Номер.

Почему его там нет, сложный вопрос. Забегая вперед скажу, что программа после удачной проверки Имени и Номера переходит к процедуре соединиения с сервером компании производителя по Интернет. Может быть введенные данные передаются туда для проверки? Во всяком случае при отсутствии соединения регистрация программы прерывается и считается незавершенной, а копия остается Evaluation. Вобщем, не вдаваясь в подробности, я предположил, что по адресам 004e2610 и 004e2620 вызываются процедуры какого-то сравнения. А чтобы им было что с чем сравнивать , я просто поменял структуру [ebp-0C] в обоих случаях на структуру, которая используется в качестве второго параметра при вызове процедуры. Т.е. измененный код выглядит так:


 :004E260A 8D55FС                  	lea edx, dword ptr [ebp-04]
 :004E260D 8B45FC                  	mov eax, dword ptr [ebp-04]
 :004E2610 E86FFDFFFF           call 004E2384
 :004E2615 8B45F8                  	mov eax, dword ptr [ebp-08]
 :004E2618 8B55F8                  	mov edx, dword ptr [ebp-08]
 :004E261B E8581BF2FF           call 00404178
 :004E2620 0F94C3                  	sete bl
 

Теперь при попытке регистрации, программа не выдает никаких "IDKeyInvalid", однако потом просит ввести имя какого-нибудь mail-сервера, для посылки информации о регистрации по интернету фирме-производителю. Естественно в процессе связи наверняка можно сделать какие-нибудь дополнительные проверки (например на предмет поступления долларов от пользователя на счет компании за регистрацию).

Косвенно такие догадки подтверждает наличие в программе следующих комментариев WinDasm:


 * Possible StringData Ref from Code Obj ->"http://www.transsoft.com/codes/leg.txt"
 

Соединившись с узлом компании Транссофт вы нигде не найдете ссылки на файл leg.txt. Однако вы легко можете получить данный файл по вышеуказанному URL. Там длинный список всяких имен и названий фирм, типа:


 Marianne Churilla
 Raimundo Hora Goncalves
 Daivd Piaia
 Mehmet Faith Akin
 Phil Barkey
 Jean Sumption
 Mitch Larson
 Manfred Albert
 jherman@smart.net
 Keith Frank
 Richard Tan
 Mladen Krstonosic
 Ric Morris
 Alexander Joller
 ReloAction
 Anthony Battles
 Manfred Bayer... и т.д.
 

Как я понял, это список легальных пользователей программы, который дополняется во время регистрации. То есть leg. от сокращенного legal. Что интересно, в этой же директории http://www.transsoft.com/codes, лежит еще один список, который называется ill.txt. Он уже поинтереснее (и надо сказать гораздо меньше по обьему). Там можно увидеть вот что:


 QuQ [FACTOR]
 Black Thorne [PC'98]
 Phrozen Crew '98
 SiraX/[CORE]-1998
 mRFANATIc [D4C]
 JellyTop
 astaga [D4C]
 C4A Team... и т.д.
 

Ясно, что это список нелегальных пользователей, которых удалось распознать компании Транссофт, то есть ILLEGAL. Вобщем, чтобы избежать попадания в разные списки и закончить регистрацию, нужно прерваться где-нибудь после введения кода, например по адресу 4E2b21, и начинать выполнять программу пошагово без захода в процедуры (т.е.по F10), фиксируя появление окна, где предлагается ввести имя mail-сервера.

Выяснилось, что данное окно появляется при вызове процедуры по адресу 004E2F90, причем, если невозможно соединиться с указанным mail-сервером, из процедуры возврата уже нет (видимо опять try…………except срабатывает). Рискнув предположить, что при удачном соединении и обмене информацией с сервером Транссофт, выход из процедуры осуществляется обычным образом, затираем код вызова процедуры по адресу 004E2F90 пятью Nop'ами и после "регистрации" получаем сообщение о том что она была "successful". После перезапуска убеждаемся, в том, что исчезли все напоминания об "Evaluation Period" а также, что в названии окна исчезло слово UNREGISTERED и появилось введенное Вами имя.




Исследуем демку GameAdmin 2.2.2002

Автор: Hex

Веселая прога "типа" демка. Явный пример того как не надо делать процедуру регистрации. Ограничение - управление только 3-мя компами. Маловато...

Инструменты: Filemon, windasm.

Запускаем filemon и видим что прога ищет какой-то key.dll

Радует то, что написана на делфи без всяких протекторов, которые продлили бы исследование минут эдак 20. Загружаем Gameadmin.exe в Windasm и смотрим в String References "key.dll" делаем двойной клик и видим вот такое чудо:


 :0049F16A 8BC0 mov eax, eax
 :0049F16C 832D28BE4C0001 sub dword ptr [004CBE28], 00000001
 :0049F173 0F839F000000 jnb 0049F218
 :0049F179 C70518BE4C00E0F04900 mov dword ptr [004CBE18], 0049F0E0
 :0049F183 C7051CBE4C0000F14900 mov dword ptr [004CBE1C], 0049F100
 :0049F18D C70520BE4C0020F14900 mov dword ptr [004CBE20], 0049F120
 :0049F197 C70524BE4C0034F14900 mov dword ptr [004CBE24], 0049F134
 
 * Possible StringData Ref from Code Obj ->"key.dll"
 
 :0049F1A1 681CF24900 push 0049F21C
 
 * Reference To: kernel32.LoadLibraryA, Ord:0000h
 
 :0049F1A6 E8ED7BF6FF Call 00406D98
 :0049F1AB A32CBE4C00 mov dword ptr [004CBE2C], eax
 :0049F1B0 833D2CBE4C0000 cmp dword ptr [004CBE2C], 00000000
 :0049F1B7 7454 je 0049F20D
 
 * Possible StringData Ref from Code Obj ->"GetRegistrationName"
 
 :0049F1B9 6824F24900 push 0049F224
 :0049F1BE A12CBE4C00 mov eax, dword ptr [004CBE2C]
 :0049F1C3 50 push eax
 
 * Reference To: kernel32.GetProcAddress, Ord:0000h
 |
 :0049F1C4 E8FF7AF6FF Call 00406CC8
 :0049F1C9 A318BE4C00 mov dword ptr [004CBE18], eax
 
 * Possible StringData Ref from Code Obj ->"GetRegistrationString"
 |
 :0049F1CE 6838F24900 push 0049F238
 :0049F1D3 A12CBE4C00 mov eax, dword ptr [004CBE2C]
 :0049F1D8 50 push eax
 
 * Reference To: kernel32.GetProcAddress, Ord:0000h
 |
 :0049F1D9 E8EA7AF6FF Call 00406CC8
 :0049F1DE A31CBE4C00 mov dword ptr [004CBE1C], eax
 
 * Possible StringData Ref from Code Obj ->"GetComputerCount"
 |
 :0049F1E3 6850F24900 push 0049F250
 :0049F1E8 A12CBE4C00 mov eax, dword ptr [004CBE2C]
 :0049F1ED 50 push eax
 
 * Reference To: kernel32.GetProcAddress, Ord:0000h
 |
 :0049F1EE E8D57AF6FF Call 00406CC8
 :0049F1F3 A320BE4C00 mov dword ptr [004CBE20], eax
 
 * Possible StringData Ref from Code Obj ->"CheckKeyVersion"
 |
 :0049F1F8 6864F24900 push 0049F264
 :0049F1FD A12CBE4C00 mov eax, dword ptr [004CBE2C]
 :0049F202 50 push eax
 
 * Reference To: kernel32.GetProcAddress, Ord:0000h
 |
 :0049F203 E8C07AF6FF Call 00406CC8
 :0049F208 A324BE4C00 mov dword ptr [004CBE24], eax
 

Как мы видим, происходит загрузка библиотеки "key.dll", далее адреса ее процедур записываются в память по адресам:


 [004CBE18] <- "GetRegistrationName"
 [004CBE1С] <- "GetRegistrationString"
 [004CBE20] <- "GetComputerCount"
 [004CBE24] <- "CheckKeyVersion"
 

А теперь смотрим на код прям перед LoadLibraryA:


 :0049F179 mov dword ptr [004CBE18], 0049F0E0 <- "GetRegistrationName"
 :0049F183 mov dword ptr [004CBE1C], 0049F100 <- "GetRegistrationString"
 :0049F18D mov dword ptr [004CBE20], 0049F120 <- "GetComputerCount"
 :0049F197 mov dword ptr [004CBE24], 0049F134 <- "CheckKeyVersion"
 

Хе-хе :) Помоему тут и ежу понятно. Автор решил не подвергать риску юзера и задал значения адресов, если такой key.dll не будет найдено :)

Итак вот что прописано для демо режима:


 "GetRegistrationName":
 :0049F0E0 push ebp
 :0049F0E1 mov ebp, esp
 :0049F0E3 push ecx
 :0049F0E4 mov eax, 0049F0F4 - Это адрес строки "Демо Версия"
 :0049F0E9 mov dword ptr [ebp-04], eax
 :0049F0EC mov eax, dword ptr [ebp-04]
 :0049F0EF pop ecx
 :0049F0F0 pop ebp
 :0049F0F1 ret
 
 "GetRegistrationString":
 :0049F100
 :0049F100 push ebp
 :0049F101 mov ebp, esp
 :0049F103 push ecx
 :0049F104 mov eax, 0049F114 - Это адрес еще одной строки "Демо Версия"
 :0049F109 mov dword ptr [ebp-04], eax
 :0049F10C mov eax, dword ptr [ebp-04]
 :0049F10F pop ecx
 :0049F110 pop ebp
 :0049F111 retn
 
 "GetComputerCount":
 :0049F120 push ebp
 :0049F121 mov ebp, esp
 :0049F123 push ecx
 :0049F124 mov [ebp-04], 00000003 - Число компов :)
 :0049F12B mov eax, dword ptr [ebp-04]
 :0049F12E pop ecx
 :0049F12F pop ebp
 :0049F130 retn
 
 "CheckKeyVersion":
 :0049F134 push ebp
 :0049F135 mov ebp, esp
 :0049F137 pop ebp
 :0049F138 ret 0004
 

Ну дальше остается только исправить как кому нравится этот код. Или написать свою супер DLL :) Она должна экспортировать хотя бы функцию GetComputerCount чтобы задать число компов. А так все остальное не важно :)




Исследование программы Ulead Gif Animator v3.0

В центре Москвы очередная бандитская разборка. С братками, джипами, автоматной перестрелкой. Программист бросается на тротуар и машинально на асфальте: "IDDQD, IDDQD".

Введение

Целью нашего сегодняшнего исследования будет Ulead Gif Animaton v3.0. Программа защищена с помощью Vbox v4.10. Защитные алгоритмы реализованы в трех DLL: Vboxp410.dll, Vboxb410.dll и Vboxt410.dll (или Vboxc410.dll – в "коммерческой" версии). Все эти библиотеки , за исключением первой, упакованны, поэтому все модификации мы будем вносить в Vboxp410.dll.

Как осуществляется защита? Тело программы модифицируется так, что сначала вызывается процедура проверки, и, после ее прохождения, происходит переход на реальную точку входа программы, если проверка прошла успешно, или на выход, если отведенное для тестирования программы время истекло. В теле нашей программы это выглядит так (дизассемблерные листинги взяты из WinDASM'а):


 * Reference To: vboxp410., Ord:0001h
                                   |
 :004A8020      Call dword ptr [004A8290]
 :004A8026      push FFFFFFFF
 :004A802B      call eax
 :004A802D      ret 000C
 

Первое, что приходит на ум – "подсмотреть" нужное значение регистра EAX, и, подкорректировав стек, сделать переход (jmp) через процедуру проверки. Попробуйте… Не вышло? Это потому, что защита, кроме процедуры проверки, осуществляет восстановление заголовка и секций файла. Значит, нужно исследовать защиту. Думаю, многие пробовали это и до меня, но заканчивали с плачевным результатом. Защита действительно сильная. Модули проверяются на изменения в файле на диске (CRC), мало того, производится еще и проверка "развернутого" кода в памяти после запуска. Именно проверка кода в памяти и реагирует на установки контрольных точек (bpx) в SoftICE, ведь реально SoftICE заменяет код по нужному адресу на int 3 и выполняет i3here on. Следовательно, обычные контрольные точки применять нельзя – нарушим целостность кода в памяти.

Исследование

При запуске защищенной программы появляется окно, сообщающее об оставшемся времени. Находится это окно по адресу 070025C3 в Vboxt410.dll (которая нам недоступна) – поставьте в SoftICE контрольную точку bpmb 070025C3 x (это наша замена bpx) – и попадете в функцию DialogBoxParamA(). Вы убедитесь, что при нажатии кнопки "Quit" содержимое регистра ЕАХ будет равно 1, а при нажатии кнопки "Try" – нулю. Переведите часы на месяц вперед, и снова запустите программу. Нажав кнопку "Quit" вручную измените содержимое регистра ЕАХ на 0, и продолжите выполнение программы. Появится стандартный MessageBox с сообщением, что испытательный срок работы истек, и программа закрывается. Происходит это по адресу 7035629 (KERNEL32!EnterCriticalSection). Если же вместо вызова функции (сall) сделать переход через него с соответствующей коррекцией стека – программа будет работать. Попробуем проделать то же самое с DialogBoxParamA(), и Вы увидите, что здесь это не поможет – защита проверяет, выполнялась ли данная процедура, или нет, и реагирует соответствующим образом.

Итак, наша цель достигнута, но только в работающей программе с помощью SoftICE. Теперь нам необходимо сделать эти изменения постоянными, что является более трудной задачей.

Обозначим наши цели:

  • Изменить DialogBoxParamA() – иммитировать нажатие кнопки "Try".
  • Выполнить обход процедуры KERNEL32!EnterCriticalSection.
  • Скрыть наши действия от программных проверок.

Проделывать мы все это будем с Vboxp410.dll, и начнем с ее заголовка. Запускаем ProcDump, нажимаем кнопку "PE Editor", и открываем нашу DLL. Нажимаем кнопку "Sections". В описании характеристик секции .text мы видим 60000020, что означает Code, Executable, Readable . Изменяем это значение на E0000020: нажимаем правой кнопкой мыши на .text, и выбираем пункт "Edit Section". Теперь секция .rdata, значение характеристик равно 40000040, что означает Initialized Data, Readable. Изменяем это значение на C0000040. Этим самым мы изменили параметр файла Readable (только чтение) на свободный доступ – чтение и запись. Если бы мы этого не сделали, то при работе нашего модуля, который будет изменять свой же код, мы бы получили ошибку Invalid Page Fault. Далее перед нами стоит задача найти свободное место для записи нашего кода. Эта DLL создана с помощь компилятора С++, который включает в код кучу ненужного мусора. Свободное место нашлось, начиная с адреса 5021918.

Теперь описание нашей процедуры:


 VBOXP410.DLL , точка входа:
 5001F99  E97AF90100           jmp 05021918
 
 Процедуры обработки:
 5021918  C705991F0005B8960402 mov dword ptr [05001F99],020496B8
 5021922  C6059D1F000505       mov byte ptr [05001F9D],05
 5021929  A1AOB50205           mov eax,[0502B5A0]
 502192E  C705AOB5020554190205 mov dword ptr [0502B5A0],05021954
 5021938  A344190205           mov [05021944],eax
 502193D  E95706FEFF           jmp 05001F99
 5021942  0000
 5021944  0000
 5021946  0000
 5021948  0000
 502194A  0000
 502194C  0000
 502194E  0000
 5021950  0000
 5021952  0000
 5021954  66FF0542190205       inc word ptr [05021942]
 502195B  66813D421902050040   cmp word ptr [05021942],4000
 5021964  742B                 jz  05021991
 5021966  66813D42190205002E   cmp word ptr [05021942],2EOO
 502196F  7534                 jnz 050219A5
 5021971  C70590890507AB190205 mov dword ptr [07058990],050219AB
 502197B  A1348A0507           mov eax,[07058A34]
 5021980  A348190205           mov [05021948],eax
 5021985  C705348A0507AE190205 mov dword ptr [07058A34],050219AE
 502198F  EB14                 jmp 050219A5
 5021991  A1E8760808           mov eax,[080876E8]
 5021996  A348190205           mov [05021948],eax
 502199B  C705E8760808AE190205 mov dword ptr [080876E8],050219AE
 50219A5  FF2544190205         jmp [05021944]
 50219AB  C21000               ret 0010
 50219AE  8B442410             mov eax,[ESP+10]
 50219B2  A34C190205           mov [0502194C],eax
 50219B7  C7442410E1190205     mov dword ptr [esp+10],050219E1
 50219BF  8F0550190205         pop dword ptr [05021950]
 50219C5  68D0190205           push 050219DO
 50219CA  FF2548190205         jmp [05021948]
 50219DO  33CO                 xor eax, eax
 50219D2  66C70554190205EB4F   mov word ptr [05021954],4FEB
 50219DB  FF2550190205         jmp [05021950]
 50219E1  837C240818           cmp dword ptr [ESP+08],18
 50219E6  7510                 jnz 050219F8
 50219E8  C744240811010000     mov dword ptr [ESP+08],00000111
 50219FO  C744240C95040000     mov dword ptr [esp+OC],00000495
 50219F8  FF254C190205         jmp [0502194C]
 

С точки входа (5001F99) переход осуществляется на нашу процедуру модификации. После загрузки DLL этот переход будет заменен оригинальным кодом, что сохранит нормальный вид DLL для прохождения программных проверок. Также наш код как бы ставит hook на вызов EnterCriticalSection(), и заменяет его на наш, новый обработчик. Этот обработчик ждет, пока не распакуется Vboxt410.dll, и после этого перенаправляет вызовы RaiseExeption() и DialogBoxParamA() на наши обработчики. Наш обработчик вызова RaiseException() представляет собой команду RET 10 – немедленный возврат с коррекцией стека. А вот обработчик DialogBoxParamA() немного сложнее: он вносит в стек значения, эмулирующие нормальный возврат из нормальной процедуры DialogBoxParamA(), и перехватывает процедуру передачи сообщений на диалог, подменяя ее своим обработчиком. Этот обработчик ждет, пока в окно не будет посланно сообщение WM_SHOWWINDOW, и заменяет его сообщением закрытия окна. После чего в регистр ЕАХ записывается ноль, и обработчик изолируется, записывая в свое начало команду безусловного перехода (jmp) на настоящий Critical_Handler. После чего мы выходим из нашего обработчика обратно в защиту, которая пытается показать окно с сообщением об истечении срока действия программы (DialogBoxParamA), контроль над которым осуществляет наш код – окно лишь промелькнет на экране. После этого вызывается API-функция RaiseException(). Но она тоже контролируется нашим кодом, который просто делает возврат с коррекцией стека. После всего этого запускается защищенная программа.

Для тех, кто не понял, привожу указанное выше вкратце:

  1. Загружается Vboxp410.dll, сразу же просходит безусловный переход на наш обработчик, который восстанавливает измененный код в точке входа (то место, откуда был JMP), и ставит HOOK на процедуру EnterCriticalSection() – для ожидания распаковки Vboxt410.dll.
  2. Наш обработчик ждет окончания распаковки и проверки, и ставит hook на RaiseException() и DialogBoxParamA().
  3. Новый обработчик диалога инициирует его закрытие, выставляет значения, необходимые для корректной работы программе.
  4. Новый обработчик RaiseException() осуществляет возврат, не производя никаких действий.

Ниже приведен тот же код в "структурном" виде:


 Точка входа:
 jmp Восстановление кода
 
 Восстановление кода:
 mov dword ptr [Точка входа],020496B8 – восстановление точки входа
 mov byte ptr [Точка входа+4],05      – восстановление точки входа
 mov eax,[KERNEL32!EnterCriticalSection]
 mov dword ptr [KERNEL32!EnterCriticalSection], Новый обработчик EnterCriticalSection
 – подмена обработчика
 mov [Временный контейнер для EnterCriticalSection],eax
 jmp Точка входа
 
 Cчетчик:
 dw 0
 Временный контейнер для EnterCriticalSection:
 dd 0
 Контейнер для диалога:
 dd 0
 Контейнер для процедуры диалога:
 dd 0
 Контейнер возврата из диалога:
 dd 0
 
 Новый обработчик EnterCriticalSection:
 inc word ptr [Счетчик]
 cmp word ptr [Cчетчик],4000
 jz Hook DialogBox для VBOXC410 – подмена обработчика DialogBoxParamA после распаковки
 cmp word ptr [Cчетчик],2EOO
 jnz Реальный обработчик EnterCriticalSection – еще не распакованна, обрабатывается
 "родным" обработчиком
 
 Обработчики для VBOXT410:
 mov dword ptr [KERNEL32!RaiseException], Новый обработчик RaiseException
 – подмена обработчика
 mov eax,[USER32!DialogBoxParamA]
 mov [Контейнер для диалога],eax
 mov dword ptr [USER32!DialogBoxParamA], Указатель на новый обработчик
 - подмена обработчика
 jmp  настоящий EnterCriticalSection
 
 Обработчик для VBOXC410:
 mov eax,[USER32!DialogBoxParamA]
 mov [Контейнер диалога],eax
 mov dword ptr [USER32!DialogBoxParamA], Указатель на новый обработчик диалога
 – подмена обработчика
 Настоящий EnterCriticalSection:
 jmp [Контейнер для EnterCriticalSection]
 
 Новый обработчик RaiseException:
 ret 10
 
 Новый обработчик диалога:
 mov eax,[esp+1O]
 mov [Контейнер процедуры диалога],eax
 mov dword ptr [esp+1O],Новый обработчик процедуры диалога
 pop dword ptr [Возврат из диалога]
 push  Call_возврат из диалога
 jmp [Контейнер для диалога]
 
 Call_возврат из диалога:
 xor eax,eax
 mov word ptr [Новый обработчик EnterCriticalSection],4feb
 jmp [Возврат из диалога]
 
 Указатель на новую процедуру диалога:
 cmp dword ptr [esp+08],18
 jnz Указатель на настоящую процедуру диалога
 mov dword ptr [esp+08],111
 mov dword ptr [esp+OC],495
 
 Указатель на настоящую процедуру диалога:
 jmp [Контейнер для процедуры диалога]
 

Заключение

Так сдалась эта действительно сильная защита, технология работы которой, как я сильно подозреваю, была позаимствованна у вирусов.




Доделываем плагин для Import REconstructor 1.3

Автор: EGOiST
WEB сайт: http://www.ego1st.cjb.net/

Как говорил Hex : бывают 2 типа джимпов : относительный и прямой. При прямом джампе (jmp xxxxxxxx) xxxxxxxx = адресу, на который ссылается джамп. При относительном джампе xxxxxxxx = адрес, на который ссылается джамп - текущий адрес байта - 5(длина джампа). Так вот. Плагин от Hex'а не находил ф-ии вида :


 push ebp
 mov xxx,xxx
 push xxx
 ...
 jmp xxxxxxxx
 

Плагин не находил ф-ию, потому что к xxxxxxxx прибавлялся g_pointer, а не текущий адрес байта - это первая ошибка.. Еще у ASProtect'a есть емуляция вызовов типа :


 push ebp
 push BFFxxxxx
 push xxx
 push BFFxxxxx
 ...
 push xxxxxxxx
 ret
 

Тут тоже плагин попадал на первый push BFFxxxxx и выдавал это в результат (нужно добавить проверку на ret) - это вторая ошибка.. Я это все поправил и привожу исходник :


 {ASProtect plugin for Import REConstructor v1.3.
 It helps to reconstruct import table of executables
 protected by ASProtect 1.xx.
 This plugin tested on ASProtect 1.2(asprotect.exe),
 ASPack 2.11d(aspack.exe), Undisker 1.1(undisker.exe).
 EGOiST[TSRh].[www.ego1st.cjb.net]}
 
 library ASProtect12x;
 uses
   windows;
 
 var
   g_temp: array[0..MAX_PATH] of char;
   g_ftmp: array[0..MAX_PATH] of char;
   g_ftmp2: array[0..MAX_PATH] of char;
   g_time_out: DWORD;
   g_pointer: DWORD;
 
 function Trace(param: DWORD): DWORD; cdecl;
 var
   i: DWORD;
   hFile: DWORD;
   BytesRead: DWORD;
   to_trace: array[0..128] of BYTE;
   val: DWORD;
   BytesWritten: DWORD;
   adr: DWORD;
 begin
   lstrcpy(g_ftmp, g_temp);
   lstrcat(g_ftmp, '\');
   i := lstrlen(g_ftmp);
   movememory(addr(g_ftmp[i]), addr(param), 4);
   g_ftmp[i + 4] := chr(0);
   lstrcpy(g_ftmp2, g_ftmp);
   lstrcat(g_ftmp, '.tmp');
   lstrcat(g_ftmp2, '_.tmp');
 
   hFile := CreateFile(g_ftmp, GENERIC_READ, 0, nil,
     OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
   if (hFile = INVALID_HANDLE_VALUE) then
   begin
     Trace := 201;
     exit;
   end;
 
   if not ReadFile(hFile, g_time_out, 4, BytesRead, nil)
     or (BytesRead <> 4) then
   begin
     CloseHandle(hFile);
     Trace := 203;
     exit;
   end;
 
   if not ReadFile(hFile, g_pointer, 4, BytesRead, nil)
     or (BytesRead <> 4) then
   begin
     CloseHandle(hFile);
     Trace := 203;
     exit;
   end;
   CloseHandle(hFile);
 
   if (IsBadReadPtr(pointer(g_pointer), 4)) then
   begin
     Trace := 205;
     exit;
   end;
 
   movememory(addr(to_trace), pointer(g_pointer), 129);
   for i := 0 to 128 do
   begin
     if (to_trace[i] = $E9) or (to_trace[i] = $68) then
     begin
       if (to_trace[i] = $68) and (to_trace[i + 5] <> $C3) then
         continue;
       val := to_trace[i + 4] shl 24 + to_trace[i + 3] shl 16 +
         to_trace[i + 2] shl 8 + to_trace[i + 1];
       if val < $400000 then
         continue;
       asm
 mov adr, eax
       end;
       if IsBadReadPtr(pointer(val), 4) then
         val := val + adr + 5
       else
         val := val;
       break;
     end;
   end;
 
   if (IsBadReadPtr(pointer(val), 4)) then
   begin
     Trace := 205;
     exit;
   end;
 
   hFile := CreateFile(g_ftmp2, GENERIC_WRITE, 0,
     nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
   if (hFile = INVALID_HANDLE_VALUE) then
   begin
     Trace := 207;
     exit;
   end;
 
   if (not WriteFile(hFile, val, 4, BytesWritten, nil)) then
   begin
     CloseHandle(hFile);
     Trace := 209;
     exit;
   end;
   CloseHandle(hFile);
 
   Trace := 200;
 end;
 
 exports Trace;
 
 begin
   GetTempPath(MAX_PATH, g_temp);
 end.
 

В массиве to_trace ищется байт E9h или 68h. Если нашли E9h, т.е. jmp, читаются 4 байта после E9h (в обратном порядке) и это выдается в результат. Если же нашли 68h, то дальше сравнивается 5 байт после 68h с С3h(ret). Если равно, то читаем 4 байта после 68h. Если нет, то продолжаем поиск...вот и все.

Все вопросы в мыло...
e-mail : egoist_tsrh@rbcmail.ru

writer : EGOiST[TSRh]
coder : EGOiST[TSRh]
web : http://www.ego1st.cjb.net/
: http://kickme.to/tsrh
e-mail : egoist_tsrh@rbcmail.ru
: tsrh@mail.ru




Регистрация Internet Maniac

ВВЕДЕНИЕ

Internet Maniac - программа для получения информация о серверах в Интернет. Включает такие возможности, как Ping, Finger, Traceroute и т.д. Для исследованиz данной программы мы будем использовать отладчик SoftICE. Же- лательно, версии 4.00 или выше.

ИССЛЕДОВАНИЕ

При каждом запуске программа в заголовке окна выдает строку UNREGISTERED, что само по себе уже угнетает. В этой статье мы рассмотрим, как ее зарегистриро- вать.

Запустим программу. В меню Help есть пункт Register..., с помощью которого мож- но ввести имя (Your Name) и код (Registration code). Попробуем ввести любые имя и код и нажмем на кнопку Register. Появится окошко с надписью Incorrect registration code. Хорошо... Перейдем в SoftICE и поставим контрольные точки (breakpoint) на выполнение функций GetDlgItemTextA() и GetWindowTextA(). Для этого введем сле- дующие команды:


 bpx GetDlgItemTextA
 bpx GetWindowTextA
 

Хотя названия функций можно вводить и маленькими буквами, рекомендуется вводить их так. Таким образом, Вы быстрее их запомните. Ну, что ж... Введем имя (я ввел YH2K) и код (я ввел 110184). Нажимаем на кнопку, и... Программа прерывается, и мы попадаем в отладчик. Программа остановилась при вызове функции GetDlgItemTextA(). Нажмем F12, чтобы вернуться из функции в про- грамму. Теперь посмотрим на участок, откуда она вызывается:


 015F:0040449D 6800010000 PUSH 00000100
 015F:004044A2 50 PUSH EAX
 015F:004044A3 684A040000 PUSH 0000044A
 015F:004044A8 56 PUSH ESI
 015F:004044A9 FFD7 CALL EDI <- вызов GetDlgItemTextA (считывается имя)
 015F:004044AB 8D4C2408 LEA ECX,[ESP+08] <- мы тут
 015F:004044AF 6800010000 PUSH 00000100
 015F:004044B4 51 PUSH ECX
 015F:004044B5 6849040000 PUSH 00000449
 015F:004044BA 56 PUSH ESI
 015F:004044BB FFD7 CALL EDI <- вызов GetDlgItemTextA (считывается код)
 015F:004044BD 8D542448 LEA EDX,[ESP+48]
 015F:004044C1 8D442408 LEA EAX,[ESP+08]
 015F:004044C5 52 PUSH EDX <- запись в стек кода
 015F:004044C6 50 PUSH EAX <- запись в стек имени
 015F:004044C7 E8C4240000 CALL 00406990 <- вызов функции
 

Попробуем разобраться. По адресу 4044А9 происходит вызов функции, считыва- ющей имя, введенное пользователем. По адресу 4044ВВ происходит вызов функции,.NeCrOmAnCeR. Регистрация Internet Maniac v1.08. 2 считывающей код. По адресам 4044С5 и 4044С6 происходит запись в стек двух пара- метров, которые будут переданы функции, вызывающейся по адресу 4044С7. С по- мощью кнопки F10 дойдем до адреса 4044С5 и посмотрим, что хранится в регистрах EDX и EAX. Выполняется это с помощью следующих команд:


 d edx
 d eax
 

И что же мы увидим??? В этих регистрах хранится имя и код, которые мы ввели! А зачем функции получать в качестве параметров имя и код??? Правильно... Отпра- вимся внутрь функции с помощью кнопки F8. Начнем анализировать код:


 015F:00406990 83EC20 SUB ESP,20 <- мы здесь
 015F:00406993 56 PUSH ESI
 015F:00406994 8B742428 MOV ESI,[ESP+28]
 015F:00406998 56 PUSH ESI
 015F:00406999 FF154C004100 CALL [KERNEL32!lstrlen] <- определяется длина имени
 015F:0040699F 83F804 CMP EAX,04 <- если длина не меньше 4,
 015F:004069A2 7D07 JGE 004069AB <- то осуществляется переход на 4069АВ
 015F:004069A4 33C0 XOR EAX,EAX
 015F:004069A6 5E POP ESI
 015F:004069A7 83C420 ADD ESP,20
 015F:004069AA C3 RET <- выход из функции
 015F:004069AB 0FBE4601 MOVSX EAX,BYTE PTR [ESI+01]
 ...
 015F:004069E0 8B542440 MOV EDX,[ESP+40]
 015F:004069E4 83C414 ADD ESP,14
 015F:004069E7 8D442404 LEA EAX,[ESP+04]
 015F:004069EB 52 PUSH EDX <- запись в стек содержимого регистра EDX
 015F:004069EC 50 PUSH EAX <- запись в стек содержимого регистра EAX
 015F:004069ED FF15F8004100 CALL [KERNEL32!lstrcmp] <- сравнивается две строки
 

Обратим внимание на адрес 406999. Вызывается функция, определяющая длину имени. Далее, если длина имени не меньше 4, то осуществляется переход на 4069АВ. А если длина меньше 4, то переход не осуществляется и происходит выход из функ- ции по адресу 4069АА. После того, как произошел переход на 4069АВ, по шагу про- кручиваем код и доходим до того места, в котором сравнивается введенный нами код с правильным. Конечно... Вызывается функция lstrcmp(), которая предназначена для сравнения строк. Посмотрим параметры, которые ей передаются: в регистре EDX (d edx) находится код введенный нами код (у меня 110184), а в регистре EAX (d eax) - искомый код. В моем случае - 1184420864-200144. Вот его-то и следует переписать... Выйдем из отладчика и введем новые данные в окно регистрации (не забудьте перед этим отключить все контрольные точки - bd *). Нажимаем на кнопку Register, и... программа зарегистрирована!

Казалось бы, программа исследована... Но, есть один нюанс. Помните, осуществ- лялся выход из функции, если длина имени была менее 4 символов? Вот тут-то и надо рассмотреть этот случай. В отладчике вновь включим все контрольные (be *), введем имя меньше четырех символов и нажмем на Register. Пройдем по вышеописанному пути и после выхода из функции попадем сюда:


 015F:004044C7 E8C4240000 CALL 00406990
 015F:004044CC 83C408 ADD ESP,08 <- сюда мы попадаем
 015F:004044CF 85C0 TEST EAX,EAX <- проверяется значение EAX.NeCrOmAnCeR. Регистрация Internet Maniac v1.08. 3
 015F:004044D1 744A JZ 0040451D <- осуществляется переход, если установ-
 лен флаг нуля (Z)
 015F:004044D3 8B3D78004100 MOV EDI,[KERNEL32!WritePrivateProfileSt...
 

По адресу 4044CF проверяется значение EAX, после которого устанавливается флаг нуля (Z). Далее, с адреса 4044D1 происходит переход в случае, если данный флаг уста- новлен. После этого перехода появляется окно с сообщением о неправильно введен- ном коде. Значит, нам нужно сделать так, чтобы переход не осуществлялся. Этого можно добиться путем изменения оператора условного перехода JZ на обратный ему JNZ. Шестнадцатеричный код оператора JZ 0040451D - 74h 4Аh. Когда курсор уста- новлен на строке 4044D1, наберите команду «a» (без кавычек) для переключения в режим интерактивного ассемблера. Поменяйте данный оператор на JNZ 0040451D. Таким образом, шестнадцатеричный код изменится на 75h 4Ah. Теперь если Вы вый- дете из отладчика, высветится окошко об успешной регистрации. Но... Код изменил- ся только на одну сессию работы программы, поэтому Вам нужно открыть исполняе- мый файл в шестнадцатеричном редакторе и производить модификацию в нем. Глав- ное, найти нужное место в файле. Так как комбинация 744Ah очень коротка, она мо- жет встречаться в нескольких местах в программе. Поэтому следует переписать так- же несколько байт, стоящих до и после изменяемой комбинации, например: 83C40885C0744A8B3D78004100h. Далее надо изменить байт 74h на 75h (т.е. изменить код команды JZ на JNZ). Удачи Вам!




Брутфорс для IP-Tools v1.11

Автор: Hex

Брутфорс уместен везде, где нужен ввод пароля. Почему бы не юзать брутфорс для регистрации? Почему не заставить прогу подбирать серийный номер к самой себе? Никто не запрещал. Вот и заставим IP-Tools 1.11 сгенерить нам номер :)

Для начала найдем где происходит генерация и проверка. Запускаем прогу. Заходим в окно ввода регистрационного номера. Ставим брейкпоинт на hmemcpy. Жмем ок. Мы в айсе. Жмем F12 пока не выберемся на уровень с адресами 4973хх. Теперь идем вниз по коду и посматривая в регистры замечаем:


 0049738B 8B45F0 mov eax, dword ptr [ebp-10] - в ЕАХ попадает наш номер.
 0049738E E8617DFFFF call 0048F0F4 - проверка на длину и символы, которые юзались
 00497393 663BF8 cmp di, ax
 00497396 0F8574010000 jne 00497510 - прыжок к ошибке
 0049739C A1B0994B00 mov eax, dword ptr [004B99B0]
 004973A1 BAFF010000 mov edx, 000001FF
 004973A6 E8957CFFFF call 0048F040
 004973AB 8BF8 mov edi, eax
 004973AD A1E8984B00 mov eax, dword ptr [004B98E8]
 004973B2 BAFF010000 mov edx, 000001FF
 004973B7 E8847CFFFF call 0048F040 - проверка самого кода
 004973BC 3BF8 cmp edi, eax
 004973BE 0F854C010000 jne 00497510 - прыжок к ошибке
 

Итак если ни один из прыжков (00497396 и 004973BE) не выполнится - выводится месага о том, что все зарегено. Так как в ЕАХ по адресу 0049738B загоняется номер из ebp, то можно зациклить прогу заставив ее прыгать сюда загоняя в eax номер для проверки. Как я определил, в коде юзаются тока цифры, причем 0 не юзается. Ну и слава богу. Меньше мороки. Теперь нам остается тока написать небольшую прогу которая будет генерить код который мы будем загонять в EAX перед этой все проверкой :)

Как это сделал я. Я открыл iptools.exe в qview и нашел пустое место (т.е. там тока 00 00 00 ...)

И написал там свою программу. Вот она:


 000B2C8E: 60 pushad - сохраняем на всякий случай регистры
 000B2C8F: 31C9 xor ecx,ecx - очистили ecx
 000B2C91: 8B45F0 mov eax,dword ptr [ebp-10] - прочитали серийный номер
 000B2C94: 823839 cmp byte ptr [eax],39 - первая цифра = '9'
 000B2C97: 7404 jz 000B2C9D - равно
 000B2C99: FE00 inc byte ptr [eax] - увеличили первую цифру на 1
 000B2C9B: EB0B jmp 000B2CA8 - вернулись в прогу
 000B2C9D: C60031 mov byte ptr [eax],31 - записали в первую цифру 1
 000B2CA0: 40 inc eax - переключились на вторую цифру
 000B2CA1: 823831 cmp byte ptr [eax],31 - проверили не закончился ли код
 000B2CA4: 7C08 jl 000B2CAE - закончился
 000B2CA6: EBEC jmp 000B2C94 - повторим проверку для следующей цифры
 000B2CA8: 61 popad - вернули регистры на место
 000B2CA9: E9DD3AFEFF jmp 0009678B - вернулись в прогу (49738B = 9678B offset)
 000B2CAE: C60031 mov byte ptr [eax],31 - если уже конец кода то допишем 1.
 000B2CB1: 61 popad - вернули регистры на место
 000B2CB2: E9D43AFEFF jmp 0009678B - вернулись в прогу.
 

Ну и еще надо сделать это


 000967BE: 0F85CAC40100 jnz 000B2C8E - т.е. если код неверный то увеличиваем на 1.
 

Итак как этот бред работает. Читаю из стэка номер и читаю его слева направо (еврейские замашки :)) Если первая цифра не 9. То увеличиваю ее на 1 и выхожу в прогу. Если первая цифра 9 то заменяю ее на 1 и проверяю следующую цифру. При проверке следующей цифры проверяю чтобы она не оказалась последней в коде. В памяти это выглядит вот так (мой код 12345):


 31 32 33 34 35 00 00 00 00
 

Так вот я слежу чтобы не попасть на 00 или еще че-нить. Если так 00 то делаем из него 31 (это '1') т.е. увеличиваем длину кода на одну цифру. ну и под конец опять таки выхожу в программу. Генерить код оно будет в таком виде:


 11111
 21111
 ...
 91111
 12111
 22111
 32111
 ...
 13111 и т.д.
 

Т.е. в обратном порядке. Нам то ведь пофиг, главное - перебрать все варианты. Я вводил имя 'Hex' и начальный код '1111', помоему, код не может быть короче 4 символов, я уже не помню. И еще два нюанса. Первое:


 00497396 0F8574010000 jne 00497510
 

тут почему то она выпрыгивает :( занопить эту команду. Второе - прога проверяет контрольную сумму и выводит страшную месагу о том, что файл был поврежден. Bpx на showwindow и потом найдете, что проверка идет по адресу 4B34A7. Правим там джамп и терь запускаем прогу. Терь осталось тока поставить брейкпоинт на 4973C4 чтобы попасть в айс когда генерация закончится. Запускаем вводим 'Hex' и '1111' жмем ок. Прога типа подвисает. И секунд через 40 (это на celeron 366, запущено тока айс и IP-tools) мы в айсе. Теперь d *(ebp-10) и там у нас '96521'. Какая красота. :)

Теперь берем оригинальный exe, запускаем и регимся :) P.S. Это так все хорошо потому, что юзались тока цифры, а если бы в коде были еще и буквы... Есть еще проги в, которых в проверку специально вставляют процедуру KERNEL32!Sleep, чтобы типа замедлить генерацию. Занопить ее и все.




Нахождение регистрационного кода тремя способами для Irfan View 3.17

Автор: Fess

Target: Irfan View 3.17

Tools:

  • Some brains
  • TRW2000/Soft-Ice
  • Win32Dasm 8.93
  • Pe Identifier 0.7 beta
  • Распаковщик AsPack'a 2000
  • ProcDump32 1.6.2
  • Hex-редактор (QView, Hiew)
  • Delphi

Все, кроме мозгов, можно найти на www.exetools.com

Вступление

Как это начиналось:

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

Что за прога:

Очень рульный (ИМХО: самый лучший) просмотрщик графических файлов, поддерживает кучу форматов, с помощью плагинов еще больше, быстрый, удобный, занимает мало места и главное БЕСПЛАТНЫЙ.

Отсебятина

Вы можете подумать, что я дурак ломаю бесплатную программу, но это не так! Первое: В программе все-таки предусмотрена регистрация для зарегист- рированных юзеров тех.поддержка и всякое такое. Второе: если вы сломаете эту прогу, вы тем самым не нарушите ничьи авторские права, поскольку ее стоимость равна 0$. Третье: для крэкера не важна цена, а важен процесс взлома, моральные ощущения от этого, я еще напишу об этом в каком-нибудь крэкерско-филосовском мануале "Зачем ломают программы или крэкеры на воле" Как сказал Гагарин: Пое-е-ехали! (ЗЫ: вообще-то, он сказал ЧТО У ВАС ТАМ КРЫШИ ПОЕ-Е-ЕХАЛИ?!!)

Начало

Хм.Хм.

Перво-наперво определимся, чем защищена программа, для этого проверим не запакована ли она. Для этих целей воспользуемлся программой PeIdentifier она правильно определила это AsPack 2000 для снятия воспользуйтесь любым доступным унпакером (я пользовался сборкой UN-PACK 2.2). После этого, если вы любите дизассемблировать в Win32Dasm'e, а я люблю, то придется немного подправить таблицу импорта для этого воспользуемся программой ProcDump32 нажимает PE Editor открываем распакованый файл. Нажимаем на кнопку Section и появляемся в разделе секций. По содержащимся секциям можно предположить, что это C++ (первая секция .text). Вот ее-то и правим в поле Section Characteristics заносим E0000020 вместо C0000040. Теперь можно дизассемблировать и текст дизассемблируется нормально.

Листинг дизассемблера есть проверим программу, заходим в Help\Registration и вводим любое имя и любой код, должна появится строка Incorrect Registration, если вы угадали и у Вас такая строка не появилась, то искренне Вас с этим поздравляю и направляю в казино в Лас-Вегасе (если выйграете меня не забывайте!).

Строка есть, заходим в Win32Dasm и ищем ее там, и откудова она вызывается, для этого заходим в раздел строк и ищем там нашу, а после того как найдете, нажмите на нее два раза и окажитесь здесь


 :00446587 8D8C2474140000  lea ecx, dword ptr [esp+00001474]
 :0044658E 8D942474130000  lea edx, dword ptr [esp+00001374]
 :00446595 51              push ecx
 :00446596 52              push edx
 :00446597 E8640BFEFF      call 00427100
 :0044659C 83C408          add esp, 00000008
 :0044659F 85C0            test eax, eax
 :004465A1 752C            jne 004465CF
 
 * Possible StringData Ref from Data Obj ->"Incorrect registration !"
                                   |
 :004465A3 A138F94B00      mov eax, dword ptr [004BF938]
 

Из этого участка кода ясно видно, что по адресу 446597 находится процедура проверки. И в ecx и edx в нее вносятся введенный код и введенный ключ.

Давайте все же определимся, что нам предстоит сделать нам предстоит три раза взломать эту программу тремя разными способами:

Способы:

  • Подсмотр настоящего кода
  • Создание кейгена на основе самой программы
  • Написание кейгена на Делфи

Подсмотр настоящего кода

Самый простой и самый применяемый начинающими метод взлома, поскольку подсмотреть легко и вроде настоящий ключ на руках. (В первый раз я ее так и сломал)

Сперва заходим в процедуру проверки и идем в самый конец, а там смотрим, что творится перед выходом!

Запускаем любой отладчик, я использовал TRW 2000, поскольку перезагружаться не хотелось.


 :00427370 8D542410   lea edx, dword ptr [esp+10]
 :00427374 52         push edx
 :00427375 E816130700 call 00498690
 :0042737A 83C404     add esp, 00000004
 :0042737D 33C9       xor ecx, ecx
 :0042737F 3BE8       cmp ebp, eax
 :00427381 5F         pop edi
 :00427382 5E         pop esi
 :00427383 0F94C1     sete cl
 

Тут все просто, если сравнение верное, то в ecx будет 1, если нет то 0. Но что заносится параметром edx в процедуру? Давайте посмотрим для этого запускаем отладчик, ставим бряк на этот адрес bpx 427374. Вводим любое имя и код, я использовал Fess и 11002233. Когда вываливаемся, набираем команду d edx и что мы видим? Какие-то циферки, списываем их себе на бумажку, у меня 202382506, еще раз запускаем и вводим их вместо регистрационного номера. И что мы видим - МЫ ЗАРЕГЕНЫ!!! УРА!!! Вот и все, можете успокоится, а мы продолжаем.

Создание кейгена на основе самой программы

Я уже много раз описывал эту процедуру, но для Вас повторюсь еще разок. Так вот вы уже видели, как подсмотреть регистрационный номер в отладчике, а теперь мы сделаем, чтобы программа сама выдавала его в MessageBox'е. Для этого нужно, посмотреть hex-представление каманды MessageBoxA. Заходим в таблицу импорта в Win32Dasme щелкаем на USER32.MessageBoxA и видим


 :0040114F 50           push eax
 
 * Reference To: USER32.MessageBoxA, Ord:0195h
                             |
 :00401150 FF1554834A00 Call dword ptr [004A8354]
 :00401156 33C0         xor eax, eax
 

Hex-представление FF1554834A00 запишем, нам это еще пригодится. Теперь будем думать, как это осуществить на вот этом участке кода, знаком * в конце строки, я помечу, чем можно здесь пожертвовать и выкинуть


 :00427374 52         push edx        *
 :00427375 E816130700 call 00498690   *
 :0042737A 83C404     add esp, 00000004   *
 :0042737D 33C9       xor ecx, ecx    *
 :0042737F 3BE8       cmp ebp, eax    *
 :00427381 5F         pop edi
 :00427382 5E         pop esi
 :00427383 0F94C1     sete cl         *
 

Готово осталось написать последовательность команд не длиннее, заданного участка кода. Я решил сделать вот так: (Описание параметров функции MessageBoxA смотри в WinAPI или других моих тьюториалах)


  6A00         push 00000000
  52           push edx
  52           push edx
  6A00         push 00000000
  FF155483A400 Call MessageBoxA
  90           nop
  5F           pop edi
  5E           pop esi
  31C9         xor ecx,ecx
  90           nop
 

Вы, наверно, думаете почуму я заменил sete cl на xor ecx,ecx и nop очень просто, если так оставить, то получится обыкновенный патч и он зарегистрируется на введенное вами имя и чтобы повторно использовать этот кейген вам понадобится лезть в каталог WINDOWS и удалять из файла I_VIEW32.INI информацию о регистрации, а можно и весь файл, а тут она никогда не зарегистрируется и всегда можно генерить новые ключи.

Начинаем. Заходим в hex-редактор набираем для поиска такую строку 52E81613070083C404. Почему такую? Нам надо найти место правки кода, для этого мы переписываем достаточное кол-во байт исходного кода и ищем, где они встречаются. У меня все нашлось на 26774. Теперь начиная с этого места переписываем представление всех команд из блока выше. Если все выполнено правильно, то теперь после ввода регистрационных данных, в окне отображается истинный ключ для вашего имени. Гейген готов, но хочется большего, поэтому продолжаем.

Написание кейгена на Делфи

Для написания гейгена надо полностью исседовать код процедуры генерации и воссоздать его заново, это обычно делается в отладчике, но поскольку вы читаете этот тьюториал, я рядом с кодом буду писать, что он делает.


 ..
 :0042710E E87D150700    call 00498690 <- Процедура проверки введенного s/n, чтобы были
                                          только цифры
 ..
 :00427125 F2     repnz         <- Проверка длинны имени
 :00427126 AE     scasb
 :00427127 F7D1   not ecx
 :00427129 49     dec ecx       <- В ecx длинна имени
 :0042712A 85C9   test ecx, ecx <- Если длинна равна 0, то уходим из процедуры
 :0042712C 7E17   jle 00427145
 ..
 :0042712E 0FBE0C32 movsx ecx, byte ptr [edx+esi] <- Берем символ имени
 :00427132 03D9     add ebx, ecx <-Добавляем его к ebx
 :00427134 8BFE     mov edi, esi
 :00427136 83C9FF   or ecx, FFFFFFFF
 :00427139 33C0     xor eax, eax
 :0042713B 42       inc edx <- Увеличиваем счетчик (переход на след.символ)
 :0042713C F2       repnz
 :0042713D AE       scasb
 :0042713E F7D1     not ecx
 :00427140 49       dec ecx
 :00427141 3BD1     cmp edx, ecx <- Пока счетчик не будет равен длинне имени повтояем
 :00427143 7CE9     jl 0042712E
               (Общий смысл фрагмента сумма всех кодов имени в ebx)
 ..
 :00427145 B804010000    mov eax, 00000104 <- eax=104
 :0042714A 6A0A          push 0000000A
 :0042714C 2BC3          sub eax, ebx      <- eax=eax-ebx(сумма кодов)
 :0042714E 99            cdq
 :0042714F 33C2          xor eax, edx      <- eax = eax xor edx (edx=FFFFFFFF)
 :00427151 2BC2          sub eax, edx      <- eax = eax-edx
 :00427153 054C010000    add eax, 0000014C <- eax = eax+14C
 :00427158 8BD0          mov edx, eax      <- edx= eax
 :0042715A C1E203        shl edx, 03       <- edx = shl edx, 3
 :0042715D 2BD0          sub edx, eax      <- edx= edx-eax
 :0042715F 8D0C90        lea ecx, dword ptr [eax+4*edx]  <- ecx= eax+4*edx
 :00427162 8D542414      lea edx, dword ptr [esp+14]     <- edx=адрес памяти
 :00427166 52            push edx                        <- заносим адрес памяти в стек
 :00427167 8D3448        lea esi, dword ptr [eax+2*ecx]  <- esi= eax+2*ecx
 :0042716A C1E603        shl esi, 03                     <- esi = shl esi,3
 :0042716D 56            push esi                        <- заносим esi в стек
 :0042716E E84DE60700    call 004A57C0                   <- по адресу в edx выдается символьное
                                                         представление числа в esi
 :00427173 83C40C        add esp, 0000000C
 :00427176 81FE3F420F00  cmp esi, 000F423F               <- Если esi больше, то переход
 :0042717C 0F87ED000000  ja 0042726F
 ...
 
 :00427182 8A4C2414     mov cl, byte ptr [esp+14] <- Берем 5 символ кода
 :00427186 8A442415     mov al, byte ptr [esp+15] <- Берем 6 символ
 :0042718A 8A542413     mov dl, byte ptr [esp+13] <- Берем 4 символ
 :0042718E 884C2416     mov byte ptr [esp+16], cl <- На место 7 пишем 5
 :00427192 8A4C2411     mov cl, byte ptr [esp+11] <- Берем 2
 :00427196 88442418     mov byte ptr [esp+18], al <- На место 9 пишем 6
 :0042719A 8A442412     mov al, byte ptr [esp+12] <- Берем 3
 :0042719E 884C2412     mov byte ptr [esp+12], cl <- На место 3 пишем 2
 :004271A2 8B4C2418     mov ecx, dword ptr [esp+18] <- Берем 9
 :004271A6 88442413     mov byte ptr [esp+13], al <-На место 4 пишем 3
 :004271AA 81E1FF000000 and ecx, 000000FF
 :004271B0 88542415     mov byte ptr [esp+15], dl <- На место 6 пишем 4
 :004271B4 8B542414     mov edx, dword ptr [esp+14] <- Берем 5
 :004271B8 8D0489       lea eax, dword ptr [ecx+4*ecx] eax=ecx+4*усч
 :004271BB 81E2FF000000 and edx, 000000FF
 :004271C1 C1E003       shl eax, 03    <- eax= shl eax,3
 :004271C4 2BC1         sub eax, ecx   <- eax=eax-ecx
 :004271C6 8BCA         mov ecx, edx   <- ecx=edx
 :004271C8 C1E105       shl ecx, 05    <- ecx= shl ecx,5
 :004271CB 2BCA         sub ecx, edx   <- ecx=ecx-edx
 :004271CD 8D1449       lea edx, dword ptr [ecx+2*ecx] <-edx=ecx+2*ecx
 :004271D0 2BC2         sub eax, edx   <- eax=eax-edx
 :004271D2 99           cdq
 :004271D3 8BC8         mov ecx, eax   <- ecx=eax
 :004271D5 33CA         xor ecx, edx   <- ecx=ecx xor edx
 :004271D7 2BCA         sub ecx, edx   <- ecx=ecx-edx
 :004271D9 8D0489       lea eax, dword ptr [ecx+4*ecx] <- eax=ecx+4*ecx
 :004271DC C1E003       shl eax, 03  <- eax=shl eax,3
 :004271DF 2BC1         sub eax, ecx <- eax=eax-ecx
 :004271E1 B909000000   mov ecx, 00000009 <- ecx=9
 :004271E6 99           cdq
 :004271E7 F7F9         idiv ecx  <- eax=eax/ecx остаток в edx
 :004271E9 8B442413     mov eax, dword ptr [esp+13] <- Берем 4 символ
 :004271ED 25FF000000   and eax, 000000FF
 :004271F2 83C230       add edx, 00000030      <- edx=edx+30
 :004271F5 88542417     mov byte ptr [esp+17], dl <- Пишем 8-ым символом
 

А дальше вы и сами разберетесь, просто иначе место много займет, а там все аналогично. Я ту попарился минут с 40 (не сложно, а писанины много) и соорудил такой кейген. Создаете новое приложение, ставите на форму два компонента Edit с именами Edit1 (для ввода имени) и Edit2 (для вывода ключа) {они по умолчанию такие}, ставите одну кнопку. Нажимаете на нее два раза, вы окажитесь в процедуре обработки нажатия на кнопку, теперь замените ее моей и разбирайтесь:


 procedure TForm1.Button1Click(Sender: TObject);
 Var
   B,B1:Byte;
   Z:Dword;
   S:String;
 begin
   Z:=0;
   For B:=1 To Length(Edit1.Text) Do Z:=Z+ORD(Edit1.Text[B]); // Сумма кодов имени
   asm
    xor edx,edx;
    mov dl,B;
    mov ebx,Z;
    mov eax,104h;
    sub eax,ebx;
    cdq;
    xor eax,edx;
    sub eax, edx;
    add eax, 14Ch;
    mov edx, eax;
    shl edx, 03;
    sub edx, eax;
    mov ecx, eax
    imul edx,edx,4;
    add ecx, edx;
    mov ebx, eax;
    imul ecx,ecx,2
    add ebx,ecx;
    shl ebx, 03;
    mov Z, ebx;
   end;
   S:=IntToStr(Z)+'   ';
   if Z<$0F423F then
    begin
     S[9]:=S[6];
     s[7]:=S[5];
     S[6]:=S[4];
     S[4]:=S[3];
     S[3]:=S[2];
     B:=Ord(S[5]);
     B1:=Ord(S[9]);
     asm
     xor ecx,ecx;
     mov cl,B1;
     xor edx,edx;
     mov dl, B;
     imul eax, ecx,5;
     shl eax, 03;
     sub eax, ecx;
     mov ecx, edx;
     shl ecx, 05;
     sub ecx, edx;
     imul edx, ecx,3;
     sub eax, edx;
     cdq;
     mov ecx, eax;
     xor ecx, edx;
     sub ecx, edx;
     imul eax,ecx,5;
     shl eax, 03;
     sub eax, ecx;
     mov ecx, 00000009;
     cdq;
     idiv ecx;
     add dl,30h;
     mov B,dl;
     end;
     S[8]:=CHR(B);
     B:=Ord(S[4]);
     B1:=Ord(S[6]);
     asm
     xor eax,eax;
     mov al,B;
     imul edx,eax,3
     shl edx, 04;
     sub edx, eax;
     xor eax,eax;
     mov al, B1;
     imul ecx, eax,9;
     imul ecx,ecx,4;
     add eax, ecx;
     add eax, eax;
     add eax,edx;
     cdq;
     xor eax, edx;
     sub eax, edx;
     imul ecx, eax,9;
     imul ecx,ecx,4;
     add eax,ecx;
     mov ecx, 9;
     shl eax, 1;
     cdq;
     idiv ecx;
     add dl,30h;
     mov B,dl;
     end;
     S[5]:=CHR(B);
     B:=ORD(S[1]);
     B1:=ORD(S[2]);
     asm
     xor ecx,ecx;
     mov cl, B;
     imul eax, ecx,3;
     imul eax, eax,9;
     shl eax, 1;
     sub eax, ecx;
     xor edx,edx;
     mov dl, B1;
     mov ecx, edx;
     shl ecx, 03;
     sub ecx, edx;
     imul edx, ecx,5;
     sub eax, edx;
     cdq;
     mov ecx, eax;
     xor ecx, edx;
     sub ecx, edx;
     imul eax, ecx,3;
     imul eax, eax,9;
     shl eax, 1;
     sub eax, ecx;
     cdq;
     mov ecx, 09;
     idiv ecx
     add dl, 30h;
     mov B,dl
    end;
    S[2]:=CHR(B)
   end
   else
    begin
    S[9]:=S[7];
    S[7]:=S[6];
    S[6]:=S[5];
    S[4]:=S[3];
    S[3]:=S[2];
    B:=ORD(S[7]);
    B1:=Ord(S[9]);
    asm
    xor eax,eax
    mov al, B;
    mov ecx, eax
    shl ecx, 06
    sub ecx, eax
    xor eax,eax
    mov al, B1
    imul eax, eax,9
    shl eax, 02
    sub eax, ecx
    mov ecx, 09
    cdq
    xor eax, edx
    sub eax, edx
    imul eax, eax,9
    shl eax, 02
    cdq
    idiv ecx
    add dl, 30h
    mov b,dl
    end;
    S[8]:=CHR(B);
    B:=ORD(S[5]);
    B1:=ORD(S[4]);
    asm
    xor eax,eax
    mov al, b
    add eax, 00000020
    mov edx, eax
    shl edx, 03
    sub edx, eax
    imul edx,edx,4
    add eax,edx
    imul ecx, eax,3
    xor eax,eax
    mov al, B1
    imul edx, eax,5
    shl edx, 03
    sub edx, eax
    mov eax,ecx
    add edx,edx
    add eax,edx
    cdq
    xor eax, edx
    sub eax, edx
    mov ecx, eax
    shl ecx, 03
    sub ecx, eax
    imul ecx,ecx,4
    add eax, ecx
    mov ecx, 00000009
    imul eax, eax,3
    cdq
    idiv ecx
    add dl, 30h
    mov B,dl
    end;
    S[5]:=CHR(B);
    B:=ORD(S[1]);
    B1:=ORD(S[2]);
    asm
    xor eax,eax
    mov al,b
    mov edx, eax
    shl edx, 03
    sub edx, eax
    imul edx,edx,4
    add eax, edx
    xor edx,edx
    mov dl, B1
    mov ecx, edx
    shl ecx, 04
    add ecx, edx
    shl eax, 1
    imul ecx, ecx,5
    sub eax, ecx
    cdq
    xor eax, edx
    sub eax, edx
    mov edx, eax
    shl edx, 03
    sub edx, eax
    imul edx,edx,4
    add eax, edx
    shl eax, 1
    cdq
    mov ecx, 09
    idiv ecx
    add dl, 30h
    mov B,dl
    end;
    S[2]:=CHR(b);
    end;
    Edit2.Text:=S;
 end;
 

Процедура генерации опробована и проверена!! Так что ошибок нет. Сдалана на Delphi 4.5, я думаю, подойдут все версии, начиная с 4.

Будем надеятся, что вы все поняли из выше сказанного, если что-то непонятно пишите на мыло. Я помогу!

Спасибо за интерес к моему творчеству!

Удачи в Reversing Engeneering!

Послесловие

Спасибо автору за предоставленный для исследования продукт. Было очень интересно.

Господа, если у Вас есть деньги, то скиньте немного автору, поскольку продукт и правда замечательный. Так же выражаю ему благодарность за интересное времяпрепровождение.

Вредный совет

  Если ночью Вы не спите,
  Жмете клавиши на клаве,
  А соседи ваши ночью
  Очень любят отдыхать.
  То купите себе принтер
  Матричный, обыкновенный
  И тогда они узнают,
  Что такое тишина."
 	           (Fess)
 

Все ругательства отправлять в null
Все остальное на lomovskih@yandex.ru

З.Ы. Возможны ошибки. Взлом игры 3 минуты. Написание статьи 50 минут, видите как старался.

With best wishes Fess




Нахождение ключей для JPEG Optimizer v2.02 (взлом без Soft-Ice)

Автор: Fess
WEB сайт: vallkor.chat.ru

Target: JPEG Optimizer v2.02

Tools:
Some brains
Win32Dasm 8.93

Все, кроме мозгов, можно найти на www.exetools.com

Вступление

Как это начиналось:

Принесли мне компакт от какой-то книги издательства Питер, кажется книга про Web. Там валялись всякие проги для работы с графикой и html, не долго думая, я решил скопировать все это на свой хард, чтобы потом поразобраться...

Спустя неделю ко мне в руки попал компакт с огромным количеством картинок в формате jpg. При не очень большом разрешении их размер достигал 500 кило, и это меня не обрадовало. Их было так много, что я сразу отбросил мысль об использовании IrfanView'а (очень рулезная прога, всем рекомендую www.irfanview.com). Тут я вспомнил о компакте, где были проги для работы с графикой, а точнее оптимизаторы. Так я вышел на эту прогу. К моему сожалению, она просила за регистрацию $29 и это была ее главная ошибка. Приступаем

Что за прога:

Очень хорошая прога для сжатия графики, позволяет обрабатывать файлы в автоматическом режиме. Короче мне понравилась, я ее себе даже русифицировал, если кому интересно, то напишите на мыло. Размер в архиве примерно 400Kb. Единственный недостаток - регистрация, который мы сейчас исправим.

Начало

Взглянув на ресурсы, я понял, что программа написана на Delphi и это хорошо (присутствуют bmp файлы всегда вставляющиеся Delphi). Запускаем ее, заходим в раздел Register и вводим произвольный пароль. На это нам выплевывается надпись Incorrect Registration Code. Это будет главная зацепка.

Кидаем программу в Win32Dasm. И в секции строк ищем нашу. Нашли. Нажимаем на нее два раза и оказываемся тут


 :00427F8E 50            push eax
 :00427F8F FF4E1C        dec [esi+1C]
 :00427F92 8D45F8        lea eax, dword ptr [ebp-08]
 :00427F95 BA02000000    mov edx, 00000002
 :00427F9A E891D10100    call 00445130
 :00427F9F 59            pop ecx
 :00427FA0 84C9          test cl, cl
 :00427FA2 0F84B7000000  je 0042805F
 :00427FA8 66C746102C00  mov [esi+10], 002C
 
 * Possible StringData Ref from Data Obj ->"JPEG Optimizer"
                            |
 :00427FAE BA6D9B4700    mov edx, 00479B6D
 (всякий мусор выкинут)
 :00427FF0 8D4DEC        lea ecx, dword ptr [ebp-14]
 
 * Possible StringData Ref from Data Obj ->"Code"
                            |
 :00427FF3 BA7C9B4700    mov edx, 00479B7C
 (здесь выкинут код отвечающий за запись правильного кода в реестр)
 :00428040 8BC3          mov eax, ebx
 :00428042 E8054A0200    call 0044CA4C
 :00428047 6A40          push 00000040
 
 * Possible StringData Ref from Data Obj ->"Message"
                            |
 :00428049 B9AA9B4700    mov ecx, 00479BAA <- Выводится сообщение о правильной регистрации.
 
 * Possible StringData Ref from Data Obj ->"Thank you for registering JPEG "
                                        ->"Optimizer"
                             |
 :0042804E BA819B4700    mov edx, 00479B81
 :00428053 A104F54700    mov eax, dword ptr [0047F504]
 :00428058 E843A80300    call 004628A0
 :0042805D EB16          jmp 00428075
 
 * Referenced by a (U)nconditional or (C)onditional Jump at Address:
 |:00427FA2(C) <- Вот оттуда переход на првильный или неправильный
 |
 :0042805F 6A30          push 00000030
 
 * Possible StringData Ref from Data Obj ->"Error"
                                   |
 :00428061 B9CE9B4700    mov ecx, 00479BCE
 
 * Possible StringData Ref from Data Obj ->"Incorrect Registration Code"
                                  |
 :00428066 BAB29B4700    mov edx, 00479BB2
 :0042806B A104F54700    mov eax, dword ptr [0047F504]
 :00428070 E82BA80300    call 004628A0
 

Вроде бы проверка в процедуре call 445130. Но меня смутила строка 427F9F (она выделена). А потом я просек в чем дело, взглянув чуть выше, строка 427F8E. Я уж не знаю, сами ли программеры решили так нас обмануть, а может компилятор. Скорее всего второе. Теперь смотрим повыше


 :00427F75 BA6C9B4700  mov edx, 00479B6C
 
 * Referenced by a (U)nconditional or (C)onditional Jump at Address:
 |:00427F73(U)
 |
 :00427F7A 52         push edx
 :00427F7B E8380A0000 call 004289B8
 :00427F80 59         pop ecx
 :00427F81 84C0       test al, al     <- Проверяем
 :00427F83 7504       jne 00427F89    <- Если все тип-топ (al<>0), то переход
 
 * Referenced by a (U)nconditional or (C)onditional Jump at Address:
 |:00427F68(C)
 |
 :00427F85 33C0       xor eax, eax
 :00427F87 EB05       jmp 00427F8E
 
 * Referenced by a (U)nconditional or (C)onditional Jump at Address:
 |:00427F83(C)
 |
 :00427F89 B801000000 mov eax, 00000001
 

Что ж идем в процедуру и попробуем разобраться в коде.


 :004289B8 55       push ebp
 :004289B9 8BEC     mov ebp, esp
 :004289BB 83C4F8   add esp, FFFFFFF8
 :004289BE 53       push ebx
 :004289BF 8B4508   mov eax, dword ptr [ebp+08]
 :004289C2 8D5DF8   lea ebx, dword ptr [ebp-08]
 :004289C5 8A10     mov dl, byte ptr [eax]     <-
 :004289C7 8813     mov byte ptr [ebx], dl     <-
 :004289C9 8A4801   mov cl, byte ptr [eax+01]  <-
 :004289CC 884B01   mov byte ptr [ebx+01], cl  <-
 :004289CF 8A5002   mov dl, byte ptr [eax+02]  <-
 :004289D2 885302   mov byte ptr [ebx+02], dl  <- Копируем 6 байт регистрационного кода
 :004289D5 8A4803   mov cl, byte ptr [eax+03]  <- в другое местов оперативной памяти
 :004289D8 884B03   mov byte ptr [ebx+03], cl  <-
 :004289DB 8A5004   mov dl, byte ptr [eax+04]  <-
 :004289DE 885304   mov byte ptr [ebx+04], dl  <-
 :004289E1 8A4005   mov al, byte ptr [eax+05]  <-
 :004289E4 884305   mov byte ptr [ebx+05], al  <-
 :004289E7 0FBE0B   movsx ecx, byte ptr [ebx]  <-
 :004289EA 51       push ecx
 :004289EB E8AC240400  call 0046AE9C
 :004289F0 59       pop ecx
 :004289F1 83F851   cmp eax, 00000051  <- Проверка кода 1 символа пароля с кодом буквы Q
 :004289F4 7544     jne 00428A3A       <- Если не правильно, то переход
 :004289F6 0FBE4301 movsx eax, byte ptr [ebx+01]
 :004289FA 50       push eax
 :004289FB E89C240400  call 0046AE9C
 :00428A00 59       pop ecx
 :00428A01 83F84F   cmp eax, 0000004F  <- Проверка кода 2 символа пароля с кодом буквы O
 :00428A04 7534     jne 00428A3A       <- Если не правильно, то переход
 :00428A06 0FBE5302 movsx edx, byte ptr [ebx+02]
 :00428A0A 83FA37   cmp edx, 00000037  <- Проверка кода 3 символа пароля с кодом символа 7
 :00428A0D 752B     jne 00428A3A       <- Если не правильно, то переход
 :00428A0F 0FBE4B03 movsx ecx, byte ptr [ebx+03]
 :00428A13 83F930   cmp ecx, 00000030  <- Проверка кода 4 символа пароля с кодом символа 0
 :00428A16 7522     jne 00428A3A       <- Если не правильно, то переход
 :00428A18 0FBE4304 movsx eax, byte ptr [ebx+04]
 :00428A1C 83F831   cmp eax, 00000031  <- Проверка кода 5 символа пароля с кодом символа 1
 :00428A1F 7519     jne 00428A3A       <- Если не правильно, то переход
 :00428A21 0FBE5305 movsx edx, byte ptr [ebx+05]
 :00428A25 83FA32   cmp edx, 00000032  <- Проверка кода 6 символа пароля с кодом символа 2
 :00428A28 7510     jne 00428A3A       <- Если не правильно, то переход
 :00428A2A C605F0EC470001 mov byte ptr [0047ECF0], 01
 :00428A31 E85AAEFDFF  call 00403890
 :00428A36 B001      mov al, 01  <- Вроде как код введен правильно
 {В итоге для того, чтобы пройти эту проверку нужно написать код QO7012}
 :00428A38 EB18      jmp 00428A52
 
 * Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
 |:004289F4(C), :00428A04(C), :00428A0D(C), :00428A16(C), :00428A1F(C)
 |:00428A28(C)
 |
 :00428A3A 53       push ebx
 :00428A3B E8501A0000  call 0042A490
 :00428A40 59       pop ecx
 :00428A41 84C0     test al, al
 :00428A43 7404     je 00428A49
 :00428A45 B001     mov al, 01
 :00428A47 EB09     jmp 00428A52
 
 * Referenced by a (U)nconditional or (C)onditional Jump at Address:
 |:00428A43(C)
 |
 :00428A49 C605F0EC470000 mov byte ptr [0047ECF0], 00
 :00428A50 33C0     xor eax, eax
 
 * Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
 |:00428A38(U), :00428A47(U)
 |
 :00428A52 5B       pop ebx
 :00428A53 59       pop ecx
 :00428A54 59       pop ecx
 :00428A55 5D       pop ebp
 :00428A56 C3       ret
 

И вроде бы, код найден, но меня заинтересовала процедура call 0042A490 (она выделена). После нее AL тоже устанавливается в единицу. И я решил проверить.


 :0042A490 55      push ebp
 :0042A491 8BEC    mov ebp, esp
 :0042A493 53      push ebx
 :0042A494 8B5D08  mov ebx, dword ptr [ebp+08] <- В ebx адрес памяти с введенным кодом
 (какой-то мутный код)
 :0042A4B1 33D2    xor edx, edx <- Обнуляем edx
 :0042A4B3 8BC3    mov eax, ebx <- В eax указатель на введенный код
 
 * Referenced by a (U)nconditional or (C)onditional Jump at Address:
 |:0042A4BD(C)
 |
 :0042A4B5 803093   xor byte ptr [eax], 93 <- Ксорим все символы введеного кода с 93h
 :0042A4B8 42       inc edx                <- Увеличиваем счетчик
 :0042A4B9 40       inc eax                <- Увеличиваем указатель
 :0042A4BA 83FA06   cmp edx, 00000006      <- Сравниваем счетчик с 6
 :0042A4BD 7CF6     jl 0042A4B5            <- Если меньше, то повторяем цикл
 :0042A4BF 0FBE0B   movsx ecx, byte ptr [ebx]  <- Берем 1 символ отxorенного кода
 :0042A4C2 83F9D8   cmp ecx, FFFFFFD8          <- Сравниваем с
 :0042A4C5 7545     jne 0042A50C               <- Если не равно, то в пролете
 {Значит надо, чтобы было равно этим мы вычислим символ, который надо написать в коде,
 можно, конечно, попробовать угадать в дебаггере или написать программу, но мы попробуем
 посчитать все в ручную. Как, надеюсь, вам известно, что функция xor обратима, поэтому
 ее часто используют для шифрования. Функция устанавливаем бит в единицу, если один
 операнд единица, а второй ноль, иначе будет ноль. Переводим 93h в двоичную систему,
 это можно сделать с помощью простого виндового калькулятора, можно тетрадами, а можно
 в ручную.
 93h - 10010011           93h - 10010011            93h - 10010011
 D8h - 11011000           D9h - 11011001            A4h - 10100100
 --------------           --------------            --------------
       01001011 -> 4Bh(K)       01001010 -> 4Ah(J)        00110111 - 37h(7)
 и так далее. В итоге получаем KJ7246}
 :0042A4C7 0FBE4301 movsx eax, byte ptr [ebx+01]
 :0042A4CB 83F8D9   cmp eax, FFFFFFD9
 :0042A4CE 753C     jne 0042A50C
 :0042A4D0 0FBE5302 movsx edx, byte ptr [ebx+02]
 :0042A4D4 83FAA4   cmp edx, FFFFFFA4
 :0042A4D7 7533     jne 0042A50C
 :0042A4D9 0FBE4B03 movsx ecx, byte ptr [ebx+03]
 :0042A4DD 83F9A1   cmp ecx, FFFFFFA1
 :0042A4E0 752A     jne 0042A50C
 :0042A4E2 0FBE4304 movsx eax, byte ptr [ebx+04]
 :0042A4E6 83F8A7   cmp eax, FFFFFFA7
 :0042A4E9 7521     jne 0042A50C
 :0042A4EB 0FBE5305 movsx edx, byte ptr [ebx+05]
 :0042A4EF 83FAA5   cmp edx, FFFFFFA5
 :0042A4F2 7518     jne 0042A50C
 :0042A4F4 C605F0EC470001  mov byte ptr [0047ECF0], 01
 :0042A4FB C60500F4470001  mov byte ptr [0047F400], 01
 :0042A502 E88993FDFF       call 00403890
 :0042A507 B001     mov al, 01 <- Если все хорошо
 :0042A509 5B       pop ebx
 :0042A50A 5D       pop ebp
 :0042A50B C3       ret
 
 * Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
 |:0042A4C5(C), :0042A4CE(C), :0042A4D7(C), :0042A4E0(C), :0042A4E9(C)
 |:0042A4F2(C)
 |
 :0042A50C 33C0     xor eax, eax  <- здесь не есть хорошо
 :0042A50E 5B       pop ebx
 :0042A50F 5D       pop ebp
 :0042A510 C3       ret
 

Ну вот и все, найдены два кода QO7012 и KJ7246. Чем они отличаются, честно говоря не знаю. Но все-таки думаю, что второй код приоритетней, т.к. он был замудрован посильнее первого (хотя и не очень сильно).

Спасибо за интерес к моему творчеству!

Удачи в Reversing Engeneering!

Послесловие

Спасибо авторам за предоставленный для исследования продукт. Он мне очень помог при переделывании картинок.

Господа Авторы: "Худшая защита месяца" только так можно охарактеризовать то, что я увидел в этой программе. Перед тем, как делать защиту можно было и почитать мануалы, написанные в этом направлении. Извините меня, но $29 для России это не дела. Крэкеров в Росии много. Россия рулез!

Братья Крэкеры: Не стоит сильно ругать авторов, они там за бугром не ведают, что творят.

Автор: Fess
Все ругательства отправлять в null
Все остальное на lomovskih@yandex.ru

P.S. Запомните все материалы публикуются только в учебных целях и автор за их использование ответственности не несет!!

P.P.S. Возможно имеют место опечатки, заранее извините!

Самые последние мои статьи можно найти на vallkor.chat.ru

With best wishes Fess Member of the group PTDS

И да пребудет с вами великий дух bad-сектора




<< ВЕРНУТЬСЯ В ОГЛАВЛЕНИЕ



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



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


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