Позбавляємося від «історичних причин» cmd.exe

image

Сфера розробки програмного забезпечення є однією з тих областей людської діяльності, де термін «історичні причини» використовується найбільш часто. Воно і зрозуміло — багато «довгограючі» проекти на зразок ядер різних операційних систем, браузерів і іншого обросли за час свого існування некволий арсеналом речей, змінювати поведінку яких стане далеко не кожен, навіть якщо перфекціоніст всередині розробника говорить зворотне. Найімовірніше, велика частина коду була написана програмістами, які вже давно не працюють в компанії, а навіть ті, хто ще пов'язують своє життя з даною корпорацією, сумніваються, що інші компоненти програмного комплексу нормально отрерагируют на ті чи інші зміни. «Ні вже, краще залишу, як воно є».

Як приклад одного з таких речей можна назвати cmd.exe. Так-так, це той самий інтерпретатор командного рядка, що входить в постачання всіх сучасних (і не дуже) операційних систем сімейства Windows. Історичних причин у нього накопичилося чимало — досить згадати хоча б те, як необхідно проводити вставку і копіювання в даний інтерпретатор (заради справедливості варто сказати, що в Windows 10 цю ситуацію нарешті виправили, так і програми на зразок ConEmu здорово в цьому допомагають). Але мова сьогодні піде про інше поведінку, яка змушує замислитися вперше зіткнувся з cmd.exe людини, здавалося б, там, де цього зовсім не потрібно.

Як ви знаєте, однією з команд, які сприймає cmd.exe є «CD». Офіційний хелп по цій команді повідомляє наступне:

C:\Users\Nikita.Trophimov>CD /?
Displays the name of or changes the current directory.
[...]
Здавалося б, все просто. Викликаєш CD без аргументу — в stdout відображається шлях до поточної директорії, передаєш іншу директорію як аргумент — він змінює поточну теку на вказану. Підводні камені тут починаються в тому випадку, якщо користувач вирішив змінити директорію одночасно разом з диском. Наприклад, якщо ви перебуваєте в директорії «C:\Windows\system32» команда «CD D:\books» не зробить рівним рахунком нічого. На мій погляд, очевидного для нових користувачів в цьому абсолютно нічого немає, так що їх рятує гугл або офіційна документація, яка, до речі, повідомляє:

Use the /D switch to change current drive in addition to changing current
directory for a drive.
Зрозуміло, це питання, так само як і причини виникнення такого поводження, вже не раз обговорювалося в інтернеті (наприклад, тут), так що зупинятися на подібних речах ми не будемо. Замість цього ми спробуємо налагодити cmd.exe, щоб прибрати необхідність явного вказівки ключа "/D".

Як протікав процес, і що з цього вийшло, читайте під катом.

