![]() | ||||||||||||||||||||||||||||
Домой | Статьи | 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) на некую рекурсивную структуру, которую я назвал список наследственности:
Путешествуя по этому списку, можно с лёгкостью выяснить генеалогическое дерево класса TForm1: |
смещение | тип | описание |
0 | WORD | число CompCount различных классов компонентов |
2 | DWORD | указатель на массив указателей на структуры RTTI этих классов. Первым элементом этого массива является WORD - число его элементов, далее расположены указатели на структуры RTTI. |
Сразу вслед за ней идут CompCount структур, описывающих эти компоненты:
смещение | тип | описание |
0 | WORD | смещение в классе, по которому находится указатель на компонент |
1 | WORD | значение не выяснено |
2 | WORD | индекс в массиве структур RTTI - по нему определяется класс компонента |
N+2 | WORD | длина Pascal-строки |
N+6 | String | имя компонента (например, Edit1) |
Самым важным здесь являются смещение на компонент во включающем классе и его тип. Запомним их для компонентов в форме TForm1:
имя компонента | cмещение в классе | тип компонента |
Edit1 | 02C4h | 0 - TEdit |
Edit2 | 02C8h | 0 - TEdit |
Button1 | 02CCh | 1 - TButton |
Button2 | 02D0h | 1 - TButton |
Button3 | 02D4h | 1 - TButton |
BitBtn1 | 02D8h | 2 - TBitBtn |
BitBtn2 | 02DCh | 2 - TBitBtn |
BitBtn3 | 02E0h | 2 - TBitBtn |
Снова вернёмся к структуре RTTI класса TForm1. По смещению 18h находится указатель на одну из самых полезных структур - на массив обработчиков событий (но только тех, которые заданы во время проектирования!). Первым элементом этого массива идёт WORD, определяющий длину этого массива, а его элементы имеют такие поля:
смещение | тип | описание |
0 | WORD | тип обработчика |
2 | DWORD | указатель на функцию-обработчик |
6 | BYTE | длина Pascal-строки |
7 | String | имя функции-обработчика |
Тип определяет количество и размерность аргументов. Для обработчиков OnClick он равен 13h, для OnShow 0Fh.
Не прошло и получаса, а я уже нашёл свой код. Мы рассмотрим его чуть позже (пока Вы можете назвать найденные функции как в оригинале), а сейчас продолжим рассмотрение структуры RTTI класса. По смещению 24h записывается размер класса (DWORD) - для TForm1 он составляет 02E4h байт. Сравните его с таблицей смещений компонентов. По смещению 28h находится указатель на структуру RTTI класса-предка. У объекта TObject он равен нулю. По смещению 20h находится указатель на Pascal-строку - имя класса. Я повторю всю вышеизложенную информацию в следующей таблице:
смещение | тип | описание |
0 | DWORD | указатель на VTBL |
4 | 12 байт | значение не выяснено |
10h | DWORD | указатель на список наследований |
14h | DWORD | указатель на компоненты, которыми владеет данный класс |
18h | DWORD | указатель на массив обработчиков событий |
1Ch | DWORD | значение не выяснено |
20h | DWORD | указатель на Pascal-строку - имя класса |
24h | DWORD | размер класса |
28h | DWORD | указатель на структуру 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.
Материалы находятся на сайте https://exelab.ru