Microsoft Calculator Plus

Решение проблем с размерами окна.
  1. Оффлайн

    Автор темы

    Leserg

    Звание: Ветеран

    Команда сайта

    Сообщений: 929

    Создано тем: 79

    Рейтинг: 8

    Репа: (131|131|0)

    Баллы: 1596

    Был: 2024-04-28 00:36

    Лайков: 140

    Microsoft Calculator Plus


    ВОПРОС:
    Как увеличить размер окна по вертикали, если в редакторе ресурсов оно растянуто до нужных размеров?

    Microsoft Calculator Plus

    Рис. 1


    После запуска окно обрезано снизу:


    Рис. 2

    Кто ищет, тот всегда найдет!

    21 апреля 2024 - 10:43 / #1
  2. Оффлайн

    Автор темы

    Leserg

    Звание: Ветеран

    Команда сайта

    Сообщений: 929

    Создано тем: 79

    Рейтинг: 8

    Репа: (131|131|0)

    Баллы: 1596

    Был: 2024-04-28 00:36

    Лайков: 140

    ОТВЕТ:

    Инструменты:
    - отладчик x64dbg (в описании используется англоязычный интерфейс);
    - редактор ресурсов Resource Hacker;
    - сканер оконных классов WinSpy++.

    Решение:

    Если изменение свойств окна в ресурсах программы не приводит к положительному результату, значит проблему будем искать в коде. Очевидно что, при создании, при загрузке ресурса диалога или вывода его на экран, для него выполняется установка определённого значения высоты. Обращение к любому элементу интерфейса программы, к примеру для изменения какого-либо свойства, выполняется по идентификатору (Id) этого элемента, который можно узнать из любого редактора ресурсов. Открываем файл программы в Resource Hacker. Из него узнаём, что идентификатор этого диалога равен 101.

    Microsoft Calculator Plus

    Рис. 1


    Значение 101 в шестнадцатеричном формате счисления будет представлено числом 65h. Поищем его в коде программы при помощи отладчика. Загружаем файл программы calc.exe в отладчик x64dbg. После загрузки мы находимся на точке входа программы. Будем искать константу (число 101 (65h) - это константа). Находясь на вкладке "CPU" вызываем окно поиска констант (команда контекстного меню "Search for -> Current Region -> Constant")


    Рис. 2


    Искать необходимо именно в коде программы (обычно первая секция файла), поэтому мы ограничиваем границы поиска именно регионом, который открыт на вкладке "CPU". В открывшемся окне в поле Signed вводим десятичное значение идентификатора 101 (или можно ввести шестнадцатеричное значение идентификатора, но уже в поле Expression) и подтверждаем ввод, нажав ОК.


    Рис. 3


    Будет найдено приличное количество инструкций, в которых присутствует константа 101 (65h). Все адреса этих инструкций будут выведены на вкладке "References" отладчика. В первую очередь следует обратить внимание на инструкции вида PUSH и MOV. Например, отфильтруйте список по инструкции PUSH (для этого в нижней части вкладки "References" в поле Search введите слово push):


    Рис. 4


    С этой инструкцией для константы 101 (65h) имеется всего лишь два адреса. Перейдем в код по первому адресу. Он окажется довольно интересным: сразу бросаются в глаза API оконных функций. Поэтому делаем для себя пометку, что этот участок кода нуждается в дальнейшем исследовании. Я обычно устанавливаю точку останова [F2]. Переходим в код по второму адресу. А тут и нет ничего для нас интересного. Причём по характеру ассемблерного кода видно, что это и не код вовсе, а строковые данные. В этом вы можете легко убедиться, а в будущем проверять, если у вас будут сомнения. Просто откройте этот участок кода в дампе (команда контекстного меню "Follow in Dump -> Selected address"):


    Рис. 5


    Это массив строк, который отладчик интерпретировал как код. Здесь нам точно нечего делать, поэтому возвращаемся в код по первому адресу.


    Рис. 6


    Функция CreateDialogParamW выполняет создание не модального диалогового окна из шаблона диалога в ресурсах программы. В данном случае шаблоном окна служит ресурс диалога с идентификатором 101 (65h). Далее идет загрузка меню из ресурсов - функция LoadMenuW. Так как диалог загружается из ресурсов, то, естественно и свойства диалога (ширина и высота) также автоматом принимаются из ресурса. Смотрим код далее, опускаясь ниже. С адреса 01003120 начинается участок, который обходится условным переходом по адресу 0100311A. Но если этот код работает, то здесь выполняются какие-то манипуляции с окном программы, что может приводить к его изменению. Это видно по именам функций API:
    - GetWindowRect - возвращает глобальные координаты и размеры окна (на экране);
    - GetClientRect - возвращает локальные координаты и размеры клиентской области окна;
    - MapWindowPoints - преобразует (сопоставляет) набор точек из координатного пространства относительно одного окна в координатное пространство относительно другого окна;
    - OffsetRect - выполняет перемещение прямоугольника окна на указанное смещение по Х и Y;
    - SetWindowPos - устанавливает размеры и положение окна на экране.


    Рис. 7


    Самая интересная для нас функция - это SetWindowPos. Чтобы проверить, работает ли этот код при запуске программы, установим точку останова на адрес с условным переходом (0100311A на рис. 7). Запустим программу под отладчиком по [F9]. Когда отладчик остановит выполнение программы на нашей точке останова, то мы увидим, что интересуемый нас участок кода с манипуляциями окна выполняться не будет, и работа будет продолжена за функцией SetWindowPos, с адреса 010031B1. Если пройтись по коду подпрограммы до её конца (RET 8), то функция SetWindowPos встретится ещё раз по адресу 010033AB, но снова под условным переходом. Поставим точку останова на адрес 01003396 (условный переход) и продолжим выполнение программы по [F9]. Здесь снова непруха - функция SetWindowPos не будет работать. Убираем эту точку останова. Идём ниже и почти в самом конце находится функция ShowWindow (по адресу 01003505), которая выводит окно программы на экран. После её вызова на экране появится окно программы калькулятора. Проверим. Ставим точку останова на адрес 01003505 и продолжаем выполнение кода по [F9]. После остановки убираем эту точку останова и нажимаем в отладчике клавишу [F8] (шаг с обходом). На экране покажется пустое окно программы с главным меню и это окно уже будет иметь усеченные размеры. А чтобы интерфейс программы загрузился полностью, нужно выполнить функцию UpdateWindow по адресу 01003511. В итоге продолжите выполнение кода по [F9]. Окно программы на экране. С помощью WinSpy++ узнаем текущие размеры окна: ширина 398 (18Eh) и высота 355 (163h). Высоту нужно увеличить, но на сколько. Попробуем изменить её значение при помощи все той же WinSpy++.


    Рис. 8


    Опа, а сделать это не получается. Значение высоты не изменяется и стоит как вкопанное. А вот значение ширины можно свободно изменять. Это говорит о том, что программа постоянно мониторит высоту окна программы и как только она изменяется хоть на один пиксель, тут же возвращает исходное значение. Также это говорит ещё и том, что размеры окна устанавливаются уже в процессе работы программы, а не во время инициализации, т.е. уже после или во время работы функции ShowWindow. Делается это при помощи уже известной нам функции SetWindowPos.

    Возвращаемся в отладчик (программа у нас все еще работает под отладчиком); задаем поиск всех системных вызовов (кнопка на панели инструментов "Find Intermodular Call"); среди них отфильтровываем вызов функции SetWindowPos и устанавливаем на них точки останова (команда контекстного меню "Set breakpoint on all command").


    Рис. 9


    Теперь переключаемся обратно на инструмент WinSpy++ и снова пытаемся изменить высоту окна программы. Отладчик тут же прервёт её выполнение и покажет, где это произошло. А это сработала одна из точек останова, установленных на обращении к функции SetWindowPos.


    Рис. 10


    Точку останова по адресу 01010D1F оставляем, а все остальные для функций SetWindowPos убираем. Теперь нам известно место в коде, где изменяется размер окна. Смотрите, чуть выше по адресу 01010CFE (см. рис. 10) вызывается функция GetWindowRect, которая возвращает текущие размеры и положение окна. А еще выше, по адресу 01010CEE, находится инструкция условного перехода, что подразумевает возможность обхода участка с установкой размеров окна, в котором мы сейчас находимся. По идее, если сделать безусловный обход этого кода, то размеры окна будут иметь начальное значение, заданное в ресурсах. Проверим. Изменяем условный переход по адресу 01010CEE на безусловный. Для этого щёлкаем по строке с этим адресом и нажимаем пробел (или команда контекстного меню "Assemble"). Откроется окно редактирования ассемблерных команд. В нём вместо инструкции JE введите JMP (обязательно отметьте опцию "Fill with NOPs") и подтвердите изменения, нажав ОК.


    Рис. 11


    Получится вот так:


    Рис. 12




    Нажмите в отладчике [F9], чтобы продолжить выполнение программы, и вернитесь к окну WinSpy++. Снова попробуйте изменить высоту окна программы и вы увидите, что теперь это сделать стало возможным. Теперь, если сохранить сделанные в коде изменения в файл, то окно программы должно принимать начальные размеры сразу при запуске. Возвращаемся в отладчик и сохраняем сделанные нами изменения в новый файл (и в файл патча). Для этого нажимаем на панели инструментов кнопку "Patches" [Ctrl+P]. Откроется окно данных патча.


    Рис. 13


    Нажмите "Patch File" Укажите новое имя файла, например calc1.exe, и сохраните его. После запустите. Перед вами будет окно программы в полном виде.


    Рис. 14


    На этом можно было бы и завершить; мы получили желаемый результат. Но может возникнуть резонный вопрос: "А вдруг этот код, который мы обходим, важен для приложениям и его нельзя вот так взять и выбросить? Может возможен другой вариант?" Что же, давайте посмотрим.

    Перезагружаем программу в отладчике и переходим в код по адресу 01010D1F, где идет вызов функции SetWindowPos (здесь мы устанавливали точку прерывания). Выше (см. рис. 10), по адресу 01010CFE, идет обращение к функции GetWindowRect, которая возвращает глобальные координаты окна (прямоугольную область с координатами X,Y левого верхнего угла и координаты X1, Y1 правого нижнего угла). Проверяем. Устанавливаем точку останова на адрес 01010CFE и запускаем программу на выполнение по [F9]. После останова на экране появилось пустое окно программы; не нужно по нему клацать и пытаться куда-то передвинуть - оно еще не активно. Кстати обратите внимание, что окно имеет правильные размеры, согласно изменениям диалога 101 ресурсах программы.


    Рис. 15


    В регистре EAX сейчас находится адрес, по которому функция GetWindowRect вернёт координаты клиентской области. Откройте этот адрес в дампе по команде контекстного меню "Follow in Dump".


    Рис. 16


    Теперь в окне "CPU" выполните инструкцию GetWindowRect, нажав клавишу [F8] (шаг с обходом), смотрим в одно дампа.


    Рис. 17


    Имеем следующие координаты прямоугольной области окна (обратный порядок байт):
    Х=34h (52d)
    Y=34h (52d)
    X1=1C2h (450d)
    Y1=1BAh (442d)

    Ширина окна (W): 450-52=398 пикс.
    Высота окна (H): 442-52=390 пикс.

    Дальше в инструкциях по адресам 01010D04 и 01010D07 вычисляется высота окна, так как только что сделали это мы. Проходим их по [F8] и получаем в регистре EAX значение 186h (390d). Следующей инструкцией по адресу 01010D0A в стек заносится значение 16 - проходим её по [F8]. Сейчас мы на адресе 01010D0C с инструкцией add eax, esi. Здесь к значению в регистре EAX прибавляется значение в регистре ESI. Мы знаем, что в регистре EAX у нас высота окна (нормальная, какая нужна), а что у нас в регистре ESI - значение FFFFFFDD. Это отрицательное число. Щелкните по значению регистра ESI правой кнопкой мышки и в контекстном меню выберите команду "Modify value" (Изменить значение) или нажмите клавишу [Enter]. В открывшемся окошке редактора в поле "Signed" вы увидите значение -35.


    Рис. 18


    Если эта инструкция будет выполнена, то высота окна уменьшится до 390 + (-35) = 355 пикселей. Поэтому здесь можно сделать патч. Чтобы эта инструкция не выполнялась, просто заNOPим её (щелкните по ней и нажмите комбинацию клавиш [Ctrl+9]. Откроется окно с количеством команд NOP, которыми будет заменена инструкция. Длина инструкции add eax, esi два байта, значит и команд NOP тоже 2. Нажимаем ОК. Или можете вручную дважды ввести опкод 90, открыв редактор двоичных данных для этой инструкции. Получится вот так.


    Рис. 19


    Сохраните изменения в новый файл, как было рассказано выше. Запустите его. Мы снова получили желаемый результат.

    "А откуда берётся значение FFFFFFDD в регистре ESI?" - спросите вы. Будем разбираться дальше.

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


    Рис. 20


    По адресу 01010CB5 вызывается функция GetDlgItem, которая возвращает дескриптор элемента управления в указанном диалоговом окне. Параметрами этой функции являются дескриптор диалогового окна и идентификатор элемента управления. Так, по адресу 01010CAA в стек заносится значение 3E8 - это идентификатор элемента управления, а по адресу 01010CAF - дескриптор окна. Тут следует сделать небольшое пояснение касательно параметров функций. К примеру, синтаксис функции GetDlgItem следующий

    HWND GetDlgItem(
      [in, optional] HWND hDlg, - дескриптор диалогового окна
      [in]           int  nIDDlgItem  - идентификатор элемента управления
    );


    То есть первым параметром идёт дескриптор диалогового окна, а вторым параметром - идентификатор элемента управления.

    В отладчике, в листинге дизассемблера, порядок передачи параметров в функцию выполняется в обратном порядке. Сначала идентификатор элемента управления, а потом дескриптор диалогового окна. Это правило касается всех (любых функций). Именно поэтому ранее и было сказано, что по адресу 01010CAA в стек заносится значение 3E8 - это идентификатор элемента управления, а по адресу 01010CAF - дескриптор окна.

    Далее, по адресу 01010CCA, вызывается функции GetClientRect, которая возвращает координаты клиентской области для этого элемента управления. Потом, по адресу 01010CDD, функцией MapWindowPoints координаты элемента управления преобразуются в координаты клиентской области окна, на котором находится этот элемент управления. По сути получаются координаты элемента управления в пикселях относительно окна. И, наконец, с адреса 01010CE3 по 01010CEC включительно выполняется вычисление значения для регистра ESI. Это значение является разницей координат Y элемента управления и окна. Если значение не равно нулю, то выполняется код с адреса 01010CF4 по 01010D25 (см. рис. 20), который устанавливает высоту окна по значению координаты Y элемента управления с идентификатором 3E8h.

    Осталось разобраться с элементом управления, который имеет идентификатор 3E8h (в десятичном формате - 1000). Запускаем редактор Resource Hacker и открываем в нём файл программы. Смотрим диалог 101, который был модифицирован. Хм, действительно, есть такой элемент с идентификатором под номером 1000. Его координата Y равна 162 в единицах диалога, размеры составляют 1х1. Немудрено, что при редактировании диалога такой элемент можно упустить из вида. Кстати, наверное вы обратили внимание, что в ресурсах использована специальная система измерений, выраженная в единицах диалога (dialog units). В этой системе фактические размеры диалога в пикселях зависят от размера используемого в диалоге шрифта.


    Рис. 21


    Похоже, что этот элемент управления разработчик программы использовал в качестве маячка, по которому выполняет программную фиксацию высоты диалога. Что нужно сделать? Правильно, увеличить координату Y со значением 162. В оригинальной программе это значение было больше на 2 единицы меньше значения высоты диалога (162 против 164). После редактирования диалога его высота стала 182 единиц, поэтому устанавливаем значение координаты Y равное 180. После сохраняем сделанные изменения в новый файл. Запускаем его и смотрим.


    Рис. 22


    Ну теперь-то уж точно все! ura

    Справочная:
    1. Информация по оконным функциям: ссылка
    2. Перечень всех оконных классов: ссылка
    NNK_RTR, Смотрящий, 78Sergey нравится это сообщение.

    Кто ищет, тот всегда найдет!

    21 апреля 2024 - 11:15 / #2

Статистика форума, пользователей онлайн: 0 (за последние 20 минут)

---
Создано тем
1177
Всего сообщений
15352
Пользователей
17856
Новый участник
Daniel7375ysk