Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
выпущен 12 ноября!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

Themida - обновлённый XProtector

Обсудить статью на форуме

Хорошая подборка видеоуроков, инструментов крэкера, книг и статей - здесь.

Автор: CrystalDragon (dragon) <c_dragon at mail.ru>

Прошло уже больше года с момента написания статьи про XProtector версии 1.07, с тех пор многое поменялось и многое из того, что было описано уже устарело. Вот собственно поэтому и выходит новая статья по этому протектору. Стоит сказать сразу, что стать скорее описание того, как исследовать протектор а не руководство по распаковке.

При исследовании использовалась ОС Windows XP SP2, для изучения желательно иметь установленную WinXP или Win2003, дизассемблер IDA(f.e. 4.80), Import Reconstructor, HEX-редактор, PE Tools with Extreme Dumper Plugin(причём не для дампа самого процесса) а также некоторые специфические инструменты, такие как эмулирующий отладчик, плагин, удаляющий мусорный код, и простенькая утилита R0cmd, которая использовалась ещё год назад и была в исходниках к предыдущей статье. Подопытная программа - WinLicense Demo at 01.11.2005

Общие принципы работы защиты остались те же, используется драйвер, который открывает доступ к IDT любому процессу, передавшему запрос драйверу oreans.sys, причём неважно будет ли этот процесс защищённой программой или эксплоитом для получения прав администратора, проникающем в ring0 с помощью этого вспомогательного драйвера протектора. Именно свободное использование IDT не даёт свободно отлаживать защищённую программу, т.к. протектор использует отладочные прерывания под свои нужды, например размещает свой обработчик в int1 или int3 и расшифровывает лежащий впереди код находясь в нулевом кольце. Абсолютно также как и раньше используется перехват функций ядра с помощью SST, по тем же принципам формируются переходники на импортируемые функции и защищается пользовательский код макросами SDK. Раз так, значит снимать защиту будет точно также - с помощью подгружаемой в адресное пространство своей DLL. В сопровождающем архиве приведен исходник этой DLL, принцип её действия это перехват всевозможных полезных событий и ведение лога с указанием адресов и прочих параметров.

Антидамповая защита и антиотладка

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

Логика такова, если происходит перезагрузка, причём сразу же после восстановления sst, то очевидно что протектор проверяет, на месте ли его обработчики функций. В таком случае проверка может быть в двух местах - в одном из многочисленных потоков, порождённых протектором, или во время переключения потоков либо процессов. Проверяется это легко, достаточно остановить все потоки, заморозив тем самым защищённый процесс. Делается это примерно так, внедрённая DLL собирает идентификаторы всех создаваемых потоков в таблицу, в любой момент можно вызвать SuspendThread в цикле, остановив их все, только последним надо останавливать текущий поток. После заморозки процесса восстановить SST не удаётся, следовательно этот вариант неверный и код проверки SST и IDT находится в ядре и вызывается при переключении контекстов. Для поиска применим такой оригинальный способ: делаем дампы главных модулей ядра Windows - ntoskrnl.exe(или аналога) и hal.dll дважды - до и после запуска протектора(вот именно для этого и нужен плагин extreme dumper). Если есть перехваты, они обнаружаться простым сравнением сравнением секций кода в дампах. Итак, сравнение дампов ntoskrnl:

B78C и др. - внутренности sst shadow, не интересно.
14938 - преобразовав это файловое смещение в RVA(для удобства можно использовать FLC из PE Tools, а ntoskrnl.exe загрузить в IDA по той базе, по которой она загружена у вас в памяти), выходим на функцию KeAttachProcess, которая используется для переключения в адресное пространство другого процесса и считается устаревшей, также не интересно.
31E4D - vsprintf, можно даже не разбираться сразу видно что это не то, что нужно.

смотрим hal.dll:

2278 - KfRaiseIrql, вот это уже очень интересно, т.к. эта функция используется ядром очень активно, в том числе и при переключении процессов. Смотрим, что же делает протектор при перехвате(адрес опять же проще поправить ImageBase в заголовке тому дампу где есть перехват и просто посмотреть адрес в IDA):



seg002:F8AAB000                 pusha
 seg002:F8AAB001                 call    $+5
 seg002:F8AAB006                 pop     ebp
 seg002:F8AAB007                 sub     ebp, 4CAFD95h
 
  ;Обновление переменной, содержащей число тиков таймера(не интересно для нас)
 seg002:F8AAB00D                 mov     eax, 80551280h  ; KeTickCount
 seg002:F8AAB012                 mov     eax, [eax]
 seg002:F8AAB014                 cmp     dword ptr [ebp+xvTickCount], 0
 seg002:F8AAB01B                 jnz     short loc_F8AAB023
 seg002:F8AAB01D                 mov     [ebp+xvTickCount], eax
 seg002:F8AAB023
 seg002:F8AAB023 loc_F8AAB023:                           ; CODE XREF: seg002:F8AAB01B
 seg002:F8AAB023                 mov     ebx, [ebp+xvTickCount]
 seg002:F8AAB029                 add     ebx, 78h ; ’x’
 seg002:F8AAB02C                 cmp     eax, ebx
 seg002:F8AAB02E                 jbe     _return_1
 seg002:F8AAB034                 mov     [ebp+xvTickCount], eax
 
  ;DR7 - управляющий отладочный регистр, десятый бит обязан быть установлен;
  ;остальный биты сбрасываются - такие образом удаляются все 4 брэйкпоинта(bpm)
 seg002:F8AAB03A                 mov     edx, 801BFAh
 seg002:F8AAB03F                 sub     edx, 8017FAh
 seg002:F8AAB045                 mov     dr7, edx        ; edx = 00000400
 
 
  ;загрузка в eax адреса IDT..
 seg002:F8AAB048                 mov     eax, 0AB57712h
 seg002:F8AAB04D                 sub     eax, 8AB18312h
 seg002:F8AAB052                 add     eax, 0Ch        ; int1 descriptor
 seg002:F8AAB055                 or      byte ptr [eax+1], 1100000b ; set DPL to 3
 
  ;читаем адрес обработчика
 seg002:F8AAB059                 mov     ecx, cs:[eax]
 seg002:F8AAB05C                 mov     cx, cs:[eax-4]  ; ecx = address of handler
 
  ;переход далее, если обработчик находится не в ядре
 seg002:F8AAB061                 cmp     ecx, 0FFFF0000h
 seg002:F8AAB067                 jnb     short _int3_check
 seg002:F8AAB069                 cmp     ecx, 80000000h
 seg002:F8AAB06F                 jb      short _int3_check
 
 
  ;а вот это интересно, если адрес обработчика находится в ядре, то он меняется на 0xFFFFFFFF,
  ;это значит что пока функция KfRaiseIrql перехвачена, восстановить IDT невозможно
 seg002:F8AAB071                 push    ds
 seg002:F8AAB072                 mov     ecx, 10h
 seg002:F8AAB077                 db      66h
 seg002:F8AAB077                 mov     ds, cx
 seg002:F8AAB07A                 assume ds:nothing
 seg002:F8AAB07A                 mov     cx, 0FFFFh
 seg002:F8AAB07E                 mov     [eax-4], cx
 seg002:F8AAB082                 mov     [eax+2], cx     ; set handler address to 0xFFFFFFFF
 seg002:F8AAB086                 pop     ds
 seg002:F8AAB087                 assume ds:seg003
 
  ;далее аналогично обрабатывается ещё одно отладочное прерывание
   ...
 
 seg002:F8AAB0DB _pf_test:                               ; CODE XREF: seg002:F8AAB0D4
 seg002:F8AAB0DB                 mov     esi, 0ED1DBFE1h
 seg002:F8AAB0E0                 add     esi, 60F5D3h
 seg002:F8AAB0E6                 cmp     byte ptr [esi], 0
 seg002:F8AAB0E9                 jz      loc_F8AAB1B4
 
  ;здесь читается адрес обработчика исключения #PF - page fault
  ;если IDT была восстановлена, то перезагружаем компьютер :)
 seg002:F8AAB0EF                 mov     esi, 0AB57712h
 seg002:F8AAB0F4                 sub     esi, 8AB18312h
 seg002:F8AAB0FA                 add     esi, 74h ; ’t’
 seg002:F8AAB0FD                 mov     eax, [esi]
 seg002:F8AAB0FF                 mov     ax, [esi-4]
 seg002:F8AAB103                 mov     esi, 9D0B83C2h
 seg002:F8AAB108                 xor     esi, 65A4B3C2h
 seg002:F8AAB10E                 cmp     esi, eax
 seg002:F8AAB110                 jz      short loc_F8AAB117
 seg002:F8AAB112                 jmp     _reboot
 
 seg002:F8AAB117 loc_F8AAB117:                           ; CODE XREF: seg002:F8AAB110
 seg002:F8AAB117                 mov     ecx, 202h
 seg002:F8AAB11C                 call    _hash1_calc
 seg002:F8AAB121                 mov     ebx, eax
 seg002:F8AAB123                 add     esi, 206h
 seg002:F8AAB129                 mov     ecx, 32h ; ’2’
 seg002:F8AAB12E                 call    _hash1_calc
 seg002:F8AAB133                 add     eax, ebx
 seg002:F8AAB135                 mov     ebx, 0ECF96E68h
 seg002:F8AAB13A                 add     ebx, 854748h
 
  ;это то, чего не было в xprotector’е и есть в themid’е - проверка контрольной суммы обработчика
  ;если пропатчен обработчик - перезагрузка
 seg002:F8AAB140                 cmp     [ebx], eax
 seg002:F8AAB142                 jz      short _pf_nopatch
 seg002:F8AAB144                 jmp     _reboot


Далее идёт однотипный код, проверяющий исключение #NP(неприсутствующего сегмента) и что более важно некоторых функций sst. Именно поэтому восстановление sst при "живом" перехвате KfRaiseIrql невозможно. Способ обхода очевиден - восстанавливаем 5 оригинальных байт обработчика этой функции hal.dll и вперёд, можно восстанавливать sst. Правда есть одно но, в разных версиях протектора перехватываются разные функции в hal.dll, поэтому предлагаю универсальный способ - после загрузки ОС снимать дамп секции кода hal.dll(можно за одно и ntoskrnl) и когда необходимо снять дамп, восстановить импорт или вообще просто открыть доступ к памяти, просто записываем на место оригинальную секцию кода и подгружаем sst с диска, всё, антидампа как не бывало. Можно даже восстановить IDT и отлаживать защищённый процесс ring3 отладчиками, правда только до первой ring0-decrypt конструкции, толку от такой отладки практически не будет.

Нахождение и восстановление кода на OEP

