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

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


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

Регистрируем Advanced PDF Password Recovery 1.48

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

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

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

Это интересный пример, того как ограничение пробной версии писал один человек, а саму защиту делал другой, вероятно очень стараясь, т.к. кешами-мЭшами и прочими пакостями от последней несет за версту... При этом они вероятно друг друга в глаза не видели... Кар0че...

Инструменты:

сосулька под любую винду
LordPE или PETools для снятия дампа
ImpRec для восстановления импорта
IDA ясен красен...
какой-нить двоичный редактор
мозги... да ну их... вот, автопилот - другое дело, я пользуюсь версией 2,5л. ;)

Уровень: для начинающих

Значится, ш0 это така за беда. Вы когда нибудь видели PDF-ники, с которых нельзя ни текст скопировать, ни картинку, ни распечатать нихрена... вот, вот и мне иногда попадаются... ой, ля! - материшся... а жо паделаеш - приходилось делать принт_скрин да и распознавать потом то что надо. Морока да и только. Дык, вот эта програмулина раскриптовывает такие PDF-ники, так что с них потом можно копировать все, что угодно. Вообщем полезная даже очень! Советую обязательно скачать, размер всего около 900Кб, здесь www.elcomsoft.com/apdfpr.html. Там есть два варианта проги, это APDFPR Professional 2.12 и просто APDFPR 1.48, о которой щас речь и идет. Эта версия может раскриптовывать только PDF-ники, которые защищены паролем уровня "owner" (типа внутренний), но не шарит если PDF-ник защищен паролем уровня "user_level", который надо подбирать брут-форсом. Что и делает версия Pro. Скажу честно - ни разу не видел PDF-ника с "user_level" уровнем :)... Я так понимаю там выскакивает мессага, ш0то типа "Введи пароль, юзер"... ну, да ладно. Версия 1.48 - последняя на момент выхода статьи. В незареганой версии расшифровываются только 10% процентов от общего числа страниц в PDF-нике, остальные просто делаются пустыми, и не обрабатываются KDF-файлы размером больше 100Кб... Ну, ну...

PEiD нам конечно же показывает ASProtect 1.23 RC4 build 1.3.08.24. Видать там все сидят на аспирине... Будет распаковывать.

1. Распаковка
2. Восстановление импорта
- переходник 1
- переходник 2
- переходник 3
- переходник 4
- переходник 5
- переходник 6
- переходник 7
- переходник 8
3. Описание защиты
4. Взлом
5. Веселое послебредие

Распаковка:

Для начала определим размер и адрес таблицы импорта. Для этого просто запустим прогу и брякнемся на любой функции, например MessageBoxA. Выйдем по F12 и поставим бряк прямо перед вызовом, еще раз брякнемся и зайдем внутрь по F8. Увидим такую картину вроде
 001B:004E1056 FF2578215300 jmp [ADVAPI32!RegSetValueExA]
 001B:004E105C FF25B8235300 jmp [KERNEL32!CloseHandle]
 001B:004E1062 FF25BC235300 jmp [005323BC]
 001B:004E1068 FF25C0235300 jmp [005323C0]
 001B:004E106E FF25C4235300 jmp [005323C4]
 001B:004E1074 FF25C8235300 jmp [005323C8]
 001B:004E107A FF25CC235300 jmp [KERNEL32!CreateFileA]
 001B:004E1080 FF25D0235300 jmp [005323D0]
 001B:004E1086 FF25D4235300 jmp [005323D4]
 
Это переходники на Win API и не только, которые заделал аспирин. Адреса относительно которых происходит переход и есть таблица импорта. Проскроллим вниз (Page Down) до тех пор пока эти джампы не закончатся, это будет хорошо видно (ну, то что они закончились). Последний косвенный джамп содержит последний адресс таблицы импорта. Он то нам и нужен. Если сосулька будет показывать символьное имя, тогда посмотрим на опкод джампа - FF2548315300. Отбросим первые два байта, останется 48315300. Это тот косвенный адрес только перевернутый, поэтому 00533148 - последняя ячейка в таблице импорта, содержащая адрес API функции. Теперь нужна первая ячейка. Аналогично скроллим вверх (бывает сосулька не хочет Page Up выполнять, поэтому попустите ее немного "Ctrl + [стрелка вниз]", а потом "Ctrl + [стрелка вверх]"), пока джампы не закончатся. Берем первый джамп и получаем адресс 0053214C. Итого выходит: начало таблицы импорта 0053214C, размер (00533148 + 4) - 0053214C = 0x1000 или 0x400 адресов (ведь адресс занимает 4 байта). Прибавлять 4 надо, т.к. последний адресс нужен включительно. Посмотрим на кусок импорта
 001B:0053265C 71A93E5D 71A9D755 71A92BBF 71A91746 <- API
 001B:0053266C 71A912F8 71AB1020 71A91AF4 71A93C22 <- API
 001B:0053267C DF907A3D 09A0C201 2B0D0216 8CC5DB93 <- мусор
 001B:0053268C 378CB851 FD869B89 72FC1626 72FC71DF <- API
 001B:0053269C 72FC25E1 72FC716C D711412A 3CD199FE \
 001B:005326AC 638757A3 A0A6971A 9B625B05 81482A9A |
 001B:005326BC 3F43F882 8B8F79F6 B2782AD0 66DBC281 |
 001B:005326CC 0ACACF0C 3DCBB47A 97964976 D940D3EF |<- мусор
 001B:005326DC 0F3E25FD 51B2F46D 3DE3E597 3FD9C082 |
 001B:005326EC 4779CA67 8FA24E10 801EB4BA DE6FCA6B |
 001B:005326FC CFC711D4 60B32D4F 9B47DC80 5BD6039E /
 001B:0053270C 578FB283 00A50D24 00A50D34 00A50D44 \
 001B:0053271C 00A50D54 00A50D64 00A50D70 00A50D7C |
 001B:0053272C 00A50D88 00A50D94 00A50DA8 00A50DB4 |<- переходники на API
 001B:0053273C 00A50DC4 00A50DD4 00A50DE4 00A50DF4 |
 001B:0053274C 00A50E04 00A50E14 00A50E24 00A50E34 |
 001B:0053275C 00A50E44 00A50E54 00A50E64 00A50E74 /
 001B:0053276C 7195F078 00A50E84 66C64588 2E1DB02D <- мусор
 001B:0053277C 9DB4BDBF 29D8CD9E 763822B3 76388BD6 <- API
 
