Статья [reversing] «Прокачиваем» notepad.exe

Dragokas

Very kind Developer
Команда форума
Супер-Модератор
Разработчик
Клуб переводчиков
Сообщения
5,963
Симпатии
5,786
Баллы
588
#1
proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F281%2F859%2F209%2F281859209908f1c75298c64c838c3bf3.png&hash=a918cac5ee17d329c6fc85b38ba2fd03


Какая ассоциация связана у Вас с клавишей F5? Обновление страницы в браузере? Копирование файла из одной директории в другую? Запуск приложения из Visual Studio? А вот авторы notepad.exe подошли к этому вопросу довольно оригинально — по нажатию клавиши F5 происходит добавление текущей даты и времени в место, куда в этот момент указывает курсор. Всё было бы круто, если бы в notepad.exe была такая популярная и вполне естественная для большинства текстовых редакторов фича, как перечитывание содержимого текущего файла, которая, казалось бы, и должна быть назначена на F5 / Ctrl-R или ещё какой-нибудь общепринятый хоткей.

Мы можем ждать, пока её реализуют Microsoft, выбрать другой текстовый редактор (ведь это не единственное ограничение по функционалу стандартного notepad.exe) или… Взять в руки дизассемблер, отладчик и редактор PE-файлов.

Как протекал процесс, и что из этого вышло, читайте под катом (осторожно, много скриншотов). Перед прочтением данной статьи также настоятельно рекомендую ознакомиться с предыдущими.
предыдущей статье, давайте для начала отключим использование ASLR. Согласно вики, ASLR (Address space layout randomization) — это технология, при использовании которой случайным образом изменяется расположение в адресном пространстве процесса важных структур, а именно: образа исполняемого файла, подгружаемых библиотек, кучи и стека. Именно из-за неё в прошлый раз перезапуск приложения и приводил к изменению уже найденных нами ранее адресов. Если Вы используете Windows XP или более старую ОС, то можете с лёгкостью пропустить то, о чём будет рассказано в нескольких следующих абзацах, ведь ASLR на тот момент ещё не было.

Отключить использование ASLR можно как глобально (для этого необходимо добавить / отредактировать значение опции «MoveImages», хранящейся в реестре по адресу «HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management», чтобы сделать её равной нулю), так и локально, т.е. для конкретного исполняемого файла. Последний вариант выглядит более привлекательным, особенно если речь идёт не о виртуальной машине, а о реальной системе, так что давайте остановимся на нём.

Копируем notepad.exe в любую отличную от "%WINDIR%\System32" директорию, скачиваем, разархивируем и запускаем PE Tools, нажимаем Alt-1 и выбираем скопированный ранее notepad.exe:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F78d%2Fb16%2F4e8%2F78db164e8aa21150cf4a4669093c9fde.png&hash=7939ad78f1257e94dea2308b2381ebf2


Нажимаем на кнопку «Optional Header» и смотрим на поле DLL Flags, которое в нашем случае равно 0x8140:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F9e6%2F287%2Fefa%2F9e6287efa3137262583a6090ddf8390c.png&hash=52b4087715fd8ad2b6fa806a2511711b


Значение в этом поле является результатом выполнения операции битового «OR» для констант, перечисленных в официальной документации на MSDN. Несложно заметить, что наш бинарник обладает следующими характеристиками:

IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
0x8000
The image is terminal server aware

IMAGE_DLLCHARACTERISTICS_NX_COMPAT
0x0100
The image is compatible with data execution prevention (DEP)

IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
0x0040
The DLL can be relocated at load time
Обратили внимание на последнее значение? Что ж, это именно то, что нас интересует. Меняем 0x8140 на 0x8100, нажимаем «Ok» в обоих окнах и приступаем к отладке.

На какие этапы можно условно разделить наш патчинг notepad.exe?

  • Поиск адреса, по которому хранится путь до текущего файла
  • Поиск процедуры считывания содержимого файла
  • Поиск кода, отвечающего за обработку нажатия клавиши F5
  • Собственно, написание самого патча