Themida имеет опцию защиты, позволюющую прятать оригинальную точку входа программы, чего не было в xprotector’е. Делается это общеизвестным способом, инструкции крадутся с OEP, разбавляются мусором и отправляются куда-нибудь подальше в память. Чтобы научится ловить эти инструкции и вообще переход на OEP, надо знать, где конкретно располагаются эти краденные инструкции. Чтобы это узнать используем "троянский принцип" - подкинем протектору инструкцию, вызывающую исключение. А конкретно берём любую программу(например всеми любимый calc.exe), меняем первый байт на OEP на 0xFA(инструкция cli, причём лучше два байта 0xFA подряд на OEP вписать, чтобы структуру кода не нарушить), запаковываем его WinLicense, не забыв указать опцию прятания OEP и дело сделано - при начале исполнения программы будем получать исключение EXCEPTION_PRIV_INSTRUCTION. Т.к. мы имеем в адресном пространстве свою DLL, то ничто не мешает перехватить это исключение, и VEH подходит для этого как нельзя лучше, т.к. векторный обработчик исполняется первым и для всех потоков процесса. Добавляем код, который выдаёт MessageBox при исключении привилегированной инструкции с заголовком, показывающим адрес исключения выдаст, снимаем дамп и можно изучать код на OEP. Код, приведённый ниже обработан обновленным unscrambled-плагином:



 ;Это две инструкции cli, которые мы записали на EP calc.exe
 WinLicen:0123E87D                 cli
 WinLicen:0123E895                 cli
 
  ;А вот эти две инструкции заменяют одну - push 010015E0h
 WinLicen:0123E907                 push    0C6301166h
 WinLicen:0123E924                 add     dword ptr [esp], 3AD0047Ah
 
  ;Здесь краденный с OEP код закончился
  ;1247Ch - это RVA первой некраденной инструкции на OEP
 WinLicen:0123E92E                 push    1247Ch
 WinLicen:0123E933                 pushf
 WinLicen:0123E934                 cld
 WinLicen:0123E9CE                 push    eax
 WinLicen:0123EA16                 push    eax
 WinLicen:0123EA40                 mov     [esp], ebp
 WinLicen:0123EA61                 clc
 WinLicen:0123EA98                 call    $+5
 WinLicen:0123EA9D                 pop     ebp
 WinLicen:0123EA9E                 sub     ebp, 0AA7D278h
 
  ;Синхронизация(возможно сигнал окончиния распаковки)
 WinLicen:0123EB01                 mov     eax, [ebp+0A940E25h]
 WinLicen:0123EB07                 cld
 WinLicen:0123EB08                 mov     byte ptr [eax], 0
 WinLicen:0123EB2F                 mov     eax, [ebp+0A942385h]
 WinLicen:0123EB64                 pusha
 WinLicen:0123EB78                 pushf
 
  ;Здесь мы видим код, затирающий краденные байты, чтобы их нельзя было восстановить
  ;простым дампом во время выполнения, из этого следует, что придётся ловить окончание
  ;распаковки до выполнения этого блока кода
 WinLicen:0123EBCD                 call    $+5
 WinLicen:0123EBD2                 pop     edi
 WinLicen:0123EC42                 mov     eax, 0
 WinLicen:0123EC72                 mov     edx, edi
 WinLicen:0123EC93                 lea     ecx, [ebp+0AA7D108h]
 WinLicen:0123ECED                 sub     edx, ecx
 WinLicen:0123ED05                 sub     edi, edx
 WinLicen:0123ED24                 mov     ecx, 0F8h
 WinLicen:0123ED29                 cld
 WinLicen:0123ED2B                 sub     edi, ecx
 WinLicen:0123ED68                 lea     esi, [ebp+0AA7D108h]
 WinLicen:0123ED86                 lea     edx, [ebp+0AA7D5E9h]
 WinLicen:0123EDE6                 sub     edx, esi
 WinLicen:0123EDEA                 add     ecx, edx
 WinLicen:0123EE0E                 rep stosb
 WinLicen:0123EE10                 inc     edx
 WinLicen:0123EE11                 stosd
 
  ;выравнивание стека и переход в секцию кода программы
 WinLicen:0123EE8D                 popf
 WinLicen:0123EEAA                 cmc
 WinLicen:0123EEAB                 popa
 WinLicen:0123EED1                 pop     ebp
 WinLicen:0123EF12                 add     [esp+8], eax
 WinLicen:0123EF16                 cld
 WinLicen:0123EF17                 pop     eax
 WinLicen:0123EF91                 popf
 WinLicen:0123EF92                 retn


Как видно с помощью плагина не составляет труда восстановить украденные инструкции и найти истинную OEP, но этого мало, надо ещё научится останавливать поток перед тем, как код будет затёрт нулями, но после того, как он будет расшифрован. Немного поисследовав код в окрестностях можно сделать несколько выводов. Во-первых краденые байты лежат практически в самом конце секции протектора. Во-вторых до передачи управления на эти байты они зашифрованы, и расшифровываются одним из ring0-дешифровщиков, лежашим чуть выше. А для вызова дешифровщика необходимо записать в IDT его адрес. Смотрим, как протектор это делает:



 ;загружаем в eax адрес выхода из прерывания(после окончания работы дешифровщика инструкция
  ;iret передаст управление на этот адрес), а в esi - адрес самого обработчика
 WinLicen:0123D181                 lea     eax, [ebp+0AA7D009h]
 WinLicen:0123D199                 lea     esi, [ebp+0AA7C1D0h]
 
  ;Если вдруг страница с обработчиком прерывания будет сброшена в файл подкачки, то
  ;вместо дешифровки кода появится синий экран. Эти команды подгружают страницы в таком случае
 WinLicen:0123D1BC                 mov     edx, [eax]
 WinLicen:0123D1E6                 mov     [eax], edx
 WinLicen:0123D221                 mov     edx, [eax+12Ch]
 WinLicen:0123D262                 mov     [eax+12Ch], edx
 
  ;...
 
  ;А здесь мы видим вызов функции, устанавливающей обработчики прерываний int1 и int3.
  ;Адрес передаётся через стек. Обратите внимание, что функция перезаписывает IDT для
  ;всех процессоров в системе
 WinLicen:0123D703                 xchg    eax, esi
 WinLicen:0123D72C                 push    eax
 WinLicen:0123D73B                 xchg    eax, esi
 WinLicen:0123D746                 lea     edx, [ebp+xf_set_dbg_int]
 WinLicen:0123D765                 call    edx
 
  ;...
 
  ;Начало обработчика 3-его прерывания
 WinLicen:0123D9F5                 mov     [esp], eax
 WinLicen:0123DA1C                 push    0
 WinLicen:0123DA1F                 lea     eax, [ebp+0AA7D009h]
 WinLicen:0123DA3D                 add     eax, 5
 WinLicen:0123DA82                 push    eax
 WinLicen:0123DA9C                 push    eax
 WinLicen:0123DAA8                 mov     [esp], eax
 
  ;В каждом подобном обработчике сбрасываются отладочные регистры
 WinLicen:0123DAC9                 sub     eax, eax
 WinLicen:0123DAF7                 mov     dr0, eax
 WinLicen:0123DB36                 mov     dr1, eax
 WinLicen:0123DB74                 mov     dr2, eax
 WinLicen:0123DBC3                 mov     dr3, eax
 WinLicen:0123DBF0                 pop     eax
 WinLicen:0123DBF1                 cld
 WinLicen:0123DBF2                 mov     edx, [esp+4]
 WinLicen:0123DC2C                 mov     edi, [esp]
 WinLicen:0123DC6E                 mov     ecx, 0
 WinLicen:0123DC99 ; ---------------------------------------------------------------------------
 WinLicen:0123DC99
 WinLicen:0123DC99 loc_123DC99:                            ; CODE XREF: WinLicen:0123E512
 WinLicen:0123DC99                 mov     eax, 400h
 WinLicen:0123DC9E                 mov     dr7, eax
 
  ;...
 
  ;Код впереди на данный момент расшифрован, записываем 0xFFFFFFFF на место(т.е. в качестве
  ;обработчиков int1 и int3). Если вспомнить код обработчика перехваченной функции KfRaiseIrql,
  ;то он записывал 0xFFFFFFFF в IDT только если обработчики 1-ого и 3-его прерывания находились
  ;в ядре, а сейчас адрес обработчиков - 0123D9F5h, поэтому протектор и вызывает снова функцию
  ;xf_set_dbg_int, чтобы деактивировать отладочные прерывания
 WinLicen:0123E5B7                 push    8BF57B2Ah
 WinLicen:0123E5C3                 xor     dword ptr [esp], 740A84D5h
 WinLicen:0123E5EF                 lea     ecx, [ebp+xf_set_dbg_int]
 WinLicen:0123E61D                 call    ecx
 WinLicen:0123E64B                 cld
 
  ;Это видимо для подстраховки, дублирование xf_set_dbg_int только для текущего процессора
  ;(т.е. для того, на котором выполняется этот код)
 WinLicen:0123E64C                 push    eax
 WinLicen:0123E667                 mov     [esp], eax
 WinLicen:0123E69F                 sidt    qword ptr [esp-2]
 WinLicen:0123E6B7                 pop     eax
 WinLicen:0123E6B9                 add     eax, 0Ch
 WinLicen:0123E6D3                 mov     word ptr [eax-4], 0FFFFh
 WinLicen:0123E6F4                 mov     word ptr [eax+2], 0FFFFh
 WinLicen:0123E718                 push    eax
 WinLicen:0123E738                 mov     [esp], eax
 WinLicen:0123E759                 sidt    qword ptr [esp-2]
 WinLicen:0123E779                 pop     eax
 WinLicen:0123E784                 add     eax, 1Ch
 WinLicen:0123E79E                 mov     word ptr [eax-4], 0FFFFh
 WinLicen:0123E7A5                 mov     word ptr [eax+2], 0FFFFh
 WinLicen:0123E7AC                 mov     ecx, [ebp+0A9420A1h]
 WinLicen:0123E7CA                 mov     byte ptr [ecx], 0
 WinLicen:0123E7CD                 stc
 WinLicen:0123E7CE
 WinLicen:0123E7CE loc_123E7CE:                            ; CODE XREF: WinLicen:0123E570
 WinLicen:0123E7CE                 pop     ecx
 WinLicen:0123E7F1                 add     esp, 8
 
  ;Выход обратно в ring3
 WinLicen:0123E80E                 iret


Наиболее простой путь это перехват внутренней функции протектора - xf_set_dbg_int. Пусть наш код будет скидывать в лог адреса вызова(точнее адреса следующие за вызовом) перехваченной функции, когда этим адресом окажется 0123E61Fh, можно спокойно снимать дамп, определять положение OEP и восстанавливать краденные байты. Правда остаются проблемы - как найти функцию xf_set_dbg_int внутри защищённой программы и в какой момент её перехватывать. Искать можно несколькими способами, например найти любой ring0-decryptor в дампе и вызов этой функции, но есть способ проще и лучше - по сигнатуре. Внутренности этой функции не менялись со времён xprotector’а, поэтому поиск по сигнатуре будет работать абсолютно со всеми версиями протектора. А перехватывать лучще всего в момент, когда DLL получит уведомление о запуске последнего треда перед окончанием распаковки. Номер его можно посмотреть в логе, например в упакованном calc.exe последний тред имеет номер 28(включая главный). Также надо ещё помнить, что наш обработчик xf_set_dbg_int после расшифровки спертых с OEP байт будет выполняться в ring0 и перед тем, как снимать дамп необходимо вернуться в ring3(например чтобы вывести MessageBox с сообщением о том, что распаковка окончена). Для этого подменяем адрес возврата для инструкции iret прямо в стеке. Рассчитываем смещение в стеке: допустим при выполнение инструкции iret по адресу 0123E80Eh адрес возврата лежит по смещению 0. Поднимаемся вверх до call’а на 0123E61D и получаем что адрес возврата здесь: [esp + 0Ch]. Но учитывая, что мы находимся внутри процедуры, надо добавить ещё - 8(т.к. при вызове xf_set_dbg_int в стек кидается адрес возврата из функции и 1 параметр), получаем [esp + 14h]. Но в самом начале обработчика находится инструкция pushad, уменьщающая стек на 20h, в итоге адрес возврата будет находится по адресу [esp + 34h] внутри нашего обработчика xf_set_dbg_int. Сохраняем куда-нибудь старый адрес, и записываем на это место какой-нибудь свой адрес, где будет находится вызов MessageBox, выводящий сообщение "Stolen bytes at 0XXXXXXXXh". Теперь рассмотрим всё это подробнее на примере нашей цели - WinLicense Demo.



 ;Это небольшой кусок кода из внедрямой DLL, а конкретно всё, что касается
  ;поиска OEP и краденный байт
 
 cmp	reason, DLL_THREAD_ATTACH
 jnz	@@reason2
 
  ;========== Thread create processing ==========;
 	assume fs:NOTHING
 
  ;Читаем и сохраняем идентификатор нового потока, а также записываем сообщение в лог
 mov	eax, DWORD PTR fs:[24h]
 mov	edx, ThreadCounter
 mov	[offset threads + edx*4], eax
 
  @@l1:
 inc	ThreadCounter
 invoke	wsprintf, offset buffer1, offset fmt003, ThreadCounter, DWORD PTR fs:[24h]
 invoke	LogWrite, offset buffer1
  	assume fs:ERROR
 
  ;Здесь надо указать номер последнего треда, который создаётся протектором,
  ;в WinLicense demo этот номер равен 26, все последующие создаёт уже пользовательский код
 cmp	ThreadCounter, 26
 jnz	@@exit
 
 
  ;Поиск xf_set_dbg_int по сигнатуре
 invoke	find_signature, MHandle, ImageSize, offset xf_set_dbg_int_sign
 test	eax, eax
 jz	@@l2	;если не найдено
 
  ;Вывод в лог адреса найденной функции
 mov	ebx, eax
 invoke	wsprintf, offset buffer1, offset fmt005, ebx
 invoke	LogWrite, offset buffer1
 
  ;Перехват функции
 mov	eax, offset h_xf_set_dbg_int_sign
 mov	BYTE PTR [ebx], 0E9h
 sub	eax, ebx
 sub	eax, 5
 mov	DWORD PTR [ebx+1], eax
 
  ;Сохраняем адрес для перехода на него из нашего обработчика
 add	ebx, 5
 mov	_xf_set_dbg_int, ebx
 jmp	@@exit
 
  ;Вывод в лог сообшения о том, что функция xf_set_dbg_int не обнаружена
  @@l2:
 invoke	LogWrite, offset mess002
 jmp	@@exit


