Магія Ctrl-C, Ctrl-V, або як перестати зберігати картинки і почати жити

image

Одна з небагатьох стандартних утиліт Windows, якою я користуюся практично кожен день — це snippingtool, або, просто кажучи, «Ножиці». Своє завдання вона виконує на ура (втім, багато від неї я і не вимагаю), а з інших корисностей можна відзначити вставку виділеного регіону безпосередньо в Skype, без необхідності зберігати зображення у файл — достатньо лише натиснути Ctrl-V у вікні введення повідомлень. Приємно, що назва файлу в такому випадку буде складатися з дати і часу замість, наприклад, хеш.

Незважаючи на те, що в самому Snipping Tool є можливість обводити певні частини зображення, іноді цього недостатньо:

  • По-перше, «Ножиці» не вміють обробляти комбінацію клавіш Ctrl-Z, тобто зробити в них Undo не вийде, у зв'язку з чим одна-єдина помилка у редагуванні може змусити почати все з початку
  • По-друге, обводити зображення можна тільки за допомогою Pen'а і Highlighter'а, що не дуже зручно, коли потрібно, наприклад, вказати на прямокутну область
Саме з цих причин я часто звертаюся до mspaint. А от у нього є зворотний недолік — вставити зображення безпосередньо з буфера обміну в Skype вже не вийде.

У чому ж причина такої поведінки? Чи можна його виправити? Давайте розберемося.

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

Пропоную розпочати з порівняння вмісту буфера обміну в разі копіювання зображення з snippingtool і з mspaint. Для цього можна скористатися програмою під назвою InsideClipboard. Завантажуємо, разархивируем і запускаємо InsideClipboard, відкриваємо «Ножиці» (Win-R -> snippingtool), виділяємо який-небудь регіон (в моєму випадку це невелика частина чорного фону на робочому столі), натискаємо F5 в InsideClipboard і бачимо наступну картину:

image

Зберігаємо це ж зображення куди-небудь на диск, відкриваємо його в mspaint (Win-R -> mspaint), виділяємо його повністю (Select -> Select all), копіюємо в буфер обміну за допомогою Ctrl-C, знову оновлюємо стан InsideClipboard'а і дивимося, що виявилося в clipboard'е на цей раз:

image

Крім самого bitmap'а в даному випадку ми можемо спостерігати в буфері обміну якусь додаткову інформацію. Може бути, Skype'у саме вона заважає при вставці зображення?

Оскільки клієнт Skype'а навешана самопальна захист, для даної задачі набагато простіше буде скористатися яким-небудь перехоплювачем WinAPI-функцій, щоб подивитися, як Skype «заглядає» в буфер обміну. Завантажуємо і запускаємо WinAPIOverride, вказуємо PID Skype'а в полі «Process ID» і натискаємо на кнопку «Start»:

image

Відмінно, тиснемо на кнопку «Monitoring Library Files»

image

і починаємо ставити галочки поруч з функціями, пов'язаними з роботою з clipboard'ом. Повний їх список можна знайти, наприклад, на MSDN:

AddClipboardFormatListener
ChangeClipboardChain
CloseClipboard
CountClipboardFormats
EmptyClipboard
EnumClipboardFormats
GetClipboardData
GetClipboardFormatName
GetClipboardOwner
GetClipboardSequenceNumber
GetClipboardViewer
GetOpenClipboardWindow
GetPriorityClipboardFormat
GetUpdatedClipboardFormats
IsClipboardFormatAvailable
OpenClipboard
RegisterClipboardFormat
RemoveClipboardFormatListener
SetClipboardData
SetClipboardViewer
Переходимо на документацію до будь-якої з них і звертаємо увагу, що робота з буфером обміну здійснюється за допомогою модуля User32. Ставимо галочку поруч з ним і відповідними функціями і натискаємо на кнопку «OK»:

image

Вставляємо у вікно вводу Skype'а зображення з snippingtool і дивимося на ланцюжок викликів:

image

Тепер проробляємо те ж саме з mspaint:

image

Зверніть увагу, що функція IsClipboardFormatAvailable, викликана з аргументом 0x0000C013, повертає різні результати у двох розглянутих нами випадках. Аргумент цей позначає формат, наявність якого, власне, і потрібно перевірити:

format [in]
Type: UINT
A standard or registered clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats
Давайте поглянемо на визначення predefined-форматів в заголовочном файлі WinUser.h:

/*
* Predefined Clipboard Formats
*/
#define CF_TEXT 1
#define CF_BITMAP 2
#define CF_METAFILEPICT 3
#define CF_SYLK 4
#define CF_DIF 5
#define CF_TIFF 6
#define CF_OEMTEXT 7
#define CF_DIB 8
#define CF_PALETTE 9
#define CF_PENDATA 10
#define CF_RIFF 11
#define CF_WAVE 12
#define CF_UNICODETEXT 13
#define CF_ENHMETAFILE 14
#if(WINVER >= 0x0400)
#define CF_HDROP 15
#define CF_LOCALE 16
#endif /* WINVER >= 0x0400 */
#if(WINVER >= 0x0500)
#define CF_DIBV5 17
#endif /* WINVER >= 0x0500 */

#if(WINVER >= 0x0500)
#define CF_MAX 18
#elif(WINVER >= 0x0400)
#define CF_MAX 17
#else
#define CF_MAX 15
#endif

#define CF_OWNERDISPLAY 0x0080
#define CF_DSPTEXT 0x0081
#define CF_DSPBITMAP 0x0082
#define CF_DSPMETAFILEPICT 0x0083
#define CF_DSPENHMETAFILE 0x008E

/*
* "Private" formats don't get GlobalFree()'d
*/
#define CF_PRIVATEFIRST 0x0200
#define CF_PRIVATELAST 0x02FF

/*
* "GDIOBJ" formats do get DeleteObject()'d
*/
#define CF_GDIOBJFIRST 0x0300
#define CF_GDIOBJLAST 0x03FF

Як бачите, нас цікавить 0x0000C013 серед них, на жаль, немає. Погугливши трохи, я натрапив на кілька джерел (наприклад, тут), які повідомляють, що даний формат пов'язаний з OLE:

The Windows clipboard is the mechanism that Microsoft
Windows operating systems use to allow data to be shared
between applications. It first appeared in Windows 3.1,
although its functionality has greatly increased since then.
Table 1 shows the standard formats used by the clipboard
(Petzold, 1999). However, Microsoft also provides the ability
for private data formats", formats that are application
specific (for example, fonts in a word processing program),
and that could be registered so that other applications could
data transfer in these formats (Petzold, 1999). Two private data
formats that are used extensively are object link embedding
(OLE) 0xC013) and dataobjects (0xC009)
Якщо завантажити mspaint в OllyDbg і поставити бряк на початок функції SetClipboardData, то при копіюванні зображення або його частини в буфер обміну ми побачимо по Call Stack'у нас дійсно покликали з пов'язаних з OLE функцій:

image

Мабуть, Skype дійсно при зустрічі в буфері обміну даних, пов'язаних з OLE, перестає думати, що в ньому знаходиться повноцінне зображення. Не можу сказати, що це баг чи фіча, але мене така поведінка явно не влаштовує.

До речі, Ви звернули увагу, що InsideClipboard не показав даних з форматом 0x0000C013? Якщо ж скачать який-небудь інший viewer clipboard'а (наприклад, Free Clipboard Viewer), то ми побачимо ці самі «Ole Private Data»:

image

Але зачекайте! Зображення дійсно є в буфері обміну, раз ми можемо скопіювати його, наприклад, в той же mspaint. Давайте спробуємо отримати його, очистити поточний вміст clipboard'а і «скласти» його наново, щоб в ньому не залишилося ні найменшої згадки про OLE.

Пишемо наступний код на C#

using System;
using System.Windows.Forms;

namespace clipboard_helper
{
class Program
{
[STAThread]
static void Main(string[] args)
{
if (Clipboard.ContainsData(DataFormats.Bitmap))
{
object data = Clipboard.GetData(DataFormats.Bitmap);
Clipboard.SetData(DataFormats.Dib, data);
}
}
}
}

, копіюємо в буфер обміну зображення з mspaint, дивимося на висновок InsideClipboard'а

image

, запускаємо наш додаток і дивимося на вміст буфера обміну ще раз:

image

Пробуємо вставити зображення в Skype, і…

image

Здорово!

