Статья [reversing] Магия Ctrl-C Ctrl-V, или как перестать сохранять картинки и начать жить

Dragokas

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


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

Несмотря на то, что в самом Snipping Tool имеется возможность обводить определённые части изображения, порой этого недостаточно:

  • Во-первых, «Ножницы» не умеют обрабатывать комбинацию клавиш Ctrl-Z, т.е. сделать в них Undo не получится, в связи с чем одна-единственная ошибка в редактировании может заставить начать всё с начала
  • Во-вторых, обводить изображение можно только при помощи Pen'а и Highlighter'а, что не очень удобно, когда надо, например, указать на прямоугольную область

Именно по этим причинам я зачастую обращаюсь к mspaint. А вот у него есть обратный недостаток — вставить изображение напрямую из буфера обмена в Skype уже не получится.

В чём же причина такого поведения? Можно ли его исправить? Давайте разберёмся.

Как протекал процесс, и что из этого вышло, читайте под катом.
InsideClipboard. Скачиваем, разархивируем и запускаем InsideClipboard, открываем «Ножницы» (Win-R -> snippingtool), выделяем какой-нибудь регион (в моём случае это небольшая часть чёрного фона на рабочем столе), нажимаем F5 в InsideClipboard и видим следующую картину:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Faa5%2F404%2F784%2Faa54047848233aedd8529c13fd4dc0cf.png&hash=b1c72b60e42b4aa8d84c0f5056d44211


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

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Fd04%2F93b%2Fdcc%2Fd0493bdcc2afc355f6ea677b5d65e153.png&hash=ff57ef057bbb45e36cdd443124ee91a9


Помимо самого bitmap'а в данном случае мы можем наблюдать в буфере обмена какую-то дополнительную информацию. Может быть, Skype'у именно она и мешает при вставке изображения?

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

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Fa2f%2F06a%2Fc77%2Fa2f06ac772ed0a8388ed9ae272dcb0d1.png&hash=05bd5c8258d9865e2423d02a5f9b6f1c


Отлично, жмём на кнопку «Monitoring Files Library»

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F435%2F5a8%2F086%2F4355a8086b1ea1da1aab5b3466eea570.png&hash=e50828209835b80b1b744dee50def7a0


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

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

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F34d%2F66c%2F1fa%2F34d66c1fa2221d7293d402de882495ab.png&hash=95c8121be0fef663097c86cbe295a3dd


Вставляем в окно ввода сообщений Skype'а изображение из snippingtool и смотрим на цепочку вызовов:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F17d%2F905%2Fc13%2F17d905c136bd840d9b1febabaa8b06c7.png&hash=6416fe87ab2c951863bad244a4a55e9a


Теперь проделываем то же самое с mspaint:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F461%2Fef0%2F5f6%2F461ef05f60ed38aa25b32fec47def155.png&hash=07d858d9375809f303dd8f14211e0173


Обратите внимание, что функция 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
transfer data 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 функций:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F42d%2F500%2F513%2F42d500513328fdc000cc82860614ecdf.png&hash=7d69b4494b7aa1134b699f531c9247f6


Видимо, Skype действительно при встрече в буфере обмена данных, связанных с OLE, перестаёт думать, что в нём находится полноценное изображение. Не могу сказать, баг это или фича, но меня такое поведение явно не устраивает.

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

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Fef6%2Ff6e%2F618%2Fef6f6e61888b1fbb35e6067b4ad08c15.png&hash=12d48d71063a00ae700a3b26d2bbb629


Но подождите! Изображение ведь действительно есть в буфере обмена, раз мы можем скопировать его, например, в тот же 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'а

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Fa41%2F8f3%2Fafc%2Fa418f3afc1d38174f95bdbfc38b49b06.png&hash=72154e7d0210d5994442527d8e3d173a


, запускаем наше приложение и смотрим на содержимое буфера обмена ещё раз:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F29b%2F385%2F809%2F29b385809c9f6a3de4b205c60f762cfc.png&hash=9e84ebbdaf30552e83c13e9af1682a57


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

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F69f%2Fe7c%2F40a%2F69fe7c40a7110130b338bc5b2ea577eb.png&hash=81aeb1b82d501230607e6c762672c9c7


