James Pope

46 papers B 6C 6Misc 1Journal 11Unranked 21
YearRankTypeTitle / Venue / Authors
2026 conf
ICISSP (2)
Pratyush Singh, Yuxiang Huang, Haoxiang Li, George Oikonomou, James Pope
2026 conf
BIOSTEC (4)
Thrisha Rajkumar, Sarah Koerner, Anika Pinto, Regan Shakya, James Pope, Maria J. Galvez Trigo, Ali Alnuaimi, Michael Loizou, Kenton O'Hara, Praveen Kumar
2025 conf
ICAART (2)
William Dennis, James Pope
2025 conf
ICAART (3)
Yang Zhang, James Pope
2025 conf
ISC2
Hakan Erdol, Robert Klar, Vangelis Angelakis, James Pope, Robert J. Piechocki, Theo Tryfonas, George Oikonomou
2025 conf
ICAART (3)
Hongqiao Wang, James Pope
2024 J jnl
Inf.
Habib Taha Kose, José L. Núñez-Yáñez, Robert J. Piechocki, James Pope
2024 C conf
ICPRAM
Yi Sheng Heng, James Pope
2024 C conf
ICPRAM
Hope Hodges, J. A. (Jim) Connell, Carolyn Garrity, James Pope
2024 conf
ICAART (3)
Naman Agarwal, James Pope
2024 conf
VISIGRAPP (3): VISAPP
Nishitha Prakash, James Pope
2024 J jnl
CoRR
James Pope, Md Hassanuzzaman, Mingmar Sherpa, Omar Emara, Ayush Joshi, Nirmala Adhikari
2024 conf
ICAART (3)
Yuchen Guo, James Pope
2023 conf
ICC
Chrysanthi Paschou, F. Raimondo M. Gugala, Dave McEwan, James Pope, George C. Oikonomou
2023 J jnl
CoRR
Vijay Kumar, Sam Gunner, Theodoros Spyridopoulos, Antonis Vafeas, James Pope, Poonam Yadav, George Oikonomou, Theo Tryfonas
2023 B conf
EWSN
Ufuk Erol, Francesco Raimondo, James Pope, Samuel Gunner, Vijay Kumar, Ioannis Mavromatis, Pietro Edoardo Carnelli, Theodoros Spyridopoulos, Aftab Khan, George Oikonomou
2023 conf
CCNC
Ioannis Mavromatis, Adrián Sánchez-Mompó, Francesco Raimondo, James Pope, Marcello Bullo, Ingram Weeks, Vijay Kumar, Pietro Edoardo Carnelli, George Oikonomou, Theodoros Spyridopoulos, Aftab Khan
2023 C conf
HealthCom
James Pope, Catherine Morgan, Alessandro Masullo, Ian Craddock, Alan L. Whone
2022 J jnl
CoRR
Yu Qiao, James Pope
2022 C conf
ICPRAM
Channing Donaldson, James Pope
2022 B conf
EWSN
Francesco Raimondo, Ufuk Erol, Samuel Gunner, James Pope, Robert Zakrzewski, Mike Faulks, Ryan McConville, Thomas Pasquier, Robert J. Piechocki, George Oikonomou
2022 J jnl
CoRR
Ioannis Mavromatis, Adrián Sánchez-Mompó, Francesco Raimondo, James Pope, Marcello Bullo, Ingram Weeks, Vijay Kumar, Pietro Edoardo Carnelli, George Oikonomou, Theodoros Spyridopoulos, Aftab Khan
2022 J jnl
CoRR
James Pope, Jinyuan Liang, Vijay Kumar, Francesco Raimondo, Xinyi Sun, Ryan McConville, Thomas Pasquier, Robert J. Piechocki, George Oikonomou, Bo Luo, Dan Howarth, Ioannis Mavromatis, Adrián Sánchez-Mompó, Pietro Edoardo Carnelli, Theodoros Spyridopoulos, Aftab Khan
2021 Misc conf
SenSys
James Pope, Francesco Raimondo, Vijay Kumar, Ryan McConville, Robert J. Piechocki, George Oikonomou, Thomas Pasquier, Bo Luo, Dan Howarth, Ioannis Mavromatis, Pietro Edoardo Carnelli, Adrián Sánchez-Mompó, Theodoros Spyridopoulos, Aftab Khan
2021 C conf
ICPRAM
James Pope, Mark G. Terwilliger
2021 J jnl
Future Gener. Comput. Syst.
Ryan McConville, Gareth Archer, Ian Craddock, Michal Kozlowski, Robert J. Piechocki, James Pope, Raúl Santos-Rodríguez
2020 conf
AIPR
James Pope, Mark G. Terwilliger, J. A. (Jim) Connell, Gabriel Talley, Nicholas Blozik, David Taylor
2020 C conf
ICPRAM
James Pope, Daniel Powers, J. A. (Jim) Connell, Milad Jasemi, David Taylor, Xenofon Fafoutis
2019 B conf
EWSN
Atis Elsts, James Pope, Xenofon Fafoutis, Robert J. Piechocki, George Oikonomou
2018 J jnl
IEEE Access
Christopher Beach, Sammy Krachunov, James Pope, Xenofon Fafoutis, Robert J. Piechocki, Ian Craddock, Alexander J. Casson
2018 conf
WCNC Workshops
James Pope, Antonis Vafeas, Atis Elsts, George Oikonomou, Robert J. Piechocki, Ian Craddock
2018 B conf
SMARTCOMP
Antonis Vafeas, Atis Elsts, James Pope, Xenofon Fafoutis, George Oikonomou, Robert J. Piechocki, Ian Craddock
2018 conf
WF-IoT
Xenofon Fafoutis, Letizia Marchegiani, Atis Elsts, James Pope, Robert J. Piechocki, Ian Craddock
2018 J jnl
CoRR
Ryan McConville, Gareth Archer, Ian Craddock, Herman J. ter Horst, Robert J. Piechocki, James Pope, Raúl Santos-Rodriguez
2018 conf
WF-IoT
Ryan McConville, Dallan Byrne, Ian Craddock, Robert J. Piechocki, James Pope, Raúl Santos-Rodríguez
2017 J jnl
EAI Endorsed Trans. Pervasive Health Technol.
Xenofon Fafoutis, Antonis Vafeas, Balazs Janko, R. Simon Sherratt, James Pope, Atis Elsts, Evangelos Mellios, Geoffrey S. Hilton, George Oikonomou, Robert J. Piechocki, Ian Craddock
2017 conf
GIoTS
Sammy Krachunov, Christopher Beach, Alexander J. Casson, James Pope, Xenofon Fafoutis, Robert J. Piechocki, Ian Craddock
2017 conf
LCN Workshops
James Pope, Ryan McConville, Michal Kozlowski, Xenofon Fafoutis, Raúl Santos-Rodríguez, Robert J. Piechocki, Ian Craddock
2017 B conf
DCOSS
Atis Elsts, Xenofon Fafoutis, James Pope, George C. Oikonomou, Robert J. Piechocki, Ian Craddock
2016 conf
PE-WASUN@MSWiM
James Pope, Robert Simon
2015 B conf
LCN
James Pope, Robert Simon
2015 conf
LCN Workshops
James Pope, Patrick Orsinger, Matthew D. Fisher
2013 conf
LCN Workshops
James Pope, Robert Simon
2013 J jnl
J. Comput. Inf. Technol.
James Pope, Robert Simon
2013 conf
ITI
James Pope, Robert Simon
2007
James Pope
redb/extractors/apk_extractors/apk_inconsistency_tests.py
← Index redb/extractors/apk_extractors/apk_inconsistency_tests.py python
import inspect
import re
from datetime import datetime, timezone
from typing import Any