Открываем notepad.exe в OllyDbg и приступаем к первому этапу.

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

Надеясь, что файл при сохранении каждый раз открывается заново, ставим бряки на вызовы WinAPI-функции CreateFileW:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F798%2F7dc%2F7c7%2F7987dc7c798f1c39de0201bd7886463b.png&hash=dd50e586cc869e4157ec7d7a5c08a3f5


Нажимаем Ctrl-S, выбираем имя файла (в моём случае это «C:\helper.txt») и останавливаемся на следующем месте:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Fbdd%2F096%2F56f%2Fbdd09656f1219f3d3743babbf05d6c98.png&hash=4d682cb713361fa58e0c77aa84f04713


Посмотрим, откуда и с какими аргументами нас позвали:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F5e1%2F9af%2Faca%2F5e19afacae2d58d4e8158a7477563ee0.png&hash=2d978c32286ff4da5f6a8bb2ba6e0d5e


Если посмотреть, на что указывает адрес, переданный в качестве второго аргумента (right-click по строке с данным аргументом -> Follow address in stack), то мы увидим как раз наш путь:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F9a2%2F3a1%2Fa46%2F9a23a1a461bb47886d06824158a5ead0.png&hash=0a184767acd3097ead0f490af3d0c898


Давайте посмотрим на код, находящийся перед вызовом исследуемой нами процедуры, чтобы понять, откуда и как именно к нам попал этот адрес:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Fffc%2F8ab%2F2c1%2Fffc8ab2c1b842d41c4b424fe6905ca04.png&hash=ccae4b4714b716f549919ba536c5ba9f


Как Вы видите, адрес, по которому хранится путь до файла, содержится в EBP-8. Давайте снова нажмём Ctrl-S и посмотрим, куда мы попадём на этот раз (ведь теперь программа уже знает путь до файла, что может поменять ход работы приложения):

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F87f%2F8ae%2Fca5%2F87f8aeca5a197434a333bd1988675816.png&hash=e7855fb0bcf56c88d07346c830a3c7a8


Итак, мы оказались на том же самом бряке, что и раньше, однако позвали нас уже из другого места:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Fa98%2F7bf%2Fab3%2Fa987bfab36496c6b184db031b96456e3.png&hash=02b245b81abd1e183dab19dc47e9a649


На этот раз адрес, по которому содержится путь до файла, хранится в регистре EBX. С момента начала текущего case-блока (обратите внимание на комментарий несколькими инструкциями раньше выделенного места) значение данного регистра не изменяется, что означает, что искать оригинальный адрес надо где-то раньше. Смотрим, какие инструкции ссылаются на начало данного case-блока (left-click по адресу 0x01004D5D -> Ctrl-R):

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F9f4%2Fe0a%2F0f8%2F9f4e0a0f82914e283fb6e113690dd5e1.png&hash=38b9a59657f905f44366915e807da903


Раз такое обращение всего одно, прыгаем на него по нажатию клавиши Enter и сразу же видим, откуда в EBX появляется данный адрес:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F614%2Fea4%2Fd67%2F614ea4d675e551fc3e292670a077739d.png&hash=8c838aa1c77dd2befc051d34c2e1c010


Итак, мы поняли, что по адресу 0x0100CAE0 хранится путь до текущего файла. Что дальше? А дальше мы должны найти процедуру, ответственную за считывание содержимого файла.

Очевидно, что она также будет вызывать CreateFileW (вместо этого мы могли бы перехватить вызов функции GetOpenFileName, но её нет в списке межмодульных вызовов — видимо, вместо неё используется Common Item Dialog API, которое рекомендуется на MSDN). Нажимаем Ctrl-O, выбираем любой файл (я выбрал тот же самый) и, не успев сделать двойной клик мышью, оказываемся на бряке по адресу 0x01006E8C:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F76c%2F842%2F1e7%2F76c8421e7d135b4690d76b160a24e5a7.png&hash=545d9bdef9564ed6f27f54f7622beb16


