• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Реверсим 64-разрядные приложения на Delphi

baykal

(L2) cache
Пользователь
Регистрация
16.03.2021
Сообщения
370
Реакции
838
Программы на Delphi пусть и нечасто, но все же попадают в руки любителей поковыряться в чужом софте. Сегодня мы разберем методы реверса и отладки таких приложений. А заодно запасемся нужными инструментами.
Хорошего хакера от хорошего кодера отличает то, что первый не просто знаком со всеми необходимыми для выполнения текущей задачи инструментами и умеет ими пользоваться, но также понимает их внутреннее устройство и принципы работы. Чтобы при необходимости обойтись без них, просто руками выполнив действие, под которое инструмент по каким‑то причинам не был заточен.

Одна из самых благодатных тем для хакеров — реверс приложений на Delphi. Об этом написано множество статей (в том числе и в «Хакере»), а также создано множество удобных инструментов, облегчающих хакерам жизнь. Однако тема настолько обширна, что даже эти инструменты не охватывают ее полностью: всегда найдутся случаи, когда все тулзы бессильны и приходится проявлять хакерскую смекалку.


Итак, сформулируем задачу. Имеется 64-битное приложение, первичный анализ которого при помощи DIE выдает его родство с Delphi.

0.png


Вроде бы все хорошо, однако на приведенном выше скриншоте нетрудно заметить взаимоисключающие параметры: предлагаемый анализатором Delphi не может быть 64-битным. Переключив режим сканирования с автоматического на Nauz File Detector, проясняем возникшее недоразумение.

1.png

Конечно же, это гораздо более поздняя версия Embarcadero Delphi 35.0 (28.0.44500.8973). Естественно, перед нами не XE7+, а целый XE11+. Данное открытие совсем нас не радует — общеизвестные инструменты DeDe и IDR не то что столь свежую версию не понимают, они вообще не умеют работать с 64-битным Delphi.

Есть робкая надежда на зачаточную бета‑версию IDR64, однако и она не признает наш модуль родным. Не откладываем ее далеко, она нам еще пригодится, гуглим дальше.

Находим занятный проект питоновского скрипта под IDA. Его авторы обнаружили в Delphi и успешно эксплуатируют интересную фичу. При выходе из внутреннего метода (Event Constructor) регистр EDX будет содержать ссылку на имя обработчика события. Адрес обоаботчика будет находиться в регистре EAX, примерно так, как показано на следующем скриншоте.

2.jpg

Проанализировав код 32-битных хендлеров, они выделили общий паттерн для поиска 80 E3 DF 75 ?? 49 75 ?? 8B 46 02 ?? ?? 5B C3 (соответствующие ему байты помечены плюсиком):
Код:
.text:00408DB8              loc_408DB8:                             ; CODE XREF: sub_408D68+67↓j
.text:00408DB8 8A 5C 31 06                  mov     bl, [ecx+esi+6]
.text:00408DBC F6 C3 80                     test    bl, 80h
.text:00408DBF 75 E1                        jnz     short loc_408DA2
.text:00408DC1 32 1C 11                     xor     bl, [ecx+edx]
.text:00408DC4 F6 C3 80                     test    bl, 80h
.text:00408DC7 75 D9                        jnz     short loc_408DA2
.text:00408DC9 80+ E3+ DF+                  and     bl, 0DFh
.text:00408DCC 75+ D0                       jnz     short loc_408D9E
.text:00408DCE 49+                          dec     ecx
.text:00408DCF 75+ E7                       jnz     short loc_408DB8
.text:00408DD1
.text:00408DD1              loc_408DD1:                             ; CODE XREF: sub_408D68+4C↑j
.text:00408DD1 8B+ 46+ 02+                  mov     eax, [esi+2]
.text:00408DD4
.text:00408DD4              loc_408DD4:                             ; CODE XREF: sub_408D68+34↑j
.text:00408DD4 5F                           pop     edi
.text:00408DD5 5E                           pop     esi
.text:00408DD6 5B+                          pop     ebx
.text:00408DD7 C3+                          retn
Аналогичный код хендлера для 64-битной версии находится по паттерну 80 ?? ?? 00 74 ?? E8 ?? ?? ?? ?? 48 8B ?? ?? 48 8D ?? ?? ?? ?? ?? ?? C3. Например, вот как это выглядит для версии 31 (10.1).

