image

В комментариях к предыдущей статье у нас разразился небольшой спор на тему того, помогает ли команда «set localecho» в решении проблемы с отсутствием echo при взаимодействии с сервером bat.org. Я стоял на том, что команда эта не сделает ровным счётом ничего для исправления рассмотренной ситуации, и говорил это вовсе неспроста — специально после одного из комментариев я решил ещё раз проверить мою правоту в данном вопросе. Проделав все необходимые действия (запуск telnet.exe, нажатие Ctrl-], ввод команды «set localecho» и двойное нажатие клавиши Enter), я в очередной раз убедился, что был прав. О чём же тогда так уверенно твердят остальные?

Я попросил выслать мне бинарник «работающего» telnet-клиента и версию ОС, на котором он запускался, в личку. Убедившись, что версии ОС совпадают (использовалась Windows 7 SP1 x64), я обратил своё внимание на сам telnet-клиент. Хеши совпали. Запустив «работающий», по заверению пользователя k0ldbl00d, бинарник, я с удивлением обнаружил, что на моём компьютере не работает и он.

Может быть, дело в окружении, в котором выполняется telnet.exe? Оригинальный исполняемый файл был взят из директории "%WINDIR%\System32", так что я запустил свой telnet-клиент оттуда, и… Обнаружил, что команда «set localecho» корректно отрабатывает при таком раскладе. А если скопировать тот же самый исполняемый файл в любую другую директорию и воспользоваться уже им, то, несмотря на то, что основной функционал telnet.exe будет продолжать работать, команды перестанут выполнять то, что от них требуется.

В чём же дело? Давайте раберёмся.

Как протекал процесс, и что из этого вышло, читайте под катом. Перед прочтением данной статьи также настоятельно рекомендую ознакомиться с предыдущими, т.к. в них уже объяснены многие из опущенных здесь моментов.

Во-первых, мой взгляд упал на отсутствие «приветствующих» сообщений в случае выполнения telnet.exe в директории, отличной от "%WINDIR%\System32":

В случае корректной работы (запуск из "%WINDIR%\System32")
image

В случае некорректной работы (запуск из «C:\»)
image

Что ж, давайте поставим бряки на инструкциях, которые обращаются к данным строкам. Но перед тем, как это делать, давайте отключим ASLR. Сделать это при помощи способа, использованного в предыдущей статье (редактирование поля «DLL flags» в бинарнике), не получится, ведь нормальная замена исполняемых файлов в директории "%WINDIR%\System32" практически невозможна. Следовательно, предлагаю отключить ASLR для всей системы в целом. Нажимаем Win-R -> regedit -> лезем в «HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management» -> создаём или редактируем уже существующую опцию под названием «MoveImages», чтобы сделать её равной нулю. После этого перезагружаем систему и наслаждаемся отключённым ASLR.

Запускаем telnet.exe, находящийся в директории "%WINDIR%\System32", в OllyDbg, останавливаемся на Entry Point и ищем «Welcome to» в списке найденных OllyDbg строк, на которые ссылаются инструкции в модуле «telnet». Видим сообщение «Item not found» и начинаем думать в сторону динамической загрузки строк из сторонних ресурсов.

Нажимаем F9, дожидаемся старта telnet-клиента и снова ищем ту же самую строку:

image

Нажимаем Enter, предварительно выделив найденную только что строку, и видим инструкции, которые обращаются к строкам из «приветствующих» сообщений:

image

Давайте поставим хардварный бряк на запись по адресу 0x01029C40, перезапустим приложение и посмотрим, откуда всё же берутся эти строки. Точка останова срабатывает тут:

image

Как видите, мы находимся где-то в недрах WinAPI-функции LoadString (да, явно этого, к сожалению, не заметно, однако, вероятнее всего, LoadStringBaseEx вызывается как раз из неё). Прыгаем на ближайший пользовательский код, который в данном случае находится по адресу 0x0100C788, и видим, что рядом находятся такие же вызовы для получения других строк:

image

Посмотрим чуть Выше и убедимся, что в данных местах действительно вызывалась LoadString:

image

Давайте узнаём, из какого модуля telnet.exe берёт данные строки. Ставим бряк по адресу 0x0100C62A, перезапускаем отладку и видим, что аргумент, отвечающий за имя модуля в WinAPI-функции GetModuleHandle, равен нулю:

image

Согласно документации, в таком случае GetModuleHandle возвращает, по сути, хэндл файла текущего процесса:

