Underground InformatioN Center [&articles] 
[network & security news] [RSS & Twitter] [articles, programing info] [books] [links, soft & more...] [soft archive][home]

Исследование полиморфного движка ASProtect 1.23

Введение
Написанная Алексеем Солодовниковым программа ASProtect является коммерческим средством защиты программного обеспечения. Первоначально это был просто упаковщик, носивший имя ASPack. Его вполне можно было использовать для уменьшения размера исполняемых файлов, но не для их защиты. Однако начиная с версии 2.11 в ASPack добавляется полиморфный движок вируса Win95.Marburg, что затрудняет отладку пакованных программ. (В этой статье будет описана именно работа полиморфика, а не распаковка секций защищенной программы и восстановление импорта.)
Затем на свет появляется ASProtect - мощная защита Win32 программ, которая также с версии 1.1 включает в себя этот полиморфик. Вот ее основные функции - сжатие и шифровка бинарного кода программы, добавление антиотладочного кода, а также кода противодействия дизассемблерам и программам снятия дампа памяти. ASProtect использует сильные криптографические алгоритмы для генерации регистрационных ключей - RSA 1024, BlowFish, TwoFish и так далее.
Взаимодействие защиты с целевым приложением осуществляется путем перехвата API вызовов. Особенности ASProtect - большое количество мутаций и защищенных ею условно бесплатных программ. Версия 1.11 вобрала в себя код противодействия перехвату собственных API из вируса Win32.Crypto и полиморфный движок из BPE32. Самая поздняя версия, которую я встречал "в диком виде" (здесь столько из кода вирусов, что думаю такое выражение будет уместно) - 1.23.

Внешний вид файлов
Будем рассматривать программу над которой поработали сначала компилятор VS .NET, а затем ASProtect 1.23. Начнем со сравнения секций PE без защиты и после ее установки.

Name    VS        VO        RS        RO        Char

до:
.text   000038F8  00001000  00003A00  00000400  60000020
.rdata  00000FB4  00005000  00001000  00003E00  40000040
.data   000007F8  00006000  00000400  00004E00  C0000040

после:
        00004000  00001000  00002200  00000400  С0000040
        00001000  00005000  00000600  00002600  С0000040
        00001000  00006000  00000200  00002E00  C0000040
.data   0000F000  00007000  0000E400  00002E00  C0000040
.adata  00001000  00016000  00000000  00011200  C0000040

Если внимательно посмотреть на границы секций в файле и в памяти (или разобрать заголовки), то видно, что выравнивание на диске 0х200 и 0х1000 в памяти защита не тронула. После заголовка в 0х400 байт в защищенной программе три исходных секции расположены в том же порядке, но без имен и слегка измельчав после шифрации. Зато появились две дополнительные секции .data и .adata. Обратите внимание, что атрибуты секций в защищенном варианте пытаются убедить нас, что кода здесь нет.

Инициализация
Не мудрствуя лукаво начинаем от точки входа, указанной в заголовке PE File:


  адрес    содержимое    инструкция
  401000   6801704000    push 00407001
  401005   E801000000    call 0040100B
  40100A   C3            ret
  40100B   C3            ret

Этот код находится в самом начале первой безымянной секции. Она же бывшая .text исходной программы. Смысл этого двенадцати байтового "нехилого молочного коктейля за пять долларов" - jmp 407001. Вам кажется, что это довольно извращенный переход? Дальше будет еще интереснее. Смотрим, что творится по адресу 407001 (Эта ячейка находится по смещению 1 от начала добавленной ASProtect’ом .data. В первом байте стоит nop.):

  407001   60            pushad
Сохранения состояния регистров.
  407002   E803000000    call 40700A
  407007   E9EB04        <пока не важно>
  40700A   9D            pop ebp
  40700B   45            inc ebp
  40700C   55            push ebp
  40700D   C3            ret

Здесь напрямую меняется адрес возврата. Если бы вместо строк 0A-0C было бы что-то более стандартное, то вернулись бы мы на 407007 E9. А так попадем на 407008 EB.

  407008   EB04          jmp 0040700E

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

  40700E                 call 00407014
  407013   EB
  407014                 pop ebp

Подобный код с мусорным байтом нам только что встречался. Здесь таким образом определяется адрес инструкции 407013. В принципе количество байт между call и pop могло быть абсолютно произвольным.

  407015                 mov ebx, FFFFFFED
  40701A                 add ebx, ebp
  40701C                 sub ebx, 00007000

