Возможно вы искали: 'Real Deal Poker'

May 15 2025 19:10:04
  • Как сделать 8Gamers.Ru домашней страницей?
  • Игры
    • База данных по играх
    • Игровые новости
    • Игровая индустрия
    • Обзоры на игры
    • Прохождения игр
    • Гайды к играм
    • Превью о играх
    • Игровые тизеры
    • Игровые арты
    • Игровые обои
    • Игровые скриншоты
    • Игровые обложки
    • Игровые трейлеры
    • Игровое видео
    • Вышедшие игры
    • Ближайшие релизы игр
  • Кино и ТВ
    • База данных по кино
    • Статьи о кино
    • Постеры
    • Кадры из кино
    • Кино трейлеры
    • Сегодня в кино
    • Скоро в кино
  • Комиксы и манга
    • Манга по алфавиту
    • База данных по комиксах
    • Читать онлайн комиксы
    • Читать онлайн манга
    • База персонажей
  • Читы и коды
    • Чит-коды для PC игр
    • Чит-коды для консольных игр
    • Трейнеры
    • Коды Game Genie
  • Моддинг
    • Модификации
    • Карты к играм
    • Программы для моддинга
    • Статьи о моддинге
  • Геймдев
    • Всё о создании игр
    • Список движков
    • Утилиты в помощь игроделу
    • Конструкторы игр
    • Игровые движки
    • Библиотеки разработки
    • 3D-модели
    • Спрайты и тайлы
    • Музыка и звуки
    • Текстуры и фоны
  • Рецензии
    • Игры
    • Кино
    • Аниме
    • Комиксы
    • Мангу
    • Саундтреки
  • Саундтреки
    • Лирика
  • Файлы
    • Патчи к играм
    • Русификаторы к играм
    • Сохранения к играм
    • Субтитры к кино
  • Медиа
    • Видео
    • Фото
    • Аудио
    • Фан-арты
    • Косплей
    • Фото с виставок
    • Девушки из игр
    • Рисунки
    • Рисуем онлайн
    • Фотохостинг
  • Юмор
    • Анекдоты
    • Афоризмы
    • Истории
    • Стишки и эпиграммы
    • Тосты
    • Цитаты
  • Флеш
    • Азартные
    • Аркады
    • Бродилки
    • Гонки
    • Для девочек
    • Для мальчиков
    • Драки
    • Квесты
    • Леталки
    • Логические
    • Мультфильмы
    • Открытки
    • Приколы
    • Разное
    • Спорт
    • Стратегии
    • Стрелялки
Статистика

Статей: 87772
Просмотров: 96111483
Игры
Injustice:  Gods Among Us
Injustice: Gods Among Us
...
Dark Souls 2
Dark Souls 2
Dark Souls II - вторая часть самой хардкорной ролевой игры 2011-2012 года, с новым героем, сюжето...
Battlefield 4
Battlefield 4
Battlefield 4 - продолжение венценосного мультиплеер-ориентированного шутера от первого ли...
Кино
Steins;Gate
Steins;Gate
Любители японской анимации уже давно поняли ,что аниме сериалы могут дать порой гораздо больше пи...
Ку! Кин-дза-дза
Ку! Кин-дза-дза
Начинающий диджей Толик и всемирно известный виолончелист Владимир Чижов встречают на шумной моск...
Обзоры на игры
• Обзор Ibara [PCB/PS2] 18357
• Обзор The Walking ... 18801
• Обзор DMC: Devil M... 19879
• Обзор на игру Valk... 15877
• Обзор на игру Stars! 17764
• Обзор на Far Cry 3 17948
• Обзор на Resident ... 16024
• Обзор на Chivalry:... 17508
• Обзор на игру Kerb... 17981
• Обзор игры 007: Fr... 16619
Превью о играх
• Превью к игре Comp... 17960
• Превью о игре Mage... 14464
• Превью Incredible ... 14721
• Превью Firefall 13479
• Превью Dead Space 3 16334
• Превью о игре SimC... 14730
• Превью к игре Fuse 15442
• Превью Red Orche... 15542
• Превью Gothic 3 16343
• Превью Black & W... 17354
Главная » Статьи » Разное » Трассировка процессов с помощью Ptrace (gcc debug linux elf exec)

Трассировка процессов с помощью Ptrace (gcc debug linux elf exec)

Ключевые слова: gcc, debug, linux, elf, exec, (найти похожие документы)

From: Андрей Киселев <kis_an [at] linuxgazette [dot] ru>
Newsgroups: Russian Linux Gazette
Date: Mon, 17 May 2004 18:21:07 +0000 (UTC)
Subject: Трассировка процессов с помощью Ptrace

Оригинал: http://gazette.linux.ru.net/lg81/sandeep.html

Трассировка процессов с помощью Ptrace -- Часть 1.
Автор: Sandeep S <http://www.linuxgazette.com/authors/sandeep.html>
Перевод: Андрей Киселев <kis_an [at] linuxgazette [dot] ru>


Системный вызов ptrace является основой основ для программ-отладчиков,
таких как gdb, но все же принципы работы с этим системным вызовом
недостаточно хорошо освещены в документации, если не считать, что
самой лучшей документацией являются исходные тексты ядра! Я постараюсь
продемонстрировать основные приемы при работе с ptrace и некоторые его
функциональные возможности, доступные в инструментальных средствах
подобных gdb.

1. Введение
-----------

ptrace() -- это системный вызов, который дает возможность одному
процессу управлять исполнением другого. Он так же позволяет изменять
содержимое памяти трассируемого процесса. Трассируемый процесс ведет
себя как обычно до тех пор пока не получит сигнал. Когда это
происходит, процесс переходит в состояние останова, а
процесс-трассировщик информируется об этом вызовом wait(). После этого
процесс-трассировщик, через вызовы ptrace, определяет реакцию
трассируемого процесса. Исключение составляет сигнал SIGKILL, который
само собой разумеется, уничтожает процесс.

