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

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


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

Исследование кода, генерируемого Delphi. Часть 2

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

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

Часть 2. Обработка исключений и сообщений

Итак, продолжим. На сей раз я наваял приложение, использующее несколько более продвинутые технологии, предоставляемые Delphi - exceptions handling ( перехват исключений ), virtual & dynamic функции, обработку формой сообщений Windows, производные классы и загрузку строковых ресурсов из реестра. Исходный код моей программы мог бы выглядеть как-нибудь так:

 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   StdCtrls;
 
 type
   TRPEnum = ( RP_One, RP_Two, RP_Tree );
   TRPEnumSet = set of TRPEnum;
 
   TRPException = class(Exception)
   private
    RP_Array: array[7..9] of string;
    Code: TRPEnumSet;
   public
    Procedure Old_one_virtual; virtual;
    Procedure Old_one_dynamic; dynamic;
    Constructor Create;
    Destructor Destroy; override;
   end;
 
   TRPExceptionChild = class(TRPException)
    Procedure Old_one_virtual; override;
    Procedure Old_one_dynamic; override;
   end;
 
   TForm1 = class(TForm)
     Button1: TButton;
     procedure Button1Click(Sender: TObject);
     procedure FormCreate(Sender: TObject);
   private
     { Private declarations }
     FDesignedWidth, FDesignedHeight: Integer;
     procedure BuggyOne;
     Procedure WMSizing( var Message: TMessage ); message WM_SIZING;
   public
     { Public declarations }
   end;
 
 var
   Form1: TForm1;
 
 implementation
 
 {$R *.DFM}
 
 resourcestring
  BuggyOneCaption = 'BuggyOne';
  MalformedException = 'Malformed exception';
 
 (* TRPException *)
 Constructor TRPException.Create;
 begin
  Application.MessageBox('Create', 'TRPException', ID_OK);
  inherited Create('BuggyOne object');
 end;
 
 Destructor TRPException.Destroy;
 begin
  Application.MessageBox('Destroy', 'TRPException', ID_OK);
  Inherited;
 end;
 
 Procedure TRPException.Old_one_virtual;
 begin
  Application.MessageBox('Old_one_virtual','TRPException', ID_OK);
 end;
 
 Procedure TRPException.Old_one_dynamic;
 begin
  Application.MessageBox('Old_one_dynamic','TRPException', ID_OK);
 end;
 
 (* TRPExceptionChild *)
 Procedure TRPExceptionChild.Old_one_virtual;
 begin
  Application.MessageBox('Old_one_virtual','TRPExceptionChild', ID_OK);
 end;
 
 Procedure TRPExceptionChild.Old_one_dynamic;
 begin
  Application.MessageBox('Old_one_dynamic','TRPExceptionChild', ID_OK);
 end;
 
 (* TForm1 *)
 procedure TForm1.BuggyOne;
 var
  RP_E: TRPExceptionChild;
  N: Integer;
 begin
  MessageDlg(BuggyOneCaption,mtConfirmation,[mbOk],0);
 try
  RP_E := TRPExceptionChild.Create;
  RP_E.Code := [RP_One];
  RP_E.RP_Array[7] := 'Seven';
  N := 9;
  RP_E.RP_Array[8] := 'Eight';
  RP_E.RP_Array[N] := 'Nine inch nails';
  RP_E.Code := RP_E.Code + [RP_Two];
  Raise RP_E;
  MessageDlg('Not will showed at the end',mtConfirmation,[mbOk],0);
 finally
  MessageDlg('In finally part',mtConfirmation,[mbOk],0);
 end;
  MessageDlg('Not will showed at the end',mtConfirmation,[mbOk],0);
 end;
 
 procedure TForm1.Button1Click(Sender: TObject);
 begin
  MessageDlg('Button1Click',mtConfirmation,[mbOk],0);
  try
   BuggyOne;
  except
   on E:TRPException do
    begin
     MessageDlg('Button1Click in exception block',mtConfirmation,[mbOk],0);
     E.Old_one_virtual;
     E.Old_one_dynamic;
    end;
   on E:TRPExceptionChild do
    begin
     MessageDlg(MalformedException,mtConfirmation,[mbOk],0);
    end;
  end;
  MessageDlg('Button1Click at the end',mtConfirmation,[mbOk],0);
 end;
 
 Procedure TForm1.WMSizing( var Message: TMessage );
 var
  PRect : ^TRect;
 Begin
   PRect := Pointer (Message . LParam );
   if PRect^. Right - PRect^. Left < FDesignedWidth then
   begin
     if Message.WParam in [ WMSZ_BOTTOMLEFT, WMSZ_LEFT, WMSZ_TOPLEFT ]
     then
      PRect^.Left := PRect^ . Right - FDesignedWidth
     else
      PRect^.Right := PRect^ . Left + FDesignedWidth;
   end;
   if PRect^ . Bottom - Prect^.Top < FDesignedHeight then
   begin
     if Message . WParam in [ WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT ]
     then
      PRect^.Top := PRect^ . Bottom - FDesignedHeight
     else
      PRect^. Bottom := PRect^ . Top + FDesignedHeight;
   end;
 End;
 
 procedure TForm1.FormCreate(Sender: TObject);
 begin
  FDesignedWidth := Width;
  FDesignedHeight := Height;
  MessageDlg('FormCreate',mtConfirmation,[mbOk],0);
 end;
 
