Telnet і echo - хто правий, хто винен

Telnet і echo - хто правий, хто винен

У коментарях до попередньої статті у нас вибухнула невелика суперечка на тему того, чи допомагає команда «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" ")

У разі некоректної роботи (запуск з "C:\»)

Давайте поставимо бряки на інструкціях, які звертаються до цих рядків. Але перед тим, як це робити, давайте відключимо ASLR. Зробити це за допомогою способу, використаного в попередній статті (редагування поля «DLL flags» у бінарнику), не вдасться, адже нормальна заміна виконуваних файлів у каталозі «»% WINDIR %\System32 «» практично неможлива. Отже, пропоную відключити ASLR для всієї системи в цілому. Натискаємо «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-клієнта і знову шукаємо той же самий рядок:

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

Давайте поставимо хардварний бряк на запис за адресою 0x01029C40, перезапустимо додаток і подивимося, звідки все ж беруться ці рядки. Точка зупину спрацьовує тут:

Як бачите, ми знаходимося десь у надрах WinAPI-функції LoadString (так, явно цього, на жаль, не помітно, проте, найімовірніше, LoadStringBaseEx викликається якраз з неї). Стрибаємо на найближчий код користувача, який в даному випадку знаходиться за адресою 0x0100C788, і бачимо, що поруч знаходяться такі ж виклики для отримання інших рядків:

Подивимося трохи Вище і переконаємося, що в даних місцях дійсно викликалася LoadString:

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

Згідно документації, в такому випадку 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, то ми побачимо, що це дійсно так:

Далі цей хендл використовується у всіх зустрінутих нами викликах LoadString. Наприклад,

Але в чому ж тоді проблема? Якщо LoadString бере рядки з того ж самого файла, то як на успішність їх вилучення може вплинути зміна робочої директорії?

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

Як бачите, її немає. Та що ж таке? Звідки тоді ці рядки взагалі беруться? Подгружуються в той же модуль в run-time? Давайте перезапустимо налагодження і подивимося, чи немає їх у пам'яті процесу вже на старті програми. Натискаємо Ctrl-F2 в OllyDbg, натисніть Alt-M, щоб відкрити вікно «Memory», виділяємо лівою кнопкою миші першу сходинку, натискаємо Ctrl-B і шукаємо юнікодовий рядок «Welcome». Цей та інші рядки дійсно знаходяться в пам'яті процесу вже на даному етапі:

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

Тепер зрозуміло - це MUI. Якщо створити директорію під назвою «en-US», наприклад, в корені диска C, покласти туди файл telnet.exe.mui і запустити раніше некоректно працював telnet-клієнт, ми побачимо, що тепер команда «set localecho» веде себе абсолютно правильно.

Але зачекайте. Навіть якщо telnet.exe не міг вивести на екран якісь рядки, як це могло вплинути на сам результат виконання того ж «set localecho»? Адже одна справа щось не вивести, і зовсім інша справа не відрерагувати на введену користувачем команду належним чином.

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

Натискаємо Ctrl-] у вікні telnet'a, біжимо за налагоджуваним додатком за допомогою F8 і зупиняємося на найближчому виклику WinAPI-функції ReadConsole:

Вводимо команду «set localecho» і вивчаємо, що відбувається після зчитування рядка зі стандартного потоку вводу.

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

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

Якщо запустити зневадку з іншого місця, в якому не лежить директорії «en-US» з необхідним .mui-файлом, то ми побачимо, що рядки з назвами команд будуть порожніми! Це наштовхує на думку, що навіть вони зберігаються у файлі telnet.exe.mui. А переконатися в цьому можна, пошукавши відповідні рядки в пам'яті процесу:

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

Навіщо було виносити навіть ці рядки в .mui-файл - особисто для мене загадка. Можливо, Microsoft не знали, наскільки далеко зайде інтернаціоналізація їх продуктів (наприклад, «включити локальне відлуння»), або, можливо, просто хотіли мати єдине місце, де зберігалися б абсолютно всі використовувані в програмі рядки.

Післямова

Іноді навіть найменша зміна умов, в яких виконується досліджуваний додаток, може вплинути на найнесподіваніші лінії поведінки. Не лінуйтеся перевіряти всі можливі варіанти і уважно ставтеся до будь-яких змін у логіці роботи програми.

Спасибі за увагу, і знову сподіваюся, що стаття виявилася кому-небудь корисною.