Tuesday, November 25, 2014

One of the features for ClamAV 0.98.5 is the File Properties Collection Analysis which collects information on scanned files. However, question is, how do we go about using this new feature?

Well, the first thing to know is there is a new ClamAV target type set for scanning the internal JSON structure generated by enabling this feature, target type 13. All signatures set for target type 13 with be only applied to the internal JSON structure. Thus you can write a normal ClamAV signature to detect on the value of certain fields but this is very limited in a number of regards. For example, you cannot have any insight on how the properties are related to one another or, in certain cases, whether a match is from the file itself or an embedded file.

In fact, the other case where you can make that distinction is on the root file type and embedded file types but this can, at best, just provide a form of filtering. This is the case where a bytecode signature is needed. In this post, we will be examining a bytecode signature source on the common things to look for in writing bytecode signatures. This post is directed towards signature writers.

 This bytecode source can be used to examine all supported files for embedded executables:
/* ClamAV.BCC.SandBox.Submit */
/* ClamAV.BCC.SandBox.InActive */
VIRUSNAME_PREFIX("ClamAV.BCC.SandBox")
VIRUSNAMES("Submit""InActive")

/* Target type is 13, internal JSON properties */
TARGET(13)

/* JSON API call will require FUNC_LEVEL_098_5 = 78 */
FUNCTIONALITY_LEVEL_MIN(FUNC_LEVEL_098_5)

SIGNATURES_DECL_BEGIN
DECLARE_SIGNATURE(sig1)
SIGNATURES_DECL_END

SIGNATURES_DEF_BEGIN
/* search @offset 0 : '{ "Magic": "CLAMJSON' */
/* this can be readjusted for specific filetypes */
DEFINE_SIGNATURE(sig1, "0: 7b20224d61676963223a2022434c414d4a534f4e")
SIGNATURES_END

bool logical_trigger(void)
{
    return matches(Signatures.sig1);
}

#define STR_MAXLEN 256

int entrypoint ()
{
    int i;
    int32_t type, obj, objarr, objit, arrlen, strlen;
    char str[STR_MAXLEN];

    /* check is json is available, alerts on inactive (optional) */
    if (!json_is_active())
        foundVirus("InActive");

    /* acquire array of internal contained objects */
    objarr = json_get_object("ContainedObjects", 16, 0);
    type = json_get_type(objarr);
    /* debug print uint (no '\n' or prepended message */
    debug_print_uint(type);

    if (type != JSON_TYPE_ARRAY) {
        return -1;
    }

    /* check array length for iteration over elements */
    arrlen = json_get_array_length(objarr);
    for (i = 0; i < arrlen; ++i) {
        /* acquire json object @ idx i */
        objit = json_get_array_idx(i, objarr);
        if (objit <= 0) continue;

        /* acquire FileType object of the array element @ idx i */
        obj = json_get_object("FileType", 8, objit);
        if (obj <= 0) continue;

        /* acquire and check type */
        type = json_get_type(obj);
        if (type == JSON_TYPE_STRING) {
            /* acquire string length, note +1 is for the NULL terminator */
            strlen = json_get_string_length(obj)+1;
            /* prevent buffer overflow */
            if (strlen > STR_MAXLEN)
                strlen = STR_MAXLEN;
            /* acquire string data, note strlen includes NULL terminator */
            if (json_get_string(str, strlen, obj)) {
                /* debug print str (with '\n' and prepended message */
                debug_print_str(str,strlen);

                /* check the contained object's type */
                if (strlen == 14 && !memcmp(str, "CL_TYPE_MSEXE", 14)) {
                    /* alert for submission */
                    foundVirus("Submit");
                    return 0;
                }
            }
        }
    }

    return 0;
}


Reported Signatures
The first thing that a bytecode signature source needs is the detection name it returns upon detecting the desired trait. The signatures that a bytecode can reported are determined by the strings passed to the VIRUSNAME_PREFIX and VIRUSNAMES macros.
/* ClamAV.BCC.SandBox.Submit */
/* ClamAV.BCC.SandBox.InActive */
VIRUSNAME_PREFIX("ClamAV.BCC.SandBox")
VIRUSNAMES("Submit""InActive")

VIRUSNAME_PREFIX: a REQUIRED macro field. It consists of exactly one string value which may contain alphanumeric characters and periods; periods are used to mark different groups the signature is attributed to.
VIRUSNAMES: an OPTIONAL macro field. It consists of an array of string values which may only contain alphanumeric characters.

Once the names of possible detections are declared, you need to specify in the entrypoint function when a detection has occurred. The bytecode signature reports specific detections through the usage of the bytecode API function foundVirus() which takes a single string argument that correlates to the VIRUSNAMES.
        foundVirus("InActive");
              ...
                    foundVirus("Submit");

Using an empty string (“”) will have the bytecode simple report the VIRUSNAME_PREFIX. Note that the VIRUSNAME_PREFIX string is not part of the foundVirus() call. Bytecode signatures may report one detection; multiple calls to foundVirus() may overwrite the previous detection though this behavior is not guaranteed.


Target Group and Engine
Bytecode allows for the user to direct the application of a bytecode signature specifically at a particular filetype and for a specific version of the ClamAV engine. While normally, these parameters are optional, using the File Properties Collection Analysis requires TARGET(13) and FUNCTIONALITY_LEVEL_MIN(FUNC_LEVEL_098_5).
/* Target type is 13, internal JSON properties */
TARGET(13)

/* JSON API call will require FUNC_LEVEL_098_5 = 78 */
FUNCTIONALITY_LEVEL_MIN(FUNC_LEVEL_098_5)

TARGET: a normally OPTIONAL macro field. For the case of ClamAV internal File Properties Collection Analysis, this is REQUIRED and MUST BE set to 13. It consists of single integer value [1-13 at time of writing] which represents the intended target of the bytecode (bytecode will only run on that target type). Target types are listed in the ClamAV Signature document.
FUNCTIONALITY_LEVEL_MIN: a normally OPTIONAL macro field. For the case of File Properties Collection Analysis, this is REQUIRED and MUST BE set to at least FUNC_LEVEL_098_5. It consists of an enumeration value that represents the minimum functionality level of ClamAV for this bytecode to run on. ClamAV versions prior to this value will not load this bytecode.
FUNCTIONALITY_LEVEL_MAX: an OPTIONAL macro field. It consists of an enumeration value that represents the maximum functionality level of ClamAV for this bytecode to run on. ClamAV versions after this value will not load this bytecode.


Logical signatures
Running bytecode signatures are quite expensive and so bytecode signatures are only executed where a certain logical condition is fulfilled. The first section that needs to be specified is the associated subsignatures and the logical trigger function. These are REQUIRED.
SIGNATURES_DECL_BEGIN
DECLARE_SIGNATURE(sig1)
SIGNATURES_DECL_END

SIGNATURES_DEF_BEGIN
/* search @offset 0 : '{ "Magic": "CLAMJSON' */
/* this can be readjusted for specific filetypes */
DEFINE_SIGNATURE(sig1, "0: 7b20224d61676963223a2022434c414d4a534f4e")
SIGNATURES_END

bool logical_trigger(void)
{
    return matches(Signatures.sig1);
}

Excerpt from clambc-user.pdf (from ClamAV Bytecode Documentation):
“Logical signatures use .ndb style patterns….
Each pattern has a name (like a variable), and a string that is the hex pattern itself. The declarations are delimited by the macros SIGNATURES_DECL_BEGIN, and SIGNATURES_DECL_END. The definitions are delimited by the macros SIGNATURES_DEF_BEGIN, and SIGNATURES_END. Declarations must always come before definitions, and you can have only one declaration and declaration section! (think of declaration like variable declarations, and definitions as variable assignments, since that what they are under the hood). The order in which you declare the signatures is the order in which they appear in the generated logical signature.
You can use any name for the patterns that is a valid record field name in C, and doesn’t conflict with anything else declared.
After using the above macros, the global variable Signatures will have [one field, sig1]. [This] can be used as arguments to the functions count_match(), and matches() anywhere in the program…:
·         matches[Signatures.sig1) will return true when the [sig1] signature matches (at least once)
·         count_match(Signatures.sig1) will return the number of times the [sig1] signature matched
The condition in [the logical_trigger function can be interpreted as if sig1 is matched at least once].”

The logical trigger for the sample bytecode is set to trigger on all supported file tyes by triggering on the detection of the associated internal structure “file magic”. If you want to trigger the bytecode on a specific file type, you could add a subsignature for the file type, for example:


/* search '"FileType": "CL_TYPE_MSEXE"' */
DEFINE_SIGNATURE(sig2, "2246696c6554797065223a2022434c5f545950455f4d5345584522")