Количество тредов можно ввести максимальное и уменьшать, пока количество изменений не возрастёт резко до несколько десятков тысяч. При запуске создаётся 29 тредов, но 3 последние - порождение уже не протектора а пользовательского кода. Теперь рассмотрим наш обработчик xf_set_dbg_int_sign:


 ;Вот этот адрес надо будет определить, а точнее переписать из лога
 LAST_IDTACCESS		equ		0BE8668h
 
  ;Об этих константах рассказано далее
 ;_offset1		equ		0
 ;_sleeparg		equ		0
 
 h_xf_set_dbg_int_sign proc
 pusha
 
  ;Увеличение счётчика обращений к IDT
 inc	idtacc_c
 
  ;"Обновление" переменной - максимально большего адреса обращения к IDT
 mov	eax, [esp + 20h]
 cmp	eax, top_idtacc
 jbe	@@exit
 
 mov	top_idtacc, eax
 
  ;Распаковка окончена?
 cmp	eax, LAST_IDTACCESS
 jnz	@@exit
 
  ;_offset1 - смещение адреса возврата с ring0-декриптора,
  ;о том как его искать было рассказано выше
 
   ifdef _offset1
 mov	eax, [esp + _offset1]
 mov	oep, eax
 mov	DWORD PTR [esp + _offset1], offset @@oep
   else
 invoke	MessageBox, NULL, offset mess003, offset mess, MB_OK
   endif
 
 jmp	@@exit
 
  @@oep:
 pusha
 
  ;...
 
   ifdef _sleeparg
 mov	BYTE PTR ds:[_sleeparg], 0FFh
   endif
 
 
  ;...
 
 invoke	MessageBox, NULL, offset mess004, offset mess, MB_OK
 popa
 cli ;исключение обеспечит корректный выход на 100%
 
 
  @@exit:
 mov     ebx, [esp+24h]	;инструкция с начала перехваченной функции в протекторе
 jmp	_xf_set_dbg_int
 h_xf_set_dbg_int_sign endp


После определения количество тредов и адреса последнего обращения к IDT можно снимать дамп и изучать окрестности OEP. Адреса этих самых окрестностей нужно брать из лога: top address 0XXXXXXXXh - адрес, где меняется IDT, будет известен при перехвате xf_set_dbg_int, oep(?) = 0XXXXXXXXh - адрес выхода из последнего перед OEP дешифровщика нулевого кольца, этот будет известен, только после определения смещения в стеке этого самого адреса


 ;Вызов xf_set_dbg_int, однако "окружение" этого вызова выглядит совсем не так, как в
  ;запакованном calc.exe, потому что это другой тип ring0-decryptor’а
  ;Высчитывая смещение стека, где хранится EIP для инструкции iret:
  ;-20(смещение стека перед вызовом) -8(адрес возврата и параметр) - 20(инструкция pusha
  ;в начале h_xf_set_dbg_int_sign, приведённой выше) получаем -48, вписываем
  ;константу _offset1 равную 48h в исходник DLL
 WinLicen:00BE865D                 push    0FFFFFFFFh
 WinLicen:00BE8662                 call    dword ptr [ebp+4BF2A11h]
 WinLicen:00BE8668                 push    ecx	;-20
 WinLicen:00BE8669                 sidt    qword ptr [esp-2]
 WinLicen:00BE866E                 pop     ecx	;-24
 WinLicen:00BE866F                 add     ecx, 0Ch
 WinLicen:00BE8672                 mov     word ptr [ecx-4], 0FFFFh
 WinLicen:00BE8678                 mov     word ptr [ecx+2], 0FFFFh
 WinLicen:00BE867E                 mov     ecx, [ebp+4BF03E9h]
 WinLicen:00BE8684                 mov     byte ptr [ecx], 0
 WinLicen:00BE8687                 mov     eax, ebx
 WinLicen:00BE86AF                 popa	;-20
 WinLicen:00BE86B0                 iret	;0
 
  ;...
 
 WinLicen:00BE86DA                 mov     dword ptr [ebp+4BF0449h], 4D20h
 WinLicen:00BE86ED                 mov     dword ptr [ebp+4BF12B9h], 0
 WinLicen:00BE870D                 push    dword ptr [ebp+4BF1DB5h]
 WinLicen:00BE8713                 mov     [ebp+4BF14C1h], esi
 WinLicen:00BE8719                 call    dword ptr [ebp+4BF1C61h]
 WinLicen:00BE87EC                 mov     esi, ecx
 WinLicen:00BE87EE
 
  ;Хмм.. бесконечный цикл. Очевидно, что по адресу 00BE8719h вызывается SetEvent
  ;я правда не проверял, вдруг это не так :)
  ;А по адресу 00BE87F0h - Sleep. Оба этих адреса лежат в heap’е, напомню, что некторые библиотеки,
  ;включая kernel32.dll подгружаются с диска и функции вызываются минуя kernel32.dll в памяти
 WinLicen:00BE87EE loc_BE87EE:                             ; CODE XREF: WinLicen:00BE87F8
 WinLicen:00BE87EE                 push    0
 WinLicen:00BE87F0                 call    dword ptr [ebp+4BF2851h]
 WinLicen:00BE87F6                 mov     eax, eax
 WinLicen:00BE87F8                 jmp     short loc_BE87EE


Дальнейшую расщифровку будет выполнять другой тред, и нужно как-то поймать окончание этой расшифровки. Недолго думая вписываем вместо инструкции push 0 по адресу 00BE87EEh инструкцию push 0FFFFFFFFh(т.о. константа _sleeparg будет равна 00BE87EFh) тем самым делаю задержку более чем на 4 миллиарда секунд, которого уж точно хватит для того, чтобы тред успел закончить расшифровку и даже для того, чтобы снять дамп уже с готовыми к восстановлению краденными байтами:


 ;замена push ebp
 WinLicen:00BE8859                 push    eax
 WinLicen:00BE886E                 mov     [esp], ebp
 
 WinLicen:00BE8891                 mov     ebp, esp
 WinLicen:00BE88BA                 add     esp, 0FFFFFFE0h
 
  ;push ebx
 WinLicen:00BE88F7                 xchg    eax, ebx
 WinLicen:00BE892D                 push    eax
 WinLicen:00BE897C                 xchg    eax, ebx
 
 WinLicen:00BE897E                 sub     eax, eax
 WinLicen:00BE89C6                 mov     [ebp-20h], eax
 WinLicen:00BE89F3                 mov     [ebp-1Ch], eax
 WinLicen:00BE8A0C                 mov     [ebp-18h], eax
 WinLicen:00BE8A57                 mov     [ebp-14h], eax
 WinLicen:00BE8A9C                 mov     eax, offset dword_776200
 
  ;далее идёт код протектора, из которого нам полезна только следующая инструкция,
  ;где в стек записывается RVA OEP + 0XXh, где 0XXh - размер украденных инструкций
 WinLicen:00BE8AA4                 push    376D22h
 WinLicen:00BE8AA9                 pushf
  ;...
 
 
  ;Вбиваем в FASM или HIEW эти инструкции, определяем их размер(26 байт),
  ;из этого следует, что в WinLicense Demo OEP = 776D08h(RVA=376D08h)
  ;и именно туда надо добавить эти инструкции
 push    ebp
 mov     ebp, esp
 add     esp, 0FFFFFFE0h
 push    ebx
 xor     eax, eax
 mov     [ebp-20h], eax
 mov     [ebp-1Ch], eax
 mov     [ebp-18h], eax
 mov     [ebp-14h], eax
 mov     eax, 776200h


На этом заканчивается работа над OEP. Теперь можно переходить к импорту.

Восстановление импорта

В основном восстановление импорта мало чем отличается от xprotector’а, но некоторые моменты стоит упомянуть, особенно перехват API-функций. Ведь теперь протектор замечает изменение кода в native API функциях, но всё же есть пока способ не лезть в ядро. Каждое обращение к ядру идёт через функцию KiFastSystemCall, а адрес её записан здесь: 7FFE0300h, оттуда он каждый и читается. Запись в эту область памяти запрещена для кода режима пользователя, и к тому же эта область является общей для всех процессов, и если даже написать свой драйвер, который будет записывать туда что-нибудь по требованию система сразу рухнет, потому что все остальные процессы окажутся изолированными от ядра. Выход в том, что надо как-то сделать страницу памяти контекстно-зависимой. Как раз для этого Windows NT использует зарезервированный девятый бит в таблице страниц, установление его равнозначно вызову VirtualProtect с параметром PAGE_WRITECOPY. Т.е. чтобы перехватить обращения к ядру, надо установить в PTE страницы с адресом 7FFE0000h бит copy-on-write и можно спокойно перехватывать. Причём драйвер можно всё-таки не писать, ведь протектор открывает доступ к IDT, и никто не помешает нам использовать любое прерывание для того, чтобы попасть в ring0:


hook_kifastsystemcall proc
 
  ;адрес дескриптора 255-ого прерывания
 mov	edx, 8003F400h+0FFh*8
 mov	eax, offset _intFF
 mov	[edx], ax	;младшие 16 бит смешения обработчика
 mov	WORD PTR [edx+2], 08h	;селектор обработчика
 shr	eax, 16
 mov	WORD PTR [edx+6], ax	;старшие 16 бит
 or	WORD PTR [edx+4], 6000h	;DPL устанавливаем 3
 
  ;Вызываем 255-ое прерывание, в eax - адрес для установки флага copy-on-write
 mov	eax, 7FFE0300h
 int	0FFh
 
  ;Собственно перехват
 mov	DWORD PTR ds:[7FFE0300h], offset hKiFastSystemCall
 ret
 hook_kifastsystemcall endp
 
  ;...
 
  _intFF:
 invoke	set_copyonwrite_flag, eax	;Вызов функции для установки флага
 iretd
 
 
  ;Это новая функция KiFastSystemCall
  hKiFastSystemCall proc
 
  ;Это необходимо для вызова функций API из перехваченной функции
  ;Например записи в чего-нибудь в лог, чтобы не было ненужной рекурсии
 cmp	hook_active, 1
 jz	@@systemcall
 
 
 pusha
 mov	hook_active, 1 
 
  ;Здесь можно размещать свой код для перехвата
 
  @@exit:
  
  ;Выход(почему-то masm не хочет ассемблировать инструкцию SYSENTER)
 popa
 mov	hook_active, 0
 mov	edx, esp
 db 0Fh, 034h	;SYSENTER
 
 
  @@systemcall:
 mov	edx, esp
 db 0Fh, 034h	;SYSENTER
 hKiFastSystemCall endp