Проделываем то же самое несколько раз, прежде чем убрать данный бряк и надеяться на остальные. И правда, после того, как был убран бряк по указанному ранее адресу, мы всё же смогли сделать двойной клик по интересующему нас файлу, в результате чего сработала точка останова уже совершенно в другом месте:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Fc90%2F25f%2F658%2Fc9025f6580186f80446cd8b831af7eff.png&hash=9e7c5ec3dbad8984bc9171032a56678c


Итак, наша задача — выяснить, каким образом и какую именно процедуру необходимо вызвать, чтобы успешно перечитать интересующий нас файл. Ставим бряк на адрес, с которого нас позвали

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F624%2F33e%2F892%2F62433e892ae8d3602893622d502bffc0.png&hash=cea4a7a5c461c6d89afa35446e6e2c50


, нажимаем F9, и… Он тут же срабатывает! Ничего, снова нажимаем F9, пытаемся передать фокус окну notepad.exe и видим, что бряк снова срабатывает. Да что ж такое! Давайте посмотрим на начало процедуры, которую вызывает данный CALL:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Fd0f%2F4c9%2Fdfa%2Fd0f4c9dfa62fd88eebd798dbe35e0c11.png&hash=7359029ff89a03f3787cd58494bd5cdf


Обратите внимание на единственный комментарий — судя по кол-ву обрабатываемых значений и тому, что мы наблюдаем на практике, данная процедура служит для реакции на любое выполняемое пользователем действие, будь то передача окну notepad.exe фокуса или открытие файла. Видимо, после нажатия Ctrl-O программа не выполняет никакого CALL'а, а лишь переходит на соответствующий case-блок при помощи операции условного перехода. Давайте уберём данный бряк, ещё раз попытаемся открыть файл и найдём ближайшую к бряку, стоящему на месте вызова CreateFileW, инструкцию, к которой есть обращения в коде. Ею оказалась инструкция по адресу 0x01004DF5:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F48d%2Fce7%2Fe54%2F48dce7e5413c84f9abae70fb33873cee.png&hash=a6d621b8644818cc7e74162862fe1ca5


Ставим бряки на оба обращения, проделываем те же самые действия и оказываемся тут:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F71c%2F5d5%2F075%2F71c5d5075a3e8746fb06f505d8634f47.png&hash=59e0eb60ff02b121b6370fb821a7f046


Ставим бряк на начало данного case'а, снова открываем тот же самый файл и пытаемся понять, что тут происходит:

; Зануляем значение в регистре EDI
01003ECC > \33FF XOR EDI,EDI ; Case 2 of switch 01001824
; Вызываем процедуру проверки изменений в текущем файле
; Если они были, отобразится диалоговое окно с предложением сохранить изменения в файл
01003ECE . 57 PUSH EDI
01003ECF . E8 90D7FFFF CALL notepad.01001664
; Проверяем возвращаемое значение
; EAX == 1, если изменений не было / пользователь нажал клавишу Save / Don't Save, EAX == 0, если была нажата кнопка Cancel
01003ED4 . 85C0 TEST EAX,EAX
; Если нажали Cancel, то дальнейшее нас уже не интересует, переходим в другой case
01003ED6 .^ 0F84 8ED9FFFF JE notepad.0100186A
; Перемещаем нечто с адреса 0x100C00C в EAX и затем в EBP-10
01003EDC . A1 0CC00001 MOV EAX,DWORD PTR DS:[100C00C]
01003EE1 . 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX
; Вызываем процедуру отображения диалогового окна с просьбой выбрать файл
01003EE4 . 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8]
01003EE7 . 50 PUSH EAX ; /Arg2
01003EE8 . FF75 F4 PUSH DWORD PTR SS:[EBP-C] ; |Arg1
01003EEB . E8 31000000 CALL notepad.01003F21 ; \notepad.01003F21
; В результате вызова данной процедуры в EBP-8 будет храниться путь до открываемого файла
; EAX == 0 в случае успеха и 0x800704C7 в случае нажаия кнопки Cancel
01003EF0 . 8BF0 MOV ESI,EAX
01003EF2 . 3BF7 CMP ESI,EDI
; Один из прыжков на интересующую нас процедуру
01003EF4 . 0F8D FB0E0000 JGE notepad.01004DF5
01003EFA . 81FE C7040780 CMP ESI,800704C7
01003F00 . 0F85 DC0E0000 JNZ notepad.01004DE2
01003F06 > 3BF7 CMP ESI,EDI
01003F08 . 0F8D E70E0000 JGE notepad.01004DF5
01003F0E > 8B45 F0 MOV EAX,DWORD PTR SS:[EBP-10]
01003F11 . A3 0CC00001 MOV DWORD PTR DS:[100C00C],EAX
01003F16 . 56 PUSH ESI
01003F17 .^ E9 A2FCFFFF JMP notepad.01003BBE


