Русский дизассемблер
Дата: 27 марта 2008 года
Дизассемблирование - неотъемлемая часть мира программирования, как археология, история в мире человеческом. И если второе прошло уже несколько этапов развития - от машинного кодирования до блочно- модульного в языках высокого уровня, а теперь перешло и на уровень системного программирования в визуал-языках, то первое до сих пор топчется на первом этапе. Ведь до сих пор вспоминают SOURSER. И в той же IDA PRO, которая считается лучшей, человеку до сих пор приходится решать элементарные задачи - в ручную определять, где байты, а где коды. А ведь пора бы перейти и на блочно-модульное дизассемблирование с выходом на тексты языка высокого уровня. Причиной топтания на месте считаю неудачную идеологию дизассемблирования (линейную) в этих, да и других дизассемблерах тоже. Представляя свой дизассемблер RD16.exe, пытаюсь продемонстрировать и новую идеологию дизассемблирования (мозаичную), которая позволяет более эффективно и более качественно решать те же задачи. Полагаю, что и перспектива развития дизассемблирования открывается иная.
История рождения
Происхождение Русского Дизассемблера довольно древнее. Это началось еще в Советские времена, когда IBMок у нас еще и не было, но появились первые любительские компьютеры. В частности "Специалист", если кто-то еще помнит. Тогда на его базе я сделал свой компьютер "Специалист_МХ", с RAMDISKом, с "операционной системой" по внешнему подобию "Нортона", с дисководами. Успел по тиражировать по стране. Вот тогда я и столкнулся с потребностью дизассемблирования. С одной стороны это прекрасная практика познания ассемблерного языка, и школа обучения писать на нем. С другой стороны это удобный способ адаптации программ к новым нуждам. А когда дело дошло до больших программ типа LAYOUT, BASIC, то потребовался дизассемблер более мощный. Вот тогда я и написал свой первый дизассемблер. Именно в нем я тогда применил и отработал свою идеологию дизассемблирования и весьма успешно. Правда, тогда я не думал, что она какая-то особенная, считал само собой разумеющейся.
Однако пришло время, "Специалист" тихо ушел из жизни. И я перешел уже на более современную технику. Так уж получилось, что первое, с чем я познакомился, оказался дизассемблер SOURSER. Ох уж и поплевался я тогда. Положить в основу автоматического дизассемблирования абсолютно неверную идеологию, буквально с точностью до наоборот. Впрочем, к первопроходцам все равно надо относиться с уважением. Им всегда труднее. Вот тогда я и решил не дать умереть своей идее, переложить ее на современный компьютер.
Путь был не легкий и не быстрый. Писать библиотеку команд тогда было просто немыслимо. Информации явно не хватало. Пришлось взять из SOURSERa версии 1.07. Дизассемблировал ее с помощью версии 3.07. Угрохал кучу недель, прежде чем получил приемлемый текст. Да и дальше довольно долго многие куски программ нельзя было трогать, пока не утряс все OFFSETы и прочее...
В итоге родился RD16, в котором от SOURSERa осталась только библиотека команд. Набор процессоров тоже сохранен, правда, все их не проверял, не на чем было. Однако теперь тип процессора определяется автоматически. В итоге теперь на RD16 туже работу можно сделать и за один вечер. Кстати, свои программы не плохо проверять дизассемблером. Видны становятся многие ошибки в программе. Да и мусора выплывает не мало. Случается, процедуры, ранее используемые, а потом выключенные, остаются.
Идеология дизассемблирования
Как известно, машинный процесс ассемблирования - это процесс с потерями. Вне программы остается вся смысловая информация, например, комментарии. Обратный процесс машинного дизассемблирования эти потери естественно восстановить не может. Это может только человек. Поэтому разделение труда неизбежно. Отсюда вытекает и первая задача. В программе дизассемблера должно быть предусмотрено два режима - автоматический и интерактивный.
В SOURSERe основное внимание было уделено первой функции - автоматичности. Однако эффективность машинной работы была низка. Да и откуда она могла взяться при примитивном линейном алгоритме - сплошняком декодируй все байты файла, а потом гадай, где реальные коды, а где ложные. Даже человеку не просто решить эту задачу. И он то, чаще всего, применяет метод "научного тыка", по наитию. А как машину научить этому наитию?
В IDA PRO внимание уже уделили второй функции - интерактивности. И наработали мощный ресурс для ручной работы. Однако примитивный алгоритм машинной работы остался в общем прежним. И потому немалая часть этого ресурса направлена на компенсацию неэффективной машинной работы.
Все программы состоят из двух частей - области кода и области данных. И задача дизассемблирования прежде всего разобраться - что и где. Нагружать человека этой работой неразумно. Слишком уж она рутинна и объемна. Поэтому разделение труда должно быть не только количественное, а, прежде всего, качественное. Ясно, что человек умнее любой машины. Поэтому и заниматься он должен самой умной работой, оставив машине всю примитивную и рутинную. Иначе говоря, машина должна выявлять машинную функцию байтов кода, а человек ее смысловую функцию.
Главной частью программы, конечно, является код, а область данных, как приложение к нему и потому как бы вторична. Именно это представление сыграло злую шутку с разработчиками дизассемблеров. Они свое представление передали и дизассемблеру в виде обобщенного принципа. Вся дизассемблируемая программа является ОБЛАСТЬЮ КОДА, и задача машины в автоматическом режиме выявить области данных. А для того чтобы машина решила задачу в этом режиме четко и однозначно, ей должны быть даны также четкие и однозначные инструкции. А их увы, просто не существует при той идеологии. Потому мир дизассемблирования и топчется на месте.
Русская идеология
В русском дизассемблере подход прямо противоположный. Вся дизассемблируемая программа представлена как ОБЛАСТЬ ДАННЫХ. И задачей дизассемблера является наоборот выделить область кода. А что останется, то и будет областью данных. И здесь на первый вопрос машины - а с чего начать, где взять первый байт кода? - ответ четкий и однозначный. У любой программы есть точка входа и где находится ее адрес тоже известно. Поэтому первая инструкция проста - бери стартовый адрес. Он указывает на первый байт кода. Следующая инструкция - декодируй первый байт. Далее анализируй. Если команда простая, типа MOV, ADD, значит за ней следующий байт кода. Тогда шагай на него и снова декодируй. Если встретилась команда JMP, тогда инструкция - за нее шагать нельзя. Там еще не ясно что. Но, у команды JMP есть операнд, который четко указывает на область кода. Соответственна и инструкция будет однозначна - бери адрес перехода и продолжай шагать с новой области кода. Если встретилась команда RET, или функция DOS - завершить процесс, тогда остановись и оглянись назад. Возможно, по ходу процесса встречались условные переходы. Они возможно указывают еще на не пройденные области кода. Тогда их надо пройти. А уж когда все адреса кода отработаны, тогда заверши процесс. Настало время представить результат человеку для творческой работы.
Разумеется, по ходу процесса будут встречаться команды CALL вызова процедуры. На первый взгляд ситуация не сложная. Эта команда предполагает возврат на место после нее. А значит запомни адрес вызова и шагай дальше. Однако, на практике не во всех процедурах авторы предусматривают этот возврат. Они, пусть и не часто, но могут завершиться функцией DOS - завершить процесс. А это значит, после такой команды CALL продолжения кода может и не быть. Возможны и другие варианты, например текстовые сообщения, после команды CALL, и только после этого сообщения продолжение кода. По этой причине дизассемблеру дана четкая инструкция - при первой встрече всегда прерви текущую дорогу кода, войди в процедуру и изучи ее по выше описанному алгоритму. А по результату принимай решение. Если есть хотя бы одно нормальное завершение процедуры командой RET, тогда вернись из нее и продолжи предыдущую траекторию кода. Иначе прервись.
В процессе дизассемблирования могут возникать ситуации, на которые заранее инструкций не напишешь. Например, те же команды JMP и CALL могут иметь и неявную адресацию перехода\вызова. Эти адреса могут предварительно загружаться в регистры или размещаться в области данных в виде таблиц. В этих ситуациях уровня машинного декодирования явно недостаточно. Необходимо перейти на уровень смыслового декодирования, чтобы разобраться, например, со структурой таблицы. В таких случаях машине дана инструкция - запроси дополнительную инструкцию у человека. А для этого пометь эту проблемную ситуацию и продолжи дальше, не нарушая принципы описанного выше алгоритма.
Помимо основного процесса есть и попутные, параллельные. Например, не маловажной задачей является выявление OFFSETных меток. Здесь дизассемблеру дана инструкция - при встрече команд обращающихся к памяти, обрати внимание, какой регистр используется для этого. Затем оглянись назад и найди место, где была последняя загрузка в этот регистр, и в этом месте оставь самому себе дополнительную инструкцию - автоофсет. На следующем проходе она будет отработана. Разумеется, машине не всегда удается решить эту задачу, поэтому на втором проходе машине дана инструкция - все места загрузки регистров без автоофсета пометь, как проблемные, в помощь человеку....
Подробнее об интерактивном режиме, распаковке файлов в процессе дизассемблирования можно узнать в пакете программы RD16. Она выставлена в разделе "дополнения к языкам программирования".
Оставить комментарий
Комментарии
RD16 выставлен 27.06.2008г.