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

Статья Ломаем защиту приложений Enigma x64 актуальных версий

tabac

CPU register
Пользователь
Регистрация
30.09.2018
Сообщения
1 610
Решения
1
Реакции
3 332
Мы неоднократно публиковали развернутые статьи по дизассемблированию приложений и не собираемся останавливаться на достигнутом. Однако многие программы используют различные механизмы антиотладки, из‑за которых подступиться к ним становится непросто. Сегодня мы поговорим о том, как вскрыть популярную у разработчиков защиту Enigma версии 5 и выше, использующую продвинутые инструменты противодействия анализу и взлому.
Бытует распространенное мнение, что к категории крутых, хороших годных защит относятся VMProtect и Themida. В них, мол, и криптование, и виртуальные машины, и антиотладка. Все остальное — так, для лохов и младших школьников. Например, для какой‑нибудь «Энигмы» в сети можно найти кучу туториалов и видосов с инструкциями по взлому, воспользовавшись которыми, любой нуб может почувствовать себя кулхакером.

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

Большинство решений они позаимствовали у «взрослых» защит (ниже читатель сможет в этом убедиться). Однако, как известно, в этом мире нет ничего нового, и использование известных решений не делает взлом защиты более простым и приятным. Скорее наоборот. Давай убедимся в этом, хорошенько пощупав Enigma собственными руками.

ЭКСПЕРИМЕНТ​

Ради эксперимента скачаем какую‑нибудь программу (для простоты — дотнетовскую) и натравим на нее, например, Exeinfo. Предположим, анализатор опознал упаковщик как Enigma Protector x64 [v.5.0 — 7.0]. Вот и отлично, мысленно выдыхаем мы: не VMProtect и не Themida, а по Enigma точно что‑то можно найти на YouTube. Лезем в гугл — и правда, видосов полно, однако максимальная ломаемая версия — 4, а по x64 нет вообще ничего.

Ну что ж, думаем мы, не боги горшки обжигают: не могли же они придумать что‑то принципиально новое? Попробуем действовать по образу и подобию, авось получится. И вот тут нас поджидает неприятный сюрприз.

Практически все «кулхакерские» мануалы начинаются с одной и той же неизменной фразы: «загрузите программу в отладчик» или «создайте дамп приложения». Именно на этом шаге исследователя подстерегает первый коварный облом. В любимый всеми x64dbg программа не грузится вообще, дамперы запущенную софтину тоже не желают дампить, а если это и удается, то на выходе получается совершенно неработоспособный (и уж и близко не дотнетовский) кусок памяти. Даже в работающем приложении .NET не распознается от слова «совсем», dnSpy ее родной не признает и аттачить не хочет. Приаттачить программу удается разве что в x64dbg, но при малейшей попытке сдвинуться с точки останова процесс мгновенно закрывается. В общем, налицо все признаки взрослой защиты — шифрование кода, защита от дампа и отладчика.

Придется браться за дело основательно. Для начала установим плагины ScyllaHide и Scylla, чтобы хоть как‑то заработала отладка. Благодаря первой тулзе программа наконец‑то позволяет загрузить себя в отладчик. Однако радость оказывается преждевременной: после прерывания в процессе никакие настройки ScyllaHide не позволяют нам продвинуться дальше, программа захлопывается с завидным упорством. Scylla придет на помощь чуть позже, пока же попробуем извлечь всю выгоду из маленькой победы, которую мы только что одержали.

Итак, программа загружена в отладчик и пошагово трассируется. Потихоньку двигаемся, минуя несколько саморасшифровывающихся участков кода, и обнаруживаем, что они — всего лишь обвязка для огромной упакованной и зашифрованной секции, стартовая точка которой выглядит так:
Код:
push         rcx
push         rdx
push         r8
push         r9
mov          r8,000B5ED8E
call        .00007FF7`BA4D9610
mov          r8,0
mov          rdx,1
mov          rcx,[rsp][000000018]
call        .00007FF7`BA4E49A0
mov          r8,0
mov          rdx,1
mov          rcx,[rsp][000000018]
call        .00007FF7`BA13E400
pop          r9
pop          r8
pop          rdx
pop          rcx
call        .00007FF7`BA12A210
Вот тут нам и пригодится ранее установленная Scylla — эту секцию можно сдампить в EXE-файл. Толку, правда, от этого мало: модуль неработоспособный. Вдобавок это явно не наш искомый защищенный модуль, дотнетом там по‑прежнему не пахнет, однако пахнет — сюрприз! — Delphi. Оказывается, Enigma написана на дельфи! Но это вовсе не упрощает нам жизнь.

Углубляясь пошагово в последний call, мы довольно быстро вязнем в мешанине безумного кода. Похоже, мы не сильно продвинулись в нашем исследовании: замучаешься пошагово перемещаться до ближайшего осмысленного места, а безболезненно прерваться в процессе выполнения кода антиотладчик по‑прежнему не дает.

