Themida - обновлённый XProtector — Архив WASM.RU

Все статьи

Themida - обновлённый XProtector — Архив WASM.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+6.j
WinLicen:00BCCA68                 mov     ecx, [esp+pExceptionRecord]
WinLicen:00BCCA6C                 mov     ebx, [esp+0]
WinLicen:00BCCA6F
WinLicen:00BCCA6F loc_BCCA6F:                             ; CODE XREF: sub_BCCA52+10.j
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+40.j
WinLicen:00BCCA9C                 mov     eax, [ebp+4BF1AE9h]
WinLicen:00BCCAA2                 jmp     short loc_BCCAFC
WinLicen:00BCCAA4 ; ---------------------------------------------------------------------------

 ;Ожидание, пока IDT не окажется свободна(её могут использовать
 ;другие потоки для своих ring0 дешифровщиков)
WinLicen:00BCCAA4 loc_BCCAA4:                             ; CODE XREF: sub_BCCA52+48.j
WinLicen:00BCCAA4                 mov     edx, 2
WinLicen:00BCCAA9                 mov     eax, [ebp+4BF060Dh]
WinLicen:00BCCAAF
WinLicen:00BCCAAF @@waitforidt_loop:                      ; CODE XREF: sub_BCCA52+6D.j
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+61.j
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+7C.j
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+50.j
WinLicen:00BCCAFC                                         ; sub_BCCA52+87.j
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+AF.j
WinLicen:00BCCB13                 lea     eax, [ebp+4D218E3h]
WinLicen:00BCCB19
WinLicen:00BCCB19 loc_BCCB19:                             ; CODE XREF: sub_BCCA52+BF.j
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:00BBC2F7.j
 
 ;...
 ;Весь смысл внутри этого 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+210.j
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+139.j
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:00BE1C39.j
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:0121F176.j
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:0121F1A1.j
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+2D.j
___: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+12E.p
___:005C1B38                                         ; sub_55BE60+4B7.p
___: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+8.j
___:005C1B49                 mov     eax, 0
___:005C1B4E
___:005C1B4E locret_5C1B4E:                          ; CODE XREF: sub_5C1B38+F.j
___:005C1B4E                 retn
___:005C1B4E sub_5C1B38      endp

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

  Послесловие:

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

Исходники и инструменты: здесь

2002-2013 (c) wasm.ru