и мне совершенно не хотелось
Поскольку я человек ленивый ( лень - двигатель прогресса ) и мне совершенно не хотелось вручную сообщать 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 приведен в конце данной статьи.
Содержание раздела