А дальше полная аналогия с xprot-ом, перехватываем VirtualAlloc(только смещение адреса возврата из VirtualAlloc надо, это можно сделать в ollydbg или softice протрассировав вызов этой функции до инструкции SYSENTER и прибавив 20h уравновешивая инструкцию pusha в начале hKiFastSystemCall. В логе видно, что часто вызывается VirtualAlloc в адреса 0BD290Dh, это и есть процедура создания импорта, но если вписать этот адрес в исходник DLL, то можно устать закрывать MessageBox’ы, вписать туда надо адрес начала процедуры создания импорта, это 0BD209Ch. После сообщения "Imports creating begins; see log for detalis" можно снимать дамп и изучать создание импорта. Здесь я не буду приводить её полностью, а только самые важные моменты.


 ;ecx - ImageBase библиотеки импортируемая функция
 WinLicen:00BD2395                 cmp     dword ptr [ebp+4BF002Dh], 1
 WinLicen:00BD239C                 jz      loc_BD23DB
 WinLicen:00BD23A2                 cmp     ecx, [ebp+xvKernel32Handle]
 WinLicen:00BD23A8                 jz      loc_BD23DB
 WinLicen:00BD23AE                 cmp     ecx, [ebp+xvUser32Handle]
 WinLicen:00BD23B4                 jz      loc_BD23DB
 WinLicen:00BD23BA                 cmp     ecx, [ebp+xvAdvapi32Handle]
 WinLicen:00BD23C0                 jz      loc_BD23DB
 WinLicen:00BD23C6
 
  ;Если ни один переход не сработал то функция запишется в IAT как есть
  ;без переходника
 WinLicen:00BD23C6 _save_to_IATx1:                         ; CODE XREF: WinLicen:loc_BD2408
 WinLicen:00BD23C6                                         ; WinLicen:00BD242C ...
 WinLicen:00BD23C6                 lea     ebx, [ebp+xfiGetProcAddress]
 WinLicen:00BD23CC                 call    ebx
 WinLicen:00BD23CE
 WinLicen:00BD23CE _save_to_IATx0:                         ; CODE XREF: WinLicen:00BD245C
 WinLicen:00BD23CE                                         ; WinLicen:00BD2480 ...
 WinLicen:00BD23CE                 mov     edi, eax
 WinLicen:00BD23D0                 mov     [ebp+xvImportAddress], eax
 WinLicen:00BD23D6                 jmp     _save_to_IAT
 WinLicen:00BD23DB ; ---------------------------------------------------------------------------
 WinLicen:00BD23DB
 WinLicen:00BD23DB loc_BD23DB:                             ; CODE XREF: WinLicen:00BD239C
 WinLicen:00BD23DB                                         ; WinLicen:00BD23A8 ...
 WinLicen:00BD23DB                 lea     ebx, [ebp+xfiGetProcAddress]
 WinLicen:00BD23E1                 call    ebx
 WinLicen:00BD23E3                 cmp     dword ptr [ebp+4BF002Dh], 0
 WinLicen:00BD23EA                 jz      loc_BD240D      ; 00A9182Ch
 WinLicen:00BD23F0                 cmp     eax, [ebp+4BF25D1h] ; 00A91EB8h
 WinLicen:00BD23F6                 jz      loc_BD2408
 WinLicen:00BD23FC                 cmp     eax, [ebp+4BF01A1h] ; 00A8FA88h
 WinLicen:00BD2402                 jnz     loc_BD240D      ; 00A9182Ch
 WinLicen:00BD2408
 WinLicen:00BD2408 loc_BD2408:                             ; CODE XREF: WinLicen:00BD23F6
 WinLicen:00BD2408                 jmp     _save_to_IATx1
 WinLicen:00BD240D ; ---------------------------------------------------------------------------
 
 
  ;Замена некоторых функций протекторными эмуляторами соответствующий функций
 WinLicen:00BD240D loc_BD240D:                             ; CODE XREF: WinLicen:00BD23EA
 WinLicen:00BD240D                                         ; WinLicen:00BD2402
 WinLicen:00BD240D                 cmp     eax, [ebp+xfExitProcess] ; 00A9182Ch
 WinLicen:00BD2413                 jnz     loc_BD2431
 WinLicen:00BD2419                 cmp     dword ptr [ebp+4BF0A6Dh], 0
 WinLicen:00BD2420                 jnz     loc_BD2431
 WinLicen:00BD2426                 lea     eax, [ebp+xfi_emul_ExitProcess] ; 00BBB01D
 WinLicen:00BD242C                 jmp     _save_to_IATx1
 WinLicen:00BD2431 ; ---------------------------------------------------------------------------
 WinLicen:00BD2431
 WinLicen:00BD2431 loc_BD2431:                             ; CODE XREF: WinLicen:00BD2413
 WinLicen:00BD2431                                         ; WinLicen:00BD2420
 WinLicen:00BD2431                 cmp     eax, [ebp+xfExitProcess]
 WinLicen:00BD2437                 jz      _save_to_IATx1
 WinLicen:00BD243D                 cmp     dword ptr [ebp+4BF0CF5h], 0
 WinLicen:00BD2444                 jz      loc_BD2461
 WinLicen:00BD244A                 cmp     eax, [ebp+xfReadFile] ; 00A900B0h
 WinLicen:00BD2450                 jnz     loc_BD2461
 WinLicen:00BD2456                 lea     eax, [ebp+xfi_emul_ReadFile]
 WinLicen:00BD245C                 jmp     _save_to_IATx0
 WinLicen:00BD2461 ; ---------------------------------------------------------------------------
 WinLicen:00BD2461
 WinLicen:00BD2461 loc_BD2461:                             ; CODE XREF: WinLicen:00BD2444
 WinLicen:00BD2461                                         ; WinLicen:00BD2450
 WinLicen:00BD2461                 cmp     dword ptr [ebp+4D3201Fh], 1
 WinLicen:00BD2468                 jnz     loc_BD2485
 WinLicen:00BD246E                 cmp     eax, [ebp+xfGetProcAddress] ; 00BD195Dh
 WinLicen:00BD2474                 jnz     loc_BD2485
 WinLicen:00BD247A                 lea     eax, [ebp+xfi_emul_GetProcAddress] ; 00BCCBA6
 WinLicen:00BD2480                 jmp     _save_to_IATx0
 
 WinLicen:00BD2485
 WinLicen:00BD2485 loc_BD2485:                             ; CODE XREF: WinLicen:00BD2468
 WinLicen:00BD2485                                         ; WinLicen:00BD2474
 WinLicen:00BD2485                 xor     edi, edi
 WinLicen:00BD2487                 cmp     dword ptr [ebp+4BF1641h], 0
 WinLicen:00BD248E                 jz      loc_BD26A1
 WinLicen:00BD2494                 cmp     eax, [ebp+4D32062h] ; 00BD1949h
 WinLicen:00BD249A                 jnz     short loc_BD24A3 ; 00BD1951h
 WinLicen:00BD249C                 mov     eax, [ebp+4BF28FDh]
 WinLicen:00BD24A2                 inc     edi
 
  ;... пропустим мусор :)
 
 WinLicen:00BD26A1 loc_BD26A1:                             ; CODE XREF: WinLicen:00BD248E
 WinLicen:00BD26A1                                         ; WinLicen:00BD2698
 WinLicen:00BD26A1                 or      edi, edi
 WinLicen:00BD26A3                 jz      loc_BD26AE      ; 00A905B8h
 WinLicen:00BD26A9                 jmp     _save_to_IATx0
 WinLicen:00BD26AE ; ---------------------------------------------------------------------------
 
  ;После мусора видна замена функции wsprintfA переходником, подробнее рассмотрим это
  ;после в главе про макросы sdk
 WinLicen:00BD26AE loc_BD26AE:                             ; CODE XREF: WinLicen:00BD26A3
 WinLicen:00BD26AE                 cmp     eax, [ebp+xfwsprintf] ; 00A905B8h
 WinLicen:00BD26B4                 jnz     loc_BD26C5      ; 00A9185Ch
 WinLicen:00BD26BA                 lea     eax, [ebp+xfi_emul_wsprintf] ; 00B8CD09h
 WinLicen:00BD26C0                 jmp     _save_to_IATx0
 WinLicen:00BD26C5 ; ---------------------------------------------------------------------------
 WinLicen:00BD26C5
 WinLicen:00BD26C5 loc_BD26C5:                             ; CODE XREF: WinLicen:00BD26B4
 WinLicen:00BD26C5                 cmp     eax, [ebp+xfRaiseException] ; 00A9185Ch
 WinLicen:00BD26CB                 jnz     loc_BD26E9      ; 00BD1941h
 WinLicen:00BD26D1                 cmp     dword ptr [ebp+4D3201Fh], 1
 WinLicen:00BD26D8                 jnz     loc_BD26E9      ; 00BD1941h
 WinLicen:00BD26DE                 lea     eax, [ebp+xfi_emul_RaiseException] ; 00BCCB29h
 WinLicen:00BD26E4                 jmp     _save_to_IATx0
 WinLicen:00BD26E9 ; ---------------------------------------------------------------------------
 WinLicen:00BD26E9
 WinLicen:00BD26E9 loc_BD26E9:                             ; CODE XREF: WinLicen:00BD26CB
 WinLicen:00BD26E9                                         ; WinLicen:00BD26D8
 WinLicen:00BD26E9                 cmp     eax, [ebp+xfRtlEnterCriticalSection] ; 00BD1941h
 WinLicen:00BD26EF                 jz      loc_BD2701
 WinLicen:00BD26F5                 cmp     eax, [ebp+xfRtlLeaveCriticalSection] ; 00BD1945h
 WinLicen:00BD26FB                 jnz     loc_BD2706
 WinLicen:00BD2701
 WinLicen:00BD2701 loc_BD2701:                             ; CODE XREF: WinLicen:00BD26EF
 WinLicen:00BD2701                 jmp     _save_to_IATx0
 WinLicen:00BD2706 ; ---------------------------------------------------------------------------
 WinLicen:00BD2706
 WinLicen:00BD2706 loc_BD2706:                             ; CODE XREF: WinLicen:00BD26FB
 WinLicen:00BD2706                 mov     esi, 0
 WinLicen:00BD270B                 cmp     esi, 1
 WinLicen:00BD270E                 jnz     loc_BD2759
 WinLicen:00BD2714                 cmp     eax, [ebp+xfCreateThread] ; 00BD1935h
 WinLicen:00BD271A                 jnz     loc_BD272B      ; 00BD1939h
 WinLicen:00BD2720                 lea     eax, [ebp+xfi_emul_CreateThread]
 WinLicen:00BD2726                 jmp     _save_to_IATx0
 WinLicen:00BD272B ; ---------------------------------------------------------------------------
 WinLicen:00BD272B
 WinLicen:00BD272B loc_BD272B:                             ; CODE XREF: WinLicen:00BD271A
 WinLicen:00BD272B                 cmp     eax, [ebp+xfTerminateThread] ; 00BD1939h
 WinLicen:00BD2731                 jnz     loc_BD2742      ; 00BD193Dh
 WinLicen:00BD2737                 lea     eax, [ebp+xfi_emul_TerminateThread]
 WinLicen:00BD273D                 jmp     _save_to_IATx0
 WinLicen:00BD2742 ; ---------------------------------------------------------------------------
 WinLicen:00BD2742
 WinLicen:00BD2742 loc_BD2742:                             ; CODE XREF: WinLicen:00BD2731
 WinLicen:00BD2742                 cmp     eax, [ebp+xfExitThread] ; 00BD193Dh
 WinLicen:00BD2748                 jnz     loc_BD2759
 WinLicen:00BD274E                 lea     eax, [ebp+xfi_emul_ExitThread]
 WinLicen:00BD2754                 jmp     _save_to_IATx0
 
  ; ...
  
  ;Запись в IAT очередного элемента
 WinLicen:00BD294F _save_to_IAT:                           ; CODE XREF: WinLicen:00BD23D6
 WinLicen:00BD294F                 mov     esi, [ebp+MainImportHashArray]
 WinLicen:00BD2955                 lodsd
 WinLicen:00BD2956                 mov     dword ptr [esi-4], 0
 WinLicen:00BD295D                 rol     eax, 5
 WinLicen:00BD2960                 add     eax, 470EE6D2h
 WinLicen:00BD2965                 add     eax, [ebp+xvMainHandle]
 WinLicen:00BD296B                 mov     ecx, [ebp+xvImportAddress]
 WinLicen:00BD2971                 mov     [eax], ecx
 WinLicen:00BD2973                 lodsd
 WinLicen:00BD2974                 mov     dword ptr [esi-4], 0
 
  ;...
 
  ;сохранение переходов на импортируемые функции
 WinLicen:00BD29E1 loc_BD29E1:                             ; CODE XREF: WinLicen:00BD29D9
 WinLicen:00BD29E1                 push    eax
 WinLicen:00BD29E2                 cmp     dword ptr [ebp+4BF002Dh], 1
 WinLicen:00BD29E9                 jz      loc_BD2A0F
 WinLicen:00BD29EF                 mov     eax, 100h
 WinLicen:00BD29F4                 lea     ebx, [ebp+4CD12FDh]
 WinLicen:00BD29FA                 call    ebx
 WinLicen:00BD29FC                 cmp     eax, 50h ; ’P’
 WinLicen:00BD29FF                 jb      loc_BD2A0F
 WinLicen:00BD2A05                 mov     al, 90h ; ’Р’
 
  ;запись опкода инструкции перехода(jmp или call) на элемент импорта
  ;с предшествующей инструкцией nop(инструкции call [mem32] и jmp [mem32]
  ;занимают 6 байт, а непосредственные переходы - 5, поэтому протектор заменяя
  ;инструкцию может поставить nop впереди)
 WinLicen:00BD2A07                 stosb
 WinLicen:00BD2A08                 pop     eax
 WinLicen:00BD2A09                 stosb
 WinLicen:00BD2A0A                 jmp     loc_BD2A26
 WinLicen:00BD2A0F ; ---------------------------------------------------------------------------
 
  ;запись того же опкода, но уже без nop
 WinLicen:00BD2A0F loc_BD2A0F:                             ; CODE XREF: WinLicen:00BD29E9
 WinLicen:00BD2A0F                                         ; WinLicen:00BD29FF
 WinLicen:00BD2A0F                 pop     eax
 WinLicen:00BD2A10                 stosb
 WinLicen:00BD2A11                 cmp     byte ptr [edi-1], 0E9h ; ’щ’
 WinLicen:00BD2A15                 jnz     loc_BD2A26
 WinLicen:00BD2A1B                 lea     ebx, [ebp+4CD12CDh]
 WinLicen:00BD2A21                 call    ebx
 WinLicen:00BD2A23                 mov     [edi+4], al
 
  ;Запись смещения
 WinLicen:00BD2A26 loc_BD2A26:                             ; CODE XREF: WinLicen:00BD2A0A
 WinLicen:00BD2A26                                         ; WinLicen:00BD2A15
 WinLicen:00BD2A26                 mov     eax, [ebp+xvImportAddress]
 WinLicen:00BD2A2C                 sub     eax, edi
 WinLicen:00BD2A2E                 sub     eax, 4
 WinLicen:00BD2A31                 stosd