3_szPMj1g.jpg


Код будет таким (соответствующие паттерну байты тоже помечены плюсиком):
Код:
55              push    rbp
57              push    rdi
56              push    rsi
53              push    rbx
48 83 EC 48     sub     rsp, 48h
48 8B EC        mov     rbp, rsp
48 89 CB        mov     rbx, rcx
4C 89 C6        mov     rsi, r8
48 8B 0A        mov     rcx, [rdx]
48 89 F2        mov     rdx, rsi
E8 94 89 E5 FF  call    sub_40E690
48 89 45 38     mov     [rbp+var_s38], rax
48 83 7D 38 00  cmp     [rbp+var_s38], 0
0F 94 C0        setz    al
88 45 37        mov     [rbp+var_s37], al
48 83 7B 68 00  cmp     qword ptr [rbx+68h], 0
74 1D           jz      short loc_5B5D2F
48 8D 7B 68     lea     rdi, [rbx+68h]
48 8B 4F 08     mov     rcx, [rdi+8]
48 89 DA        mov     rdx, rbx
49 89 F0        mov     r8, rsi
4C 8D 4D 38     lea     r9, [rbp+var_s38]
48 8D 45 37     lea     rax, [rbp+var_s37]
48 89 44 24 20  mov     [rsp+var_s20], rax
FF 17           call    qword ptr [rdi]
80+ 7D 37 00+   cmp     [rbp+var_s37], 0
74+ 05          jz      short loc_5B5D3A
E8+ B6 F9 FF FF call    sub_5B56F0
48+ 8B+ 45 38   mov     rax, [rbp+var_s38]
48+ 8D+ 65 48   lea     rsp, [rbp+48h]
5B              pop     rbx
5E              pop     rsi
5F              pop     rdi
5D              pop     rbp
C3+             retn
Легко убедиться, что при останове в конце этой функции адресу метода в регистре RAX соответствует его имя в регистре RDI (да‑да, в коде скрипта ошибка, не RDX, а RDI).

4.jpg


Но это мелочь, поскольку недостатки данного метода видны невооруженным глазом. Трассировать программу ради того, чтобы получить весьма ограниченный список методов, довольно тоскливо, да и вообще если программа подлежит запуску и трассировке, то это большое везение. И наконец, самая главная беда — в нашей целевой версии XE11 аналогичного кода нет и быть не может, поскольку там весь этот процесс, судя по всему, проходит в недрах библиотеки rtl280.dll.

Подумаем, чем еще нам может пригодиться IDR64. Я немного покривил душой, упомянув ее отказ от родства с нашим модулем. Конечно, у нее отсутствует база знаний по XE11. По сути, она только три версии и знает. Поэтому при открытии в режиме автодетекта сомневается в «дельфовости» нашей программы. Однако если немного схитрить и попробовать открыть ее как Delphi XE4, а затем согласиться с native knowledge base, то IDR64 вовсе не противится этому и после очень долгих раздумий кое‑как загружает программу в себя.

Конечно, она полноценно не восстанавливает всю структуру модулей и классов, однако из восстановленного ею кода можно почерпнуть значительно больше информации, чем из IDA. В частности, мы получим список имен методов и классов, не говоря уже о том, что IDA чертовски не любит паскалевские строки со счетчиком.