помимо адресов API (под 2k, XP значения адресов системных API обычно начинаются с 7), видны переходники на API заделаные аспирином (адреса вроде 00A50DF4). С большинством из них разберется ImpRec, с некоторыми придется вручную. А главное - куча мусора, значения вроде 23454221 и т.п. Если такую таблицу загнать в ImpRec, то он выдаст нам более 500 адресов с которыми надо возится... если вообще что-то выдаст... Поэтому сначала очистим импорт от мусора и паралельно снимем дамп проги. Для этого берем PETools, выбирает PE Editor -> Break&Enter (в сосулке предварительно набрали bpint 3) жмем OK... упали... набрали eb eip 68, протрассируем немного по F8 пока не увидим команду PUSHAD, не проходя ее наберем bpmd esp-4 (упаковщик сохраняет состояние всех регистров, а восстановит только перед самой OEP, там и упадем... правда так бывает не всегда, но в данном случае нам повезло :), еще раз F8. Теперь надо брякнутся там где начинается заполнение импорта. Для аспирина есть стандартный метод. Ставим бряк bpx MapViewOfFile... F5 отпустили... упали, удалили этот бряк. Теперь bpx GetProcAddress. Упали... Для предыдущих версий здесь и начиналось заполнение импорта, т.е. после F12(три раза) мы увидим код, который приведен ниже. Для данной версии надо еще раз брякнутся (т.е. второй GetProcAddress после MapViewOfFile, а вот под 98й - все равно один... хотя это не важно) и уже потом три раза F12...
 001B:00A435F3 mov edx, [edi] <- мы здесь
 001B:00A435F5 mov [edx], eax <- адресс API заносится в таблицу импорта
 001B:00A435F7 jmp 00A43602
 001B:00A435F9 mov eax, 0A410ACh
 001B:00A435FE mov edx, [edi]
 001B:00A43600 mov [edx], eax
 001B:00A43602 add dword ptr [edi], 4
 001B:00A43605 pop edi
 001B:00A43606 pop esi
 001B:00A43607 pop ebx
 001B:00A43608 mov esp, ebp
 001B:00A4360A pop ebp
 001B:00A4360B retn 0Ch
 
а, теперь удалим весь мусор в таблице. Запомните несколько последующих команд и наберите в сосулке следующее
 a eip
 00A435F3: pushad
 00A435F4: rep
 00A435F5: stosd
 00A435F6: popad
 00A435F7: [enter]
 [F8]
 r eax 0
 r edi 0053214C
 r ecx 400
 
Мы просто заполним весь импорт нулями. Не забыли конечно же после popad поставить бряк, ведь нормальный ход проги надо восстановить, а также проверьте флаг направления FD, он должен быть сброшен. Ну, конечно в IceExt есть фишка !loadfile, но че то она у меня не работает. Отпустили, упали после popad - теперь у нас чистый таблица... Отгребем назад и восстановим исходный код
 r eip eip-4
 a eip
 00A435F3: mov edx, dword ptr [edi]
 00A435F5: mov dword ptr [edx], eax
 [F5]
 
Тепер после некоторого раздумья сработает тот первый бряк (bpmd esp-4), мы окажемся перед оригинальной точкой входа проги (OEP)
 001B:00A5547A mov [esp+1Ch], eax
 001B:00A5547E popad <- мы здесь
 001B:00A5547F push eax <- OEP
 001B:00A55480 retn <- ret на OEP
 
дойдем до retn и зациклим прогу (первый dword в стеке - адресс OEP 00401000)
 a eip
 00A5547F: jmp eip
 [F5]
 
Дамп снимем PETools'ом (правое меню мыши -> Dump Full, в опциях должны быть установлены флажки "paste header from disk" и "fix header"). Кстати, посмотрим теперь на наш импорт
 001B:0053265C 71A93E5D 71A9D755 71A92BBF 71A91746
 001B:0053266C 71A912F8 71AB1020 71A91AF4 71A93C22
 001B:0053267C 00000000 00000000 00000000 00000000
 001B:0053268C 00000000 00000000 72FC1626 72FC71DF
 001B:0053269C 72FC25E1 72FC716C 00000000 00000000
 001B:005326AC 00000000 00000000 00000000 00000000
 001B:005326BC 00000000 00000000 00000000 00000000
 001B:005326CC 00000000 00000000 00000000 00000000
 001B:005326DC 00000000 00000000 00000000 00000000
 001B:005326EC 00000000 00000000 00000000 00000000
 001B:005326FC 00000000 00000000 00000000 00000000
 001B:0053270C 00000000 00A50D24 00A50D34 00A50D44
 001B:0053271C 00A50D54 00A50D64 00A50D70 00A50D7C
 001B:0053272C 00A50D88 00A50D94 00A50DA8 00A50DB4
 001B:0053273C 00A50DC4 00A50DD4 00A50DE4 00A50DF4
 001B:0053274C 00A50E04 00A50E14 00A50E24 00A50E34
 001B:0053275C 00A50E44 00A50E54 00A50E64 00A50E74
 001B:0053276C 7195F078 00A50E84 00000000 00000000
 001B:0053277C 00000000 00000000 763822B3 76388BD6
 
... чистота-чистотайд, внатуре :)

