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

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


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

Krypton - бородатое чудовище...

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

Очень удобно, когда все крэкерские инструменты, книги и статьи в одном месте. Используйте сборник от EXELAB - вот тут.

Автор: Godness <godness@omen.ru>

Есть у меня довольно устойчивые ассоциации, связанные с этим криптором - эдакий леший (грязный, вонючий) высунул бошку из леса и смотрит в даль своим одним глазом (а, в дали город, сады, особняки... шароварная, счастливая жизнь) и типа пугает: "Ууууу, бля... кто зайдет в мой лес - съем, на...". Вот как ни придет в голову слово "Krypton" - так сразу же и "леший" за ним :)...

Теперь серьезно. Кто не знает, что за Krypton - криптор такой, скачать можно с www.Lockless.com. Это крэкерский сайт - туторы, тулзы, крэкми, форум - все как полагается, солидно и со вкусом. Главный разработчик Krypton’а (и видимо один из админов сайта) - некий товарищ Yado... Упакуеш прогу размером в 10Kб - получиш 90Кб, упакуеш прогу в 300Kб - получиш... анн нет, не 1Мб, как было в прошлых версиях (последняя версия 0.5), а всего лишь немного большего размера, а в некоторых случаях даже меньшего. На мой взгляд Krypton довольно перспективный криптор ring-3, хотя все еще очень недоработанный и такой же "зеленый", как мокрый кирпич. Ну, если каждую четвертую прогу, которую я ему подсовывал, он просто напросто не хотел корректно упаковывать, как я ни вертел с опциями, прога просто падала при запуске. А с настройками "безопасности" (об этом позже) я так и ни одной проги не смог упаковать! Поэтому распаковывать будем сам Krypton.exe, в нем то и реализована вся та техника, которую он якобы умеет применять... Кстати, если вдруг кто знает реальную шароварную прогу, упакованую криптоном, киньте ссылку в коментариях. Сам распакованный криптон лежит в аттаче внизу.


Типа плАн... покуримкА

1. Антиотладочные приемы
2. Добираемся до OEP
3. В реанимации...
3.1 - Восстановление импорта
3.2 - Шифрованый код
3.3 - Украденные параметры процедур
3.4 - Западл0 в расшифрованном коде
3.5 - Метаморфные инструкции
3.6 - Ну, и на последок
4. Некролог...

1). Антиотладочные приемы

Гмм, не знаю с чего начать... собственно ничего особенного криптон тут не показал, за исключением некоторых интересных моментов (интересных потому, что я с таким встретился впервые :-). Тут надо сказать, что криптон после небольшой предварительной распаковки, прыгает в стек и все последующее, вплоть до перехода на OEP, происходит именно в стеке! Переход в стек происходит по адресу 0x004A2F46 (там jmp esi). Под ХР выполнение в стеке начинается с адреса 0x00350000, а под мастдаем (9x) - с адреса 0х00760000. Поэтому в этой части я в основном буду указывать RVA адресов относительно последних. Главный упор делается на использование следующего back door интерфейса
push SI_NOT_EXIST ;устанавливаем обработчик глюков
push dword [fs:0]
mov dword [fs:0], esp
...
mov si, 4647h
mov di, 4A4Dh
int 3 ;back door

SI_EXIST: ;сосулька есть
pop dword [fs:0] ;убираем обработчик


SI_NOT_EXIST: ;сосульки нет
pop dword [fs:0] ;убираем обработчик
jmp next
Эту методу криптон юзает аЖ целых 5 раз по следующим RVA (в порядке выполнения): 0xB332, 0x0C41, 0x668B, 0xCDFE, 0x13B2C. Если кто не знает, что за back door рассказываю - при отсутствии сосульки в системе любое int 3 приведет к генерации системой исключения, которое будет обработано установлеными обработчиками исключений (хм... товтология). Короче управление будет передано на SI_NOT_EXIST. Разработчики сосульки оставили возможность общения приложений ring-3 с отладчиком посредством int 3, только в регистрах должны быть установлены определенные значения (называемые MAGIC VALUES), воспринимаемые отладчиком как команды (что это за команды я не знаю, да это и не важно). Поэтому при запущеной сосульке исключения не произойдет и прога будет выполнятся дальше на SI_EXIST. Выход - юзать IceExt или IceDump. Либо поменять значения регистров перед выполнением int 3.

Также перед вызовом API производится проверка первого байта на соответствие значению 0xCC. На этом все приколы под NT’ями заканчиваются (ну там есть еще пару SEH’ов вида mov [0], ebx, но ихние обработчики ничего особенного не делают). И криптон можно легко запустить без IceExt (если вдруг нет под рукой) следующим образом: упадите на 0x004A2F46 (там где происходит переход в стек) и простым поиском по стеку s 0 l 00400000 66,BE,47,46 (опкод команды mov si, 4647h) замените на 66,EB,11,11 (mov si, 1111h). Запускается аж бЭгом.

