Проблемы локализации WinSpy++

Проблемы локализации WinSpy++(Удлинение жестко-закодированных строк)Тема создана по вопросу от пользователя Metabolic и здесь будет рассказано,
  1. Оффлайн

    Автор темы

    Leserg

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

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

    Сообщений: 933

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

    Рейтинг: 8

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

    Баллы: 1616

    Был: 2024-05-09 15:29

    Лайков: 146

    Проблемы локализации WinSpy++
    (Удлинение жестко-закодированных строк)


    Тема создана по вопросу от пользователя Metabolic и здесь будет рассказано, как можно увеличить длину жестко-закодированных строк, которые отображаются во всплывающих подсказках приложения WinSpy++.

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

    Итак, есть приложение WinSpy++ и вы взялись за его локализацию. Создали проект, перевели строки и собрали локализованный файл. При его отладке вдруг выясняется, что некоторые строки в текстах всплывающих подсказок отображаются не полностью, т.е. обрезаются, как например вот здесь (см. рис. 1):

    Рисунок 1.


    Смотрим исходную строку: "Window Finder Tool", длина строки 18 символов, включая символы пробелов. Кодировка строки Unicode - это означает, что на каждый символ отводится 2 байта.

    Рисунок 2.


    Например, символ "W" в шестнадцатеричном формате записывается двумя байтами 57 00. Значение счётчика обычно соответствует длине строки, поэтому в нашем конкретном случае это число 18, в шестнадцатеричном формате - 12h. Иногда, если строки находятся в формате Unicode (UTF-8), то они могут считываться побайтно, а не посимвольно, поэтому значение счетчика удваивается. Для нашей строки значение счетчика также может быть 18 х 2 = 36, в формате НЕХ — 24h. Со значениями счетчиков определились: или 12h, или 24h. Теперь перевод строки. Допустим вы перевели строку как "Инструмент выбора окна". Её длина — 22 символа (в формате НЕХ — 16h), т.е. больше исходной и в работающем приложении хорошо видно, что она отображается не полностью (нет последних трёх букв). Если вы не хотите влазить в дебри отладчика и разбираться в коде программы, то попробуйте изменить перевод строки так, чтобы её длина была меньше или равнялась длине исходной строки. Например, "Курсор выбора окна", или просто "Выбор окна". В этом случае они всегда будут корректно отображаться в локализованной программе. Но ежели оставить первоначальный вариант перевода строки, то как же всё таки увеличить её длину? Давайте попробуем в этом разобраться.

    Возможные значения счетчиков для строки "Window Finder Tool" мы уже выяснили (12h или 24h). Поищем их в коде программы. Если вы локализуете приложение при помощи Radialix, то наверняка создавали в отладчике IDA файл с картой ссылок для возможности перевода данной строки и ей подобных. Посему можно осмотреть окрестности строки в коде программы прямо из Radialix (при условии, что в IDA в текущий момент загружен файл локализуемого приложения). Двойной клик левой кнопкой мышки по ссылке в проекте Radialix и вы окажетесь в проекте IDA по месту вызова строки.

    Рисунок 3.


    Как видите, по адресу 0040FC95 идёт инструкция MOV, которая записывает адрес с нашей строкой в регистр ESI. Ниже по коду выполняются аналогичные инструкции для других строк ("Keep On-Screen (F4)", "Minimize On Use" и т.д.). По адресу 0040FC8F инструкцией PUSH в стек записывается число 9h. А по адресу 0040FCB4 - число 0Fh. Очевидно, что эти значения и близко не соотносятся с длиной строки. Для других строк наблюдается такая же картина. Возникает вопрос: "А где счетчики?" (И есть ли они вообще?)

    Берем свой любимый отладчик и загружаем в него локализованный файл WinSpy++. Дальнейшее повествование будет вестись на примере отладчика OllyDbg v2.01. Сейчас мы проанализируем и посмотрим, что происходит с нашей строкой во время работы программы. Ранее мы установили, что обращение к проблемной строке происходит по адресу 0040FC95. Перейдите на этот адрес в окне отладчика (клавиатурная команда [Ctrl+G]), и установите здесь точку останова (F2).

    Рисунок 4.


    В поле комментария, напротив инструкции MOV ESI,OFFSET 0041F0C0, я вручную ввел локализованную строку "Инструмент выбора окна", чтобы легче было ориентироваться в коде. Можете поступить также. Сейчас вы можете проверить и убедиться, что здесь идёт обращение именно к этой строке. Для этого щелкните правой кнопкой мышки по этой инструкции и выберите в контекстном меню команду "Follow in Dump -> Immediate constant" (Перейти в дамп -> Непосредственно к константе):

    Рисунок 5.


    Посмотрите в окно дампа, здесь будет показан адрес, по которому записана наша локализованная строка. Переключите отображение текстовой части НЕХ-редактора на отображение строк в формате Unicode, чтобы видеть строку в нормальном виде.

    Рисунок 6.


    Здесь адрес 0041F0C0 — это адрес начала строки (Внимание! В вашем файле адрес будет другой). Хорошо видно, что в формате Unicode на каждую букву отводится два байта. Теперь, запустив программу под отладчиком (F9), в пошаговом режиме посмотрим, что происходит с нашей строкой.

    После запуска программы отладчик остановит её выполнение по адресу 0040FC95, на точке останова. Нажмите F7 (Шаг с заходом), будет выполнена инструкция MOV ESI,OFFSET 0041F0C0. Посмотрите на панель регистров, в регистре ESI появился адрес, который указывает на начало строки "Инструмент выбора окна". Сейчас мы находимся на адресе 0040FC9A с инструкцией LEA EDI,[LOCAL.497], которая запишет в регистр EDI определённый адрес в памяти. Выполним эту инструкцию — F7. Смотрим в панель регистров: в регистре EDI появился адрес 0012F408. Следующая инструкция MOV DWORD PTR [LOCAL.499],EBX по адресу 0040FCA0 запишет в отведенную область памяти значение регистра EBX. Сейчас в этом регистре записано значение FFFFFFFFh. Выполним эту инструкцию — F7. Далее, по адресу 0040FCA6, идёт инструкция MOV DWORD PTR [LOCAL.498],3E9. Она запишет в ячейку памяти значение 3E9h. Возможно это значение является идентификатором строки "Инструмент выбора окна". Снова нажимаем F7 и мы окажемся на адресе 0040FCB0 с инструкцией REP MOVS DWORD PTR [EDI],DWORD PTR [ESI]. Инструкция MOVS выполняет пересылку последовательности байт размером BYTE, WORD или DWORD из одной ячейки памяти в другую ячейку. Префикс REP заставляет циклически выполняться указанную команду до тех пор, пока содержимое регистра ECX (CX) не станет равным нулю. Смотрим на панель регистров. В регистре ECX находится значение 9h, значит инструкция MOVS будет повторяться 9 раз (НЕХ-значение 9h в десятичном формате будет 9). Размер пересылки указан как DWORD, т.е. двойное слово или 4 байта. В регистре ESI у нас находится адрес 0041F0C0, откуда будут копироваться данные и который сейчас указывает на начало строки "Инструмент выбора окна", а регистр EDI содержит адрес 0012F408, куда будет выполняться копирование данных.

    Рисунок 7.


    Таким образом наша строка будет скопирована в новое место (по адресу 0012F408), причём копирование строки будет выполнено блоками по 4 байта. Прежде чем выполнить эту инструкцию, откроем в дампе область памяти с адресом в регистре EDI. Так мы сможем проконтролировать и визуально наблюдать ход выполнения этой операции. Чтобы перейти на интересуемый нас адрес в дампе, на панели регистров щёлкните правой кнопкой мышки по значению регистра EDI и в контекстном меню выберите команду "Follow in Dump" (Перейти в дамп):

    Рисунок 8.


    В окне дампа памяти курсор будет установлен на данные по адресу 0012F408.

    Рисунок 9.


    Теперь нажмите F7 и посмотрите в окно дампа. Что видите? По адресу 0012F408 появились первые 4 байта нашей строки, а именно буквы "И" и "н".

    Рисунок 10.


    Если вы глянете в окно отладчика, то увидите, что курсор выполнения остался на адресе 0040FCB0. Но если вы глянете в панель регистров, то заметите, что значение регистра ECX уменьшилось на единицу, т.е. стало 8h. Это означает, что инструкция цикла была выполнена 1 раз. При этом, смотрите в панель регистров, соответственно на 4 байта увеличились адреса в регистрах EDI и ESI. Таким образом сопроцессор подготовился к выполнению следующей итерации цикла, т.е. копированию следующего блока данных. Нажмите F7 ещё раз. Значение счётчика уменьшится еще на единицу — 7h, а в дампе памяти появится следующий блок данных нашей строки (буквы "с" и "т").

    Рисунок 11.


    Сейчас мы буквально наблюдаем, как идёт выполнение инструкции REP MOVS DWORD PTR [EDI],DWORD PTR [ESI]. Нажмите F7 ещё раз и строка увеличится ещё на 4 байта, а счётчик уменьшиться ещё на единицу. Надеюсь вы поняли, что так строка копируется в новое место блоками по 4 байта, и копирование будет выполняться до тех пор пока значение в регистре ECX не обнулится. А будет ли скопирована вся наша строка? Пройдите все итерации цикла, нажимая F7, пока курсор выполнения в окне отладки не перейдет на следующий адрес (при этом регистр ECX станет равен нулю). Посмотрите в окно дампа. Наша строка скопирована не полностью, а операция копирования уже завершилась.

    Рисунок 12.


    Как видите сейчас у нас в памяти находится строка вида "Инструмент выбора ". В отладчике мы сейчас находимся на адресе 0040FCB2 с инструкцией MOVS WORD PTR [EDI],WORD PTR [ESI]. Это та же инструкция, которую мы разобрали чуть выше, только она без цикла и размер копируемых данных составляет 2 байта (WORD). Выполните её, нажав F7. Посмотрите в окно дампа памяти, наша строка пополнилась ещё одной буквой - "о". В итоге получилась строка вида "Инструмент выбора о" и она будет выведена в качестве подсказки в окне приложения. Если бы это была оригинальная строка, то никаких бы проблем не было. Вы прекрасно видели, что в оригинальной программе строка подсказки отображается полностью. Решение напрашивается само собой: раз увеличилась длина строки, значит нужно увеличить значение счетчика, чтобы копировалась вся строка.


    Продолжение следует...
    kurkoff1965, NNK_RTR нравится это сообщение.
    Сообщение отредактировал 5 февраля 2021 - 02:14

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

    1 декабря 2015 - 00:37 / #1
  2. Оффлайн

    Автор темы

    Leserg

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

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

    Сообщений: 933

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

    Рейтинг: 8

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

    Баллы: 1616

    Был: 2024-05-09 15:29

    Лайков: 146

    Продолжение.

    На сколько нужно увеличить значение счётчика? Давайте посчитаем. Строка копируется блоками по 4 байта. Для вывода одного символа в формате Unicode отводится 2 байта. Выходит, что в блоке размером 4 байта помещается два символа (мы в этом уже убедились - см. рис. 10). Итак, посмотрите на следующий рисунок 13. На нём показана исходная строка и перевод этой строки. Соответственно под каждой из них показаны НЕХ-значения строк, разбитые для удобства и понимания принципа счёта на блоки по 4 байта.

    Рисунок 13.


    Видите, что для оригинальной строки получилось 9 блоков, т.е. значение счетчика равно 9h. Именно это значение было в регистре ECX на начало выполнения инструкции по адресу 0040FCB0. Смотрите код программы чуть выше (рис. 3 или 4): как раз по адресу 0040FC8F в стек заносится значение 9 (инструкция PUSH 9) и извлекается из него в регистр ECX (инструкция POP ECX) по адресу 0040FC94. Для переведенной строки получается 11 блоков по 4 байта. В шестнадцатеричном формате — 0Bh. Понятно теперь, чтобы скопировать всю строку, инструкцию переноса данных нужно выполнить 11 раз, т.е. для счётчика необходимо задать значение 0Bh.

    Проверим наши рассуждения. Перегружаем программу в отладчике (Ctrl+F2) и переходим на адрес 0040FC8F с инструкцией PUSH 9. Нажмите клавишу "Пробел" и измените в инструкции значение 9h на 0Bh. Подтвердите сделанные изменения, нажав "Assemble":[/j]

    Рисунок 14.


    Теперь запустите программу в отладчике — F9. Когда её выполнение будет остановлено на точке останова, в пошаговом режиме (F7) дойдите до адреса 0040FCB0 и остановитесь на нём. Сейчас мы стоим на инструкции копирования строки в новую ячейку памяти. В регистре ECX у нас находится значение 0Bh (11), как мы и рассчитали. Теперь перейдите в дампе памяти на адрес, который находится в регистре EDI (в моем случае это адрес 0012F408), чтобы проконтролировать процесс переноса строки. Возвращаемся в окно отладчика. Если вы не хотите проходить все шаги выполнения цикла, то вместо F7 нажмите F8 (Шаг без захода). После выполнения инструкции по адресу 0040FCB0 посмотрите в панель дампа памяти. Как видите, теперь строка скопирована полностью:

    Рисунок 15.


    Сейчас мы стоим на адресе 0040FCB2 с инструкцией MOVS WORD PTR [EDI],WORD PTR [ESI]. Эта инструкция копирует два байта, которые находятся в конце строки. Это должны быть нулевые байты, т.к. говорят приложению, в этом месте строка завершается. Когда значение счетчика было равно 9, то у нас добавлялась одна буква "о", что конечно было неправильно. После того как мы подкорректировали значение счетчика, то признак окончания строки должен быть установлен правильно. В регистре ESI сейчас у нас находится адрес, который должен указывать на нулевые данные, находящиеся в конце строки. Проверьте это, перейдя на этот адрес в дампе памяти:

    Рисунок 16.


    Все верно, адрес указывает на разрыв строк. Возвращаемся обратно на адрес 0012F408 в дампе памяти и выполняем инструкцию по адресу 0040FCB2, нажав F7. Смотрим в панель дампа, в конце строки появились два нулевых байта.

    Рисунок 17.


    Если вы теперь запустите программу на выполнение (F9), то с удивлением обнаружите, что строка подсказки все равно отображается не полностью! Поэтому не спешите, а посмотрите, что происходит дальше. В отладчике мы стоим на адресе 0040FCB4 с инструкцией PUSH 0F - запись в стек значения 0Fh (15 в десятичном формате). Выполняем её, нажав F7. Cледующая инструкция XOR EAX,EAX выполняет обнуление регистра EAX — жмём F7. Далее идёт инструкция POP ECX, в регистр ECX из стека извлекается значение 0Fh — снова F7. Мы стоим на адресе 0040FCB9 с инструкцией LEA EDI,[LOCAL.488+2]. Эта инструкция записывает в регистр EDI адрес относительно адреса, находящегося в регистре EBP. Далее идёт инструкция REP STOS DWORD PTR [EDI]. С префиксом REP вы уже знакомы - это цикл со счетчиком в регистре ECX, а инструкция STOS записывает данные из регистра EAX размером DWORD (4 байта) в ячейку памяти по адресу из регистра EDI. В регистре EAX находятся нули, в регистре ECX — значение 0Fh, таким образом, с адреса в регистре EDI будут записаны нулевые данные! Куда указывает относительный адрес [LOCAL.488+2]? Это показывает отладчик:

    Рисунок 18.


    Теперь перейдите в окно дампа и посмотрите, где находится адрес 0012F42E. Он находится в данных только что скопированной строки и указывает на букву "к".

    Рисунок 19.


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

    Перейдите в окно отладки (мы все еще на адресе 0040FCB9) и нажмите "Пробел". Откроется окно ввода/редактирования инструкции. Здесь инструкция LEA EDI,[LOCAL.488+2] примет вид LEA EDI,[EBP-79E], где значение 79Eh — это смещение относительно адреса в регистре EBP. Сейчас в регистре EBP находится адрес 0012FBCC. Запустим калькулятор Windows и посчитаем

    0012FBCCh - 79Eh = 0012F42Eh

    Это значение нам показывает отладчик (см. рис 18 ). Подсчитаем новое смещение на адрес 0012F436

    0012FBCCh - 0012F436h = 796h

    Теперь введём это значение в инструкцию и подтвердим ввод:

    Рисунок 20.


    После изменения инструкции проверяем адресацию до и после:

    Рисунок 21.


    Все верно. Запустите программу на выполнение - F9. Как теперь отображается строка подсказки?

    Рисунок 22.


    Цель достигнута. Еще один завершающий штрих. На самом деле в показанной и разобранной секции кода формируются элементы управления всплывающих подсказок.

    Рисунок 23.


    Размер каждого блока данных с элементом управления один и тот же - 6Ch. Поэтому, если мы изменили счетчик копирования, то необходимо изменить счетчик заполнения массива элемента нулями. Для нашей строки он находится по адресу 0040FCB4. Так как счётчик блоков копирования данных строки увеличился на 0Bh - 9h = 2h, то соответственно необходимо уменьшить счетчик заполнения массива нулями до 0Fh - 2h = 0Dh. Поэтому окончательные изменения кода для нашей строки "Инструмент выбора окна" будут следующие:

    Рисунок 24.


    При необходимости откорректируйте другие строки всплывающих подсказок. Сохраните сделанные изменения в файл.

    На этом наше небольшое исследование заканчивается. Вы познакомились с некоторыми командами ассемблера и одним из методов анализирования дизассемблированного кода приложения. Надеюсь в будущем вы сможете самостоятельно решать подобные задачи.

    Спасибо за внимание.
    NNK_RTR нравится это сообщение.
    Сообщение отредактировал 27 января 2021 - 23:17

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

    1 декабря 2015 - 00:40 / #2
  3. Оффлайн

    NNK_RTR

    Звание: Бывалый

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

    Сообщений: 286

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

    Рейтинг: 5

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

    Баллы: 1831

    Был: 2024-05-09 19:12

    Лайков: 88

    Вопрос (или уточнение)
    Я изучаю эту статью с целью усовершенствования инструмента ScanStCounter
    Во второй части статьи, там, где вы говорите, что "еще необходимо корректировать и адресацию" и предлагаете способ корректировки, я обратил внимание на команду: LEA EDI,[EBP-79E] (1), которая настраивает (подготавливает) команду REP STOS DWORD PTR ES:[EDI]

    С учетом, что команда MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI] автоматически увеличивает (в нашем случае) значения регистров EDI и ESI, команда (1) оказывается излишней и ее можно просто "заNOPить"
    Эксперимент показал, что все работает.

    Дальше о размере блоков:
    Достоверно отследить изменение блоков не удалось. Не заметил считывания нулей, идет сразу перезапись, без проверки содержимого
    Провел эксперимент - заменил все ответственные за размер блоков команды PUSH на PUSH 4 (хватило бы 2, чтобы записать завершающие строки нули, но пусть будет 4) - видимых ошибок нет. По всей видимости командам ответственным за элементы управления плевать на определенные здесь размеры блока. Хорошо еще, что где-то проверяется длина строки и последующая перезапись не затирает ее (строку)

    Вопрос: Мои рассуждения с изъяном? Я что-то не предусматриваю? Что будет, если не изменять размер блоков совсем?

    С уважением, Николай.
    30 апреля 2024 - 10:21 / #3
  4. Оффлайн

    Автор темы

    Leserg

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

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

    Сообщений: 933

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

    Рейтинг: 8

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

    Баллы: 1616

    Был: 2024-05-09 15:29

    Лайков: 146

    Цитата: NNK_RTR
    Мои рассуждения с изъяном? Я что-то не предусматриваю? Что будет, если не изменять размер блоков совсем?

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

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

    Цитата: NNK_RTR
    Я изучаю эту статью с целью усовершенствования инструмента ScanStCounter

    Это единичный случай и вашему инструменту он не нужен.

    wink_mini
    NNK_RTR нравится это сообщение.

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

    Вчера, 01:57 / #4

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

---
Создано тем
1179
Всего сообщений
15382
Пользователей
17859
Новый участник
Leongsm