Теперь давайте посмотрим, какие регистры и адреса использует код по адресу 0x01004DF5, чтобы понять, какое «окружение» необходимо для его корректной работы:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F8d0%2F67d%2F3c0%2F8d067d3c07b4c371741cd6d35bd4acf7.png&hash=2384892ed0d24afe9b4319e3da9b603a


Разумеется, данный код обращается к EBP-8, по которому, как Вы помните, хранится путь до открываемого файла. Помимо этого, ему также важно значение регистра EDI, который используется в качестве аргументов для параметров hTemplateFile и pSecurity. Первое мы можем достать из адреса 0x0100CAE0, а в обозначенные параметры можно просто передать ноль.

Теперь давайте найдём код, отвечающий за обработку нажатия клавиши F5. Для этого предлагаю поставить бряк на вызовы функций, отвечающих за получение текущего времени. Наиболее популярные из них — GetSystemTime и GetLocalTime. Первой нет в списке межмодульных вызовов, а вот вторая вызывается сразу из двух мест:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F2ee%2F21d%2Fd18%2F2ee21dd18388d46e27989fab89ed4d03.png&hash=4b12535108a504675453c0617c0d2b88


Ставим бряки, нажимаем F5 и оказываемся тут:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F134%2F684%2Fdf0%2F134684df01971a975d71bb3e60041d6a.png&hash=46d23c12dbe7b3259fdf9746b8bd8514


Прыгаем на место вызова текущей процедуры и попадаем практически в самое начало ещё одного case-блока, который, очевидно, и отвечает за обработку нажатия F5:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Fe2f%2F5fb%2Ffaf%2Fe2f5fbfaff67765c2adfe18e955ae52f.png&hash=c2d81416b28cf97135c67c75fcc97ea9


Отлично. Ищем место для нашего code cave'а и пишем (разумеется, адреса могут отличаться):

0100BEB3 33FF XOR EDI,EDI
0100BEB5 C745 F8 E0CA0>MOV DWORD PTR SS:[EBP-8],notepad.0100CAE0 ; UNICODE "C:\helper.txt"
0100BEBC A1 0CC00001 MOV EAX,DWORD PTR DS:[100C00C]
0100BEC1 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX
0100BEC4 ^ E9 2C8FFFFF JMP notepad.01004DF5


Вставляем по адресу 0x0100447B прыжок на наш code cave:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Faea%2Fdfc%2Fde3%2Faeadfcde3203988d7b81ae79728b39f1.png&hash=37379b8b0bf81c41d4d130863b34a04b


Нажимаем F9, снова жмём F5 и наблюдаем следующую картину:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F7b6%2Fd05%2Ff03%2F7b6d05f035e35b342db055cfa1416e31.png&hash=759d185ca6444d31658ec5e241fa057d