lpModuleName [in, optional]

[...]

If this parameter is NULL, GetModuleHandle returns a handle to the file used to create the calling process (.exe file)

Если нажать два раза F8, то мы увидим, что это действительно так:

image

Далее этот хэндл используется во всех встреченных нами вызовах LoadString. Например,

image

Но в чём же тогда проблема? Если LoadString берёт строки из того же самого файла, то как на успешность их извлечения может повлиять смена рабочей директории?

Для начала давайте возьмём в руки Resource Hacker и посмотрим, найдёт ли он STRINGTABLE в telnet.exe:

image

Как видите, её нет. Да что ж такое? Откуда тогда эти строки вообще берутся? Подгружаются в тот же модуль в run-time? Давайте перезапустим отладку и посмотрим, нет ли их в памяти процесса уже на старте приложения. Нажимаем Ctrl-F2 в OllyDbg, жмём Alt-M, чтобы открыть окно «Memory», выделяем левой кнопкой мыши первую строчку, нажимаем Ctrl-B и ищем юникодовую строку «Welcome». Эта и остальные строки действительно находятся в памяти процесса уже на данном этапе:

image

Посмотрим, откуда был замаппеен данный участок памяти. Нажимаем Alt-M и видим:

image

Теперь понятно — это MUI. Если создать директорию под названием «en-US», например, в корне диска C, положить туда файл telnet.exe.mui и запустить ранее некорректно работавший telnet-клиент, мы увидим, что теперь команда «set localecho» ведёт себя абсолютно правильно.

Но постойте. Даже если telnet.exe не мог вывести на экран какие-то строки, как это могло повлиять на сам результат выполнения того же «set localecho»? Ведь одно дело что-то не вывести, и совсем другое дело не отрерагировать на введённую пользователем команду должным образом.

Давайте поставим бряк на обращение к строке «Microsoft Telnet> », которая является «приглашением» для ввода следующей команды. Такая инструкция находится по адресу 0x0100BEAA:

image

Нажимаем Ctrl-] в окне telnet'а, бежим по отлаживаемому приложению при помощи F8 и останавливаемся на ближайшем вызове WinAPI-функции ReadConsole:

image

Вводим команду «set localecho» и изучаем, что происходит после считывания строки из стандартного потока ввода.

Сначала в программе проверяется корректность выполнения функции ReadConsole (возвращаемое значение не ноль, кол-во прочитанных байт больше нуля etc):

image

Через некоторое время после этого мы попадаем в цикл, в котором проверяется первый символ введённой пользователем команды (в нашем случае это 's') на равенство с первыми символами таких команд, как, например, «quit» или «set»:

image

Если запустить отладку из другого места, в котором не лежит директории «en-US» с необходимым .mui-файлом, то мы увидим, что строки с названиями команд будут пустыми! Это наталкивает на мысль, что даже они хранятся в файле telnet.exe.mui. А убедиться в этом можно, поискав соответствующие строки в памяти процесса:

image

Следовательно, в случае отсутствия .mui-файла telnet-клиент не мог даже понять, что за команда была введена пользователем, т.к. строки для сравнения не были загружены.

Зачем было выносить даже эти строки в .mui-файл — лично для меня загадка. Возможно, Microsoft не знали, насколько далеко зайдёт интернационализация их продуктов (например, «включить локальное эхо»), или, может быть, просто хотели иметь единое место, где хранились бы абсолютно все используемые в программе строки.

Послесловие


Порой даже малейшее изменение условий, в которых выполняется исследуемое приложение, может повлиять на самые неожиданные линии поведения. Не ленитесь перепроверять все возможные варианты и внимательно относитесь к любым изменениям в логике работы приложения.

Спасибо за внимание, и снова надеюсь, что статья оказалась кому-нибудь полезной.

Комментарии (4)


  1. xRay
    10.07.2015 19:51
    +4

    Любопытное расследование.
    Впечатляет.


  1. lockywolf
    10.07.2015 23:00
    +3

    Очень круто. Всегда с большим удовольствием читаю ваши ассемблерные расследования.

    Буду рад, если будут ещё.


  1. navion
    10.07.2015 23:30

    В семёрке у tracert тоже сломан MUI и он выводит каракули вместо сообщений на русском, но остальной функционал работает.


  1. ComodoHacker
    11.07.2015 00:03

    Интересно сравнить .mui для других языков. Может действительно где-то команды локализованы. У немцев, например или у французов. Они, говорят, люди привиредливые.