Зацикленную прогу не киляйте, т.к. по ходу восстановим таблицу импорта. Запускаем ImpRec, выбираем наш процесс apdfpr.exe, в поле OEP вводим rva точки входа -> 00401000 - 00400000 = 00001000, в поле RVA - rva найденной таблицы импорта 0053214C - 00400000 = 0013214C, в поле Size размер последней в байтах - 1000. Жмем "Get Import". ImpRec покажет, что определено 506 импортируемых функций, а 161 значение - в непонятке... Тепер жмем "Auto Trace". ImpRec поколбасится, поколбасится да и выдаст нам, что неопределены всего лишь 8 функций, всего ничего. С ними пакалупаемся вручную.Для этого загоним получившийся дамп в ID'у (прописывать найденную OEP распакованной проги в дампе не надо, т.к. она оказалась равно точке входа запакованого файла). IDA конечно руганется пару раз из-за отсутствующего импорта, но нам нужны от нее сейчас только XREF'ы (перекрестные ссылки или как их там...). Теперь можно минут десять покумарится пока IDA закончит свое дело...

пошли покумарились... пришли... (лирическое отступление :прим.ред)

Жмем в ImpRec "Show Invalid". Попадаем на первый адресс с непонятным импортом - rva:00132418ptr:00A4158C. Т.е. в таблице импорта в ячейке с адресом 00532418 (c учетом imagebase 00400000) содержится адресс эдакой импортируемой процедуры 00A4158C, которая возвращает непонятный результат и вообще хрен знает что делает (для ImpRec непонятный)... Запустим экземпляр оригинальной проги,откроем сосульку, переключимся на контекст проги addr apdfpr и посмотрим че там делается - u 00A4158C

001B:00A4158C push ebp
001B:00A4158D mov ebp, esp
001B:00A4158F mov eax, dword [00A47CF0]
001B:00A41595 pop ebp
001B:00A41596 retn 4

просто возвращает, то что лежит по адресу 00A47CF0, а там какой-то адресс в стеке. Смотрим чеже там в стеке d *00A47CF0 и видим
 0023:00152360 5C3A4322 474F5250 317E4152 4450415C "C:\PROGRA~1\APD
 0023:00152370 7E525046 70615C31 72706664 6578652E FPR~1\apdfpr.exe
 0023:00152380 44222022 49535C3A 4D204543 61756E61 " "D:\SICE Manua
 0023:00152390 64702E6C 2D202266 00000062 00000000 l.pdf" -b.......
 
Значит возвращается адресс данной строки, т.е. эмулируется функция GetCommandLineA это очевидно. Теперь в ImpRec по рассматриваемому адресу rva:00132418 ptr:00A4158C клацните два раза, появится окно IMPORT EDITOR. В нем выберите библиотеку kernel32, а в ней функцию GetCommandLineA. OK. Функция восстановлена... Правда retn 4 означает, что в функцию передается какой-то параметр, а GetCommandLineA - без параметров. Это типа такая хитрость, если так оставить то ессно глюканется, поэтому найдем места где вызывается эта функция и запачим передаваемый параметр. Переключимся на дизасемблированный дамп нажмем G и введем адресс 00532418. Теперь [Ctrl + X] и [Enter]. Окажемся на знакомом косвенном джампе. Снова [Ctrl + X] увидим два места, где наша функция вызывается. В обоих случаях при вызове в стек ложится push eax, его то и надо удалить... Т.е. в дампе по смещениям 0x2E4D1 и 0xBAE56 надо поставить по одному nop'y (значение 0x90) воспользовавшись двоичным редактором.

Снова "Show Invalid". Видим следующий глючный адресс в импорте rva:00132424 ptr:00A41574. Переключаемся в сосульке на контекст проги, u 00A41574
 001B:00A41574 push 0
 001B:00A41576 call KERNEL32!GetModuleHandleA
 001B:00A4157B push dword [00A47CE0]
 001B:00A41581 pop eax
 001B:00A41582 mov eax, dword [00A47CF0] <- знакомо?
 001B:00A41588 retn
 
Хех, дык это-ж та же самая GetCommandLineA, только в данном случае со стеком все нормально и пачить ничего не надо... Смело выбираем в ImpRec GetCommandLineA. Вторая функция восстановлена.

Следующая функция rva:13242C ptr:00A4155C. Смотрим шо там u 00A4155C
 001B:00A4155C mov eax, dword [00A47CEC]
 001B:00A41561 retn
 
Возващается значение по адресу 00A47CEC. Запустите PETools и посмотрите значение напротив нашей проги в столбце PID, а потом на то что находится по 00A47CEC. Они совпадают. Можете перезапустить пару раз - результат не изменится, т.е. эмулируется функция GetCurrentProcessID.

Следующая функция rva:132474 ptr:00A41550. Там...
 001B:00A41500 push ebp
 001B:00A41501 mov ebp, esp
 001B:00A41503 mov eax, [ebp+8]
 001B:00A41506 test eax, eax
 001B:00A41508 jnz 00A4151D
 001B:00A4150A cmp dword [00A47970], 00400000
 001B:00A41514 jnz 00A4151D
 001B:00A41516 mov eax, dword [00A47970]
 001B:00A4151B jmp 00A41523
 001B:00A4151D push eax
 001B:00A4151E call KERNEL32!GetModuleHandleA
 001B:00A41523 pop ebp
 001B:00A41524 retn 4
 
Заглянем по адресу 00A47970, а там значение 00400000. Значит это просто функция GetModuleHandleA, ничего особенного. Восстанавливаем в ImpRec GetModuleHandleA...

Далее... rva:132484 ptr:00A410AС
 001B:00A410AC push ebp
 001B:00A410AD mov ebp, esp
 001B:00A410AF mov edx, [ebp+0Ch]
 001B:00A410B2 mov eax, [ebp+8]
 001B:00A410B5 mov ecx, dword [00A46474]
 001B:00A410BB mov ecx, [ecx]
 001B:00A410BD cmp ecx, eax
 001B:00A410BF jnz 00A410CA
 001B:00A410C1 mov eax, dword [edx*4 + 00A46350]
 001B:00A410C8 jmp 00A410D1
 001B:00A410CA push edx
 001B:00A410CB push eax
 001B:00A410CC call KERNEL32!GetProcAddress
 001B:00A410D1 pop ebp
 001B:00A410D2 retn 8
 