В три инструкции получили в ebx адрес начала проекции файла в память отняв сначала от 407013 смещение ячейки от начала секции, а затем смещение секции от начала проекции.

  407022                 cmp dword ptr [ebp+25], 00
  407026                 mov [ebp+25], ebx
Сохранили полученный адрес.
  407029                 jnz 0040703c
  40702b                 lea eax, [ebp+2a]
  40702E                 push eax
  40702F                 push ebx
  407030                 push dword ptr [ebp+91d]

На стек заносятся три параметра - ничем не примечательный адрес недалеко от начала секции .data ASProtect’а - 407039, адрес начала проекции файла в памяти 400000 и адрес чего-то напоминающего таблицу импорта для трех функций GetProcAddress, GetModuleHandleA, LoadLibraryA.

  407036                 jmp 407057

Вот как раз следующий свободный адрес за этим переходом - 407039. При переходе на 407057 мы оставили за собой 1Е свободных байт.

  407057                 mov eax, 166906B1
  40705C                 call 40706C
  ...
  40706C                 push eax
  40706D                 push edi
  40706E                 push ecx
  40706F                 pop ebx
  407070                 pop ebx
  407071                 pop eax
Манипуляции со стеком - полнейший мусор. Опять никакой полезной работы.
  407072                 pop ecx

Теперь в ecx адрес следующей за вызовом 40706С ячейки памяти. Помните, я об этом говорил - количество байт между call и pop при определении адреса следующей за call инструкции может быть любым. И ничего страшного, если эти байты представляют собой не выполняющие реальной работы инструкции.

  407073                 jmp 407082
  ...
  407082                 add ecx, 814
  407088                 sub ax, 06FC
  40708D                 mov esi, 1DE
  407092                 mov bx, ax

Несмотря на то, что много инструкций было потрачено на два запутанных перехода и два определения адресов и просто мусорные инструкции (на этом участке, допустим, никакой нагрузки не несут операции над eax и ebx) мы закончили инициализацию регистров и памяти нужными значениями и с этого момента начинает работу непосредственно полиморфный движок, распаковывающий код самого ASProtect’а.
С этого момента установка программных точек останова для контроля работы программы теряет смысл. Можно либо пользоваться аппаратными (если ставящий защиту программист не предусмотрел работу с отладочными регистрами в коде защиты), либо через определенные промежутки времени контролировать инициализацию нужного байта нужным значением. Но при этом остановка произойдет не на требуемой инструкции, как бы ни был мал заданный промежуток времени.

Цикл расшифровки первой секции ASProtect’а
  407095                 mov edx, [ecx]
Получили в edx очередные (на этом шаге первые) четыре закодированных байта.
  407097                 mov bx, BC32
  40709B                 sub edx, 46F4DA7D
  4070A1                 mov ebx, edx
  4070A3                 xor edx, 0A814D72
  4070A9                 jmp 4070BB

Первая и третья инструкции - хлам. (Я буду выделять инструкции, выполняющие реальную работу жирным шрифтом.) Остальные расшифровывают порцию данных:

  4070BB                 sub edx, 36D2E5C3

Все, на этом трехшаговая (sub - xor - sub) расшифровка окончена. Дальше идет подготовка к следующему шагу цикла:

  4070C1                 mov edi, esi
  4070C3                 mov [ecx], edx
Теперь эти расшифрованные байты готовы к выполнению.
  4070C5                 mov bl, 59
  4070C7                 sub ecx, 01
  4070CA                 mov ebx, eax
  4070CC                 dec ecx
  4070CD                 dec ecx
  4070CE                 dec ecx
  4070CF                 mov ax, 9789
  4070D3                 sub esi, 1
  4070D6                 jnz 4070F0

Полезная работа этого участка - уменьшение на 4 ecx, теперь он указывает на следующий нерасшифрованный блок, и декремент счетчика цикла esi, куда до начала работы мы записали 1DE.

  4070F0                 mov bl, ah
  4070F1                 jmp 407095