И в заключение я, как и обещал, немного расскажу о примерной внутренней структуре дельфовского кода и принципах его анализа «голыми руками». Когда мы анализировали дельфовские приложения при помощи IDR, то заметили, что для каждого скомпилированного класса присутствует своя характерная структура данных, называемая VMT. Структура не документирована, однако, погуглив, можно найти старую статью с ее описанием. В ней эта структура описывается (как тип в синтаксисе дельфи) следующим образом:
Код:
type
  PClass = ^TClass;
  PSafeCallException = function  (Self: TObject; ExceptObject:
    TObject; ExceptAddr: Pointer): HResult;
  PAfterConstruction = procedure (Self: TObject);
  PBeforeDestruction = procedure (Self: TObject);
  PDispatch          = procedure (Self: TObject; var Message);
  PDefaultHandler    = procedure (Self: TObject; var Message);
  PNewInstance       = function  (Self: TClass) : TObject;
  PFreeInstance      = procedure (Self: TObject);
  PDestroy           = procedure (Self: TObject; OuterMost: ShortInt);
  PVmt = ^TVmt;
  TVmt = packed record
    SelfPtr           : TClass;
    IntfTable         : Pointer;
    AutoTable         : Pointer;
    InitTable         : Pointer;
    TypeInfo          : Pointer;
    FieldTable        : Pointer;
    MethodTable       : Pointer;
    DynamicTable      : Pointer;
    ClassName         : PShortString;
    InstanceSize      : PLongint;
    Parent            : PClass;
    SafeCallException : PSafeCallException;
    AfterConstruction : PAfterConstruction;
    BeforeDestruction : PBeforeDestruction;
    Dispatch          : PDispatch;
    DefaultHandler    : PDefaultHandler;
    NewInstance       : PNewInstance;
    FreeInstance      : PFreeInstance;
    Destroy           : PDestroy;
   {UserDefinedVirtuals: array[0..999] of procedure;}
  end;