Попробуем зайти с другой стороны. Внимательно посмотрев на сдампленный модуль, мы обнаруживаем, что он не такой уж и бесполезный. У него, внезапно, есть экспортируемые символы:
Код:
0  .00007FF7`BABBD06E EP_RegHardwareID
1  .00007FF7`BABBD073 EP_RegHardwareIDA
2  .00007FF7`BABBD078 EP_RegHardwareIDW
3  .00007FF7`BABBD07D EP_RegCheckKey
4  .00007FF7`BABBD082 EP_RegCheckKeyA
5  .00007FF7`BABBD087 EP_RegCheckKeyW
6  .00007FF7`BABBD08C EP_RegSaveKey
7  .00007FF7`BABBD091 EP_RegSaveKeyA
8  .00007FF7`BABBD096 EP_RegSaveKeyW
9  .00007FF7`BABBD09B EP_RegLoadKey
10 .00007FF7`BABBD0A0 EP_RegLoadKeyA
11 .00007FF7`BABBD0A5 EP_RegLoadKeyW
12 .00007FF7`BABBD0AA EP_RegLoadAndCheckKey
13 .00007FF7`BABBD0AF EP_RegCheckAndSaveKey
14 .00007FF7`BABBD0B4 EP_RegCheckAndSaveKeyA
15 .00007FF7`BABBD0B9 EP_RegCheckAndSaveKeyW
16 .00007FF7`BABBD0BE EP_RegDeleteKey
...
Как нетрудно догадаться, EP — это Enigma Protection, а точки входа указывают на какие‑то очень полезные ништяки, содержащиеся внутри.

Ткнувшись подряд в некоторые из них, мы обнаружим, что через пару длинных jmp все они упираются в конструкцию следующего вида:
Код:
push         005C7AC29
jmp         .00007FF7`BA4F3F50
Что‑то подобное мы уже наблюдали в VMProtect. Ну конечно: мы таки вышли на виртуальную машину!

ДИЗАССЕМБЛИРУЕМ​