Кроме того, можно задать переход трассируемого процесса в состояние
останова по определенному событию, которое возникло в ходе его
исполнения. Это происходит только в том случае, если
процесс-трассировщик установил какие-либо флаги событий в контексте
трассируемого процесса. Трассировщик может даже завершить трассируемый
процесс, установив при этом код его завершения. После выполнения
каких-либо действий трассировщик может завершить отлаживаемый процесс
или продолжить его исполнение.

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


2. Подробнее
------------

Объявление ptrace() выглядит следующим образом.
______________________________________________________________

#include <sys/ptrace.h>
long int ptrace(enum __ptrace_request request, pid_t pid,
void * addr, void * data)
______________________________________________________________

Вызову передаются четыре аргумента, где request -- определяет что
необходимо сделать. pid -- это ID трассируемого процесса. addr -- это
смещение в пользовательском пространстве трассируемого процесса,
откуда будет прочитано слово данных и возвращено в качестве результата
работы вызова.

Родительский процесс может породить дочерний процесс и выполнять его
трассировку посредством вызова ptrace с аргументом request, имеющим
значение PTRACE_TRACEME. Процесс-трассировщик может выполнять
трассировку уже существующего процесса, используя значение
PTRACE_ATTACH для аргумента request. Значения, которые может принимать
аргумент request, обсуждаются ниже.


2.Как работает ptrace().
------------------------

Всякий раз, когда вызывается ptrace, в первую очередь выполняется
блокировка ядра. А непосредственно перед возвратом блокировка
снимается. Рассмотрим работу ptrace() для различных значений аргумента
request.

PTRACE_TRACEME:

Это значение используется при трассировке дочернего процесса. Как уже
говорилось выше, любой сигнал (за исключением SIGKILL), как
поступивший от системы, так и поступивший через вызов exec, от самого
процесса, вынуждает процесс перейти в состояние останова, что
позволяет "родителю" определять дальнейший ход развития событий.
Единственное, что делает ptrace() в этом случае -- проверяет,
установлен ли флаг PT_PTRACED для текущего процесса. Если флаг не
установлен, то проверяются права доступа и флаг устанавливается. Все
остальные аргументы игнорируются.

PTRACE_ATTACH:

Это значение используется в том случае, если необходимо выполнить
трассировку существующего процесса. Единственное замечание: ни один
процесс не сможет получить контроль над процессом init или над самим
собой. Трассировка этих процессов является недопустимой. Выполнив
вызов ptrace, с этим значением аргумента request, процесс становится
"родителем" для процесса с ID равным pid. Однако, вызов getpid(),
выполняемый "дочерним" процессом, по-прежнему будет возвращать PID
реального родителя.

После обычной проверки прав доступа, проверяется -- не производится ли
попытка получить контроль над процессом init или над самим собой, не
установлен ли флаг PT_PTRACED. Если проблем не возникло, то
устанавливается флаг PT_PTRACED. Затем исправляются ссылки
трассируемого процесса, например, он удаляется из очереди задач, поле
ссылки на родительский процесс изменяется (подлинный родитель остается
тем же самым). Процесс снова помещается в очередь и ему передается
сигнал SIGSTOP. Аргументы addr и data игнорируются.

PTRACE_DETACH:

Прекращает трассировку процесса. В этот момент принимается решение о
прекращении или продолжении работы трассируемого процесса. Отменяются
все изменения, произведенные по PTRACE_ATTACH/PTRACE_TRACEME. Через
аргумент data устанавливается код завершения. В поле связи, у
трассируемого процесса, восстанавливается ссылка на настоящего
родителя. Сбрасывается бит пошаговой отладки. И наконец, трассируемый
процесс "пробуждается". Аргумент addr игнорируется.

PTRACE_PEEKTEXT, PTRACE_PEEKDATA, PTRACE_PEEKUSER:

При значениях аргумента request PTRACE_PEEKTEXT и PTRACE_PEEKDATA,
родительскому процессу возвращается слово, находящееся по адресу addr
в адресном пространстве трассируемого (дочернего) процесса. Оба эти
значения request приводят к одинаковым результатам. В случае
PTRACE_PEEKUSER - читается слово из структуры типа user (см.
sys/user.h), размещенной в системном адресном пространстве и
соответствующей трассируемому процессу. Аргумент addr задает смещение
от начала структуры. Прочитанное слово возвращается через аргумент
data. В случае успеха возвращается 0. Исходное значение аргумента data
игнорируется.

PTRACE_POKETEXT, PTRACE_POKEDATA, PTRACE_POKEUSER:

При значениях request, PTRACE_POKETEXT и PTRACE_POKEDATA, производится
запись значения аргумента data по адресу addr в пространстве
трассируемого процесса. Оба эти значения приводят к одинаковым
результатам.

В случае PTRACE_POKEUSER, производится запись в структуру типа user,
соответствующей трассируемому процессу. Следует быть очень осторожным
при работе с этим параметром, поскольку в данном случае мы вторгаемся
в область ядра. После выполнения большого количества проверок, ptrace
выполняет запись в указанную позицию структуры, при этом доступными
для записи оказываются далеко не все элементы структуры. Аргумент addr
в данном случае определяет смещение относительно начала структуры.

PTRACE_SYSCALL, PTRACE_CONT:

Обе эти команды активируют трассируемый процесс. В случае
PTRACE_SYSCALL дочернему процессу предписывается остановиться на
следующем системном вызове. PTRACE_CONT -- просто возобновляет работу
трассируемого процесса. И в том и в другом случае, если аргумент data
не равен нулю или SIGSTOP, ptrace() передает его процессу как сигнал,
который необходимо обработать. При этом ptrace() сбрасывает бит
пошаговой трассировки и устанавливает/сбрасывает бит трассировки
системных вызовов. Аргумент addr игнорируется.

