Автор: (c)Крис Касперски ака мыщъх
Обучаться взлому лучше всего на простых защитах - таких как, например, архиватор WinRAR, который мы сегодня захачим по полной программе, заодно научившись пользоваться hex-редактором, API-шпионом и дизассемблером, а с отладчиком познакомимся в следующий раз.
Свежескаченная версия WinRAR'а работает 40 дней, а потом вопит о регистрации, выбрасывая противный NAG-screen при каждом запуске, что очень напрягает и возникает естественное желание его отломать.
Мы будем хачить последую стабильную версию 3.42, на которую ведет ссылка http://www.rarsoft.com/rar/wrar342.exe. Все остальные ломаются аналогичным образом, только смещения "защитных" байт будут другими.
Рисунок 1. Противный NAG-Screen.
Еще нам понадобиться любой HEX-редактор (например, HIEW), API-шпион Kerberos, дизассемблер IDA Pro и редактор ресурсов (например, Microsoft Visual Studio). Различные версии hiew'а имеют различную раскладку "горячих" клавиш, что создает некоторую путаницу. Мы будем использовать бесплатную версию 6.04 без функциональных ограничений. Последние версии hiew'а распространяются на коммерческой основе, а коммерция и хакерство несовместимы!
Всякий диалог выводится не сам по себе (это ведь не кошка), а отображается некоторой API-функцией. Перехватив API-функцию, выводящую NAG, мы сможем дизассемблировать защитный код, который ее вызывает, и проанализировать условия, определяющие появление NAG'а на экране.
Существует множество API-функций, связанных с диалогами: CreateDialog, DialogBox, MessageBox и т.д. Какую из них использовал разработчик RAR'а? Чтобы не гадать, воспользуемся API-шпионом. Он все покажет. Только сначала настроим фильтр, чтобы Kerberos отбрасывал малоинформативные API-вызовы, захламляющие файл отчета. Откроем ke_spy.txt и закомментируем следующие функции: TlsGetValue, DefWindowProcA, DispatchMessageA, GetFocus, GetMessageA, SendMessageA, SendMessageW, TranslateAcceleratorA, TranslateAcceleratorW и TranslateMessage (чтобы закомментировать функцию, перед ее именем вставляется знак ';'). Для усиления фильтрации имеет смысл зайти в Опции (кнопка "options") и взвести флажок "report only .exe calls", чтобы собирать API-вызовы только из winrar.exe, но не из загружаемых им DLL. Если этого не сделать, ничего страшного не произойдет, но файл отчета получится слишком большой и удручающе ненаглядный.
Рисунок 2. Настройка фильтра перед запуском шпиона.
Теперь нажимаем Browse, указываем путь к RAR'у и давим Inject. Дождавшись появления NAG'а на экране, выходим из RAR'а и открываем файл отчета WinRAR.rep, находящийся в одном каталоге с RAR'ом.
Рисунок 3. API-шпион kerberos, готовый к работе.
Изучение файла-отчета (см. листинг 1) лучше начинать с конца (ведь NAG-screen появляется в последнюю очередь, когда основной интерфейс уже инициализирован). Только слепой не обнажит вызов функции DialogBoxParamA, выводящей диалог с грозным именем "REMINDER" (то есть, "напоминатель"). Она-то этот противный диалог и создает. Да... с фантазией у разработчиков защит всегда были напряги.
WinRAR.exe|0044B030|LoadAcceleratorsA(00400000, 00496BA8: "VIEWACC") returns: 001E006F WinRAR.exe|00440F73|DialogBoxParamA(400000,495FE1:"REMINDER",70094,444FF4,0) returns:0 WinRAR.exe|00440F9B|WaitForSingleObject(00000110, 0000000A) returns: 00000102
Листинг 1. Фрагмент файла отчета.
Kerberos (вот умница!) даже сообщает адрес возврата из функции - 440А73h, ведущий прямо к защитному коду. Заглянем сюда дизассемблером? Загружаем winrar.exe в IDA PRO и давим <G> (Jump to address), "440A73", <Enter>.
Рисунок 4. Защитный код, исследуемый в дизассемблере IDA PRO.
Отчетливо виден вызов DialogBoxParamA, выше которого находится следующий дизассемблерный код:
00440F1D cmp dword_4B3A90, 0 00440F24 jnz short loc_440F73 00440F26 cmp byte_495A60, 0 00440F2D jnz short loc_440F73 00440F2F cmp byte_4B7E00, 0 00440F36 jnz short loc_440F73 00440F38 cmp byte_49F9BC, 0 00440F3F jnz short loc_440F73 00440F41 mov eax, dword_004B43C8 00440F46 cmp eax, 28h 00440F49 jg short loc_440F4F 00440F4B test eax, eax 00440F4D jge short loc_440F73 00440F4F 00440F4F loc_440F4F: ; CODE XREF: sub_4408C8+681^j 00440F4F mov byte_495A60, 1 00440F56 push 0 ; dwInitParam 00440F58 push offset sub_444FF4 ; lpDialogFunc 00440F5D push dword_4B161C ; hWndParent 00440F63 push offset aReminder ; lpTemplateName 00440F68 push hLibModule ; hInstance 00440F6E call DialogBoxParamA 00440F73 loc_440F73: ; CODE XREF: sub_4408C8+65C^j 00440F73 ; sub_4408C8+665^j ... 00440F73 cmp dword_4B3A90, 0
Листинг 2. Дизассемблерный листинг защитного механизма (фрагмент).
Как мы видим, функция DialogBoxParamA вызывается, когда выполняется условный переход: cmp eax, 28h/jg loc_440F4F (прыжок, если eax > 28h). В десятичной системе 28h равно 40. Это и есть срок демонстрационного периода, положенный нам по праву. Теперь становится понятен "физический" смысл переменной dword_004B43C8, содержащей количество дней, прошедших с момента установки программы.
Открываем свежее пиво! Штаб-квартира защитного механизма найдена! Как мы будем действовать? Чтобы заблокировать NAG, можно, например, изменить cmp eax,28h (83 F8 28) на xor eax,eax/nop (33 C0/90), тогда eax всегда будет равен нулю независимо от того, какой день сейчас за окном, ну а команда nop понадобилась нам для того, чтобы скомпенсировать уменьшение длины инструкции (cmp занимает три байта, а xor - только два).
Запускаем hiew, загружаем winrar.exe, дважды нажимаем на <ENTER> чтобы перейти в ассемблерный режим, давим <F5> (goto) и пишем ".440F46" - адрес инструкции cmp, ну а точка здесь затем, чтобы сообщить hiew'у, что это именно адрес, а не смещение в файле. Нажимаем <F3> для перехода в режим редактирования (edit), а затем <ENTER> для ввода ассемблерной инструкции. В появившемся диалоговом окне пишем "xor eax,eax" <ENTER> "nop" ESC>. Сохраняем все изменения в файле нажатием <F9> и выходим.
Рисунок 5. Три байта, блокирующие NAG.
Запускаем winrar. Теперь NAG уже не выводится! Весь взлом не занял и десяти минут! Как вариант, можно заменить mov eax, dword_004B43C8 (A1 C8 43 4B 00) на mov eax, 6 (B8 06 00 00 00) и тогда RAR будет считать, что с момента регистрации всегда прошло ровно шесть дней. Почему именно шесть? Ну, не шесть, так девять. Какая нам разница?! Главное, чтобы не больше 40! А еще можно заменить jg short loc_440F4F (7F 04) на jmp short loc_440F73 (EB 28), тогда безусловный переход будет перескакивать диалог независимо от текущего времени.
Наиболее красивым считается решение, требующее минимальных исправлений. Лучшие из вышеупомянутых решений хачатся двумя байтами, но можно захачить программу и с помощью одного. Поиск этого байта и будет нашим домашним заданием, ок?
Несмотря на то, что раздражающий NAG успешно убран, программа остается незарегистрированной и честно пишет в заголовке окна: "evolution copy". А если нажать "About", мы увидим "40 days trial copy". И хотя никаких ограничений в демонстрационной версии нет, чисто психологически работать с зарегистрированной копией намного приятнее.
Известно, что регистрация осуществляется с помощью ключевого файла с электронной подписью, сгенерированной на криптографической основе с таким расчетом, чтобы подделка ключа была невозможной. Все это так, но ведь нам не нужен ключ! Мы хотим установить флаг регистрации! А как его найти? Вернемся к листингу 2. Выше уже известной нам инструкции "cmp eax, 28h" ополчилась целая серия условных переходов, при определенных обстоятельствах перепрыгивающих через этот противный диалог. Очевидно, один из них принадлежит флагу регистрации (ведь у зарегистрированных пользователей NAG не выводится), но как определить, какой из них - какой?
Будем действовать по плану (план - эта такая шутка, что растет во дворе). Назначение переменной byte_495A60 определяется сразу. При выводе диалога сюда записывается 1, то есть диалог уже выведен и повторно выводить его не нужно. С переменной dword_4B3A90 разобраться гораздо сложнее. Чтобы узнать, кем она используется и для чего, необходимо просмотреть перекрестные ссылки. Подводим курсор к имени переменной, вызываем контекстное меню и выбираем пункт "jump to xref to operand" или просто нажимаем <X>. Появляется следующее окно (см. рис. 6):
Рисунок 6. Исследование перекрестных ссылок.
Фу! Куча перекрестных ссылок по чтению (r) и записи (w), разбросанных по всему телу программы, среди которых доминируют dec и inc. На флаг регистрации это мало похоже. Скорее, это какой-то дикий семафор, использующийся для организации взаимоблокировок. В общем, запчасть от интерфейса. К переменной byte_4B7E00, ведут три перекрестные ссылки, две из которых находятся в непосредственной близости от функции DoDragDrop, так что их можно сразу откинуть.
А вот переменная byte_49F9BC - это настоящий клад. К ней ведет множество перекрестные ссылок на чтение и запись, но все записываемые значения возвращаются либо функцией sub_40DB5C, либо функций sub_44A278. И только одна ссылка ведет к команде mov byte_49F9BC, 0, принудительно сбрасывающей переменную в ноль.
При первом же взгляде на sub_44A278 бросаются в глаза текстовые строки "rarkey", заботливо оформленные дизассемблером как комментарии. Ага! Держи мыщъх'а за хвост! Похоже, это и есть процедура, ответственная за регистрацию. Подводим курсор к ее началу, нажимаем <N> и переименовываем ее в "DoRegister".
Рисунок 7. Дизассемблерный листинг функции DoRegister, выдающей себя текстовыми строками "rarkey".
С функцией sub_40DB5C разобраться тоже несложно. Достаточно проанализировать код, находящийся в самом начале DoRegister:
DoRegister proc near ... 0044A299 call sub_40DB5C 0044A29E test al, al 0044A2A0 jz short loc_44A2B6 ; продолжение регистрации 0044A2A2 mov al, 1 0044A2A4 mov edx, [ebp+var_11C] 0044A2AA mov large fs:0, edx 0044A2B1 jmp loc_44A40D ; на выход из функции
Листинг 3. Загадочная функция sub_40DB5C.
Если sub_40DB5C возвращает ноль, функция DoRegister продолжает регистрацию. Ненулевое значение приводит к немедленному выходу из функции. Логично предположить, что sub_40DB5C просто сообщает статус регистрации: ноль - не зарегистрирован, не ноль - зарегистрирован. Подведем курсор к началу sub_40DB5C и переименуем ее в "IsRegistered".
А давайте заставим IsRegistered всегда возвращать ненулевое значение! Тогда программа будет признана зарегистрированной, несмотря на то, что ключевого файла, заверенного электронной подписью, у нас нет (да и откуда бы ему взяться)!
Запускаем hiew, дважды нажимаем <ENTER> для перехода в дизассемблерный режим, давим <F5>, вводим ".40DB5C" (адрес функции IsRegistered), затем <F3> для перехода в режим редактирования и <ENTER> xor eax,eax <ENTER> inc eax <ENTER> retn <ESC> (обнулить регистр eax, тут же увеличить его на единицу и свалить из функции нахрен). Записываем изменения клавишей <F9> и выходим из hiew'а.
Надпись "evaluation copy" в заголовке окна послушно исчезает, а в окне About появляется строка "Registered to" (см. рис. 8)
Рисунок 8. Как демонстрационная версия стала зарегистрированной.
Правда, после добавления любого файла в архив, RAR-регистрация загадочным образом исчезает. А все потому, что мы забыли исправить команду "mov byte_49F9BC, 0", расположенную по смещению 44D049h на "mov byte_49F9BC, 1". Сделаем это и тогда надпись evaluation copy никогда не появится!
Надпись "Registered to" это, конечно, хорошо, только не понятно - на кого именно программа зарегистрирована. Первое, что приходит на ум - найти этот "Registered to" в программе (он там находится по смещению 50DBA4h) и заменить его на "hacked by KPNC", однако более длинный ник вместить уже не удастся, поскольку предельно допустимая длина строки жестко ограничена сверху. Лучше найдем тот код, который эту строку выводит!
Запускаем Kerberos, загружаем winrar.exe, открываем "About", закрываем winrar.exe и лезем в протокол, в конце которого содержится строка, DialogBoxParamA(400000, 496005: "ABOUTRARDLG", 001200AA, 00444618, 00000000), вызываемая по адресу 441D1Ch. Ага, это наш About Rar Dialog и есть! Возвращаемся в ИДУ и переходим по указанному адресу.
00441D01 push offset sub_444618 ; lpDialogFunc 00441D06 push dword_4B161C 00441D0C push offset aAboutrardlg 00441D11 push hLibModule 00441D17 call DialogBoxParamA
Листинг 4. Код, создающий About-диалог.
Функция sub_444618, как и подсказывает IDA, представляет собой диалоговую процедуру, ответственную за вывод диалога. Заглянем, что там? Ой-ой-ой, сколько всяких вызовов! Это же крышей поехать можно, пока разберешься, что к чему! Мы видим множество вызовов SetDlgItemTextA. Какой из них наш? Чтобы ответить на этот вопрос, требуется выяснить идентификатор соответствующего элемента управления.
Запускаем Microsoft Visual Studio (или любой другой редактор ресурсов), говорим "open file", в "типе файлов" выбираем "все файлы", а в "open as" - "resources" (если этого не сделать, файл будет открыт как двоичный, что совсем не входит в наши планы). В дереве ресурсов находим ветку "Dialogs", а в ней - "ABOUTRARDLG". Щелкнем по нему мышью. Дважды. Или нажмем ENTER. Запустится редактор ресурсов. Находим строку "40 days trial copy" на месте которой в зарегистрированной версии выводится "Registered to" и, вызвав контекстное меню, определяем ее ID, равным в данном случае 102 (или 66 в hex-представлении).
Рисунок 9. Определение идентификатора поля вывода в Microsoft Visual Studio.
Просматривая дизассемблерный листинг, ищем такую функцию SetDlgItemTextA, чьим аргументом будет идентификатор 66h и находим ее, в конечном счете, по адресу 4447ECh:
004447E6 call sub_4113DC 004447EB push eax ; lpString 004447EC push 66h ; nIDDlgItem 004447EE push [ebp+hDlg] 004447F1 call SetDlgItemTextA
Листинг 5. Код, выводящий строку "Registered to".
Функция sub_4113DC возвращает указатель на выводимую строку, которая тут же передается SetDlgItemTextA. Исследовать саму sub_4113DC мы не будем. Имя зарегистрированного пользователя берется из ключевого файла, над которым можно просидеть всю оставшуюся жизнь. Лучше внедрить свою строку в исполняемый файл и подменить указатель. Внедряться мы будем в секцию данных, в хвосте которой практически всегда имеется свободное место. Размещать выводимую строку в секции кода нельзя, поскольку RAR требует, чтобы она была доступа на запись.
Рисунок 10. Каталог секций.
Открываем hiew, однократным нажатием на <ENTER> переходим в hex-режим, давим <F8> для отображения заголовка файла и вызываем таблицу объектов (object table) клавишей <F6>. За секций .data расположена секция .tls. Подгоняем сюда курсор и нажимаем на ENTER, а затем перемещаемся на несколько строк вверх, следя за тем, чтобы не залезть в значимые данные, которые начинаются там, где кончается цепочка нулей. В нашем случае это будет адрес 49D7B0h (хотя при желании также можно выбрать 49D7AEh, 49D7AFh и т.д). Нажимаем <F3> для перехода в режим редактирования и записываем "registered version hacked by nezumi" (nezumi - это мыщъх по-японски).
Рисунок 11. Создание подложной строки с именем зарегистрированного пользователя.
Теперь переходим по адресу 4447E6h, возвращаясь к нашей диалоговой процедуре (см. листинг 5) и заменяем call sub_4113DC (E8 F1 CB FC FF) на mov eax, 49D7B0 (B8 B0 D7 49 00), где 49D7B0h - адрес хакнутой строки. Сохраняем изменения в файле и...
Рисунок 12. Полностью хакнутая версия, зарегистрированная на мыщъх'а.
Это работает! Теперь хакнутая версия ничем не отличается от легально зарегистрированной! Разумеется, это еще не означает, что теперь RAR'ом можно пользоваться и ничего за него не платить (законов ведь никто не отменял!), поэтому сразу же после экспериментов взломанный файл должен быть удален с жесткого диска.
Версия | Адрес IsRegistered | Адрес mov flag,0 | Адрес lpString |
3.0 stable (rus) | 40BA4C | 441384 | 439740 |
3.42 stable (eng) | 40DB5C | 44D049 | 4447E6 |
3.50 beta 5 (eng) | 40DE2C | 44E5EA | 4457B0 |
3.42 stable (rus) | 40DB5C | 44D049 | 4447E6 |
Таблица 1. Адреса хакаемых байт в различных версиях RAR'a.
Многие разработчики используют электронные подписи и прочие криптографические механизмы, надеясь, что они уберегут их от зловредных хакеров. Как бы не так! Криптография, конечно, мощная штука, но к ней нужен свой подход. Если программа опирается на флаг регистрации (а так поступает большинство программ), она элементарно взламывается правкой нескольких байт, на поиск которых уходит совсем немного времени.
Зачем искать в сети краки (большинство из которых работают с устаревшими версиями или не работают совсем)? Гораздо быстрее взламывать программы самостоятельно!