Выбрать главу

int count, handle

string contents

handle = open (argv[1])

while (count = read (handle, contents, 512))

write (STDOUT, contents, count)

exit (0)

Для сравнения приведем результат выполнения утилиты truss для той же самой команды, выполненной в системе Solaris 7 на машине (x86):

execve(“/usr/bin/cat”, 0x08047E50, 0x08047E5C) argc = 2

open(“/dev/zero”, O_RDONLY) = 3

mmap(0x00000000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC,

MAP_PRIVATE, 3, 0) = 0xDFBE1000

xstat(2, “/usr/bin/cat”, 0x08047BCC) = 0

sysconfig(_CONFIG_PAGESIZE) = 4096

open(“/usr/lib/libc.so.1”, O_RDONLY) = 4

fxstat(2, 4, 0x08047A0C) = 0

mmap(0x00000000, 4096, PROT_READ|PROT_EXEC, MAP_PRIVATE, 4,

0) = 0xDFBDF000

mmap(0x00000000, 598016, PROT_READ|PROT_EXEC, MAP_PRIVATE,

4, 0) = 0xDFB4C000

mmap(0xDFBD6000, 24392, PROT_READ|PROT_WRITE|PROT_EXEC,

MAP_PRIVATE|MAP_FIXED, 4, 561152) = 0xDFBD6000

mmap(0xDFBDC000, 6356, PROT_READ|PROT_WRITE|PROT_EXEC,

MAP_PRIVATE|MAP_FIXED, 3, 0) = 0xDFBDC000

close(4) = 0

open(“/usr/lib/libdl.so.1”, O_RDONLY) = 4

fxstat(2, 4, 0x08047A0C) = 0

mmap(0xDFBDF000, 4096, PROT_READ|PROT_EXEC,

MAP_PRIVATE|MAP_FIXED, 4, 0) = 0xDFBDF000

close(4) = 0

close(3) = 0

sysi86(SI86FPHW, 0xDFBDD8C0, 0x08047E0C, 0xDFBFCEA0)

= 0x00000000

fstat64(1, 0x08047D80) = 0

open64(“test”, O_RDONLY) = 3

fstat64(3, 0x08047CF0) = 0

llseek(3, 0, SEEK_CUR) = 0

mmap64(0x00000000, 6, PROT_READ, MAP_SHARED, 3, 0)

= 0xDFB4A000

read(3, “ h”, 1) = 1

memcntl(0xDFB4A000, 6, MC_ADVISE, 0x0002, 0, 0) = 0

write(1, “ h e l l o\n”, 6) = 6

llseek(3, 6, SEEK_SET) = 6

munmap(0xDFB4A000, 6) = 0

llseek(3, 0, SEEK_CUR) = 6

close(3) = 0

close(1) = 0

llseek(0, 0, SEEK_CUR) = 296569

_exit(0)

Проанализировав конец протокола, можно заметить, что в Solaris команда cat выполняется несколько по-другому. Различие проявляется в том, что в Solaris ош использует проецируемый в память файл для передачи диапазона адресов непосредственно вызову функции write. Эксперимент с большим файлом (результаты которого здесь не приведены) выявил цикл запросов между вызовами функций memorymap/write, причем за один раз обрабатывается 256 Кб.

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

Для углубления своих представлений об используемом инструментарии следует рассмотреть случаи применения файлов с предсказуемыми именами в директории временной памяти /tmp, чтения информации из файлов, доступных всем для записи, различных вариантов вызова функций и т. д.

Дизассемблеры, декомпиляторы и отладчики

Подготовка к анализу загрузочного файла – тема отдельного разговора. Отладчики – это программные средства, предназначенные для контроля выполнения программ. Отладчики позволяют приостановить выполнение программы в некоторой точке, изменить значение переменных и даже, в некоторых случаях, внести изменения в машинный код программы на лету в процессе ее выполнения. К сожалению, возможность выполнения отладчиком подобных действий зависит от включения в выполнимый код отладочной информации, прежде всего таблицы соответствия символов (для большинства загрузочных программ это не выполняется). Если отладочной информации в выполнимом коде нет, то отладчик может выполнить некоторые функции, хотя большую часть работы по отладке программ приходится выполнять вручную, например при указании точек прерывания вместо имен приходится задавать адреса памяти.

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

Проблема несколько упрощается, если исследователь в состоянии разобраться с ассемблерным кодом, генерируемым декомпилятором. В этом случае декомпилятор особенно полезен. Рассмотрим пример результатов работы декомпилятора.

Среди коммерческих декомпиляторов для Windows хорошая репутация у IDA Pro компании DataRescue (пример работы декомпилятора показан на рис. 4.1). IDA Pro может декомпилировать программный код многих процессоров, включая виртуальную машину Java.

Рис. 4.1. Пример работы IDA Pro

На рисунке показан пример применения декомпилятора IDA Pro для дизассемблирования программы pbrush.exe (Paintbrush). IDA Pro нашел секцию внешних функций, используемых программой pbrush.exe. Если программа выполняется под управлением операционной системы, которая поддерживает разделяемые библиотеки (например, под управлением операционных систем Windows или UNIX), то она содержит список необходимых ей библиотек. Обычно этот список представлен в удобочитаемом виде, который легко обнаружить при экспертизе выполняемого кода. Для выполнения программ операционной системе также требуется этот список, поэтому она загружает его в память. В большинстве случаев это позволяет декомпилятору вставить список в двоичный код программы, сделав его более понятным.