PTRACE_SINGLESTEP:

Имеет тот же смысл, что и PTRACE_SYSCALL, за исключением того, что
трассируемый процесс останавливается после исполнения каждой
инструкции. Устанавливает бит пошаговой трассировки. Как и выше,
аргумент data содержит код завершения для трассируемого процесса.
Аргумент addr игнорируется.

PTRACE_KILL:

Используется для завершения трассируемого процесса. Завершение
производится следующим образом. Ptrace() проверяет -- "жив" ли
трассируемый процесс, затем устанавливает код завершения дочернего
процесса в значение sigkill, сбрасывает бит пошаговой трассировки и
активирует дочерний процесс, который в соответствии с кодом завершения
прекращает свою работу.


2.2 Аппаратно-зависимые значения для аргумента request
------------------------------------------------------

Описанные выше значения аргумента request являются
аппаратно-независимыми. Значения, описываемые ниже, позволяют
читать/изменять регистры процессора дочернего процесса, а потому очень
тесно связаны с аппаратной реализацией системы. Набор доступных
регистров включает в себя регистры общего назначения и регистры FPU
(арифметического сопроцессора).

PTRACE_GETREGS, PTRACE_GETFPREGS, PTRACE_GETFPXREGS:

При этих значениях request, после обычной проверки прав доступа,
производится копирование значений регистров общего назначения,
регистров с плавающей точкой, дополнительных регистров с плавающей
точкой дочернего процесса в переменную родительского процесса data.
Копирование выполняется с помощью функций getreg() и __put_user(),
аргумент addr игнорируется.

PTRACE_SETREGS, PTRACE_SETFPREGS, PTRACE_SETFPXREGS:

При этих значениях аргумента request выполняется запись в регистры
процессора трассируемого процесса. В данном случае доступ к отдельным
регистрам ограничивается. Значения регистров берутся из аргумента
data. Аргумент addr игнорируется.


2.3 Возвращаемые значения системного вызова ptrace()
----------------------------------------------------

В случае успеха ptrace() возвращает ноль. В случае возникновения
ошибки -- возвращается значение -1, а код ошибки -- в переменной
errno. Поскольку при выполнении операций PEEKDATA/PEEKTEXT, даже в
случае успеха может быть возвращено значение -1, то лучше выполнять
проверку на наличие ошибки по переменной errno. Коды ошибок могут быть
следующими

EPERM : Отсутствие прав доступа.

ESRCH : Требуемый процесс не найден или уже трассируется.

EIO : Недопустимый код запроса (request) или задан недопустимый адрес
памяти для чтения/записи.

EFAULT : Была сделана попытка записи информации в область памяти, но
скорее всего эта память не существует или недоступна.

К сожалению, зачастую ошибки EIO и EFAULT порождаются практически
идентичными ситуациями, из-за чего очень сложно интерпретировать
разницу между ними.


3. Небольшой пример.
--------------------

Если вы нашли описание аргументов чересчур сухим, не отчаивайтесь.
Далее я попробую представить ряд маленьких программ, которые
проиллюстрируют все вышесказанное.

Вот первый пример. Здесь родительский процесс подсчитывает число
инструкций, выполненных тестовой программой, которая запускается как
дочерний процесс.

Тестовая программа выводит содержимое текущего каталога и подсчитывает
количество затраченных машинных инструкций.
______________________________________________________________


#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>


int main(void)
{
long long counter = 0; /* Счетчик машинных инструкций */
int wait_val; /* значение, возвращаемое потомком */
int pid; /* pid потомка */

puts("Минутку терпения");

switch (pid = fork()) {
case -1:
perror("fork");
break;
case 0: /* запуск дочернего процесса */
ptrace(PTRACE_TRACEME, 0, 0, 0);
/*
* необходимо, чтобы передать
* управление дочернему процессу
*/
execl("/bin/ls", "ls", NULL);
/*
* выполнить программу и заставить
* потомка остановиться и передать сигнал
* родителю, теперь родитель
* сможет перейти в PTRACE_SINGLESTEP
*/
break;
/* завершение дочернего процесса */
default:/* запуск родительского процесса */
wait(&wait_val);
/*
* родитель ожидает, пока потомок не остановится
* на следующей инструкции (execl())
*/
while (wait_val == 1407 ) {
counter++;
if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0) != 0)
perror("ptrace");
/*
* переход в пошаговый режим
* и активация потомка
*/
wait(&wait_val);
/* ожидание выполнения следующей инструкции */
}
/*
* цикл продолжается до тех пор, пока
* потомок не завершит работу; wait_val != 1407
* младший байт = 0177L и старший = 05 (SIGTRAP)
*/
}
printf("Количество машинных инструкций : %lldn", counter);
return 0;
}
______________________________________________________________

Скопируйте текст программы в текстовый редактор, сохраните ее в файл
file.c и дайте команды на выполнение:

cc file.c
./a.out

В результате работы программы, на экран будут выведены содержимое
текущего каталога и количество затраченных машинных инструкций. Теперь
попробуйте перейти в другой каталог и запустить программу оттуда.
Сравните полученные результаты. (Обратите внимание, если у вас
медленная машина, то вывод может занять довольно продолжительное
время). (На P4 1.7 ГГц на это ушло около 7 секунд. Прим.ред.)


4. Заключение
-------------

Ptrace() -- это средство отладки программ. Он может использоваться и
для трассировки системных вызовов. Родительский процесс может начать
трассировку, вызвав сначала функцию fork(2), для запуска дочернего
процесса, а затем дочерний процесс может выполнить PTRACE_TRACEME, за
которым (как правило) следует выполнение exec(3) (в примере выше --
это программа "ls"). Затем, после выполнения каждой инструкции,
родитель может просматривать значения регистров потомка, данные в
памяти и влиять на протекание процесса исполнения. В следующей части
статьи я приведу пример программы, которая использует различные
особенности ptrace(). До скорой встречи!