Не вершина программистского мастерства, конечно, но для наших целей вполне годится. Итак, запустим дизассемблер ( я использовал IDA 3.8b ) и не забудьте применить файл сигнатур для библиотеки VCL версии 4 ( d4vcl ) - в моём случае IDA опознала 2172 функции.
А пока IDA делает грязную работу за нас, можно предаться чтению документации ( весьма рекомендую заниматься этим время от времени - можно узнать столько интересного :-). Итак, что мы можем узнать из официальной документации по Delphi о тонкой разнице между динамическими (dynamic) и виртуальными (virtual) методами ?
Virtual методы расположены в таблице виртуальных методах, по традиции называемой VTBL, которая дублируется для каждого производного класса. Если в производном классе переопределяется новый метод, указатель на него будет в этой таблице под тем же индексом, что и в VTBL класса-предка - но указывать он будет на перегруженный метод. За счёт этого достигается наилучшая скорость - вызов функции по указателю через смещение в VTBL. С другой стороны, для каждого нового класса полностью дублируется вся VTBL ! Короче, классический случай ножниц "скорость против размера".
Dynamic методы имеют несколько другой способ хранения. Им назначается некоторый индекс - но не в таблице, а в hash-структуре. Также эта структура не дублируется для каждого производного класса - если переопределяется dynamic метод, он переопределяется для данного класса - и всё. Но вызов dynamic методов имеет больше накладных расходов - при вызове Delphi просматривает все классы-предки данного класса в поисках метода с нужным индексом.
Посмотрим, как всё вышесказанное выглядит на Ассемблере:
Я надеюсь, Вы ещё помните, насколько полезна бывает RTTI ? RTTI класса нашей единственной формы расположена по адресу 0x44016C. На сей раз она содержит по смещению 1Ch ненулевое значение, а указатель на hash-массив dynamic методов. Структура эта имеет примерно такой вид:

Смещениетипописание
0WORDРазмер N hashа
2WORDN слов - индексов dynamic методов
N * 2 + 2DWORDN указателей на функции

Что ещё более интересно, в нашём случае индекс единственной функции - WMSizing 0x214. Если Вы посмотрите в файле заголовков messages.pas, 0x214 ( eq 532 ) есть значение сообщения WM_SIZING. В Borlandе, видимо, простые парни работают...
Итак, сейчас у нас есть более полное описание RTTI, я позволю себе повторить его здесь полностью:

