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):Код (Text):
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-плагином:
Код (Text):
;Это две инструкции 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 его адрес. Смотрим, как протектор это делает:
Код (Text):
;загружаем в 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.
Код (Text):
;Это небольшой кусок кода из внедрямой 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 ;Здесь надо указать номер последнего треда, который создаётся <I>протектором,</I> ;в 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:
Код (Text):
;Вот этот адрес надо будет определить, а точнее переписать из лога 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 дешифровщика нулевого кольца, этот будет известен, только после определения смещения в стеке этого самого адреса
Код (Text):
;Вызов 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 миллиарда секунд, которого уж точно хватит для того, чтобы тред успел закончить расшифровку и даже для того, чтобы снять дамп уже с готовыми к восстановлению краденными байтами:
Код (Text):
;замена 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 ;... Код (Text):
;Вбиваем в 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:
Код (Text):
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" можно снимать дамп и изучать создание импорта. Здесь я не буду приводить её полностью, а только самые важные моменты.
Код (Text):
;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 цикла, но фактически мешается только один:
Код (Text):
;Загрузка в регистры адреса и размера проверяемого блока кода - 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, но пока вместо ссылок на неё импорт вызывается напрямую, например:
Код (Text):
___: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 можно здесь:
Код (Text):
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'а, но всё равно есть, что пояснить. Вот пример такой конструкции:
Код (Text):
___: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А теперь смотрим как удалять эти вставки:
Код (Text):
; ========== 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 endifwsprintf_in_IAT - можно найти после восстановления импорта imprec'ом, а wsprintf_emul_addr проскакивает в создании импорта, можно брать прямо оттуда. Теперь рассмотрим ещё два простеньких макроса - CODE_ENCRYPT и CODE_CLEAR:
Код (Text):
;Начало у макросов 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'ами и ещё больше приблизится к моменту полной распаковки.
Код (Text):
; ========== 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, то аналогичные макросы там выглядели так:
Код (Text):
jmp invalid_opcode db "xpro" ;Это просто сигнатура dd 0 ;0 - начало, 1 - окончание dd 0 db "xpro" invalid_opcode:Т.е. они выглядели почти также как и в sdk. Делаем предположение, что и в themid'е они также практически не меняются при упаковке. Смотрим SDK:
Код (Text):
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 принадлежит конечно же протектору. Ничего не остаётся, кроме того, как туда заглянуть:
Код (Text):
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, хотя толку от этого мало будет
Код (Text):
;Код этот так сильно разбавлен мусором, что без плагина невозможно вообще понять ;что к чему. С плагином идёт описание, как удалять мусорные конструкции, ;здесь оно точно уж пригодится 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 не представляется возможным, но..
Код (Text):
WinLicen:00BCCADB mov eax, [ecx+CONTEXT.Eip] WinLicen:00BCCAE1 mov [ebp+xvOldEIP], eax ;где xvOldEIP = 4BF14B9hЕсли код проверки на принадлежность адреса исключения к CODE_REPLACE лежит в открытом виде, то HEX-редакторе можно будет найти DWORD 4BF14B9h. И действительно, найти его можно и не раз. Первое вхождение 7CCAE3 выводит нас на перехваченную функцию KiUserExceptionDispatcher, но уже следующее выводит на совсем другой код. Он также очень сильно разбавлен мусором, поэтому придётся несколько минут посидеть, прежде чем раскопать окрестности вхождения и выделить нужный код, который обслуживает макрос(т.е. сверяет адрес из xvOldEIP с таблицей адресов, где применены макросы). Смотрим на этот код, на этот раз толку будет хоть отбавляй:
Код (Text):
;Вот где она, таблица-то.. 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 макросов, но вот в таблице они будут все, вне зависимости от того, автомитически ли они были созданы или нет. Теперь пара слов о восстановлении:
Код (Text):
;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 перед исключением, быстро находим инструкцию, приводящую к краху:
Код (Text):
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. По перекрестным ссылкам можно найти код, который учавствует в упаковке и как раз обрабатывает такие куски:
Код (Text):
;Видим кучки 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'ам выходим на такой код:
Код (Text):
___: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 отдыхает. Новинка превращает пользовательский код в псевдо-код для виртульной машины, это уже очень много говорит о силе защиты. Вообще будушее защиты именно за виртуальными машинами и обфускацией кода, и пора думать как с этим бороться.. На этом пожалуй всё.
Исходники и инструменты: здесь © CrystalDragon
Themida - обновлённый XProtector
Дата публикации 15 ноя 2005