Здорово!

Разумеется, запускать вручную каждый раз при копировании отдельный исполняемый файл — не самая лучшая идея, так что предлагаю вооружиться OllyDbg и начать делать это автоматически. Да, можно позвать соответствующий код напрямую из модуля OllyDbg, но зачем, если у нас уже есть готовая программа?

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

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Ff33%2Fe89%2Fcf9%2Ff33e89cf95624cb7fffde65df26051d7.png&hash=2ab94ece7c410cce0413ebfe9d81b23a


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

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

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F267%2F0e3%2Fd62%2F2670e3d62ce21535539810d68edda2c8.png&hash=85b48ee95dbfda816ac2e7968e9903a7


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

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Ff2e%2Fc36%2Feee%2Ff2ec36eee1b2628061ee398ac03a8741.png&hash=b2f709a90fb9995064ad2be653ab0f00


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

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F420%2F28b%2F32b%2F42028b32bbad392c6723e2874de1e449.png&hash=973d922717ec311e88dd8961c7f4548b


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

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Fdab%2F74f%2F12a%2Fdab74f12a56000cac472cc9e13d91d7e.png&hash=747815a4699b438aad214d8a1553c3bc


Отлично, по адресу 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 (именно там, согласно документации, находится реализация данной функции):

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F402%2Ff8e%2Fa6c%2F402f8ea6c5e98cfd69da047543df840e.png&hash=3edde753437b2a4421b8a6464a524678


К сожалению, среди импортируемых из SHELL32.dll функций нет ни имени ShellExecute, ни CreateProcess, ни system (впрочем, имеется импорт функции ShellExecuteExW, но в нашем случае она несколько избыточна). Может быть, она импортируется по ординалу?

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F12b%2F740%2F952%2F12b7409520ef5e996773cf21382d1a1c.png&hash=6aa56916a220493a9a2d895610df2520


Давайте узнаем, какой ординал ей соответствует. Для решения этой задачи я воспользовался утилитой dumpbin, доступной из VS Command Prompt:

dumpbin /exports shell32.dll

Microsoft (R) COFF/PE Dumper Version 11.00.60610.1
Copyright (C) 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», после чего сохраняем изменения:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F0b2%2F85c%2Fdd6%2F0b285cdd6ccff7bb096f9b50304bd19b.png&hash=3e63c35d298f446a1cf45fc4c35992d4


В результате наших действий во вкладке «Import Directory» той же тулзы должна появиться следующая запись:

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Ff33%2F044%2F4fe%2Ff330444fee106ba0be832b8098891d07.png&hash=e5b38a2fea1604a09c21d15dd1a19ae6


Странно, но выполнение данных шагов привело к разным результатам на разных версиях Windows. В результате проделывания данных операций на Windows 7 мы получаем бинарник, который содержит вместо адреса функции ShellExecuteA какую-то ерунду, но если выполнить все эти действия на Windows XP, то всё работает так, как и ожидается. На момент написания данной статьи я находился в процессе общения с пользователем -=AkaBOSS=- с exelab, чтобы выяснить причину данного поведения.

Взяв в руки бинарник, полученный в результате работы программы CFF Explorer на Windows XP, я открыл его на моей основной системе в OllyDbg и посмотрел, что находится по адресу 0x01617198. Почему именно этот адрес? Потому, что модуль mspaint загрузился по адресу 0x01000000 (впрочем, он и не мог загрузиться по какой-либо другой базе, ведь мы отключили ASLR ранее)

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2Ffa5%2Fff9%2F3de%2Ffa5ff93deae1932fa7db49cd67505938.png&hash=5ac0023ddadba5fac41c91d6cb3940b9


, а CFF Explorer сообщил нам, что нужно смотреть на смещение 0x00617198. 0x01000000 + 0x00617198 = 0x01617198.

proxy.php?image=https%3A%2F%2Fhabrastorage.org%2Fgetpro%2Fhabr%2Fpost_images%2F665%2F258%2F250%2F6652582509aabe7f6b7d878d0f8e20ff.png&hash=0408c323e866386dde7d227fa0a81b21


Как видите, тут действительно находится адрес функции 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 открыть.

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

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