Посоветовал мне тут друг
хороший Add-In для Micro$ost Visual Studio 2k3 под
названием Visual Assist X. Весьма и весьма удобная
вещь. Попользовался я им немного и подсел. А он,
подлец, работает всего несколько дней. В общем
началось все как обычно - PEiD. На этом обычность
закончилась :(, ибо красовалась там надпись весьма
неприятная - "Armadillo 2.51 - 3.xx DLL Stub ->
Silicon Realms Toolworks". Из неприятных
неожиданностей - невозможность работы библиотеки при
наличии УСТАНОВЛЕННОГО Soft-Ice’a. После колупаний
во внутренностях обнаружено, что он ищет NTICE,
SOFTICE и BPMSuper. У системного программиста
наверняка должен быть Soft-Ice, поэтому столь
жесткая позиция удивляет - ведь библиотека
рассчитана как раз на программистов, да к тому же
пишущих на C++. В общем весьма странно... Зато у нас
появился лишний повод снять эту армадиллу.
Т.к. с армой я еще не был знаком, я начитался
Нарваи, поэтому здесь будут встречаться куски
текста, весьма похожие на его, но способы борьбы
будут другие. В связи с этим прошу не наезжать с
темой, что все, мол, нагло скатано с Нарваи.
Ну вот, с лирикой на этом пожалуй надо кончить :)
Что нам понадобится:
OllyDebug Основной инструмент
PEiD Вроде больше и не понадобится
LordPE Будем дампить и править дамп
ImpRec Будем лечить импорт
Блокнот :) Будем там хранить все необходимое
Калькулятор Если вы Hex числа обсчитываете в уме, он
вам не нужен ;)
Начинаем распаковку...
Как обычно, чтобы распаковать программу надо найти
OEP, сделать дамп и восстановить таблицу импорта.
Начнем ;)
<Unpacking Programm...>
<Find OEP & Dump File…>
Приступим... Чтобы попасть на OEP достаточно
поставить мемори бряк на доступ на секцию .text
Прервемся где-то здесь:
1EE4D3DC PUSH EBP
1EE4D3DD MOV EBP,ESP
1EE4D3DF PUSH EBX
1EE4D3E0 MOV EBX,DWORD PTR SS:[EBP+8]
1EE4D3E3 PUSH ESI
1EE4D3E4 MOV ESI,DWORD PTR SS:[EBP+C]
1EE4D3E7 PUSH EDI
1EE4D3E8 MOV EDI,DWORD PTR SS:[EBP+10]
1EE4D3EB TEST ESI,ESI
1EE4D3ED JNZ SHORT VA_X.1EE4D3F8
Это OEP. Дампим... Дамп есть.
</OEP Finded & File Dumped>
<Restore IAT…>
Теперь используя ImpRec пробуем восстановить импорт.
Если вы не знаете, как восстанавливать импорт у
библиотеки - закрываете эту страницу и идете еще
чуток накачать экспу ;) А остальные уже видят, что
там куча неопределившихся функций, и все они
указывают куда-то на 0037####. Итак, таблица
подпорчена. Это плохо, будем разбираться...
Можно поступить разными способами - например
смотреть в Оле в запакованной библиотеке, куда ведут
эти переходники. Но тут много неудобств - занимает
много времени, можем что-нибудь пропустить... да
мало ли что еще может нам не понравится :) К этому
способу, если что, можно прибегнуть в любой момент.
Мы пойдем по другому пути - попробуем разобраться с
алгоритмом заполнения таблицы импорта. Может найдем
что-нибудь интересненькое...
Ладно, приступим:
Если вы заметили, импорт начинается с адреса
001BC000 (это секция rdata) - значит будем искать,
когда и откуда туда заносятся эти адреса.
Перезапускаем олю и на первом эксепшене в окне дампа
ставим хард бряк на запись по адресу 1EEBC000.
Окажемся здесь:
00396217 MOV DWORD PTR DS:[EAX],ECX
00396219 MOV EAX,DWORD PTR
SS:[EBP-37F0] ; VA_X.1EEBC000
0039621F ADD EAX,4
00396222 MOV DWORD PTR
SS:[EBP-37F0],EAX
00396228 JMP 00395EFB
Это цикл заполнения таблицы импорта, то есть то, что
нам и надо. Обратите внимание, что ниже в таблице
уже есть и нормальные адреса и переходники. Значит
заполняется она не сверху, но нам надо попасть на
самое начало заполнения. Пока это не так важно. Для
начала разберемся с переходниками. Начинаем
трейсить...
00395F15 AND DWORD PTR
SS:[EBP-3D9C],0
00395F1C MOV EAX,DWORD PTR
SS:[EBP-3784]
Здесь в EAX адрес строки, содержащей название API
функции
Это нас особо не интересует. Тут довольно длинный
цикл. Смотрим далее:
0039609E TEST EAX,EAX
003960A0 JNZ SHORT 003960B3
003960A2 MOV EAX,DWORD PTR
SS:[EBP-3DA8]
003960A8 MOV EAX,DWORD PTR DS:[EAX+8]
003960AB MOV DWORD PTR
SS:[EBP-3D9C],EAX
003960B1 JMP SHORT 003960B5
003960B3 JMP SHORT 00396052
003960B5 MOV EAX,DWORD PTR
SS:[EBP-3B5C]
Сразу ставим бряк на 003960B5 ( у Вас адрес может
быть другой, туда ведет безусловный прыг, до
которого программа дойдет, если не сработает
условный прыг (JNZ SHORT 003960B3), то есть это
выход из цикла, который нас не интересует (цикл нас
не интересует %))
Чуток потрейсим:
003960B5 MOV EAX,DWORD PTR
SS:[EBP-3B5C]
003960BB INC EAX ;Переходим к следующей API
функции
003960BC MOV DWORD PTR
SS:[EBP-3B5C],EAX ;Сохраняем
003960C2 CMP DWORD PTR
SS:[EBP-3D9C],0 ;Подозрительное сравнение
003960C2 - Довольно подозрительное сравнение, т.к.
если там не ноль он идет дальше и дальше и в итоге
сохраняет это значение (DWORD PTR SS:[EBP-3D9C]) в
таблице импорта, но пока там ноль. Можно заметить,
что в EAX хранится порядковый номер функции из той
библиотеки, которую мы сейчас обрабатываем (сейчас
это ADVAPI32). Заглянем в ImpRec и увидим, что
вместо адреса 4 функции - переходник. Значит
теоретически скоро в DWORD PTR SS:[EBP-3D9C] будет
записано 37E06D. Это легко проверить. Трейсим... О
да, когда EAX=4 по тому адресу записан именно тот
переходник.
А что же происходит, если переходника нет и прыг не
срабатывает? Адреса нормальных функций вычисляются
вот здесь:
003960E5 MOV EAX,DWORD PTR
SS:[EBP-3DA0]
003960EB MOV DWORD PTR
SS:[EBP+FFFFAD7C],EAX
003960F1 PUSH 1
003960F3 PUSH DWORD PTR
SS:[EBP+FFFFAD7C];Хэндл библиотеки
003960F9 PUSH DWORD PTR SS:[EBP-3B64]
;Разыскиваемая функция
003960FF CALL 00379DF9
Думаю, вам понятно, как избавиться от
переходников... Если нет, поясняю - арма проверяет,
есть ли переходник для функции, рассматриваемой в
данный момент (CMP DWORD PTR SS:[EBP-3D9C],0), если
есть, она сразу записывет его в таблицу импорта
программы, если нет, то находит нормальный адрес
функции и записывает его. Если занопить прыг после
проверки наличия переходника получим нормальный
импорт.
Алгоритм заполнения таблицы импорта понятен.
Осталась малость - попасть на формирование таблицы
импорта до внесения первой записи. Ставим хард бряк
на исполнение на адрес 003960C2 (на проверку наличия
переходника), перезапускаемся и останавливаемся.
Теперь нопим прыжок (003960C9 75 42 JNZ SHORT
0039610D) и отпускаем библу на волю... А эта сволочь
хлоп и загнулась, да и текст теперь красного цвета
:( Но нам это уже до лампочки, т.к. правильный
импорт арма нам составила. Запускаем ImpRec и
натравливаем на скончавшуюся библиотеку, но все еще
висящую в памяти на исключении (надеюсь Вы не нажали
Shift+F9). OEP указываем 14D3DC и смотрим на импорт
- опять левые адреса :( , но их меньше, да и
выглядит импорт лучше, без неопределившихся адресов
в середине функций из одной библиотеки %).
Любопытные могли заметить, что арма заменяет
нули-разделители своими адресами, а происходит это
во время исключений (тогда же она переходит к другим
библиотекам), поэтому смело выкидываем
неопределившиеся и фиксим дамп... ImpRec не может
добавить секцию :( У Нарваи написано, что делать.
Если забыли - надо поменять свойства всех секций на
E00000020 и сделать Rebuild. С импортом вроде все.
Как видите, ничего особо сложного в этой программе с
ним не делалось.
<IAT Restored>
</Programm Unpacked>
Ну вот... Теоретически распаковка закончена. Итак,
теперь мы запускаем дамп с исправленным импортом
и... видим обращения к каким-то не тем адресам :(((
Первый раз (и пока последний ;) это происходит
здесь:
1ED1BAFF NOP
1ED1BB00 JMP 01EA899D
1ED1BB05 CALL VA_X.1EE48AF2
Это есть антидамп (кто не в курсе - это тот джамп
;). В итоге дамп все равно не рабочий. Но Вы его не
удаляйте, мы из него импорт будем брать ;)
Как выяснилось, не все так просто, как хотелось.
Типовой распаковки не получилось. Ладно, будем
исправлять и эту неприятность.
<Fix Antidump…>
Теперь берем запакованный файл и продолжаем
извращаться с ним.
Способ поиска антидампов абсолютно такой же, как у
Нарваи (интересно, а есть другой). Как обычно ставим
хард бряк (далее просто бряк) на запись по адресу
1ED1BB01 (т.к. нас интересует сам адрес джампа).
Остановимся здесь:
77C32F41 JB SHORT MSVCRT.77C32F6C
77C32F43 REP MOVS DWORD PTR ES:[EDI],DWORD PTR
DS:[ESI]
77C32F45 JMP NEAR DWORD PTR
DS:[EDX*4+77C33058]
Тут в EDI копируются байты из ESI. Смотрим, что там:
00D1AB1C 90 90 90 90 | E9 98 CE 9D ђђђђй�Оќ
00D1AB24 E2 E8 E8 CF | 12 00 8D 46 вииП_.ЌF
Тут все на лицо - это тот кусок кода, который
расположен вокруг антидампа и сам антидамп, E8 CF |
12 00 - а это колл, который после антидампа. Теперь
ставим бряк на запись сюда (98 CE 9D E2) - это байты
прыжка. Следите за содержимым дампа, оно будет
довольно часто меняться. Тормознемся тут:
00394EE1 MOV EAX,DWORD PTR
SS:[EBP-3A34]
00394EE7 ADD EAX,DWORD PTR
SS:[EBP-3A5C]
00394EED MOV ECX,DWORD PTR
SS:[EBP-3A54]
00394EF3 MOV DWORD PTR DS:[EAX],ECX
00394EF5 JMP SHORT 00394F05
00394EF7 MOV EAX,DWORD PTR
SS:[EBP-3A58]
00394EFD MOV ECX,DWORD PTR
SS:[EBP-3A54]
00394F03 MOV DWORD PTR DS:[EAX],ECX
00394F05 ^0AFFFFFF JMP 00394E14
Опять цикл. Видим, что байты записаны из ECX, а в
ECX они попадают из EBP-3A54. Посчитаем теперь,
сколько это. У меня это 6B394. Ставим бряк на запись
туда...
00394E23 8B85 B0C5FFFF MOV EAX,DWORD PTR
SS:[EBP-3A50]
00394E29 8B00 MOV EAX,DWORD PTR DS:[EAX]
00394E2B 8985 A8C5FFFF MOV DWORD PTR
SS:[EBP-3A58],EAX
00394E31 8B85 B0C5FFFF MOV EAX,DWORD PTR
SS:[EBP-3A50]
00394E37 83C0 04 ADD EAX,4
00394E3A 8985 B0C5FFFF MOV DWORD PTR
SS:[EBP-3A50],EAX
00394E40 8B85 B0C5FFFF MOV EAX,DWORD PTR
SS:[EBP-3A50]
00394E46 8B00 MOV EAX,DWORD PTR DS:[EAX]
00394E48 8985 ACC5FFFF MOV DWORD PTR
SS:[EBP-3A54],EAX
00394E4E 8B85 B0C5FFFF MOV EAX,DWORD PTR
SS:[EBP-3A50]
00394E54 83C0 04 ADD EAX,4
А в это время, неподалеку...
00394E48 8985 ACC5FFFF MOV DWORD PTR
SS:[EBP-3A54],EAX
Вот здесь запись. Смотрим, как эти байты попадают в
EAX.
00394E40 8B85 B0C5FFFF MOV EAX,DWORD PTR
SS:[EBP-3A50]
00394E46 8B00 MOV EAX,DWORD PTR DS:[EAX]
Значит из DWORD PTR SS:[EBP-3A50] получают адрес,
где хранятся байты. Смотрим туда...
0094A8F8 0E 10 D0 1E | EE EF 59 E4 _Р_опYд
0094A900 21 00 2A 03 | ED 0F A6 1B !.*_н_¦_
0094A908 25 10 D0 1E | FC EF 59 E4 %_Р_ьпYд
0094A910 3B 00 2A 03 | EA 0F A6 1B ;.*_к_¦_
Это таблица, содержащая адреса и байты: *** - куда
писать (адрес в программе), *** - какие байты
(прыжок в антидамп), в следующей строчке - то же
самое, только разница в том, что это адрес в
антидампе, а байты - байты возврата в программу.
Далее все повторяется... Обратите внимание - первый
адрес совсем не тот, где мы запоролись при запуске
исправленного антидампа. Когда остановитесь на OEP
прокрутите код на самый верх - 1ED0100D E9 EEEF92E2
JMP 01630000 - вот самый первый антидамп. Теперь
посмотрите на первую строчку в таблице - адрес
1ED0100E. На конце E а не D потому, что не
учитывается байт прыжка (E9). А сами байты весьма
похожи - EEEF****. Значит здесь (94A8F8) начало
таблицы адресов и байтов. Его можно найти, просто
посмотрев выше - там совсем другие байты, не похожие
на искомые.
Ставим бряк на запись на 0094A8F8. Следите за
содержимым. Прервемся здесь:
0039416B 0385 28C6FFFF ADD EAX,DWORD PTR
SS:[EBP-39D8]
00394171 8B8D 50C6FFFF MOV ECX,DWORD PTR
SS:[EBP-39B0]
00394177 8901 MOV DWORD PTR DS:[ECX],EAX
00394179 8B85 50C6FFFF MOV EAX,DWORD PTR
SS:[EBP-39B0]
0039417F 83C0 04 ADD EAX,4
00394182 8985 50C6FFFF MOV DWORD PTR
SS:[EBP-39B0],EAX
Опять цикл, причем весьма большой. Давайте его
рассматривать подробно.
В этом месте рассчитываются байты для прыжка в
антидамп и для возврата из него. Если кто не в курсе
- байты прыжка показывают не адрес, куда надо
прыгать, а на сколько относительно текущего
положения надо передвинуться. Итак, алгоритм
достаточно прост - из адреса "куда" вычитаем адрес
"откуда".
003940EB MOV EAX,DWORD PTR
SS:[EBP-39B8]
003940F1 MOV EAX,DWORD PTR DS:[EAX] ; где
поправить адреса для прыга в антидамп
003940F3 MOV DWORD PTR
SS:[EBP-39D8],EAX
003940F9 MOV EAX,DWORD PTR
SS:[EBP-39B8]
003940FF ADD EAX,4
00394102 MOV DWORD PTR
SS:[EBP-39B8],EAX
00394108 MOV EAX,DWORD PTR
SS:[EBP-39B8]
0039410E MOV EAX,DWORD PTR DS:[EAX] ; смещение
начала антидампа
00394110 MOV DWORD PTR
SS:[EBP-39DC],EAX
00394116 MOV EAX,DWORD PTR
SS:[EBP-39B8]
0039411C ADD EAX,4
0039411F MOV DWORD PTR
SS:[EBP-39B8],EAX
00394125 MOV EAX,DWORD PTR
SS:[EBP-39B8]
0039412B MOV EAX,DWORD PTR DS:[EAX] ; смещение
конца антидампа (тут править байты для возврата)
0039412D MOV DWORD PTR
SS:[EBP-39D4],EAX
00394133 MOV EAX,DWORD PTR
SS:[EBP-39B8]
00394139 ADD EAX,4
0039413C MOV DWORD PTR
SS:[EBP-39B8],EAX
00394142 MOV EAX,DWORD PTR DS:[3AE880] ;
начало выделенной памяти под антидампы
00394147 ADD EAX,DWORD PTR
SS:[EBP-39DC] ; Начало антидампа с учетом адреса
загрузки
0039414D MOV ECX,DWORD PTR
SS:[EBP-39D8]
00394153 MOV EDX,DWORD PTR
SS:[EBP-38F4]
00394159 LEA ECX,DWORD PTR DS:[EDX+ECX+4]
; Считаем адрес возврата
0039415D SUB EAX,ECX ; Считаем байты прыга в
антидамп
0039415F MOV DWORD PTR
SS:[EBP-39D0],EAX
00394165 MOV EAX,DWORD PTR
SS:[EBP-38F4]
0039416B ADD EAX,DWORD PTR
SS:[EBP-39D8] ; Куда писать байты прыга в антидамп
00394171 MOV ECX,DWORD PTR
SS:[EBP-39B0]
00394177 MOV DWORD PTR DS:[ECX],EAX ; Пишем в
таблицу
00394179 MOV EAX,DWORD PTR
SS:[EBP-39B0]
0039417F ADD EAX,4
00394182 MOV DWORD PTR
SS:[EBP-39B0],EAX
00394188 MOV EAX,DWORD PTR
SS:[EBP-39B0]
0039418E MOV ECX,DWORD PTR
SS:[EBP-39D0] ; Байты для прыга в антидамп
00394194 MOV DWORD PTR DS:[EAX],ECX ; Пишем в
таблицу
00394196 MOV EAX,DWORD PTR
SS:[EBP-39B0]
0039419C ADD EAX,4
0039419F MOV DWORD PTR
SS:[EBP-39B0],EAX
003941A5 MOV EAX,DWORD PTR
SS:[EBP-39D8]
003941AB MOV ECX,DWORD PTR
SS:[EBP-38F4]
003941B1 LEA EAX,DWORD PTR DS:[ECX+EAX+4]
; Куда вернуться
003941B5 MOV ECX,DWORD PTR
SS:[EBP-39D4] ; Куда писать байты возврата
003941BB MOV EDX,DWORD PTR DS:[3AE880]
003941C1 LEA ECX,DWORD PTR DS:[EDX+ECX+4]
003941C5 SUB EAX,ECX ; Считаем байты возврата
003941C7 MOV DWORD PTR
SS:[EBP-39D0],EAX
003941CD MOV EAX,DWORD PTR DS:[3AE880]
003941D2 ADD EAX,DWORD PTR
SS:[EBP-39D4] ; Куда их пропишем
003941D8 MOV ECX,DWORD PTR
SS:[EBP-39B0]
003941DE MOV DWORD PTR DS:[ECX],EAX ; Сохраним
адрес для записи
003941E0 MOV EAX,DWORD PTR
SS:[EBP-39B0]
003941E6 ADD EAX,4
003941E9 MOV DWORD PTR
SS:[EBP-39B0],EAX
003941EF MOV EAX,DWORD PTR
SS:[EBP-39B0]
003941F5 MOV ECX,DWORD PTR
SS:[EBP-39D0]
003941FB MOV DWORD PTR DS:[EAX],ECX ; И сами
байты возврата
003941FD MOV EAX,DWORD PTR
SS:[EBP-39B0]
00394203 ADD EAX,4
00394206 MOV DWORD PTR
SS:[EBP-39B0],EAX
0039420C ^JMP 003940D9
Вот и весь алгоритм (прочитав понять не легко, но
если потрейсите в оле, все поймете и разберетесь
довольно быстро - тут сплошная математика). Отсюда
становится понятным, как нам перенаправить антидамп
туда, куда надо нам. Если не понятно - просто
изменив значение по адресу DWORD PTR DS:[3AE880] (от
него зависит, куда направится прыжок из программы в
антидамп и прыжок возврата в антидампа в программу,
а больше нам ничего и не требуется) Но для начала
посмотрим, сколько места нам надо... И вот тут нас
ждет полная засада, т.к. FFBF свободных байтов у нас
нет :( Самое главное - нам надо писать в такую
область памяти, которая не будет использоваться
программой после распаковки. Единственная подобная
область - секции армадилы. В .text1 писать нельзя -
там код армадилы, да и доступа на запись в эту
секцию нет (хотя это и поправимо). .adata в принципе
подходит, но я решил туда не писать. .data1 - импорт
арме нужен долго, полетим 100%. .reloc1 - может
потом пригодится, да и места там в притирочку. Будем
писать в .pdata. По размеру похоже, что здесь
находится запакованный код программы. Ну да
попробуем, т.к. 64 килобайта кода наверно уже будут
распакованы. В общем ставим бряк на запись DWORD PTR
DS:[3AE880]. Прервемся здесь:
00393DD7 A3 80E83A00 MOV DWORD PTR DS:[3AE880],EAX
00393DDC EB 10 JMP SHORT 00393DEE
00393DDE 83BD 68C6FFFF 00 CMP DWORD PTR
SS:[EBP-3998],0
00393DE5 75 07 JNZ SHORT 00393DEE
00393DE7 8325 80E83A00 00 AND DWORD PTR
DS:[3AE880],0
00393DEE 6A 40 PUSH 40
00393DF0 68 00200000 PUSH 2000
00393DF5 FFB5 64C6FFFF PUSH DWORD PTR SS:[EBP-399C]
00393DFB FF35 80E83A00 PUSH DWORD PTR DS:[3AE880]
00393E01 FF15 A4F13900 CALL NEAR DWORD PTR
DS:[39F1A4] ; kernel32.VirtualAlloc
00393E07 8985 6CC6FFFF MOV DWORD PTR
SS:[EBP-3994],EAX
00393E0D 83BD 6CC6FFFF 00 CMP DWORD PTR
SS:[EBP-3994],0
00393E14 74 33 JE SHORT 00393E49
00393E16 6A 40 PUSH 40
00393E18 68 00100000 PUSH 1000
00393E1D FFB5 64C6FFFF PUSH DWORD PTR SS:[EBP-399C]
00393E23 FF35 80E83A00 PUSH DWORD PTR DS:[3AE880]
00393E29 FF15 A4F13900 CALL NEAR DWORD PTR
DS:[39F1A4] ; kernel32.VirtualAlloc
00393E2F 8985 6CC6FFFF MOV DWORD PTR
SS:[EBP-3994],EAX
00393E35 83BD 6CC6FFFF 00 CMP DWORD PTR
SS:[EBP-3994],0
00393E3C 74 0B JE SHORT 00393E49
00393E3E 8B85 6CC6FFFF MOV EAX,DWORD PTR
SS:[EBP-3994]
00393E44 A3 80E83A00 MOV DWORD PTR DS:[3AE880],EAX
Здесь выделяется память под антидамп. Главная
строчка последняя. Останавливайтесь на ней и в ЕАХ
пишите 1EFE7000 (чтобы не париться и не переписывать
эти байты задом наперед в окне дампа данных). Теперь
ставим бряк на доступ на секцию .text и запускаем
программу, пропуская все бряки... До OEP вроде
дошли, крутим окно кода в самый верх и смотрим на
первый антидамп:
1ED01007 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
1ED0100D -E9 EE5F2E00 JMP VA_X.1EFE7000
1ED01012 93 XCHG EAX,EBX
Теперь он ведет внутрь библиотеки. Уже хорошо.
Теперь идем и смотрим, что нас ждет в антидампе:
1EFE7000 50 PUSH EAX
1EFE7001 44 INC ESP
1EFE7002 41 INC ECX
1EFE7003 54 PUSH ESP
1EFE7004 41 INC ECX
1EFE7005 3030 XOR BYTE PTR DS:[EAX],DH
1EFE7007 3000 XOR BYTE PTR DS:[EAX],AL
1EFE7009 0000 ADD BYTE PTR DS:[EAX],AL
1EFE700B F0:0300 LOCK ADD EAX,DWORD PTR DS:[EAX] ;
LOCK prefix is not allowed
1EFE700E 6217 BOUND EDX,QWORD PTR DS:[EDI]
1EFE7010 0200 ADD AL,BYTE PTR DS:[EAX]
1EFE7012 -78 DA JS SHORT VA_X.1EFE6FEE
1EFE7014 EC IN AL,DX ; I/O command
1EFE7015 BD 7F7C93D5 MOV EBP,D5937C7F
1EFE701A F5 CMC
1EFE701B 38FE CMP DH,BH
1EFE701D E4 47 IN AL,47 ; I/O command
1EFE701F DB40 ED FILD DWORD PTR DS:[EAX-13]
1EFE7022 9F LAHF
1EFE7023 D1FF SAR EDI,1
1EFE7025 8A45 AB MOV AL,BYTE PTR SS:[EBP-55]
1EFE7028 54 PUSH ESP
1EFE7029 45 INC EBP
1EFE702A AB STOS DWORD PTR ES:[EDI]
1EFE702B AE SCAS BYTE PTR ES:[EDI]
Полный отстой. Это совсем не то, что мы ожидали
здесь увидеть. Антидампа здесь нет :( Думать о
причинах не будем. Надо что-то делать. Думаем, что
делать... Для начала сдампим то, что сделали...
Теперь думаем... Надо где-то достать антидамп. В
принципе это не сложно, только прыги у нас будут ну
совсем не те... Короче задумка такая - прописать
антидамп где-нибудь, где он не затрется, а байты
возврата получить из расчета, что антидамп будет
начинаться с адреса 1EFE7000. Сам антидамп не
зависит от того, где он находится. Прыги возврата
наоборот напрямую зависят от этого. Теперь
подробнее:
Надо сделать, чтобы антидамп писался по такому
адресу, по которому он запишется и не испортится -
значит будем писать его туда, куда нам предложит
арма.
Следующий вопрос: "байты возврата получить из
расчета, что антидамп будет начинаться с адреса
1EFE7000" - Сейчас нам важны только байты возврата,
т.к. байты джампа в антидамп мы вроде как получили.
Итак, нас интересует лишь этот кусок генератора
(бинарно его копируем):
0039419F 8985 50C6FFFF MOV DWORD PTR
SS:[EBP-39B0],EAX
003941A5 8B85 28C6FFFF MOV EAX,DWORD PTR
SS:[EBP-39D8]
003941AB 8B8D 0CC7FFFF MOV ECX,DWORD PTR
SS:[EBP-38F4]
003941B1 8D4401 04 LEA EAX,DWORD PTR DS:[ECX+EAX+4]
; Куда вернуться
003941B5 8B8D 2CC6FFFF MOV ECX,DWORD PTR
SS:[EBP-39D4] ; Куда писать байты возврата
003941BB 8B15 80E83A00 MOV EDX,DWORD PTR DS:[3AE880]
003941C1 8D4C0A 04 LEA ECX,DWORD PTR DS:[EDX+ECX+4]
003941C5 2BC1 SUB EAX,ECX ; Считаем байты возврата
Получаются они вычитанием из адреса, куда вернемся,
адреса, откуда вернемся. "Куда вернемся" у нас
постоянен (в том смысле, что ImageBase один, поэтому
сдвиги относительно него всегда ведут туда же, куда
и в прошлый раз), а вот "откуда" - уже по разному,
т.к. винда выделяет разные участки памяти, а нам
нужно, чтобы это "откуда" (DWORD PTR DS:[3AE880])
было 1EFE7000, причем не постоянно, т.к. куда эти
байты возврата запишутся тоже рассчитывается из
этого числа, и следовательно, они опять попадут в ту
секцию и затрутся. Внимание! Вывод - надо подправить
процедуру генерации так, чтобы перед генерацией
байтов возврата в DWORD PTR DS:[3AE880] был
1EFE7000, а как только байты получены, DWORD PTR
DS:[3AE880]) = тому, что и предложила арма. А потом
мы скопируем полученный антидамп и вставим начиная с
адреса 1EFE7000. В общем все просто. Меняем
0039419F MOV DWORD PTR SS:[EBP-39B0],EAX
на
0039419F JMP ********
где ******** - подходящий адрес...
А там делаем так (бинарно вставляем генератор и
дописываем необходимые инструкции):
003B0000 C705 80E83A00 00>MOV DWORD PTR
DS:[3AE880],1EFE7000 ; ASCII "PDATA000"
003B000A 90 NOP
003B000B 90 NOP
003B000C 90 NOP
003B000D 90 NOP
003B000E 90 NOP
003B000F 90 NOP
003B0010 90 NOP
003B0011 90 NOP
003B0012 90 NOP
003B0013 90 NOP
003B0014 8985 50C6FFFF MOV DWORD PTR
SS:[EBP-39B0],EAX
003B001A 8B85 28C6FFFF MOV EAX,DWORD PTR
SS:[EBP-39D8]
003B0020 8B8D 0CC7FFFF MOV ECX,DWORD PTR
SS:[EBP-38F4]
003B0026 8D4401 04 LEA EAX,DWORD PTR DS:[ECX+EAX+4]
003B002A 8B8D 2CC6FFFF MOV ECX,DWORD PTR
SS:[EBP-39D4]
003B0030 8B15 80E83A00 MOV EDX,DWORD PTR DS:[3AE880]
003B0036 8D4C0A 04 LEA ECX,DWORD PTR DS:[EDX+ECX+4]
003B003A 2BC1 SUB EAX,ECX
003B003C 90 NOP
003B003D 90 NOP
003B003E 90 NOP
003B003F 90 NOP
003B0040 90 NOP
003B0041 90 NOP
003B0042 C705 80E83A00 00>MOV DWORD PTR
DS:[3AE880],1C20000
003B004C 90 NOP
003B004D 90 NOP
003B004E ^E9 1FECFEFF JMP 0039EC72
003B0053 90 NOP
(Не забудьте скопировать бинарно этот патч и
сохранить, он нам еще пригодится)
Джамп меняете на свой и 1C20000 меняете на то
значение, которое было у вас. Если вы его не
записали - перезапускайте и повторяйте шаги снова...
Жмем F9 и смотрим на результат... Нда... Опять
заглохла... Попробуем стереть патч и вернуть все как
было, остановившись сразу после завершения цикла.
Опять ошибка:( Можно провести эксперимент - заменить
в конце участка памяти пару байтов с 00 00 на 90 90,
а потом вернуть их обратно, и итог будет тот же.
Значит тут нас пасут, и значит тут нас ждет, верно,
облом:). Но не будем отчаиваться....
Еще немного пошевеля мозгами мы вспоминаем, что это
еще не запись байтов в антидамп, а лишь составление
таблицы. Потом арма пишет по адресу из таблицы байты
все из той же таблицы... Уловили мысль? Если нет -
программа слетает после любых изменений в ее коде. А
нам позарез нужна не стандартная таблица, а так
сказать "тюнингованная" под наши нужды. Если мы ее
делаем арма падает. Но перед слетом арма любезно
составляет нашу таблицу. Значит таблица-то у нас
есть. Ну а раз есть, что нам мешает заменить ей
таблицу в другой запущенной копии этой библиотеки,
чтобы та сделала из нее нормальный антидамп. Тут
никакие проверки целостности кода не помогут, т.к.
пропишем мы ее туда, где она и должна быть.
Внимание! Значит теперь делаем так - запускаем две
копии, доходим во второй до выделения памяти под
антидамп и записываем адрес начала участка памяти. В
первой копии доходим до самого-самого начала цикла
составления таблицы и делаем патч. Самое главное -
не забудьте заменить значение по адресу DWORD PTR
DS:[3AE880] тем, которое используется во второй
копии (Для тех, кто в танке - этот (первый)
экземпляр библиотеки после всех издевательств
работать не будет, он нужен нам только для
составления таблицы. Таблица рассчитана на другой
(второй) экземпляр библиотеки. Байты возврата из
антидампа пишутся по адресу, указанному в этой
таблице, сам адрес получается путем несложных
вычислений из адреса, с которого расположен в памяти
антидамп. Причем при каждом запуске этот адрес
меняется. Если мы его не изменим перед расчетом, в
таблицу внесутся адреса, рассчитанные на первый
экземпляр библиотеки, потом байты будут записаны по
этим адресам, а они наверняка не совпадают с
адресами участка памяти под антидамп во втором
экземпляре, поэтому последует неожиданное исключение
из-за записи в несуществующую память и арма опять
нас кинет, ну а если память существует, все равно
антидамп получится не рабочий, то есть без возвратов
в библиотеку (т.к. куда надо байты не записались)
Если что-то непонятно дочитайте до конца этот тутор
и перечитайте его сначала, может и дойдет :) ). И в
патче тоже не забудьте этот адрес, заодно и джампы
проверьте, все ли туда, куда надо прыгают, а то мало
ли куда вернемся... Теперь ставим бряк за концом
цикла и F9. Новая таблица готова. Выделяем ее и
бинарно копируем (если не знаете, что выделять:
смотрим здесь (самое начало цикла)
003940C7 8985 00C7FFFF MOV DWORD PTR
SS:[EBP-3900],EAX
В EAX начало таблицы. Конец можно увидеть по
количеству байтов 00 и F0 и отсутствию привычных 1E
и ED). Отлично! Половину дела сделали.
Переходим ко второй копии - встаем после завершения
цикла заполнения таблицы(перед GetTickCount)(сейчас
таблица во второй копии сформированна, поэтому ее
можно без боязни заменять), и бинарно вставляем ту
таблицу, которую мы получили в первой копии, теперь
исправляем флаг нуля у GetTickCount( этот
GetTickCount не с проста - так они пытаются
проследить, трейсим мы программу или нет. Там идет
сравнение результата с BB8h (что в переводе на более
привычные числа означает 3000, или 3 секунды)), и
если больше, то нам будет плохо, запускаемся и...
Опять падаем :((( Но тут вряд ли проверки на
изменение кода виноваты, ведь мы изменили то, что
изменять можно. Значит наверняка это связано с
GetTickCount. Все искать глупо. Тогда поступим
следующим образом - опять запускаем 2 копии, у
второй на первом исключении ищем вызов GetTickCount
и смотрим в дизассемблере, что там делается...
77E7A29B > BA 0000FE7F MOV EDX,7FFE0000
77E7A2A0 8B02 MOV EAX,DWORD PTR DS:[EDX]
77E7A2A2 F762 04 MUL DWORD PTR DS:[EDX+4]
77E7A2A5 0FACD0 18 SHRD EAX,EDX,18
77E7A2A9 C3 RETN
Было так... А сделаем вот так...
77E7A29B > BA 0000FE7F MOV EDX,7FFE0000
77E7A2A0 8B02 MOV EAX,DWORD PTR DS:[EDX]
77E7A2A2 B8 FFFFFFFF MOV EAX,-1
77E7A2A7 90 NOP
77E7A2A8 90 NOP
77E7A2A9 C3 RETN
Теперь нам не будут докучать проверки на
трассирование кода :)
Теперь повторим описанное выше...
После вставления таблицы во вторую копию ставим бряк
на доступ на секцию .text и пропускаем все
исключения и с замеранием сердца ждем... Прерываемся
на OEP, что оЧень радует. Крутим в самый верх и
видим, что антидамп направлен куда-то, но не в наш
файл - это понятно. Заходим туда, антидамп в норме,
но вот адреса возврата... они идут в никуда - что
тоже понятно и уже обнадеживает (кто не понял -
перечитываем ту часть, где рассказывается о
получениях байтов возврата и о получении
"тюнингованной" таблицы). Теперь бинарно скопируем
весь код антидампа, откроем тот дамп, в котором
прыги антидампа перенаправлены внутрь библиотеки в
секцию .pdata(надеюсь, Вы его еще не удалили;),
выделим начиная с адреса 1EFE7000 FFFF байт и
бинарно вставим. И... о чудо, мы видим следующее:
01E1000E 71 00 JNO SHORT 01E10010
01E10010 75 02 JNZ SHORT 01E10014
01E10012 75 41 JNZ SHORT 01E10055
01E10014 7A 00 JPE SHORT 01E10016
01E10016 66:87F3 XCHG BX,SI
01E10019 8BF1 MOV ESI,ECX
01E1001B 57 PUSH EDI
01E1001C 897424 0C MOV DWORD PTR SS:[ESP+C],ESI
01E10020 -E9 ED0FEF1C JMP VA_X.1ED01012
01E10025 8D7E 48 LEA EDI,DWORD PTR DS:[ESI+48]
01E10028 C74424 18 00000000 MOV DWORD PTR
SS:[ESP+18],0
01E10030 51 PUSH ECX
01E10031 53 PUSH EBX
01E10032 73 00 JNB SHORT 01E10034
01E10034 5B POP EBX
01E10035 73 00 JNB SHORT 01E10037
01E10037 59 POP ECX
01E10038 8BCF MOV ECX,EDI
01E1003A -E9 EA0FEF1C JMP VA_X.1ED01029
01E1003F 8B5C24 20 MOV EBX,DWORD PTR SS:[ESP+20]
01E10043 C64424 18 01 MOV BYTE PTR SS:[ESP+18],1
01E10048 -E9 EE0FEF1C JMP VA_X.1ED0103B
01E1004D 66:87CB XCHG BX,CX
01E10050 57 PUSH EDI
01E10051 66:87F3 XCHG BX,SI
01E10054 0FCF BSWAP EDI
01E10056 0FCB BSWAP EBX
01E10058 79 02 JNS SHORT 01E1005C
01E1005A 79 03 JNS SHORT 01E1005F
01E1005C 0FCB BSWAP EBX
01E1005E 0FCF BSWAP EDI
01E10060 66:87F3 XCHG BX,SI
01E10063 5F POP EDI
01E10064 66:87CB XCHG BX,CX
01E10067 C64424 18 02 MOV BYTE PTR SS:[ESP+18],2
01E1006C -E9 0110EF1C JMP VA_X.1ED01072
Антидамп цел, а прыги возврата идут именно туда,
куда надо. Теперь “Copy to executable file”,
сохраняем этот файл как VA_X.DLL. Восстанавливаем
импорт, запустив дамп с исправленным IAT и натравив
на него ИмпРек. Не забудьте поправить свойства
секций. Заменяем старый VA_X.DLL распакованным,
запускаем студию и... Начинаем ломать... ;) Надеюсь
это вы сделаете сами. Если нет - пишите, может
помогу :)
</Antidump Fixed>
Программа распакована...
Юппииииии, вот теперь действительно все. Всем
спасибо за внимание.
P.S.:
Если Вы дошли до этого места и библиотека и у Вас
распакована - значит я не зря карпел над этой
статьей. Если у Вас что-то не получилось - не
спешите слать мне гневные письма - перечитайте и
переделайте все с нуля. Читая эту статью не
стремитесь слепо следовать всем шагам, лучше
перечитайте ее несколько раз, параллельно
рассматривая работу АРМы в OllyDebug, главное -
поймите, какие основные методы были применены для
распаковки и как мы к ним пришли, старайтесь понять
ход мыслей автора. Лишь после этого распаковывайте
сами, изредка глядя в этот тутор за разъяснением
непонятных мест, хотя объем его не столь велик,
чтобы осветить хотя бы сотую часть работу АРМы.
Тогда Вы действительно научитесь распаковывать
армадиллу (правда только с этими настройками) на
любой программе.
PS Большое спасибо Sne за подсунутую программу и
моральную поддержку :)
|