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

Статья Суровая жаба. Изучаем защиту Excelsior JET для программ на Java

top

(L3) cache
Пользователь
Регистрация
03.02.2020
Сообщения
252
Реакции
342
Автор @МВК, aka Mikhail Kondakov

На какие только ухищрения не приходится идти разработчикам программ на Java, чтобы усложнить взлом и реверс! Однако у всех подобных приложений есть слабое место: в определенный момент исполнения программа должна быть передана в JVM в исходных байт‑кодах, дизассемблировать который очень просто. Чтобы избежать этого, некоторые программисты вовсе избавляются от JVM-байт‑кода. Как хакеры обычно поступают в таких случаях? Сейчас разберемся!

Авторы одной программы, суровые сибирские программисты, решили поступить совсем радикальным способом: скомпилировали Java-код в натив, причем (по их собственному утверждению) с обфускацией и оптимизацией, как бы противоречиво это ни звучало. Фактически они пожертвовали кросс‑платформенностью (ну и зачем она, спрашивается, нужна в уже скомпилированной программе, заточенной под определенную архитектуру?).

Уж не знаю, насколько такой подход способствует оптимизации, — исследованное мной приложение чертовски неторопливо и прожорливо к ресурсам компьютера, а главное, занимает несколько сот мегабайт. Но реверс‑инженерам предложенный разработчиками подход сильно усложняет жизнь. Лично я не нашел в паблике внятного мануала по организации данных в таких программах, и во многих обзорах эта технология считается лучшей для защиты Java-приложений от взлома и декодинга. Называется она Excelsior JET.

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

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

Дело в том, что программа пишет в лог стек вызовов при возникновении исключений — там присутствуют и полные названия методов с классами, и даже имена исходных файлов Java, из которых они были скомпилированы вместе с номерами строк, выполняющих вложенные вызовы.

Это вдохновило меня на дальнейшие поиски. Как минимум при входе в каждый метод информация о нем каким‑то образом должна заноситься в отладочный стек. Бегло рассмотрев код, находим первую зацепку. На подавляющем большинстве процедур начало кода выглядит следующим образом (схожие места помечены стрелкой):
Код:
                add     rsp, 0FFFFFFFFFFFFFFF8h
                mov     eax, [rsp-0C00h]             ; <--------------------- 1
                lea     rax, unk_9EEDFC8             ; <--------------------- 2
                mov     [rsp], rax                   ; <--------------------- 3
                add     rsp, 0FFFFFFFFFFFFFFF8h
                mov     eax, [rsp+8+var_C08]         ; <--------------------- 1
                lea     rax, unk_9F2E060             ; <--------------------- 2
                mov     [rsp+8+var_8], rax           ; <--------------------- 3
                push    rbx
                push    rbp
                push    rsi
                push    rdi
                push    r12
                push    r13
                push    r14
                add     rsp, 0FFFFFFFFFFFFFF60h
                mov     eax, [rsp+0D8h+var_CD8]      ; <--------------------- 1
                lea     rax, unk_ABFB080             ; <--------------------- 2
                mov     [rsp+0D8h+var_D8], rax       ; <--------------------- 3
                push    rbx
                push    rbp
                push    rsi
                push    rdi
                push    r12
                push    r13
                push    r14
                add     rsp, 0FFFFFFFFFFFFFFC0h
                mov     eax, [rsp+78h+var_C78]        ; <--------------------- 1
                lea     rax, unk_ABFB040              ; <--------------------- 2
                mov     [rsp+78h+var_78], rax         ; <--------------------- 3
Строка 1 чисто рудиментарная и никакой полезной нагрузки (во всяком случае, в приведенных выше примерах) не несет. Здесь в eax присваивается значение, лежащее на стеке выше текущего положения на C00h байт. Можно предположить, что это своеобразная защита от переполнения, — при вызове каждой процедуры на стеке гарантированно должен быть запас из C00h байт.

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

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

По счастью, создатели старенького легендарного отладчика OllyDbg перед тем, как проект закрылся, успели сделать тестовую 64-битную версию своей программы, очень сырую, с урезанными возможностями, но не конфликтующую с капризной и жадной до ресурсов софтиной. Итак, загружаем исследуемую программу в OllyDbg и останавливаем ее на начале любой из подобных процедур. Структура, ссылку на которую упорно кладут на стек, выглядит примерно так.