Трассировка процессов с помощью Ptrace -- Часть 2
Оригинал: http://gazette.linux.ru.net/lg83/sandeep.html

В первой части мы узнали об основных особенностях ptrace и
рассмотрели небольшой пример. Как я уже говорил ранее, практически
всегда процессы-отладчики обращаются к памяти или регистрам процессора
трассируемого приложения. Теперь бы я хотел осветить строение
исполняемых файлов, чтобы вы получили представление о том что и где в
них находится. Здесь я расскажу о формате исполняемых файлов ELF,
используемом в Linux. А в последнем разделе этой части я представлю
небольшой пример программы, которая изменяет содержимое памяти и
регистров процессора другой программы, и внедряет в ее тело некоторый
дополнительный исполняемый код.


Обратите внимание: Пусть вас не смущает такое вступление. Эта часть
статьи, вне всякого сомнения, рассказывает о ptrace, а не об ELF. Но
знание формата ELF определенно необходимо, чтобы уметь обращаться к
памяти трассируемого процесса. Итак, приступим.


1. Что такое ELF?
-----------------

ELF -- это Executable and Linking Format (Формат Исполняемых и
Связываемых файлов). Он определяет формат двоичных исполняемых файлов,
объектных файлов, разделяемых объектов (библиотек), а так же файлов
core dump. Формат ELF используется как компоновщиками (linkers), так и
загрузчиками программ, хотя каждый из них интерпретирует ELF-файлы
по-своему.

ELF-файл состоит из множества секций и сегментов. Объектные файлы
имеют таблицу заголовков секций, исполняемые -- таблицу программных
заголовков, а разделяемые библиотеки -- и то и другое. В следующем
разделе я познакомлю вас с этими заголовками поближе.


2. Заголовки ELF
----------------

Любой ELF-файл имеет ELF-заголовок. Заголовок всегда размещается в
самом начале файла. Он содержит описание двоичного файла, определяя
таким образом порядок интерпретации файла.

Структура заголовка приведена ниже (см. /usr/src/include/linux/elf.h)
(путь меняется в зависимости от дистрибутива -- прим.ред.)

______________________________________________________________

#define EI_NIDENT 16

typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry; /* Точка входа */
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
______________________________________________________________


Кратко опишу поля структуры
1. e_ident : Сигнатура и прочая информация. Зависит от аппаратной
платформы.
2. e_type : Содержит информацию о типе файла. Тип может быть одним из
следующих: "объектный", "исполняемый", "разделяемый" (shared
object) и "core".
3. e_machine : Вы наверняка уже догадались, что это поле определяет
аппаратную архитектуру -- Intel 386, Alpha, Sparc и т.п.
4. e_version : Версия объектного файла.
5. e_phoff : Смещение до первого программного заголовка.
6. e_shoff : Смещение до первого заголовка секции.
7. e_flags : Флаги процессора. Не используется для i386
8. e_ehsize : Размер ELF-заголовка в байтах.
9. e_phentsize & e_shentsize : Размер программного заголовка и
заголовка секции, в таблицах программных заголовков и заголовков
секций соответственно.
10. e_phnum & e_shnum : Количество программных заголовков и заголовков
секций в соответствующих таблицах.
11. e_shstrndx : В таблице заголовков секций есть секция, которая
содержит имена других секций. Это индекс такой секции в таблице.
(см. ниже)


3. Секции и Сегменты
--------------------

Как я уже упоминал выше, компоновщики интерпретируют файл как
множество секций, описанных в таблице заголовков секций, а загрузчик
-- как множество сегментов, описанных в таблице программных
заголовков. В этом разделе приводится более или менее подробное
описание заголовков секций и сегментов.


3.1 Секции и заголовки секций
-----------------------------

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

Таблица заголовков секций представляет из себя массив заголовков.
Нулевой элемент массива всегда пуст и не соответствует ни одной из
секций. Каждый заголовок секции имеет следующий формат (см.
/usr/src/include/linux/elf.h):
______________________________________________________________


typedef struct elf32_shdr {
Elf32_Word sh_name; /* Имя секции, индекс в таблице строк (Elf32) */
Elf32_Word sh_type; /* Тип секции (Elf32) */
Elf32_Word sh_flags; /* Различные атрибуты секции */
Elf32_Addr sh_addr; /* Виртуальный адрес секции */
Elf32_Off sh_offset; /* Смещение от начала файла */
Elf32_Word sh_size; /* Размер секции в байтах */
Elf32_Word sh_link; /* Индекс следующей секции (Elf32) */
Elf32_Word sh_info; /* Дополнительные сведения о секции (Elf32) */
Elf32_Word sh_addralign; /* Выравнивание секции */
Elf32_Word sh_entsize; /* Размер записи в таблице */
} Elf32_Shdr;
______________________________________________________________


Теперь о полях структуры более подробно.
1. sh_name : Индекс строки в секции, содержащей таблицу строк
e_shstrndx. Указывает на начало строки, завершающейся нулевым
(0x00) символом, которая используется в качестве имени секции.
+ .text -- Эта секция содержит инструкции, исполняемые
процессором
+ .data -- В этой секции находятся инициализированные данные
программы.
+ .init -- Эта секция содержит инструкции, исполняемые
процессором при запуске программы.
2. sh_type : Тип секции, например, данные, таблица символов, таблица
строк и т.п..
3. sh_flags : Содержит вспомогательную информацию, определяющую
порядок интерпретации содержимого секции.
4. sh_addralign : Содержит размер выравнивания для секции, обычно 0,
1 (оба означают отсутствие выравнивания) или 4.

Смысл назначения остальных полей структуры достаточно прозрачно
следует из их названий.


3.2 Сегменты и программные заголовки.
-------------------------------------

