4. Написание собственных модулей

Начиная с YARA версии 3.0 вы можете расширить ее возможности. Это возможно с помощью модулей, которые вы можете использовать для определения структур данных и функций, которые впоследствии можно будет применить в ваших правилах. Вы можете увидеть некоторые примеры того, что может делать модуль, в Главе 3 Модули.

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

4.1. Модуль “Hello World!”

Модули пишутся на языке программирования C и встраиваются в YARA в процессе компиляции. Чтобы создавать свои собственные модули, вы должны быть знакомы с языком программирования C, а также с тем, как конфигурировать и собирать YARA из исходного кода. Вам не нужно понимать, как YARA реализует свои функции; YARA предоставляет простой API для модулей, и это все, что вам нужно знать.

Исходный код модуля должен находиться в каталоге libyara/modules с исходными кодами. Рекомендуется использовать имя модуля в качестве имени файла для исходного файла, если имя вашего модуля foo, его исходный файл должен быть foo.с.

В каталоге libyara/modules вы найдете файл demo.c, который мы будем использовать в качестве отправной точки. Файл выглядит следующим образом:

#include <yara/modules.h>
#define MODULE_NAME demo
begin_declarations;

        declare_string("greeting");

end_declarations;

int module_initialize(YR_MODULE* module)
{
        return ERROR_SUCCESS;
}

int module_finalize(YR_MODULE* module)
{
        return ERROR_SUCCESS;

int module_load(YR_SCAN_CONTEXT* context, YR_OBJECT* module_object, void* module_data, size_t module_data_size)
{
        set_string("Hello World!", module_object, "greeting");
        return ERROR_SUCCESS;
}
int module_unload(YR_OBJECT* module_object)
{
        return ERROR_SUCCESS;
}
#undef MODULE_NAME

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

#include <yara/modules.h>

В заголовочном файле modules.h находятся определения API для модулей YARA, поэтому эта директива include необходима во всех ваших модулях. Вторая строка:

#define MODULE_NAME demo

Это, определение имени вашего модуля. Для каждого модуля необходимо определить свое имя в начале исходного кода. Имена модулей должны быть уникальными среди модулей, встроенных в YARA.

Затем следует раздел объявлений функций и данных:

begin_declarations;

        declare_string("greeting");

end_declarations;

Здесь модуль объявляет функции и структуры данных, которые будут доступны для ваших правил YARA. В этом случае мы объявляем только строковую переменную с именем greeting. Более подробно мы обсудим эти вопросы в разделе 4.2.

После раздела объявлений, показанного выше, идет пара функций:

int module_initialize(YR_MODULE* module)
{
        return ERROR_SUCCESS;
}

int module_finalize(YR_MODULE* module)
{
        return ERROR_SUCCESS;
}

Функция module_initialize вызывается во время инициализации YARA, в то время как функция module_finalize вызывается при завершении YARA. Эти функции позволяют инициализировать и завершить любую глобальную структуру данных, использование которой требуется для работы модуля.

Затем идет функция module_load:

int module_load(
        YR_SCAN_CONTEXT* context,
        YR_OBJECT* module_object,
        void* module_data,
        size_t module_data_size)
{
        set_string("Hello World!", module_object, "greeting");
        return ERROR_SUCCESS;
}

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

В этом примере функция module_load вообще не проверяет содержимое файла, она просто присваивает строку “Hello World!”к переменной greeting, объявленной ранее.

И, наконец, у нас есть функция module_unload:

int module_unload(YR_OBJECT* module_object)
{
        return ERROR_SUCCESS;
}

Для каждого вызова module_load существует соответствующий вызов module_unload. Эта функция позволяет модулю освободить любой ресурс, выделенный во время module_load. В нашем случае ничего не нужно освобождать, поэтому функция просто возвращает ERROR_SUCCESS. И module_load и module_unload должны возвращать ERROR_SUCCESS, чтобы указать, что все прошло нормально. Если возвращается другое значение, сканирование будет прервано и пользователю будет сообщено об ошибке.

4.1.1. Сборка нашего “Hello World!”

Для того, чтобы встроить модули в YARA, необходимо поместить их исходный код в каталог libyara/modules, и выполнить два дальнейших шага, чтобы заставить их работать. Первым шагом является добавление модуля в файл module_list, который также находится в каталоге libyara/modules.

Файл module_list выглядит следующим образом:

MODULE(tests)
MODULE(pe)

#ifdef CUCKOO_MODULE
MODULE(cuckoo)
#endif

Второй шаг-изменение файла Makefile.am чтобы сообщить программе make, что исходный код вашего модуля должен быть скомпилирован и связан с YARA. В самом начале файла libyara/Makefile.ам вы найдете следующее:

MODULES = modules/tests.c
MODULES += modules/pe.c

if CUCKOO_MODULE
MODULES += modules/cuckoo.c
endif

Просто добавьте новую строку для вашего модуля:

И это все! Теперь вы готовы построить YARA с вашим новым модулем. Просто перейдите в корневой каталог с исходниками и введите:

make
sudo make install

Теперь вы можете создать такое правило:

import "demo"
rule HelloWorld
{
        condition:
                demo.greeting == "Hello World!"
}

Любой файл, отсканированный с помощью этого правила, будет ему соответствовать, поскольку условие demo.greeting == "Hello World!" всегда true.

4.2. Раздел объявлений

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

begin_declarations;

        <your declarations here>

end_declarations;

4.2.1. Основные типы

В разделе объявлений можно использовать declare_string(<имя переменной>), declare_integer(<имя переменной>) и declare_float(<имя переменной>) для объявления строковых, целочисленных переменных или переменных с плавающей запятой соответственно. Например:

begin_declarations;

        declare_integer("foo");
        declare_string("bar");
        declare_float("baz");

end_declarations;

Note

Переменные с плавающей запятой требуют YARA версии 3.3.0 или более поздней.

Имена переменных могут содержать такие символы как; буквы, цифры и символы подчеркивания. Эти переменные могут быть использованы позже в ваших правилах в любом месте, где ожидается число или строка. Предположим, что имя вашего модуля mymodule, тогда переменные могут быть использованы следующим образом:

mymodule.foo > 5

mymodule.bar matches /someregexp/

4.2.2. Структуры

Ваши объявления могут быть организованы более структурированным образом:

begin_declarations;

        declare_integer("foo");
        declare_string("bar");
        declare_float("baz");

        begin_struct("some_structure");

                declare_integer("foo");

                begin_struct("nested_structure");

                        declare_integer("bar");

                end_struct("nested_structure");

        end_struct("some_structure");

        begin_struct("another_structure");

                declare_integer("foo");
                declare_string("bar");
                declare_string("baz");
                declare_float("tux");

        end_struct("another_structure");

end_declarations;

В этом примере мы используем begin_struct(<имя структуры>) и end_struct (<имя структуры>) для разграничения двух структур some_structure и another_structure. В разделители структуры можно поместить любые другие объявления, включая другое объявление структуры. Также обратите внимание, что члены разных структур могут иметь одно и то же имя, но члены одной структуры должны иметь уникальные имена.

Обращение к этим переменным из ваших правил будет выглядеть следующим образом:

mymodule.foo
mymodule.some_structure.foo
mymodule.some_structure.nested_structure.bar
mymodule.another_structure.baz

4.2.3. Массивы

Точно так же, как вы объявляете отдельные строки, целые числа, числа с плавающей запятой или структуры, вы можете объявлять массивы из них:

begin_declarations;

        declare_integer_array("foo");
        declare_string_array("bar");
        declare_float_array("baz");

        begin_struct_array("struct_array");

                declare_integer("foo");
                declare_string("bar");

        end_struct_array("struct_array");

end_declarations;

К отдельным значениям в массиве обращаются, как и в большинстве языков программирования:

foo[0]
bar[1]
baz[3]
struct_array[4].foo
struct_array[1].bar

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

4.2.4. Словари

Добавлено в версии 3.2.0.

Вы также можете объявить словари целых чисел, чисел с плавающей запятой, строк или структур:

begin_declarations;

        declare_integer_dictionary("foo");
        declare_string_dictionary("bar");
        declare_float_dictionary("baz")

        begin_struct_dictionary("struct_dict");

                declare_integer("foo");
                declare_string("bar");

        end_struct_dictionary("struct_dict");

end_declarations;

Отдельные значения в словаре доступны с помощью строкового ключа:

foo["somekey"]
bar["anotherkey"]
baz["yetanotherkey"]
struct_dict["k1"].foo
struct_dict["k1"].bar

4.2.5. Функции

Одной из наиболее мощных возможностей модулей YARA является возможность объявления функций, которые впоследствии могут быть вызваны из ваших правил. Функции должны появляться в разделе объявлений следующим образом:

declare_function(<function name>, <argument types>, <return tuype>, <C function>);

<function name> - это имя, которое будет использоваться в ваших правилах YARA для вызова функции.

<argument types> - это строка, содержащая один символ на аргумент функции, где символ указывает тип аргумента. Функции могут принимать четыре различных типа аргументов: строка, целое число, число с плавающей точкой и регулярное выражение, обозначаемые символами: s, i, f и r соответственно. Если ваша функция в качестве аргумента получает два целых числа, <argument types> должен быть "ii", если она получает целое число в качестве первого аргумента и строку в качестве второго, то <argument types> должен быть "is", если она получает три строки и число с плавающей запятой <argument types> должен быть "sssf".

<return tuype> - это строка с одним символом, обозначающим тип возвращаемого значения. Возможные типы возвращаемых значений: строка "s", целое число "i" и число с плавающей запятой "f".

<C function> - идентификатор для фактической реализации вашей функции.

Ниже приведен полный пример:

    define_function(isum)
    {
            int64_t a = integer_argument(1);
            int64_t b = integer_argument(2);

            return_integer(a + b);
    }

    define_function(fsum)
    {
            double a = float_argument(1);
            double b = float_argument(2);

            return_integer(a + b);
    }

    begin_declarations;

            declare_function("sum", "ii", "i", sum);

end_declarations;

Как вы можете видеть в приведенном выше примере, ваш код функции должен быть определен перед разделом объявлений, например:

define_function(<function identifier>)
{
    //..ваш код
}

Функции могут быть перегружены, как в C++ и других языках программирования. Вы можете объявить две функции с одинаковыми именами, если они различаются по типу или количеству аргументов. Один пример перегруженных функций можно найти в модуле Hash, он имеет две функции для вычисления MD5-хэшей, одна получает в качестве аргументов смещение и длину в файле, а другая получает строку:

begin_declarations;

        declare_function("md5", "ii", "s", data_md5);
        declare_function("md5", "s", "s", string_md5);

end_declarations;

Подробнее обсудим реализацию функций в разделе 4.5 Подробнее о функциях.

4.3. Инициализация и завершение

Каждый модуль должен реализовать две функции для инициализации и завершения: module_initialize и module_finalize. Первый вызывается во время инициализации YARA через функцию yr_initialize () (см. п. 7.6.2), а второй-во время завершения через функцию yr_finalize() (см. п. 7.6.2). Обе функции вызываются независимо от того, импортируется ли модуль каким-либо правилом.

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

int module_initialize(YR_MODULE* module)
{
        return ERROR_SUCCESS;
}

int module_finalize(YR_MODULE* module)
{
        return ERROR_SUCCESS;
}

Любое возвращаемое значение, отличное от ERROR_SUCCESS, прервет выполнение YARA.

4.4. Реализации логики работы модуля

Кроме module_initialize и module_finalize каждый модуль должен реализовывать еще две функции, которые вызываются YARA при сканировании файла или пространства памяти процесса: module_load и module_unload. Обе функции вызываются один раз для каждого сканируемого файла или процесса, но только если модуль был импортирован с помощью директивы import. Если модуль не импортируется в какое-либо правило, то module_load или module_unload вызываться не будут.

Функция module_load имеет следующий прототип:

int module_load(
        YR_SCAN_CONTEXT* context,
        YR_OBJECT* module_object,
        void* module_data,
        size_t module_data_size)

Аргумент context содержит информацию относительно текущего сканирования, включая сканируемые данные. Аргумент module_object является указателем на структуру YR_OBJECT, связанную с модулем. Каждая структура, переменная или функция, объявленная в модуле YARA, представлена структурой YR_OBJECT. Эти структуры образуют дерево, корнем которого является структура модуля YR_OBJECT. Например, если у вас есть следующие объявления в модуле с именем mymodule:

begin_declarations;

        declare_integer("foo");

                begin_struct("bar");

                        declare_string("baz");

                end_struct("bar");

end_declarations;

Тогда дерево будет выглядеть так:

YR_OBJECT(type=OBJECT_TYPE_STRUCT, name="mymodule")
!
!_ YR_OBJECT(type=OBJECT_TYPE_INTEGER, name="foo")
!
!_ YR_OBJECT(type=OBJECT_TYPE_STRUCT, name="bar")
        !
        !_ YR_OBJECT(type=OBJECT_TYPE_STRING, name="baz")

Обратите внимание, что и bar, и mymodule имеют одинаковый тип OBJECT_TYPE_STRUCT, что означает, что YR_OBJECT, связанный с модулем, является просто еще одной структурой, подобной bar. Фактически, когда вы пишете в своих правилах что-то вроде mymodule.foo, вы выполняете поиск полей в структуре так же, как это делает bar.baz.

Таким образом, аргумент module_object позволяет вам получить доступ к каждой переменной, структуре или функции, объявленной модулем, предоставив указатель на корень дерева объектов.

Аргумент module_data - это указатель на любые дополнительные данные, передаваемые модулю, а module_data_size - это размер этих данных. Не все модули требуют дополнительных данных, большинство из них полагаются только на данные, которые сканируются, но некоторые из них требуют дополнительной информации в качестве входных данных. Модуль Cuckoo является хорошим примером этого, он получает отчет о поведении, связанный с проверяемыми PE-файлами, который передается в аргументах module_data и module_data_size.

Для получения дополнительной информации о том, как передать дополнительные данные в ваш модуль, посмотрите на применение опции -x в Главе 5.

4.4.1. Доступ к сканируемым данным

Большинству модулей YARA необходим доступ к сканируемому файлу или памяти процесса, чтобы извлечь из него информацию. Сканируемые данные отправляются в модуль в структуре YR_SCAN_CONTEXT, передаваемой в функцию mdule_load. Данные иногда разбиваются на блоки, поэтому вашему модулю необходимо выполнять итерации по блокам с помощью макроса foreach_memory_block:

int module_load(
        R_SCAN_CONTEXT* context,
        YR_OBJECT* module_object,
        void* module_data,
        size_t module_data_size)
{
        YR_MEMORY_BLOCK* block;
        foreach_memory_block(context, block)
        {
                //..делаем какие-либо операции с текущим блоком памяти
        }
}

Каждый блок памяти представлен структурой YR_MEMORY_BLOCK со следующими атрибутами:

  • YR_MEMORY_BLOCK_FETCH_DATA_FUNC fetch_data

Указатель на функцию, возвращающую указатель на блок данных.

  • size_t size

Размер блока данных.

  • size_t base

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

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

При сканировании пространства памяти процесса ваш модуль определенно получит большое количество блоков, по одному для каждой выделенной области памяти в адресном пространстве процесса.

Однако в некоторых случаях перебирать блоки не требуется. Если ваш модуль просто анализирует заголовок какого-либо формата файла, вы можете смело предполагать, что весь заголовок содержится в первом блоке (тем не менее, добавьте некоторые проверки в ваш код). В этих случаях вы можете использовать макрос first_memory_block:

int module_load(
        YR_SCAN_CONTEXT* context,
        YR_OBJECT* module_object,
        void* module_data,
        size_t module_data_size)
{
        YR_MEMORY_BLOCK* block;
        const uint8_t* block_data;

        block = first_memory_block(context);
        block_data = block->fetch_data(block)

        if (block_data != NULL)
        {
                //..делаем какие-либо операции с текущим блоком памяти
        }
}

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

const uint8_t* block_data;

foreach_memory_block(context, block)
{
        block_data = block->fetch_data(block);

        if (block_data != NULL)
        {
                // использование block_data здесь безопасно.
        }
}
// память, на которую указывает block_data, здесь уже может быть освобождена.

4.4.2. Присваивание значений переменным

Функция module_load позволяет назначать значения переменным, объявленным в разделе объявлений, после того, как вы пропарсили или проанализировали сканируемые данные и/или данные любого дополнительного модуля. Это делается с помощью функций set_integer и set_string:

void set_integer (int64_t value, YR_OBJECT* object, const char* field, …)

void set_string (const char* value, YR_OBJECT* object, const char* field, …)

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

Если мы присваиваем значение переменной, представленной самим объектом, то дескриптор поля должен быть NULL.

Например, предполагая, что объект указывает на структуру YR_OBJECT, соответствующую некоторой целочисленной переменной, мы можем установить значение для этой целочисленной переменной с помощью:

set_integer(<value>, object, NULL);

Дескриптор поля используется, когда вы хотите присвоить значение некоторому потомку объекта. Например, рассмотрим следующие объявления:

begin_declarations;

        begin_struct("foo");

                declare_string("bar");

                begin_struct("baz");

                        declare_integer("qux");

                end_struct("baz");

        end_struct("foo");

end_declarations;

Если объект указывает на YR_OBJECT, связанный со структурой foo, вы можете установить значение для строки bar следующим образом:

set_string(<value>, object, "bar");

И значение для qux таким образом:

set_integer(<value>, object, "baz.qux");

Вы помните, что аргумент module_object для module_load был указателем на YR_OBJECT? Вы помните, что этот YR_OBJECT является структурой, как и bar? Исходя из этого, вы также можете установить значения для bar и qux следующим образом:

set_string(<value>, module_object, "foo.bar");
set_integer(<value>, module_object, "foo.baz.qux");

Но что происходит с массивами? Каким образом можно установить значения для элементов массива? Если у вас есть следующее объявление:

begin_declarations;

        declare_integer_array("foo");

        begin_struct_array("bar")

                declare_string("baz");
                declare_integer_array("qux");

        end_struct_array("bar");

end_declarations;

Тогда следующие представления операторов set_integer и set_struing являются валидными:

set_integer(<value>, module, "foo[0]");
set_integer(<value>, module, "foo[%i]", 2);
set_string(<value>, module, "bar[%i].baz", 5);
set_string(<value>, module, "bar[0].qux[0]");
set_string(<value>, module, "bar[0].qux[%i]", 0);
set_string(<value>, module, "bar[%i].qux[%i]", 100, 200);

Спецификатор формата %i в дескрипторе поля заменяются дополнительными целочисленными аргументами, передаваемыми функции. Это работает так же, как printf в программах на C, но единственными допустимыми спецификаторами формата являются %i и %s для целочисленных и строковых аргументов соответственно.

Спецификатор формата %s используется для назначения значений определенному ключу в словаре:

set_integer(<value>, module, "foo[\"key\"]");
set_integer(<value>, module, "foo[%s]", "key");
set_string(<value>, module, "bar[%s].baz", "another_key");

Если явно не присвоить значение объявленной переменной, массиву или элементу справочника, то они останутся в неопределенном состоянии. Это не проблема, и даже полезно во многих случаях. Например, если модуль предназначен для анализа файлов определенного формата, а получает для анализа файлы другого формата, можно оставить все переменные неопределенными, а не присваивать им фиктивные значения, которые не имеют смысла. YARA будет обрабатывать неопределенные значения в условиях правила, как описано в Главе 3 Модули.

В дополнение к функциям set_integer и set_string у вас есть их аналоги get_integer и get_string. Как следует из их имен, они используются для получения значения переменной, что может быть полезно при реализации ваших функций для получения значений, ранее сохраненных в module_load.

int64_t get_integer (YR_OBJECT* object, const char* field, …)

char* get_string (YR_OBJECT* object, const char* field, …)

Также есть функция для получения любого YR_OBJECT в дереве объектов:

YR_OBJECT* get_object (YR_OBJECT* object, const char* field, …)

Теперь небольшой экзамен…

Эквивалентны ли следующие две строки? Почему?

set_integer(1, get_object(module_object, "foo.bar"), NULL);
set_integer(1, module_object, "foo.bar");

4.4.3. Сохранение данных для дальнейшего использования

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

Хранение информации важно, когда ваш модуль экспортирует функции для использования в правилах YARA. Реализация этих функций обычно требует доступа к информации, генерируемой module_load, которая должна где-то храниться. У вас может возникнуть желание определить глобальные переменные для хранения необходимой информации, но это сделает ваш код не поточно-ориентированным. Правильный подход заключается в использовании поля данных структур YR_OBJECT.

Каждый YR_OBJECT имеет поле void* data, которое может быть безопасно использовано вашим кодом для хранения указателя на любые данные, которые вам могут понадобиться. Типичный шаблон использует поле data YR_OBJECT модуля, как в следующем примере:

typedef struct _MY_DATA
{
        int some_integer;

} MY_DATA;

int module_load(
        YR_SCAN_CONTEXT* context,
        YR_OBJECT* module_object,
        void* module_data,
        size_t module_data_size)
{
        module->data = yr_malloc(sizeof(MY_DATA));
        ((MY_DATA*) module_object->data)->some_integer = 0;

        return ERROR_SUCCESS;
}

Не забудьте освободить выделенную память в функции module_unload:

int module_unload(YR_OBJECT* module_object)
{
        yr_free(module_object->data);

        eturn ERROR_SUCCESS;
}

Note

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

4.5. Подробнее о функциях

Мы уже показали, как объявить функцию в разделе объявлений (см. п. 4.2.5). Здесь мы собираемся показать, как обеспечить их реализацию.

4.5.1. Аргументы функций

В коде функции вы получаете ее аргументы с помощью integer_argument(n), float_argument(n), regexp_argument(n), string_argument(n) или sized_string_argument(n) в зависимости от типа аргумента, где n - номер аргумента начиная с 1.

string_argument(n) может использоваться, когда ваша функция ожидает получить C-строку с нулевым завершением, если ваша функция может получать произвольные двоичные данные, возможно содержащие нулевые байты, вы должны использовать sized_string_argument(n).

Вот несколько примеров:

int64_t arg_1 = integer_argument(1);
RE* arg_2 = regexp_argument(2);
char* arg_3 = string_argument(3);
SIZED_STRING* arg_4 = sized_string_argument(4);
double arg_5 = float_argument(1);

Тип C для целочисленных аргументов - int64_t, для аргументов с плавающей запятой - double, для регулярных выражений - RE*, для NULL-завершенных строк - char*, а для строк, возможно содержащих NULL-символы, - SIZED_STRING*. Структуры SIZED_STRING имеют следующие атрибуты:

SIZED_STRING

  • length - Длина строки.
  • c_string - char* указатель на содержимое строки.

4.5.2. Возвращаемые значения

Функции могут возвращать три типа значений: строки, целые числа и числа с плавающей точкой. Вместо использования оператора возврата, используемого в языке программирования C вы должны использовать return_string (x), return_integer (x) или return_float (x) для возврата из функции, в зависимости от типа возвращаемого значения функции. Во всех случаях x является константой, переменной или выражением, оцениваемым как char*, int64_t или double соответственно.

Вы можете использовать return_string (UNDEFINED), return_float (UNDEFINED) и return_integer (UNDEFINED) для возврата неопределенных значений из функции. Это полезно во многих ситуациях, например, если аргументы, переданные функциям, не имеют смысла, или если ваш модуль ожидает определенный формат файла, а сканируемый файл - другого формата, или в любом другом случае, когда ваша функция не может возвратить верное значение.

Note

Не используйте оператор возврата C для возврата из функции. Возвращаемое значение будет интерпретировано как код ошибки.

4.5.3. Доступ к объектам

При написании функции нам иногда требуется доступ к значениям, ранее назначенным переменным модуля, или дополнительным данным, хранящимся в поле data структур YR_OBJECT, как обсуждалось ранее в п. 4.4.3, для последующего использования. Но для этого нам нужен способ, позволяющий получить доступ к соответствующей структуре YR_OBJECT. Для этого есть две функции: module () и parent (). Функция module () возвращает указатель на YR_OBJECT верхнего уровня, соответствующий модулю, который передается в функцию module_load. Функция parent () возвращает указатель на YR_OBJECT, соответствующий структуре, в которой содержится функция. Например, рассмотрим следующий фрагмент кода:

define_function(f1)
{
        YR_OBJECT* module = module();
        YR_OBJECT* parent = parent();

        // parent == module;
}

define_function(f2)
{
        YR_OBJECT* module = module();
        YR_OBJECT* parent = parent();

        // parent != module;
}

begin_declarations;

        declare_function("f1", "i", "i", f1);

        begin_struct("foo");

                declare_function("f2", "i", "i", f2);

        end_struct("foo");

end_declarations;

В функции f1 переменная module указывает на верхний уровень YR_OBJECT, а также на переменную parent, потому что родителем для f1 является сам модуль. Однако в функции f2 переменная parent указывает на YR_OBJECT, соответствующий структуре foo, а module указывает на верхний уровень YR_OBJECT, как и в первом случае.

4.5.4. Контекст сканирования

Из функции вы также можете получить доступ к структуре YR_SCAN_CONTEXT, обсуждавшейся ранее в п. 4.4.1. Это полезно для функций, которые должны проверять сканируемый файл или память процесса. Вот как вы получаете указатель на структуру YR_SCAN_CONTEXT:

YR_SCAN_CONTEXT* context = scan_context();