Как видишь, она содержит исчерпывающую информацию о классе, в которой нас в первую очередь интересуют его методы. Для понимания этого покурим немного исходники IDR64. Бегло просмотрев их, находим два основных модуля, ответственных за разбор кода на классы: Threads.cpp и Misc.cpp. Первый содержит методы для поиска таблиц VMT в скомпилированном коде, второй служит для извлечения информации из них. В двух словах коснусь реализации этих процессов. В модуле Threads.cpp есть метод с соответствующим названием:
Код:
// Collect information from VMT structure
void __fastcall TAnalyzeThread::FindVMTs()
{
   ...
    if (Adj0Count > Adj24Count)
        Adjustment = 0;
    else
    {
        Adjustment = -24;
        Vmt.AdjustVmtConsts(Adjustment);
    }
    for (int i = 0; i < TotalSize && !Terminated; i += 8)
    {
        ...
        if (idr.IsFlagSet(cfCode | cfData, i)) continue;
        DWORD adr = *((ULONGLONG*)(Code + i));  // Points to vmt0 (VmtSelfPtr)
        if (IsValidImageAdr(adr) && Pos2Adr(i) == adr + Vmt.SelfPtr)
        {
            DWORD classVMT = adr;
...
Фактически он перебирает все 64-битные слова в файле, проверяя, указывает ли каждое на самое себя (если его интерпретировать как адрес со смещением + Vmt.SelfPtr (-0xBO)+ Adjustment (-24)). Например, на рисунке выделенный красным длинный указатель по адресу 0x3A806F48 полностью проходит данный магический тест, поскольку его содержимое имеет вид 0x3A807010=0x3A806F48+0xB0+0x18.

Этой особенностью, в частности, объясняется весьма неприятное торможение IDR при загрузке больших файлов.

4a.jpg

Итак, найдя начало таблицы VMT, можно начинать разбирать ее содержимое. Модуль Misc.cpp содержит множество методов для этого: GetParentAdr, GetChildAdr, GetClassSize, GetClsName и так далее. Устроены они, в общем‑то, однотипно: в них выполняется обращение к соответствующим полям структуры VMT относительно указателя Vmt.SelfPtr:
Код:
String __fastcall GetClsName(DWORD adr)
{
    if (!IsValidImageAdr(adr)) return "";
    DWORD vmtAdr = adr - Vmt.SelfPtr;
    DWORD pos = Adr2Pos(vmtAdr) + Vmt.ClassName;
...
Vmt.SelfPtr, Vmt.ClassName — это константы относительных смещений до полей данной структуры, инициализирующихся в методах DelphiVmt::SetVmtConsts и DelphiVmt::AdjustVmtConsts. Они зависят от версии Delphi, и нам сильно повезло, что они не менялись с 2014 года. Проанализировав их, составим схему блока VMT для класса, приведенного на предыдущем скриншоте:
Код:
SelfPtr         DQ  3A807010h
IntfTable       DQ  3A806f10h
AutoTable       DQ  0
InitTable       DQ  3A807010h
TypeInfo        DQ  3A807A10h ; TMkManager
FieldTable      DQ  3A80702Bh
MethodTable     DQ  3A80716Bh
DynamicTable        DQ  0
ClassName       DQ  3A8072D9h ; TMkManager
InstanceSize        DQ  0B8h
Parent          DQ  3A813848h
Equals          DQ  3A801080h
GetHashCode     DQ  3A801090h
ToString        DQ  3A8010B0h
SafeCallException   DQ  3A8010A0h
AfterConstruction   DQ  3A807B50h
BeforeDestruction   DQ  3A8012F0h
Dispatch        DQ  3A8010D0h
DefaultHandler      DQ  3A801300h
NewInstance     DQ  3A801020h
FreeInstance        DQ  3A807FE0h
Destroy         DQ  3A801030h
Как видим, раскладка 64-битной структуры вполне соответствует приведенному выше описанию. Не буду утомлять читателей подробным разбором структуры всяких полезных таблиц, ссылки на которые можно получить из содержимого полей. При желании ты можешь разобраться в этом сам, изучив исходники IDR. Тем более что в реальной жизни столь подробный разбор кода обычно и не нужен, если только пользователь не желает написать свой собственный декомпилятор покруче IDR. Рассмотрим практический способ применения этой информации для анализа дельфовского кода без специальных инструментов, прямо во время отладки приложения в нашем любимом отладчике x64dbg.

Итак, ковыряя приложение в этом отладчике, мы набрели на некий класс TMkManager, ответственный за связь приложения с ключом MetroKey. Для наглядности: в classwiever IDR64 список методов данного класса выглядят примерно так.

5.jpg


Рассмотрим организацию этого класса в памяти загруженного в отладчик процесса на основе анализа таблицы MethodTable.
6.jpg

Красным цветом выделено имя класса TMkManager, синим — имена его методов и их адреса, оранжевым — указатели на тип возвращаемого значения, а фиолетовым — параметры каждого метода, их имена и типы. Как видишь, значения возвращают только два из семи присутствующих на экране методов, хотя параметры есть у всех (как минимум Self).

Работает это так: допустим, в какой‑то момент мы при отладке процесса зашли в процедуру по адресу 5E4AB020, и она нас так заинтересовала, что мы решили узнать о ней подробности. Для этого мы ищем ссылку на нее. Простой референс наверняка не найдется, а вот если искать его как последовательность байтов 20,B0,4A,5E,00,00,00,00, то будет найден указатель по адресу 5E4A72E6 (выделено синим).

За ним следует строка со счетчиком, имя этого метода — GetKeyServerIP. Если полученная информация заинтересовала нас еще сильнее, то следующий указатель 5E4B3958 (выделено оранжевым) ссылается на другой указатель (в нашем примере по стрелке 59913С0), который, в свою очередь, указывает на имя типа возвращаемого значения — string.

Двигаясь дальше, мы обнаруживаем выделенное фиолетовым имя первого параметра этого метода Self и указатель на его тип — 5E4A7A08. Так же как и в предыдущем случае, через два переименования мы выходим на имя типа, точнее, класса — искомый TMkManager, который по понятным причинам является родительским классом и для нашего метода.

На скриншоте видно, что если продолжить изыскания, то можно пройти по цепочке порождающих классов, но я предоставлю читателю, воодушевленному полученной информацией, самому развить эту тему, вплоть до написания собственных скриптов или даже декомпиляторов.

автор @МВК
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх