![]() |
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс |
Новичку | Ссылки | Программирование | Интервью | Архив | Связь |
Распаковка ThunderBolt 0.02Обсудить статью на форумеМассу крэкерских инструментов, видеоуроков и статей вы сможете найти на видеокурсе от нашего сайта. Подробнее здесь. Автор: kR01eG <netu@ego.ru> Сегодня в меню: ThunderBolt v0.02 (http://tuts4you.com/request.php?1942)Тут нас ждет: 1. Morphed Code 2. AntiDebug 3. Import steal Столовые приборы: OllyDbg, ImpRec, LordPE, компилятор C++ по вкусу, мозги (тоже по вкусу). Начнем-с.. Мы сюда не эксепшены ловить пришли, так что смело врубаем игнор всех ошибок в olly. Едем дальше, до EP. 0040B000 > /E9 00000000 JMP UnPackMe.0040B005 0040B005 60 PUSHAD 0040B006 E8 14000000 CALL UnPackMe.0040B01F 0040B00B 5D POP EBP 0040B00C 81ED 00000000 SUB EBP,0 0040B012 6A 45 PUSH 45 0040B014 E8 A3000000 CALL UnPackMe.0040B0BC 0040B019 68 00000000 PUSH 0 0040B01E E8 5861E8AA CALL AB29117B 0040B023 0000 ADD BYTE PTR DS:[EAX],AL Заходим в CALL по F7, потом еще в один, потом еще.. Видим такое: 0040B1CD C3 RETN 0040B1CE F0:EB FF LOCK JMP SHORT UnPackMe.0040B1D0 ; LOCK prefix is not allowed 0040B1D1 ^ 73 ED JNB SHORT UnPackMe.0040B1C0 0040B1D3 ^ 72 EB JB SHORT UnPackMe.0040B1C0 0040B1D5 ^ EB FA JMP SHORT UnPackMe.0040B1D1 0040B1D7 ^ 71 83 JNO SHORT UnPackMe.0040B15C 0040B1D9 C3 RETN 0040B1DA F8 CLC 0040B1DB EB FF JMP SHORT UnPackMe.0040B1DC 0040B1DD ^ 73 ED JNB SHORT UnPackMe.0040B1CC 0040B1DF ^ 72 EB JB SHORT UnPackMe.0040B1CC 0040B1E1 ^ EB FA JMP SHORT UnPackMe.0040B1DD 0040B1E3 ^ 71 83 JNO SHORT UnPackMe.0040B168 0040B1E5 C3 RETN 0040B1E6 14 EB ADC AL,0EB 0040B1E8 FF73 ED PUSH DWORD PTR DS:[EBX-13] 0040B1EB ^ 72 EB JB SHORT UnPackMe.0040B1D8 0040B1ED ^ EB FA JMP SHORT UnPackMe.0040B1E9 0040B1EF ^ EB 83 JMP SHORT UnPackMe.0040B174 0040B1F1 C3 RETN 0040B1F2 10EB ADC BL,CH 0040B1F4 FF73 ED PUSH DWORD PTR DS:[EBX-13] 0040B1F7 ^ 72 EB JB SHORT UnPackMe.0040B1E4 0040B1F9 ^ EB FA JMP SHORT UnPackMe.0040B1F5 0040B1FB E8 83C30CEB CALL EB4D7583 0040B200 FF73 ED PUSH DWORD PTR DS:[EBX-13] 0040B203 ^ 72 EB JB SHORT UnPackMe.0040B1F0 0040B205 ^ EB FA JMP SHORT UnPackMe.0040B201 0040B207 FF83 C308EBFF INC DWORD PTR DS:[EBX+FFEB08C3] 0040B20D ^ 73 ED JNB SHORT UnPackMe.0040B1FC 0040B20F ^ 72 EB JB SHORT UnPackMe.0040B1FC 0040B211 ^ EB FA JMP SHORT UnPackMe.0040B20D 0040B213 ^ EB 83 JMP SHORT UnPackMe.0040B198 0040B215 C3 RETN 0040B216 17 POP SS ; Modification of segment register Что, код нестабильный скрипит на зубах? :) Это у нас Morphed Code. Кто хочет потратить лучшие годы своей жизни на трассировку, могут продолжать. А мы "пойдем другим путем" (c). Ну че делать? Бум наверно запускать прогу :) Жмем Alt+M, ставим break-on-access на секцию .text нашего UnPackMe, давим Shift+F9 и... И... и приплыли!.. Итак, это вторая часть нашего путешествия - антидебаг. Для софтайса он конечно может быть и незаметен, но в целях самообразования мы его найдем сами. А потом - мы его убьем. Открываем анпакми в LordPE, там Directories и кнопка "..." напротив ImportTable. Там еще много разных директориес существует, это очень глубокая ж*па, и многие хорошие протекторы ее юзают. Открылась табличка импорта. Что мы видим - из одной-единственной библиотеки KERNEL32.dll импортируется 3 функции, посмотрите дети - это GetProcAddress, GetModuleHandleA и LoadLibraryA. Вы слышали чтобы дебагеры грохал LoadLibraryA? А GetProcAddress??? Да, вы отчасти правы, действительно, GetProcAddress существует для усложнения (а в клинических случаях - и для облегчения) жизни простого народа, т.е. крэкеров. А посему перезагружаем программу в OllyDbg и пишем в команд лайн (Alt+F1 если не видно :)) - BP GetProcAddress и жмем Enter. Ну, попробуем: Shift+F9. Остановились раз, остановились два, три. Четвертый раз: EDI 7C90E642 ntdll.ZwSetInformationThread EIP 7C80ADA0 kernel32.GetProcAddress Обана! ZwSetInformationThread, да еще и низкоуровневый! Неспроста это... Хорошо, убираем бряк с GetProcAddress, проходим по Ctrl+F9 до RET и возвращаемся в главную программу. Ну ладно, раз уж мы его в EDI нашли, значит так и напишем. Жмем Ctrl+B и набираем FF D7. Это код инструкции CALL EDI, давно пора знать уже! ]:-) Находим ее по адресу 321459h, ставим бряк по F2. Shift+F9 - попался скотина!! Убираем бряк, и пойдем с 32145Bh, не выполняя CALL EDI. Для этого выделим следующую инструкцию и нажмем Ctrl+Num* (звездочка на NumPad). Нас спросят, в своем ли мы уме - отвечаем "да", как бы сильны ни были наши сомнения в этом. И еще - функции из ntdll - низкоуровневые, там используется регистровый метод вызова, поэтому стек выравнивать не надо. Все! Теперь пробуем снова поставить break-on-access на секцию .text и запустить программу. Вуаля! 0040B2A2 8B7424 24 MOV ESI,DWORD PTR SS:[ESP+24] 0040B2A6 8B7C24 28 MOV EDI,DWORD PTR SS:[ESP+28] 0040B2AA FC CLD 0040B2AB B2 80 MOV DL,80 0040B2AD 33DB XOR EBX,EBX 0040B2AF A4 MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] 0040B2B0 B3 02 MOV BL,2 0040B2B2 E8 6D000000 CALL UnPackMe.0040B324 0040B2B7 ^ 73 F6 JNB SHORT UnPackMe.0040B2AF 0040B2B9 33C9 XOR ECX,ECX 0040B2BB E8 64000000 CALL UnPackMe.0040B324 0040B2C0 73 1C JNB SHORT UnPackMe.0040B2DE 0040B2C2 33C0 XOR EAX,EAX 0040B2C4 E8 5B000000 CALL UnPackMe.0040B324 0040B2C9 73 23 JNB SHORT UnPackMe.0040B2EE 0040B2CB B3 02 MOV BL,2 Теперь стоим здесь. Видно, что тут расшифровывается код, мало того - это до боли знакомый еще из EXAMPLES к masm32 - aPLib. Чистый код, не замутненный всякими там JMP, JO и т.д. Ставим бряк на последний POPAD/RETN 8 и благополучно выходим из подпрограммы. Ставим Memory breakpoint on write на все тот же .text и пропускаем все попадания, кроме этого: 003216BD 8907 MOV DWORD PTR DS:[EDI],EAX ; kernel32.CreateThread 003216BF 5A POP EDX 003216C0 0FB642 FF MOVZX EAX,BYTE PTR DS:[EDX-1] 003216C4 03D0 ADD EDX,EAX 003216C6 59 POP ECX 003216C7 42 INC EDX 003216C8 83C7 04 ADD EDI,4 003216CB ^ E2 E5 LOOPD SHORT 003216B2 003216CD ^ EB B0 JMP SHORT 0032167F 003216CF 64:A1 30000000 MOV EAX,DWORD PTR FS:[30] 003216D5 85C0 TEST EAX,EAX 003216D7 78 0F JS SHORT 003216E8 003216D9 8B40 0C MOV EAX,DWORD PTR DS:[EAX+C] 003216DC 8B40 0C MOV EAX,DWORD PTR DS:[EAX+C] 003216DF 8140 20 0020000>ADD DWORD PTR DS:[EAX+20],2000 003216E6 EB 23 JMP SHORT 0032170B 003216E8 6A 00 PUSH 0 003216EA FF95 0A190000 CALL NEAR DWORD PTR SS:[EBP+190A] 003216F0 85D2 TEST EDX,EDX 003216F2 79 17 JNS SHORT 0032170B 003216F4 837A 08 FF CMP DWORD PTR DS:[EDX+8],-1 003216F8 75 11 JNZ SHORT 0032170B 003216FA 52 PUSH EDX 003216FB 8B52 04 MOV EDX,DWORD PTR DS:[EDX+4] 003216FE 8142 50 0020000>ADD DWORD PTR DS:[EDX+50],2000 00321705 5A POP EDX 00321706 8B52 0C MOV EDX,DWORD PTR DS:[EDX+C] 00321709 FE02 INC BYTE PTR DS:[EDX] 0032170B 8B85 1A190000 MOV EAX,DWORD PTR SS:[EBP+191A] 00321711 83F8 01 CMP EAX,1 00321714 0F85 7D010000 JNZ 00321897 0032171A 8BBD 22190000 MOV EDI,DWORD PTR SS:[EBP+1922] 00321720 03FD ADD EDI,EBP 00321722 8DB5 AB180000 LEA ESI,DWORD PTR SS:[EBP+18AB] 00321728 8B07 MOV EAX,DWORD PTR DS:[EDI] 0032172A 0BC0 OR EAX,EAX 0032172C 0F85 51010000 JNZ 00321883 00321732 68 00FE9F07 PUSH 79FFE00 00321737 53 PUSH EBX 00321738 E8 5D000000 CALL 0032179A Обратите внимание на LOOPD, JMP и MOV - это признак (точнее, сигнатура), что мы остановились правильно. Снимаем breakpoint on write с секции, ставим бряк на выделенную жирным инструкцию и скромно, подобно падающему с дерева листу, наблюдаем за подгрузкой библиотек. Ну вот теперь-то, мы так долго этого ждали и наконец - ставим break-on-access на секцию .text, отпускаем прогу и радуемся (ну, сначала нажав Ctrl+A для анализа кода): 00401000 . E8 4D320000 CALL UnPackMe.00404252 ; OEP!!!!!! 00401005 . 6A 00 PUSH 0 00401007 . E8 C2310000 CALL UnPackMe.004041CE 0040100C . A3 94724000 MOV DWORD PTR DS:[407294],EAX 00401011 . 33D2 XOR EDX,EDX 00401013 . 52 PUSH EDX 00401014 . 68 28104000 PUSH UnPackMe.00401028 00401019 . 52 PUSH EDX 0040101A . 6A 64 PUSH 64 0040101C . 50 PUSH EAX 0040101D . E8 00320000 CALL UnPackMe.00404222 00401022 . 50 PUSH EAX 00401023 . E8 8E310000 CALL UnPackMe.004041B6 Это наш OEP, с чем друг друга и поздравляем! Но что такое?? Почему ImpRec не находит ни одной функции? В нормальной программе уже сразу с OEP видны call MessageBox, call GetModuleHandle и подобные (вспомнили OEP Дельфи? верной дорогой идете .))! Но у нас такого нету.. Посмотрим.. Зайдем к примеру в первый же CALL и видим: 00404198 /$ 90 NOP 00404199 |. E8 0DD7F1FF CALL 003218AB 0040419E |$ 90 NOP 0040419F |. E8 07D7F1FF CALL 003218AB 004041A4 |$ 90 NOP 004041A5 |. E8 01D7F1FF CALL 003218AB 004041AA |$ 90 NOP 004041AB |. E8 FBD6F1FF CALL 003218AB 004041B0 |$ 90 NOP 004041B1 |. E8 F5D6F1FF CALL 003218AB 004041B6 |$ 90 NOP 004041B7 |. E8 EFD6F1FF CALL 003218AB 004041BC |$ 90 NOP 004041BD |. E8 E9D6F1FF CALL 003218AB 004041C2 |$ 90 NOP 004041C3 |. E8 E3D6F1FF CALL 003218AB 004041C8 |$ 90 NOP 004041C9 |. E8 DDD6F1FF CALL 003218AB 004041CE |$ 90 NOP 004041CF |. E8 D7D6F1FF CALL 003218AB 004041D4 |$ 90 NOP 004041D5 |. E8 D1D6F1FF CALL 003218AB 004041DA |$ 90 NOP 004041DB |. E8 CBD6F1FF CALL 003218AB 004041E0 |$ 90 NOP 004041E1 |. E8 C5D6F1FF CALL 003218AB 004041E6 |$ 90 NOP 004041E7 |. E8 BFD6F1FF CALL 003218AB 004041EC |$ 90 NOP 004041ED |. E8 B9D6F1FF CALL 003218AB 004041F2 |$ 90 NOP 004041F3 |. E8 B3D6F1FF CALL 003218AB 004041F8 |$ 90 NOP 004041F9 |. E8 ADD6F1FF CALL 003218AB 004041FE |$ 90 NOP Нет, вы не сошли с ума, так и есть! Просто таблица импорта бессовестно сперта в неизвестном направлении. Точнее, в относительно неизвестном. Начинающие анпакеры часто пугаются таких извращений и жмут Alt+F2, но мы все-таки доведем дело до конца, как бы ни было страшно, противно, мерзко и т.д. Попробуем разобраться. То, что программа выполняется эквивалентно тому что ее можно распаковать. Мало того, необходимо! Процессор загружает левый код, а ведь в это время вы могли бы посчитать структуру какого-нибудь нуклеотида, участвуя в проекте распределенных вычислений! Каким образом программа узнает о том куда надо будет уйти с этого CALL? Вспомним как действует инструкция CALL. В стек заносится адрес следующей после CALL инструкции и идет переход на подпрограмму. Кстати! Команда CALL xxxxxx в 32-разрядном коде занимает ровно 5 байт! Но у нас еще есть NOP, занимающий, как вы уже знаете, 1 байт. 5+1=6 байт. Запомним это значение, дабы не было непоняток. Дело вот в чем. В стек заносится не адрес CALL, а адрес СЛЕДУЮЩИЙ после CALL. И в декодировании используется именно он. Это важный момент, может так получиться что весь импорт вроде бы восстановлен, а программа вылетает, т.к. при ошибке таблица получится "сдвинутая" и вместо GetModuleHandle получится ExitProcess. Я сам попался первый раз :) Учите матчасть, товарищи! Зайдем же скорее в этот чудный CALL 3218ABh и позырим чудеса заморские. 003218AB 50 PUSH EAX ;Непонятно 003218AC 8BC4 MOV EAX,ESP ;Уже понятнее 003218AE 60 PUSHAD ;Сохраняем регистры и смотрим резко ниже - там POPAD :) 003218AF 8BD8 MOV EBX,EAX ;EBX = ESP 003218B1 E8 04000000 CALL 003218BA ;перескакиваем через следующие 4 байта 003218B6 0000 ADD BYTE PTR DS:[EAX],AL ;т.е. вот это вот 003218B8 3200 XOR AL,BYTE PTR DS:[EAX] ;не выполнится :) 003218BA 5D POP EBP ;EBP = 3218B6h, еще помните про коварный CALL? :) 003218BB 8B6D 00 MOV EBP,DWORD PTR SS:[EBP] ;EBP = 320000h, посмотрите на 4 байта, да это же DWORD! 003218BE 8B7B 04 MOV EDI,DWORD PTR DS:[EBX+4] ;EDI = АДРЕС ВОЗВРАТА! 003218C1 8BB5 22190000 MOV ESI,DWORD PTR SS:[EBP+1922] ;ESI = dword[321922h] = смещение какой-то таблички. 003218C7 03F5 ADD ESI,EBP ;ESI = полный адрес, в EBP была база секции. 003218C9 8B06 MOV EAX,DWORD PTR DS:[ESI] ;а это - сюрприз :) посчитаем немного 003218CB 33D2 XOR EDX,EDX ;EAX = DWORD из таблички, EDX = 0 003218CD B9 02000000 MOV ECX,2 ;умножаем EAX на 2 003218D2 F7E1 MUL ECX ;вотъ 003218D4 D1E8 SHR EAX,1 ;делим на 2, простым сдвигом 003218D6 3BF8 CMP EDI,EAX ;и сравниваем с адресом возврата 003218D8 75 0A JNZ SHORT 003218E4 ;если не равны, то берем следующий адрес возврата 003218DA 0AD2 OR DL,DL ;а если равны, то интерес представляет DL ;он остался после MUL ECX. EDX здесь - старший DWORD 003218DC 75 04 JNZ SHORT 003218E2 ----+ ;если DL не ноль, то идем на Блок2 003218DE EB 09 JMP SHORT 003218E9 | ;иначе прыгаем на Блок1 ------------------+ 003218E0 EB 02 JMP SHORT 003218E4 | | 003218E2 EB 10 JMP SHORT 003218F4 <---+ | 003218E4 83C6 08 ADD ESI,8 | 003218E7 ^ EB E0 JMP SHORT 003218C9 | ----- Блок1 ---------------------------------------------- | 003218E9 8B46 04 MOV EAX,DWORD PTR DS:[ESI+4] ;интересно, EAX = какой-то адрес <-------+ 003218EC 8903 MOV DWORD PTR DS:[EBX],EAX ;т.к. EBX = ESP, значение инструкции очевидно 003218EE 61 POPAD ;восстанавливаем регистры 003218EF 58 POP EAX ;посмотрите, в [EBX] лежит адрес из таблички! 003218F0 8B00 MOV EAX,DWORD PTR DS:[EAX] ;по этому адресу лежит адрес API! 003218F2 FFE0 JMP NEAR EAX ;уходим на импорт, дальнейшее неважно ----- Блок2 ---------------------------------------------- 003218F4 8B46 04 MOV EAX,DWORD PTR DS:[ESI+4] 003218F7 8903 MOV DWORD PTR DS:[EBX],EAX 003218F9 61 POPAD 003218FA 58 POP EAX 003218FB 83C4 04 ADD ESP,4 003218FE 8B00 MOV EAX,DWORD PTR DS:[EAX] 00321900 FFE0 JMP NEAR EAX ;уходим на импорт, дальнейшее неважно JNZ по 3218DCh запросто может прыгнуть на 3218F4h, видимо автор спецально оставил такой код, собираясь дальше развить свою инженерную мысль. Значит не зря мы мучаемся :) Смотрим, Блок1 и Блок2 отличаются только указателем стека после вызова импорта. Логика очевидна: так как переходники протектора у нас в виде CALL, для импорта необходимо подставить адрес возврата в главную программу, а не в таблицу импорта (а текущий адрес возврата именно туда и указывает). Теперь все встало на свои места! Нам нужно заменить NOP/CALL xxxxxx на эквивалентные по размеру инструкции, переходящие по заданному адресу. Первое (а значит верное), что пришло в голову - это FF 25 XX XX XX XX, т.е. инструкция вида CALL [адрес], которая весит ровно 6 байт. Но таблица спертого импорта порой достигает двух-трех килобайт, поэтому мы автоматизируем этот процесс. Вот исходник программы на C++ (для MSVC 6.0, но легко компилится борландовским bcc32), которая проделает всю грязную работу за нас: #pragma comment (linker,"/NODEFAULTLIB /merge:.rdata=.data /filealign:0x200") #pragma comment (linker," /merge:.data=.text /merge:.bss=.text /merge:.text=kR01eG /section:kR01eG,WREX /filealign:0x200") #pragma comment (linker,"/entry:main /subsystem:windows") #include Укажем начало спертой таблицы, ее размер и PID программы, все естественно в 16-ричном виде большими буквами (no foolproof! сами себе навредите :)) и нажмем Recover. Проверяем в отладчике, импорт восстановлен :) Все! Дампим и прикручиваем импорт. Теперь прога будет в последний раз сопротивляться при попытке снять дамп в LordPE. Тут можно сделать Correct ImageSize там же в LordPE, после чего программа сдерется запросто, или же юзать PE Tools, там такой шняги нету. (В Options нужно сначала в групбоксе Task Viewer снять все галки, кроме Delete temp files for PE Editor). Дамп сняли. Теперь заставим его работать :) Открываем файл в PE Editor из LordPE, лезем в Sections, там каждой секции проставляем: * RawOffset = VirtualAddress * RawSize = VirtualSize Делается это так потому, что мы сняли дамп прямо с памяти, то есть физические адреса и размеры в файле такие же как и в памяти. Еще надо сменить EntryPoint RVA на наш - 1000. Все, жмем Save и OK. Посмотрите, у файла появилась иконка ;) Восстанавливать импорт бум с помощью ImpRec. На этот раз он все функции нашел, то то же :) Все здесь конеЦ! Да, че еще хочецо сказать по поводу такой защиты. Импорт издревле (со времен вин'95) был основной игрушкой самых разных категорий программистов, игрушкой он остался и по сей день. Его ищут вирусы, его шифруют протекторы, мы его восстанавливаем. Такая техника реализована также в популярном ASProtect 2.xx SKE (ну там посложнее чуток, чем в TB :)). В основном ничего нового придумано не было, основной упор сделан на то что если даже распаковщик сможет снять дамп, до системных функций ему не добраться. Это очень сильное заблуждение. Достаточно грамотный и упорный человек достанет все что угодно, и не только импорт. В досе в этом смысле было легче, ничего импортировать не надо было - все работало автономно на прерываниях. Поэтому там использовались в основном антидебаг-трюки, типа установки своего обработчика прерывания или CLI и HLT в самом неожиданном месте. И о восстановлении импорта еще надо сказать, что необязательно каждый раз использовать ImpRec. Множество людей просто ленятся затереть исходную таблицу импорта и она мирно лежит на своем месте в упакованной программе (в философском смсле :)). После распаковки достаточно лишь изменить Import Table RVA и Import Table Size на оригинальные. Найти таблицу в файле также не представляет особого труда для человека натренированного. Ищется она просто визуально - dword со смещением из файла, 2 нулевых dword-а, 2 dword-а со смещениями. Чем больше таблица, тем легче ее заметить, если она конечно присутствует ) * http://crackworld.stsland.ru/page/works/tbimport.rar Комментарии к статье: Распаковка ThunderBolt 0.02 -= XjekaCR =- 23.11.2007 21:08:13 классая статья --- Материалы находятся на сайте https://exelab.ru ![]() |
Вы находитесь на EXELAB.rU |
![]() |