смещениетипописание
0DWORDуказатель на VTBL
4DWORDзначение не выяснено (vmtIntfTable)
8DWORDзначение не выяснено (vmtAutoTable)
ChDWORDзначение не выяснено (vmtInitTable)
10hDWORDуказатель на список наследований
14hDWORDуказатель на компоненты, которыми владеет данный класс
18hDWORDуказатель на массив обработчиков событий
1ChDWORDуказатель на hash dynamic методов
20hDWORDуказатель на Pascal-строку - имя класса
24hDWORDразмер класса
28hDWORDуказатель на структуру RTTI класса-предка данного класса
2ChDWORDуказатель на метод SafeCallException
30hDWORDуказатель на метод AfterConstruction
34hDWORDуказатель на метод BeforeDestruction
38hDWORDуказатель на метод Dispatch
3ChDWORDуказатель на метод DefaultHandler
40hDWORDуказатель на метод NewInstance
44hDWORDуказатель на метод FreeInstance
48hDWORDуказатель на метод Destroy
4ChDWORDsначало VTBL

Давайте рассмотрим самую примечательную функцию в моей программе - TForm1.Button1Click. Примечательна она исключительно тем, что вызывает функцию BuggyOne, выбрасывающую исключение, которое затем сама же и ловит двумя руками.
 BuggyOne        proc near
 
 var_4           = dword ptr -4
 
                 push    ebp
                 mov     ebp, esp
                 push    0	; инициализация в 0 var_4
                 push    ebx	; сохранить ebx
                 push    esi	; и esi
                 xor     eax, eax
                 push    ebp     ; и ещё ebp
                 push    offset loc_0_44064F	; поместим в стек адрес
 						; finally кода
                 push    dword ptr fs:[eax]      ; и прежнее значение стека
 						; обработки исключений
                 mov     fs:[eax], esp		; в стек обработки исключений
 			     ; помещается указатель на текущее значение стека
                 push    0
                 lea     edx, [ebp+var_4]	; загрузим в var_4 строку из
                 mov     eax, offset off_0_440368	; ресурсов
                 call    @LoadResString
 ...
 loc_0_440646:
                 lea     eax, [ebp+var_4]	; очистить строку в var_4
                 call    @@LStrClr       ; ::`intcls'::LStrClr
                 retn
 ; ------------------------------------------------------------------
 loc_0_44064F:
                 jmp     @@HandleFinally ; ::`intcls'::HandleFinally
 ; ------------------------------------------------------------------
                 jmp     short loc_0_440646
 
Обратите внимание на две вещи:

1) инициализация стека исключений. В стек помещается указатель 0x44064f, далее значение стека заносится с fs:[0]. По адресу 0x44064f нет ничего примечательного - просто переход на одинаковый для всех кусок кода HandleFinally. Но вот сразу за ним идёт код, который, казалось бы, никогда не достигается - переход на 0x440646. Но вот этот-то код как раз и есть finally часть в обработке исключений. В данном случае - это освобождение строки var_4.
HandleFinally:
 @@HandleFinally:
          mov     eax, [esp+4]
          mov     edx, [esp+8]
          test    dword ptr [eax+4], 6
          jz      short loc_0_403294
          mov     ecx, [edx+4]	; адрес перехода на HandleFinally
          mov     dword ptr [edx+4], offset loc_0_403294
          push    ebx
          push    esi
          push    edi
          push    ebp
          mov     ebp, [edx+8]
          add     ecx, 5          ; добавим к нему 5
          call    @System@_16583  ; System::_16583
          call    ecx		; и вызовем как функцию
          pop     ebp
          pop     edi
          pop     esi
          pop     ebx
 loc_0_403294:
          mov     eax, 1
          retn
 
На момент вылета на этот код в fs:[0] и eax содержится указатель стека, в котором находятся ранее занесённые в него ( смотрите начало процедуры BuggyOne; также я привык изображать вершину стека сверху, а не как оно есть на самом деле ):
  • [eax + 4] прежнее значение стека в fs:[0]
  • [eax + 8] указатель на инструкцию перехода к HandleFinally
  • [eax + 0xC] ebp
Т.е. происходит следующее - инструкция следом за переходом на HandleFinally является процедурой обработки finally-части ( так как размер инструкции "jmp HandleFinally" равен ровно 5 байт )

2) Загрузка строки из ресурса. Строка BuggyOneCaption описана как resourcestring - это значит, что Delphi помесила её в строковую таблицу. Прототип функции LoadResString ( из Sys/System.pas ):
 type
   PResStringRec = ^TResStringRec;
   TResStringRec = record
     Module: ^Longint;
     Identifier: Integer;
   end;
 
 function LoadResString(ResStringRec: PResStringRec): string;
 