В общем то если заменить 4 перехода nop’ами, и заодно устранить инструкцию stosb по адресу 0BD2A07h, то переходники генерироваться не будут, а также не будут смещаться непосредственные переходы на импорт, в результате чего получится чистая IAT и останется только переориентировать переходы. Но.. "An Error has ocurred while loading imports" - не стоит забывать про проверку CRC кода, создающего импорт. Здесь просматривается целых 2 цикла, но фактически мешается только один:


 ;Загрузка в регистры адреса и размера проверяемого блока кода - 00BD19D9h, 1874h
 WinLicen:00BD2217                 lea     esi, [ebp+4D320F2h]
 WinLicen:00BD221D                 lea     edi, [ebp+4D33966h]
 WinLicen:00BD2223                 sub     edi, esi
 
 WinLicen:00BD2225                 mov     edx, edi
 WinLicen:00BD2227                 mov     edi, [ebp+4BF0811h]
 WinLicen:00BD222D                 or      ecx, 0FFFFFFFFh
 WinLicen:00BD2230
 
  ;Цикл подсчёта хэша
 WinLicen:00BD2230 loc_BD2230:                             ; CODE XREF: WinLicen:00BD2240
 WinLicen:00BD2230                 xor     eax, eax
 WinLicen:00BD2232                 mov     al, [esi]
 WinLicen:00BD2234                 xor     al, cl
 WinLicen:00BD2236                 inc     esi
 WinLicen:00BD2237                 mov     eax, [edi+eax*4]
 WinLicen:00BD223A                 shr     ecx, 8
 WinLicen:00BD223D                 xor     ecx, eax
 WinLicen:00BD223F                 dec     edx
 WinLicen:00BD2240                 jnz     loc_BD2230
 WinLicen:00BD2246                 mov     eax, ecx
 WinLicen:00BD2248                 not     eax


;сравнения, инструкия по адресу 00BD2263h должна выполнится
;чтоб ошибок при загрузке импорта не было

WinLicen:00BD224A                 cmp     [ebp+4BF132Dh], eax
 WinLicen:00BD2250                 jz      loc_BD226D
 WinLicen:00BD2256                 cmp     dword ptr [ebp+4BF2865h], 0
 WinLicen:00BD225D                 jnz     loc_BD226D
 WinLicen:00BD2263                 mov     dword ptr [ebp+4BF0255h], 1


И вот, заменяя переход jz по адресу 00BD2250h на jmp получаем чистую IAT, но пока вместо ссылок на неё импорт вызывается напрямую, например:


___:00401E90                 jmp     near ptr 7756846Dh
 ___:00401E90 sub_401E90      endp
 ___:00401E90
 ___:00401E95 ; ---------------------------------------------------------------------------
 ___:00401E95                 dec     ebx
 ___:00401E96                 mov     eax, eax
 ___:00401E98
 ___:00401E98 ; --------------- S U B R O U T I N E ---------------------------------------
 ___:00401E98
 ___:00401E98
 ___:00401E98 sub_401E98      proc near               ; CODE XREF: sub_533024+88
 ___:00401E98                                         ; sub_534544+71 ...
 ___:00401E98                 jmp     near ptr 77520519h
 ___:00401E98 sub_401E98      endp
 ___:00401E98
 ___:00401E98 ; ---------------------------------------------------------------------------
 ___:00401E9D                 db 0C5h, 8Bh, 0C0h
 ___:00401EA0
 ___:00401EA0 ; --------------- S U B R O U T I N E ---------------------------------------
 ___:00401EA0
 ___:00401EA0
 ___:00401EA0 sub_401EA0      proc near               ; CODE XREF: sub_533024+A5
 ___:00401EA0                 jmp     near ptr 774F0326h
 ___:00401EA0 sub_401EA0      endp
 ___:00401EA0
 ___:00401EA5 ; ---------------------------------------------------------------------------
 ___:00401EA5                 das
 ___:00401EA6                 mov     eax, eax
 ___:00401EA8
 ___:00401EA8 ; --------------- S U B R O U T I N E ---------------------------------------
 ___:00401EA8
 ___:00401EA8
 ___:00401EA8 sub_401EA8      proc near               ; CODE XREF: sub_44CA24+8F
 ___:00401EA8                 jmp     near ptr 774ED024h
 ___:00401EA8 sub_401EA8      endp


Это конечно никуда не годиться, здесь должны находится ссылки на IAT. Поэтому в DLL есть код для восстановления, выполняться он будет после окончания распаковки(там где идёт переход на OEP, т.е. перед MessageBox’ом "Unpack finished!". Чтобы найти IAT, надо простассировать код после метки _save_to_IAT, когда MainImportHashArray будет известен. Взять этот MainImportHashArray можно здесь:


WinLicen:00BD20E4                 mov     esi, [ebp+4BF00CDh]	;Здесь находится этот адрес
 WinLicen:00BD20EA                 mov     ebx, [ebp+4BF0781h]
 WinLicen:00BD20F0                 mov     [ebp+MainImportHashArray], esi


Трассировать код можно отладчиком-эмулятором или в уме, что сложнее. Если в момент записи(адрес 00BD2971h), в eax будет адрес, непохожий на IAT, то можно переориентировать EIP на адрес 00BD2955h и трассировать снова, пока не увидим адрес 007CF618h - начало IAT. Размер можно определить, просмотрев дамп после запуска. Кончается IAT вот здесь: 007D24A0h. 7D24A0-7CF618 = 2E88h. И наконец после правки констант начала и размера IAT получаем полностью рабочий импорт, иногда распаковка на этом и заканчивается если тот, кто упаковывал программу не посмотрел SDK, которое позволяет не так уж и плохо защитить пользовательский код.

Восстановление пользовательского кода(удаление макросов SDK)

В примере используются три документированных макроса(CODE_ENCRYPT, CODE_CLEAR и CODE_REPLACE, см. include-файл из SDK для подробностей) и один недокументированный с использованием переходника wsprintf. CODE_ENCRYPT, CODE_CLEAR и wsprintf всего лишь расшифровывают впереди(а может и не впереди) лежащий код, для восстановления надо всего лишь передать управление на начало конструкции и перехватить управление после расшифровки. С CODE_REPLACE немного сложнее, придётся немного закопаться в код протектора, и даже столкнуться и обойти простенькую виртуальную машину.

Начнём с wsprintf. Правда здесь совсем нет отличий от xprotector’а, но всё равно есть, что пояснить. Вот пример такой конструкции:


___:0055A960                 push    78263845h
 ___:0055A965                 push    5
 ___:0055A967                 push    0	;действие 0 - расшифровка
 ___:0055A969                 push    0E3B90E0Ch
 ___:0055A96E                 push    0F478990Ah
 ___:0055A973                 push    78263845h
 ___:0055A978                 call    emul_wsprintfA
 ___:0055A97D                 add     esp, 18h
 
  ;... (зашифрованный кусок кода)
 
 ___:0055AB04                 push    78263845h
 ___:0055AB09                 push    5
 ___:0055AB0B                 push    1 ;1 - шифровка
 ___:0055AB0D                 push    0E3B90E0Ch
 ___:0055AB12                 push    0F478990Ah
 ___:0055AB17                 push    78263845h
 ___:0055AB1C                 call    emul_wsprintfA
 ___:0055AB21                 add     esp, 18h
 
         А теперь смотрим как удалять эти вставки:
 
 ;	========== wsprintf recoverer ==========
   ifdef wsprintf_in_IAT
   ifdef wsprintf_emul_addr
 
  ;Сохраняем в регистре ebp правильный адрес wsprintf и помещаем туда адрес
  ;своего кода, которые подменит в стеке адрес возврата и передаст
  ;управление на функцию протектора xfi_emul_wsprintf
 mov	ebp, DWORD PTR ds:[wsprintf_in_IAT]
 mov	DWORD PTR ds:[wsprintf_in_IAT], offset @@wsprintf_redirect
 
  ;edi - начало секции кода, ebx - счётчик конструкций
 mov	edi, CodeSectionVA
 xor	ebx, ebx
 
  @@wsprintf_loop:
 
  ;Ищем(по сигнатуре) начальную(шифрующую)конструкцию,
  ;если таких больше нет - выход из цикла
 invoke	find_wsprintf_decrypt, edi
 test	eax, eax
 jz	@@wsprintf_end
 
  ;Вывод сообщения в лог о том, что макрос wsprintf найден
 mov	edi, eax
 invoke	wsprintfA, offset buffer1, offset fmt009, edi
 invoke	LogWrite, offset buffer1
 
  ;Передаём управление макросу - пусть расшифровывает всё что надо
 jmp	edi	;decryption
 
  @@wsprintf_after_decryption:
 
  ;А сюда мы попадаем, потому что код около метки @@wsprintf_redirect
  ;записывает в стек этот адрес
 add	esp, 18h
 inc	ebx
 
  ;Заменяем макрос nop’ами
 mov	ecx, wsprintf_block_size
 mov	al, 90h
 rep stosb
 jmp	@@wsprintf_loop
 
 
  @@wsprintf_end:
  
  ;Вывод в лог сообщения о количестве найденных макросов wsprintf
 invoke	wsprintf, offset buffer1, offset fmt011, ebx
 invoke	LogWrite, offset buffer1
 
  ;Записываем на место адрес истинной функции wsprintf,
  ;переходник протектора больше не нужен
 mov	DWORD PTR ds:[wsprintf_in_IAT], ebp
 mov	edi, CodeSectionVA
 
  ;Далее идёт цикл по удалению завершающих конструкций макроса
  @@wsprintf_clearloop:
 invoke	find_wsprintf_encrypt, edi
 test	eax, eax
 jz	@@wsprintf_clearend
 
 mov	edi, eax
 invoke	wsprintf, offset buffer1, offset fmt010, edi
 invoke	LogWrite, offset buffer1
 
 mov	ecx, wsprintf_block_size
 mov	al, 90h
 rep stosb
 jmp	@@wsprintf_clearloop
 
  ;Всё, макросов wsprintf больше не осталось
  @@wsprintf_clearend:
   endif
   endif


wsprintf_in_IAT - можно найти после восстановления импорта imprec’ом, а wsprintf_emul_addr проскакивает в создании импорта, можно брать прямо оттуда. Теперь рассмотрим ещё два простеньких макроса - CODE_ENCRYPT и CODE_CLEAR:



 ;Начало у макросов CODE_ENCRYPT и CODE_REPLACE одинаковое:
 ___:0055B772                 call    near ptr unk_BD32FA
 ___:0055B772 ; ---------------------------------------------------------------------------
 ___:0055B777                 dd 0Ah ;Индекс для определения thread ID
 ___:0055B77B                 dd 0 ;decrypt
 ___:0055B77F                 dd 21h ;code size
 ___:0055B783                 db 20h ;какой-то байт(он не нужен)
 ___:0055B784 ; ---------------------------------------------------------------------------
 
  ;Скрытый пользовательский код
 ___:0055B784                 call    loc_5C079D
 ___:0055B789                 sub     edi, dword_7B8A2C
 ___:0055B78F                 mov     [ebp-4], edi
 ___:0055B792                 push    dword_77EF82
 ___:0055B798                 call    loc_55BDD2
 ___:0055B79D                 call    near ptr unk_BA772B
 ___:0055B79D ; ---------------------------------------------------------------------------
 ___:0055B7A2                 dd 0Ah ; -//-
 ___:0055B7A6                 dd 1 ;encrypt
 ___:0055B7AA                 dd 21h ; -//-
 ___:0055B7AE                 db 20h ; -//-
 
  ;В WinLicense demo нет макросов CODE_CLEAR, поэтому приведу такой макрос из
  ;примера к SDK. sub_5ED4BF здесь и unk_BD32FA выше - одна и та же процедура
 ___:0040105D                 call    near ptr sub_5ED4BF
 ___:0040105D ; ---------------------------------------------------------------------------
 ___:00401062                 dd 8 ;thread ID index
 ___:00401066                 dd 0 ;action decrypt
 ___:0040106A                 dd 2Ah ;size
 ___:0040106E                 db 20h ;?
 ___:0040106F ; ---------------------------------------------------------------------------
 
  ;код пользователя
 ___:0040106F                 push    0
 ___:00401071                 push    offset aThemidaSdkEx_4 ; "Themida SDK example"
 ___:00401076                 push    offset aWeAreShowing_0 ; "We are showing this message inside a CL"...
 ___:0040107B                 push    0
 ___:0040107D                 call    sub_401106
 
  ;Затираем этот код пользователя, чтоб его не было видно в дизассемблере
  ;после снятия дампа. Очевидно, что такой макрос подходит только для
  ;кода, который должен выполнится всего один раз
 ___:00401082                 pusha
 ___:00401083                 call    $+5
 ___:00401088                 pop     edi
 ___:00401089                 sub     edi, 2Bh
 ___:0040108F                 mov     ecx, 2Bh
 ___:00401094                 xor     eax, eax
 ___:00401096                 rep stosb
 ___:00401098                 popa


При вызове процедуры расшифровки(или шифровки) поток уходит в бесконечный цикл, предварительно разморозив другой поток, который будет расшифровывать код, причём этот поток просто вынужден будет менять контекст(а конкретно регистр EIP в контексте) после обработки кода. Способ такой, передаём управление на начало макроса, не забыв перехватить native API функцию ZwSetContextThread. При вызове переориентируем EIP на продолжение цикла восстановление кода, после чего остаётся только заменить всё, что относиться к макросам nop’ами и ещё больше приблизится к моменту полной распаковки.


;	========== CODE_ENCRYPT and CODE_CLEAR destroyer ==========
   ifdef code_encrypt_proc
 
  ;Инициализация цикла
 mov	edi, CodeSectionVA
 
  @@code_encrypt_loop:
 
  ;Поиск начала макроса, ищется по инструкции call code_encrypt_proc, это аналог
  ;sub_5ED4BF из примера. Чтобы найти этот адрес, можно поискать например в hiew
  ;вызовы(call’ы) процедур из секции кода программы в секцию протектора
 invoke	find_macro1_start, edi, code_encrypt_proc
 test	eax, eax
 jz	@@code_encrypt_end
 
  ;Вывод в лог сообшения о найденном макросе
 mov	edi, eax
 invoke	wsprintf, offset buffer1, offset fmt012, edi
 invoke	LogWrite, offset buffer1
 
  ;code_encrypt_indicator - переменная, которую будет использовать перехваченная
  ;функция ZwSetContextThread для того, чтобы определить, надо ли менять EIP или нет
 mov	code_encrypt_indicator, 1
 mov	gv0, edi
 mov	gv1, ebx
 
  ;расшифровываем код
 jmp	edi
 
  ;Сюда попадаем после расшифровки.
  ;Обратите внимание на метку, она объявлена с двумя двоеточиями, это значит что к
  ;ней можно обращаться из другой процедуры - особенность MASM’а
  _code_encrypt_done::
 mov	ebx, gv1
 mov	edi, gv0
 
  ;Сброс индикатора для ZwSetContextThread
 mov	code_encrypt_indicator, 0
 
  ;Читаем длину зашифрованного кода
 mov	edx, [edi + 0Dh]
 
  ;Уничтожение начала макроса
 mov	ecx, code_encrypt_size
 mov	al, 90h
 rep stosb
 
  ;CODE_ENCRYPT?
  ;(опкод первой инструкции окончания макроса CODE_ENCRYPT - E8, т.е. это call, см. пример)
 lea	esi, [edi + edx]
 sub	esi, 8
 cmp	BYTE PTR [esi], 0E8h
 jnz	@@code_encrypt_no_CE
 
  ;Вывод сообщения об уничтожении CODE_ENCRYPT,
  ;а затем и само уничтожение
 invoke	wsprintf, offset buffer1, offset fmt013, edi, edx
 invoke	LogWrite, offset buffer1
 
 mov	edi, esi
 mov	ecx, code_encrypt_size
 mov	al, 90h
 rep stosb
 jmp	@@code_encrypt_loop
 
 
  @@code_encrypt_no_CE:
 
  ;Всё аналогично для удаления окончания CODE_CLEAR
 lea	esi, [edi + edx]
 sub	esi, code_clear_end_size
 cmp	BYTE PTR [esi], 60h ;pusha
 jnz	@@code_encrypt_no_CC
 
 invoke	wsprintf, offset buffer1, offset fmt014, edi, edx
 invoke	LogWrite, offset buffer1
 
 mov	edi, esi
 mov	ecx, code_clear_end_size
 mov	al, 90h
 rep stosb
 jmp	@@code_encrypt_loop
 
  @@code_encrypt_no_CC:
 jmp	@@code_encrypt_loop
 
  @@code_encrypt_end:
   endif


Результаты работы DLL можно посмотреть в IDA, перебирая адреса из лога, код должен восстановится правильно. Теперь остался только один вид макросов - CODE_REPLACE, но этот макрос представляет собой одну из самых мощных(если не самую мощную) защиту кода, макросу этому посвящается даже свой раздел :)

