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

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

    Автор темы

    Leserg

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

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

    Сообщений: 927

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

    Рейтинг: 8

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

    Баллы: 1586

    Был: 2024-04-27 16:42

    Лайков: 135

    Проблемы локализации 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

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

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

    Сообщений: 927

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

    Рейтинг: 8

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

    Баллы: 1586

    Был: 2024-04-27 16:42

    Лайков: 135

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

    На сколько нужно увеличить значение счётчика? Давайте посчитаем. Строка копируется блоками по 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

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

Создано тем
1177
Всего сообщений
15333
Пользователей
17851
Новый участник
Dobriy-76