Module - handler загруженного модуля, содержащего в себе ресурс. Для нашей программы это hInstance самого приложения ( поскольку главная форма находится в том же модуле, что и объект TApplication ).
Identifier - целое число, меньшее 65536, или указатель на LPSZ строку - имя ресурса. По адресу 0x440368 содержится:
 off_0_440368    dd offset dword_0_4424D8 ; hModule приложения
                 dd 0FF5Dh		 ; Identifier
 
Число 0xFF5D = 65373 < 65536, так что наша строка идентифицируется по числовому значению. Посмотрим ресурсы моей программы в редакторе ресурсов Restorator ( кстати, весьма рекомендую эту программу для исследований приложений на Delphi - она умеет показывать описание Delphi-форм ! ). Наша строка нашлась в секции string tables под номером секции 4086, смещение -3. Как это соотносится с ранее найденным значением идентификатора ? Очень просто:
  65373 = 4086 * 16 - 3;
 
Всё гениальное просто ( однако не всё простое гениально ).
       xor     eax, eax
       push    ebp
       push    offset loc_0_44061D  ; новый finally handler
       push    dword ptr fs:[eax]
       mov     fs:[eax], esp
       mov     dl, 1
       mov     eax, ds:off_0_44016C ; ptr to TRPExceptionChild RTTI
       call    sub_0_440378         ; TRPExceptionChild::Create
       mov     ebx, eax
       mov     al, ds:byte_0_440660 ; db 1 eq RP_One из TRPEnum
       mov     [ebx+18h], al
       lea     eax, [ebx+0Ch]
       mov     edx, offset aSeven   ; "Seven"
       call    @@LStrAsg       ; ::`intcls'::LStrAsg
       mov     esi, 9
       lea     eax, [ebx+10h]
       mov     edx, offset aEight   ; "Eight"
       call    @@LStrAsg       ; ::`intcls'::LStrAsg
       lea     eax, [ebx+esi*4-10h]
       mov     edx, offset aNineInchNails	; "Nine inch nails"
       call    @@LStrAsg       ; ::`intcls'::LStrAsg
       mov     al, [ebx+18h]
       or      al, ds:byte_0_44069C ; db 2 eq RP_Two из TRPEnum
       mov     [ebx+18h], al
       mov     eax, ebx
       call    @@RaiseExcept   ; ::`intcls'::RaiseExcept
 ...
 loc_0_44061D:
       jmp     @@HandleFinally
 ; ----------------------------------------
       jmp     short loc_0_440607
 ...
 loc_0_440607:
       push    0
       mov     cx, ds:word_0_44065C
       mov     dl, 3
       mov     eax, offset aInFinallyPart
       call    @MessageDlg
       retn
 