Сегменты используются в процессе загрузки программы, т.е. при создании
образа процесса в памяти. Каждый сегмент описывается соответствующим
программным заголовком. Таблица представляет из себя массив
заголовков. Каждый программный заголовок имеет следующий формат:
______________________________________________________________

typedef struct
{
Elf32_Word p_type; /* Тип сегмента */
Elf32_Off p_offset; /* Смещение от начала файла */
Elf32_Addr p_vaddr; /* Виртуальный адрес сегмента */
Elf32_Addr p_paddr; /* Физический адрес сегмента */
Elf32_Word p_filesz; /* Размер сегмента в файле */
Elf32_Word p_memsz; /* Размер сегмента в памяти */
Elf32_Word p_flags; /* Флаги сегмента */
Elf32_Word p_align; /* Выравнивание сегмента */
} Elf32_Phdr;
______________________________________________________________

1. p_type : Определяет тип сегмента, т.е. задает порядок его
интерпретации, например:
+ неиспользуемый (unused)
+ загружаемый (loadable)
+ Информация для динамического связывания
+ зарезервировано (reserved)
и т.п..
2. p_vaddr : относительный виртуальный адрес загрузки сегмента.
3. p_paddr : физический адрес загрузки сегмента.
4. p_flags : Содержит флаги прав доступа -- чтение/запись/исполнение
5. p_align : Выравнивание сегмента в памяти. Если сегмент имеет тип
"загружаемый" (loadable), то он выравнивается по границе страницы
памяти.

Смысл назначения остальных полей структуры понятен из их названий.


4. Загрузка ELF-файла
---------------------

Мы уже имеем представление о структуре ELF-файла. Теперь перейдем к
рассмотрению порядка загрузки файла. Обычно, для того чтобы запустить
программу мы набираем ее имя в командной строке. На самом деле, после
того как мы нажмем на клавишу RETURN (или, если хотите -- ENTER),
происходит масса интересных вещей.

Прежде всего командная оболочка вызывает стандартную функцию из libc,
которая в свою очередь обращается к ядру. Теперь в игру вступает ядро.
Ядро открывает файл и определяет тип файла как исполняемый. Затем
загружает файл и все необходимые библиотеки, инициализирует стек и
передает управление загруженной программе.

Программа загружается по адресу 0x08048000 (см. /proc/pid/maps), а
стек начинается с адреса 0xBFFFFFFF (стек "растет" в сторону меньших
адресов).


5. Внедрение кода
-----------------

Теперь, когда процесс загружен в память и нам известно его адресное
пространство, мы можем выполнять трассировку этого процесса (при
наличии прав доступа) и просматривать/изменять данные в памяти
процесса. Однако сказать легко, сделать -- сложнее. Тем не менее,
почему бы не попробовать?

Прежде всего, давайте попробуем создать программу, которая могла бы
читать/писать в регистры процессора другой программы. Для аргумента
request мы будем использовать следующие значения.
* PTRACE_ATTACH : Начать трассировку существующего процесса с
заданным pid.
* PTRACE_DETACH : Завершить трассировку процесса с заданным pid.
Важно : Не следует забывать о необходимости этого вызова, иначе
процесс останется в режиме останова.
* PTRACE_GETREGS : Скопировать содержимое регистров процессора в
структуру, адрес которой передается в аргументе data (аргумент
addr игнорируется). Эта структура (struct user_regs_struct)
определена в файле asm/user.h.
______________________________________________________________

struct user_regs_struct {
long ebx, ecx, edx, esi, edi, ebp, eax;
unsigned short ds, __ds, es, __es;
unsigned short fs, __fs, gs, __gs;
long orig_eax, eip;
unsigned short cs, __cs;
long eflags, esp;
unsigned short ss, __ss;
};
______________________________________________________________

* PTRACE_SETREGS : Скопировать данные из структуры, адрес которой
передается в аргументе data, в регистры процессора.
* PTRACE_POKETEXT : Скопировать 32-битное слово из адреса, который
передается в аргументе data, в область памяти трассируемого
процесса, адресуемой аргументом addr.

Теперь вставим некоторый код в тело трассируемого процесса и заставим
процесс исполнить его, изменив содержимое регистра eip (instruction
pointer).

Делается это довольно просто. Для начала мы запускаем трассировку
процесса, затем считываем содержимое регистров. Куда-нибудь в стек
вставляем наш код, который мы хотим исполнить и изменяем содержимое
регистра eip таким образом, чтобы он указывал на первую инструкцию
нашего кода. После чего завершаем трассировку процесса. После этих
действий трассируемый процесс приступит к исполнению нашего
внедренного кода.

Пример состоит из трех файлов с исходными текстами. Первый -- это сам
трассировщик. Второй -- это код на языке Ассемблера, который будет
вставляться в тело трассируемого процесса. И третий -- небольшая
программка, которая будет подвержена нашим экспериментам.

Файлы с исходными текстами
* Tracer.c http://gazette.linux.ru.net/lg83/misc/sandeep/Tracer.c
* Code.S http://gazette.linux.ru.net/lg83/misc/sandeep/Code.S
* Sample.c http://gazette.linux.ru.net/lg83/misc/sandeep/Sample.c

Скомпилируем файлы.

#cc Sample.c -o loop
#cc Tracer.c Code.S -o catch

Перейдите в другую консоль и запустите программу loop:

#./loop

Вернитесь обратно и запустите трассировщик:

#./catch `ps ax | grep "loop" | cut -f 2 -d ' '`

Теперь вернитесь в консоль, в которой была запущена программа 'loop' и
увидите что произошло! Итак! Ваши игры с ptrace начались!