Зрозуміло, запускати вручну кожен раз при копіюванні окремий виконуваний файл — не найкраща ідея, так що пропоную озброїтися OllyDbg і почати робити це автоматично. Так, можна покликати відповідний код безпосередньо з модуля OllyDbg, але навіщо, якщо у нас вже є готова програма?

Копіюємо mspaint.exe з "%WINDIR%\System32" у будь-яку іншу директорію, прибираємо використання технології ASLR за допомогою PE Tools (цей процес вже був описаний кілька разів в попередніх статтях — наприклад, тут), запускаємо Paint OllyDbg і бачимо наступне собщение:

image

Що ж, раніше ми вже мали справу з зміною поведінки додатки у разі зміни його оточення, так що давайте створимо директорію під назвою «en-US» (у Вашому випадку воно, зрозуміло, може відрізнятися), і покладемо туди файл mspaint.exe.mui.

Так, тепер Paint запускається коректно:

image

Переходимо в модуль User32 (right-click по вікну CPU -> View -> Module 'USER32'), натискаємо Ctrl-N і шукаємо у списку імен SetClipboardData:

image

Ставимо бряк на початок даної функції, копіюємо що-небудь в буфер обміну з вікна mspaint'а і дивимося на call stack:

image

Стрибаємо на найближчий «користувацький» код, який в даному випадку знаходиться за адресою 0x104FDE3 і дивимося на «оточення»:

image

Відмінно, за адресою 0x0104FDE8 можна буде розташувати стрибок на наш code cave. Давайте продумаємо, як він буде виглядати:

; Зберігаємо стан регістрів на стеку
PUSHAD
PUSHFD

; Викликаємо функцію ShellExecuteA
PUSH 0 ; nShowCmd
PUSH 0 ; lpDirectory
PUSH 0 ; lpParameters
PUSH "cb_helper.exe" ; lpFile
PUSH "open" ; lpOperation
PUSH 0 ; hwnd
CALL ShellExecuteA

; Повертаємо стан регістрів в "початковий" стан
POPFD
POPAD

; Виконуємо інструкції, які повинні були здійснитися замість нашого code cave'а
MOV EAX,DWORD PTR DS:[ESI]
PUSH EDI
MOV ECX,ESI
JMP 0x0104FDFD

Тепер необхідно дізнатися адресу в IAT, за яким знаходиться адресу функції ShellExecuteA. Завантажуємо mspaint.exe в PE Tools, натискаємо на кнопку «Directories», розкриваємо пункт «Import Directory» і клацаємо по SHELL32.dll (саме там, згідно документації, знаходиться реалізація даної функції):

image

На жаль, серед імпортованих з SHELL32.dll функцій немає ні імені ShellExecute ні Застосунком ні system (втім, є імпорт функції ShellExecuteExW, але в нашому випадку вона дещо надлишкова). Може бути, вона імпортується з ординалу?

image

Давайте дізнаємося, який ординал їй відповідає. Для вирішення цього завдання я скористався утилітою dumpbin, доступною з VS Command Prompt:

dumpbin /exports shell32.dll

Microsoft ® COFF/PE Dumper Version 11.00.60610.1
Copyright © Microsoft Corporation. All rights reserved.

Dump of file shell32.dll

File Type: DLL

Section contains the following exports for SHELL32.dll

00000000 characteristics
505A94F2 time date stamp Thu Sep 20 08:00:50 2012
0.00 version
2 ordinal base
930 number of functions
349 number of names

ordinal hint RVA name
[...]
451 12E 0027CF17 ShellExecuteA
452 12F 0027CFA0 ShellExecuteEx
453 130 0027CFA0 ShellExecuteExA
454 131 0006CD23 ShellExecuteExW
455 132 001F8CAB ShellExecuteW

Як бачите, імпортуватися із ординалу вона також не може.

Що ж, тоді у нас є наступні варіанти:
  • Переписати код з C# на WinAPI і викликати необхідні для роботи з clipboard'ом WinAPI-функції безпосередньо з модуля mspaint
  • Отримати адресу функції ShellExecuteA за допомогою зв'язки функцій дзвінки на loadlibrary і GetProcAddress, які є в IAT
  • Додати функцію ShellExecuteA в IAT самостійно