Вот и все. 1DE раз программа будет брать по 4 байта, применять к ним sub - xor - sub и записывать полученный результат обратно. Еще обратите внимание на переход с 4070A9 на 4070BB. Если дизассемблер не поддерживает интерактивный режим, то вы не увидите инструкции с началом в 4070BB. Ближайшая будет начинаться в 4070BA. Этот переход запутывает дизассемблер.
Выйдем мы из цикла вот здесь:

  4070DC                 mov ebx, 22AFF0FBC
  4070E1                 jmp 407101

Переход в очередной раз осуществляется на неудобный для дизассемблирования адрес. Теперь становится понятно, что содержимое первой секции по адресам памяти 407000 - 407100 отвечает за самораспаковку, далее следует распакованный код. Этим циклом мы сделали исполняемым участок памяти 407101 - 407878.

  407101                 sbb si, 246C
  407105                 call 407115
  ...
  407115                 call 40712D
  ...
  40712D                 jmp 40713F

Это для нас уже не ново - sbb для отвода глаз, занос двух адресов на стек и переход дальше (опять с фокусом против дизассемблирования)

  40713F                 pop esi
  407140                 pop edi
Достали в esi 407117 и 407107 в edi.
  407141                 push 3FA33F01
  407146                 movzx ebx, dx
  407149                 pop ecx
Здесь нет ничего, что бы использовалось в дальнейшем.
  40714A                 add edi, 76A
  407150                 sub ecx, 6D8AE018
  407156                 sub edx, edx
  407158                 push esi
  407159                 jnp 407165
  40715F                 push 2915342E
  407164                 pop ebx
  407165                 pop ecx

Только две полезные инструкции - занулен edx, значение edi увеличено на 76A. Переход не будет выполнен никогда.

  407166                 push dword ptr [edi+edx]
  407169                 sub esi, 4F039B06
  40716F                 pop eax
  407170                 add ch, DE
  407173                 add eax, 2B105828
  407179                 mov esi, ebx
  40717B                 sub eax, 27C1E941
  407181                 sbb bx, 158D
  407186                 sub eax, 6BE84EE6
  40718C                 mov cl, 69
  40718E                 push eax
  40718F                 call 40719F

edi - база расшифровываемого участка, edx - смещение, равное на первой итерации расшифровки нулю.

  40719F                 mov ebx, eax
  4071A1                 pop ecx
  4071A2                 pop dword ptr [edx+edi]
  4071A5                 jae 4071B3
  4071AB                 jle 4071B3
Этот участок можно было бы заменить одной инструкцией безусловного перехода.
  4071B3                 mov bx, BDC6
  4071B7                 sub edx, 401D08B4
  4071BD                 push 0DD37F23
  4071C2                 jmp 4071DA
Модификация смещения и переход дальше.
  4071DA                 pop ecx
  4071DB                 add edx, 401D08B0
  4071E1                 jg 4071EB
  4071E7                 mov cx, 4B03
  4071EB                 cmp edx, FFFFF984
  4071F0                 jnz 407166

В результате этого шаманизма смещение уменьшается на 4 (т.е. на второй итерации оно будет -4d, затем -8d и так далее).
Давайте аккуратно вспомним, что на данный момент успело произойти с программой. В начале первой секции (бывшей секции кода) ASProtect вставил двенадцатибайтный переход на собственную первую секцию. В ней первые 0х100 байт уже находились в виде инструкций процессора (причем до смещения 0х95 находится код инициализации регистров нужными значениями, а дальше цикл расшифровки) и при выполнении этот код распаковал содержимое памяти по адресам 407101 - 407875. Распаковал-то он весь этот участок, но реально для расшифровки первичного распаковщика используются инструкции до смещения 1F0. Остальное вторично распаковывается от 407875 до 4071F9, после чего управление передается на

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

Заключение
После прохождения всех циклов распаковки секций ASProtect'а начинается основная часть работы - расшифровка кода защищенной программы. Это отдельный большой рассказ, выходящий за рамки статьи.

[c] wirepuller, (wirepuller@tut.by)
29.08.02

Все документы и программы на этом сайте собраны ТОЛЬКО для образовательных целей, мы не отвечаем ни за какие последствия, которые имели место как следствие использования этих материалов\программ. Вы используете все вышеперечисленное на свой страх и риск.

Любые материалы с этого сайта не могут быть скопированы без разрешения автора или администрации.


[network & security news] [RSS & Twitter] [articles, programing info] [books] [links, soft & more...] [soft archive][home]
 Underground InformatioN Center [&articles] Choose style:
2000-2015 © uinC Team