CVE-2019-5020
An exploitable Denial of Service vulnerability exists in the object lookup functionality of Yara 3.8.1. A specially crafted binary file can cause a negative value to be read to satisfy an assert, resulting in Denial of Service. An attacker can create a malicious binary to trigger this vulnerability.
Yara 3.8.1
http://virustotal.github.io/yara/
6.5 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
CWE-617: Reachable Assertion
Yara is tool for finding patterns in specific files, targeted towards the malware research community. Yara allows for automated scanning of samples to help classify samples for easier analysis. Researchers can write specific rules using yara’s format that will trigger when used to scan various samples.
Yara can parse a variety of binary file formats, such as PE, MachO, ELF, and Dex. In order for Yara to parse Dex files, it must decode a variety of fields from the binary file itself. In order to accomplish this, load_encoded_field
is called on a given field type. During the initial parsing of a Dex file, the raw fields are stored in the Dex object under a variety of string keys. A few of these keys are shown below.
begin_struct_array("type_ids");
declare_integer("descriptor_idx");
end_struct_array("type_ids");
begin_struct_array("proto_ids");
declare_integer("shorty_idx");
declare_integer("return_type_idx");
declare_integer("parameters_offset");
end_struct_array("proto_ids");
begin_struct_array("field_ids");
declare_integer("class_idx");
declare_integer("type_idx");
declare_integer("name_idx");
end_struct_array("field_ids");
In order to retrieve one of these values, an index into wanted struct array is given along with a string key such as field_ids[%i].name_idx
.
libyara/modules/dex.c:350
int32_t load_encoded_field(
DEX* dex,
size_t start_offset,
uint32_t *previous_field_idx,
int index_encoded_field,
int static_field,
int instance_field)
{
...
encoded_field.field_idx_diff = (uint32_t) read_uleb128(
(dex->data + start_offset + current_size), ¤t_size); [0]
encoded_field.access_flags = (uint32_t) read_uleb128(
(dex->data + start_offset + current_size), ¤t_size);
*previous_field_idx = encoded_field.field_idx_diff + *previous_field_idx;
int name_idx = (int) get_integer(
dex->object, "field_ids[%i].name_idx", *previous_field_idx); [1]
Above is trying to reading a value [0] from the Dex binary and asking for the integer parsed at this index [1] from the field_ids
array. In order to retrieve the integer, a lookup through the Dex object is performed.
libyara/object.c:896
int64_t yr_object_get_integer(
YR_OBJECT* object,
const char* field,
...)
{
YR_OBJECT* integer_obj;
va_list args;
va_start(args, field);
if (field != NULL)
integer_obj = _yr_object_lookup(object, 0, field, args); [2]
else
integer_obj = object;
In this case, [2] object is the dex->object
, field
is field_ids[%i].name_idx
, and args
is previous_field_idx
which depended on a read in value from the Dex file. The implementation of the generic object lookup is below.
libyara/object.c:422
static YR_OBJECT* _yr_object_lookup(
YR_OBJECT* object,
int flags,
const char* pattern,
va_list args)
{
YR_OBJECT* obj = object;
const char* p = pattern;
const char* key = NULL;
char str[256];
int i;
int index = -1;
while (obj != NULL)
{
...
if (*p == '[')
{
p++;
if (*p == '%')
{
p++;
switch(*p++)
{
case 'i':
index = va_arg(args, int);
break;
case 's':
key = va_arg(args, const char*);
break;
default:
return NULL;
}
}
...
This function implements a minimal format string parser. In particular, the value %i
treats the current args
function parameter as as an integer, overwriting the original value of -1
. The function continues to attempt to retrieve the indexed value from the object.
libyara/object.c:503
switch(obj->type)
{
case OBJECT_TYPE_ARRAY:
assert(index != -1);
obj = yr_object_array_get_item(obj, flags, index);
break;
case OBJECT_TYPE_DICTIONARY:
assert(key != NULL);
obj = yr_object_dict_get_item(obj, flags, key);
break;
}
Based on the current object’s type, a particular function is called to handle the wanted functionality. The index
is checked to ensure it was overwritten and has a value that was different than the original value of -1
. Failing this check, the program aborts. If an attacker crafts the binary scanned by yara to have a -1 read in at [0], the overwritten index value is still -1
, causing the abort to trigger. If yara scans a directory of samples containing this malicious binary, due to the denial of service, the other samples would not be scanned and could be overlooked.
2019-02-19 - Vendor Disclosure
2019-02-20 - Plain text copy of report issued per vendor request
2019-02-22 - Vendor patched
Discovered by Cory Duplantis of Cisco Talos.