Техническая поддержка :

Современные решения

для защиты Windows программ

и восстановления исходного кода
Автор: CrystalDragon. Дата публикации: 10.11.2005

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

Прошло уже больше года с момента написания статьи про 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):


CODE NOW!
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-плагином:


CODE NOW!
;Это две инструкции 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 его адрес. Смотрим, как протектор это делает:


CODE NOW!
;загружаем в 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.


CODE NOW!
;Это небольшой кусок кода из внедрямой 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:

CODE NOW!
;Вот этот адрес надо будет определить, а точнее переписать из лога
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

CODE NOW!
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 миллиарда секунд, которого уж точно хватит для того, чтобы тред успел закончить расшифровку и даже для того, чтобы снять дамп уже с готовыми к восстановлению краденными байтами:

CODE NOW!
;замена 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:

CODE NOW!
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" можно снимать дамп и изучать создание импорта. Здесь я не буду приводить её полностью, а только самые важные моменты.

CODE NOW!
;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 цикла, но фактически мешается только один:

CODE NOW!
;Загрузка в регистры адреса и размера проверяемого блока кода - 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, но пока вместо ссылок на неё импорт вызывается напрямую, например:

CODE NOW!

___: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 можно здесь:

CODE NOW!
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’а, но всё равно есть, что пояснить. Вот пример такой конструкции:

CODE NOW!
___: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 NOW!
;Начало у макросов 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 NOW!
; ========== 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, то аналогичные макросы там выглядели так:

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



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

CODE NOW!
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 принадлежит конечно же протектору. Ничего не остаётся, кроме того, как туда заглянуть:

CODE NOW!
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, хотя толку от этого мало будет :)

CODE NOW!
;Код этот так сильно разбавлен мусором, что без плагина невозможно вообще понять
;что к чему. С плагином идёт описание, как удалять мусорные конструкции,
;здесь оно точно уж пригодится
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 не представляется возможным, но..

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



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

CODE NOW!
;Вот где она, таблица-то..
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 NOW!
;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 перед исключением, быстро находим инструкцию, приводящую к краху:

CODE NOW!
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. По перекрестным ссылкам можно найти код, который учавствует в упаковке и как раз обрабатывает такие куски:


CODE NOW!
;Видим кучки 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’ам выходим на такой код:

CODE NOW!
___: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://reversing.dotfix.net/th_adv.rar


Комментарии

Добавил: Admin Дата: 27.11.2005

Отличная статья Драгон! Ты всегда на уровне :)


Добавление комментария


Ваше имя (на форуме):

Ваш пароль (на форуме):

Комментарии могут добавлять только пользователи,
зарегистрированные на форуме данного сайта. Если Вы не
зарегистрированы, то сначала зарегистрируйтесь тут

Комментарий:





Главная     Программы     Статьи     Разное     Форум     Контакты