from redb.extractors.enum import Tag
from redb.extractors.apk_extractor import APKExtractor
from redb.models.dataclasses import APKInconsistencyTests

# Emulator detection indicator strings
EMULATOR_INDICATORS = {
    "generic", "sdk", "google_sdk", "Emulator",
    "goldfish", "ranchu", "Andy", "Genymotion",
    "BlueStacks", "nox", "ttVM_Hdragon",
}

# Root detection indicator strings
ROOT_INDICATORS = {
    "/system/app/Superuser.apk",
    "/system/xbin/su",
    "/system/bin/su",
    "com.noshufou.android.su",
    "com.thirdparty.superuser",
    "eu.chainfire.supersu",
    "com.koushikdutta.superuser",
    "com.topjohnwu.magisk",
}

# Standard DEX filename pattern
STANDARD_DEX_PATTERN = re.compile(r"^classes\d*\.dex$")


class APKInconsistencyTestsExtractor(APKExtractor):

    def __init__(
        self, filepath, log, exporters=None, index_prefix=None,
        known_benign=False, known_malicious=False,
        apk=None,
    ):
        super().__init__(
            filepath, log, exporters, index_prefix,
            known_benign, known_malicious, apk,
        )
        self.test_results = None
        self.log.debug(inspect.currentframe().f_code.co_name)

    def tag(self):
        return Tag.APK_INCONSISTENCY_TESTS.value

    def _test_zip_bomb(self):
        """Check if any ZIP entry has compression ratio > 100:1."""
        try:
            zf = self._get_zip_file()
            if not zf:
                return None
            with zf:
                for info in zf.infolist():
                    if info.compress_size > 0:
                        ratio = info.file_size / info.compress_size
                        if ratio > 100:
                            return True
            return False
        except Exception as e:
            self.log.warning(f"Error in zip bomb test: {e}")
            return None

    def _test_zip_duplicate_entries(self):
        """Check for duplicate filenames in ZIP directory."""
        try:
            zf = self._get_zip_file()
            if not zf:
                return None
            with zf:
                names = [info.filename for info in zf.infolist()]
                return len(names) != len(set(names))
        except Exception as e:
            self.log.warning(f"Error in duplicate entries test: {e}")
            return None

    def _test_zip_path_traversal(self):
        """Check for path traversal (../) in ZIP entry names."""
        try:
            for f in self._list_files():
                if ".." in f or f.startswith("/"):
                    return True
            return False
        except Exception as e:
            self.log.warning(f"Error in path traversal test: {e}")
            return None

    def _test_zip_suspicious_timestamps(self):
        """Check for timestamps at epoch (1980) or in the future."""
        try:
            zf = self._get_zip_file()
            if not zf:
                return None
            now = datetime.now()
            with zf:
                for info in zf.infolist():
                    try:
                        dt = datetime(*info.date_time)
                        if dt.year <= 1980 or dt > now:
                            return True
                    except (ValueError, TypeError):
                        continue
            return False
        except Exception as e:
            self.log.warning(f"Error in suspicious timestamps test: {e}")
            return None

    def _test_hidden_dex_files(self):
        """Check for DEX files not matching classes*.dex pattern."""
        try:
            for f in self._list_files():
                if f.endswith(".dex"):
                    basename = f.split("/")[-1]
                    if not STANDARD_DEX_PATTERN.match(basename):
                        return True
            return False
        except Exception as e:
            self.log.warning(f"Error in hidden DEX files test: {e}")
            return None

    def _test_manifest_component_mismatch(self):
        """Check for declared components that don't exist in DEX."""
        try:
            if not self._is_valid_apk():
                return None

            # Get all class names from DEX
            dex_classes = set()
            try:
                from androguard.core.dex import DEX
                for dex_data in (self.apk.get_all_dex() or []):
                    try:
                        d = DEX(dex_data)
                        for cls in d.get_classes():
                            name = cls.get_name()
                            if name:
                                # Convert "Lcom/example/Foo;" to "com.example.Foo"
                                dex_classes.add(
                                    name[1:-1].replace("/", ".") if name.startswith("L") else name
                                )
                    except Exception:
                        continue
            except Exception:
                return None

            if not dex_classes:
                return None

            # Check manifest components against DEX classes
            components = []
            try:
                components.extend(self.apk.get_activities() or [])
                components.extend(self.apk.get_services() or [])
                components.extend(self.apk.get_receivers() or [])
                components.extend(self.apk.get_providers() or [])
            except Exception:
                return None

            for comp in components:
                if comp and comp not in dex_classes:
                    # Component might use a shorthand; check with package prefix
                    package = self.apk.get_package() or ""
                    full_name = package + comp if comp.startswith(".") else comp
                    if full_name not in dex_classes:
                        return True

            return False
        except Exception as e:
            self.log.warning(f"Error in manifest component mismatch test: {e}")
            return None

    def _test_debuggable_release(self):
        """Check android:debuggable=true combined with a release signature."""
        try:
            if not self._is_valid_apk():
                return None

            is_debuggable = self.apk.get_attribute_value(
                "application", "debuggable"
            ) == "true"

            if not is_debuggable:
                return False

            # Check if it has a signing certificate (release builds have certs)
            try:
                certs = self.apk.get_certificates()
                if certs and len(certs) > 0:
                    return True
            except Exception:
                pass

            return False
        except Exception as e:
            self.log.warning(f"Error in debuggable release test: {e}")
            return None

    def _get_dex_strings(self):
        """Get all string constants from DEX files."""
        all_strings = set()
        try:
            from androguard.core.dex import DEX
            for dex_data in (self.apk.get_all_dex() or []):
                try:
                    d = DEX(dex_data)
                    for s in d.get_strings():
                        if s:
                            all_strings.add(s)
                except Exception:
                    continue
        except Exception:
            pass
        return all_strings

    def _test_emulator_detection_strings(self):
        """Check for emulator detection patterns in DEX strings."""
        try:
            if not self._is_valid_apk():
                return None

            dex_strings = self._get_dex_strings()
            for indicator in EMULATOR_INDICATORS:
                for s in dex_strings:
                    if indicator in s:
                        return True
            return False
        except Exception as e:
            self.log.warning(f"Error in emulator detection test: {e}")
            return None

    def _test_debugger_detection(self):
        """Check for debugger detection API calls in DEX."""
        try:
            if not self._is_valid_apk():
                return None

            dex_strings = self._get_dex_strings()
            debugger_patterns = {
                "isDebuggerConnected",
                "waitingForDebugger",
                "Debug.isDebuggerConnected",
            }
            for pattern in debugger_patterns:
                for s in dex_strings:
                    if pattern in s:
                        return True
            return False
        except Exception as e:
            self.log.warning(f"Error in debugger detection test: {e}")
            return None

    def _test_root_detection(self):
        """Check for root detection patterns in DEX."""
        try:
            if not self._is_valid_apk():
                return None

            dex_strings = self._get_dex_strings()
            for indicator in ROOT_INDICATORS:
                for s in dex_strings:
                    if indicator in s:
                        return True
            return False
        except Exception as e:
            self.log.warning(f"Error in root detection test: {e}")
            return None

    def extract(self):
        if not self._is_valid_apk():
            self.log.error(f"Invalid APK for {self.hash.sha256}")
            return None

        self.test_results = APKInconsistencyTests(
            test_zip_bomb=self._test_zip_bomb(),
            test_zip_duplicate_entries=self._test_zip_duplicate_entries(),
            test_zip_path_traversal=self._test_zip_path_traversal(),
            test_zip_suspicious_timestamps=self._test_zip_suspicious_timestamps(),
            test_hidden_dex_files=self._test_hidden_dex_files(),
            test_manifest_component_mismatch=self._test_manifest_component_mismatch(),
            test_debuggable_release=self._test_debuggable_release(),
            test_emulator_detection_strings=self._test_emulator_detection_strings(),
            test_debugger_detection=self._test_debugger_detection(),
            test_root_detection=self._test_root_detection(),
        )
        return self.test_results

    def _bool_to_nullable(self, val):
        """Convert bool/None to ClickHouse Nullable(UInt8)."""
        if val is None:
            return None
        return int(val)

    def prepare_export_data(self, exporter_type: str) -> Any:
        if exporter_type == "ClickHouseExporter":
            if not self.test_results:
                return None

            current_time = datetime.now(timezone.utc)
            t = self.test_results

            data = [[
                self.sha256,
                self._bool_to_nullable(t.test_zip_bomb),
                self._bool_to_nullable(t.test_zip_duplicate_entries),
                self._bool_to_nullable(t.test_zip_path_traversal),
                self._bool_to_nullable(t.test_zip_suspicious_timestamps),
                self._bool_to_nullable(t.test_hidden_dex_files),
                self._bool_to_nullable(t.test_manifest_component_mismatch),
                self._bool_to_nullable(t.test_debuggable_release),
                self._bool_to_nullable(t.test_emulator_detection_strings),
                self._bool_to_nullable(t.test_debugger_detection),
                self._bool_to_nullable(t.test_root_detection),
                current_time,
            ]]

            column_names = [
                'sha256',
                'test_zip_bomb', 'test_zip_duplicate_entries',
                'test_zip_path_traversal', 'test_zip_suspicious_timestamps',
                'test_hidden_dex_files', 'test_manifest_component_mismatch',
                'test_debuggable_release', 'test_emulator_detection_strings',
                'test_debugger_detection', 'test_root_detection',
                'analysis_date',
            ]

            column_type_names = [
                'FixedString(64)',
                'Nullable(UInt8)', 'Nullable(UInt8)',
                'Nullable(UInt8)', 'Nullable(UInt8)',
                'Nullable(UInt8)', 'Nullable(UInt8)',
                'Nullable(UInt8)', 'Nullable(UInt8)',
                'Nullable(UInt8)', 'Nullable(UInt8)',
                "DateTime64(3, 'UTC')",
            ]

            return (data, column_names, column_type_names)

    def get_clickhouse_table(self) -> str:
        return "redb_apk_inconsistency_tests"