Как видите, мы упали где-то в недрах функции CoTaskMemFree. Обратите внимание на аргумент, переданный этой функции — да-да, это адрес нашей строки с путём до файла. Значит, память под неё необходимо выделять при помощи CoTaskMemAlloc. В этом нам может помочь функция SHStrDup, которая создаёт дупликат переданной ей строки, выделив память под неё при помощи CoTaskMemAlloc.

Перезапускаем notepad.exe и ищем адрес функции SHStrDupW в IAT. Для этого смотрим на вызов любой другой WinAPI-функции в модуле:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F7d1%2F3a2%2F2ba%2F7d13a22baa0ef10a380f0109dba3a451.png&hash=45570c87582543ba1d4e8c90e7bd245f


Следовательно, адрес функции GetDlgItemTextW в IAT — 0x010012A4. Прыгаем на него и ищем нашу SHStrDupW:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F2fd%2F574%2F77e%2F2fd57477ec5fd0bb80cfde4c398f2faa.png&hash=65147c50a1741ea3b27e074b84f23036


Получается, её вызов можно оформить в виде инструкции CALL DWORD PTR DS:[010013B4]. Тогда пишем следующий код (проверка на наличие ошибок опущена):

0100BFA5 . 33FF XOR EDI,EDI
0100BFA7 . 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8]
0100BFAA . 50 PUSH EAX ; /pTarget
0100BFAB . 68 E0CA0001 PUSH notepad.0100CAE0 ; |Source = "C:\helper.txt"
0100BFB0 . FF15 B4130001 CALL DWORD PTR DS:[<&SHLWAPI.SHStrDupW>] ; \SHStrDupW
0100BFB6 . A1 0CC00001 MOV EAX,DWORD PTR DS:[100C00C]
0100BFBB . 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX
0100BFBE .^ E9 328EFFFF JMP notepad.01004DF5


Открываем наш файл «C:\helper.txt», убеждаемся, что он пустой, редактируем и сохраняем его в другой копии notepad.exe, нажимаем F5 в отлаживаемой нами версии, и… Файл обновляется!

Давайте сохраним наши изменения в исполняемый файл. Делаем right-click по окну CPU -> Copy to executable -> All modifications -> Copy all и видим:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F618%2F763%2Fc93%2F618763c9382a7a2a621884439df7175a.png&hash=bba572563bda24483ce711f1a1089d12


Получается, что мы вылезли за физические границы исполняемого файла. Давайте взглянем на границы секций в PE Tools (кнопка «Sections»)

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Fd31%2F4f8%2Fef1%2Fd314f8ef1e11f76917ae13c028d94d72.png&hash=4804b4301eae9fb1bb6d090cacdc2b8f


и поместим наш code cave в какое-нибудь другое место. Для получения верхней «границы» области для «безболезненного» патча мы должны сложить Virtual Offset секции .text, куда мы собираемся положить наш патч, её Raw Size и Image Base, т.е. Virtual Offset (0x00001000) + Raw Size (0x0000A800) + Image Base (0x01000000) = 0x0100B800. Поместим его, например, по адресу 0x0100B6CF и попытаемся сохранить изменения ещё раз (right-click по окну CPU -> Copy to executable -> All modifications -> Copy all -> right-click на появившемся окне -> Save file).

Проверяем получившийся исполняемый файл на работоспособность и убеждаемся, что всё ведёт себя так, как и ожидается.

Послесловие

Цель данной статьи — в очередной раз продемонстрировать возможность добавления собственного функционала в существующие программы, не имея при этом на руках исходных кодов. А теперь возвращайтесь к своим vim'ам / emacs'ам / Notepad++ / etc, но помните — если Вы встретите баг или обратите внимание на отсутствие какого-либо функционала в редакторе с закрытым кодом, теперь Вы знаете, что надо делать.

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

Автор: @NikitaTrophimov
Источник
 
Последнее редактирование:
Сверху Снизу