Структура, ссылку на которую кладут на стек

Видно, что у каждой процедуры есть своя собственная запись размером 0x40 байт. Не знаю, как они правильно называются, давай для удобства называть их структура-40 по их размеру. Назначение полей этой структуры малопонятно, за исключением указателя на процедуры (выделено синим) и по нулевому смещению указателя на другую, более интересную структуру, выделенного зеленым. У соседних записей ссылка на эту новую структуру одинакова, и, если присмотреться, в ней явно видно полное имя класса. Структура инициализирована в исходном коде, но без имени класса и некоторых полей.

Описатель класса, инициализированный в исходном коде (справа) и во время работы программы (слева)

При первом же взгляде на блок данных в месте, где должно располагаться имя класса, возникают смутные сомнения, что этот блок просто зашифрован каким‑то нехитрым шифром типа XOR. Дела продвигаются: у нас наметились уже два направления дальнейшей работы — определить соответствие процедур классам в изначальном коде и расшифровать их имена.

Как ни странно, метод решения у этих задач один: ставим точку останова типа Memory на интересный нам адрес и ждем в засаде, пока не поймается изменяющий его кусок кода.

Начнем с расшифровки имен классов. Ставим Memory breakpoint на первый байт строки java/ по адресу 72B7718 и запускаем программу. Наша ловушка сразу срабатывает на простенькой процедуре расшифровки:

Код:
jmp     short loc_93A54A
На входе RCX-адрес зашифрованной строки и RDX-адрес расшифрованной строки (в нашем случае исходный RCX). А еще R8-байт, с которым строка ксорится, в нашем случае это F9h.
Код:
loc_93A542:                             ; CODE XREF: sub_93A540+40↓j
                add     rcx, 1
                add     rdx, 1
loc_93A54A:                             ; CODE XREF: sub_93A540↑j
                movsx   eax, byte ptr [rcx]  ; EAX <- текущий байт строки
                test    eax, eax
                jz      short loc_93A56F
                cmp     r8d, eax
                jz      short loc_93A561     ; Проверки на конец строки — 0 или F9h
                mov     r9d, eax
                xor     eax, r8d             ; EAX <- EAX XOR R8D
                movsx   eax, al
                jmp     short loc_93A57B
; ---------------------------------------------------------------------------
loc_93A561:                             ; CODE XREF: sub_93A540+14↑j
                mov     r9d, eax
                mov     r10d, r9d
                mov     r9d, eax
                mov     eax, r10d
                jmp     short loc_93A57B
; ---------------------------------------------------------------------------
loc_93A56F:                             ; CODE XREF: sub_93A540+F↑j
                xor     r9d, r9d
                mov     r10d, r9d
                mov     r9d, eax
                mov     eax, r10d
loc_93A57B:                             ; CODE XREF: sub_93A540+1F↑j
                                        ; sub_93A540+2D↑j
                mov     [rdx], al       ; Текущий байт <- новое значение EAX
                test    r9d, r9d
                jnz     short loc_93A542
                retn
Предчувствия нас не обманули: это простейший XOR с фиксированным байтом F9h. Причем похоже, что названия всех описателей классов расшифровываются сразу в одной процедуре при старте программы. В таком случае попробуем извлечь из нее список всех классов и положение строки имени класса в структуре описателя.

sub_9BA640 proc near ; На входе RCX — адрес структуры CDES
Код:
            ...
            movsx   edx, byte ptr [rcx+0ACh] ; По смещению ACh в этой структуре байт-шифровальщик F9h
            mov     r8d, edx
            mov     rdx, rcx
            mov     rcx, rax
            mov     rbx, rdx
            mov     ebp, r8d
            call    sub_9C4500               ; Инициализация указателей на таблицу описателей классов
            jmp     short loc_9BA6A3
; ---------------------------------------------------------------------------

