mirror of
https://gitlab.com/fdroid/fdroidserver.git
synced 2024-10-04 02:00:11 +02:00
[scanner] replace apkanalyzer by dexdump
This commit is contained in:
parent
23d9f0ed50
commit
9c8e128064
@ -122,7 +122,7 @@ ubuntu_bionic_pip:
|
|||||||
image: ubuntu:bionic
|
image: ubuntu:bionic
|
||||||
<<: *apt-template
|
<<: *apt-template
|
||||||
script:
|
script:
|
||||||
- apt-get install git default-jdk-headless python3-pip python3-venv rsync zipalign libarchive13
|
- apt-get install git default-jdk-headless python3-pip python3-venv rsync zipalign libarchive13 dexdump
|
||||||
- rm -rf env
|
- rm -rf env
|
||||||
- pyvenv env
|
- pyvenv env
|
||||||
- . env/bin/activate
|
- . env/bin/activate
|
||||||
|
@ -22,8 +22,10 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
import zipfile
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
import logging
|
import logging
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
@ -42,19 +44,13 @@ MAVEN_URL_REGEX = re.compile(r"""\smaven\s*{.*?(?:setUrl|url)\s*=?\s*(?:uri)?\(?
|
|||||||
re.DOTALL)
|
re.DOTALL)
|
||||||
|
|
||||||
CODE_SIGNATURES = {
|
CODE_SIGNATURES = {
|
||||||
# The `apkanalyzer dex packages` output looks like this:
|
exp: re.compile(r'.*' + exp, re.IGNORECASE) for exp in [
|
||||||
# M d 1 1 93 <packagename> <other stuff>
|
r'com/google/firebase',
|
||||||
# The first column has P/C/M/F for package, class, method or field
|
r'com/google/android/gms',
|
||||||
# The second column has x/k/r/d for removed, kept, referenced and defined.
|
r'com/google/android/play/core',
|
||||||
# We already filter for defined only in the apkanalyzer call. 'r' will be
|
r'com/google/tagmanager',
|
||||||
# for things referenced but not distributed in the apk.
|
r'com/google/analytics',
|
||||||
exp: re.compile(r'.[\s]*d[\s]*[0-9]*[\s]*[0-9*][\s]*[0-9]*[\s]*' + exp, re.IGNORECASE) for exp in [
|
r'com/android/billing',
|
||||||
r'(com\.google\.firebase[^\s]*)',
|
|
||||||
r'(com\.google\.android\.gms[^\s]*)',
|
|
||||||
r'(com\.google\.android\.play\.core[^\s]*)',
|
|
||||||
r'(com\.google\.tagmanager[^\s]*)',
|
|
||||||
r'(com\.google\.analytics[^\s]*)',
|
|
||||||
r'(com\.android\.billing[^\s]*)',
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,26 +102,34 @@ def get_gradle_compile_commands(build):
|
|||||||
return [re.compile(r'\s*' + c, re.IGNORECASE) for c in commands]
|
return [re.compile(r'\s*' + c, re.IGNORECASE) for c in commands]
|
||||||
|
|
||||||
|
|
||||||
def scan_binary(apkfile):
|
def get_embedded_classes(apkfile):
|
||||||
"""Scan output of apkanalyzer for known non-free classes.
|
|
||||||
|
|
||||||
apkanalyzer produces useful output when it can run, but it does
|
|
||||||
not support all recent JDK versions, and also some DEX versions,
|
|
||||||
so this cannot count on it to always produce useful output or even
|
|
||||||
to run without exiting with an error.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logging.info(_('Scanning APK with apkanalyzer for known non-free classes.'))
|
Get the list of Java classes embedded into all DEX files.
|
||||||
result = common.SdkToolsPopen(["apkanalyzer", "dex", "packages", "--defined-only", apkfile], output=False)
|
|
||||||
if result.returncode != 0:
|
:return: set of Java classes names as string
|
||||||
logging.warning(_('scanner not cleanly run apkanalyzer: %s') % result.output)
|
"""
|
||||||
|
class_regex = re.compile(r'classes.*\.dex')
|
||||||
|
with TemporaryDirectory() as tmp_dir:
|
||||||
|
with zipfile.ZipFile(apkfile, 'r') as apk_zip:
|
||||||
|
class_infos = [info for info in apk_zip.infolist() if class_regex.search(info.filename)]
|
||||||
|
for info in class_infos:
|
||||||
|
apk_zip.extract(info, tmp_dir)
|
||||||
|
dexes = ['{}/{}'.format(tmp_dir, dex.filename) for dex in class_infos]
|
||||||
|
run = common.SdkToolsPopen(["dexdump"] + dexes)
|
||||||
|
classes = set(re.findall(r'[A-Z]+((?:\w+\/)+\w+)', run.output))
|
||||||
|
return classes
|
||||||
|
|
||||||
|
|
||||||
|
def scan_binary(apkfile):
|
||||||
|
"""Scan output of dexdump for known non-free classes."""
|
||||||
|
logging.info(_('Scanning APK with dexdump for known non-free classes.'))
|
||||||
|
result = get_embedded_classes(apkfile)
|
||||||
problems = 0
|
problems = 0
|
||||||
for suspect, regexp in CODE_SIGNATURES.items():
|
for classname in result:
|
||||||
matches = regexp.findall(result.output)
|
for suspect, regexp in CODE_SIGNATURES.items():
|
||||||
if matches:
|
if regexp.match(classname):
|
||||||
for m in set(matches):
|
logging.debug("Found class '%s'" % classname)
|
||||||
logging.debug("Found class '%s'" % m)
|
problems += 1
|
||||||
problems += 1
|
|
||||||
if problems:
|
if problems:
|
||||||
logging.critical("Found problems in %s" % apkfile)
|
logging.critical("Found problems in %s" % apkfile)
|
||||||
return problems
|
return problems
|
||||||
|
@ -5,6 +5,7 @@ import inspect
|
|||||||
import logging
|
import logging
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -202,6 +203,28 @@ class ScannerTest(unittest.TestCase):
|
|||||||
self.assertTrue(f in files['infos'],
|
self.assertTrue(f in files['infos'],
|
||||||
f + ' should be removed with an info message')
|
f + ' should be removed with an info message')
|
||||||
|
|
||||||
|
def test_scan_binary(self):
|
||||||
|
config = dict()
|
||||||
|
fdroidserver.common.fill_config_defaults(config)
|
||||||
|
fdroidserver.common.config = config
|
||||||
|
fdroidserver.common.options = mock.Mock()
|
||||||
|
fdroidserver.common.options.verbose = False
|
||||||
|
|
||||||
|
apkfile = os.path.join(self.basedir, 'no_targetsdk_minsdk1_unsigned.apk')
|
||||||
|
self.assertEqual(
|
||||||
|
0,
|
||||||
|
fdroidserver.scanner.scan_binary(apkfile),
|
||||||
|
'Found false positives in binary',
|
||||||
|
)
|
||||||
|
fdroidserver.scanner.CODE_SIGNATURES["java/lang/Object"] = re.compile(
|
||||||
|
r'.*java/lang/Object', re.IGNORECASE | re.UNICODE
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
1,
|
||||||
|
fdroidserver.scanner.scan_binary(apkfile),
|
||||||
|
'Did not find bad code signature in binary',
|
||||||
|
)
|
||||||
|
|
||||||
def test_build_local_scanner(self):
|
def test_build_local_scanner(self):
|
||||||
"""`fdroid build` calls scanner functions, test them here"""
|
"""`fdroid build` calls scanner functions, test them here"""
|
||||||
testdir = tempfile.mkdtemp(
|
testdir = tempfile.mkdtemp(
|
||||||
|
Loading…
Reference in New Issue
Block a user