От переводчика: В программу Tracer.c мною были внесены изменения. В
оригинальном варианте программа loop при исполнении внедренного кода
выводила сообщение "Oh, Caught!". Я взял на себя смелость заменить его
текстом "Во! Поймали!". Однако текст "зашит" в кодировке koi8-r,
поэтому, если у вас локаль настроена на иную кодировку, то вы увидите
это сообщение в искаженном виде. Оригинальный вариант файла
Tracer.c находится здесь (http://gazette.linux.ru.net/lg83/misc/sandeep/Tracer.c.orig).

6. Забегая вперед
-----------------

В первой части статьи мы подсчитали количество ассемблерных
инструкций, выполненных программой. В этой части мы рассмотрели
структуру исполняемого файла и попробовали вставить свой код в тело
"подопытного" процесса. В следующей части я покажу как получить доступ
к памяти трассируемого процесса. До скорых встреч! Sandeep S.



Трассировка процессов с помощью Ptrace -- Часть 3
Оригинал: http://gazette.linux.ru.net/lg85/sandeep.html

В первой части мы с вами рассмотрели основы работы с ptrace. Во
второй части был приведен пример небольшой программы, которая
изменяла содержимое регистров процессора в другом приложении,
заставляя его выполнить внедренный код. На этот раз мы будем учиться
обращаться к памяти трассируемого процесса. Цель данной части состоит
в том, чтобы продемонстрировать методику доступа к идентификаторам
процесса во время исполнения. Область применения этой методики
настолько широка, что ограничивается лишь вашей фантазией.


1. Введение.
------------

Мы уже знакомы с ptrace и знаем как начать трассировку существующего
процесса, как выполнять и как завершать ее. Мы также познакомились с
форматом двоичных исполняемых файлов Linux -- ELF.

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

Объявление структуры link_map (см. /usr/include/link.h) выглядит так:

struct link_map
{
ElfW(Addr) l_addr; /* Базовый адрес загруженного объекта. */
char *l_name; /* Полное имя файла объекта. */
ElfW(Dyn) *l_ld; /* Динамическая секция разделяемого объекта. */
struct link_map *l_next, *l_prev; /* Ссылки на загруженные объекты. */
};


Краткое описание полей структуры.
* l_addr: Базовый адрес памяти, куда был загружен объект. Это
значение можно найти в /proc/<pid>/maps
* l_name: указатель на имя библиотеки в таблице строк
* l_ld :указатель на динамическую (DT_*) секцию разделяемой
библиотеки
* l_next: указатель на следующий элемент списка
* l_prev: указатель на предыдущий элемент списка

Link-map -- это двусвязный список, каждый элемент которого имеет
ссылку на загруженную библиотеку. Все что нам нужно -- это пройти по
списку и отыскать требуемый идентификатор. Теперь мы подошли к
вопросу: "И где же взять этот link_map?"

Для каждого объектного файла создается Глобальная Таблица Смещений
(global offset table -- GOT). Второй элемент этой таблицы как раз и
отвечает за link_map. Так что нам остается лишь забрать адрес link_map
из GOT[1] и найти искомый идентификатор.


2. Пример кода.
---------------

Теперь мы владеем основными сведениями, которые нам понадобятся, можно
начинать. Прежде всего начнем трассировку процесса 'pid', а затем
отыщем link_map. В файле с исходным текстом примера вы найдете ряд
вспомогательных функций, таких как read_data, read_str и пр., которые
значительно облегчают жизнь программиста при работе с ptrace.
Назначение функций очевидно из их названий.

Функция для поиска link_map :


struct link_map *locate_linkmap(int pid)
{
Elf32_Ehdr *ehdr = malloc(sizeof(Elf32_Ehdr));
Elf32_Phdr *phdr = malloc(sizeof(Elf32_Phdr));
Elf32_Dyn *dyn = malloc(sizeof(Elf32_Dyn));
Elf32_Word got;
struct link_map *l = malloc(sizeof(struct link_map));
unsigned long phdr_addr, dyn_addr, map_addr;

read_data(pid, 0x08048000, ehdr, sizeof(Elf32_Ehdr));
phdr_addr = 0x08048000 + ehdr->e_phoff;
printf("program header at %pn", phdr_addr);
read_data(pid, phdr_addr, phdr, sizeof(Elf32_Phdr));

while (phdr->p_type != PT_DYNAMIC) {
read_data(pid, phdr_addr += sizeof(Elf32_Phdr), phdr,
sizeof(Elf32_Phdr));
}

read_data(pid, phdr->p_vaddr, dyn, sizeof(Elf32_Dyn));
dyn_addr = phdr->p_vaddr;

while (dyn->d_tag != DT_PLTGOT) {
read_data(pid, dyn_addr += sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
}

got = (Elf32_Word) dyn->d_un.d_ptr;
got += 4; /* помните? второй элемент таблицы GOT. */

read_data(pid, (unsigned long) got, &map_addr, 4);
read_data(pid, map_addr, l, sizeof(struct link_map));
free(phdr);
free(ehdr);
free(dyn);
return l;
}

Поиск начинается с адреса 0x08048000, где размещен ELF-заголовок
трассируемого процесса. По содержимому ELF-заголовка определяется
местоположение программного заголовка. (Структура заголовков
обсуждалась во второй части статьи.) После того как будет получен
программный заголовок, выполняется поиск заголовка с информацией о
динамическом связывании. Затем по полученной информации выполняется
поиск базового адреса глобальной таблицы смещений.

После того как получен адрес таблицы -- из нее читается второй
элемент, где находится адрес link_map, а из полученного адреса
читается содержимое link_map, которое и возвращается в качестве
результата.

Мы получили в свое распоряжение struct link_map, а теперь надо
получить таблицу символов (symtab) и таблицу строк (strtab). Для этого
обратимся к полю l_ld структуры link_map и пройдемся по набору
динамических секций, пока не обнаружим секции DT_SYMTAB и DT_STRTAB, в
этих секциях мы как раз и попытаемся обнаружить искомые
идентификаторы.

Функция поиска таблицы символов и таблицы строк:

void resolv_tables(int pid, struct link_map *map)
{
Elf32_Dyn *dyn = malloc(sizeof(Elf32_Dyn));
unsigned long addr;
addr = (unsigned long) map->l_ld;
read_data(pid, addr, dyn, sizeof(Elf32_Dyn));
while (dyn->d_tag) {
switch (dyn->d_tag) {
case DT_HASH:
read_data(pid, dyn->d_un.d_ptr + map->l_addr + 4,
&nchains, sizeof(nchains));
break;
case DT_STRTAB:
strtab = dyn->d_un.d_ptr;
break;
case DT_SYMTAB:
symtab = dyn->d_un.d_ptr;
break;
default:
break;
}
addr += sizeof(Elf32_Dyn);
read_data(pid, addr, dyn, sizeof(Elf32_Dyn));
}
free(dyn);
}

Эта функция проходит по динамическим секциям, проверяя каждую -- не
содержит ли она признак DT_STRTAB или DT_SYMTAB. Если секция имеет
один из этих признаков, то адрес таблицы запоминается в
соответствующем указателе strtab или symtab. Цикл завершается после
того, как будут просмотрены все динамические секции.

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

Теперь мы получили значение идентификатора, что собственно и
требовалось. Как его можно использовать? Ответ на этот вопрос зависит
уже от вас, уважаемый читатель. Как и все в этом мире, его можно
использовать как во благо так и во вред.

У вас может сложиться впечатление, что на этом мы закончили -- но это
не так. Мы забыли выполнить еще один обязательный шаг -- "отпустить"
приложение, т.е. закончить трассировку. Если этого не сделать, то
трассируемое приложение останется в состоянии останова "на веки
вечные", последствия этого мы частично рассматривали в [6]части I. Так
что прежде чем завершить работу мы завершаем трассировку.

Пример программы вы найдете в файле Ptrace.c
http://gazette.linux.ru.net/lg85/misc/sandeep/Ptrace.c.txt

Соберите программу командой

#cc Ptrace.c -o symtrace

А теперь протестируем ее. Запустите какое либо приложение (в другом
терминале) и подайте следующую команду. (Здесь я хочу уточнить, что в
качестве "подопытного" приложения я запускал emacs и искал
идентификатор strcpy). Вы можете выбрать для экспериментов любое
другое приложение и попытаться отыскать в нем любой другой
идентификатор.

#./symtrace `ps ax | grep 'emacs' | cut -f 2 -d " "` strcpy

От переводчика: на моей системе этот вариант команды работает
некорректно. Я использовал следующую команду:
#./symtrace `ps -e | grep 'emacs' | cut -f 1 -d " "` strcpy

От редактора: объясняется это тем, что ключ '-e' команды ps выводит
список процессов без параметров, с которыми они были вызваны, что
снижает (по крайней мере, исключает сам grep из результатов), но не
исключает вероятность дублирования информации (слегка утрируя):

#ps -e | grep 'log'
942 ? 00:00:00 syslogd
960 ? 00:00:00 klogd


3. Заключение.
--------------

Итак, мы подошли к концу последней части статьи, посвященной основам
работы с ptrace. Как только вы окончательно поймете основную
концепцию, то для вас не составит труда двинуться дальше. Более
подробные сведения о ptrace и об ELF вы найдете на [8]www.phrack.org.
Еще я хотел бы заметить, что мы подошли к концу последней части ни
разу не упомянув об одной важной особенности ptrace -- взаимодействии
с системными вызовами. В User Mode Linux эта особенность используется
очень широко. Сейчас я занят своей учебой и работой над курсовым
проектом, но обещаю, что как только позволит время я вернусь к этой
теме и мы продолжим рассмотрение особенностей ptrace.

Приветствуются любые предложения, замечания, дополнения и пр. Пишите
мне по адресу: busybox at sancharnet.in

Copyright (C) 2002, Sandeep S. Copying license
http://www.linuxgazette.com/copying.html
Published in Issue 85 of Linux Gazette, December 2002


Ptrace.c:

#include <link.h>
#include <elf.h>
#include <sys/ptrace.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <errno.h>

/*
* поиск местоположения секций DT_SYMTAB и DT_STRTAB и сохранение указателей на них
* в глобальных переменных.
*/

unsigned long symtab;
unsigned long strtab;
int nchains;

/* начать трассировку */
void ptrace_attach(int pid)
{
if ((ptrace(PTRACE_ATTACH, pid, NULL, NULL)) < 0) {
perror("ptrace_attach");
exit(-1);
}
waitpid(pid, NULL, WUNTRACED);
}

/* закончить трассировку */
void ptrace_detach(int pid)
{
if (ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) {
perror("ptrace_detach");
exit(-1);
}
}

/* прочитать данные из указанного адреса */
void read_data(int pid, unsigned long addr, void *vptr, int len)
{
int i, count;
long word;
unsigned long *ptr = (unsigned long *) vptr;
count = i = 0;
while (count < len) {
word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
count += 4;
ptr[i++] = word;
}
}

/* прочитать строку из памяти процесса */
char *read_str(int pid, unsigned long addr, int len)
{
char *ret = calloc(32, sizeof(char));
read_data(pid, addr, ret, len);
return ret;
}

/* записать данные по указанному адресу */
void write_data(int pid, unsigned long addr, void *vptr, int len)
{
int i, count;
long word;
i = count = 0;
while (count < len) {
memcpy(&word, vptr + count, sizeof(word));
word = ptrace(PTRACE_POKETEXT, pid, addr + count, word);
count += 4;
}
}

/* поиск структуры link-map в памяти процесса */
struct link_map *locate_linkmap(int pid)
{
Elf32_Ehdr *ehdr = malloc(sizeof(Elf32_Ehdr));
Elf32_Phdr *phdr = malloc(sizeof(Elf32_Phdr));
Elf32_Dyn *dyn = malloc(sizeof(Elf32_Dyn));
Elf32_Word got;
struct link_map *l = malloc(sizeof(struct link_map));
unsigned long phdr_addr, dyn_addr, map_addr;

/*
* для начала найти в elf-заголовке, расположенному по адресу 0x08048000,
* таблицу программных заголовков, откуда мы попробуем
* извлечь информацию о секции PT_DYNAMIC.
*/

read_data(pid, 0x08048000, ehdr, sizeof(Elf32_Ehdr));
phdr_addr = 0x08048000 + ehdr->e_phoff;
printf("program header at %pn",(void *) phdr_addr);
read_data(pid, phdr_addr, phdr, sizeof(Elf32_Phdr));

while (phdr->p_type != PT_DYNAMIC) {
read_data(pid, phdr_addr += sizeof(Elf32_Phdr), phdr,
sizeof(Elf32_Phdr));
}

/*
* теперь пройти по динамической секции, чтобы найти адрес GOT
*/

read_data(pid, phdr->p_vaddr, dyn, sizeof(Elf32_Dyn));
dyn_addr = phdr->p_vaddr;

while (dyn->d_tag != DT_PLTGOT) {
read_data(pid, dyn_addr +=
sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
}

got = (Elf32_Word) dyn->d_un.d_ptr;
got += 4; /* second GOT entry, remember? */
/*
* теперь просто прочитать первую запись в link_map и вернуть ее
*/
read_data(pid, (unsigned long) got, &map_addr, 4);
read_data(pid, map_addr, l, sizeof(struct link_map));
free(phdr);
free(ehdr);
free(dyn);
return l;
}

/* поиск идентификатора по таблицам */
void resolv_tables(int pid, struct link_map *map)
{
Elf32_Dyn *dyn = malloc(sizeof(Elf32_Dyn));
unsigned long addr;
addr = (unsigned long) map->l_ld;
read_data(pid, addr, dyn, sizeof(Elf32_Dyn));
while (dyn->d_tag) {
switch (dyn->d_tag) {
case DT_HASH:
read_data(pid, dyn->d_un.d_ptr +
map->l_addr + 4, &nchains,
sizeof(nchains));
break;
case DT_STRTAB:
strtab = dyn->d_un.d_ptr;
break;
case DT_SYMTAB:
symtab = dyn->d_un.d_ptr;
break;
default:
break;
}
addr += sizeof(Elf32_Dyn);
read_data(pid, addr, dyn, sizeof(Elf32_Dyn));
}
free(dyn);
}

/* поиск символа в DT_SYMTAB */
unsigned long find_sym_in_tables(int pid, struct link_map *map,
char *sym_name)
{
Elf32_Sym *sym = malloc(sizeof(Elf32_Sym));
char *str;
int i = 0;
while (i < nchains) {
read_data(pid, symtab + (i * sizeof(Elf32_Sym)), sym,
sizeof(Elf32_Sym));
i++;
if (ELF32_ST_TYPE(sym->st_info) != STT_FUNC)
continue;

/* прочитать имя идентификатора из таблицы строк */
str = read_str(pid, strtab + sym->st_name, 32);

/* сравнить с заданным */
if (strcmp(str, sym_name) == 0) {
printf("nSuccess: got itn");
return (map->l_addr + sym->st_value);
}
}
/* идентификатор не найден - вернуть 0 */
printf("nЗаданный идентификатор, %s, не найденn", sym_name);
return 0;
}

int main(int argc, char *argv[])
{
int pid;
unsigned long value;
struct link_map *lm;
pid = atoi(argv[1]);
ptrace_attach(pid);
lm = locate_linkmap(pid);
printf("nАдрес link_map = %pn", lm);
resolv_tables(pid, lm);
value = find_sym_in_tables(pid, lm, argv[2]);
printf("Значение %s = %xn", argv[2], value);
ptrace_detach(pid);
return 0;
}
571 Прочтений •  [Трассировка процессов с помощью Ptrace (gcc debug linux elf exec)] [08.05.2012] [Комментариев: 0]
Добавил: Ukraine Vova
Ссылки
HTML: 
[BB Url]: 
Похожие статьи
Название Добавил Добавлено
• Трассировка процессов с помощью Ptr... Ukraine Vova 08.05.2012
Ни одного комментария? Будешь первым :).
Пожалуйста, авторизуйтесь для добавления комментария.

Проект входит в сеть сайтов «8Gamers Network»

Все права сохранены. 8Gamers.NET © 2011 - 2025

Статьи
Рецензия на Pressure
Рецензия на Pressure
Чтобы обратить на себя внимание, начинающие маленькие разработчики, как правило, уходят в жанры, ...
Рецензия на Lost Chronicles of Zerzura
Рецензия на Lost Chron...
Игры, сделанные без любви и старания, похожи на воздушный шар – оболочка есть, а внутри пусто. Lo...
Рецензия на The Bridge
Рецензия на The Bridge
«Верх» и «низ» в The Bridge — понятия относительные. Прогуливаясь под аркой, можно запросто перей...
Рецензия на SimCity
Рецензия на SimCity
Когда месяц назад состоялся релиз SimCity, по Сети прокатилось цунами народного гнева – глупые ош...
Рецензия на Strategy & Tactics: World War 2
Рецензия на Strategy &...
Название Strategy & Tactics: World War II вряд ли кому-то знакомо. Зато одного взгляда на ее скри...
Рецензия на игру Scribblenauts Unlimited
Рецензия на игру Scrib...
По сложившейся традиции в информационной карточке игры мы приводим в пример несколько похожих игр...
Рецензия на игру Walking Dead: Survival Instinct, The
Рецензия на игру Walki...
Зомби и продукция-по-лицензии — которые и сами по себе не лучшие представители игровой биосферы —...
Обратная связь | RSS | Донейт | Статистика | Команда | Техническая поддержка