А вот тот интересный антитрассировочный прием (идет сразу после перехода в стек)
0000B16E xor eax, eax
0000B170 call 0035B17E
0000B175 inc byte ptr [0035C975] ;обработчик глюков
0000B17B sub eax, eax
0000B17D retn
0000B17E push dword ptr fs:[eax]
0000B181 mov fs:[eax], esp
0000B184 pushf
0000B185 or byte ptr [esp+1], 1 ;тут то и прикол
0000B18A popf
0000B18B nop
0000B18C pop dword ptr fs:[eax]
0000B18F pop eax
0000B190 dec byte ptr [0035C975]
Пройдите по такому коду по F8 и через пару десятков мусорных команд окажетесь на ExitProcess. А отпустите F5 - и прога запускается. Минут десять(!) я возил туда-сюда этот кусочек пока не допер в чем прикол :)... Оказывается путем or byte [esp+1], 1 и popf прога пытается выставить влаг трассировки, что не вызывает исключения, если вы трассируете данный кусок по F8 т.к. флаг трассировки уже установлен. Прикольно... PS: Как позже я выяснил - мне просто надо было иногда почитывать туторы, когда то выкачаные мной с инета... Интересно как он определяет под какой системой запущен.
0000E459 mov cx, cs
...
0000E483 xor cl, cl
...
0000E4AE jecxz 0035E4B2 ;джамп если под NT
Под мастдаем (помимо предыдущего) криптон демонстрирует технику а-ля Xtreme-Protect. Запускает драйвер.
0000E611 call CreateFile ;KKK.TMP <- прикольное название
...
0000E76E call WriteFile ;драйверок размером 4Кб с копейками...
...
0000E7C4 call CloseHandle
...
0000E922 call CreateFile ;\\.\KKK.TMP запускает драйвер
Криптон создает драйвер в своем каталоге, запускает и потом (если все нормально) киляет его. Запуск драйвера синхронный, т.е. пока последний полностью не отработает возврат в прогу не происходит. Вот что криптон там напукАл:
PCOD:C0001000 loc_C0001000:
PCOD:C0001000 mov ds:dword_C0000050+53h, 0
PCOD:C000100A mov ds:dword_C0000050+57h, offset loc_C000109C
PCOD:C0001014 mov ds:dword_C0000050+5Bh, offset loc_C000109E
PCOD:C000101E mov ds:dword_C0000050+5Fh, offset loc_C00010D6
PCOD:C0001028 mov esi, (offset dword_C0000050+53h)
PCOD:C000102D VMMCall Install_Exception_Handler
PCOD:C0001033 jb loc_C00010D4
PCOD:C0001039 mov ds:dword_C0000050+63h, 0
PCOD:C0001043 mov ds:dword_C0000050+67h, offset loc_C00010AC
PCOD:C000104D mov ds:dword_C0000050+6Bh, offset loc_C00010AE
PCOD:C0001057 mov ds:dword_C0000050+6Fh, offset loc_C00010DE
PCOD:C0001061 mov esi, (offset dword_C0000050+63h)
PCOD:C0001066 VMMCall Install_Exception_Handler
PCOD:C000106C jb short loc_C00010D4 ;<- просто заменить на
PCOD:C000106E xor eax, eax ;jmp C00010BE (EB50)
PCOD:C0001070 mov dr0, eax ;...и ни хЭ не работает
PCOD:C0001073 mov dr1, eax
PCOD:C0001076 mov dr2, eax
PCOD:C0001079 mov dr3, eax
PCOD:C000107C mov dr6, eax
PCOD:C000107F mov eax, dr7
PCOD:C0001082 and eax, 0FFFFFF2Ah
PCOD:C0001087 mov dr7, eax
PCOD:C000108A cld
PCOD:C000108B mov edi, 0C0000000h
PCOD:C0001090 mov ecx, 0FFFFFh
PCOD:C0001095 loc_C0001095:
PCOD:C0001095 mov al, 1Dh
PCOD:C0001097 mov esi, offset dword_C0000050 ;там в *esi E460
PCOD:C000109C loc_C000109C: ;(опкод команды in al,60)
PCOD:C000109C repne scasb ;а находится вся эта байда
PCOD:C000109E loc_C000109E: ;как раз внутри WINICE
PCOD:C000109E test ecx, ecx
PCOD:C00010A0 jz short loc_C00010BE
PCOD:C00010A2 push edi
PCOD:C00010A3 push ecx
PCOD:C00010A4 add edi, 4
PCOD:C00010A7 mov ecx, 2
PCOD:C00010AC loc_C00010AC:
PCOD:C00010AC repe cmpsb
PCOD:C00010AE loc_C00010AE:
PCOD:C00010AE pop ecx
PCOD:C00010AF pop edi
PCOD:C00010B0 jnz short loc_C0001095
PCOD:C00010B2 add edi, 4
PCOD:C00010B5 mov word ptr [edi], 0FEEBh ;подсаживает сосульку
PCOD:C00010BA loc_C00010BA: ;на измену
PCOD:C00010BA jmp short loc_C00010BA ;и себя за компанию...
PCOD:C00010BC xor ebx, ebx
PCOD:C00010BE loc_C00010BE:
PCOD:C00010BE mov esi, (offset dword_C0000050+53h)
PCOD:C00010C3 VMMCall Remove_Exception_Handler
PCOD:C00010C9 mov esi, (offset dword_C0000050+63h)
PCOD:C00010CE VMMCall Remove_Exception_Handler
PCOD:C00010D4 loc_C00010D4:
PCOD:C00010D4 clc
PCOD:C00010D5 retn
PCOD:C00010D6 loc_C00010D6:
PCOD:C00010D6 test ecx, ecx ;обработчик исключения, если вдруг
PCOD:C00010D8 jz short loc_C00010BE ;посканит память, которой нет...
PCOD:C00010DA dec ecx
PCOD:C00010DB inc edi
PCOD:C00010DC jmp short loc_C000109C
PCOD:C00010DE loc_C00010DE:
PCOD:C00010DE lahf
PCOD:C00010DF and ah, 0BFh
PCOD:C00010E2 sahf
PCOD:C00010E3 jmp short loc_C00010AE
PCOD:C00010E5 loc_C00010E5:
PCOD:C00010E5 clc
PCOD:C00010E6 retn
PCOD:C00010E7 loc_C00010E7:
PCOD:C00010E7 cmp dword ptr [esi+0Ch], 0
PCOD:C00010EB jnz short locret_C00010EF
PCOD:C00010ED xor eax, eax
PCOD:C00010EF locret_C00010EF:
PCOD:C00010EF retn
Драйвер сканит адреса с 0xC0000000 по 0xС0100000 (память, отведенная мастдаем под дрова) на наличие байта 1D и через 4-ре байта после него слова E460 и находит их внутри winice.exe вот здесь.
C003D940 1E push ds
C003D941 662E8E1D971804C0 mov ds, cs:[C0041897]
C003D949 E460 in al, 60h ;<- пачит на jmp eip (EBFE)
C003D94B A27F1804C0 mov ds:[C004187F], al
Можно "стерилизовать" драйвер на момент создания, т.е. поставить бряк bpmb 00XXE76E x do "ew (*(esp+4) + 126C) 50EB". По смещению 0x126С в файле драйвера находится та команда jb short loc_C00010D4, которая на листинге по смещению PCOD:C000106C. Можно перепрыгнуть команду запуска драйвера (CreateFile(\\.\KKK.TMP, ...)), подкорректировав стек и возвращаемый результат. Или просто проверку на версию системы "поправить", чтобы думал что висит под NT’ями.

На этом криптон полностью иссякает и преспокойно запускается под мастдаем.

2). Добираемся до OEP

Тут криптон немного лопухнулся. Подсунув ему пару "кроликов", удалось выяснить следующую методу. Как и полагается нормальному криптору криптон не сохраняет первоначального состояния ни одного регистра и даже значения в стеке на момент OEP отличные от тех, что были на EP ...кроме одного - первого dword’а! Он такой же как и был на момент EP. За это и зацепимся. Делаете так падаете на EP, запоминаете первый dword в стеке (у меня под XP - 77E7EB69). Ставите бряк на GetVolumeInformationA (не забудьте, что первый байт API сканится на соответствие 0xCC)... упали, теперь ставите бряк на RegCloseKey... упали, и F12. Это последние API которые юзает распаковщик перед переходом OEP. В этот момент указатель стека будет таким же как и на OEP! Ну и теперь ставите этот бряк: bpmd esp if *(esp) == 77E7EB69. Через мгновение выливаетесь и потрассировав совсем немного увидите следующую команду mov edx, [ebp + 00447813]. Угадайте, что в edx... До OEP совсем немного и можете пешком, если хотите прогулятся :)... Я на такси - bpx edx. Эта метода с успехом прокатывала на всех прогах, которые мне удавалось упаковать криптоном.

По поводу этих API - упаковщик создает уникальный ID тачки, на которой в данный момент запускается, и кидает его (ID) в раздел реестра HKEY_LOCAL_MACHINE\SOFTWARE\Krypton. В итоге там остаются следы после каждой проги, им упакованой.

Примечание: интересно что у всех(!) прог, упакованых криптоном, и под ХР и под мастдаем в момент нахождения на OEP указатель стека всегда заканчивался на FFB0, что видимо должно о чем-то говорить... хм-м... но, что-то не говорит...

На OEP прогу можно дампить. Секцию кода потом все равно надо будет заменять восстановленной. Сразу можно удалить секции упаковщика
Name Virtual Size Virtual Offset Raw_Size Raw_Offset Characteristics
| | | | |
YADO 00003000 00001000 00003000 00001000 E0000020
YADO 0004C000 00004000 0004C000 00004000 C0000040
YADO 00001000 00050000 00001000 00050000 C0000040
YADO 00001000 00051000 00001000 00051000 D0000040
YADO 0000F000 00052000 0000F000 00052000 D0000040
krypton 00043000 00061000 00043000 00061000 E0000020 <- удалить
_!_!_!_ 0003D600 000A4000 0003D600 000A4000 E0000020 <- удалить
При упаковке криптон прикручивает к экзешнику две секции с названиями ’krypton’ и ’_!_!_!_’. Секции, что принадлежат экзешнику, переименовывает в ’YADO’ и "вырезает" из файла... ну, в смысле Raw_Size и Raw_Offset делает нулевыми, таким образом секции в файле физически отсутствуют. Только секцию ресурсов оставляет на месте, просто переименовывая. Последние две секции в дампе можно смело удалять. Также там две секции будут иметь атрибут Shareable in memory - уберите это безобразие. Видать это звязано с тем, что криптон после каждой упаковки перезапускает себя - типа какую-то хитрость мутит. В чем конкретно прикол, я не разбирался.

3). Восстановление импорта и секции кода

Сразу хочу оговорится что способ, которым я восстанавливал импорт и секцию кода, довольно муторный и претендует на оскар в номинации "Самая закумаренная распаковка года". Наверняка можно было бы автоматизировать этот процесс, написав какой-нить плагин для ImpRec. Но для такого редкого зверя как криптон, которого надо занести в Красную книгу, мне реально было лень над этим парится ...поэтому и так сойдет. Я в основном использовал тот факт, что секция кода у криптона на удивление очень мала - всего 12Кб! И для того чтобы обнаружить все зашифрованные участки я просто вручную проматривал секцию кода. К слову, секция данных криптона неприлично большая - более 300Кб. А при дизасемблировании ида умудрялась найти xref’ы чуть ли не в самом конце этой секции, при этом большая половина последней - просто нули...

Итак, что же предлагает криптон в качестве защиты (в порядке увеличения сложности защиты): Просто упаковка -> K-Protect on API -> Enable K-Exec -> K-Exec on API -> K-Exec Levels: +1; +2; +3;

K-Protect on API: это простые переходники на API реализованные, следующим образом:
Было допустим так
call dword [00501000] ;а в 00501000 - адресс API 77E79F93

А стало так
call dword [00501000] ;a в 00501000 - адресс в стеке 0017001F, а там

0017001F add dword [0017003A], 5F25A8E8h
00170029 mov eax, dword [0017003A]
0017002E sub dword [0017003A], 5F25A8E8h
00170038 jmp eax <- джамп на API
00170038 ; ---------------------------------------
0017003A dd 18C1F6ABh <- зашифрованный адресс API

или так

0017003E xor dword [00170059], 7EFCB01h
00170048 mov eax, dword [00170059]
0017004D xor dword [00170059], 7EFCB01h
00170057 jmp eax
00170057 ; --------------------------------------
00170059 dd 700897B4h
Для каждого API числа естевственно разные. ImpRec хоть и видит эти переходники, но просто так ни фига не понимает... (далее я буду называть их К-переходники, для однозначности). Это та максимально крутая опция защиты, с которой мне удавалось упаковывать проги. С остальными более крутыми опциями, которые я опишу дальше, упакованые проги просто не запускались. Ну я не особо так старался, конечно, - после десятой упавшей, с этими крутыми опциями, проги я забил на это дело... Но, как вы увидите далее - сам криптон напичкан этой техникой по самые "не балуйся"...

K-Exec: это техника, которая позволяет проге не быть в какой-то момент полностью распакованной, т.е. криптон определенные куски кода, указаные програмистом, просто шифрует в проге и расшифровывает только тогда, когда дойдет очередь. После этого либо просто удаляет зашифрованый кусок (если он больше никогда не выполнится) либо снова его шифрует (неточно выразился - шифрованый кусок кода копируется в выделенную память, там расшифровывается и выполняется, а в проге остается как и был).
db 0ebh,0eh <- заметте, опкод команды jmp $+0Eh
db ’KDES’
db 00,00,00,00,00
db 00,00,00,00,00

код, обрамленный этими сигнатурами, будет выполнен
один раз... затем удален из проги (заполнен нулями)

db 0ebh,0eh
db ’KDEE’
db 00,00,00,00,00
db 00,00,00,00,00
Аналогично, если вместо соответствующих строк поставить следующие: db ’KEES’ и db ’KEEE’, то такой код не будет удален после выполнения. Это я к тому, что в распакованом криптоне вы можете найти остатки этих сигнатур, таким образом определив, что же автор хотел от нас спрятать ;)...

K-Exec on API: прежде чем перейти непосредственно на K-переходник, криптон предложит вам долгую и 0чень увлекательную экскурсию по темным, мокрым, забитым до отказа мусором, лабиринтам его пещер (то бишь стека)... Как вам перспективка 1-2Кб пешком протопать? (Угадайте какая клавиша у кракера ломается первой?!... Думаете, что F8... гы, а вот и не угадали... - а, ломается первой клавиша F8! :-)

K-Exec Levels +1; +2; +3: это уже и по смыслу можно догадаться - чем больше мусора, тем лучше...

Ладно, добрались до OEP (которая, забыл сказать, равна 00401000 - стандартная точка входа для прог, написанных на ассемблере, не правда ли...) и сразу бросается в глаза:
00401000 push 0
00401002 call 00403A9D ;(K-Exec on API) + (K-Protect) = GetModuleHandleA
00401007 mov dword [004063B5], eax
0040100C push offset aKrypt_tmp ; "Krypt.tmp"
00401011 call 00403A91 ;(K-Exec on API) + (K-Protect) = DeleteFileA
00401016 call 00403AA3 ;(K-Exec on API) + (K-Protect) = GetCommandLineA
0040101B push 0001E426 ;начало процедуры расшифровки
00401020 push 000B070C ;последующего участка кода
00401025 call dword [00AE0000]
0040102B ;----- зашифрованный участок кода ----------
0040102B into
0040102C pop ss
0040102D sbb eax, A9CBEDB6
00401032 sahf ...и т.д.
3.1) Значит, сначала брешу как восстановил импорт. Смотрим, что у нас по адресу 00403A9D (ну, первый call), там
00403A91 call dword [0037847A] ;и так вся таблица от начала
00403A97 call dword [0037847A] ;до конца - на один манер
00403A9D call dword [0037847A]
00403AA3 call dword [0037847A] ;по адресу 0037847A - 0036441F
00403AA9 call dword [0037847A]
Можно предположить, что это когда-то была таблица вида jmp dword [...], поэтому именно к такому виду приведем ее снова. То, что вся таблица под одну гребенку, нам наруку... во первых, определение того, какую именно функцию сейчас хочет прога, зависит от адреса возврата, ложащегося в стек при данных вызовах... а, во-вторых это значит, что сама процедура определяющая требуемый API (собственно это и есть процедура K-Exec on API) и потом К-переходник, расположена по одному и тому же адресу! Дык, что нам тогда стоит подпачить ее таким образом, чтобы при каждом выполнении данных call’ов, эта процедура сама восстанавливала данную таблицу к виду jmp dword [...]. Да, ни чего не стоит! Такую методу, как проверку своего тела на целостность криптон еще пока не юзает. А, даже если бы и юзал - было бы это проблемой?!... хм-м... скорей думаю, что было бы пару лишних абзацев в данном туторе ;)...

Сначала определимся относительно каких адресов заделаем jmp dword [...]. Тут долго думать не приходится - это четвертая секция, начинается с VA 00451000, ида не одного xref’а в ней не нашла да и размер небольшой, всего 4Кб - имхо бывшая секция импорта, аккуратно подстриженная криптоном под нуляк... ну, сплошные ноли...

Итак, вот начало и конец процедуры выполняющей K-Exec on API (как вы видите, теперь я уже привожу адреса, соответствующие XP ...хотя может и для 9x тоже подойдут если базу стека поменять, я не проверял):
0036441F push edi ;начало процедуры K-Exec on API
00364420 push ecx
00364421 pushf
00364422 mov ecx, [esp+0Ch] ;здесь в ecx адресс возврата после
00364426 sub ecx, 6 ;соответствующего call dword [0037847A]
00364429 push edx ;он то нам и надо
0036442A push ebp
0036442B push ebx
0036442C call 00364431
00364431 pop ebp
00364432 sub ebp, 41BD7Ch
00364438 mov edx, [ecx]
...
003649D5 pop ebx ;конец процедуры К-Exec on API
003649D6 pop ebp ;здесь в eax адресс соответствующего
003649D7 pop edx ;K-переходника, что опять же нам
003649D8 popf ;надо для извлечения адреса API
003649D9 pop ecx
003649DA test edi, edi
003649DC jz 003649E4
003649DE pop edi
003649DF add esp, 4
003649E2 jmp 003649E5
003649E4 pop edi
003649E5 jmp dword [eax] ;джамп на K-переходник

Подпачим ее следующим образом:

0036441F push edi
00364420 push ecx
00364421 pushf
00364422 mov ecx, [esp+0Ch]
00364426 jmp 00AE0F35 ;<- изменение
0036442B push ebx
0036442C call 00364431
00364431 pop ebp
00364432 sub ebp, 41BD7Ch
00364438 mov edx, [ecx]
...
003649D5 jmp 00AE0F45 ;<- изменение
003649DA test edi, edi
003649DC jz 003649E4
003649DE pop edi
003649DF add esp, 4
003649E2 jmp 003649E5
003649E4 pop edi
003649E5 jmp dword [eax]
Подпачив эту процедуру в начале, мы извлечем из нее адресс возврата и, отняв 6, узнаем адресс call’а выполнившегося в данный момент. А подпачив в конце - узнаем адресс K-переходника, из которого уже извлечем все необходимые числа для вычисления текущего API. Вот и сама процедура восстановления импорта:
00AE0F2D dd 0
00AE0F31 dd 0 ;тут сохранится адресс текущего call’а
00AE0F35 ;- джамп из начала -----------;
00AE0F35 sub ecx, 6 ;вычисляем текущий call
00AE0F38 mov [00AE0F31], ecx ;и сохраняем его адресс
00AE0F3E push edx
00AE0F3F push ebp ;выполняем спионереные команды
00AE0F40 jmp 0036442B ;и возвращаемся
00AE0F45 ;- джамп из конца ------------;
00AE0F45 pushad ;сохраняем состояние регистров
00AE0F46 pushf
00AE0F47 mov ecx, [eax] ;выбираем необходимые
00AE0F49 inc ecx ;числа из К-переходника
00AE0F4A mov al, [ecx] ;а, для пущей ясности, смотрите
00AE0F4C inc ecx ;на опкод последнего чуть ниже
00AE0F4D mov ebx, [ecx]
00AE0F4F mov edx, [ecx+4]
00AE0F52 mov ebx, [ebx]
00AE0F54 cmp al, 5 ;определяем какой K-переходник
00AE0F56 jz loc_AE0F5C ;xor или add-sub
00AE0F58 xor ebx, edx ;вычисляем API
00AE0F5A jmp loc_AE0F5E
00AE0F5C loc_AE0F5C:
00AE0F5C add ebx, edx ;или здесь вычисляем API
00AE0F5E loc_AE0F5E:
00AE0F5E mov eax, 00403A67 ;это начало таблицы call’ов
00AE0F63 mov edx, [00AE0F31]
00AE0F69 push edx ;тут вычисляем какой call по счету
00AE0F6A sub edx, eax
00AE0F6C mov eax, edx
00AE0F6E cdq
00AE0F6F xor ecx, ecx
00AE0F71 mov cl, 6
00AE0F73 div cx
00AE0F76 shl ax, 2
00AE0F7A mov esi, 00451000 ;заполняем таблицу адресов импорта
00AE0F7F add esi, eax
00AE0F81 mov [esi], ebx
00AE0F83 pop edx
00AE0F84 mov word [edx], 25FF ;опкод команды jmp dword [...]
00AE0F89 mov [edx+2], esi ;ну, и адресок для скобочек...
00AE0F8C popf
00AE0F8D popad ;восстанавливаем состояние регистров
00AE0F8E pop ebx \
00AE0F8F pop ebp |
00AE0F90 pop edx |<- украденные инструкции, надо выполнить
00AE0F91 popf |
00AE0F92 pop ecx /
00AE0F93 jmp 003649DA ;<- возвращаемся

а вот опкоды некоторых команд, что бы было понятно

81353A001700FD1A241F xor dword [0017003A], 1F241AFD
FF2524104500 jmp dword [00451024]
Забыл сказать, но вероятно вы уже поняли, что для каждого API K-переходник свой и они аккуратно расбросаны по стеку. По поводу самих адресов 00AE0F90 - это конец области памяти, которую выделил себе криптон для процедуры расшифровки, которая расположена в начале этой области, но об этом потом. Возвратимся к импорту. Сейчас опытный взгляд заметит, что таблица адресов импорта, которая получится, будет не совсем корректной с точки зрения структуры стандартной таблицы импорта. Поэтому ImpRec хоть и опознает весь импорт правильно, но не признает такую таблицу валидной и соответственно не захочет восстанавливать... Эту стрАшную тайну я узнал тоже потом, и соответственно поведаю о ней абзацем позже.

Теперь просто отпускаете прогу и большая часть импорта сразу восстанавливается. А то что не восстановилось - просто ручками, т.е. командой сосульки r перемещаете значение eip на не восстановленный call dword [0037847A], ставите бряк на 003649E5 (в конце процедуры K-Exec on API, ну чтобы не вылететь куда не надо) и отпускаете F5. Потом перемещаете eip на следующий call dword [0037847A] и т.д. пока не восстановите все остальное...

И вот, все готово, но ImpRec не хочет признавать такую таблицу импорта... Пришлось немного попарится и вспомнить как же устроена оная. Оказалось, что стрАшная тайна состоит в следующем. У нас адреса импортируемых процедур получились расположеными последовательно один за другим, а дык вот в стандартном импорте адреса, принадлежащие разным dll-кам, разделяются через dword со значением 0! 0, как! Тут я долго не думал - просто ручками в отладчике пораздвигал восстановленные адреса, чтобы они разделялись нулевым dword’ом (ведь теперь уже сосулька показывала символьные имена вроде jmp [KERNEL32!GetModuleHandleA]).Ну и все - ImpRec нормально восстановил импорт. Вот какой рабочий импорт у распакованного криптона:
Target: D:\CrackMe\Krypton\Krypton.exe
OEP: 00001000 IATRVA: 00051000 IATSize: 000000C4

FThunk: 00051000 NbFunc: 0000001B
1 00051000 kernel32.dll 0359 VirtualFree
1 00051004 kernel32.dll 0356 VirtualAlloc
1 00051008 kernel32.dll 01AB GetSystemTime
1 0005100C kernel32.dll 0377 WriteFile
1 00051010 kernel32.dll 003B CopyFileA
1 00051014 kernel32.dll 004B CreateFileA
1 00051018 kernel32.dll 00AC ExitProcess
1 0005101C kernel32.dll 0079 DeleteFileA
1 00051020 kernel32.dll 018A GetProcAddress
1 00051024 kernel32.dll 0168 GetModuleHandleA
1 00051028 kernel32.dll 00FE GetCommandLineA
1 0005102C kernel32.dll 013E GetDriveTypeA
1 00051030 kernel32.dll 014E GetFileSize
1 00051034 kernel32.dll 0162 GetLogicalDrives
1 00051038 kernel32.dll 00E6 FreeLibrary
1 0005103C kernel32.dll 01BF GetTickCount
1 00051040 kernel32.dll 01B7 GetTempPathA
1 00051044 kernel32.dll 01B5 GetTempFileNameA
1 00051048 kernel32.dll 002D CloseHandle
1 0005104C kernel32.dll 01CB GetVolumeInformationA
1 00051050 kernel32.dll 005D CreateProcessA
1 00051054 kernel32.dll 022F LoadLibraryA
1 00051058 kernel32.dll 024B MoveFileA
1 0005105C kernel32.dll 0291 ReadFile
1 00051060 kernel32.dll 02E9 SetEndOfFile
1 00051064 kernel32.dll 02F2 SetFilePointer
1 00051068 kernel32.dll 032A Sleep

FThunk: 00051070 NbFunc: 0000000D
1 00051070 user32.dll 023C SendMessageA
1 00051074 user32.dll 02BC UpdateWindow
1 00051078 user32.dll 0287 SetWindowTextA
1 0005107C user32.dll 0202 PostQuitMessage
1 00051080 user32.dll 01EA MoveWindow
1 00051084 user32.dll 01DD MessageBoxA
1 00051088 user32.dll 0175 GetWindowRect
1 0005108C user32.dll 0112 GetDlgItem
1 00051090 user32.dll 010F GetDesktopWindow
1 00051094 user32.dll 00C7 EndDialog
1 00051098 user32.dll 01BC LoadIconA
1 0005109C user32.dll 00C5 EnableWindow
1 000510A0 user32.dll 009F DialogBoxParamA

FThunk: 000510A8 NbFunc: 00000001
1 000510A8 comdlg32.dll 006E GetOpenFileNameA

FThunk: 000510B0 NbFunc: 00000004
1 000510B0 advapi32.dll 01D0 RegDeleteKeyA
1 000510B4 advapi32.dll 01F9 RegSetValueExA
1 000510B8 advapi32.dll 01CD RegCreateKeyExA
1 000510BC advapi32.dll 01C9 RegCloseKey
3.2) Теперь займемся зашифроваными кусками кода. Вот так они начинаются
push 0001E426
push 000B070C
call dword [00AE0000] <- процедура расшифровки

Передаваемые параметры конечно каждый раз разные, они определяют колличество зашифрованного кода (который идет сразу за call’ом), что делать потом с кодом (удалить или оставить) и т.д. Шифрованый код копируется в выделенную память, расшифровывается и выполняется. Снова таки, сама эта процедура расшифровки расположена статично и, соответственно, ни что не мешает нам пропачить ее так, чтобы получить в итоге нормальный код на месте шифрованного (хм-м... опять товтология... расшифровка шифрованного - масло маслянное... ну, ладно - не Толстой все таки). Кстати заметте(!), что колличество байт, занимаемое этим кодом, в точности соответствует колличеству байт выше упомянутой сигнатуры, вот этой:
db 0ebh,0eh
db ’KDES’
db 00,00,00,00,00
db 00,00,00,00,00

А вот и процедура расшифровки - сама 0на:

00AE0000 dd 00AE0004
00AE0004 push eax
00AE0005 pushf
00AE0006 mov eax, [esp + 8]
00AE000A push ebx
00AE000B push ecx
00AE000C push esi
00AE000D push edi
00AE000E push ebp
00AE000F call $+5
00AE0014 pop ebp
00AE0015 sub ebp, 00416B3B
00AE001B mov esi, [ebp + 00416E1F]
00AE0045 xor [esp + 20h], esi
00AE006B xor [esp + 24h], si
00AE0092 mov ecx, [esp + 20h] ;извлекается первый параметр
00AE00B8 mov ebx, [esp + 24h] ;и второй
00AE00E3 lea edi, [ebp + 00417248]
00AE0110 mov esi, eax ;в edi 00AE0721
00AE0139 rep movsb ;переписывает зашифрованый код
00AE015C mov ecx, edi ;в конец своего тела
00AE0184 add ecx, 6
00AE01AF mov dword [edi], 25FFh
00AE01D4 mov [edi + 2], ecx
00AE01FD mov [edi + 6], esi
00AE021F mov ebx, [esp + 24h]
00AE024B and ebx, FFFF0000
00AE0278 cmp ebx, 10000h
00AE02A5 jnz loc_AE0443
00AE0347 mov ecx, [esp + 20h]
00AE036C mov esi, eax
00AE038E dec esi
loc_AE03B5:
00AE03D5 inc esi ;зашифрованный код если надо
00AE03F6 mov byte [esi], 0 ;удаляется из проги
00AE041F loopne loc_AE03B5
loc_AE0443:
00AE046A mov ebx, [esp + 24h]
00AE0493 mov ecx, [esp + 20h]
00AE04B8 lea edi, [ebp + 00417248]
00AE04E3 xor eax, eax
00AE0508 mov ax, bx
loc_AE0530:
00AE0554 neg cl
00AE0577 add [edi], cl
00AE059B xor [edi], cl
00AE05BE rol byte [edi], cl
00AE05E3 neg cl
00AE0606 sub [edi], al
00AE0629 add [edi], ah
00AE0653 xor [edi], al
00AE0674 rol byte [edi], cl
00AE069C xor [edi], ah
00AE06BE inc edi
00AE06E6 loopne loc_AE06EA ;а это непосредственно
00AE06E8 jmp short loc_AE06EF ;цикл расшифровки... не особо хитрый
loc_AE06EA:
00AE06EA jmp loc_AE0530
loc_AE06EF:
00AE06EF jmp short loc_AE0717
loc_AE0717:
00AE0717 pop ebp ;восстановление стека
00AE0718 pop edi ;для выполнения расшифрованного кода
00AE0719 pop esi
00AE071A pop ecx
00AE071B pop ebx
00AE071C popf
00AE071D pop eax
00AE071E add esp, 0Ch
00AE0721 ;<- а, здесь уже начинается расшифрованый код
Это 0на уже приведена в божеский вид, т.к., как вы понимаете, все это было смачно перемешано с мусором и всякими ложными джампами и растянуто почти на 2Кб... соответственно, указанные адреса команд истинные, т.е. там, где эта команда и находится. Подпачил 0ную здесь:
00AE06E8 nop
00AE06E9 nop
00AE06EA jmp 00AE0F00

сам патч

00AE0F00 jcxz loc_AE0F08
00AE0F03 jmp 00AE0530 ;джамп, если расшифровка еще не завершилась
00AE0F08 pushad
00AE0F09 pushf
00AE0F0A mov ecx, [esp + 44h] ;тут, думаю, все понятно...
00AE0F0E mov esi, [esp + 40h]
00AE0F12 xchg edi, esi
00AE0F14 cld
00AE0F15 sub edi, 10h
00AE0F18 mov al, 90h
00AE0F1A push ecx
00AE0F1B xor ecx, ecx
00AE0F1D mov cl, 10h
00AE0F1F rep stosb
00AE0F21 pop ecx
00AE0F22 sub esi, ecx
00AE0F24 rep movsb
00AE0F26 popf
00AE0F27 popad
00AE0F28 jmp 00AE06EF
В конце расшифрованного кода оказывается сигнатура db ’KEES’ или db ’KEEE’ поэтому в распакованном криптоне можно найти все "бывшие в употреблении", хотя это я вроде уже говорил. call dword [00AE0000] легко ищется в коде по сигнатуре, потом перемещаете eip на первый push, ставите где нужно бряк и отпускаете... Зашифрованных кусков оказалось не так много... как того, о чем речь пойдет дальше.

3.3) Украденные параметры процедур. Посмотрите как он это делает:
00401131 push 0 ;какой-то параметр для API
00401133 push 405452h ;аналогично
00401138 call dword [00378476] ;<- находимся мы здесь
0040113E call 00403B69 ;какое-то API
...
00401131 push 0
00401133 push 405452h
00401138 push dword [0040533E] ;<- и снова мы здесь!
0040113E call dword [00378476] ;где же API?
...
00401131 push 0
00401133 push 405452h
00401138 call dword [00378476] ;и вроде, как ни в чем не бывало
0040113E call 00403B69 ;<- а, теперь мы здесь
Процедура call dword [00378476] переписывает на свое место украденную инструкцию, перемещая себя вперед, чтобы потом снова спрятать оную. Не принимает ни каких параметров и ориентируется только по ложащемуся в стек адресу возврата. А чтобы определить, что надо в данный момент сделать (возобновить украденную инструкцию или спрятать), у нее есть просто булевская переменная, при каждом вызове инвертирующаяся. И опять же(!) эта процедура расположена статично. Мы подпачим ее так, чтобы она только восстанавливала параметры и не прятала их назад. Надо просто сделать так, чтобы упомянутая булевская переменная не инвертировалась. Вот здесь:
00363EF0 push ecx ;начало этой процедуры
00363EF1 pushf
00363EF2 sub dword [esp + 8], 6
00363EF7 mov ecx, [esp + 8]
00363EFB push eax
00363EFC push edx
00363EFD push ebp
00363EFE push ebx
00363EFF call $+5
00363F04 pop ebp
00363F05 sub ebp, 0041B84F
00363F0B not dword [ebp + 0041C4D8] ;<- вот это удалить
... ;просто забить нопами
Отпускаете прогу и она сама восстанавливает большую часть украденых параметров (а, они были стырены почти перед каждым вызовом API! и кое-где еще). А то, что не восстановилось - ищем по сигнатуре.

Так, как я это все описываю, может показаться, что также все гладко и шло... Конечно же нет. Поэтому, чтобы все выше сказанное, не переделывать каждый раз, когда прога вылетала и ее приходилось перезапускать, я просто задел макросы которые инициализировались при запуске сосульки. Вот они
macro import = "eb 00364426 E9,0A,CB,77,0;eb 003649D5 E9,6B,C5,77,0"
macro steck = "eb 003640F2 90,90,90,90,90,90;eb 0036413E 90,90,90;eb 00363F0B 90,90,90,90,90,90;ed 00364B8D FFFFFFFF"
macro crack = "import;steck;eb 00AE06E8 90,90,E9,11,8,0,0;dd;!loadfile c:\\patch.dat 00AE0F00"
Упав на OEP, я набирал "crack" и все пачилось в один миг. По названию и адресам вы можете догадаться, что каждый макрос пачит. А в файлике patch.dat - те самые два куска кода, восстанавливающие импорт и код.

Ну, вот казалось бы, и все восстановлено, что может быть еще... Запускаю... - иии... матЮююк на всЕ 17’ дюймов: "Вашы типа ручки выполнили недопустимую операцию и будут ампутированы!"... Таааак ;(... барабаня пальцами по столу... ш0 еш0 там такое?!

3.4) Западл0 в расшифрованном коде:

0040337A push eax
0040337B mov eax, [0040680C]
00403380 xor eax, 00112233
00403385 and eax, 11000000
0040338A test eax, eax
0040338C jz 004011A4 <- просто заменить на jmp
00403392 mov eax, [0040680C]
00403397 xor eax, 00112233
0040339C and eax, 00FFFFFF
004033A1 call eax
004033A3 pop eax
004033A4 jmp short loc_4033B4
004033A4 ; ----------------------------
004033A6 db ’KEEE’
004033AA db 0,0,0,0,0
004033AF db 0,0,0,0,0
004033B4 loc_4033B4:
Это один из кусков расшифрованного кода. В нем всего лишь производится проверка на наличие упаковщика. При наличие такового call eax прыгал в стек, где кроме нескольких десятков ложных джампов ничего не было, потом возвращался. В распакованном экзешнике прога естевственно просто вылетала. Подобных проверок обнаружилось несколько.