Восстановление кода после макросов CODE_REPLACE

Если вспомнить xprotector, то аналогичные макросы там выглядели так:


jmp     invalid_opcode
 db      "xpro" ;Это просто сигнатура
 dd      0 ;0 - начало, 1 - окончание
 dd      0
 db      "xpro"
  invalid_opcode:


Т.е. они выглядели почти также как и в sdk. Делаем предположение, что и в themid’е они также практически не меняются при упаковке. Смотрим SDK:


CODEREPLACE_START MACRO 
 
     jmp     @F
 
  ;Сигнатура уже здесь другая, WL - сокращение от WinLicense как я понимаю
     db      ’WL  ’ ;
     dd      ID_CODEREPLACE_START
     dd      0
     db      ’WL  ’
 
     @@:
 
 ENDM    
 
 CODEREPLACE_END MACRO 
 
     jmp     @F
 
     db      ’WL  ’
     dd      ID_CODEREPLACE_END
     dd      0
     db      ’WL  ’
 
     @@:
 
 ENDM


Чтобы найти макрос CODE_REPLACE, достаточно поискать в шестнадцатиричном редакторе "WL ". Первый макрос находится по адресу 0558D23h. Однако испытания "древнего" способа с перехватам NtContinue заканчиваются неудачей. Да не просто неудачей а вообще непонятно чем, в лог не попадает исключение о неправильном опкоде, а это значит, что управление не попадает к векторному обработчику, чего просто не может быть, однако это так. Вспоминая, как Windows NT обрабатывает исключения, начинаем проверять функции, которые задействованы при этой обработке. Первая же функция этого ряда - KiUserExceptionDispatcher радует нас очень интересной инструкцией - jmp near ptr 0BCCA52h, причём адрес 0BCCA52h принадлежит конечно же протектору. Ничего не остаётся, кроме того, как туда заглянуть:


WinLicen:00BCCA52 sub_BCCA52      proc near
 WinLicen:00BCCA52
 WinLicen:00BCCA52 pExceptionRecord= dword ptr  4
 WinLicen:00BCCA52 arg_4           = dword ptr  8
 
  ;Это недокументированный способ проверить версию ОС, WinNT перед нами или Win9x
 WinLicen:00BCCA52                 mov     cx, ds
 WinLicen:00BCCA55                 test    cl, 4
 WinLicen:00BCCA58                 jz      short @@WinNT
 WinLicen:00BCCA5A                 mov     ebx, [esp+arg_4]
 WinLicen:00BCCA5E                 mov     ecx, [esp+pExceptionRecord]
 WinLicen:00BCCA62                 jmp     short loc_BCCA6F
 WinLicen:00BCCA62 ; ---------------------------------------------------------------------------
 WinLicen:00BCCA64                 dd 0
 WinLicen:00BCCA68 ; ---------------------------------------------------------------------------
 WinLicen:00BCCA68
 WinLicen:00BCCA68 @@WinNT:                                ; CODE XREF: sub_BCCA52+6j
 WinLicen:00BCCA68                 mov     ecx, [esp+pExceptionRecord]
 WinLicen:00BCCA6C                 mov     ebx, [esp+0]
 WinLicen:00BCCA6F
 WinLicen:00BCCA6F loc_BCCA6F:                             ; CODE XREF: sub_BCCA52+10j
 WinLicen:00BCCA6F                 mov     eax, [ebx]
 WinLicen:00BCCA71                 call    $+5
 WinLicen:00BCCA76                 pop     ebp
 WinLicen:00BCCA77                 sub     ebp, 4D2D18Fh
 WinLicen:00BCCA7D                 pusha
 
  ;Проверка, произошло ли исключение внутри программы или скажем где-то в DLL
 WinLicen:00BCCA7E                 mov     eax, [ecx+CONTEXT.Eip]
 WinLicen:00BCCA84                 mov     edx, [ebp+xvImageSize]
 WinLicen:00BCCA8A                 add     edx, [ebp+xvHandle]
 WinLicen:00BCCA90                 cmp     eax, edx
 WinLicen:00BCCA92                 ja      short loc_BCCA9C
 WinLicen:00BCCA94                 cmp     eax, [ebp+xvHandle]
 WinLicen:00BCCA9A                 jnb     short loc_BCCAA4
 WinLicen:00BCCA9C
 WinLicen:00BCCA9C loc_BCCA9C:                             ; CODE XREF: sub_BCCA52+40j
 WinLicen:00BCCA9C                 mov     eax, [ebp+4BF1AE9h]
 WinLicen:00BCCAA2                 jmp     short loc_BCCAFC
 WinLicen:00BCCAA4 ; ---------------------------------------------------------------------------
 
  ;Ожидание, пока IDT не окажется свободна(её могут использовать
  ;другие потоки для своих ring0 дешифровщиков)
 WinLicen:00BCCAA4 loc_BCCAA4:                             ; CODE XREF: sub_BCCA52+48j
 WinLicen:00BCCAA4                 mov     edx, 2
 WinLicen:00BCCAA9                 mov     eax, [ebp+4BF060Dh]
 WinLicen:00BCCAAF
 WinLicen:00BCCAAF @@waitforidt_loop:                      ; CODE XREF: sub_BCCA52+6Dj
 WinLicen:00BCCAAF                 xchg    dl, [eax]
 WinLicen:00BCCAB1                 or      dl, dl
 WinLicen:00BCCAB3                 jz      short loc_BCCAC1
 WinLicen:00BCCAB5                 pusha
 WinLicen:00BCCAB6                 push    1
 WinLicen:00BCCAB8                 call    dword ptr [ebp+Sleep]
 WinLicen:00BCCABE                 popa
 WinLicen:00BCCABF                 jmp     short @@waitforidt_loop
 WinLicen:00BCCAC1 ; ---------------------------------------------------------------------------
 WinLicen:00BCCAC1
 WinLicen:00BCCAC1 loc_BCCAC1:                             ; CODE XREF: sub_BCCA52+61j
 WinLicen:00BCCAC1                 mov     ebx, [ecx+CONTEXT.Eip]
 
  ;Протектор держит таблицу с адресами исключений
 WinLicen:00BCCAC7                 call    find_exception_address
 WinLicen:00BCCACC                 or      eax, eax
 
  ;Переход, если адреса нету в таблице
 WinLicen:00BCCACE                 jz      short loc_BCCADB
 WinLicen:00BCCAD0                 mov     eax, [ebp+4BF060Dh]
 WinLicen:00BCCAD6                 mov     byte ptr [eax], 0
 
  ;И переход на реальную функцию KiUserExceptionDispatcher если исключение есть в таблице
 WinLicen:00BCCAD9                 jmp     short loc_BCCAFC
 WinLicen:00BCCADB ; ---------------------------------------------------------------------------
 WinLicen:00BCCADB
 WinLicen:00BCCADB loc_BCCADB:                             ; CODE XREF: sub_BCCA52+7Cj
 WinLicen:00BCCADB                 mov     eax, [ecx+CONTEXT.Eip]
 WinLicen:00BCCAE1                 mov     [ebp+xvOldEIP], eax
 
  ;Информация об исключении теряется, управление передаётся внутрь протектора, контекст
  ;восстанавливается функцией NtContinue.
 WinLicen:00BCCAE7                 lea     eax, [ebp+xfExceptionHandlerProc]
 WinLicen:00BCCAED                 mov     [ecx+CONTEXT.Eip], eax
 WinLicen:00BCCAF3                 push    0
 WinLicen:00BCCAF5                 push    ecx
 WinLicen:00BCCAF6                 call    dword ptr [ebp+NtContinue]
 WinLicen:00BCCAFC
 
  ;Переход на функцию ОС, обрабатывающую исключение
 WinLicen:00BCCAFC loc_BCCAFC:                             ; CODE XREF: sub_BCCA52+50j
 WinLicen:00BCCAFC                                         ; sub_BCCA52+87j
 WinLicen:00BCCAFC                 mov     ax, ds
 WinLicen:00BCCAFF                 test    al, 4
 WinLicen:00BCCB01                 jz      short loc_BCCB13
 WinLicen:00BCCB03                 mov     eax, [ebp+4BF23E1h]
 WinLicen:00BCCB09                 add     eax, 8
 WinLicen:00BCCB0C                 add     eax, 42h ; ’B’
 WinLicen:00BCCB11                 jmp     short loc_BCCB19
 WinLicen:00BCCB13 ; ---------------------------------------------------------------------------
 WinLicen:00BCCB13
 WinLicen:00BCCB13 loc_BCCB13:                             ; CODE XREF: sub_BCCA52+AFj
 WinLicen:00BCCB13                 lea     eax, [ebp+4D218E3h]
 WinLicen:00BCCB19
 WinLicen:00BCCB19 loc_BCCB19:                             ; CODE XREF: sub_BCCA52+BFj
 WinLicen:00BCCB19                 lea     ebx, [ebp+4D2D23Ch]
 WinLicen:00BCCB1F                 mov     [ebx+1], eax
 WinLicen:00BCCB22                 popa
 WinLicen:00BCCB23                 push    12345678h
 WinLicen:00BCCB28                 retn
 WinLicen:00BCCB28 sub_BCCA52      endp ; sp = -0Ch


