old_mmap(NULL, 87756, PROT_READ, MAP_PRIVATE, 4, 0)
= 0x40116000
close(4) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 4),
...}) = 0
open(“test”, O_RDONLY|O_LARGEFILE) = 4
fstat(4, {st_mode=S_IFREG|0664, st_size=6, ...}) = 0
Наконец, команда cat открывает наш файл «test». Конечно, имя файла – это входные данные команды, но они безопасны для работоспособности команды из-за ее логики работы. В других случаях может потребоваться учет содержимого входного файла.
read(4, “hello\n”, 512) = 6
write(1, “hello\n”, 6) = 6
read(4, “”, 512) = 0
close(4) = 0
close(1) = 0
_exit(0) = ?
В заключение cat пытается прочитать 512 байтов из файла (читает 6) и выводит их на экран (который описан STDOUT с дескриптором файла 1). При повторной попытке прочитать очередные 512 байтов файла читается 0 байт, что свидетельствует о достижении конца файла. В результате файл закрывается, дескриптор файла освобождается и выполняется нормальный выход (признаком нормального выхода является нулевой код завершения). Для демонстрации читателю представляем очень простой пример. Логика работы команды cat очень проста и легко восстанавливается. На псевдокоде команду cat можно записать следующим образом:
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. В процессе трансляции большая часть информации об исходном тексте программы теряется, например имена переменных, поэтому декомпилятор пытается восстановить исходный текст программы настолько, насколько это возможно. Если при декомпиляции таблица соответствия имен была не найдена, то зачастую декомпилятор присваивает переменным имена, составленные из плохо воспринимаемой последовательности цифр и букв.
Читать дальше
Конец ознакомительного отрывка
Купить книгу