Custom Matching of Koodous YARA Rules

Koodous is a service that provides metadata about a rich collection of Android apps. Apart from that it also stores a public set of YARA rules that are being used by different 3rd parties as indicators for malware detection. While quality of available YARA rules in the public collection differ and their blind usage may […]

Koodous is a service that provides metadata about a rich collection of Android apps. Apart from that it also stores a public set of YARA rules that are being used by different 3rd parties as indicators for malware detection.

While quality of available YARA rules in the public collection differ and their blind usage may be disputable, YARA syntax extension invented by Koodous is definitely useful for two reasons:

  • It allows to match public YARA rules against own Android app samples
  • It allows to create new YARA rules taking advantage of Koodous YARA syntax extension – new syntax is documented here.

The tricky part is to get Koodous YARA extension really working in practice. In order do avoid platform-dependent complications I have decided to build a docker image wrapping YARA tool into an isolated environment. I got inspired with a GIST from here, but it needed modifications to make it really work in my Docker container:

FROM python:3.8-slim

RUN mkdir -p /usr/src/app
RUN mkdir -p /mnt/apk

RUN apt-get update
RUN apt-get --assume-yes install musl gcc g++ \
                        libjansson-dev file flex bison make libssl-dev \
                        libmagic-dev libtool automake autoconf git binutils curl \
                        libfuzzy-dev
RUN pip install --upgrade pip

WORKDIR /usr/src

# YARA library and tool compilation
RUN git clone --branch v3.11.0 https://github.com/VirusTotal/yara.git

WORKDIR /usr/src/yara/libyara/modules

# download the Androguard module from Koodous (implementing YARA syntax extension) 
RUN curl -O https://raw.githubusercontent.com/Koodous/androguard-yara/master/androguard.c

# misuse --enable-cockoo argument for compilation of the Androguard module
RUN sed -i -e 's/MODULE(cuckoo)/MODULE(androguard)/g' module_list
WORKDIR /usr/src/yara/libyara
RUN sed -i -e 's/MODULES += modules\/cuckoo.c/MODULES += modules\/androguard.c/g' Makefile.am

# build and install YARA
WORKDIR /usr/src/yara
RUN ./bootstrap.sh
RUN ./configure --enable-dotnet --enable-magic --enable-dex --enable-cuckoo && make && make install

WORKDIR /usr/src

# Download and build yara-python package for usage of YARA from Python - it must be linked with the previously compiled YARA lib
RUN git clone --branch v3.11.0 https://github.com/VirusTotal/yara-python
WORKDIR /usr/src/yara-python
RUN python setup.py build --dynamic-linking
RUN python setup.py install

WORKDIR /usr/src/app
ADD ./requirements.txt /usr/src/app/requirements.txt

#we need to extend LD_LIBRARY_PATH and C_INCLUDE_PATH before installing requirements because of the ssdeep package
RUN export LD_LIBRARY_PATH=/usr/local/lib/ && export C_INCLUDE_PATH=/usr/local/include && pip install -r requirements.txt

ADD . /usr/src/app


Having a Docker image built, we can take a look on a practical use of the extended Yara from Python. Here I provide a simple demo Python script that uses the Androguard extension to check if the app requires any of RECEIVE_SMS or READ_SMS permissions.

from androguard.core.bytecodes.apk import APK
from androguard.misc import AnalyzeAPK
from androguard.session import Session

import yara
import json

APK_PATH = 'target.apk'

RULE_SOURCE_CODE = '''import "androguard"
                      rule SmsInteraction {
                      condition:
                        androguard.permission(/android.permission.RECEIVE_SMS/)
                        or androguard.permission(/android.permission.READ_SMS/)
                      }'''

def main():
    compiled_rule = yara.compile(source=RULE_SOURCE_CODE)
    androguard_report = prepare_report()

    matching_rules = compiled_rule.match(APK_PATH,
                                         modules_data={'androguard': androguard_report})
    if matching_rules:
        print('It matches!')
    else:
        print('It does not match!')


def prepare_report() -> bytes:
    androguard_session = Session()
    apk_file: APK
    apk_file, _, _ = AnalyzeAPK(APK_PATH, androguard_session)
    apk_sha256 = list(androguard_session.get_all_apks())[0][0]

    return json.dumps({
        'app_name': apk_file.get_app_name(),
        'package_name': apk_file.get_package(),
        'certificate': {}, #TODO
        'services': apk_file.get_services(),
        'permissions': apk_file.get_permissions(),
        'new_permissions': [], # we have no older version of app to compare with
        'filters': [], #TODO
        'min_sdk_version': apk_file.get_min_sdk_version(),
        'max_sdk_version': apk_file.get_max_sdk_version(),
        'target_sdk_version': apk_file.get_target_sdk_version(),
        'version_code': apk_file.get_androidversion_code(),
        'displayed_version': apk_file.get_androidversion_name(),
        'libraries': apk_file.get_libraries(),
        'activities': apk_file.get_activities(),
        'main_activity': apk_file.get_main_activity(),
        'providers': apk_file.get_providers(),
        'receivers': apk_file.get_receivers(),
        'signature_name': apk_file.get_signature_name(),
        'wearable': apk_file.is_wearable(),
        'androidtv': apk_file.is_androidtv(),
        'sha256': apk_sha256,
        'dexes': [], #TODO
        'urls': [] #TODO
    }, indent=2).encode('utf-8')


if __name__ == '__main__':
    main()

Note that prepare_report() does not generate a complete Androguard report and there are some TODOs to make the report complete. All of those can be completed with more in-depth app analysis with Androguard.