Сказ о суровых российских инженерах.
1. С чего все началось…
Ангелы и демоны кружили надо мной
Рассекали тернии и Млечные Пути
Origa — Inner Universe
В одном, не очень отечественном САПР, есть возможность конвертировать чертежи сразу в PDF. Но то ли программисты не очень понимают, как их САПР используется, то ли просто забыли добавить возможность сохранения настроек. В итоге, САПР, при экспорте в PDF, всегда конвертирует только "текущий лист", если не забраться в параметры и принудительно не выбрать "Все листы". В нашем инженерном деле, документы из одного листа крайне редки, народ страдает и продолжает из раза в раз выкладывать, отправлять, генерировать и просто тратить процессорные мощности на однолистные PDF'ки. Томным, пятничным вечером, потягивая купажированный виски и покуривая сигару, я задумался — можно ли с этим что-нибудь сделать?
- Помните, внесение изменений в программное обеспечение может нарушать лицензионный договор. Все дальнейшие совпадения случайны. Статья носит исключительно развлекательный характер.
- Автор не является программистом и тестировщиком, а работает скромным инженером-проектировщиком всяких аббревиатурных систем типа АСУ ТП, САУ ВО\ДГ и прочих промышленных систем. Из-за этого в статье могут содержаться неточности, ошибочные утверждения и бредовые предположения.
Рисунок 1. Окно настроек PDF.
2. …и чем могло закончиться
Первая и самая очевидная идея — пойти в настройки САПРа и изменить стандартные параметры. Я бы не писал этот текст, окажись все так просто. Доступных галочек, кнопочек, полей и прочих пользовательских интерфейсов отвечающих за настройки экспорта PDF в приложении не оказалось.
Вторая, менее очевидная для обывателя идея — пойти в реестр и поискать там. Многие приложения хранят в HKEY_LOCAL_MACHINE\SOFTWARE\
(или HKEY_CURRENT_USER
) свои настройки, параметры и прочую сервисную информацию необходимую для работы. Реестр для того и задумывался. Можно догадаться, найдись там с ходу, что-либо годное для решения проблемы, этой заметки не было.
Третье и последнее — поискать в конфигурационных файлах. Часто программы хранят параметры не в реестре, а во всяких config.ini, settings.xml и прочих текстовых файлах. Вдумчивый поиск и пристальное рассматривание каталогов показали отсутствие текстовых настроек.
3. Just Do It!
Мастерами кунг-фу не рождаются
Мастерами кунг-фу становятся
Мумий Троль — Мастера Кунг-Фу
Очевидные вещи закончились, пора заныривать в чертоги памяти и призывать весь свой опыт работы с IT-системами. Прежде всего, реестр. Возможно, параметр отвечающий за "печать всех листов", хранится не так явно. Скажем, есть в реестре запись с названием "Options", имеет она значение "1,1,2,3,1,0,1" и вторая единичка, как раз отвечает за то, какой пункт меню выбран. Возникает два вопроса: "Где взять хороший вискарь?" и "Как найти эти самые опции?"
Если над первым еще можно поломать голову, то со вторым все просто — в составе Sysinternals существует замечательное приложение — Process Monitor(procmon).
Рисунок 2. Окно Process Monitor
Сначала, пришлось определиться с фильтрами, т.к. Procmon ловит вообще все события от любого приложения, коих в windows работает с пару-тройку десятков, и событий налетает тысяч 10-20 за пару секунд. Окей, указываем — отображать только события связанные с САПР и исключить всякую фигню. Далее, открываем приложение, запускаем сбор событий, нажимаем кнопку "параметры", останавливаем сбор событий… Вуаля — скромный лог "всего" на 700+ строк. Предположительно содержащий обращение к реестру для чтения настроек экспорта. После скрупулёзного изучения, были замечены строки 19:46:29,4265774
, однозначно намекающие — мол САПР считал (и записал) данные 1391,790…1,0,0
.
"Ха! Вот оно!" — подумал я, предположив, что 1 или 0 отвечают, как раз за настройки.
"Хе!" — подумал САПР, и категорически отказался, что-либо менять в окне параметров, не смотря на различные комбинации подсовываемых нулей, единичек и прочих цифр.
Суббота. Смеркалось. Других обращений к реестру не было и становилось очевидно — путь реестра ведет в никуда.
Воскресенье выдалось суетным, впереди маячила новая рабочая неделя, хотелось завалиться на диван, обнять жену, включить какой-нибудь фильм и наслаждаясь Джонни Пешеходом, окунуться в приятную негу… однако, на периферии сознания свербела мысль — "откуда то же САПР читает настройки…". Рабочая неделя не предвещала ничего хорошего, жена занималась своими девочковыми делами, Джонни оказался слишком резок, смотреть фильм не получалось. Пришлось открыть ноутбук.
Если САПР не хранит настройки в реестре, значит хранит их в файлах, вопрос лишь в котором из 20 000? Скорее всего, опция хранятся в виде нуля (первый пункт меню, для программистов нулевой), а может единички, если код писал криворукий индус. В любом случае, таких нулей и единичек в САПРе, аж три биллиона (10^9)…
Я смотрел на САПР. САПР смотрел на меня и издевательски моргал курсором в поле "векторное разрешение". Поле это динамическое, туда можно ввести любое значение и получить документ соответствующего качества. Стоп. Поле данных…любое число… но там же есть текст "DPI". Значит, поле текстовое, и текст "400 DPI" уникален (в пределах файлов). Хм. И он (текст) где-то хранится, вероятно даже рядом с остальными настройками…
TotalCommader > поиск файлов > *.* c текстом "400 DPI". Томительное ожидание и… ничего не найдено. Потому что файлы, в большинстве своем, не текстовые, а бинарные, и искать надо в HEX-е (заодно, выбрав все доступные кодировки). Вуаля — искомая комбинация встречается всего в одном файле:
imgUI.dll (название файла изменено)
Вау! Так просто? А вот фигушки. DLL — динамик лоад лайбери, если открыть его редактором, то можно увидеть нечто-подобное:
Рисунок 3. Текст DLL
Нипоня-я-ятно… однако, в каталоге с imgUI.dll встретились img.dll и imgUtils.dll. Этож-ж-ж не спроста. А еще, так как САПР не отечественный, но на русском языке, то в каталоге Rusians (ага, именно с одной s) нашлась еще парочка файлов — imgRes.dll и imgUIRes.dll. Чтож к утру понедельника у меня было пять файлов:
- imgUI.dll — UI, скорее всего сокращение — "User Interface", в нем то и нашлось "400 DPI". Можно предположить — этот файл отвечает за отрисовку интерфейса.
- imgUIRes.dll — очевидно, файл ресурсов для отображения пользовательского интерфейса
- imgUtils.dll — файл содержащий утилиты, выполняющие преобразование в pdf, или просто всякие вспомогательные штуки(?)
- img.dll — что делает не ясно, но раз имеет общее имя с файлами выше, надо обратить внимание и запомнить.
- imgRes.dll — ??? Файл ресурсов для работы img.dll ???
4.We need to go deeper (Заныриваем глубже)
All in all it was just a brick in the wall.
All in all it was just the bricks in the wall.
Pink Floyd — Another Brick In the Wall (Part 2)
Понедельник выдался нервным.
Единственное, что я понимал на тот момент: после того как программист нарисовал окошечко, оно складывается в res-файл… и существуют приложения способные эти файлы открыть. Отдельное спасибо игровому детству и разработчикам небезызвестных ArtMoney и Restorator. При помощи последних мы читирили — меняли в res-файлах различные опции, получая тем самым горы золота, шмоток и просто нереальных (по игровым меркам) персонажей.
Логично было предположить — в res-файле могут содержаться данные с настройками или окно параметров содержащие те же настройки (те кто разрабатывал графические интерфейсы на С++-подобных языках сейчас улыбнулись).
В imgRes.dll ничего интересного не нашлось, а вот imgUIRes.dll встретил знакомым окном параметров.
Еееее! — порадовался я, — сейчас я какаааак сделаю… … что-то … … Хм, а действительно, что?
Радость улетучилась. Res-файл не содержит кода, по сути, это набор элементов которые можно подвигать, изменить размер, шрифт, текст — а ведь это именно то что мне требуется! Почему бы просто не поменять местами два текста — пусть "Все листы" станет первым в списке, тогда САПР всегда будет выбирать его по умолчанию!
Сказано, сделано, сохранено.
Запуск САПРа>Экспорт>Параметры>OK>Экспорт. И полный облом. Не смотря на то что "чек-бокс" стоит там, где надо ничего не изменилось.
Рисунок 4. Измененный интерфейс.
Программисты сейчас должны умилиться моей наивности. Во-первых, если проводить проверку по тексту чек-бокса, то необходимо учитывать все языки, а не только русский. (САПР то международный, ага). Во-вторых, каждый элемент в окне имеет ID и работают, как раз через него. Выглядит это так (цифра после первой запятой — ID):
Рисунок 5. ID в Res-файле.
Хотелось верить, что где-то здесь, в цифрах, зашита заветная опция "использовать этот пункт по умолчанию ", но увы нет.
Казалось — тупик, что тут можно сделать? Выдохнуть, обнять жену, признать невозможность изменить мир, накатить, вернуться в привычное житейское русло и наконец-то выспаться.
5.Еще!
Run rabbit run
Dig that hole, forget the sun,
And when at last the work is done
Don't sit down it's time to dig another one
Pink Floyd — Breathe
Помнится лет 10 назад, нам, студентам информационно-измерительных систем, по какой-то одному деканату известной причине, читали курс по защите ПО. Преподаватель, понимая, что за семестр невозможно впихнуть в нас хоть сколько-нибудь внятные знания по всем этим DES, AES и прочим приоткрытым ключам, сделал единственный верный шаг — начал рассказывать, что нужно сделать чтобы код от наших гипотетических приборов не уперли конкуренты и какими методами они это будут пытаться сделать. Лабораторные работы строились по принципу пишешь код, а потом твой сосед пытается его сломать. С тех времен память сохранила сокращения- HEX, ASM, IDA.
С последней и было решено начать, благо есть бесплатная версия(freeware, а не то что все подумали). Ollydbg казался сомнительным, т.к. САПР х64 и была не нулевая вероятность получить неправильный код. Microsoft Debugger — можно было бы попробовать, но IDA уже была скачена и установлена.
Итак, есть две сущности:
- ID элементов из окна параметров (от 1001 до 1014)
- Текст "400 DPI"
…которые надо найти, очевидно, в imgUI.dll. Почему очевидно? Во-первых, потому что поиск текста "400 DPI" указал именно на этот файл, а во-вторых, из названия файла.
Закинув DLL в IDA я, честно признаюсь, знатно так 302A79452F5C:
Рисунок 6. IDA.
Что это? Нет, понятно что это 1. ассемблер 2. последовательность выполнения чего-то, за чем-то в зависимости от чего-то. Переключение в "TextView" так же не прибавило понимания, а родило с десяток новых вопросов — как объявляются переменные, что это за 800+ функции sub_18…, где хранятся данные, циклы, условия — где все это?, что это за регистры, где мой вискарь и котъ!?
В общем, для неподготовленного меня, последний раз писавшего программы на скриптовых-языках высокого уровня (PowerShell и VBA), все это выглядело примерно так же как и для тебя, мой дорогой читатель выглядят расчеты селективности, токов короткого замыкания, кривые отключения и прочие электрические гадости.
Но, как говорится — "фигня война, главное маневр". Что искать в файле было ясно заранее — radiobutton с ID 1004\1005. …и-и-и поиск ничего не дал. А не дал он ничего, потому что IDA нифига не дружелюбна и хранит все данные в шестнадцатеричном виде. Окей, запускаем калькулятор, переводим в "программистский режим" и получаем 1004 это 3ECh в HEX (h на конце собственно об этом и говорит). Запускаем поиск по новой — успех, данное сочетание найдено 3 раза в 2х функциях. Причем, значение ID 1005 (3EDh) находится рядышком только в тех самых двух функциях. Место найдено и выглядело оно примерно так:
Рисунок 7. ID 1005
Чего происходит, нипонятно… да, честно говоря, понимания и не требовалось. Осталось лишь поменять эти два значения местами, чтобы кнопка "Текущий лист" стала распознаваться ПО как "Все листы", а "Все листы", как "Текущий лист". И тут меня ждал первый облом — редактировать ассемблерный код IDA не дает. А вот байт-код пожалуйста. Честно говоря, всегда думал — ассемблер самый "близкий" к железу язык, оказалось — нифигашечки. Итак, байт-код. Каждый процессор имеет набор команд, большей частью стандартизированных, и каждая команда имеет свой код в виде байт(подозреваю что на самом деле бит), а ассемблер это "отображение" этих байт в более-менее читаемом виде. Команда mov edx, 3EC
в байт-коде выглядит так:
BA EC 03 00 0
А mov edx, 3ED
так:
BA ED 03 00 00
Впрочем, к черту лишние знания, надо же EC заменить на ED и наоборот! Закидываю DLL в САПР, открываю "параметры", никаких видимых изменений, но их и не должно быть… жмакаю ОК>Сохранить и… ДА!!! PDF-ка, генерируется сразу вся… ЕЕЕЕЕ! СДЕЛАНО! Интеллектуальный оргазм, победа!
Можно снять наушники, потянуться, размять затекшую спину, потереть красные глаза, посмотреть в ночное небо и подумать о вечном. Похвастаться перед парочкой друзей, которые, не смотря на первый час ночи, еще не спят.
6. Hardcore только hardcore
Мы уже думали, что опустились на самое дно, но тут снизу постучали
Народная мудрость.
Казалось бы все — конец, ответ на вопрос вселенной и всего такого найден, но, увы — меня ждала подлянка.
Оказалось, внезапно, кто бы мог подумать — если НЕ заходить в "Параметры", то окно не вызывается, изменение настроек НЕ происходит и по умолчанию PDF сохраняется с 1 листом. Т.е. по сути ничего не изменилось — для генерации полноценного PDF'а, надо было все равно, хотя бы раз заползти в параметры.
Рисунок 8. Фиаско
И что в такой ситуации делать? Правильно, занырнуть поглубже!
Как гласит название одной хорошей книги "just for fun" загрузил imgUtils.dll
. Понимания как все работает и что делать дальше не прибавилось, все было примерно так же как и в imgUI.dll
, за исключением одного — ряд функций имели вполне себе читаемые названия вида: OptionsPDFExport::SetResol
, OptionsPDFExport::SetBW
, OptionsPDFExport::GetStartSheet
и прочие. Оппачки! Слишком уж говорящие названия! Но опять таки радость была недолгой, т.к. большинство ф-ций имело вид:
mov [rcx+15h], dl
Retn
Т.е. понятно, нечто из DL
переносится в rcx
со смещением (по адресу?) но… Чё за dl
? Чё за регистр rcx
? Впрочем, что будет если ф-ция GetBW всегда будет писать в регистр rcx+15h
значение 1 (или 0, потом разберемся)?
Второе неожиданное открытие — невозможность вставить кусок кода. Совсем. Никак. Связанно это с указателями — часть команд (и не только) говорят, мол прыгни на 99 команд вверх, или считай значение по вот этому адресу Когда мы вставляем код, то фактически адреса съезжают и нам надо перепрыгнуть уже через 100 команд, т.е. переписать их … все. ИДА такого делать не умеет (или, что скорее, я не нашел).
Возвращаясь к mov rcx+15h, dl
. В байт-коде это выглядит так:
88 51 14
а чтобы написать mov rcx+15h, 1
, надо вставить:
C7 81 14 00 00 00 01 00 00 00
Т.е. надо добавить 14 байт. (Для тех кто знает ассемблер — да-да-да надо использовать другой байт-код, и вообще какого фига я сравниваю х86-64, но это наглядно).
Печаааль. Да и не наблюдалось в названиях ф-ций чего либо напоминающего выбора диапазона печати.
Нет, я забрался слишком… слишком далеко чтобы вот так все бросить. Чтобы впустую слить все затраченное время.
Но что делать дальше? Изучать ассемблер и полностью реверс-инжинирить каждую функцию в этих двух файлах? Нет, долго. Надо было вернуться к началу и пройти правильным путем — запустить мониторинг и посмотреть какие ф-ции вызываются в процессе генерации PDF без заползания в параметры и уже после этого лезть в ассемблерный код.
На просторах интернета была найдена программа API Monitor v2. В качестве "наблюдаемых" файлов были выбраны imgUtils.dll
и imgUI.dll
… что ж, лог получился на 233 вызова. Не то чтобы очень мало, но зато у меня был порядок вызова функций.
Описанные выше GetBW\SetBW\GetStartSheet встречались, но ближе к середине, т.е. они были не первыми.
В начале лога мое внимание привлекла ф-ция с названием OptionsXPSExport. Хм… а какого собственно черта, при генерации PDF'а, вызывается ф-ция от другого формата? И после нее как раз и начинались разнообразные Get'ы и Set'ы
Открыл в ИДА. Функция оказалась не маленькой, строк на 300 ассемблерного кода. Старый добрый вопрос — что искать? ID кнопок — бессмысленно, оставалось только "400 DPI".
Переводим 400 в 16-ричную систему, получаем 190h
. Поиск…и найдена 1 строка:
Рисунок 9. Настройки.
Встает вопрос — как узнать, что именно этот сегмент отвечает за стандартные настройки? Легко! Почему бы не изменить известный параметр и не посмотреть что будет! Ок, 190h
заменено на 96h
(число 150). Запуск САПР>Экспорт в PDF>Параметры и… вместо 400 DPI красовалось 150 DPI. Йухууу! Можно было сделать вывод — место хранения "дефолтных" настроек найдено. Оставалось два вопроса — где именно лежит нужный параметр и как его найти в 300 строках, примерно такого же кода.
Кстати, что это за код? Что за r12
и r13
? Ответ нашелся вначале функции:
Рисунок 10. R2D2.
В регистр r12
записывалась единичка, а в r13
ноль, что ни разу не очевидно, т.к. для этого используется команда xor, а не mov. Разница в том что xor
делается быстрее чем mov
, поэтому его и используют когда надо обнулить переменную.
Возвращаясь к коду выше, было очевидно, что по адресу rbp+330h
записывается 0, а по адресу 32Сh - 1
.
Окей, полдела сделано — появилась возможность изменять значение параметров с вкл (1) на откл (0).
Однако, особых идей где именно находится параметр отвечающий за листы все еще не было. Попытка подключить ИДА к дебаггеру и поймать момент смены того или иного байта "на лету" провалилась, по не известной мне причине — дебаггер отваливался при запуске САПРа. Оставался самый тупой способ — перебор.
Заменил r13b на r12b
в строке с адресом [rbp+326h]
, и оказалось, что этот mov
отвечает за удаление веса линий. А mov
выше за печать исключенных листов. Набросав простенький проект проверил работает ли это все без открытия параметров экспорта. И да. Проект экспортировался без весов линий. Что еще раз подтвердило — я смотрю в правильный кусок кода.
Потратив несколько дней на игры с заменой 1 и 0, появилось ощущение нового тупика. Замена оставшихся значений не приводило к сколько-нибудь заметному результату — галочки в параметрах не ставились, а иногда приложение просто крашилось. Стоит отметить — параметров было много и после замены каждого проверять САПР было долго, по этому я менял 10-20 значений и смотрел изменилось ли что-нибудь.
В один из вечеров я смотрел на приведенный выше код и думал — "да чтож с тобой не так? почему 5 из 7 параметров находятся здесь?" К слову, строка 0FFFFFFh
отвечала за "До", а строка ниже за "От".
Абсолютно логично — все настройки отвечающие за параметры сгруппированы в одном месте и mov rpb+314, r12
должен отвечать как раз за выбор диапазона печати. Но там не 0, там 1! Помните я говорил про индусов? О том, что не для всех первый элемент массива кнопок является нулевым. Тогда логично, что моя замена 1 на 0 ничего не показала. Окей, нет ничего проще — меняем mov rpb+314 r12
на mov rpb+314 r13
и… барабанная дробь… при входе в параметры ни один из пунктов меню "диапазон печати" выбран не был!
Да, это та самая строка, тот самый кусок кода который я искал на протяжении недели. Гребаные 14 байт.
Оставалось лишь изменить значение на правильное и можно открывать шампанское. Итак 0 — не выбрано ничего, 1 — первый пункт, продолжая ряд выходило что нужно прописать 2.
Тут появлялась старая, добрая проблема — mov rpb+314 r12
занимало меньше байт чем mov rpb+314 00002h
На счастье, парой строк выше можно заметить вот такой кусок кода:
Рисунок 11. Е2Е4.
Т.е. в регистр eax
ложится двойка, а команда mov rpb+314 eax
занимает на один байт меньше чем mov rpb+314 r12
!
Ассемблер | Байт-код |
---|---|
mov rpb+314, r12 | 44 89 A5 14 03 00 00 |
mov rpb+314, eax | 89 85 0C 03 00 00 |
Вопрос — что делать с этим байтом? Оставить как есть нельзя. Но, на счастье из родного Политеха я смог вынести несколько сакральных знаний:
- Как работает реле (что позволило найти инженерную работу)
- Всегда заземляйся (если вы понимаете о чем я)
- Ассемблерный nop — просто пропуск команды
- Шаверму перед парами…. кхм, впрочем к делу это не относится.
Очевидное решение — заNOPать, т.к. в байт-коде команда NOP это "90".
Запуск САПР и… да, это оно. Файл конвертируется в многостраничный PDF. А окно параметров открывается по умолчанию так:
Рисунок 11. Окно настроек PDF.
7.Заключение
We've come too far to give up who we are
So let's raise the bar and our cups to the stars
Daft Punk — Get Lucky
Ответ на вопрос, заданный в начале статьи, найден. Путь был тернист и заставил пошевелить мозгами, вспомнить университетский курс программирования и узнать много нового. За рамками осталось несколько "тупиковых веток", вида JMP'нуть в кусок кода, изменить регистры и прыгнуть назад, или попытки найти исходники, но вряд ли это кому-то интересно.
Немного обидно, что эти знания никогда не уйдут в "продакшн", а инженеры продолжат страдать из-за пары программистов, но C’est La Vie, таков лицензионный договор.
Надеюсь, эта заметка сможет вдохновить кого-нибудь на совершенствование рабочих инструментов, даже если вы инженер-проектировщик и весьма далеки от реверс-инжиниринга.
HerrDirektor
Ярко напомнило, как я в 90х
патчил… т.е. изучал, конечно же изучал, Novell DOS для некоторых кхм-хм действий.Этих ваших интернетов тогда у меня еще не было, зато была книжка П. Нортона «Язык ассемблера для PC» и Turbo Debugger от Борланд.
Ностальгия… :-)
NAI Автор
Каждое следующее поколение программистов говорит на языке более высокого уровня.
Если в 90-ые можно было считывать прерывания и вообще быть довольно близким к железу, то сейчас попробуй пойми, весь стек вызовов того же PowerShell.
Эхх, INT 09h.