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 Files Library»

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
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 функций:

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, ни CreateProcess, ни system (впрочем, имеется импорт функции ShellExecuteExW, но в нашем случае она несколько избыточна). Может быть, она импортируется по ординалу?

image

Давайте узнаем, какой ординал ей соответствует. Для решения этой задачи я воспользовался утилитой 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», после чего сохраняем изменения:

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 открыть.

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

Комментарии (31)


  1. name1ess0ne
    22.07.2015 20:06
    +1

    А почему вы просто не взяли какой-нибудь скриншоттер, их же полно и они решат вашу проблему на 100%.


    1. maeln0r
      22.07.2015 21:19
      +12

      Но согласитесь, это куда круче выглядит! Месье знает толк.


      1. PsyHaSTe
        23.07.2015 11:09

        Не знаю, давно пользуюсь paint.Net вместо обычного, он умеет вставлять в скайп, я даже сначала удивился, что встроенный пейнт этого не умеет. Думал, скайп так работает с любым изображением в буфере. Проверил сейчас — очень удивился. Разве что названия он генерирует не очень, по схеме i%number%^cimgpsh


  1. fog
    22.07.2015 20:18
    -1

    Я понимаю, что пост не об этом, но просто хочу порекомендовать Clip2net для этих целей.


    1. Stalker_RED
      22.07.2015 20:56
      +10

      Clip2net — одна из самых ужасных утилит такого плана. Обилие рекламы, попапы, ограниченный срок хранения изображений (в некоторых случаях — всего сутки!) и прочие прелести.

      В то-же время существую отличные opensource альтернативы типа ShareX _https://getsharex.com/
      Умеет сохранять на диск и/или разные хостинги на выбор (imgur, dropbox, еще 22 других варианта и настраиваемый свой)
      Имеет довольно удобный редактор (размытие, подсветка, рамочки, стрелочки, вот это всё) и можно подключить сторонний редактор.
      Я уж молчу про хоткеи

      Из минусов — разве что многовато настроек, и по умолчанию включено слишком много свистелок.


      1. maeln0r
        22.07.2015 21:17

        За sharex спасибо. Сам использовал долгое время droplr, но про известными причинами отказался в пользу monosnap.


      1. name1ess0ne
        22.07.2015 21:26

        Попробуйте тогда уж и CloudShot


      1. driessen
        23.07.2015 10:50

        ShareX классный, но к сожалению как-то коряво иногда работает с FTP. На работе стоит FTP-сервер с довольно специфическими настройками, например, можно заливать файлы, но нельзя просматривать список файлов. И походу крышу сносит ShareX от этого. Исключение выплёвывает, а описания исключения нет.


        1. Stalker_RED
          23.07.2015 11:21

          Но есть исходники, и если очень хочется — можно разобраться что к чему. Я заливаю на imgur — всё отлично раздается и хранится вечно.


          1. driessen
            23.07.2015 11:48

            Да, я качал исходники. И проект даже запустился. Но так и не смог понять где баг. Походу где-то в либах .NET.


    1. MaximChistov
      22.07.2015 21:54

      Gyazo круче! Особенно если Ctrl+Shift+C юзать не лень


      1. VEG
        22.07.2015 22:12

        Кстати, исходные коды первой версии Gyazo доступны здесь. Код небольшой (хоть и с комментариями на японском), поэтому можно достаточно быстро адаптировать под свои нужды. Я, например, научил его пробовать сжимать в JPG и PNG, и выбирать из этого то что лучше справилось с задачей, и добавил запуск редактирования выбранного участка экрана в Paint если выделение было сделано с зажатой кнопкой Shift :) Правда, писалось это исключительно под себя, поэтому там ещё для максимального сжатия изображения запускаются консольные truepng и кодер mozjpeg с моими любимыми настройками. Хотя это делалось уже достаточно давно (как только Gyazo заменил прямые ссылки на файлы изображений страницами с левым контентом помимо изображения), и наверняка новые Open Source скриншотилки уже научились делать всё что нужно без ковыряния в коде :)


    1. Egor3f
      22.07.2015 22:29

      Я ещё от себя порекомендую бесплатный Qip Shot.
      Простой, рекламы не содержит, имеет неплохой редактор, заливает к себе на хостинг(регистрация не нужна), можно получить прямую ссылку на png, + умеет сносно писать видео(можно выбрать кодек вручную и будет писать хорошо). Хоткеи есть и настраиваются.
      Из минусов — редактор всё же не самый лучший.



    1. Firsto
      23.07.2015 08:34
      +1

      Для Win/Mac рекомендую Monosnap.


    1. akamajoris
      23.07.2015 10:10
      +3

      1. fog
        23.07.2015 10:13

        Класс. Пригодится.


      1. PsyHaSTe
        23.07.2015 11:15

        В программах в том-то и плюс, что не нужно пользоваться заливальщиками. Обвел область, накидал если надо пометок, нажал одну кнопку и отправил. Если нужно просто область, то чуть другой хоткей (у меня ctrl+alt+4 например), после этого сразу можно уже разворачивать скайп/мессенджер и вставлять туда ссылку на изображение.

        Сравните с тем, что я раньше с imgur'ом парился: скриншот, открыть пейнт, вставить его, обрезать нужную область, сделать по необходимости пометки, сохранить файл (потом научился прямо из пейнта копировать), зайти на сайт, вставить туда картинку, дождаться, пока скачается (в моноснапе качается намного быстрее, и то, пока вы разворачиваете программу/ищете вкладку, куда вставить хотите), после этого копируете url на картинку и вставляете куда хотели. Отличие от одного шортката на мой взгляд достаточно существенное.


    1. pasha_golub
      23.07.2015 14:50

      Greenshot only.


  1. VEG
    22.07.2015 20:45
    +3

    JMP непонятно куда, перенос части удалённых инструкций в совсем в другое место исполняемого файла и JMP обратно в исходную точку на мой взгляд выглядит грязно. Если это делается автоматически для перехвата вызовов каких-то функций — это одно. Но здесь же вы работаете вручную и вполне можете придумать более красивое решение.

    В вашем случае, например, вы могли бы просто подменить адрес вызова функции, результат работы которой вы хотите изменить, на вызов своей функции, дописанной в конце секции кода (обычно там достаточно много свободного пространства), внутри которой вы могли бы вызвать и оригинальную функцию, и выполнить нужный вам дополнительный код, и вернуться обратно стандартным retn без танцев с бубном. То есть это выглядело бы так, будто в исходном коде вместо прямого вызова функции «A» программист вызвал функцию «B», которая в свою очередь вызвала бы искомую функцию «A» сделала что-то ещё.

    Второе решение будет работать, однако, по моему мнению, выглядит не очень элегантным.
    Любопытно. В этом предложении вы ведёте рассуждение об элегантности, но при этом всё ваше решение заключается в запуске внешнего исполняемого файла, написанного на C#, при каждой операции копирования.


    1. NikitaTrophimov Автор
      22.07.2015 21:03
      +1

      JMP непонятно куда

      Что значит «непонятно куда»? Как раз в «конец секции кода», упомянутый Вами, и осуществляется прыжок.

      JMP обратно в исходную точку на мой взгляд выглядит грязно

      Не на исходную точку. Я просто выполнил три инструкции, которые затёр мой JMP, и ещё одну, которая следовала за ними и представляла собой безусловный переход по определённому адресу в том же модуле.

      В вашем случае, например, вы могли бы просто подменить адрес вызова функции, результат работы которой вы хотите изменить

      Согласен, так я и делал в некоторых других статьях.


      1. VEG
        22.07.2015 21:46
        +6

        Если приводить аналогии из обычного программирования, это выглядит как прямо посреди какой-то фукнции вставлен goto на какой-то код внутри совершенно другого файла, а затем из того файла идёт goto обратно в код этой функции. То есть выходит, что линейный код одной функции находится не в одном месте, а разнесён на несколько частей в совершенно разных частях файла. Понятно, что будет работать и так, но выглядит не очень красиво. А при серьёзной модификации исполняемого файла с сотнями правок (типа этого патча NFS3) подобное «размазывание» кода функций по исполняемому файлу на мой взгляд усложняет дальнейшую модификацию (особенно для тех, кто потом будет изучать ваши правки и дорабатывать код дальше).

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

        Решаемая костылём проблема заключалась в том, что разработчики NFS4 в одной из функций забыли инициализировать одну из переменных в стеке. Для корректной работы функции эта переменная должна была иметь ненулевое значение. И по совершенно случайному стечению обстоятельств после всего кода, который отрабатывал до вызова этой функции, в той ячейке памяти оказывалось значение, которое было в регистре esi при старте приложения. В ранних версиях Windows видимо при старте программы регистры не обнулялись, поэтому всё работало. А в Windows 7 сразу после старта в esi всегда 0, поэтому та функция работала ошибочно и игра отказывалась запускаться. В исследуемом патче была решена изменением точки входа, где в esi устанавливалось 0xFFFFFFFF и управление отдавалось оригинальной точке входа. Таким образом, малейшее изменение в коде программы до вызова функции с ошибкой просто поломало бы этот «фикс». А ведь надо было всего-то добавить корректную инициализацию этой переменной ненулевым значением. Тут конечно всегда возникает вопрос, а как вставить эту строку — и отдельный интерес придумать красивое решение. В итоге я сместил пролог функции на -6 байт (там как раз из-за выравнивания было свободное пространство), добавил инструкцию инициализации проблемной переменной сразу после пролога и исправил адрес этой функции в месте, где она изначально вызывалась. В итоге исправление получилось даже проще, трюк с esi, но при этом этот код не рухнет ни при каких других изменениях.

        Машинный код тоже может быть как красивым, так и кошмарным. Понятно, что один небольшой костыль погоды не сделает, но мне кажется, что для статей всё же стоит подбирать какие-то более элегантные и интересные решения, на которые можно было бы равняться. Не количеством, а качеством.


  1. saaivs
    23.07.2015 12:12

    Для Windows пользователей рекомендую PrtScr


    1. saboteur_kiev
      23.07.2015 18:05

      А обрезать как?
      Кроме того, если два монитора, PrtScr делает скриншот сразу двух. Я не помню случая, когда мне нужно было отослать скриншот обоих мониторов.


      1. Karlson_rwa
        23.07.2015 18:29

        Alt+PrtScr же! Получается скрин только активного окна.


      1. saaivs
        23.07.2015 21:47

        Ctr+мышкой выделяете прямоугольную область.
        Правой кнопкой мыши можно еще что-нибудь обозначить.
        Ctr+Правой кнопкой — выделить прямоугольную область.
        Backspace — удалить если что не так выделили.
        Подробнее тут www.fiastarta.com/PrtScr/Help.html


        1. saboteur_kiev
          24.07.2015 00:21

          Попробуйте Ctrl+мышкой выделить например наши два сообщения в браузере, без всего остального окна?
          Я вот не хочу, чтобы список всех моих закладов наверху было видно, как и например названия других открытых табов.


  1. ssneg
    23.07.2015 13:55

    Интересно, что мешает майкрософту встроить такой же скриншоттер как на маке? Хочешь экран, хочешь окно, хочешь область, хочешь в буфер, хочешь в файл — все доступно системными шорткатами.


    1. Alexeyslav
      23.07.2015 15:25

      Наверняка патенты…


  1. vpetrigo
    23.07.2015 17:18
    +1

    У Яндекс.Диска для Windows похожая возможность есть. Правда нет возможности делать снимок произвольной области, но зато есть встроенный редактор, который и Ctrl+Z поддерживает.


  1. xtraroman
    23.07.2015 18:11
    +5

    Ценность статьи не в самой доделке, а в описании процесса диагностики низкоуровневых вещей, в описании правильных утилит. Я как раз искал чем поковырять внутренности клипборда.