Skip to content
This repository has been archived by the owner on Jul 27, 2024. It is now read-only.

Commit

Permalink
Replaced unofficial VirusTotal api with the official one;
Browse files Browse the repository at this point in the history
removed multiple VirusTotal keys arguments (useless feature).
  • Loading branch information
packmad committed Dec 12, 2020
1 parent d7d332d commit d0f9e2b
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 99 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Before opening this issue, I tried the following steps:

* [ ] Installed the tool in a way [described in the readme](https://github.com/ClaudiuGeorgiu/Obfuscapk#-installation) and ran `python3 -m obfuscapk.cli --help` without any errors

* [ ] Ran the tool using only `Rebuild`, `NewSignature` and `NewAlignment` obfuscators to verify that the app is not using anti-repackaging techniques
* [ ] Ran the tool using: `python3 -m obfuscapk.cli -o Rebuild -o NewSignature -o NewAlignment <YOUR_APP.APK>` to verify that the app is not using anti-repackaging techniques

* [ ] Ran the tool using `--ignore-libs` flag to exclude third party libraries from the obfuscation

Expand Down
8 changes: 3 additions & 5 deletions src/obfuscapk/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def get_cmd_args(args: list = None):
parser.add_argument(
"-k",
"--virus-total-key",
action="append",
type=str,
metavar="VT_API_KEY",
help="When using Virus Total obfuscator, a valid API key has to be provided. "
"Can be specified multiple times to use a different API key for each request "
Expand Down Expand Up @@ -121,7 +121,7 @@ def main():
-o ResStringEncryption -o ArithmeticBranch -o FieldRename -o Nop -o Goto \
-o ClassRename -o Reflection -o AdvancedReflection -o Reorder -o RandomManifest \
-o Rebuild -o NewSignature -o NewAlignment \
-o VirusTotal -k virus_total_key_1 -k virus_total_key_2 \
-o VirusTotal -k virus_total_key \
/path/to/original.apk
"""

Expand All @@ -142,9 +142,7 @@ def main():
arguments.destination = arguments.destination.strip(" '\"")

if arguments.virus_total_key:
arguments.virus_total_key = [
key.strip(" '\"") for key in arguments.virus_total_key
]
arguments.virus_total_key = arguments.virus_total_key.strip(" '\"")

if arguments.keystore_file:
arguments.keystore_file = arguments.keystore_file.strip(" '\"")
Expand Down
4 changes: 2 additions & 2 deletions src/obfuscapk/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def perform_obfuscation(
obfuscated_apk_path: str = None,
ignore_libs: bool = False,
interactive: bool = False,
virus_total_api_key: List[str] = None,
virus_total_api_key: str = None,
keystore_file: str = None,
keystore_password: str = None,
key_alias: str = None,
Expand All @@ -71,7 +71,7 @@ def perform_obfuscation(
:param ignore_libs: If True, exclude known third party libraries from the
obfuscation operations.
:param interactive: If True, show a progress bar with the obfuscation progress.
:param virus_total_api_key: A list containing Virus Total API keys, needed only
:param virus_total_api_key: A string containing Virus Total API keys, needed only
when using Virus Total obfuscator.
:param keystore_file: The path to a custom keystore file to be used for signing the
resulting obfuscated application. If not provided, a default
Expand Down
4 changes: 2 additions & 2 deletions src/obfuscapk/obfuscation.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def __init__(
obfuscated_apk_path: str = None,
ignore_libs: bool = False,
interactive: bool = False,
virus_total_api_key: List[str] = None,
virus_total_api_key: str = None,
keystore_file: str = None,
keystore_password: str = None,
key_alias: str = None,
Expand All @@ -37,7 +37,7 @@ def __init__(
self.obfuscated_apk_path: str = obfuscated_apk_path
self.ignore_libs: bool = ignore_libs
self.interactive: bool = interactive
self.virus_total_api_key: List[str] = virus_total_api_key
self.virus_total_api_key: str = virus_total_api_key
self.keystore_file: str = keystore_file
self.keystore_password: str = keystore_password
self.key_alias: str = key_alias
Expand Down
118 changes: 31 additions & 87 deletions src/obfuscapk/obfuscators/virus_total/virus_total.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
import logging
import os
import time
from http import HTTPStatus
from itertools import cycle
from pprint import pformat
from typing import Optional, Dict

from virus_total_apis import PublicApi as VirusTotalPublicApi
import vt
from pprint import pformat

from obfuscapk import obfuscator_category
from obfuscapk.obfuscation import Obfuscation
Expand All @@ -21,85 +20,34 @@ def __init__(self):
"{0}.{1}".format(__name__, self.__class__.__name__)
)
super().__init__()
self.vt_session = None

self.vt_sessions = None

def get_vt_session(self):
# Cycle through the Virus Total sessions (one session for each key):
return next(self.vt_sessions)

def get_report(self, sha256_hash: str) -> dict:
sleep_interval = 8 # In seconds.
attempt = 1
max_attempts = 16

for attempt in range(1, max_attempts + 1):
report = self.get_vt_session().get_file_report(sha256_hash)

if "response_code" in report and report["response_code"] == HTTPStatus.OK:
report_results_rc = report["results"]["response_code"]
if report_results_rc == 1:
return report

# Exponential backoff.
sleep_interval *= attempt
self.logger.warning(
"Attempt {0}/{1} (retrying in {2} s), complete result not yet "
"available: {3}".format(attempt, max_attempts, sleep_interval, report)
)
time.sleep(sleep_interval)
@staticmethod
def get_positives(report: Dict) -> int:
return report['data']['attributes']['last_analysis_stats']['malicious']

self.logger.error(
'Maximum number of {0} attempts reached for "{1}"'.format(
attempt, sha256_hash
)
)
raise Exception("Maximum number of attempts reached")
def get_report_or_none(self, sha256_hash: str) -> Optional[dict]:
try:
report = self.vt_session.get_json(f'/files/{sha256_hash}')
return report
except vt.error.APIError:
return None

def scan_apk_file(self, apk_file_path: str) -> dict:
self.logger.info('Scanning file "{0}"'.format(apk_file_path))

report = self.get_vt_session().get_file_report(sha256sum(apk_file_path))
if "error" in report:
self.logger.error(
'Error while retrieving scan for file "{0}": {1}'.format(
apk_file_path, report
)
)
raise Exception(
'Error while retrieving scan for file "{0}"'.format(apk_file_path)
)

rc = report["results"]["response_code"]

if rc == 0:
# The requested resource is not among the finished, queued or pending scans.
# The apk file needs to be uploaded to Virus Total.
scan_report = self.get_vt_session().scan_file(apk_file_path)
if scan_report["response_code"] != HTTPStatus.OK:
self.logger.error(
'Error while uploading file "{0}": {1}'.format(
apk_file_path, scan_report
)
)
raise Exception(
'Error while uploading file "{0}"'.format(apk_file_path)
)

report = self.get_report(scan_report["results"]["sha256"])

elif rc != 1:
# response_code is 1 when Virus Total has a complete result.
err_msg = report["results"]["verbose_msg"]
self.logger.error(
'Error while retrieving scan for file "{0}": {1}'.format(
apk_file_path, err_msg
)
)
raise Exception(
'Error while retrieving scan for file "{0}"'.format(apk_file_path)
)

sha256_hash = sha256sum(apk_file_path)
report = self.get_report_or_none(sha256_hash)
if report is not None:
return report

with open(apk_file_path, "rb") as f:
self.logger.info(f"Uploading '{apk_file_path}' to VirusTotal...")
analysis = self.vt_session.scan_file(f, wait_for_completion=True)
assert analysis.status == 'completed'

report = self.get_report_or_none(sha256_hash)
if report is None:
raise Exception('Error while retrieving scan for file "{0}"'.format(apk_file_path))
return report

def obfuscate(self, obfuscation_info: Obfuscation):
Expand All @@ -112,29 +60,24 @@ def obfuscate(self, obfuscation_info: Obfuscation):
"obfuscator?"
)

if not obfuscation_info.virus_total_api_key:
if obfuscation_info.virus_total_api_key is None:
raise ValueError(
"A valid API key has to be provided in order to submit the "
"obfuscated application to Virus Total"
)

self.vt_sessions = iter(
cycle(
VirusTotalPublicApi(key)
for key in obfuscation_info.virus_total_api_key
)
)
self.vt_session = vt.Client(obfuscation_info.virus_total_api_key)

original_report = self.scan_apk_file(obfuscation_info.apk_path)
self.logger.info(
"Original apk scan result ({0} positives): {1}".format(
original_report["results"]["positives"], pformat(original_report)
self.get_positives(original_report), pformat(original_report)
)
)
obfuscated_report = self.scan_apk_file(obfuscation_info.obfuscated_apk_path)
self.logger.info(
"Obfuscated apk scan result ({0} positives): {1}".format(
obfuscated_report["results"]["positives"],
self.get_positives(obfuscated_report),
pformat(obfuscated_report),
)
)
Expand Down Expand Up @@ -181,4 +124,5 @@ def obfuscate(self, obfuscation_info: Obfuscation):
raise

finally:
self.vt_session.close()
obfuscation_info.used_obfuscators.append(self.__class__.__name__)
4 changes: 2 additions & 2 deletions src/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pycryptodome==3.9.9
pytest-cov==2.10.1
tqdm==4.54.1
virustotal-api==1.1.11
Yapsy==1.12.2
vt-py==0.6.1
Yapsy==1.12.2

0 comments on commit d0f9e2b

Please sign in to comment.