TForm, файл Forms

TCustomForm, файл Forms

TScrollingWinControl, файл Forms

TWinControl, файл Controls

TControl, файл Controls

TComponent, файл Classes

TPersistent, файл Classes

TObject, файл System

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

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

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

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


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

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

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

Массу крэкерских инструментов, видеоуроков и статей вы сможете найти на видеокурсе от нашего сайта. Подробнее здесь.

Введение

На этот раз я представляю Вам сугубо теоретическое исследование, и все рассматриваемые программы написал сам. Кроме них нам понадобятся 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:

смещениетипописание
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.



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


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



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


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