Исходя из вышестоящего кода, обработка исключения передаётся операционной системе, если адреса этого исключения нет в какой-то внутренней таблице процессора, при этом информация об исключении теряется. Могут быть два случая - произошло обычное исключение, или управление попало на макрос CODE_REPLACE. Очевидно, что в первом случае управление должно вернуться на адрес, вызвавший исключение, а потом опять на 00BCCA52h, и уже потом на KiUserExceptionDispatcher, при этом адрес должен был быть занесён в таблицу. Во втором случае просто выполнится код, который протектор вытащил изнутри макроса, и управление после этого попадёт за пределы макроса. Посмотрим на код xfExceptionHandlerProc, хотя толку от этого мало будет :)


 ;Код этот так сильно разбавлен мусором, что без плагина невозможно вообще понять
  ;что к чему. С плагином идёт описание, как удалять мусорные конструкции,
  ;здесь оно точно уж пригодится
 WinLicen:00BBBE5F                 pushf
 WinLicen:00BBBE60                 pusha
 WinLicen:00BBBE61                 call    $+5
 WinLicen:00BBBE66                 pop     ebp
 WinLicen:00BBBE67                 sub     ebp, 4D1C57Fh
 WinLicen:00BBC05F                 lea     eax, [ebp+4D1DC64h]
 WinLicen:00BBC2CC                 push    eax
 WinLicen:00BBC2D0                 mov     [esp], eax
 WinLicen:00BBC2F0                 cmp     dword ptr [ebp+4BF2961h], 0
 WinLicen:00BBC2F7                 jz      loc_BBC5D4
 WinLicen:00BBC5A1                 push    0FFFFFFFFh
 WinLicen:00BBC5C8                 call    dword ptr [ebp+4BF2851h]
 WinLicen:00BBC5CE                 mov     edi, [ebp+4BF013Dh]
 WinLicen:00BBC5D4
 WinLicen:00BBC5D4 loc_BBC5D4:                             ; CODE XREF: WinLicen:00BBC2F7j
  
  ;...
  ;Весь смысл внутри этого call’а, можно посмотреть и туда
 WinLicen:00BBCD50                 call    eax
 WinLicen:00BBCD52                 mov     ebx, [ebp+4BF1C7Dh]
 WinLicen:00BBCD58                 mov     ecx, [ebp+4BF060Dh]
 WinLicen:00BBCF87                 mov     byte ptr [ecx], 0
 WinLicen:00BBCF9C                 lea     ecx, [ebp+4D1DC64h]
 WinLicen:00BBD21F                 mov     [ecx+1], eax
 WinLicen:00BBD2EB                 add     esp, 4
 WinLicen:00BBD549                 popa
 WinLicen:00BBD54A                 popf
 WinLicen:00BBD54B                 push    12345678h
 WinLicen:00BBD550                 retn
 
  ...
 
  ;Я уже упоминал о виртуальной машине, именно здесь она и находится
  ;Исследовать у меня желание не возникло, но одно её присутствие уже есть
  ;нехороший знак.
 WinLicen:00B1112E sub_B1112E      proc near
 WinLicen:00B1112E
 WinLicen:00B1112E arg_0           = dword ptr  4
 WinLicen:00B1112E arg_4           = dword ptr  8
 WinLicen:00B1112E
 WinLicen:00B1112E                 pusha
 WinLicen:00B11148                 mov     esi, [esp+20h+arg_0]
 WinLicen:00B1114C                 xor     [ebp+4BF168Dh], edi
 WinLicen:00B11152                 mov     ebx, [esp+20h+arg_4]
 WinLicen:00B11158                 mov     edi, [ebp+4C697FCh]
 WinLicen:00B1115E                 mov     [ebp+4BF070Dh], ecx
 WinLicen:00B11164                 mov     edx, [ebp+4C6980Ch]
 WinLicen:00B11176                 mov     dword ptr [ebp+4C6981Ch], 0
 WinLicen:00B1118A                 mov     dword ptr [ebp+4C69820h], 0
 WinLicen:00B111C3                 mov     eax, 1Fh
 WinLicen:00B111DB                 shl     eax, 2
 WinLicen:00B1121A                 mov     ecx, [ebp+4C69828h]
 WinLicen:00B11220                 mov     [ebp+4BF0A25h], eax
 WinLicen:00B11226                 mov     [eax+edx], ecx
 WinLicen:00B1122A                 mov     [ebp+4BF1DE9h], edi
 WinLicen:00B11230                 lea     eax, [eax+edx]
 WinLicen:00B11255                 mov     [ebp+4C69824h], eax
 WinLicen:00B11267                 jmp     loc_B1133C
 WinLicen:00B1126C ; ---------------------------------------------------------------------------
 WinLicen:00B1126C                 mov     [ebp+4BF29E9h], eax
 WinLicen:00B11272
 WinLicen:00B11272 loc_B11272:                             ; CODE XREF: sub_B1112E+210j
 WinLicen:00B11272                 movzx   eax, byte ptr [esi]
 WinLicen:00B1128E                 shl     eax, 2
 WinLicen:00B112B0                 call    dword ptr [eax+edi]
 WinLicen:00B112B3                 mov     [ebp+4BF1D81h], edi
 WinLicen:00B112B9                 inc     dword ptr [ebp+4C69820h]
 WinLicen:00B112CB                 mov     eax, [ebp+4C69820h]
 WinLicen:00B112F9                 mov     ecx, 0Ah
 WinLicen:00B11316                 mul     cl
 WinLicen:00B11329                 mov     esi, [esp+20h+arg_0]
 WinLicen:00B11334                 add     esi, eax
 WinLicen:00B11336                 sub     [ebp+4BF0571h], eax
 WinLicen:00B1133C
 WinLicen:00B1133C loc_B1133C:                             ; CODE XREF: sub_B1112E+139j
 WinLicen:00B1133C                 cmp     esi, ebx
 WinLicen:00B1133E                 jb      loc_B11272
 WinLicen:00B11345                 popa
 WinLicen:00B11346                 sub     [ebp+4BF1BBDh], ebx
 WinLicen:00B1134C                 retn    8
 WinLicen:00B1134C sub_B1112E      endp


Практически можно сдаваться, ведь легкое восстановление кода из CODE_REPLACE не представляется возможным, но..


WinLicen:00BCCADB                 mov     eax, [ecx+CONTEXT.Eip]
 WinLicen:00BCCAE1                 mov     [ebp+xvOldEIP], eax ;где xvOldEIP = 4BF14B9h


Если код проверки на принадлежность адреса исключения к CODE_REPLACE лежит в открытом виде, то HEX-редакторе можно будет найти DWORD 4BF14B9h. И действительно, найти его можно и не раз. Первое вхождение 7CCAE3 выводит нас на перехваченную функцию KiUserExceptionDispatcher, но уже следующее выводит на совсем другой код. Он также очень сильно разбавлен мусором, поэтому придётся несколько минут посидеть, прежде чем раскопать окрестности вхождения и выделить нужный код, который обслуживает макрос(т.е. сверяет адрес из xvOldEIP с таблицей адресов, где применены макросы). Смотрим на этот код, на этот раз толку будет хоть отбавляй:


 ;Вот где она, таблица-то..
 WinLicen:00BE0848                 lea     edi, [ebp+xaCodeReplaceTable]
 WinLicen:00BE0854                 add     edi, 5
 WinLicen:00BE0857                 mov     edx, ebx
 
  ;Записываем в переменную адрес таблицы
 WinLicen:00BE0931                 mov     [ebp+xvCodeReplaceTable], edi
 WinLicen:00BE095E                 lea     ecx, [ebp+xaCodeReplaceTableEnd]
 WinLicen:00BE0988                 sub     ecx, edi
 WinLicen:00BE0BE1                 shr     ecx, 2
 WinLicen:00BE0E45                 inc     ecx
 WinLicen:00BE0E46                 sub     edx, 4A202D45h
 WinLicen:00BE0F40                 mov     [ebp+4D3D93Bh], ecx
 WinLicen:00BE11AB                 mov     edx, [ebp+4BF039Dh]
 
  ;Таблица лежит в зашифрованном виде, нужны два ключа, чтоб запустить
  ;"мощный" криптоалгоритм для её расшифровки
 WinLicen:00BE11B1                 mov     eax, [ebp+xvCodeReplaceKey1]
 WinLicen:00BE11D6                 mov     ebx, [ebp+xvCodeReplaceKey2]
 WinLicen:00BE1307                 call    _codereplace_decrypt
 
  ;Получаем RVA адреса исключения и ищем его в таблице
 WinLicen:00BE14F5                 mov     ebx, [ebp+xvOldEIP]
 WinLicen:00BE177F                 sub     ebx, [ebp+xvHandle]
 WinLicen:00BE1A01                 mov     esi, [ebp+xvCodeReplaceTable]
 WinLicen:00BE1A07                 mov     edx, 4BBF4CCEh
 WinLicen:00BE1A0C                 lea     edi, [ebp+xaCodeReplaceTableEnd]
 WinLicen:00BE1A12                 push    ebx
 WinLicen:00BE1A13                 mov     dx, 1246h
 WinLicen:00BE1A17                 pop     edx
 WinLicen:00BE1A18                 call    _codereplace_find
 WinLicen:00BE1C37                 or      eax, eax
 
  ;Если исключение никак не связано с CODE_REPLACE, то вот переход выполнится
 WinLicen:00BE1C39                 jz      _no_codereplace
 
  ;Расчёт адреса для перехода на краденный из макроса код,
  ;Видно, что в таблице третий элемент это RVA - CodeReplaceConst1
 WinLicen:00BE1C5B                 mov     eax, [esi+8]
 WinLicen:00BE1C7E                 sub     ebx, [esi]
 WinLicen:00BE1D32                 add     eax, ebx
 WinLicen:00BE1D3C                 add     eax, [ebp+xvCodeReplaceConst1]
 WinLicen:00BE1DEF                 add     eax, [ebp+xvHandle]
 
  ;Запись нового адреса для передачи управления
 WinLicen:00BE207F                 mov     [ebp+xvOldEIP], eax
 WinLicen:00BE208E                 jmp     loc_BE258E
 
  ;Если это не макрос, то xvOldEIP не меняется, управление попадёт туда для
  ;повторной обработки исключения протектором
 WinLicen:00BE2096 _no_codereplace:                        ; CODE XREF: WinLicen:00BE1C39j
 WinLicen:00BE2096                 mov     ebx, [ebp+xvOldEIP]
 WinLicen:00BE22A6                 call    loc_BE423D