Все більша частина ОС сімейства Windows є 64-бітними, що не є винятком і в моєму випадку. Всі стандартні утиліти (calc.exe, taskmgr.exe наш з вами cmd.exe і т. д.) також обзавелися 64-бітними аналогами, які поставляються по дефолту разом з операційною системою. Для реверсу це означає те, що ми в даному випадку не можемо, на жаль, скористатися вже звичним нам по попереднім статтям (які можна знайти, наприклад, тут OllyDbg (до речі, робота над підтримкою x64 досі ведеться).

Які у нас є варіанти? З x64 вміють працювати як мінімум IDA Pro і відносно новий x64_dbg. На жаль, підтримкою x64 володіють лише платні версії IDA Pro, так що пропоную зупинитися на другому варіанті.

Робимо копію cmd.exe завантажуємо снепшот останньої версії x64_dbg, запускаємо його і завантажуємо в нього досліджуваний нами виконуваний файл:

image

Натискаємо F9 до тих пір, поки програма не перестане зупинятися на брейкпоинтах (приємно, що дуже багато хоткей з OllyDbg працюють і тут), робимо right-click по вмісту вікна CPU -> Search for -> String references і шукаємо рядок "/D":

image

Ставимо на кожну з них по бряку за допомогою F2, вводимо в вікно запущеного процесу cmd.exe команду «CD /D D:\books» (припускаючи, що ми, зрозуміло, перебуваємо на іншому диску) і зупиняємося на бряке за адресою 0x7F6D01F972A:

image

Поряд з бряком знаходиться виклик функції _wcsnicmp, використовуваної для порівняння зазначеного кількості байтів, переданих їй рядках:

image

Важливо розуміти, що, на відміну від x86, x64 використовується зовсім інший calling convention:

The Microsoft x64 calling convention is followed on Microsoft Windows and pre-boot UEFI (for long mode on x86-64). It uses registers RCX, RDX, R8, R9 for the first four or integer pointer arguments (in order that), and XMM0, XMM1, XMM2, XMM3 are used for floating point arguments. Additional arguments are pushed onto the stack (right to left). Integer return values (similar to x86) are returned in RAX if 64 bits or less. Floating point return values are returned in XMM0. Parameters less than 64 bits long are not zero extended; the high bits are not zeroed
В даному випадку в якості строкових аргументів функції _wcsnicmp передаються "/D" і "/D D:\books", а в регістрі R8 зберігається інформація про те, скільки байт необхідно порівнювати (у цьому випадку 2). Зрозуміло, в цьому випадку в результаті виклику функції _wcsnicmp в регістрі EAX виявиться нуль, що змусить програму перейти за адресою 0x7F6D01F97F2.

Перше, що приходить на розум — це зробити цей перехід безумовним (поміняти інструкцію JE JMP), змусивши таким чином програму думати, що їй завжди був переданий аргумент "/D". Давайте так і зробимо. Натискаємо F9, переміщаємося в попередню директорію для однаковості вихідних даних, вводимо команду «CD D:\books» (зверніть увагу на відсутність ключа "/D"), виділяємо рядок з інструкцією je cmd.7F6D01F97F2, що знаходиться за адресою 0x7F6D01F9747, натисніть пробіл і міняємо JE JMP, не забуваючи поставити галочку поруч з написом «Fill with NOP's»:

image

Знову натискаємо F9 і бачимо, що команда все одно некоректно завершила свою роботу, але вже, принаймні, не промовчала, як це було минулого разу:

image

Ставимо бряк JMP'е і займаємося трасуванням. Відразу ж після стрибка в регістр RCX потрапляє «урізана» версія рядка, яка зберігається за адресою, вказаною в регістрі RBX. Якщо бути більш точним, з неї видаляються» перші два символи (два, тому що рядки юникодовые, що можна було б зрозуміти по сигнатурі функції _wcsnicmp і символу «L» перед строковими текстовими значеннями, в зв'язку з чим на кожен з них потрібно за два байти, а команда «обрізає» рядок за допомогою RBX+4):

image

Нескладно здогадатися, що це робиться саме для того, щоб прибрати з рядка, який містить зацікавив програму шлях до директорії, ключ "/D", який складається з двох символів. Зрозуміло, нам цього робити вже не треба, оскільки тепер подібні дії будуть «обрізати» частину шляху до зазначеної користувачем директорії. Що ж, замінимо дану інструкцію lea rcx, qword ptr ds:[rbx] (занопить її не можна, т. к. в регістр RCX все ж має потрапити значення):

image

Знову вводимо команду без зазначення ключа "/D", і… Бачимо, що перехід в потрібну директорію справді здійснюється.

Для того, щоб зберегти виконані нами зміни, відкриваємо меню «Patches» за допомогою Ctrl-P, перевіряємо, що виділені всі необхідні зміни, натискаємо на кнопку «Patch File» і вибираємо ім'я для пропатченої версії cmd.exe.

На жаль, навіть якщо у нас вийде замінити оригінальний cmd.exe з директорії "%WINDIR%\system32" на пропатченних, Windows все одно відновити колишню виконуваного версію файлу з кеша, так що зробіть окремий ярлик для пропатченого бінарника і користуйтеся ним.

Післямова
Часом навіть дрібниці можуть зробити наше життя простіше і приємніше або, навпаки, лише погіршити стан справ. Якщо Ви вже кілька разів спіткнулися об підводний камінь у вигляді відсутнього прапора "/D", то чому б не взяти в руки відладчик і не виправити цю ситуацію? Не забувайте, що баги і «історичні причини» зустрічаються часто-густо, а правити їх розробники мають намір далеко не завжди.

Справедливості заради варто відзначити, що в PowerShell необхідність у вказівці ключа "/D" для команди CD все ж прибрали.

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

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.