Подозрение здесь может вызвать только возможное возвращаемое значение mov eax, dword [edx*4 + 00A46350]. Поэтому перезапустив прогу упадем на оригинальной точке входа (OEP в смысле) и поставим бряк на эту строчку bpx 00A410C1. Поюзаем прогу. Бряк не сработает ни разу. Значит вся эта байда - просто переходник на GetProcAddress.

Некст... rva:001324С0 ptr:00A41528
 001B:00A41528 push dword [00A47CE0]
 001B:00A4152E pop eax
 001B:00A4152F retn
 
А вот по адресу 00A47CE0 находится непонятное значение 0A280105 (у меня такое под XP). Щя узнаем, что это такое. Упадем на точке входа проги (на EP упакованной проги). Выловим момент когда туда записывается значение. bpmd 00A47CE0. Увидим примерно следующее
 001B:00A413CC popad
 001B:00A413CD ret
 001B:00A413CE pop edx
 001B:00A413CF pop ebx
 001B:00A413D0 push 00A413D7
 001B:00A413D5 ret
 001B:00A413D6 jmp 009A5764 <- полиморфный глюк
 001B:00A413DA jmp edx <- мы здесь, а eax наше значение 0A280105
 001B:00A413DC popad
 001B:00A413DD ret
 
Из-за полиморфного кода сосулька не может правильно дизасемблировать текущий код поэтому сейчас непонятно что за команда записала данные по нашему адресу. Будем потихоньку отгребать назад пока не увидим, то что ищем r eip eip-1; r eip eip-1; r eip eip-1; ну, вот оно...
 001B:00A413CC popad
 001B:00A413CD ret
 001B:00A413CE pop edx
 001B:00A413CF pop ebx
 001B:00A413D0 push 00A413D7
 001B:00A413D5 ret
 001B:00A413D6 db E9h
 001B:00A413D7 mov [ebx-0Ah], eax <- !!!
 001B:00A413DA jmp edx
 001B:00A413DC popad
 001B:00A413DD ret
 
Заметте чуть выше команду push и ret, оттуда вероятно и попали сюда. Перезапускаем прогу, падаем на EP и ставим бряк на исполнение bpmb 00A413D0 x. Упадем. Сосулька нам покажет, что последняя ветвь с которой был переход на данный адресс это 00A41344. Но посмотрите, в регистре eax совсем не наше значение, а какое-то 00400000... Значит еще раз упадем здесь, теперь в eax наше значение. Последняя ветвь 00A41362 - там простой джамп сюда. Теперь после перезапуска bpmb 00A41362 x. Последняя ветвь оказалась 77E7C4B6. Делаем u 77E7C4B6 и сосулька показывает, что это был вызов KERNEL32!GetVersion. Значит восстанавливаем в ImpRec GetVersion

Предпоследняя функция ptr:00132530 rva:00A41564
 001B:00A41564 push ebp
 001B:00A41565 mov ebp, esp
 001B:00A41567 mov eax, dword [00A47CF0]
 001B:00A4156D mov eax, [ebp+8]
 001B:00A41570 pop ebp
 001B:00A41571 retn 4
 
Хм, просто возвращает переданый параметр. Херня какая-то... Просто удалим этот переходник и все, для этого в ImpRec выберите в правом меню "Cut Thunk". А в дампе запачим вызовы. Жмем в ID'е "G" там пишем 00532530 и [Enter]. [Ctrl + X] и [Enter], ну и снова [Ctrl + X]. Функция вызывается в двух местах и оба вызова вида push eax, call xxxx. Поэтому в дампе по смещениям 0x2E404 и 0xBAE28 надо поставить по шесть nop'ов. Ну, длина двух команд вместе push eax и сall xxxx составляет шесть байт.

И наконец, последняя функция (зае... уже) ptr:00132E20 rva:00A415B0
 001B:00A415B0 push ebp
 001B:00A415B1 mov ebp, esp
 001B:00A415B3 push ebx
 001B:00A415B4 mov ebx, [ebp+8]
 001B:00A415B7 mov eax, [ebp+18h]
 001B:00A415BA push eax
 001B:00A415BB mov eax, [ebp+14h]
 001B:00A415BE push eax
 001B:00A415BF mov eax, [ebp+10h]
 001B:00A415C2 push eax
 001B:00A415C3 push 5
 001B:00A415C5 mov eax, [ebp+0Ch]
 001B:00A415C8 push eax
 001B:00A415C9 push ebx
 001B:00A415CA call KERNEL32!FindResource
 001B:00A415CF push eax
 001B:00A415D0 push ebx
 001B:00A415D1 call KERNEL32!LoadResource
 001B:00A415D6 push eax
 001B:00A415D7 call KERNEL32!LockResource
 001B:00A415DC push eax
 001B:00A415DD push ebx
 001B:00A415DE call KERNEL32!DialogBoxIndirectParamA
 001B:00A415E3 pop ebx
 001B:00A415E4 pop ebp
 001B:00A415E5 ret 0014
 
А это просто DialogBoxParamA, сделайте u DialogBoxParamA и сравните - не правда ли похоже... Восстанавливаем в ImpRec последнюю ссылку как DialogBoxParamA (модуль user32.dll). Теперь жмем "Fix Dump" (влажок Add new section должен быть установлен) и выбираем наш дамп. ImpRec прикрутит к нему импорт и добавит к названию символ "_", получится Dumped_.exe.

Запускаем... - о чудо, работает! 8)...

Описание защиты:

Как, наверно, вы уже поняли из заголовка защиту мы просто обойдем убрав ограничения незарегистрированной версии. Так зачем же ее описывать? Главное потому, что я ковырял ее целый месяц и она меня очень(!) подкумарила... К тому же, если кто-то захочет тоже глянуть на защиту, то не будет парить мозги в поисках оной, а воспользовавшись моим описанием сразу перейдет к делу... Ну и соответственно можете пропустить эту главу. Буду краток.