После такого описание думаю понятно что делать с краденным кодом, достаточно найти таблицу, расщифровать её, и просто скопировать код на место, не забыв подправить относительные смещения для длинных переходов. Другие способы, основанные на поиске макросов по сигнатуре "WL " недееспособны, т.к. при упаковке есть такая опция, как автоматическое создание CODE_REPLACE макросов, но вот в таблице они будут все, вне зависимости от того, автомитически ли они были созданы или нет. Теперь пара слов о восстановлении:


 ;CODE_REPLACE constants
 code_replace_table	equ		00BDD184h ;адрес таблицы
 code_replace_n		equ		0Ch ;число макросов
 code_replace_key1	equ		00BDD226h ;первый ключ ..
 code_replace_key2	equ		00BDD22Ah ;.. и второй ключ
 code_replace_const1	equ		0FB8E7h ;константа для суммирования с 3-им элементом таблицы
 code_replace_decrypt	equ		00AC9B3Dh ;функция расшифровки таблицы
 code_replace_macrosize	equ		12h ;размер начала макроса из SDK
 
  ;...
 
 ;	========== CODE_REPLACE destroyer ==========
   ifdef code_replace_table
   ifdef code_replace_n
   ifdef code_replace_key1
   ifdef code_replace_key2
   ifdef code_replace_const1
   ifdef code_replace_decrypt
 
  ;Расшифровываем таблицу
 mov	edi, code_replace_table
 mov	eax, code_replace_key1
 mov	eax, [eax]
 mov	ebx, code_replace_key2
 mov	ebx, [ebx]
 mov	ecx, code_replace_n
 imul	ecx, 3
 
 mov	ebp, code_replace_decrypt
 call	ebp
 
  ;Начинается цикл копирования и правки кода
 mov	ebp, code_replace_table
 mov	ebx, code_replace_n
 
  @@code_replace_loop:
 
  ;Первый элемент в таблице - RVA макроса в коде, второй - RVA конца
  ;макроса, третий - RVA адреса, куда протектор спрятал украденный код
 mov	esi, [ebp+8]
 add	esi, code_replace_const1
 add	esi, MHandle
 
 mov	edi, [ebp]
 add	edi, MHandle
 
 mov	ecx, [ebp+4]
 add	ecx, MHandle
 sub	ecx, edi
 
 push	ecx
 push	esi
 push	edi
 
  ;сначала копирование
 rep movsb
 
 pop	edi
 pop	esi
 pop	ecx
 
  ;теперь правда смещений
 invoke	code_replace_fix, edi, esi, ecx
 
  ;А потом удаление всяких сигнатур "WL  ", в общем
  ;удаление всего лишнего
 cmp	DWORD PTR [edi - 4], 20204C57h
 jnz	@@code_replace_iter
 
 push	edi
 push	ecx
 push	ecx
 
 mov	al, 90h
 mov	ecx, code_replace_macrosize
 sub	edi, ecx
 rep stosb
 
 pop	ecx
 add	edi, ecx
 mov	ecx, code_replace_macrosize
 rep stosb
 
 pop	ecx
 pop	edi
 
  ;Вывод в лог и итерация цикла
  @@code_replace_iter:
 invoke	wsprintf, offset buffer1, offset fmt015, edi, ecx
 invoke	LogWrite, offset buffer1
 
 add	ebp, 0Ch
 dec	ebx
 jnz	@@code_replace_loop
   endif
   endif
   endif
   endif
   endif
   endif


Всё, все ступени защиты преодолены, здесь можно закончить распаковку любой программы, за исключением творений Oreans. Программы, упакованные распакованной WinLicense не хотят работать, выдают исключения. Защита эта встроена в логику работы программы, это сильно усложняет её поиск, но не исключает совсем.

Тест на упакованность(доп. защита от авторов протектора)

Исследование приведу в кратком изложении. Вновь запакованный calc.exe не работает, вылетает с исключением обращения к памяти. Если запускать его через лоадер, то будет видно, в логе будут видны вызовы VirtualAlloc. Перехватывая и снимая дамп сразу после последнего вызова VirtualAlloc перед исключением, быстро находим инструкцию, приводящую к краху:


WinLicen:0121F163                 push    4
 WinLicen:0121F165                 push    1000h
 WinLicen:0121F16A                 push    dword ptr [ebp+7490999h]
 WinLicen:0121F170                 push    0
 WinLicen:0121F172                 call    eax
 WinLicen:0121F174                 test    eax, eax
 WinLicen:0121F176                 jnz     loc_121F189
 WinLicen:0121F17C                 mov     eax, 0
 WinLicen:0121F181                 lea     ecx, [ebp+7498F7Eh]
 WinLicen:0121F187                 jmp     ecx
 WinLicen:0121F189 ; ---------------------------------------------------------------------------
 WinLicen:0121F189
 WinLicen:0121F189 loc_121F189:                            ; CODE XREF: WinLicen:0121F176j
 WinLicen:0121F189                 mov     ecx, eax
 WinLicen:0121F18B                 mov     eax, ebx
 WinLicen:0121F18D                 add     eax, [eax+3Ch]
 WinLicen:0121F190                 add     eax, 0F8h
 WinLicen:0121F195                 mov     edx, [eax+0Ch]
 WinLicen:0121F198                 add     edx, ebx
 WinLicen:0121F19A                 cmp     dword ptr [ebp+74910A1h], 0
 WinLicen:0121F1A1                 jz      loc_121F1B5
 WinLicen:0121F1A7                 mov     ebx, [ebp+74910A1h]
 WinLicen:0121F1AD                 mov     eax, [ebp+7491D61h]
 WinLicen:0121F1B3                 mov     [ebx], eax
 WinLicen:0121F1B5
 WinLicen:0121F1B5 loc_121F1B5:                            ; CODE XREF: WinLicen:0121F1A1j
 WinLicen:0121F1B5                 push    ecx
 WinLicen:0121F1B6                 push    edx
 
  ;Смещение относительно EBP не изменилось, но если вспомнить xprotector,
  ;то там игнорировались все смещения 
 WinLicen:0121F1B7                 lea     eax, [ebp+5A6E80h]
 WinLicen:0121F1BD                 call    eax

Пользуясь тем, что код это лежит в открытом виде, находим его в дампе по адресу 005A7171. По перекрестным ссылкам можно найти код, который учавствует в упаковке и как раз обрабатывает такие куски:



 ;Видим кучки nop’ов оставшиеся после удаления макросов wsprintf
 ___:005A7227                 nop
 ___:005A7228                 nop
 ___:005A7229                 nop
 ___:005A722A                 nop
 ___:005A722B                 nop
 ___:005A722C                 nop
 ___:005A722D                 nop
 ___:005A722E                 cmp     [ebp+arg_4], 0
 ___:005A7232                 jnz     short loc_5A7251
 ___:005A7234                 mov     eax, offset loc_5A70BF
 ___:005A7239                 mov     ebx, offset sub_5A7205
 ___:005A723E                 push    ebx
 ___:005A723F                 push    eax
 ___:005A7240                 call    sub_5BFFE7
 ___:005A7245                 mov     dword_78D32F, 0
 ___:005A724F                 jmp     short loc_5A72AD
 ___:005A7251 ; ---------------------------------------------------------------------------
 ___:005A7251
 ___:005A7251 loc_5A7251:                             ; CODE XREF: sub_5A7205+2Dj
 ___:005A7251                 push    0
 ___:005A7253                 call    sub_5C0881
 ___:005A7258                 push    0
 ___:005A725A                 call    sub_5C1F49
 ___:005A725F                 push    0
 ___:005A7261                 call    sub_5C1F28
 ___:005A7266                 push    0
 ___:005A7268                 push    0
 ___:005A726A                 push    0
 ___:005A726C                 push    64h
 ___:005A726E                 call    sub_5C091B
 ___:005A7273                 call    sub_5C2538
 ___:005A7278                 call    sub_5C2549
 ___:005A727D                 call    sub_5C2551
 ___:005A7282                 call    sub_5C2559
 ___:005A7287                 call    sub_5C2561
 ___:005A728C                 call    sub_5C2579
 ___:005A7291                 call    sub_5C2571
 ___:005A7296                 mov     eax, offset loc_5A70BF
 ___:005A729B                 mov     ebx, offset sub_5A7205
 ___:005A72A0                 push    [ebp+arg_0]
 ___:005A72A3                 push    ebx
 ___:005A72A4                 push    eax
 ___:005A72A5                 call    sub_5C01D7


Как и в xprotector’е здесь тоже поддерживается массив для обработки перемещаемых элементов(как relocations в DLL). Функция 5C01D7h как раз заполняет этот массив, куда добавляются все адреса внутри обрабатываемого блока(в данном случае от 5A70BFh до 5A7205h). Но наше необработанное(5A6E80h) смещение в данный блок не попадает, попадает оно в другой блок, обработка которого ведётся в процедуре по адресу 5A6FCFh, но оказывается управление вообще не попадает на этот адрес! Попадать оно туда естесственно должно, значит защита где-то рядом. Протектор при упаковке собирает адреса всех таких блоков в единый массив и по очереди их вызывает по два раза, один раз для инициализации, второй для создания релоков(могу ошибаться), и адрес 5A6FCFh почему-то не попадает в этот массив. По xref’ам выходим на такой код:


___:0055C309 loc_55C309:                             ; CODE XREF: sub_55BE60+49D
 ___:0055C309                 mov     al, 9
 ___:0055C30B                 and     eax, 0FFh
 ___:0055C310                 add     eax, ebx
 ___:0055C312                 sub     eax, edx
 ___:0055C314                 imul    eax, ecx
 
  ;Вызывается вот эта интересная функция..
 ___:0055C317                 call    sub_5C1B38
 ___:0055C31C                 or      eax, eax
 
  ;И если функция возвращает 1, то адрес блока не передаётся в функцию,
  ;да и сама функция вообще не вызывается
 ___:0055C31E                 jnz     short loc_55C33E
 ___:0055C320                 push    offset sub_5BEDDD
 ___:0055C325                 call    sub_55B63C
 ___:0055C32A                 push    offset sub_5A6FCF
 ___:0055C32F                 call    sub_55B63C
 ___:0055C334                 push    offset sub_5A6181
 ___:0055C339                 call    sub_55B63C
 ___:0055C33E
 ___:0055C33E loc_55C33E:                             ; CODE XREF: sub_55BE60+4BE
 ___:0055C33E                 cmp     dword_7B8254, 0
 ___:0055C345                 jz      short loc_55C371
 
  ...
 
  ;Смотрим теперь что там за функция такая вызывается:
 ___:005C1B38 sub_5C1B38      proc near               ; CODE XREF: sub_558B00+12Ep
 ___:005C1B38                                         ; sub_55BE60+4B7p
 ___:005C1B38                 call    GetThreadsCount
 ___:005C1B3D                 cmp     eax, 20
 ___:005C1B40                 jbe     short loc_5C1B49
 ___:005C1B42                 mov     eax, 1
 ___:005C1B47                 jmp     short locret_5C1B4E
 ___:005C1B49 ; ---------------------------------------------------------------------------
 ___:005C1B49
 ___:005C1B49 loc_5C1B49:                             ; CODE XREF: sub_5C1B38+8j
 ___:005C1B49                 mov     eax, 0
 ___:005C1B4E
 ___:005C1B4E locret_5C1B4E:                          ; CODE XREF: sub_5C1B38+Fj
 ___:005C1B4E                 retn
 ___:005C1B4E sub_5C1B38      endp


Вот где корень зла :) Функция возвращает единицу, если количество тредов больше 20 и 0 если меньше. Если вспомнить, сколько тредов создаёт протектор при своей работе, то становится понятно, что функция вернёт 1 если WinLicense запустили в упакованном виде и 0, если запустили дамп. После патча, который сделает, чтобы эта функция всегда возвращает 1, прогамма WinLicense не узнает, что её распаковали и будет запаковывать всё подряд и без ошибок, что собственно от ней и требуется. Наконец после этого патча протектор оказывается побеждённым полностью, уррааа!! :)

Послесловие

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

Исходники и инструменты: http://cracklab.ru/art/themida_crack.rar
Автор: CrystalDragon, c_dragon at mail.ru
10 ноября 2005 г.

Обсуждение статьи: Themida - обновлённый XProtector >>>


Комментарии к статье: Themida - обновлённый XProtector

X0E-2003 12.11.2005 16:18:07
Да ты просто монстр! Респект тебе CrystalDragon!!!!!!!!!!
---
STaNN 12.11.2005 18:27:48
Мощно, респект
---
arnix 12.11.2005 18:50:34
супер
---
GL#0M 12.11.2005 20:13:35
Нет слов. =)
---
BIGz 13.11.2005 04:17:02
Прям диссертация какая-то. Внушает!
---
ilya 13.11.2005 18:28:21
как всегда на высоте
---
Smon 13.11.2005 20:09:00
Осталось только старфорс распачить млин =)
---
Devil_guy 14.11.2005 13:49:32
Круто конечно! Так держать!
---
Z0oMiK 18.11.2005 02:52:03
CrystalDragon Respect :]
Smon удачи :)))
---

Материалы находятся на сайте https://exelab.ru



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS