Sunday, October 27, 2013

Практическое применение LD_PRELOAD или замещение функций в Linux

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

Как это работает?

Да очень просто — линкер загружает вашу библиотеку с вашими «стандартными» функциями первой, а кто первый — того и тапки. А вы из своей библиотеки можете загрузить уже реальную, и «проксировать» вызовы, попутно делая что вам угодно.

Реальный Use-Case #1: Блокируем mimeinfo.cache в Opera

Мне очень нравится браузер Opera. А еще я использую KDE. Opera не очень уважает приоритеты приложений KDE, и, зачастую, так и норовит открыть скачанный ZIP-архив в mcomix, PDF в imgur-uploader, в общем, вы уловили суть. Однако, если ей запретить читать файл mimeinfo.cache, то она все будет открывать через «kioclient exec», а он-то уж лучше знает, в чем я хочу открыть тот или иной файл.

Чем может приложение открывать файл? На ум приходят две функции: fopen и open. В моем случае, opera использовала 64-битный аналог fopen — fopen64. Определить это можно, воспользовавшись утилитой ltrace, или просто посмотрев таблицу импорта утилитой objdump.

Что нам нужно для написания библиотеки? Первым делом, нужно составить прототип оригинальной функции.
Судя по man fopen, прототип у этой функции следующий:

FILE *fopen(const char *path, const char *mode)

И возвращает она указатель на FILE, либо NULL, если файл невозможно открыть. Отлично, пишем код:

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>

static FILE* (*fopen64_orig)(const char * path, const char * mode) = NULL;

FILE* fopen64(const char * path, const char * mode) {
    if (fopen64_orig == NULL)
        fopen64_orig = dlsym(RTLD_NEXT, "fopen64");
    if (strstr(path, "mimeinfo.cache") != NULL) {
        printf("Blocking mimeinfo.cache read\n");
        return NULL;
    }
    return fopen64_orig(path, mode);
}

Как видите, все просто: объявляем функцию fopen64, загружаем «следующую» (оригинальную), по отношению к нашей, функцию, и проверяем, не открываем ли мы файл «mimeinfo.cache». Компилируем ее следующей командой:

gcc -shared -fPIC -ldl -O2 -o opera-block-mime.so opera-block-mime.c

И запускаем opera:

LD_PRELOAD=./opera-block-mime.so opera

И видим:

Blocking mimeinfo.cache read
Blocking mimeinfo.cache read
Blocking mimeinfo.cache read
Blocking mimeinfo.cache read

Успех!

Реальный Use-Case #2: Превращаем файл в сокет

Read more: Habrahabr.ru
QR: Inline image 1