Перше рішення уткнется в приблизно ту ж проблему, з якою ми маємо справу зараз — ні одна з WinAPI-функцій, необхідних для роботи з clipboard'ом, на даний момент безпосередньо не імпортується в досліджуване додаток. Взаємодія з буфером обміну здійснюється через OLE, що, на мій погляд, є не найзручнішим варіантом для патчінга.
Друге рішення буде працювати, проте, на мою думку, виглядає не дуже елегантним.
А ось третє рішення виглядає досить заманливо.

Для додавання нової функції в IAT я вирішив скористатися програмою під назвою CFF Explorer, що входить до складу Explorer Suite. Відкриваємо в ній mspaint.exe заходимо у вкладку «Import Adder», натискаємо на кнопку «Add», вказуємо шлях до файлу shell32.dll ("%WINDIR%\System32\shell32.dll"), вибираємо з отобразившегося списку функцію ShellExecuteA і послідовно натискуємо кнопки «Import By Name» і «Rebuild Import Table», після чого зберігаємо зміни:

image

В результаті наших дій у вкладці «Import Directory» тієї ж тулзы має з'явитися такий запис:

image

Дивно, але виконання цих кроків призвело до різних результатів на різних версіях Windows. В результаті виконання даних операцій на Windows 7 ми отримуємо бінарники, який містить замість адреси функції ShellExecuteA якусь дурницю, але якщо виконати всі ці дії на Windows XP, то все працює так, як очікується. На момент написання цієї статті я перебував у процесі спілкування з користувачем -=AkaBOSS=- exelab, щоб з'ясувати причину цієї поведінки.

Взявши в руки бінарники, отриманий в результаті роботи програми CFF Explorer на Windows XP, я відкрив його на моїй основній системі OllyDbg і подивився, що знаходиться за адресою 0x01617198. Чому саме ця адреса? Тому, що модуль mspaint завантажився за адресою 0x01000000 (втім, він і не міг завантажитися по якій-небудь іншій базі, адже ми відключили ASLR раніше)

image

, а CFF Explorer повідомив нам, що потрібно дивитися на зсув 0x00617198. 0x01000000 + 0x00617198 = 0x01617198.

image

Як бачите, тут дійсно знаходиться адресу функції ShellExecuteA.

Шукаємо місце для code cave'а і пишемо наступний код (зрозуміло, адреси можуть відрізнятися):

0108977D . 6F 70 65 6E 00 ASCII "open",0
01089782 . 63 62 5F 68 65 6C 70 65 72 2E 65 78 65 00 ASCII "cb_helper.exe",0
01089790 > 60 PUSHAD
01089791 . 9C PUSHFD
01089792 . 6A 00 PUSH 0 ; /IsShown = 0
01089794 . 6A 00 PUSH 0 ; |DefDir = NULL
01089796 . 6A 00 PUSH 0 ; |Parameters = NULL
01089798 . 68 82970801 PUSH mspaint.01089782 ; |FileName = "cb_helper.exe"
0108979D . 68 7D970801 PUSH mspaint.0108977D ; |Operation = "open"
010897A2 . 6A 00 PUSH 0 ; |hWnd = NULL
010897A4 . FF15 98716101 CALL DWORD PTR DS:[<&shell32.ShellExecuteA>] ; \ShellExecuteA
010897AA . 9D POPFD
010897AB . 61 POPAD
010897AC . 8B06 MOV EAX,DWORD PTR DS:[ESI]
010897AE . 57 PUSH EDI
010897AF . 8BCE MOV ECX,ESI
010897B1 .^ E9 4766FCFF JMP mspaint.0104FDFD

Тепер додаємо стрибок на наш code cave після виклику процедури, що відповідає за додавання даних в clipboard:

0104FDE3 . E8 B6A70100 CALL <JMP.&MFC42u.#2066>
0104FDE8 . E9 A3990300 JMP mspaint.01089790
0104FDED . EB 0E JMP SHORT mspaint.0104FDFD

Зберігаємо зміни у виконуваний файл і насолоджуємося прямий вставка вмісту буфера обміну з mspaint в Skype.

Післямова
Прийшла пора прощатися з файлами «2.PNG» і «3.PNG» від творців «1.PNG», які лише хотіли відправити зображення своїм співрозмовникам в Skype. Не лінуйтеся придумувати рандомные імена своїх файлів? Тоді не лінуйтеся і OllyDbg відкрити.

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

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

0 коментарів

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