Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added option --report-name #343

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions qark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/env python

from __future__ import absolute_import

import logging
import logging.config
import os

import click

from qark.apk_builder import APKBuilder
from qark.decompiler.decompiler import Decompiler
from qark.report import Report
from qark.scanner.scanner import Scanner
from qark.utils import environ_path_variable_exists

DEBUG_LOG_PATH = os.path.join(os.getcwd(),
"qark_debug.log")

# Environment variable names for the SDK
ANDROID_SDK_HOME = "ANDROID_SDK_HOME"
ANDROID_HOME = "ANDROID_HOME"
ANDROID_SDK_ROOT = "ANDROID_SDK_ROOT"

logger = logging.getLogger(__name__)


@click.command()
@click.option("--sdk-path", type=click.Path(exists=True, file_okay=False, resolve_path=True),
help="Path to the downloaded SDK directory if already downloaded. "
"Only necessary if --exploit-apk is passed. If --exploit-apk is passed and this flag is not passed,"
"QARK will attempt to use the ANDROID_SDK_HOME, ANDROID_HOME, ANDROID_SDK_ROOT "
"environment variables (in that order) for a path.")
@click.option("--build-path", type=click.Path(resolve_path=True, file_okay=False),
help="Path to place decompiled files and exploit APK.", default="build", show_default=True)
@click.option("--debug/--no-debug", default=False, help="Show debugging statements (helpful for issues).",
show_default=True)
@click.option("--apk", "source", help="APK to decompile and run static analysis. If passed, "
"the --java option is not used.",
type=click.Path(exists=True, resolve_path=True, file_okay=True, dir_okay=True))
@click.option("--java", "source", type=click.Path(exists=True, resolve_path=True, file_okay=True, dir_okay=True),
help="A directory containing Java code, or a Java file, to run static analysis. If passed,"
"the --apk option is not used.")
@click.option("--report-name", help="Name of report file. For instance, Report_Instagram", default = 'Report', show_default=True)
@click.option("--report-type", help="Type of report to generate along with terminal output.", default="html", show_default=True)
@click.option("--exploit-apk/--no-exploit-apk", default=False,
help="Create an exploit APK targetting a few vulnerabilities.", show_default=True)
@click.option("--report-path", type=click.Path(resolve_path=True, file_okay=False), default=None,
help="report output path.", show_default=True)
@click.option("--keep-report/--no-keep-report", default=False,
help="Append to final report file.", show_default=True)
@click.version_option()
@click.pass_context
def cli(ctx, sdk_path, build_path, debug, source, report_type, exploit_apk, report_path, keep_report, report_name):
if not source:
click.secho("Please pass a source for scanning through either --java or --apk")
click.secho(ctx.get_help())
return

if exploit_apk:

if not sdk_path:
# Try to set the SDK from environment variables if they exist
# Follows the guidelines from https://developer.android.com/studio/command-line/variables
if environ_path_variable_exists(ANDROID_SDK_HOME):
sdk_path = os.environ[ANDROID_SDK_HOME]

elif environ_path_variable_exists(ANDROID_HOME):
sdk_path = os.environ[ANDROID_HOME]

elif environ_path_variable_exists(ANDROID_SDK_ROOT):
sdk_path = os.environ[ANDROID_SDK_ROOT]

else:
click.secho("Please provide path to android SDK if building exploit APK.")
return

# Debug controls the output to stderr, debug logs are ALWAYS stored in `qark_debug.log`
if debug:
level = "DEBUG"
else:
level = "INFO"

initialize_logging(level)

click.secho("Decompiling...")
decompiler = Decompiler(path_to_source=source, build_directory=build_path)
decompiler.run()

click.secho("Running scans...")
path_to_source = decompiler.path_to_source if decompiler.source_code else decompiler.build_directory

scanner = Scanner(manifest_path=decompiler.manifest_path, path_to_source=path_to_source)
scanner.run()
click.secho("Finish scans...")