3.5) Метаморфные инструкции:

Тут аналогично как и с параметрами процедур. Только в данном случае криптон тырит отдельные инструкции и выполняет их как метаморфный код, уже не восстанавливая в оригинальном виде в проге. Метаморфный код - это такой код, результат работы которого можно получить, выполнив всего лишь одну команду, т.е. такой код можно заменить этой одной командой (... а может и нет). А самый прикол в том, что на каждую стыреную команду, криптон заделал отдельную процедуру, поэтому по сигнатуре ничего найти не получится... Я просто просматривал секцию кода (хорошо, что маленькая) в поисках подозрительных call’ов. И надо сказать, что они обнаруживались почти на каждой третей экранной странице. На данный момент криптон "освоил" пока два вида команд, вот эти:

C705366E400001000000 mov dword [00406E36], 1 ;было так

50 push eax ;стало так
53 push ebx
FF159000DC00 call dword [00DC0090]
5B pop ebx
58 pop eax

00DC0094 push ebp ;а вот как он ее эмулирует
00DC0095 push eax
00DC0096 push ebx
00DC0097 pushf
00DC0098 call $+5
00DC009D pop ebp
00DC009E sub ebp, 0041B79A
00DC00A4 lea eax, [ebp + 0041B7CD]
00DC00AA mov ebx, [eax]
00DC00AC mov eax, [eax + 4]
00DC00AF xor ebx, eax
00DC00B1 add eax, ebx
00DC00B3 mov [ebx], eax ;<- !!!
00DC00B5 and eax, 0FFFF0000h
00DC00BA lea ebx, [ebp + 0041B7C9]
00DC00C0 mov ebx, [ebx]
00DC00C2 shr eax, 18h
00DC00C5 cmp ebx, eax
00DC00C7 popf
00DC00C8 pop ebx
00DC00C9 pop eax
00DC00CA pop ebp
00DC00CB retn

и еще одна команда

803DD642410001 cmp byte [004142D6], 1 ;было так

FF15A001E400 call dword [00E401A0] ;cтало так
90 nop

00E401A4 push ebp ;эмуляция команды
00E401A5 push eax ;все это неплохо перемешано
00E401A6 push ebx ;с мусором
00E401A7 pushf
00E401A8 call $+5
00E401AD pop ebp
00E401AE sub ebp, 0041B7E6
00E401B4 lea eax, [ebp + 0041B7F7]
...
00E401CD mov ebx, [eax]
...
00E401D7 mov eax, [eax + 4]
...
00E401EC xor ebx, eax
...
00E401F5 add eax, ebx
...
00E40226 shr eax, 18h
...
00E4023A popf
00E4023B cmp ebx, eax ;возвращается флаг ZF
...
00E40264 sub ebp, 02750474
00E4026A mov [ebx], eax
00E40273 pop ebx ;а в проге далее следует jnz
00E40274 pop eax ;ну, как правило...
00E40275 pop ebp
00E40276 retn
3.6) Ну, и на последок:

Для полноты картины хочу показать некоторые особенности его как криптора, ну чтобы легко было узнавать. Вот на каких конструкциях основан его "мусорный движок" (если так можно сказать). Это встречается не просто на каждом шагу... - а, на кАждом шАгу прост0! А именно:

loc_4A403D:
004A403D sub eax, 6 ;а это уже что-то осмысленное
004A4040 jmp 004A4071 ;прыгаем дальше...
...
004A4047 pop ecx
004A4048 jns loc_4A403D
004A404A jmp loc_4A404D
...
loc_4A404D:
004A404D js loc_4A403D
...
004A4050 pop ecx
004A4051 pushf ;эта конструкция особенно
004A4052 add ecx, -19h ;запоминается
004A4055 popf
004A4056 jmp ecx ;попадаем на 004A4047
...
004A405A push ecx ;<- сначала мы находимся здесь !!!
004A405B call sub_4A4050

или вот еще одна, как характерная разновидность предыдущего

00AE0347 mov ecx, [esp+20h]
00AE034B jmp loc_AE0388
loc_AE0352:
00AE0352 pop ecx
00AE0353 jz loc_AE0347
00AE0355 jmp loc_AE0358
loc_AE0358:
00AE0358 jnz loc_AE0347
loc_AE035B:
00AE035B push ecx
00AE035C jmp loc_AE0352
loc_AE0365:
00AE0365 jmp loc_AE035B ;<- сначала мы здесь
4). Некролог...

В ходе лабораторных испытаний, длившихся 2 недели, из отряда добровольцев в 13 прог остаsлось в живых 11. Будучи зараженными криптоном в ос0бо тяжелой форме, погибли добровольцы calc.exe и sol.exe. Врачи бились над спасением их жизней до самого конца! Вечная слава героям, отдавшим свои жизни во имя создания лекарства от угрожающей всему варезному миру болезни... А за такое коварство, насаженная на кол, голова криптона была с позором отправлена его шаману - пусть знает, что с нами шутки плохи... Типа аминь.

:)))... Короче отправил я распакованый криптон товарищу Yado и как смог на инглише накарябал несколько строк... типа, привет с Украины и т.д. Не ну, все культурно и вежливо - чин чинарем... но, так он мне ничего и не ответил... ну, да ладно...

Итак, господа, представляю вашему вниманию этого дикого зверя. Вот он, смотрите, бъется в клетке и брызжет слюной. Осторожно, близко не подходите! А вон какая-то сАбаченка в углу вякает. ККК.TMP вроде кличка... Ой, совсем забыл. Внимание, господа !!!ВНИМАНИЕ!!! Убедительная просьба - пЕченьем животных не кормить!!!...(скачать)

;+++++++++++++++++++++++;
Статью написал типа опять я, Godness (godness@omen.ru). Спасибо за интерес к последней...

Обсуждение статьи: Krypton - бородатое чудовище... >>>


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



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


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