Дальше совсем просто. Инициализируется новый обработчик finally части, при этом указатель на старое значение стека также помещается в стек. Далее вызывается конструктор TRPExceptionChild::Create - первым аргументом ему передаётся указатель на RTTI класса TRPExceptionChild, а вторым ( в регистре dl, я не знаю для чего ) 1 - указатель на созданный экземпляр класса возвращается в регистре eax, и затем пересылается в ebx, который используется в дальнейшем как базовый регистр. Члену Code присваивается значение RP_One ( eq 1 ) из набора TRPEnum. Можно заметить, что Code расположена в классе TRPException по смещению 0x18h. Затем идёт присваивание значений массиву строк - массив начинается по смещению 0xC. Довольно непонятно выглядит присваивание последнему ( 9ому элементу массива ): он должен быть расположен по смещению 0xC + (3 - 1) * 4 = 0x14; 9 * 4 - 0x10 даёт то же самое 0x14, но какова логика ! Затем к нашему набору Code добавляется RP_Two ( eq 2 ). Потом вызывается процедура RaiseExcept с единственным аргументом в eax - адресом нашего класса.
Пожалуй, в BuggyOne больше нет ничего интересного.
Button1Click
  ...
         push    offset loc_0_440728
         push    dword ptr fs:[edx]
         mov     fs:[edx], esp
         mov     eax, ebx
         call    BuggyOne	; процедура, генерирующая исключение
         xor     eax, eax
         pop     edx
         pop     ecx
         pop     ecx
         mov     fs:[eax], edx
         jmp     short loc_0_440790
 ; --------------------------------------------------------
 loc_0_440728:
         jmp     @@HandleOnException ; ::`intcls'::HandleOnExceptions
 ; --------------------------------------------------------
         dd 2	; размер фильтров исключений
         dd offset off_0_4400F4  ; адрес RTTI TRPException
         dd offset loc_0_440741  ; адрес код для TRPException
         dd offset off_0_44016C  ; TRPExceptionChild
         dd offset loc_0_44076B  ; On TRPExceptionChild
 ; ----------------------------------------------------------------
 loc_0_440741:
         mov     ebx, eax
         push    0
         mov     cx, ds:word_0_4407C8
         mov     dl, 3
         mov     eax, offset aButton1clickIn
 		; строка "Button1Click in exception block"
         call    @MessageDlg
         mov     eax, ebx
         mov     edx, [eax]	; вызов TRPExceptionChild::Old_one_virtual
         call    dword ptr [edx]
         mov     eax, ebx
         mov     bx, 0FFFFh	; вызов TRPExceptionChild::Old_one_dynamic
 				; имеет индекс 0xFFFF
         call    @@CallDynaInst  ; ::`intcls'::CallDynaInst
         jmp     short loc_0_44078B
 
Здесь можно увидеть в действии механизм фильтрации и обработки исключений. Опять в fs:[0] помещается указатель на стек, но на сей раз в него помещён адрес инструкции перехода к процедуре обработке исключений HandleOnExceptions. Следом за ней расположен массив фильтров исключений. Он имеет весьма незатейливую структуру:

Смещениетипописание
0DWORDРазмер N массива фильтров исключений
4DWORDУказатель на RTTI класса - объекта исключение
8DWORDУказатель на код, вызываемый при исключении этого класса
4 + M * 4DWORDУказатель на M-ную RTTI класса - объекта исключение
8 + M * 4DWORDУказатель на код, вызываемый при исключении M-ного класса

Далее мы можем наблюдать вызовы virtual & dynamic функций - соответственно, TRPExceptionChild::Old_one_virtual & TRPExceptionChild::Old_one_dynamic.
1) Вызов TRPExceptionChild::Old_one_virtual
Простой и понятный вызов функции по указателю. Первым членом любого класса идёт указатель на VTBL - по смещению 0x0; метод Old_one_virtual является единственным в VTBL класса TRPExceptionChild - соответственно, он расположен под индексом 0. Под тем же самым индексом в VTBL класса TRPException расположен виртуальный метод TRPException::Old_one_virtual

2) Вызов TRPExceptionChild::Old_one_dynamic
В hash dynamic методов класса TRPException метод TRPException::Old_one_dynamic прописан под индексом 0xFFFF. Сложно сказать, что будет, если, имея dynamic метод с индексом 0xFFFF, Вы попробуете обработать событие Windows с номеров 0xFFFF ( можете попробовать сделать это самостоятельно ). Остаётся надеятся, что Delphi всё-таки отслеживают занятые индексы для динамических методов.
Как видите, для вызова динамических методов используется вызов функции CallDynaInst с передаваемым в регистре bx индексом метода.

Приложение A