click.secho("Writing report...")
report = Report(issues=set(scanner.issues), report_path=report_path, keep_report=keep_report, report_name = report_name)
report_path = report.generate(file_type=report_type, report_name = report_name)
click.secho("Finish writing report to {report_path} ...".format(report_path=report_path))

if exploit_apk:
click.secho("Building exploit APK...")
exploit_builder = APKBuilder(exploit_apk_path=build_path, issues=scanner.issues, apk_name=decompiler.apk_name,
manifest_path=decompiler.manifest_path, sdk_path=sdk_path)
exploit_builder.build()
click.secho("Finish building exploit APK...")


def initialize_logging(level):
"""Creates two root handlers, one to file called `qark_debug.log` and one to stderr"""
handlers = {
"stderr_handler": {
"level": level,
"class": "logging.StreamHandler"
}
}
loggers = ["stderr_handler"]

if level == "DEBUG":
handlers["debug_handler"] = {
"level": "DEBUG",
"class": "logging.FileHandler",
"filename": DEBUG_LOG_PATH,
"mode": "w",
"formatter": "standard"
}
loggers.append("debug_handler")

logging.config.dictConfig({
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
}
},
"handlers": handlers,
"loggers": {
"": {
"handlers": handlers,
"level": "DEBUG",
"propagate": True
}
}
})

if level == "DEBUG":
logger.debug("Debug logging enabled")
73 changes: 73 additions & 0 deletions report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from __future__ import absolute_import

from os import path

from jinja2 import Environment, PackageLoader, select_autoescape, Template

from qark.issue import (Issue, Severity, issue_json) # noqa:F401 These are expected to be used later.
from qark.utils import create_directories_to_path

DEFAULT_REPORT_PATH = path.join(path.dirname(path.realpath(__file__)), 'report', '')


jinja_env = Environment(
loader=PackageLoader('qark', 'templates'),
autoescape=select_autoescape(['html', 'xml'])
)

jinja_env.filters['issue_json'] = issue_json


class Report(object):
"""An object to store issues against and to generate reports in different formats.

There is one instance created per QARK run and it uses a classic Singleton pattern
to make it easy to get a reference to that instance anywhere in QARK.
"""

# The one instance to rule them all
# http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Singleton.html#the-singleton
__instance = None

def __new__(cls, issues=None, report_path=None, keep_report=False, report_name = None):
if Report.__instance is None:
Report.__instance = object.__new__(cls)

return Report.__instance

def __init__(self, issues=None, report_path=None, keep_report=False, report_name = None):
"""This will give you an instance of a report, with a default report path which is local
to where QARK is on the file system.

:param report_path: The path to the report directory where all generated report files will be written.
:type report_path: str or None

"""
self.issues = issues if issues else []
self.report_path = report_path or DEFAULT_REPORT_PATH
self.keep_report = keep_report

def generate(self, file_type='html', template_file=None, report_name = 'report'):
"""This method uses Jinja2 to generate a standalone HTML version of the report.

:param str file_type: The type of file for the report. Defaults to 'html'.
:param str template_file: The path to an optional template file to override the default.
:return: Path to the written report
:rtype: str
"""
create_directories_to_path(self.report_path)

full_report_path = path.join(self.report_path, '{report_name}_QARK.{file_type}'.format(report_name = report_name, file_type=file_type))

open_flag = 'w'
if self.keep_report:
open_flag = 'a'
with open(full_report_path, mode=open_flag) as report_file:
if not template_file:
template = jinja_env.get_template('{file_type}_report.jinja'.format(file_type=file_type))
else:
template = Template(template_file)
report_file.write(template.render(issues=list(self.issues)))
report_file.write('\n')

return full_report_path
2 changes: 1 addition & 1 deletion requirements-test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ mccabe==0.6.1 \
--hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f \
# via flake8
pluggy==0.6.0 \
--hash=sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff \
--hash=sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff
# via pytest
pluginbase==0.5 \
--hash=sha256:b4f830242a078a4f44c978a84f3365bba4d008fdd71a591c71447f4df35354dd
Expand Down