loc_9BA66A: ; CODE XREF: sub_9BA640+6B↓j
mov rcx, rax
mov rsi, rax
call sub_9C4540 ; Возвращает RAX — адрес текущего описателя класса
mov ecx, [rax+74h]
and ecx, 20h
cmp ecx, 20h ; Если бит 20h в двойном слове по смещению 74h от начала описателя класса установлен — название класса уже расшифровано
jz short loc_9BA6A0
mov ecx, [rax+14h] ; Двойное слово по смещению 14h от начала описателя класса — смещение до зашифрованного имени
movsxd rcx, ecx
add rcx, rax ; Абсолютный адрес строки имени в ECX
mov rdx, rcx ; И в RDX тоже
mov r8d, ebp ; Байт‑шифровальщик F9h
mov rdi, rax
call sub_93A540 ; Расшифровать имя
mov eax, [rdi+74h]
or eax, 20h
mov [rdi+74h], eax ; Установить флаг расшифрованности имени
Код:
            ...
            cmp     ecx, edx
            jle     short loc_9BA66A ; Следующий класс
            ...
            retn
sub_9BA640 endp
```
Итак, мы наконец‑то получили локализацию строки имени внутри описателя класса — относительное смещение до него. Попутно мы обнаружили еще одну интересную структуру, адрес которой данная процедура получает на вход. Назовем ее условно «структура CDES» по сигнатуре 53454443h в начале. Выглядит она так.

Структура CDES

Это самая базовая структура Excelsior JET. Помимо байта, которым шифруются текстовые строки (выделен красным), из нее идут ссылки на все базовые структуры и таблицы. Находится она по смещению 8 от начала секции _bss и, к сожалению, не инициализирована в исходном коде. К вопросу ее инициализации и получения из нее интересующих нас структур и таблиц мы вернемся чуть позже, для начала же попробуем локализовать таблицу описателей классов. Немного повозившись с процедурой sub_9C4500, находим внутри следующий код:
Код:
                mov     rax, [rdx+0C0h]  ; Адрес таблицы описателей классов по смещению 0C0h от начала структуры CDES (на предыдущем рисунке выделено синим)
                ...
                mov     edx, [rax-2Ch]   ; Номер первого рассматриваемого элемента в таблице — по смещению -2Ch от начала таблицы (на следующем рисунке выделено синим)
                mov     [rcx+10h], edx
                mov     edx, [rax-2Ch]   ; Номер первого рассматриваемого элемента в таблице — по смещению -2Ch от начала таблицы (на следующем рисунке выделено синим)
                mov     r8d, [rax-28h]   ; Количество рассматриваемых элементов в таблице — по смещению -28h от начала таблицы (на следующем рисунке выделено зеленым)
                lea     eax, [rdx+r8]    ; Номер последнего рассматриваемого элемента в таблице (на следующем рисунке выделено красным)
                sub     eax, 1
                mov     [rcx+14h], eax
                jmp     short loc_9C44C7
; ---------------------------------------------------------------------------
loc_9C44BE:                             ; В цикле перебираем все элементы таблицы, начиная с первого рассматриваемого на предмет принадлежности к описателям класса
                mov     eax, [rcx+10h]
                add     eax, 1
                mov     [rcx+10h], eax
loc_9C44C7:                             ; CODE XREF: sub_9C4480+3C↑j
                mov     rax, [rcx]
                mov     edx, [rcx+10h]
                mov     rax, [rax+rdx*8]
                movsx   edx, word ptr [rax+8]
                cmp     edx, 3            ; У описателей класса 16-битное слово по смещению 8 от начала структуры должно быть 3
                jz      short loc_9C44E3
                movsx   edx, word ptr [rax+8]
                cmp     edx, 4
                jnz     short sub_9C4480  ; ...или 4
loc_9C44E3:                             ; CODE XREF: sub_9C4480+58↑j
                mov     [rcx+18h], rax    ; Если элемент таблицы удовлетворяет этим условиям, возвращаем его адрес
                jmp     short locret_9C44FE
Сама таблица описателей классов выглядит так.

Таблица описателей классов

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

Теперь вернемся к инициализации «структуры-40». Записи «структуры-40» хоть в исходном коде и не инициализированы, однако расположены по вполне фиксированным адресам, на которые можно ставить бряки. Для примера берем первый же адрес 9F2E060 из одной такой процедуры. По остановке в данной точке мы получаем процедуру, инициализирующую «структуры-40» для каждого метода заданного класса. Насколько я понимаю, это происходит каждый раз при создании объекта. В упрощенном виде эта процедура выглядит примерно так (интересные места я выделил комментариями):
Код:
sub_936500      proc near
                ...
loc_936576:                             ; Цикл по всем элементам таблицы методов класса
                ...
                mov     [r11], r9       ; Место, на котором срабатывает breakpoint, R9-адрес текущего описателя класса — по смещению 0 «структуры-40»
                ...
                mov     ebp, [r9+0B0h]  ; Относительный адрес таблицы методов находится по смещению B0h внутри описателя класса
                test    ebp, ebp
                jz      short loc_93673D
                movsxd  rbp, ebp
                and     rbp, rax
                mov     rsi, [r9+30h]   ; По смещению 30h внутри описателя класса — указатель на «структуру CDES»
                mov     rsi, [rsi+58h]  ; По смещению 58h внутри «структуры CDES» — базовый адрес исполняемого модуля 400000h, на рисунке «Структура CDES» выделен оранжевым
                add     rbp, rsi        ; Абсолютный адрес таблицы методов класса в RBP
                jmp     short loc_93673F
; ---------------------------------------------------------------------------
loc_93673D:                             ; CODE XREF: sub_936500+228↑j
                xor     ebp, ebp
loc_93673F:                             ; CODE XREF: sub_936500+23B↑j
                test    rbp, rbp
                jz      short loc_936764
                mov     ebx, [rbp+rbx*4+0]  ; RBP[RBX] — относительный адрес текущего метода
                test    ebx, ebx
                jz      short loc_93675F
                movsxd  rbx, ebx
                mov     r9, [r9+30h]        ; По смещению 30h внутри описателя класса — указатель на «структуру CDES»
                and     rbx, rax
                mov     r9, [r9+58h]        ; По смещению 58h внутри «структуры CDES» — базовый адрес исполняемого модуля 400000h, на рисунке «Структура CDES» выделен оранжевым
                add     r9, rbx             ; Абсолютный адрес текущего в R9
                ...
                mov     [rdx+r10+18h], r9   ; Адрес метода по смещению 18h «структуры-40»
                ...
                jnz     loc_936576          ; Перейти к обработке следующего метода класса и следующего блока «структуры-40»
Разумеется, описанная процедура значительно сложнее и выполняет множество других функций, но на данный момент мы ищем вполне определенные фичи, и, похоже, мы их нашли. Резюмируя, попробуем для наглядности нарисовать примерную схему описателя класса.

Описатель класса

На этом рисунке оранжевым обозначен тип блока (описатель класса — 3 или 4), красным — имя класса и смещение на него относительно начала описателя, зеленым — указатель на таблицу описателей классов, синим — таблица методов класса и смещение на нее относительно базового адреса.

Ко всему прочему мы выяснили еще один интересный факт. Несмотря на то что нативное приложение 64-битное, адреса методов внутри описателя классов — 32-битные смещения относительно базового адреса модуля. На самом деле, если внимательно присмотреться, мы найдем в описателе класса таблицу с прямыми длинными ссылками (как я понимаю, на методы из внешних классов), но нам она в данный момент не нужна.

Не знаю, как создатели приложения выкручиваются в случае с большими исполняемыми модулями, не адресуемыми 32 битами. Возможно, они имеют несколько секций и «структур CDES». Мне, во всяком случае, таковые не попадались, поэтому вернемся к нашим баранам, то бишь к жабам :)

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

Направление, в котором следует копать в данном случае, очевидно: если обработчик исключений знает имена классов — спросим его об этом! Поскольку мы теперь знаем адреса методов каждого класса, тупо ставим бряки на всякий случай на все методы класса. Ну, например, на Throwable, а еще лучше на StackTrace.

Поискав в памяти загруженного модуля, мы находим такой класс — его полное имя com/excelsior/jet/runtime/excepts/stacktrace/StackTrace. Методов у этого класса немного, порядка двадцати, поэтому, просто установив точки останова на каждый из них, получаем срабатывание по адресу D193C0.

Указанный метод по адресу возврата определяет имя метода, из которого был выполнен вызов, имя исходного Java-файла, содержащего этот метод, и номер строки. Забегая вперед, скажу, что его полное имя com/excelsior/jet/runtime/excepts/stacktrace/StackTrace/a, и, к сожалению, код перед целевой компиляцией обфусцируется. Схематически он выглядит вот так:
Код:
sub_D193C0      proc near              ; На входе в RDX — адрес возврата со стека почему-то минус 1, то есть адрес байта, предшествующего адресу возврата из call
                ...
                mov     rcx, [rax+30h] ; RCX       <- Адрес CDES
                mov     r9, [rcx+0E8h] ; CDES[E8h] — таблица имен методов по адресам, на рисунке «Структура CDES» выделена фиолетовым
                test    r9, r9
                jz      loc_D19667
                mov     r10d, [r9]
                test    r10d, r10d
                jz      loc_D19667
                test    rdx, rdx
                jz      short loc_D19411
                mov     r10, [rcx+58h] ; Базовый адрес исполняемого модуля
                sub     rdx, r10       ; RDX <- относительный адрес возврата
                ...
                call    sub_D1A100 ; Эта процедура ищет в таблице имен методов по адресам относительный адрес RDX, то есть участок кода, внутри которого он находится
                test    rax, rax   ; На выходе в RAX — абсолютный адрес найденного элемента таблицы имен методов по адресам, содержащего адрес возврата
                jz      loc_D19662
                mov     ecx, 0FFFFFFFFh
                mov     edx, [rax+8] ; 32-битное слово по смещению 8 в найденном элементе — относительный адрес структуры описания метода
                test    edx, edx
                jz      short loc_D1944D
                movsxd  rdx, edx
                and     rdx, rcx
                mov     r8, [rsi+58h]  ; Базовый адрес исполняемого модуля
                add     rdx, r8        ; Получаем абсолютный адрес структуры описания метода
                jmp     short loc_D1944F
; ---------------------------------------------------------------------------
loc_D1944D:                             ; CODE XREF: sub_D193C0+7C↑j
                xor     edx, edx
loc_D1944F:                             ; CODE XREF: sub_D193C0+8B↑j
                mov     eax, [rax]      ; 32-битное слово по смещению 0 найденного элемента таблицы — стартовый адрес метода
                sub     ebx, eax        ; Смещение от начала метода до адреса возврата
                mov     r8, rcx
                mov     rcx, rdx
                mov     edx, ebx
                mov     r12, rcx
                mov     r13, r8
                call    sub_D1A000      ; Методом половинного деления ищем в структуре описания метода номер строки по относительному смещению адреса возврата
                ,,,
                mov     ecx, [rax+4]    ; Номер строки исходного Java-файла
                mov     edx, [r12+4]    ; Индекс зашифрованной строки имени метода
                mov     eax, [r12+8]    ; Индекс зашифрованной строки имени исходного Java-файла, содержащего метод
                ...
                retn
sub_D193C0      endp
Надеюсь, к настоящему моменту я тебя не слишком напугал обилием новых структур с собственными именами? Как ты, вероятно, заметил, приведенный выше кусок кода содержит целых три новых термина: таблица имен методов по адресам, структура описания метода и индекс зашифрованной строки. Остановимся на каждом из них подробнее.

Чтобы при возникновении ошибки отследить метод и строку вызова, Excelsior Jet хранит упорядоченные таблицы соответствия адрес — метод, и для каждого метода существует таблица «адрес — строка». Таблица соответствия имен методов по адресам, как ты уже догадался, абсолютно адресуется из «структуры CDES» 64-битным адресом по смещению E8h. На рисунке «Структура CDES» она выделена фиолетовым. Структура у нее совершенно прозрачная, она показана на следующей иллюстрации.
Таблица соответствия имен методов по адресам

Первое 32-битное слово (выделено красным) — количество элементов в таблице. Каждый элемент занимает 12 байт (первые два элемента выделены фиолетовым) и соответствует одному скомпилированному методу. Первое 32-битное слово элемента (выделено желтым) — относительный адрес точки входа метода. Последнее 32-битное слово (выделено голубым) — относительный адрес структуры описания метода.

Как видно, здесь применен такой же мухлеж с 32-битной адресацией кода и данных внутри 64-битного приложения: вместо нормальных 64-битных адресов используются 32-битные смещения относительно базы модуля, хранящегося в «структуре CDES».

Вернемся ко второй найденной структуре — структуре описания метода, которую я так назвал из‑за скудности собственной фантазии. Она не слишком отличается от таблицы имен методов по адресам и тоже представляет собой упорядоченную таблицу c размером в начале (выделено красным) и 12-байтовым элементом (первый элемент выделен фиолетовым).
Структура описания метода

Отличие от предыдущей таблицы состоит в том, что в самом начале структуры описания метода идут три 32-битных слова. Первое из них (на рисунке обведено оранжевым) — индекс класса, содержащего метод в таблице описателей классов. Второе (обведено синим) — индекс зашифрованного имени метода, а третье (обведено зеленым) — индекс зашифрованного имени исходного Java-файла.

Записи устроены следующим образом. Первый 32-битный элемент — смещение кода, в который компилируется Java-строка относительно начала метода, второй — номер строки в исходном Java-файле, а третий, судя по всему, не задействован вообще, поскольку я не видел его ненулевых значений. Поскольку обе описанные структуры представляют собой упорядоченные массивы (по сути, упорядоченные словари), любое место кода быстро и однозначно идентифицируется по ним методом дихотомии.

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

Как я уже говорил, из‑за паранойи разработчиков в Excelsior Jet, помимо имен классов, шифруются вообще все‑все‑все текстовые строки, причем одним и тем же алгоритмом — тупым XOR с хранящимся в «структуре CDES» байтом‑шифровальщиком (у нас это F9h). Но если имена классов стоят на своих местах в соответствующих структурах описания класса, то почти все остальные строки собраны в одном зашифрованном блоке, на который указывает абсолютный адрес по смещению C8h в «структуре CDES» (на рисунке «Структура CDES» выделено зеленым). Таким образом, упомянутые выше индексы представляют собой смещения относительно начала этого блока. Здесь я снова обращу твое внимание: создатели компилятора явно делают допущение, что в любом 64-битном приложении размер этого блока будет адресоваться 32 битами.

И в заключение для тех, кого эта немного сумбурная статья вдохновила написать более дружественный к пользователю декомпилятор, скрипт или плагин к отладчику, вернемся к вопросу поиска и локализации основных структур данных, упомянутых в статье. Как я уже говорил, основная «структура CDES», из которой идут ссылки на главные структуры, блоки и таблицы, инициализируется после запуска программы. То есть в уже запущенной программе все эти адреса посмотреть можно, но из дизассемблера придется искать. Попробуем это сделать. Инициализируется эта структура в самое начало секции _bss, при помощи IDA мы легко находим код инициализации:
Код:
sub_743B80      proc near               ; CODE XREF: sub_743FA0+25↓p
                mov     dword ptr [rcx], 53454443h      ; Сигнатура  CDES
                ...
                lea     rax, unk_9E31718                ; Hardcoded-адрес таблицы описателей классов
                mov     [rcx+0C0h], rax
                lea     rax, unk_14F3F6F4               ; Hardcoded-адрес блока зашифрованных строк
                mov     [rcx+0C8h], rax
                lea     rax, unk_15718A54
                mov     [rcx+0D0h], rax
                lea     rax, unk_9ED8428
                mov     [rcx+0E0h], rax
                lea     rax, aExfs      ; "EXFS\a"
                mov     [rcx+0D8h], rax
                lea     rax, unk_1571F50C
                mov     [rcx+0E8h], rax                 ; Hardcoded-адрес таблицы имен методов по адресам
Стратегия поиска такова: ищем в коде адрес присваивания сигнатуры 53454443h и, начиная с этого места, по маскам команд и смещений извлекаем нужные адреса. Идея весьма скользкая, поскольку в каждой новой версии компилятора авторы могут менять этот инициализационный код, из‑за чего нам придется перерабатывать алгоритм, но как рабочий вариант такой подход сгодится.
Ты, наверное, заметил, что байт‑шифровальщик F9h тоже не инициализируется в этой процедуре, он берется из другой странной структуры, находящейся в самом начале секции _config c сигнатурой CPB.
Структура

Как видишь, смещение этого байта в структуре равно 1Ch. Последний кусочек пазла встал на место, и остается только пожелать удачи энтузиастам реверса этого хитрого компилятора.
 
Кому захочется потестить, джаву в натив можно скомпилировать с помощью GraalVM: https://www.graalvm.org/
Он также поддерживается фреймворком Spring Boot - модуль Spring Native
 


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