Перенаправление функций в Mach-O библиотеках

    В предыдущей статье был описан метод перехвата вызовов для разделяемых библиотек ELF. А сейчас мы посмотрим как сделать то же самое с библиотеками в формате Mach-O.

    Вкратце напомню ситуацию. Имеем программу под Mac OS X, которая пользуется множеством сторонних динамически-компонуемых библиотек, которые, в свою очередь, также пользуются функциями друг друга.

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

    Как обычно, нетерпеливые могут все скачать и попробовать прямо сейчас.

    Для наглядности — воображаемый пример: у нас есть программа с названием «test» на языке С (файл test.c) и разделяемая библиотека (файл libtest.c), с неизменным содержимым, откомпилированные заранее. Эта библиотека предоставляет одну функцию: libtest(). В своей реализации каждая из них пользуется функцией puts() из стандартной библиотеки языка С (поставляется вместе с Mac OS, содержится в libSystem.B.dylib). Посмотрим на схематическое изображение описываемой ситуации:

    Задача состоит в следующем:
    1. Нужно заменить вызов функции puts() для библиотеки libtest.dylib на вызов функции hooked_puts(), реализованной в главной программе (файл test.c), которая, в свою очередь, может пользоваться оригинальной puts();
    2. Отменить произведенные изменения, то есть сделать так, чтобы повторный вызов libtest() приводил к вызову оригинальной puts().
    При этом менять код или перекомпилировать сами библиотеки не разрешается, только главную программу. Само перенаправление вызова должно осуществляться только для конкретной библиотеки и налету, без перезапуска программы.

    Кратко о Mach-O


    Лучший способ понять Mach-O – это посмотреть на картинку ниже.

    Похоже, человечество еще не сумело изобразить его структуру более наглядно. В первом приближении все выглядит примерно так:
    1. Заголовок — здесь хранится информация о целевой архитектуре и различные опции дальнейшей интерпретации содержимого файла.
    2. Команды загрузки — сообщают как и куда загружать части Mach-O: сегменты (см. ниже), таблицы символов, а также — от каких библиотек зависит этот файл, чтобы сперва загрузить их
    3. Сегменты — описывают регионы памяти, куда загружать секции с кодом или данными.
    Утилиты-парсеры

    Для второго приближения придется познакомится с некоторыми утилитами:
    • otool — представляет собой консольную программу, поставляемую вместе с системой. Она способна отображать содержимое различных частей файла: заголовков, команд загрузки, сегментов, секций и прочее. Особо полезно добавлять при вызове ключ -v (verbose).
    • MachOView — распространяется под GPL, имеет GUI, работает только на Mac OS 10.6 и выше. Позволяет просматривать полное содержимое Mach-O, дополняет информацию по некоторым разделам, на основании данных из других частей, что очень удобно.


    По большому счету, чтобы обычному пользователю разобраться с Mach-O, достаточно поиграть с MachOView на различных примерах. Но, этого недостаточно для программирования Mach-O, поскольку неизвестны точные структуры заголовков, команд загрузки, сегментов, секций, таблиц символов и точное описание их полей. Но, это не большая беда, при наличии спецификации. А она всегда доступна на официальном сайте Apple. А при наличии установленных средств разработки, можно заглянуть в заголовочные файлы из /usr/include/mach-o (особенно loader.h).

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

    Структура заголовка проста (привожу для 32-битной архитектуры, 64-битная не сильно отличается):

    struct mach_header
    {
      uint32_t magic;
      cpu_type_t cputype;
      cpu_subtype_t cpusubtype;
      uint32_t filetype;
      uint32_t ncmds;
      uint32_t sizeofcmds;
      uint32_t flags;
    };
    

    Все начитается с магического значения (0xFEEDFACE или наоборот, в зависимости от соглашения относительно порядка байт в машинных словах). Затем указан тип архитектуры процессора, количество и размер команд загрузки и флаги, описывающие прочие особенности.

    Например:


    Существенные команды загрузки перечислены ниже:
    • LC_SEGMENT — содержит различную информацию о некотором сегменте: размер, количество секций, смещение в файле и, после загрузки, в памяти
    • LC_SYMTAB — загружает таблицу символов и строк
    • LC_DYSYMTAB — создает таблицу импорта, данные о символах берутся из таблицы символов
    • LC_LOAD_DYLIB — указывает зависимость от некоторой сторонней библиотеки
    Например (32- и 64-битные версии соответственно):

    Наиболее важные сегменты следующие:
    • __TEXT — исполняемый код и други данные только для чтения
    • __DATA — данные, доступные для записи; в том числе и таблицы импорта, которые имеют свойство изменяться динамическим загрузчиком во время позднего связывания
    • __OBJC — различная информация стандартной библиотеки языка Objective-C времени выполнения
    • __IMPORT — таблица импорта исключительно для 32-битной архитектуры (у меня генерировалась только на Mac OS 10.5)
    • __LINKEDIT — здесь динамический загрузчик располагает свои данные для уже загруженных модулей: таблицы символов, строк и прочее
    Любая команда загрузки начинается такими полями:
    struct load_command
    {
      uint32_t cmd;  //числовой код команды
      uint32_t cmdsize;  //размер текущей команды в байтах
    };
    

    После которых могут идти еще много различных полей, в зависимости от типа команды.

    Например:


    Самые интересные секции в перечисленных сегментах такие:
    • __TEXT,__text — собственно код
    • __TEXT,__cstring — константные строки (в двойных кавычках)
    • __TEXT,__const — различные константы
    • __DATA,__data — инициализированные переменные (строки и массивы)
    • __DATA,__la_symbol_ptr — таблица указателей на импортируемые функции
    • __DATA,__bss — неинициализированные статические переменные
    • __IMPORT,__jump_table — заглушки для вызовов импортируемых функций
    Забегая вперед, отмечу, что в одном Mach-O в качестве таблицы импорта может быть либо __IMPORT,__jump_table (32 бита, Mac OS 10.5), либо __DATA,__la_symbol_ptr (64 бита, либо Mac OS 10.6 и старше).

    Секции в сегментах имеют следующую структуру:
    struct section
    {
      char sectname[16];
      char segname[16];
      uint32_t addr;
      uint32_t size;
      uint32_t offset;
      uint32_t align;
      uint32_t reloff;
      uint32_t nreloc;
      uint32_t flags;
      uint32_t reserved1;
      uint32_t reserved2;
    };
    

    Имеем имя сегмента и самой секции, размер, смещение в файле и адрес в памяти, по которому динамический загрузчик ее разместил. Кроме того, присутствует и другая, специфическая для конкретной секции информация.

    Например:


    Fat binary


    Безусловно, стоит упомянуть, что, в следствии неоднократной плавной смены компанией Apple своих целевых архитектур (Motorola -> IBM -> Intel), исполняемые файлы и библиотеки «научились» хранить сразу несколько вариантов исполняемого кода. В общем случае, такие файлы называют fat binary. По сути, это несколько Mach-O, собранных в одном файле, но заголовок у него особый. Он содержит информацию о количестве и типе поддерживаемых архитектур и смещения к каждой из них. По такому смещению находятся обычные Mach-O со структурой, описанной выше.

    Вот как это выглядит на языке С:
    struct fat_header
    {
      uint32_t magic;
      uint32_t nfat_arch;
    };
    

    Где под magic скрывается 0xCAFEBABE (или наоборот — помним про разный порядок байт в машинных словах на разных процессорах). А после, незамедлительно следует ровно nfat_arch структур типа:
    struct fat_arch
    {
      cpu_type_t cputype;
      cpu_subtype_t cpusubtype;
      uint32_t offset;
      uint32_t size;
      uint32_t align;
    };
    

    Собственно, названия полей говорят сами за себя: тип процессора, смещение в файле конкретного Mach-O, размер и выравнивание.

    Подопытная программа


    Для исследования работы вызова импортируемой функции возьмем следующие файлы на языке С:

    File test.c
    void libtest();  //from libtest.dylib
    
    int main()
    {
        libtest();  //calls puts() from libSystem.B.dylib
    
        return 0;
    }
    


    File libtest.c
    #include <stdio.h>
    
    void libtest()  //just a simple library function
    {
        puts("libtest: calls the original puts()");
    }
    

    Исследуем динамическую компоновку


    Ограничимся процессорами Intel. Пускай у нас Mac OS 10.5. Добавим эти файлы в новый Xcode-проект, скомпилируем (32-битную версию) и запустим в отладочном режиме, остановившись на строчке, где в функции libtest() библиотеки libtest.dylib происходит вызов функции puts(). Вот ассемблерный листинг для libtest():



    Выполним еще одну инструкцию:



    И посмотри на нее в памяти:



    Это и есть та ячейка таблицы импорта (в данном случае — ячейка __IMPORT, __jump_table), которая служит трамплином для вызова динамического загрузчика (функция __dyld_stub_binding_helper_interface), если используется позднее связывание (lazy binding), либо прыгает сразу на целевую функцию. Что подтверждается последующим вызовом puts():



    И в памяти:



    Итак, мы видим, что динамический загрузчик заменил инструкцию косвенного вызова CALL (0xE8) на инструкцию косвенного перехода JMP (0xE9). Стало быть, для перенаправления элементов __jump_table нам достаточно будет прописывать вместо их изначального содержимого инструкцию косвенного перехода на начало функции-подстановки.

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

    Теперь перенесем проект на Mac OS 10.6 и скомпилируем fat binary для 32- и 64-битных архитектур. На всякий случай, в Xcode это можно сделать так:



    Компилируем, запускаем 64-битный вариант (просто для примера; таблица импорта на Snow Leopard будет одинаковая и для 32-бит) и останавливается снова на вызове puts():



    И снова простой CALL. Смотрим дальше:



    Вот тут уже заметно различие с обычным __IMPORT, __jump_table.

    Добро пожаловать в __TEXT, __symbol_stub1. Данная таблица представляет из себя набор инструкций JMP для каждой импортируемой функции. В нашем случае там только одна такая инструкция, представленная выше. Каждая такая инструкция осуществляет переход на адрес, указанный в соответствующей ячейке таблицы __DATA, __la_symbol_ptr. Последняя и является таблицей импорта для этого Mach-O.

    Но, продолжим исследование. Если заглянуть по адресу, на который собирается произойти переход:



    То мы увидим следующее:



    Мы попадаем в секцию __TEXT, __stub_helper. По сути, это PLT (Procedure Linkage Table) для Mach-O. Первой инструкцией (в данном случае — это LEA в связке с R11, а могла быть и простая PUSH) динамический компоновщик запоминает, что за символ требует переразмещения, вторая инструкция всегда ведет на один и тот же адрес — начало функции __dyld_stub_binding_helper, которая и займется связыванием:



    После того, как динамический компоновщик выполнит переразмещения для puts(), соответствующая ячейка в __DATA, __la_symbol_ptr будет иметь вид:



    А это уже и есть адрес функции puts() из модуля libSystem.B.dylib. То есть, подменив его каким-то своим адресом, мы получим требуемый эффект перенаправления вызова.

    Итак. На данном этапе мы на конкретном примере выяснили, как происходит динамическое связывание, какие бывают таблицы импорта в Mach-O и из каких элементов они состоят. Теперь приступим к разбору Mach-O!

    Поиск элемента в таблице импорта


    Нужно по имени символа найти соответствующую ему ячейку в таблице импорта. Алгоритм этого действия несколько нетривиален.

    Во-первых, нужно найти сам символ в таблице символов. Последняя представляет из себя массив следующих структур:
    struct nlist
    {
      union
      {
         int32_t n_strx;
      } n_un;
      uint8_t n_type;
      uint8_t n_sect;
      int16_t n_desc;
      uint32_t n_value;
    };
    

    Где n_un.n_strx — смещение в байтах от начала таблицы строк имени этого символа. Остальное касается типа символа, секции, в котором он находится и так далее. Словом, вот ее несколько последних элементов для нашей подопытной библиотеки libtest.dylib (32-битная версия):



    Таблица строк — это цепочка имен, каждое из которых завершается нулем. Однако, стоит обратить внимание, что к каждому имени компилятор добавляет в начало нижнее подчеркивание "_", поэтому, например имя «puts» будет выглядеть в таблице строк как "_puts".

    Вот пример:


    Узнать место нахождения таблицы символов и строк можно из соответствующей команды загрузки (LC_SYMTAB):



    Однако, таблица символов неоднородна. В ней существует несколько разделов. Один из них нам особо интересен — это неопределенные (undefined) символы, то есть те, которые компонуются динамически. Кстати, MachOView подсвечивает таковые синеватым фоном. Для того, чтобы определить какая часть таблицы символов отражает подмножество неопределенных символов, нужно заглянуть в команду загрузки динамических символов (LC_DYSYMTAB):



    Вот ее представление на языке С:
    struct dysymtab_command
    {
        uint32_t cmd;
        uint32_t cmdsize;
        uint32_t ilocalsym;
        uint32_t nlocalsym;
        uint32_t iextdefsym;
        uint32_t nextdefsym;
        uint32_t iundefsym;
        uint32_t nundefsym;
        uint32_t tocoff;
        uint32_t ntoc;
        uint32_t modtaboff;
        uint32_t nmodtab;
        uint32_t extrefsymoff;
        uint32_t nextrefsyms;
        uint32_t indirectsymoff;
        uint32_t nindirectsyms;
        uint32_t extreloff;
        uint32_t nextrel;
        uint32_t locreloff;
        uint32_t nlocrel;
    };
    
    Здесь dysymtab_command.iundefsym — это индекс в таблице символов, с которого начинается подмножество неопределенных символов. dysymtab_command.nundefsym — количество неопределенных символов. Поскольку то, что мы ищем, является заведомо неопределенным символом, то и искать его в таблице символов нужно только в этом подмножестве.

    А теперь, очень важный момент: найдя символ по его имени, самое главное для нас — запомнить его индекс в таблице символов от ее начала. Поскольку из числовых значений этих индексов состоит другая важная таблица — таблица косвенных (indirect) символов. Найти ее можно по значению dysymtab_command.indirectsymoff, а количество индексов определяет dysymtab_command.nindirectsyms.

    В нашем тривиальном случае эта таблица состоит всего из одного элемента (в реальной жизни их намного больше):



    И в конце концов, давайте посмотрим на секцию __IMPORT, __jump_table, некоторый элемент которой и нужно отыскать в конечном итоге. Она выглядит вот так:



    Поле section.reserved1 для этой секции имеет очень важное значение (MachOView назвал его Indirect Sym Index). Оно означает индекс в таблице косвенных символов, с которого начинается взаимно однозначное соответствие с элементами __jump_table. А мы помним, что элементы в таблице косвенных символов представляют собой индексы в таблице символов. Улавливаете, к чему я клоню?

    Но, перед тем, как окончательно собрать все осколки знаний воедино, для полноты картины бегло посмотрим на ситуацию в Snow Leopard, где роль таблицы импорта играет __DATA, __la_symbol_ptr. На самом деле, различия не особо ощутимы.

    Вот команда загрузки символов:



    А вот и ее последние элементы:



    На синеватом фоне видны два неопределенных символа, что соответствует данным из команды загрузки динамических символов (LC_DYSYMTAB):



    Да и в таблице косвенных символов уже не один элемент, а четыре:



    Однако, если посмотреть на поле reserved1 заветной секции __la_symbol_ptr, можно обнаружить, что взаимно однозначное отражение ее элементов на таблицу косвенных символов начитается не с начала последней, а с четвертого элемента (индекс равен 3):



    Само же содержимое таблицы импорта, что описывает секция __la_symbol_ptr, будет такое:



    Узнав обо всех этих тонкостях Mach-O, можно сформулировать алгоритм поиска нужного элемента в таблице импорта.

    Алгоритм перенаправления


    Опишем все действия словами, так как код, несмотря на обилие комментариев, может оказать не столь понятным:
    1. Отыскиваем таблицу символов и строк по данным из команды загрузки LC_SYMTAB.
    2. Узнаем из команды загрузки LC_DYSYMTAB с какого элемента таблицы символов начинается подмножество неопределенных символов (поле iundefsym).
    3. Ищем целевой символ по имени среди подмножества неопределенных символов в таблице символов.
    4. Запоминаем индекс целевого символа от начала таблицы символов.
    5. Отыскиваем таблицу косвенных символов по данным из команды загрузки LC_DYSYMTAB (поле indirectsymoff).
    6. Узнаем индекс, с которого начинается отображение таблицы импорта (содержимого секции __DATA, __la_symbol_ptr (либо __IMPORT, __jump_table — будет что-то одно)) на таблицу косвенных символов (поле reserved1).
    7. Начиная с этого индекса просматриваем таблицу косвенных символов и ищем в ней значение, соответствующее индексу целевого символа в таблице символов.
    8. Запоминаем, каким по счету с начала отображения таблицы импорта на таблицу косвенных символов попался целевой символ. Сохраненное значение — это и есть индекс нужного элемента в таблице импорта.
    9. По данным из секции __la_symbol_ptr (либо __jump_table) находим таблицу импорта (поле offset).
    10. Имея индекс целевого элемента в ней, переписываем адрес (для __la_symbol_ptr) на необходимое нам значение (либо меняем инструкцию CALL/JMP на JMP с операндом — адресом необходимой нам функции (для __jump_table)).
    Замечу, что работать с таблицами символов, строк и косвенных символов необходимо только, загрузив их из файла. А читать содержимое секций, описывающих таблицы импорта, и, естественно, производить само перенаправление, уже в памяти. Это связано с тем, что таблицы символов и строк могут отсутствовать или не отображать действительное положение вещей в целевом Mach-O. Ведь до нас там поработал динамический загрузчик и благополучно сохранил себе все необходимые данные о символах, не размещая сами таблицы.

    Реализация перенаправления


    Настало время превратить изложенные мысли в код. Для оптимизации поиска нужных элементов Mach-O при каждом перенаправлении, разобъем всю операцию на три этапа:
    1. void *mach_hook_init(char const *library_filename, void const *library_address);
      На основании самого файла Mach-O и его отображения в памяти, эта функция возвращает некий непрозрачный описатель, за которым скрывается смещения к таблице импорта, таблица символов, строк и отображение косвенных (indirect) символов из таблицы динамических символов, а также ряд полезных индексов для этого модуля. Вот этот описатель:
      struct mach_hook_handle
      {
          void const *library_address;  //base address of a library in memory
          char const *string_table;  //buffer to read string_table table from file
          struct nlist const *symbol_table;  //buffer to read symbol table from file
          uint32_t const *indirect_table;  //buffer to read the indirect symbol table in dynamic symbol table from file
          uint32_t undefined_symbols_count;  //number of undefined symbols in the symbol table
          uint32_t undefined_symbols_index;  //position of undefined symbols in the symbol table
          uint32_t indirect_symbols_count;  //number of indirect symbols in the indirect symbol table of DYSYMTAB
          uint32_t indirect_symbols_index;  //index of the first imported symbol in the indirect symbol table of DYSYMTAB
          uint32_t import_table_offset;  //the offset of (__DATA, __la_symbol_ptr) or (__IMPORT, __jump_table)
          uint32_t jump_table_present;  //special flag to show if we work with (__IMPORT, __jump_table)
      };
      
    2. mach_substitution mach_hook(void const *handle, char const *function_name, mach_substitution substitution);
      Эта функция, по имеющемуся описателю библиотеки, имени целевого символа и адреса перехватчика осуществляет само перенаправление по описанному выше алгоритму.
    3. void mach_hook_free(void *handle);
      Так осуществляется очистка любого описателя, который вернула mach_hook_init().

    С учетом этих прототипов тестовую программку придется переписать:
    #include <stdio.h>
    #include <dlfcn.h>
    
    #include "mach_hook.h"
    
    #define LIBTEST_PATH "libtest.dylib"
    
    void libtest();  //from libtest.dylib
    
    int hooked_puts(char const *s)
    {
        puts(s);  //calls the original puts() from libSystem.B.dylib, because our main executable module called "test" remains intact
    
        return puts("HOOKED!");
    }
    
    int main()
    {
        void *handle = 0;  //handle to store hook-related info
        mach_substitution original;  //original data for restoration
        Dl_info info;
    
        if (!dladdr((void const *)libtest, &info))  //gets an address of a library which contains libtest() function
        {
            fprintf(stderr, "Failed to get the base address of a library!\n", LIBTEST_PATH);
    
            goto end;
        }
    
        handle = mach_hook_init(LIBTEST_PATH, info.dli_fbase);
    
        if (!handle)
        {
            fprintf(stderr, "Redirection init failed!\n");
    
            goto end;
        }
    
        libtest();  //calls puts() from libSystem.B.dylib
    
        puts("-----------------------------");
    
        original = mach_hook(handle, "puts", (mach_substitution)hooked_puts);
    
        if (!original)
        {
            fprintf(stderr, "Redirection failed!\n");
    
            goto end;
        }
    
        libtest();  //calls hooked_puts()
    
        puts("-----------------------------");
    
        original = mach_hook(handle, "puts", original);  //restores the original relocation
    
        if (!original)
        {
            fprintf(stderr, "Restoration failed!\n");
    
            goto end;
        }
    
        libtest();  //again calls puts() from libSystem.B.dylib
    
    end:
    
        mach_hook_free(handle);
        handle = 0;  //no effect here, but just a good advice to prevent double freeing
    
        return 0;
    }
    

    Полная реализация тестового примера вместе с алгоритмом перенаправления и файлом проекта доступна для скачивания.

    Тестовый запуск


    и опробовать примерно так:
    user@mac$ arch -i386 ./test 
    libtest: calls the original puts()
    -----------------------------
    libtest: calls the original puts()
    HOOKED!
    -----------------------------
    libtest: calls the original puts()
    

    user@mac$ arch -x86_64 ./test 
    libtest: calls the original puts()
    -----------------------------
    libtest: calls the original puts()
    HOOKED!
    -----------------------------
    libtest: calls the original puts()
    

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

    Полезные ссылки

    Удачи!
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 14

      +2
      Это очень хорошая и подробная статья.

      (хотя в случае, когда загружаемый плагин захочет перехватить какую-то функцию основной программы, все равно придется возвращаться к mach_override и компании — ну или использовать Obj-C method swizzling)
        +3
        Сказ о SIMBL-подобном внедрении и swizzling'е методов уже на стадии подготовки :)
          0
          Спасибо за статью, было интересно почитать! Как раз смотрю в сторону рантайма Obj-C для написания настраиваемого логгера функций.
      +6
      Замечательная статья.

      Добавлю еще до кучи пример с __interpose секцией из cегмента __DATA (впрочем можно и без отдельной секции обойтись, главное что DYLD_INSERT_LIBRARIES может обеспечить нам загрузку раньше остальных библиотек и поэтому можно перекрыть чужие экспорты, при этом остается возможность работы из замененной функции с оригинальной при самостоятельной подзагрузки библиотеки):

      testlib.c
      #include <stdio.h>
      
      void libtest()
      {
      	puts("hello world");
      }


      test.c
      extern void libtest(void);
      
      int main (int argc, const char * argv[])
      {
      	libtest();
      	return 0;
      }
      


      libwithhook.c
      #include <stdio.h>
      
      typedef struct interpose_s {
      	void *new_func;
      	void *old_func;
      } interpose_t;
      
      int new_puts(const char *);
      
      static const interpose_t interposers[] \
      	__attribute__ ((section("__DATA, __interpose"))) = {
      		{ (void *)new_puts, (void *)puts },
      	};
      
      int new_puts(const char *str)
      {
      	return puts("hello from hook!");
      }

        –3
        Интересно, зачем вам такие извращения? надо патчить какую-то кривую проприетарную программу с закрытым кодом? ну-ну, пишите дальше костыли, которые могут сломаться в любом новом релизе.
          +1
          К примеру, если какая-то библиотека желает выйти в сеть, а допустить этого, согласно логике нашей программы, никак нельзя, то придется перехватить сетевые вызовы (обычно сокеты) того модуля и направлять их в иное русло (на другой компьютер и т.д.).
            0
            А как работают, Вы думали, вещи вроде 1Password, TotalFinder и MailTags?
            0
            Быть может не в тему напишу, но так хочется запускать программы для IPhone через valgrind. В инете даже есть что-то вроде инструкции (http://landonf.bikemonkey.org/code/iphone/iPhone_Simulator_Valgrind.20081224.html), но, к сожалению, оно не работает из-за символов типа ...$UNIX2003. Может быть у местных гуру будут какие-нибудь советы на этот счёт? =)
            0
            замечательная статья, но непонятен вот какой вопрос, каким образом перехватить скажем тот же puts у какого-то уже запущенного приложения, скажем сафари
            с DYLD_INSERT_LIBRARIES все понятно, главное перед загрузкой сделать setenv

            Only users with full accounts can post comments. Log in, please.