Советы по Delphi

         

и мне совершенно не хотелось


Поскольку я человек ленивый ( лень - двигатель прогресса ) и мне совершенно не хотелось вручную сообщать IDA Pro, что это не просто нечто бесформенное, а самая что ни наесть структура RTTI ( при этом ещё мучительно вспоминая, чего там идёт под каким смещением ), я написал небольшой script на IDC, который позволяет мне иметь немного свободного времени для прямых обязанностей сисадмина, а именно - для чтения newsов и взлома программ... /* * This script deal with Delphi RTTI structures * * Red Plait, 23-VIII-1999 */ #include // 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
Я даже в кои-то веки раз комментарии кое-где сделал, так что я надеюсь, Вам не составит труда разобраться в моих каракулях.
Часть 3. Интерфейсы и published свойства
Итак, мы уже знаем, как найти VTBL. Но в каком порядке хранятся в ней методы ? Ответ можно получить, посмотрев на ассемблерный листинг и сравнив его с исходным кодом VCL. И выяснится, что новые методы дописыватся в конец VTBL, по мере произведения новых классов. Я проследил генеалогию классов до TWinControl и вот что у меня получилось (цифра означает смещение в VTBL):
  • TObject
    Виртуальные методы этого класса расположены в VTBL по отрицательным индексам. Смотрите моё описание RTTI в предыдущей статье
  • TPersistent
  • 0x00 AssignTo
  • 0x01 DefineProperties
  • 0x02 Assign
  • TComponent
    В нём, помимо всего прочего, реализуется также интерфейсы IUnknown & IDispatch, поэтому объекты-производные от него могут быть серверами OLE-Automation
    • 0x03 Loaded
    • 0x04 Notification
    • 0x05 ReadState
    • 0x06 SetName
    • 0x07 UpdateRegistry
    • 0x08 ValidateRename
    • 0x09 WriteState
    • 0x0A QueryInterface
    • 0x0B Create(AOwner: TComponent)
    • TControl
      Его производные классы могут быть помещены на форму во время проектрирования и умеют отображать себя ( так называемые "видимые" компоненты )
      • 0x0C UpdateLastResize
      • 0x0D CanResize
      • 0x0E CanAutoResize
      • 0x0F ConstrainedResize
      • 0x10 GetClientOrigin
      • 0x11 GetClientRect
      • 0x12 GetDeviceContext
      • 0x13 GetDragImages
      • 0x14 GetEnabled
      • 0x15 GetFloating
      • 0x16 GetFloatingDockSiteClass
      • 0x17 SetDragMode
      • 0x18 SetEnabled - полезный метод, особенно для всяких кнопок в диалогах регистрации серийных номеров...
      • 0x19 SetParent
      • 0x1A SetParentBiDiMode
      • 0x1B SetBiDiMode
      • 0x1C WndProc - адрес оконной процедуры. Если она не находит обработчика у себя, вызывается метод TObject::Dispatch. И уже последний метод вызывает dynamic функцию по индексу, равному номеру сообщения Windows.
      • 0x1D InitiateAction
      • 0x1E Invalidate
      • 0x1F Repaint - адрес функции отрисовки компонента
      • 0x20 SetBounds
      • 0x21 Update
      • TWinControl
        Его производные классы имеют собственное окно
        • 0x22 AdjustClientRect
        • 0x23 AlignControls
        • 0x24 CreateHandle
        • 0x25 CreateParams
        • 0x26 CreateWindowHandle
        • 0x27 CreateWnd
        • 0x28 DestroyWindowHandle
        • 0x29 DestroyWnd
        • 0x2A GetControlExtents
        • 0x2B PaintWindow
        • 0x2C ShowControl
        • 0x2D SetFocus

        • А где же хранятся методы интерфейсов, спросите Вы ? Хороший вопрос, учитывая, что классы Delphi могут иметь только одного предка, но в то же самое время реализовывать несколько интерфейсов. Чтобы выяснить это, я написал ещё одну тестовую программу, на сей раз из нескольких файлов.
          Unit1.pas - главная форма приложения.
              interface

          uses
          Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Project1_TLB;
          type
          TForm1 = class(TForm) Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end;
          var
          Form1: TForm1;
          implementation

          {$R *.DFM}

          uses
          Unit2;
          procedure TForm1.Button1Click(Sender: TObject);
          var
          My_Object: TRP_Server; My_Interface: IRP_Server; begin
          My_Object := Nil; My_Interface := Nil; Try My_Object := TRP_Server.Create; My_Interface := My_Object; My_Interface.RP_Prop := PChar('Строка'); MessageDlg(Format('My Method1: %d, string is %s, refcount is %d', [My_Interface.Method1(1), My_Interface.RP_Prop, My_Object.RefCount]), mtConfirmation,[mbOk],0); finally if My_Interface <> Nil then My_Interface := Nil; (* это не правильно - My_Object уже не существует здесь *)
          MessageDlg(Format('refcount is %d',[My_Object.RefCount]), mtConfirmation,[mbOk],0); end; end;

          procedure TForm1.Button2Click(Sender: TObject);
          var
          RP_IO: IRP_Server; begin
          try RP_IO := CoRP_Server.Create; RP_IO.RP_Prop := 'Yet one string'; MessageDlg(Format('String is %s, Method1 return %d', [RP_IO.RP_Prop, RP_IO.Method1(123)]), mtConfirmation,[mbOk],0); except On e:Exception do MessageDlg(Format('Exception occured: %s, reason %s', [e.ClassName, e.Message]), mtError,[mbOk],0); end; end;

          Unit2.pas - объект - сервер OLE-Automation
              interface

          uses
          ComObj, ActiveX, Project1_TLB, Dialogs, SysUtils;
          type
          TRP_Server = class(TAutoObject, IRP_Server) private MyString: String; protected function Get_RP_Prop: PChar; safecall; function Method1(a: Integer): Integer; safecall; procedure Set_RP_Prop(Value: PChar); safecall; { Protected declarations } public destructor Destroy; override; end;
          implementation

          uses ComServ;

          Destructor TRP_Server.Destroy;
          begin
          MessageDlg('Destroy',mtConfirmation,[mbOk],0); Inherited Destroy; end;

          function TRP_Server.Get_RP_Prop: PChar;
          begin
          if MyString <> '' then Result := PChar(MyString) else Result := PChar(''); end;

          function TRP_Server.Method1(a: Integer): Integer;
          begin
          MessageDlg(Format('My Method1: %d', [a]),mtConfirmation,[mbOk],0); if MyString <> '' then Result := Length(MyString) else Result := 0; end;

          procedure TRP_Server.Set_RP_Prop(Value: PChar);
          begin
          if Value <> nil then MyString := Value else MyString := ''; end;

          initialization
          TAutoObjectFactory.Create(ComServer, TRP_Server, Class_RP_Server, ciMultiInstance, tmApartment); MessageDlg('Initializtion part',mtConfirmation,[mbOk],0); end.

          Projcet1_TLB.pas - файл, автоматически сгенерированный Delphi для классов, являющихся серверами OLE-Automation
              unit Project1_TLB;
          ...
          interface

          uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL;

          // *********************************************************************//
          // GUIDS declared in the TypeLibrary. Following prefixes are used:      //
          //   Type Libraries     : LIBID_xxxx                                    //
          //   CoClasses          : CLASS_xxxx                                    //
          //   DISPInterfaces     : DIID_xxxx                                     //
          //   Non-DISP interfaces: IID_xxxx                                      //
          // *********************************************************************//
          const
          LIBID_Project1: TGUID = '{198C3180-6073-11D3-908D-00104BB6F968}'; IID_IRP_Server: TGUID = '{198C3181-6073-11D3-908D-00104BB6F968}'; CLASS_RP_Server: TGUID = '{198C3183-6073-11D3-908D-00104BB6F968}'; type

          // *********************************************************************//
          // Forward declaration of interfaces defined in Type Library            //
          // *********************************************************************//
          IRP_Server = interface; IRP_ServerDisp = dispinterface;
          // *********************************************************************//
          // Declaration of CoClasses defined in Type Library                     //
          // (NOTE: Here we map each CoClass to its Default Interface)            //
          // *********************************************************************//
          RP_Server = IRP_Server;
          // *********************************************************************//
          // Interface: IRP_Server
          // Flags:     (4416) Dual OleAutomation Dispatchable
          // GUID:      {198C3181-6073-11D3-908D-00104BB6F968}
          // *********************************************************************//
          IRP_Server = interface(IDispatch) ['{198C3181-6073-11D3-908D-00104BB6F968}'] function Method1(a: Integer): Integer; safecall; function Get_RP_Prop: PChar; safecall; procedure Set_RP_Prop(Value: PChar); safecall; property RP_Prop: PChar read Get_RP_Prop write Set_RP_Prop; end;
          // *********************************************************************//
          // DispIntf:  IRP_ServerDisp
          // Flags:     (4416) Dual OleAutomation Dispatchable
          // GUID:      {198C3181-6073-11D3-908D-00104BB6F968}
          // *********************************************************************//
          IRP_ServerDisp = dispinterface ['{198C3181-6073-11D3-908D-00104BB6F968}'] function Method1(a: Integer): Integer; dispid 1; property RP_Prop: {??PChar} OleVariant dispid 2; end;
          CoRP_Server = class class function Create: IRP_Server; class function CreateRemote(const MachineName: string): IRP_Server; end;
          implementation

          uses ComObj;

          class function CoRP_Server.Create: IRP_Server;
          begin
          Result := CreateComObject(CLASS_RP_Server) as IRP_Server; end;

          class function CoRP_Server.CreateRemote(const MachineName: string): IRP_Server;
          begin Result := CreateRemoteComObject(MachineName, CLASS_RP_Server) as IRP_Server; end;

          Меня всегда интересовало, как же это так Delphi позволят иметь код, запускаемый при инициализации и деинициализации модуля ? Просмотрев исходный код в файле Rtl/Sys/System.pas ( я рекомендую иметь исходные тексты, поставляемые вместе с Delphi при исследовании написанных на ней программ ) и сравнив его с ассемблерным листингом, выясняется, что это легко и непринуждённо. Итак, существуют несколько довольно простых структур:
              PackageUnitEntry = record Init, FInit : procedure; end;
          { Compiler generated table to be processed sequentially to init & finit all package units } { Init: 0..Max-1; Final: Last Initialized..0                                              } UnitEntryTable = array [0..9999999] of PackageUnitEntry; PUnitEntryTable = ^UnitEntryTable;
          PackageInfoTable = record UnitCount : Integer;      { number of entries in UnitInfo array; always > 0 } UnitInfo : PUnitEntryTable; end;
          PackageInfo = ^PackageInfoTable;

          При startupе указатель на PackageInfoTable передаётся единственным аргументом функции InitExe: start proc near push ebp mov ebp, esp add esp, 0FFFFFFF4h mov eax, offset dword_0_445424 call @@InitExe ; ::`intcls'::InitExe По адресу 0x445424 хранится DWORD 0x29 и указатель на таблицу структур PackageUnitEntry, где, в частности, на предпоследнем месте содержатся и адреса моих процедур инициализации и деинициализации.
          Delphi помещает список реализуемых классом интерфейсов в отдельную структуру, указатель на которую помещает в RTTI по смещению 0x4. Сама эта структура описана во всё том же Rtl/Sys/System.pas:
              PGUID = ^TGUID; TGUID = record D1: LongWord; D2: Word; D3: Word; D4: array[0..7] of Byte; end;
          PInterfaceEntry = ^TInterfaceEntry; TInterfaceEntry = record IID: TGUID; VTable: Pointer; IOffset: Integer; ImplGetter: Integer; end;
          PInterfaceTable = ^TInterfaceTable; TInterfaceTable = record EntryCount: Integer; Entries: array[0..9999] of TInterfaceEntry; end;

          Указатель на TInterfaceTable и помещается в RTTI по смещению 0x4 ( если класс реализует какие-либо интерфейсы ). TGUID - это обычная структура UID, используемая в OLE, VTable - указатель на VTBL интерфейса, IOffset - смещение в данном классе на экземпляр, содержащий данные данного интерфейса. Когда вызывается метод интерфейса, он вызывается обычно от указателя на интерфейс, а не на класс, реализующий этот интерфейс. Мы же пишем методы нашего класса, которые ожидают видеть в качестве нулевого аргумента указатель на экземпляр нашего класса. Поэтому Delphi автоматически генерирует для VTable код, настраивающий свой нулевой аргумент соответствующим образом. Например, для моего класса TRP_Server значение поля IOffset составляет 0x34. Функции же, содержащиеся в VTable, выглядят так: loc_0_444B39: ; функция, вызываемая по интерфейсу add dword ptr [esp+4], 0FFFFFFCCh jmp MyMethod1 ; вызов функции в классе Напомню, что все методы интерфейсов должны объявляться как safecall - параметры передаются как в C, справо налево, но очистку стека производит вызываемая процедура. Поэтому в [esp+4] содержится нулевой параметр функции - указатель на экземпляр интерфейса - класса IRP_Server. Затем вызывается метод класса TRP_Server, которому должен нулевым параметром передаваться указатель на экземпляр TRP_Server - поэтому происходит настройка этого параметра, 0x0FFFFFFCC = -0x34.
          Самый же значимый резльтат всех этий ковыряний в коде - мне удалось обнаружить в RTTI полное описание всех published свойств ! Из системы помощи Delphi: ( файл del4op.hlp, перевод мой ): Published члены имеют такую же видимость, как public члены. Разница заключается в том, что для published членов генерируется информация о типе времени исполнения (RTTI). RTTI позволяет приложению динамически обращаться к полям и свойствам объектов и отыскивать их методы. Delphi использует RTTI для доступа к значениям свойств при сохранении и загрузке файлов форм (.DFM), для показа свойств в Object Inspector и для присваивания некоторых методов (называемых обработчиками событий) определённым свойствам (называемых событиями) Published свойства ограничены по типу данных. Они могут иметь типы Ordinal, string, класса, интерфейса и указателя на метод класса. Также могут быть использованы наборы (set), если верхний и нижний пределы их базового типа имеют порядковые значения между 0 и 31 (другими словами, набор должен помещаться в байте, слове или двойном слове ). Также можно иметь published свойство любого вещественного типа (за исключением Real48). Свойство-массив не может быть published. Все методы могут быть published, но класс не может иметь два или более перегруженных метода с одинаковыми именами. Члены класса могут быть published, только если они являются классом или интерфейсом. Класс не может содержать published свойств, если он не скомпилирован с ключом {$M+} или является производным от класса, скомпилированного с этим ключом. Подавляющее большинство классов с published свойствами являются производными от класса TPersistent, который уже скомпилирован с ключом {$M+}, так что Вам редко потребуется использовать эту директиву. Что сиё может означать для reverse engeneerов ? Значение вышесказанного трудно переоценить - мы можем извлечь из RTTI названия, типы и местоположение в классе всех published свойств любого класса ! Если вспомнить, что такие свойства, как Enable, Text, Caption, Color, Font и многие другие для таких компонентов, как TEdit,TButton,TForm и проч., обычно изменяющиеся, предположим, в диалоге регистрации в зависимости от правильности-неправильности серийного номера, имеют как раз тип published... Поскольку все формы Delphi и компоненты в них имеют published свойства, моя фантазия рисует мне куда более сочную и красочную картину...
          Одна из главных структур, применяющихся для идентификации published свойств - TPropInfo
              TPropInfo = packed record PropType: PPTypeInfo; GetProc: Pointer; SetProc: Pointer; StoredProc: Pointer; Index: Integer; Default: Longint; NameIndex: SmallInt; Name: ShortString; end;

          После структуры наследования ( по смещению 10h в RTTI ) расположен WORD - количество расположенных следом за ним структур TPropInfo, по одной на каждое published свойство. В этой структуре поля имеют следующие значения:
          • PropType - указатель на структуру, описывающую тип данного свойства. Структуры, содержащиеся в TypeInfo, довольно сложные, так что я не буду объяснять, как именно они работают, Вам достаточно знать, что мой IDC script потрошит её в 99 % случаев. Они описаны в файле vcl/typeinfo.pas.
          • GetProc,SetProc,StoredProc - поля, указывающие на методы Get ( извлечение свойства ), Set ( изменение свойства ) и Stored ( признак сохранения значения свойства ). Для всех них есть недокументрированные правила:
          • Если старший байт этих полей равен 0xFF, то в остальных байтах находится смещение в экземпляре класса, по которому находятся данные, представляющие данное свойство. В таком случае все манипуляции со свойством производятся напрямую.
          • Если старший байт равен 0xFE, то в остальных байтах содержится смещение в VTBL класса, т.е. все манипуляции со свойством производятся через виртуальную функцию.
          • Если значение поля равно 0x80000000 - метод не определён ( скажем, метод Set для read-only published свойств )
          • Значение 1 для поля StoredProc означает обязательное сохранение значения свойства.
          • Все остальные значения полей рассматриваются как ссылка на метод класса.
        • Index - значение не выяснено. Есть подозрение, что это поле связано со свойствами типа массив и подчиняется тем же правилам, что и предыдущие три поля. Во время тестирования мне не встретилось ни одного поля Index со значением, отличным от 0x80000000
        • Default - значение свойства по умолчанию
        • NameIndex - порядковый номер published свойства в данном классе, отсчёт ведётся почему-то с 2.
        • Name - Имя свойства, pascal-style строка Как видите, можно узнать о published-свойствах практически всё, включая адрес, на который нужно ставить точку останова.

        • Я изменил свой IDC script для анализа RTTI классов Delphi 4, чтобы он поддерживал все обнаруженные структуры. Cам script приведен в конце данной статьи.

          Содержание раздела