Поскольку я человек ленивый ( лень - двигатель прогресса ) и мне совершенно не хотелось вручную сообщать IDA Pro, что это не просто нечто бесформенное, а самая что ни наесть структура RTTI ( при этом ещё мучительно вспоминая, чего там идёт под каким смещением ), я написал небольшой script на IDC, который позволяет мне иметь немного свободного времени для прямых обязанностей сисадмина, а именно - для чтения newsов и взлома программ...

 /*
  * This script deal with Delphi RTTI structures
  *
  * 	Red Plait, 23-VIII-1999
  */
 #include <idc.idc>
 
 // makes dword and offset to data
 static MakeOffset(adr)
 {
   auto ref_adr;
 
   MakeUnkn(adr,0);
   MakeUnkn(adr+1,0);
   MakeUnkn(adr+2,0);
   MakeUnkn(adr+3,0);
   MakeDword(adr);
   ref_adr = Dword(adr);
   if ( ref_adr != 0 )
    add_dref(adr, ref_adr, 0);
 }
 
 // makes dword and offset to a function
 static MakeFOffset(adr,string)
 {
   auto ref_adr, func_name;
   MakeUnkn(adr,0);
   MakeUnkn(adr+1,0);
   MakeUnkn(adr+2,0);
   MakeUnkn(adr+3,0);
   MakeDword(adr);
   ref_adr = Dword(adr);
   if ( ref_adr != 0 )
   {
     MakeFunction(ref_adr, BADADDR);
     MakeName(ref_adr,string);
     add_dref(adr,ref_adr,0);
   }
 }
 
 // makes simple string
 static do_Str(adr,len)
 {
  auto count;
  for ( count = 0; count < len; count++ )
   MakeUnkn(adr + count,0);
  MakeStr(adr, adr+len);
 }
 
 // makes Pascal-style string
 static makePStr(adr)
 {
  auto len;
  MakeUnkn(adr,0);
  MakeByte(adr);
  len = Byte(adr);
  do_Str(adr+1,len);
  return len + 1;
 }
 
 // extract pascal-style string
 static getPStr(adr)
 {
  auto len, res, c;
 
  len = Byte(adr++);
  res = "";
  for ( ; len; len-- )
  {
    c = Byte(adr++);
    res = res + c;
  }
  return res;
 }
 
 // returns name of class of this RTTI
 static getRTTIName(adr)
 {
   auto ptr;
   ptr = Dword(adr+0x20);
   if ( ptr != 0 )
    return getPStr(ptr);
   else
    return "";
 }
 
 // processing owned components list
 static processOwned(adr)
 {
  auto count, str_len, comp_count, rtti_base;
 
  MakeUnkn(adr,0);
  MakeUnkn(adr+1,0);
  MakeWord(adr);
  comp_count = Word(adr); /* count of RTTI array */
  adr = adr + 2;
  MakeOffset(adr);
  rtti_base = Dword(adr); /* offset to array of RTTI */
  adr = adr + 4;
  /* process RTTI array */
  MakeUnkn(rtti_base,0);
  MakeUnkn(rtti_base+1,0);
  MakeWord(rtti_base);    /* size of array */
  count = Word(rtti_base);
  rtti_base = rtti_base + 2;
  for ( str_len = 0; str_len < count; str_len++ )
  {
    MakeOffset(rtti_base + str_len * 4);
  }
  /* process each of owned to form components */
  for ( count = 0; count < comp_count; count++ )
  {
   // offset in owners class
    MakeUnkn(adr,0);
    MakeUnkn(adr+1,0);
    MakeWord(adr);
    str_len = Word(adr);
    MakeComm(adr, "Offset 0x" + ltoa(str_len,0x10) );
    adr = adr + 2;
   // unknow word
    MakeUnkn(adr,0);
    MakeUnkn(adr+1,0);
    MakeWord(adr);
    adr = adr + 2;
   // index in RTTI array
    MakeUnkn(adr,0);
    MakeUnkn(adr+1,0);
    MakeWord(adr);
    str_len = Word(adr);
    MakeComm(adr, "Type: " + getRTTIName(Dword(rtti_base + str_len*4)) );
    adr = adr + 2;
   // pascal string - name of component
    MakeUnkn(adr,0);
    str_len = Byte(adr);
    adr = adr + 1;
    do_Str(adr,str_len);
    adr = adr + str_len;
  }
 }
 
 // process events handlers list
 static processHandlers(adr)
 {
  auto count, str_len, f_addr;
 
  MakeUnkn(adr,0);
  MakeUnkn(adr+1,0);
  MakeWord(adr);
  MakeComm(adr,"Handlers count");
  count = Word(adr);
  adr = adr + 2;
  for ( ; count; count-- )
  {
  // unknown dword
   MakeUnkn(adr,0);
   MakeUnkn(adr+1,0);
   MakeWord(adr);
   adr = adr + 2;
  // offset to function - handler
   f_addr = Dword(adr);
   MakeOffset(adr);
   adr = adr + 4;
  // Name of handler
   if ( f_addr != 0 )
   {
     MakeCode(f_addr);
     MakeFunction(f_addr, BADADDR);
     MakeName(f_addr, getPStr(adr));
   }
   adr = adr + makePStr(adr);
  }
 }
 
 // process inherited list first element ( may be recursive ? )
 // returns pointer to next parent`s struct
 static processParent(adr)
 {
   auto str_len;
   auto res;
 
   res = 0;
  // 1st byte - unknown
   MakeUnkn(adr,0);
   MakeByte(adr);
   adr = adr + 1;
  // next - Pascal string - name of class
   adr = adr + makePStr(adr);
  // VTBL pointer
   MakeOffset(adr);
   adr = adr + 4;
  // next - pointer to pointer to next this struct :-)
   MakeOffset(adr);
   str_len = Dword(adr);
   if ( str_len != 0 )
   {
    MakeOffset(str_len);
    res = Dword(str_len);
   }
   adr = adr + 4;
  // WORD - unknown
   MakeUnkn(adr,0);
   MakeUnkn(adr+1,0);
   MakeWord(adr);
   adr = adr + 2;
  // next - name of Unit name
   makePStr(adr);
   return res;
 }
 
 // process dynamic methods table
 static processDynamic(adr)
 {
   auto count, base, i, old_comm;
 
   MakeUnkn(adr,0);
   MakeUnkn(adr+1,0);
   MakeWord(adr);
   count = Word(adr);
   MakeComm(adr,"Count of dynamic methods " + ltoa(count,10) );
   adr = adr + 2;
   base = adr + 2 * count;
   for ( i = 0; i < count; i++ )
   {
     MakeUnkn(adr,0);
     MakeUnkn(adr+1,0);
     MakeWord(adr);
     MakeOffset(base + 4 * i);
     old_comm = Comment(base + 4 * i);
     if ( old_comm != "" )
      MakeComm(base + 4 * i, "Dynamic 0x" + ltoa(Word(adr),0x10) + ", " + old_comm );
     else
      MakeComm(base + 4 * i, "Dynamic 0x" + ltoa(Word(adr),0x10) );
     adr = adr + 2;
   }
   return count;
 }
 
 // makes tricky VTBL entries
 static makeF2Offset(adr,name)
 {
  auto comm,ref_adr;
 
  MakeOffset(adr);
  ref_adr = Dword(adr);
  if ( ref_adr != 0 )
     add_dref(adr,ref_adr,0);
  comm = Comment(adr);
  if ( comm != "" )
   MakeComm(adr, comm + ", " + name);
  else
   MakeComm(adr, name);
 }
 
 // main function - process RTTI structure
 static processRTTI(adr)
 {
  auto count;
  auto res;
  auto my_name;
 
  my_name = "";
  // first DWORD - VTBL pointer
  MakeOffset(adr);
  // three next DWORD is unknown
  MakeOffset(adr+4);
  MakeOffset(adr+8);
  MakeOffset(adr+0xc);
  // list of parents
  MakeOffset(adr+0x10);
  count = Dword(adr+0x10);
  if ( count != 0 )       // also process first parent for this class
   processParent(count);
  // 0x14 DWORD - owned components
  MakeOffset(adr+0x14);
  count = Dword(adr+0x14);
  if ( count != 0 )
   processOwned(count);
  // 0x18 DWORD - event handlers list
  MakeOffset(adr+0x18);
  count = Dword(adr+0x18);
  if ( count != 0 )
   processHandlers(count);
  // 0x1c DWORD - pointer to dynamic functions list
  MakeOffset(adr+0x1c);
  count = Dword(adr+0x1c);
  if ( count != 0 )
  {
   count = processDynamic(count);
   MakeComm(adr+0x1c, ltoa(count,10) + " dynamic method(s)");
  }
  // 0x20 DWORD - pointer to class name
  MakeOffset(adr+0x20);
  count = Dword(adr+0x20);
  if ( count != 0 )
  {
    makePStr(count);
    my_name = getPStr(count);
    MakeComm(adr+0x20, "Name: " + my_name );
  }
  // 0x24 DWORD - size of class
  MakeUnkn(adr+0x24,0);
  MakeUnkn(adr+0x25,0);
  MakeUnkn(adr+0x26,0);
  MakeUnkn(adr+0x27,0);
  MakeDword(adr+0x24);
  MakeComm(adr+0x24,"Size of class");
  // 0x28 - pointer to parent`s RTTI struct
  MakeOffset(adr+0x28);
  res = Dword(adr+0x28);
  MakeComm(adr+0x28,"Parent`s class");
  // 0x2c SafeCallException
  makeF2Offset(adr+0x2c,my_name + "::SafeCallException");
  // 0x30 AfterConstruction
  makeF2Offset(adr+0x30,my_name + "::AfterConstruction");
  // 0x34 BeforeConstruction
  makeF2Offset(adr+0x34,my_name + "::BeforeConstruction");
  // 0x38 Dispatch
  makeF2Offset(adr+0x38,my_name + "::Dispatch");
  // 0x3C DefaultHandler
  makeF2Offset(adr+0x3c,my_name + "::DefaultHandler");
  // 0x40 NewInstance
  makeF2Offset(adr+0x40,my_name + "::NewInstance");
  // 0x44 FreeInstance
  makeF2Offset(adr+0x44,my_name + "::FreeInstance");
  // 0x48 Destroy
  makeF2Offset(adr+0x48,my_name + "::Destroy");
  return res;
 }
 
