Классический алгоритм внедрения shell-кода выглядит так: сохраняем несколько байт перехватываемой функции и ставим jump на свой thunk, который делает что задумано, выполняет сохраненные байты и передает управление оригинальной функции, которая может вызываться как по jump, так и по call (подробнее этот вопрос рассмотрен в статье "crackme, прячущий код на API-функциях", опубликованной в Хакере).
Самое сложное— выбрать место для размещения thunk'а. Это должна быть память доступная всем процессам, а такой памяти в нашем распоряжении нет! Мы знаем, что "подопытная" библиотека отображается на адресное пространство всех процессов, но это пространство уже занято! Наскрести пару десятков байт, отведенных под выравнивание вполне реально, только нам этого не хватит! Приходится хитрить.
Самое простое: разместить код перехватчика в какой-нибудь "ненужной" функции, например, gets, а в начало всех перехватываемых функций внедрить… нет, не jump, поскольку в этом случае перехватчик не сможет определить откуда пришел вызов, а call gets. Внутри gets, перехватчик выталкивает из стека адрес возврата, уменьшает его на длину команды call (в 32-разрядном режиме — 5 байт) и получает искомый указатель на функцию. Зная указатель, можно определить имя функции — в этом нам поможет функция dladdr из GNU Extensions. В POSIX она не входит, но поддерживается практически всеми UNIX'ами, так что на этот счет можно не волноваться. (Примечание: напоминаем, что при внедрении в gets, равно как и любую другую функцию, мы можем пересекать границы страниц, поскольку за концом текущей страницы наверняка находится совсем посторонняя область памяти! если же возникает необходимость модифицировать функцию gets целиком, необходимо найти все принадлежащие ей страницы, тем же самым методом, которым мы нашли первую из них).
Проблема в том, что dladdr находится в библиотеке libdl.x.so, которой может и не быть в памяти конкретно взятого процесса, а если она там есть, то хрен знает по какому адресу загружена. Некоторые хакеры утверждают, что в thunk-коде можно использовать только прямые вызовы ядра через интерфейс INT 80h, а все остальные функции недоступны. На самом деле это не так! Как показывает дизассемблер, dladdr это всего лишь "обертка" вокруг _dl_addr, реализованной в libc.so.x, а она-то доступна наверняка! Вот только на базовый адрес загрузки закладываться ни в коем случае нельзя и вызов должен быть относительным.