При успешной регистрации прога кидает введенный вами рег. код в раздел реестра HKEY_LOCAL_MACHINE\SOFTWARE\Elcom\Advanced PDF Password Recovery\Registration. Значит создайте там строковый параметр с именем 'Code' и значением вроде '1238762873691237638476234234', по длиннее.

Надо сказать, что защита состоит из двух частей. Сначала из рег. строки высчитывается контрольная сумма и проверяется на корректность, а потом (если проверка завершилась успешно) из последней высчитывается адресс и размер памяти, куда (опять же с помощью этой контрольной суммы) начинают расшифровываться то ли код, то ли данные - я уже не разбирался... Так вот код, который выполняет вторую часть регистрации, находится далеко за пределами секций нашего рабочего дампа - в секции аспирина. Поэтому для исследования защиты надо сдампить данную область памяти и присабачить к нашему дампу как дополнительную секцию. Зациклите оригинальную прогу на OEP. Посмотрите какое значение находится у вас по адресу 004E5B60 (у меня 00A414A4). В PETools выберите правое меню -> "Dump Region" и сдампите тот регион памяти, который содержит этот адрес (у меня 00A30000 и размер 0001E000). Теперь загоните наш рабочий дамп в PEEditor, там зайдите в раздел секций (Tools -> PEEditor -> Sections). Новая секция должна мэппироваться по адресу с которого вы его сдампили, т.е. с 00A30000, поэтому последняя секция файла '.mackt' (секция, которую приделал ImpRec) должна заканчиваться на этом адресе, т.е. ее виртуальное смещение (Virtual Offset) + виртуальный размер (Virtual Size) + 00400000 (c учетом imagebase) = 00A30000, однако оно равно 00591000 (00191000). Соответственно можно увеличить виртуальный размер этой секции, но это немного запудрит ID'e мозги и она долго будет колбасится по этой пустой области в поисках чего-то (убедитесь сами)... Поэтому проще сначала сделать еще одну секцию неинициализированных данных, которая закончится на нужном адресе 00A30000, а потом прикрутить нашу сдампленную секцию. Для этого выберите правое меню -> "Add section" внизу добавится новая секция с названием '.uinC'. Выберите ее и в правом меню -> "Edit Section Header", настроим характеристики секции. Raw Size и Raw Offset сразу устанавливайте нулевыми, т.к. в файле данная секция не нужна. Virtual Offset PETools выставит автоматически, а Virtual Size высчитайте как 00A30000 - 00400000 - 00191000 = 0049F000 (понятно почему...). Зайдите в "Characteristics" и установите галочки "Readable", "Writeable", "Contains uninitialized data" (читаемая, записываемая, содержит неинициализированные данные). Секция настроена. Теперь правое меню -> "Load section from disk" выберите нашу сдампленную область памяти. Аналогично, "Name" - что хотите, "Virtual Size" - 0001E000, "Virtual Offset" - 00630000 (эти параметры PETools чего-то высчитывает неправильно...), "Raw Size" - 0001E000, "Raw Offset" - 00191000 (т.к. предыдущая добавленная секция в файле отсутствует, значит данная должна начинатся сразу после секции '.mackt'). "Characteristics" - E0000040. OK - секция прикручена. Только предварительно зайдите "Optional Header" и напротив поля "Size of Image" клацните "?" (размер всего образа exe-шника в памяти. PETools опять-же иногда не хочет автоматически его высчитывать, хотя в опцияx флажок "auto fix SizeofImage" установлен)... Запускаем - работает... Кстати все API-шные вызовы из новой секции происходят через собственную таблицу импорта, поэтому работать эта секция будет только на вашей системе, но ведь она нужна только для исследования защиты, поэтому это не есть проблема.

Итак при запуске прога считывает рег. строку и проверяет на корректность. Поставьте бряк bpx RegQueryValueExA if **(esp+8)=='Code' do "d *(esp+14)" упадете вот здесь
 seg000:00408F90 ;CODE XREF: sub_401534+93p
 seg000:00408F90 ;_TMain_butRegisterClick+3p
 seg000:00408F90
 seg000:00408F90 var_40 = dword ptr -40h
 seg000:00408F90
 seg000:00408F90     push    ebx
 seg000:00408F91     add     esp, 0FFFFFFC0h
 seg000:00408F94     cmp     dword [004E5CA4], 0 <- флаг регистрации
 seg000:00408F9B     jz      short loc_408FA4
 seg000:00408F9D     mov     eax, 1
 seg000:00408FA2     jmp     short loc_408FC4
 seg000:00408FA4     push    40h
 seg000:00408FA6     lea     edx, [esp+44h+var_40]
 seg000:00408FAA     push    edx
 seg000:00408FAB     call    sub_408AF4 <- мы здесь. тут просто считывается с реестра
 seg000:00408FB0     add     esp, 8      <- рег. строка
 seg000:00408FB3     push    eax
 seg000:00408FB4     call    sub_40CD20 ;процедура проверки
 seg000:00408FB9     pop     ecx
 seg000:00408FBA     mov     ebx, eax
 seg000:00408FBC     mov     dword [004E5CA4], ebx <- установка флага регистрации
 seg000:00408FC2     mov     eax, ebx    <- единица, если проверка прошла успешно
 seg000:00408FC4     add     esp, 40h
 seg000:00408FC7     pop     ebx
 seg000:00408FC8     retn
 