Ищем в распакованном‑расшифрованном коде начало интерпретатора:
Код:
push         rsp
push         rax
push         rcx
push         rdx
push         rbx
push         rbp
push         rsi
push         rdi
push         r8
push         r9
push         r10
push         r11
push         r12
push         r13
push         r14
push         r15
pushfq
mov          dl,1
mov          rsi,0
lea          rdi,[00007FF7`BA5032F0]
add          rdi,rsi
lea          rdi,[rdi]
mov          rcx,1
...
Теперь установим брейк‑пойнт на начало интерпретатора и еще раз убедимся — да, так оно и есть, этот фрагмент периодически вызывается среди необозримых просторов безумного кода.

Попробуем извлечь из нашего открытия максимум пользы. Прописываем в свежеиспеченную точку останова логирование — для начала RAX и параметр на стеке {RAX} {[RSP]}, после чего запускаем программу. Она, конечно, мгновенно захлопывается, ибо антиотладчик не дремлет, однако на прощанье она подарит нам развернутый лог вызовов виртуальной машины:
Код:
610 5C7C535
143336BE190 5C7CACA
143336BE1D0 5C7CACE
14335219760 5C7CAC5
630 5C7C754
7FF8DBCE0000 5C7C76D
143350D0000 5C7C778
7FF8DB160000 5C7C77F
7FF8DB17A360 5C7C770
615FF24EC83C3 5C7C777
7FF8DBD7C8C0 5C7C73F
7FF8D69911B0 5C7C73F
7FF8DBD7CD40 5C7C73F
7FF8DBD7D020 5C7C73F
7FF8DBD7CBE0 5C7C73F
7FF8DBD7CC20 5C7C73F
7FF8DBD7FF90 5C7C73F
...
143352119F0 5C7C549
143336BE928 5C7C544
F12A436D40745745 5C7C55E
15 5C7C56D
63BFFFD10 5C7C5AE
0 5C7C5BB
12 5C7C5BC
143352118F0 5C7C549
14335211910 5C7C549
14335211930 5C7C549
14335211950 5C7C549
14335211970 5C7C549
14335211990 5C7C549
143352119B0 5C7C549
143352119D0 5C7C549
143336BE968 5C7C544
704562405E78BE75 5C7C55E
8 5C7C56D
Безумный код потихоньку начинает обретать осмысленность: в частности, мы нашли конкретное место, после которого в программе срабатывает антиотладчик. Ставим условие для бряка (RAX==8 [RSP]==0x5C7C56D) и наблюдаем за поведением программы сразу после данного вызова виртуальной машины. Оказывается, вызов — это часть следующей конструкции:
Код:
mov          rcx,[00007FF7`BA49D9F0]
mov          r8d,7
call        .00007FF7`BA17C750      // вызов виртуальной машины
mov          rcx,[rbp][-018]
test         rcx,rcx
jnz         .00007FF7`BA167AA2
lea          rcx,[00007FF7`BA49B470]
call        .00007FF7`BA1270C0     // GetModuleHandleA (ntdll.dll)
mov          rcx,rax
mov          rdx,rbx
call        .00007FF7`BA127160     // GetProcAddress   (NtSetInformationThread)
mov          [rbp][-8],rax
cmp          q,[rbp][-8],0
jz          .00007FF7`BA167ADD
call        .00007FF7`BA1278A0
mov          rcx,rax
mov          r9d,0
mov          r8,0
mov          edx,000000011
call         q,[rbp][-8]
nop
mov          rcx,rbp
call        .00007FF7`BA1679D0
...
Соответственно, закоротив переход jz .00007FF7`BA167ADD, можно спокойно двигаться дальше (в смысле, до следующей подобной ловушки!). Чтобы не отнимать у читателя слишком много времени, коснемся принципа действия виртуальной машины, точку входа в интерпретатор которой мы только что нашли.

ПРИНЦИП ДЕЙСТВИЯ​

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

Вспоминая VMProtect, предположим, что волшебное число на стеке при входе в интерпретатор как‑то связано с адресом начала блока шитого кода, выполняемого интерпретатором при вызове. Вдумчиво трассируя интерпретатор, натыкаемся на такой код:
Код:
mov          [rbp][-8],ecx            ; волшебное число на стеке при входе в интерпретатор
mov          [rbp][-010],rdx
mov          edx,[00007FF7`BA5032B0]  ; волшебное число, с которым ксорится эта цифра для дополнительного мутирования кода
mov          eax,[rbp][-8]
xor          eax,edx                  ; вот здесь это и происходит
mov          [rbp][-020],eax
mov          rax,[00007FF7`BA5032C0]  ; относительная база шитого кода виртуальной машины — смещение начала шитого кода относительно начала модуля
mov          edx,[rbp][-020]
mov          edx,[rax][rdx]*4
mov          rax,[00007FF7`BA5032B8]  ; абсолютная база — адрес начала модуля
lea          rax,[rdx][rax]
; итого, адрес первой команды шитого кода виртуальной машины при входе в интерпретатор = [00007FF7`BA5032B8]+ [00007FF7`BA5032C0]+ (ecx xor [00007FF7`BA5032B0])*4 шитый код всегда выровнен на двойное слово и смещение задается именно в этих двойных словах
mov          [rbp][-000000088],rax
mov          rax,[rbp][-000000088]
cmp          d,[rax],000000231        ; а вот и выборка первой команды
jnz         .00007FF7`BA4ECB49
...
Предоставляю читателю самому разбираться с системой команд виртуальной машины, ибо она запутана, бессмысленна и беспощадна. При этом необязательно ее полное препарирование — как показал вышеописанный пример с антиотладчиком, местами достаточно знать вызовы отдельных блоков кода, на которые, кстати, указывают торчащие из экспортируемых функций уши. Для особо любопытных отмечу, что попавшая в конец вышеописанного фрагмента команда с опкодом 0x231 — это косвенный вызов процессорного (не виртуального) кода по заданному относительному смещению.

Остановимся подробнее на том, как формируется указанное смещение, — это поможет нам понять принцип взаимодействия отдельных блоков шитого кода друг с другом. Рассмотрим последовательность выхода из интерпретатора:
Код:
mov          rax,[rbp][-000000088]    ; текущий указатель команд виртуальной машины
mov          rdx,[rax][020]           ; относительный адрес машинного (не виртуального) кода, на который будет выполнен переход по выходу из интерпретатора внутри виртуальной команды завершения блока
mov          rax,[00007FF7`BA5032D0]  ; абсолютная база — адрес начала модуля
lea          rax,[rdx][rax]           ; адрес машинного кода = [00007FF7`BA5032D0] + относительный адрес внутри виртуальной команды
mov          [rbp][-018],rax
jmps        .00007FF7`BA4F3E04
jmp         .00007FF7`BA4ECAB8
mov          rax,[rbp][-018]          ; по выходу из интерпретатора RAX=адрес следующей команды для RET
mov          rbx,[rbp][-0000000B8]
lea          rsp,[rbp][0]
pop          rbp
retn
Мы видим, что из виртуального кода могут выполняться относительные вызовы непосредственно машинного кода (команда 0x631). Кроме того, по возврату из блока виртуального кода (ну, например, команда с опкодом 0x60) происходит возврат на заданный блок машинного кода, выполняющий конкретные действия. Таким образом фрагменты машинного и виртуального кода выполняются в определенной ветвящейся и вложенной последовательности, доступной для исследования пытливому уму при наличии достаточного количества свободного времени и мотивации.

ВЫВОД​

Напрашивается вывод: обновленная и усовершенствованная версия Enigma Protector x64 способна доставить изрядную головную боль исследователю, однако на любой хитрый болт всегда отыщется гайка с левой резьбой. Заимствование методов антиотладки у широко известных и популярных протекторов позволяет находить в защите скрытые лазейки, действуя по аналогии.

В итоге можно смело утверждать: универсальных защит не существует. Взломать можно все что угодно, было бы свободное время и, безусловно, желание.


Автор @МВК
 
Не самый лучший пример. Антиотладка не рассмотрена в принципе, ну вот тут занопили и дальше, да. А первопричина то какая? Ну хз... дальше трассируем... дальге разбираем вм, хоба и вы молодец.
 


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