And then set the logical trigger return to use both subsignatures:
bool logical_trigger(void)
{
    return matches(Signatures.sig1) && matches(Signatures.sig2);
}

Note that specifying TARGET(13) will already force the signature to apply strictly to File Properties Collection Analysis so the inclusion of the subsignature is simply for the requirement fulfillment in our example.


Main Program (entrypoint)
The “entrypoint” function in the bytecode signature can be seen as effectively the “main” function within a C program. It is REQUIRED and must use this prototype.
int entrypoint ()
{
...
}

At this point, the bytecode uses roughly the same syntax as the C programming language to perform operations on the file to determine whether or not to report detection. There are a key number of limitations on the bytecode language from C however, all of which can be found in section 4 of the clambc-user.pdf document of the ClamAV Bytecode Compiler.


Example Entrypoint Walkthrough
    int i;
    int32_t type, obj, objarr, objit, arrlen, strlen;
    char str[STR_MAXLEN];
These are the declarations of the variables will use in the bytecode. While it is not strictly enforced, it is generally good practice to state all variables at the start of each function. Note that only basic C types (excluding floats and doubles) and fixed-sized ints can be used. STR_MAXLEN is a macro equal to 256.

    /* check is json is available, alerts on inactive (optional) */
    if (!json_is_active())
        foundVirus("InActive");
The bytecode API is used to query if JSON is enabled in the libclamav instance. Note that the target type requirement of 13 will generally force the returned value to be true; this statement is here for extra safety. This segment also reports a virus “InActive” in the case that the JSON is inactive. A call to “foundVirus()” does not terminate the run of the program, so this sample signature will actually continue running even though JSON is not available. This is alright as most JSON parsing API functions check if JSON is enabled and return an error value.

    /* acquire array of internal contained objects */
    objarr = json_get_object("ContainedObjects", 16, 0);
    type = json_get_type(objarr);
    /* debug print uint (no '\n' or prepended message */
    debug_print_uint(type);

    if (type != JSON_TYPE_ARRAY) {
        return -1;
    }
This segment acquires an object ID for the “ContainedObjects” object and checks to see if the object is typed JSON_TYPE_ARRAY.  The other call “debug_print_uint()” prints a debug message with only the uint value (no “LibClamAV Debug” or newline”).

    /* check array length for iteration over elements */
    arrlen = json_get_array_length(objarr);
    for (i = 0; i < arrlen; ++i) {
        /* acquire json object @ idx i */
        objit = json_get_array_idx(i, objarr);
        if (objit <= 0) continue;
This segment setups an iteration across all the members of the “ContainedObjects” array retrieved earlier. Note the check for the objit ID to be a valid value.

        /* acquire FileType object of the array element @ idx i */
        obj = json_get_object("FileType", 8, objit);
        if (obj <= 0) continue;

        /* acquire and check type */
        type = json_get_type(obj);
        if (type == JSON_TYPE_STRING) {
            /* acquire string length, note +1 is for the NULL terminator */
            strlen = json_get_string_length(obj)+1;
            /* prevent buffer overflow */
            if (strlen > STR_MAXLEN)
                strlen = STR_MAXLEN;
            /* acquire string data, note strlen includes NULL terminator */
            if (json_get_string(str, strlen, obj)) {
                /* debug print str (with '\n' and prepended message */
                debug_print_str(str,strlen);

                /* check the contained object's type */
                if (strlen == 14 && !memcmp(str, "CL_TYPE_MSEXE", 14)) {
                    /* alert for submission */
                    foundVirus("Submit");
                    return 0;
                }
            }
        }
    }
This segment retrieves the objit’s type and, if it is a string, retrieves the string value and stores it in the str user string. The returned user string is a copy and modifications to the string do not change the internal object’s value. Next the string is a comparison of the returned string against “CL_TYPE_MSEXE” to determine whether to “Submit”.  Effectively, this signature returns a “Submit” whenever it detects a PE file embedded within the parent file.

Note the checks on the received strlen value to prevent a buffer overflow vulnerability. Bytecode signatures are always compiled with various runtime checks and thus a case that would cause the vulnerability would trigger a runtime error and bytecode termination (clamav continues to run). Regardless, great care should be exercised in regards to user variable boundaries.

    return 0;
}
Entrypoint function needs to return an integer; returning a 0 reports no issues with the bytecode execution however, all return values are ignored by libclamav by default.

This should about cover a basic example of how to write a bytecode signature to trigger on File Properties Collection Analysis, for more example bytecode signatures, you can look at the source code under “fileprop_analysis “ in the examples directory of the ClamAV source distribution. For more information on how to write bytecode signatures, you can review the documentation in under user in the doc directory of the clambc-compiler.

Monday, November 24, 2014

When writing signatures for the upcoming File Properties Collection Analysis feature, it is important to be aware of the various tools available for use. In this post, we will be covering the bytecode signature API specific to JSON manipulation of the collected property data structure. This is directed to signature writers. For information on writing bytecode signatures, refer to documentation in the clambc-compiler project, the walkthrough guide in the ClamAV project docs or other blog posts.

Determines if libclamav is configured with JSON.
    @return 0 - json is disabled or option not specified
    @return 1 - json is active and properties are available
int32_t json_is_active(void);

Retrieves the ID value of the specified named object within the specified parent object ID (object ID 0 is guaranteed to be defined as the topmost object).
    @return objid of json object with specified name
    @return 0 if json object of specified name cannot be found
    @return -1 if an error has occurred
    @param[in] name - name of object in ASCII
    @param[in] name_len - length of specified name (not including terminating NULL),
                          must be >= 0
    @param[in] objid - id value of json object to query
int32_t json_get_object(const int8_t* name, int32_t name_len, int32_t objid);

Determines the type of the specified JSON object, value is of the enumeration bc_json_type {JSON_TYPE_NULL=0, JSON_TYPE_BOOLEAN, JSON_TYPE_DOUBLE, JSON_TYPE_INT, JSON_TYPE_OBJECT, JSON_TYPE_ARRAY, JSON_TYPE_STRING}.
    @return type (json_type) of json object specified
    @return -1 if type unknown or invalid id
    @param[in] objid - id value of json object to query
int32_t json_get_type(int32_t objid);

Determines the length of the JSON array object; objid must be of type JSON_TYPE_ARRAY or an error will be returned.
    @return number of elements in the json array of objid
    @return -1 if an error has occurred
    @return -2 if object is not JSON_TYPE_ARRAY
    @param[in] objid - id value of json object (should be JSON_TYPE_ARRAY) to query
int32_t json_get_array_length(int32_t objid);

Retrieves the ID value for the object located at a specific index of a JSON array object; objid must be of type JSON_TYPE_ARRAY or an error will be returned.
    @return objid of json object at idx of json array of objid
    @return 0 if invalid idx
    @return -1 if an error has occurred
    @return -2 if object is not JSON_TYPE_ARRAY
    @param[in] idx - index of array to query, must be >= 0 and less than array length
    @param[in] objid - id value of json object (should be JSON_TYPE_ARRAY) to query
int32_t json_get_array_idx(int32_t idx, int32_t objid);

Determines the length of a JSON string object; objid must be of type JSON_TYPE_STRING or an error will be returned. Note: this value DOES NOT include the terminating null.
    @return length of json string of objid, not including terminating null-character
    @return -1 if an error has occurred
    @return -2 if object is not JSON_TYPE_STRING
    @param[in] objid - id value of json object (should be JSON_TYPE_STRING) to query
int32_t json_get_string_length(int32_t objid);

Retrieves the string contents of the JSON string object and stores it to a user location; objid must be of type JSON_TYPE_STRING or an error will be returned. Note: the specified length MUST include the terminating NULL.
    @return number of characters transferred (capped by str_len), 
            including terminating null-character
    @return -1 if an error has occurred
    @return -2 if object is not JSON_TYPE_STRING
    @param[out] str - user location to store string data; will be null-terminated
    @param[in] str_len - length of str or limit of string data to read,
                         including terminating null-character
    @param[in] objid - id value of json object (should be JSON_TYPE_STRING) to query
int32_t json_get_string(int8_t* str, int32_t str_len, int32_t objid);

Retrieves the boolean value of a JSON object.
    @return boolean value of queried objid; will force other types to boolean
    @param[in] objid - id value of json object to query
int32_t json_get_boolean(int32_t objid);

Retrieves the integer value of a JSON object.
    @return integer value of queried objid; will force other types to integer
    @param[in] objid - id value of json object to query
int32_t json_get_int(int32_t objid);

Note that since bytecode does not support double type, it is not possible to retrieve double values from JSON objects.

Thursday, November 20, 2014

In ClamAV 0.98.5, a new feature provides for file property collection and analysis.  The feature is intended for software developers and analysts who want to include the collection and analysis of file properties in their applications in addition to scanning file content. ClamAV 0.98.5 collects properties on the following file types:
  • Microsoft Word
  • Microsoft Excel
  • Microsoft Powerpoint
  • Office Open XML (OOXML) Document
  • Office Open XML Presentation
  • Office Open XML Workbook
  • Microsoft Portable Executable
  • Adobe Portable Document Format

How does it work? There are three main areas to understand about the file properties scanning features.

The first area is about how to initiate the file properties collection through the ClamAV API. A program using the ClamAV API may indicate property scanning by setting an option. The option is required to invoke the file property collection scan mode. Additionally, the API provides a new callback function to enable custom processing such as tailoring the result of the property scan, or to write the json properties string to a file. Use of the callback function is optional.

By way of example, the command line program clamscan uses the following API call to scan a file:
if((ret = cl_scandesc_callback(fd, virpp, &info.blocks, engine, options, &chain) ...
If you are interested in more detail, clamscan/manager.c contains this API call, and libclamav/clamav.h contains the function prototype of cl_scandesc_callback. In this case, to indicate file properties scanning to the ClamAV engine from clamscan, use the following sequence:
options |= CL_SCAN_FILE_PROPERTIES;
if((ret = cl_scandesc_callback(fd, virpp, &info.blocks, engine, options, &chain) ...
That's it! clamscan will now collect file properties for analysis. In fact, this is basically what clamscan does to support the new --gen-json option.

That brings us to the second area, which involves how file properties are collected and stored. We use json for this purpose. As ClamAV scans a file, it gathers properties about the file as well. The properties are maintained as json objects. At the end of the file scan, and prior to the file properties analysis scan, ClamAV serializes the json objects to the json files properties string.

The file properties json is a recursive structure of other file property objects. At the top level, the file property object that may be thought of as representing the file as a container, The generic schema of the json file property object contains the following objects:
  • FileSize
  • FileType
  • FileMD5
  • ContainedObjects (Array)
  • Viruses (Array)
The ContainedObjects is an array of other embedded file objects, such as a spreadsheet embedded within a Word document. The whole pattern is repeated, in this case, where the embedded spreadsheet object may have its own ContainedObjects array. There are additional file properties that are dependent upon the particular file type. A complete list of file properties and their parent file types can be found in ClamAV_Document_Properties.xlsx within the ClamAV docs/ directory.

You can see the generated json file property string from the command line by using:
clamscan --gen-json --debug <some office, pdf, or pe file>
The third and last area is the analysis of the generated json file properties string.  After the original file scan, ClamAV performs a second scan on the resulting file properties string.  Both pattern signatures and bytecode programs may by used for analysis the file properties string. ClamAV handles these signatures identically to those used on normal files, except that the target file is the generated json file properties string. Signatures should specify the json properties file target type of 13 to ensure the signature operates on the file properties string. Bytecode signatures will be the most useful for the analysis phase. Several upcoming blog posts in this series will discuss in detail about writing bytecode programs for property scanning and using the bytecode json API. You can also see sample byte code signature in ClamAV 0.98.5 directory examples/fileprop_analysis/.

One final note: To use file property scanning, json-c must be installed and configured into ClamAV. If it is not already installed, you can obtain json-c from https://github.com/json-c/json-c or install it using your package manager. We support json-c versions 0.9 through 0.12, but recommend version 0.12. After installing json-c, include it into your ClamAV configuration using './configure --with-libjson'. See ./configure --help for additional information about ClamAV configure options. Note that json-c is not required by any other ClamAV facility.

Wednesday, November 19, 2014

Bytecode signatures are a specialized type of ClamAV signature which is able to perform additional processing of the scanned file and allow for more robust detection. Unlike the standard ClamAV signature types, bytecode signatures have a number of unique distinctions which need to be respected for their effective usage.

Bytecode Signature Generation
The major distinction between bytecode signatures and the other ClamAV based standard signature languages is that bytecode signatures are actually compiled from a user-written source file, similar to Java bytecode. The tool used to generate bytecode signatures from source is the clambc-compiler which is a separate project from ClamAV.

You can get it by using one of these commands:
git clone git://github.com/vrtadmin/clamav-bytecode-compiler (recommended for git <1.7)
git clone https://github.com/vrtadmin/clamav-bytecode-compiler (recommended for git 1.7+)

The repository can also be browsed online here:
https://github.com/vrtadmin/clamav-bytecode-compiler

Once the source is acquired, read the README in the project to compile and install the clambc-compiler. To compile the bytecode signature, simply run the command:

clambc-compiler [options] [source]

For information on how to write bytecode source, please refer to the clambc-compiler documentation or other blog posts.

Running Bytecode Signatures in ClamAV
Due to the nature of how bytecode signatures are ran in ClamAV, there are a number of pre-cautions taken to ensure safety of the bytecode signature execution.

Trust
Bytecode signatures, by default, are considered untrusted. In fact, only bytecode signatures published by Cisco, in the bytecode.cvd are considered “trusted”. This means that the ClamAV engine will, by default, never load, trigger or execute untrusted bytecodes. One can bypass this safety mechanism by specifying the bytecode unsigned option to the engine but it should be noted that it is up to the user’s discretion on using untrusted bytecode signatures.

For clamscan, the command line option is --bytecode-unsigned.
For clamd, one would need to specify BytecodeUnsigned yes to clamd.conf.

Timeout
Bytecode signatures are designed to only run for a limited amount of time designated by an internal timeout value. If execution time exceeds the value, the bytecode signature’s execution is terminated and the user is notified. The bytecode signature timeout value can be set by the user.

For clamscan, the command line is --bytecode-timeout=[time in ms].
For clamd, one would specify BytecodeTimeout [time in ms] to clamd.conf.

Bytecode Databases
Bytecode signatures are stored in a separate database from the standard ClamAV signatures. In fact, it is impossible to generate database files (with sigtool) that contain both bytecode signatures and standard signatures. Bytecode databases are generated by building a database that is named “bytecode.*” which triggers specialized handling by sigtool. Sigtool will add all bytecode signatures in the specified directory regardless if the name of the signature matches the name of the database. Bytecode signatures thus can be named anything provided the extension is “.cbc”.

Bytecode databases generated this way are still considered untrusted (unless published by Cisco) which means one needs to still specify the appropriate flags to use the database.

Issue Reporting
If anyone encounters issue with bytecode signatures, whether within the clambc-compiler or within ClamAV, they can report them to https://bugzilla.clamav.net/. Be sure to include the bytecode signature, bytecode source(if possible), and any other pieces of useful information.

Tuesday, November 18, 2014

Welcome to ClamAV 0.98.5! ClamAV 0.98.5 includes important new features
for collecting and analyzing file properties. Software developers and
analysts may collect file property meta data using the ClamAV API for
subsequent analysis by ClamAV bytecode programs. Using these features
will require that libjson-c is installed, but otherwise libjson-c is not
needed.

Look for our upcoming series of blog posts to learn more about using the
ClamAV API and bytecode facilities for collecting and analyzing file
properties.

ClamAV 0.98.5 also includes these new features and bug fixes:


  • Support for the XDP file format and extracting, decoding, and scanning PDF files within XDP files. Addition of shared library support for LLVM versions 3.1 - 3.5 for the purpose of just-in-time(JIT) compilation of ClamAV bytecode signatures. Andreas Cadhalpun submitted the patch implementing this support.
  • Enhancements to the clambc command line utility to assist ClamAV bytecode signature authors by providing introspection into compiled bytecode programs.
  • Resolution of many of the warning messages from ClamAV compilation.
  • Improved detection of malicious PE files.
  • Security fix for ClamAV crash when using 'clamscan -a'. This issue was identified by Kurt Siefried of Red Hat.  -- CVE 2013-6497
  • Security fix for ClamAV crash when scanning maliciously crafted yoda's crypter files. This issue, as well as several other bugs fixed in this release, were identified by Damien Millescamp of Oppida.  -- CVE-2014-9050
  • ClamAV 0.98.5 now works with OpenSSL in FIPS compliant mode. Thanks to Reinhard Max for supplying the patch.
  • Bug fixes and other feature enhancements. See Changelog or git log for details.


Thanks to the following ClamAV community members for code submissions
and bug reporting included in ClamAV 0.98.5:

Andreas Cadhalpun
Sebastian Andrzej Siewior
Damien Millescamp
Reinhard Max
Kurt Seifried

Please download the latest release of ClamAV from 0.98.5 from our download page.