Сейчас только выставляется флаг регистрации и больше ничего. Установите его вручную и прога выдаст надпись, что запушена зарегистрированная версия и кнопка регистрации будет неактивной. Тут интересна сама проверка (до этого ничего существенного нет).
 seg000:0040CC1C sub_40CC1C  proc near    ; CODE XREF: sub_40CCDC+2Dp
 seg000:0040CC1C 
 seg000:0040CC1C var_418   = dword ptr -418h
 seg000:0040CC1C s1        = dword ptr -10h
 seg000:0040CC1C arg_0     = dword ptr  8
 seg000:0040CC1C arg_4     = dword ptr  0Ch
 seg000:0040CC1C arg_8     = dword ptr  10h
 seg000:0040CC1C 
 seg000:0040CC1C     push    ebp
 seg000:0040CC1D     mov     ebp, esp
 seg000:0040CC1F     add     esp, 0FFFFFBE8h
 seg000:0040CC25     push    ebx
 seg000:0040CC26     push    esi
 seg000:0040CC27     push    edi
 seg000:0040CC28     mov     edx, [ebp+arg_4]
 seg000:0040CC2B     mov     eax, [ebp+arg_0]
 seg000:0040CC2E     mov     edi, offset unk_52B284
 seg000:0040CC33     mov     esi, 10h
 seg000:0040CC38     test    eax, eax
 seg000:0040CC3A     jz      short loc_40CC40
 seg000:0040CC3C     test    edx, edx
 seg000:0040CC3E     jnz     short loc_40CC47
 seg000:0040CC40 loc_40CC40:
 seg000:0040CC40     xor     eax, eax
 seg000:0040CC42     jmp     loc_40CCD5
 seg000:0040CC47 loc_40CC47:
 seg000:0040CC47     lea     ecx, [ebp+s1]
 seg000:0040CC4A     push    ecx <- адрес в стеке
 seg000:0040CC4B     push    edx <- размер
 seg000:0040CC4C     push    eax <- адресс рег.строки
 seg000:0040CC4D     call    sub_40CB28 ;из рег. строки высчитывается
 seg000:0040CC52     add     esp, 0Ch   ;контрольная сумма в виде
 seg000:0040CC55     lea     edx, [ebp+s1] ;четырех dword'ов
 seg000:0040CC58     push    edx
 seg000:0040CC59     push    10h
 seg000:0040CC5B     lea     eax, [ebp+var_418]
 seg000:0040CC61     push    eax
 seg000:0040CC62     call    sub_411D05 ;создается таблица из 256 элементов
 seg000:0040CC67     add     esp, 0Ch   ;элементы последней меняются местами
 seg000:0040CC6A     xor     ebx, ebx   ;в зависимости от значений каждого
 seg000:0040CC6C loc_40CC6C:            ;байта контрольной суммы
 seg000:0040CC6C     push    edi
 seg000:0040CC6D     push    edi
 seg000:0040CC6E     push    8
 seg000:0040CC70     lea     eax, [ebp+var_418]
 seg000:0040CC76     push    eax
 seg000:0040CC77     call    sub_411E18 ;еще раз аналогичное преобразование
 seg000:0040CC7C     add     esp, 10h   ;данной таблицы, но элементы меняются
 seg000:0040CC7F     inc     ebx        ;местами уже в зависимости от
 seg000:0040CC80     cmp     ebx, 8     ;собственных значений, и так 8 раз
 seg000:0040CC83     jl      short loc_40CC6C
 seg000:0040CC85     xor     ebx, ebx
 seg000:0040CC87 loc_40CC87:
 seg000:0040CC87     mov     eax, ebx
 seg000:0040CC89     push    edi <- адресс в стеке для новых dword'ов
 seg000:0040CC8A     imul    esi
 seg000:0040CC8C     add     eax, offset unk_4E6AA8
 seg000:0040CC92     lea     edx, [ebp+var_418]
 seg000:0040CC98     push    eax <- адресс в таблице кешей (начало в 004E6AA8)
 seg000:0040CC99     push    esi <- размер 0x10
 seg000:0040CC9A     push    edx <- адресс преобразованной таблицы
 seg000:0040CC9B     call    sub_411E18  ;а теперь из этой таблицы вычисляются 
 seg000:0040CCA0     add     esp, 10h    ;новые четыре dword'а и делается xor
 seg000:0040CCA3     lea     ecx, [ebp+s1];со значениями из таблицы кешей
 seg000:0040CCA6     push    8   <- колличество байт для сравнения
 seg000:0040CCA8     push    edi <- последние вычисленые dword'ы
 seg000:0040CCA9     push    ecx <- исходные dword'ы (после sub_40CB28)
 seg000:0040CCAA     call    _memcmp <- ну, они должны совпасть...
 seg000:0040CCAF     add     esp, 0Ch
 seg000:0040CCB2     test    eax, eax
 seg000:0040CCB4     jnz     short loc_40CCCA
 seg000:0040CCB6     cmp     [ebp+arg_8], 0
 seg000:0040CCBA     jz      short loc_40CCC5
 seg000:0040CCBC     mov     eax, [ebp+arg_8]
 seg000:0040CCBF     mov     dword ptr [eax], 8
 seg000:0040CCC5 loc_40CCC5:
 seg000:0040CCC5     lea     eax, [edi+8]
 seg000:0040CCC8     jmp     short loc_40CCD5
 seg000:0040CCCA loc_40CCCA:
 seg000:0040CCCA     inc     ebx
 seg000:0040CCCB     cmp     ebx, 3A98h ;пока не переберется вся таблица кешей
 seg000:0040CCD1     jl      short loc_40CC87
 seg000:0040CCD3     xor     eax, eax
 seg000:0040CCD5 loc_40CCD5:
 seg000:0040CCD5     pop     edi
 seg000:0040CCD6     pop     esi
 seg000:0040CCD7     pop     ebx
 seg000:0040CCD8     mov     esp, ebp
 seg000:0040CCDA     pop     ebp
 seg000:0040CCDB     retn
 seg000:0040CCDB sub_40CC1C      endp
 
Тут все очень просто. Процедура call sub_40CB28 принимает первым параметром в стеке адресс рег. строки, вторым - размер, третьим - адресс в стеке, куда процедура высчитает четыре dword'a, своеобразную исходную контрольную сумму соответствующую нашей рег.строке. Каким образом вычисляется эти dword'ы абсолютно неважно. Все последующие вызовы не имеют дополнительных вложенностей и реализуют довольно простые (... но очень хитрые) алгоритмы, убедитесь в этом сами. Мне например понадобилось около дня, чтобы выучить эти алгоритмы на зубок и не заглядывать каждый раз в отладчик. Далее внутри call sub_411D05 создается таблица из 256 dword'ов с последовательными значениями (0,1,2,3... и т.д.). В зависимости от каждого байта нашей контрольной суммы ячейки данной таблицы просто меняются местами. В первом call sub_411E18 происходит аналогичное преобразование еще 8 раз (jl short loc_40CC6C). А далее в цикле (jl short loc_40CC87) из этой таблицы начинают вычислятся снова таки по четыре dword'a и делается операция xor со значениями из другой таблицы (похоже это и называется таблица кешей). В итоге полученные в результате четыре dword'a должны быть равны теми первыми, которые были вычислены после call sub_40CB28! Значит по идее любая исходная контрольная сумма, которая при проходе данных процедур не изменится, сможет зарегистрировать прогу... Ну и попробуйте подберите хоть одну такую сумму... У меня не получилось.

Ну, как я уже сказал, щас только выставляется флаг регистрации. Вторая часть регистрации происходит когда начинается расшифрование pdf-ника. Cнова вызывается эта процедура (00408F90). Удалите этот переход 0040CCB4 jnz short loc_40CCCA, как будто бы контрольные суммы совпали. Это был вызов здесь...
 seg000:00404ABF     call    sub_408F90 <- снова проверка на регистрацию
 seg000:00404AC4     test    eax, eax
 seg000:00404AC6     jz      short loc_404AF2
 seg000:00404AC8     call    sub_408A48 <- вторая часть регистрации
 seg000:00404ACD     jmp     loc_404AF0
 
Внутри call sub_408A48 снова происходит считывание рег. строки с реестра (call sub_408AF4) и вычисление контрольной суммы (call sub_40CCDC). Снова сделайте так, чтобы совпали контрольные суммы, соответсвенно вызов call sub_40CCDC возвратит единицу. Далее происходят косвенные вызовы как раз в сецию аспирина.
 seg000:00408A78     lea     ecx, [ebp+var_28]
 seg000:00408A7B     lea     eax, [ebp+var_68]
 seg000:00408A7E     push    ecx
 seg000:00408A7F     push    40h
 seg000:00408A81     push    eax
 seg000:00408A82     call    sub_408AF4 ;снова считывается рег. строка
 seg000:00408A87     add     esp, 8
 seg000:00408A8A     push    eax
 seg000:00408A8B     call    sub_40CCDC ;и производится вычисление 
 seg000:00408A90     add     esp, 8     ;контрольной суммы
 seg000:00408A93     test    eax, eax
 seg000:00408A95     jnz     short loc_408AA3
 seg000:00408A97     lea     edx, [ebp+var_24]
 seg000:00408A9A     push    edx
 seg000:00408A9B     call    sub_4DDE33
 seg000:00408AA0     pop     ecx
 seg000:00408AA1     jmp     short loc_408AED
 seg000:00408AA3 loc_408AA3:
 seg000:00408AA3     mov     ecx, [ebp+var_28]
 seg000:00408AA6     push    ecx
 seg000:00408AA7     push    eax
 seg000:00408AA8     call    dword [004E5B60] ;остальное происходит
 seg000:00408AAE     call    dword [004E5B58] ;внутри секции аспирина
 seg000:00408AB4     mov     [ebp+var_14], 0
 seg000:00408ABA     jmp     short loc_408ADA
 
Далее из высчитанной контрольной суммы вычисляется адресс и размер каких-то данных, которые опять же по данной сумме начинают расшифровываться и отправлятся по вычисленному адресу. Короче, если прога и не вылетает сразу (ну если адреса корректно подобрать)... то вылетает потом или просто ничего не делает.

Вот такая вот замучаная защита...

Взлом:

Тут такая история. Сначала мой друг Kaban дал мне версию 1.45. Парился я над ней месяц, как я уже говорил, и только потом допер, что нужно сделать. Решил глянуть, может новая версия есть - а там 1.47. За полдня убрал в ней ограничения и решил статью написать. Неделю писал (времени не было), почти уже все закончил, как решил заглянуть в инет... ядрен-батон, а там первого марта была зарелизена версия 1.48... Ну что, пришлось еще несколько дней потратить и переписать статью под эту версию. Это я вообще к тому, что на протяжении этих версий ни защита ни ограничения ни сколько не изменились и все, что написано выше и ниже относится к этим версиям на 100%... ну конечно адреса разные, а также переходники в каждой версии по разному расположены... Вероятно, когда вы будете читать эту статью, уже выйдет какая-нить версия 1.51, поэтому поставив бряк на считывание из реестра параметра 'Code' вы сразу же упадете возле защиты. А то, что написано ниже, вообще наверно будет относится к далеко последующим версиям, аж до какого-нить 1.82...

Вот же как бывает... - пилиш эту защиту, пилиш, только про нее и думаеш (прям как параноик), мыслей других вообще нет... и только в конце, зайдя в тупик, неожиданно приходят свежие идеи, настолько тривиальные, что аж смешно становится... (наверно каждый крэкер такое испытывал :)... Ядрен-батон, а что же такое 10 процентов от общего числа листов в pdf-нике? (напомню, столько расшифровывает незареганая версия). Это же примитивное деление на 10! Значит прога, где-то определяет общее колличество листов в pdf-нике и делит на десять. Надо просто найти этот первоисточник. Но с чего начать, надо же за что то зацепится? А очень просто - прога после расшифрования pdf-ника выдает MessageBox 'WARNING! Because you're using a trial version of APDFPR, only 19 of 190 page(s) of the document have been decrypted...' и т.д. (кстати враза 'Заходи ко мне в гости' имеет синонимичный смысл с последней...).

Как вы уже догадались надо просто определить из какой переменной в эту строку было занесено число 19. Ну и от туда двигаться к первоисточнику, где это число 19 высчитывается. Долго расписывать не буду, а то уже зае... Для обьеденения строки вроде 'hello %d' и числа (вместо значка %d) используется функция wsprintfA. Поэтому не долго думая
 seg000:00404CE7     mov     eax, [ebp+var_3C] <- общее кол. страниц (190)
 seg000:00404CEA     cmp     eax, [ebp+var_38] <- расшифрованое кол. (19)
 seg000:00404CED     jz      short loc_404D4C
 seg000:00404CEF     mov     edx, [ebp+var_3C]
 seg000:00404CF2     cmp     ds:dword_4E3230, 0
 seg000:00404CF9     push    edx <- число 190 ложится в стек
 seg000:00404CFA     mov     ecx, [ebp+var_38]
 seg000:00404CFD     push    ecx <- число 19 ложится в стек
 seg000:00404CFE     jz      short loc_404D07
 seg000:00404D00     mov     eax, ds:dword_4E3230
 seg000:00404D05     jmp     short loc_404D0C
 seg000:00404D07 loc_404D07:
 seg000:00404D07     mov     eax, 4E4C8Ah
 seg000:00404D0C loc_404D0C:
 seg000:00404D0C     push    eax <- строка 'WARNING! Because you're...'
 seg000:00404D0D     lea     edx, [ebp+var_244]
 seg000:00404D13     push    edx <- адресс куда будет записан результат
 seg000:00404D14     call    wsprintfA
 
немного выше
 seg000:00404C49     mov     ecx, [ebp+var_248] <- расшифрованое кол. (19)
 seg000:00404C4F     mov     [ebp+var_38], ecx
 
var_248 получило значение здесь
 seg000:0040BBD6     mov     ecx, [ebp+arg_10] <- адресс var_248
 seg000:0040BBD9     mov     eax, [ebp+var_C]  <- расшифрованое кол. (19)
 seg000:0040BBDC     mov     [ecx], eax
 
ну, а теперь самое интересное, var_C получило значение здесь
 seg000:0040B875     mov     eax, [ebp+arg_10] <- общее кол. страниц (190)
 seg000:0040B878     add     eax, 9
 seg000:0040B87B     mov     ecx, 0Ah
 seg000:0040B880     cdq
 seg000:0040B881     idiv    ecx
 seg000:0040B883     mov     edx, [ebp+arg_C] <- адресс переменной var_C
 seg000:0040B886     cmp     eax, [edx]
 seg000:0040B888     jle     short loc_40B891 <- типа, норма в 10% выполнена
 seg000:0040B88A     mov     eax, [ebp+arg_C]
 seg000:0040B88D     inc     dword ptr [eax]
 seg000:0040B88F     jmp     short loc_40B8A7 <- следующая итерация цикла
 
Вау.... !!! Угадайте, что будет если с адреса 0040B878 по адресс 0040B883 все забить nop'ами ;)... (Кстати, здесь мне действительно стало смешно).

Но всю картину портит сообщение о незарегистрированной версии при запуске проги и активная кнопочка регистрации... Значит просто вызов 00408F90 (проверка регистрации) запачим следующим образом
 seg000:00408F90 mov dword [004E5CA4], 1
 seg000:00408F9A mov eax, 1
 seg000:00408F9F ret
 
А чтобы прога при работе, определив свою зареганость, не пошла путем зарегистрированной программы просто сделаем условный переход безусловным
 seg000:00404ABF     call    sub_408F90 <- проверка на регистрацию
 seg000:00404AC4     test    eax, eax
 seg000:00404AC6     jz      short loc_404AF2
 seg000:00404AC8     call    sub_408A48 <- вторая часть регистрации
 seg000:00404ACD     jmp     loc_404AF0
 
заменить на
 seg000:00404AC6     jmp     short loc_404AF2
 
Ну а что касается kdf-файла больше 100Кб дак это вот здесь
 seg000:00401900     push    ecx ; lpFileName
 seg000:00401901     call    sub_40D504 <- сканирование на kdf формат
 seg000:00401906     pop     ecx
 seg000:00401907     test    eax, eax
 seg000:00401909     jz      short loc_401920 <- джамп если не kdf
 seg000:0040190B     push    0
 seg000:0040190D     push    0
 seg000:0040190F     lea     eax, [ebp+FileName]
 seg000:00401915     push    eax
 seg000:00401916     call    sub_40CF08 <- обработка kdf файла
 
а внутри call sub_40CF08
 seg000:0040CFE7     call    GetFileSize
 seg000:0040CFEC     cmp     eax, 19000h <- или 100Кб
 seg000:0040CFF1     jbe     loc_40D07C
 
Интересно чтобы это значило... Итак окончательный патч:
 ;-------- для импорта ----------
 0x2E4D1 - 90
 0xBAE56 - 90
 0x2E404 - 90,90,90,90,90,90
 0xBAE28 - 90,90,90,90,90,90
 ;---- для работоспособности ----
 0x0B878 - 90,90,90,90,90,90,90,90,90,90,90
 0x08F90 - C7,05,A4,5C,4E,00,01,00,00,00,B8,01,00,00,00,C3
 0x04AC6 - EB
 0x0CFEC - 3D,FF,FF,FF,FF
 ;-------------------------------
 
Теперь можно упаковать прогу каким-нить upx'ом и юзать. Ну кто теперь скажет, что она не зарегистрированная... Нн-да... Недолог был путь самурая к источнику жизненой силы...

Веселое послебредие:

... надеюсь, я тут не слишком много воды налил :)

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

Обсуждение статьи: Регистрируем Advanced PDF Password Recovery 1.48 >>>


Комментарии к статье: Регистрируем Advanced PDF Password Recovery 1.48

DFC 26.11.2004 18:47:22
Godness, ты классный чел, мне твоя статья понравилась и твое упорство.
Good Luck:)
---
Manovar 01.05.2005 21:14:35
Godness, так держать. А чувак прогу мог бы и подарить всем хорошим людям.
---

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



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


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