Есть, собственно, некий декомпилятор бинарного кода в подобие C/C++, допустим для IA-32 платформы. Проблема заключается в том, что output данного декомпилятора, как и в прочем любого другого, известного мне, не является на 100% точным, т. е., рекомпилированный код может содержать ошибки, порой трудноуловимые, что несомненно сводит на нет коммерческую ценность продукта, и ограничивает его применение, прочно приклеивая лэйбл "браузер псевдокода". Основная причина неточности результатов работы декомпилятора, на мой взгляд, это - не всегда корректное определение типов и структур данных, следствием чего является неверное восстановление высокоуровнего кода и нарушение логики работы исследуемого объекта. Поэтому, стала цель, добится 100% точности выходных данных в работе декомпилятора. Я не имею в виду идентичность декомпилированного и исходного кодов, но - абсолютную идентичность в логике работы. Как это сделать? Понятно, одного статического анализа будет недостаточно. Тогда, почему бы не производить декомпиляцию в режиме run-time? Представьте, юзером запускается исследуемый PE-модуль, через специальный launcher, который проделывает необходимую модификацию данного модуля, с тем чтобы обеспечить полный контроль над выполнением со стороны декомпилятора. Т. е., особым образом, серверная часть декомпилятора инжектируется в исследуемый объект. Есть виртуальная машина, которая запускается на выполнение с самой первой инструкции at entry-point, эмулирует прецессор, коммуницирует с ядром декомпилятора, предоставляя входной поток данных. На базе чего, ядро, имея в распоряжении контекст выполнения исследуемой программы, может делать корректные предположения для построения объектной модели и генерации output-a, исключая при этом необходимость участия юзера. Юзер же, в свою очередь, сидит, тупо бряцая по клаве, пытаясь заставить программу пробежаться по всевозможным ветвям исполнения, или на худой конец - проработать интересующую функциональность. Далее, он завершает работу программы, открывает файл манагер и находит ее исходные кода, аккуратно разложенные по папочкам, вместе с файлом проекта, который он теперь может загрузить в его любимый IDE, компилировать, выполнять, дебажить, модифицировать, т. е., производить некую исследовательскую деятельность, согласно его целям. Но, буду краток. Меня интересует исключительно имплементация. Как обеспечить контроль за адресным пространством исследуемого модуля? Как перехватывать программные переходы и обращения к данным извне и из других тредов? Например, есть идея забить кодовый сегмент однобайтовым опкодом какого-нибудь прерывания и обеспечить соответствующий обработчик. Но как быть с data-segment? Как быть с данными в сегменте кода к которым возможны обращения извне (тоже ведь не исключено)? Можно, наверное, имплиментировать концепт range-breakponts, как в Soft-Ice. Но как? Естественно, сам процесс декомпиляции в run-time не должен до невозможного замедлить выполнение исследуемой программы, а по сему, уже обработанные участки кода должны восстанавливатся оригинальным байт-кодом, и со следующим переходом они будут выполнятся аппаратным процессором, а не виртуальной машиной вплоть до достижения следующего неиследованного фрагмента. Хотелось бы услушать мнения экспертов по этому поводу. Реально ли это сделать? Какие pitfalls могут подстерегать? Может кто видит иную имплементацию? Интересны любые мнения. Thanks in advance...
andruha123 Очень правильно подмечено. Есть и другие менеее трудоемкие по реализации и неубиваемые защитой методы декомпиляции. Что представляет из себя код программы и его ассемблерное представление знают все, также этот код выполняется процессором по жестким правилам, сохраняя промежуточные данные в регистрах, стеке, памяти, и выполняя различные ветвления. Все это абсолютно не зависит от языка на котором написана программа. Поэтому можно выполнить сквозную трассировку (через всю программу, учитывая все условные и безусловные переходы) регистров, стека, памяти с пометкой статуса обращения к ним и делать их слепки на входе и выходе из функции. Всё - далее дело техники, т.е. обработка полученного data flow.
andruha123 А какую информацию даст наблюдение выполнения в run-time, которую нельзя получить статическим анализом?
andruha123, обращение к памяти, imho, можно сделать по двум поделям: а) valgrind b) bochs У меня есть проект отладчика который эмулирует весь user mode код до int/sysenter. Вопрос зачем? Всё равно пеально выполнена (code coverage) будет малая часть exe-шника.
Vam Это именно то, что я называю "статической декомпиляцией". Stiver - Корректное значение указателя стека в любой точке выполнения. - Корректное значение FPU-stack пойнтера. - Корректные значения размеров входных/выходных аргументов функций. - Корректные значения размеров объектов памяти, особенно это важно для переменных в стэке. Этого достаточно для ненарушения логики выполнения программы. Не понимаю, какое прямое отношение вопрос имеет к сабжу.
s0larian Можно ли немного развить мысль? Я думаю, что code coverage будет зависить от специфики интересующей функциональности. Полученные исходные кода будут компилироваться в рабочй исполняемый код и содержать ту функциональность, которая была проработана в режиме run-time декомпиляции. Остальное будет замещено стабами, случайный вызов которых приведет к фатальному останову программы с нотификацией пользователя.
andruha123 В этом вы неправы, то, что я описал является не статическим, а динамическим анализом. Наряду с выполнением программы run-time методом существует эмуляция выполнения программы, это такой же динамический метод, только по большому счету используется на разных процессорных платформах, а почему бы его не реализовать на одном типе процессора. В данном случае анализу будет подвергнуто 100% кода (даже можно проанализировать те функции, которые ниоткуда не вызываются), а не только code coverage.
Vam Если вы имеете в виду эмуляцию выполнения программы, то, интересующий меня метод предполагает частичное применение данного подхода.