Пояснения по каждой функции:
  • MakeOffset создаёт смещение по указанному адресу adr. Иногда IDA бывает упряма, и настаивает, что по этому адресу вовсе не смещение, а, скажем, data - четырёх вызовов MakeUnkn обычно бывает достаточно, чтобы изменить её мнение
  • MakeFOffset - аналогично MakeOffset, но только создаёт смещение на функцию, которую называет string. Warning: не проверяется результат MakeFunction, поэтому функция может быть не совсем правильной. В любом случае, это нужно проверять обычно.
  • do_Str - помечает len байт с адреса adr как строку
  • makePStr - создаёт pascal-строку по адресу adr. Возвращает её длину, включая сам байт длины.
  • getPStr - возвращает значение pascal-строки по адресу adr.
  • getRTTIName - возвращает имя класса по его RTTI, расположенной по адресу adr
  • processOwned - обрабатывает список компонентов, принадлежащих данному компоненту. Сам список начинается с адреса adr
  • processHandlers - обрабатывает список функций-обработчиков событий. Сам список начинается с адреса adr. Warning: так как имеет место попытка назвать функцию так же, как она называлась в этом классе, имя может быть неуникально ( вспомните, сколько раз у Вас были функции с именем Button1Click в разных классах )
  • processParent - обрабатывает один элемент в списке наследований по адресу adr. Это рекурсивная структура, но её окончание может быть обозначено по-разному - либо как Nil, либо как две структуры TObject, ссылающиеся друг на друга. На всякий случай возвращается ссылка на следующий элемент.
  • processDynamic - обрабатывает hash динамических методов по адресу adr. Warning: несмотря на то, что этот метод пытается сохранить ранее данные комментарии для указателя на каждую dynamic функцию, похоже, что в IDA Pro есть bug, из-за которого нельзя извлечь комментарии для опознанных с помощью сигнатур функций ( который по умолчанию имеют тёмно-коричневый цвет, скажем, TFormCustom::WMPaint ). Вызов Comment на таких комментариях возвращает пустую строку. Возвращается число динамических функций.
  • makeF2Offset - вспомогательная функция, служит по пометки служебных функций ( с отрицательным индексом в VTBL ) и добавления к ним комментария. Адрес функции передаётся в adr, строка комментария в name
  • processRTTI - собственно, самая главная функция, собирающая все остальные - обрабатывает структуру RTTI по адресу adr
Я даже в кои-то веки раз комментарии кое-где сделал, так что я надеюсь, Вам не составит труда разобраться в моих каракулях.



Обсуждение статьи: Исследование кода, генерируемого Delphi. Часть 2 >>>


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



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


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