Ian J. Hayes

150 papers A* 1A 1B 22C 20Journal 68Unranked 33
YearRankTypeTitle / Venue / Authors
2025 J jnl
CoRR
Ian J. Hayes, Larissa A. Meinicke, Cliff B. Jones
2024 J jnl
CoRR
Larissa A. Meinicke, Ian J. Hayes, Cliff B. Jones
2024 J jnl
CoRR
Ian J. Hayes, Cliff B. Jones, Larissa A. Meinicke
2024 J jnl
CoRR
Robert J. Colvin, Ian J. Hayes, Scott Heiner, Peter Höfner, Larissa Meinicke, Roger C. Su
2024 ch.
The Practice of Formal Methods (I)
Robert J. Colvin, Ian J. Hayes, Scott Heiner, Peter Höfner, Larissa Meinicke, Roger C. Su
2024 ch.
The Practice of Formal Methods (II)
Larissa A. Meinicke, Ian J. Hayes
2024 J jnl
CoRR
Larissa A. Meinicke, Ian J. Hayes
2024 C conf
RAMiCS
Ian J. Hayes, Larissa A. Meinicke, Nasos Evangelou-Oost
2024 J jnl
CoRR
Ian J. Hayes, Larissa A. Meinicke, Nasos Evangelou-Oost
2023 C conf
RAMiCS
Nasos Evangelou-Oost, Callum Bannister, Ian J. Hayes
2023 conf
FormaliSE
Mark Utting, Brae J. Webb, Ian J. Hayes
2023 conf
Theories of Programming and Formal Methods
Ian J. Hayes, Cliff B. Jones, Larissa A. Meinicke
2023 C conf
ICFEM
Nasos Evangelou-Oost, Larissa Meinicke, Callum Bannister, Ian J. Hayes
2023 J jnl
CoRR
Nasos Evangelou-Oost, Callum Bannister, Larissa Meinicke, Ian J. Hayes
2023 conf
FormaliSE
Larissa A. Meinicke, Ian J. Hayes
2023 C conf
ICFEM
Ian J. Hayes, Mark Utting, Brae J. Webb
2023 B conf
CPP
Brae J. Webb, Ian J. Hayes, Mark Utting
2022 J jnl
CoRR
Nasos Evangelou-Oost, Callum Bannister, Ian J. Hayes
2022 J jnl
CoRR
Mark Utting, Brae J. Webb, Ian J. Hayes
2022 J jnl
CoRR
Brae J. Webb, Ian J. Hayes, Mark Utting
2021 B conf
ATVA
Brae J. Webb, Mark Utting, Ian J. Hayes
2021 J jnl
CoRR
Brae J. Webb, Mark Utting, Ian J. Hayes
2021 J jnl
Log. Methods Comput. Sci.
Brijesh Dongol, Ian J. Hayes, Georg Struth
2021 J jnl
CoRR
Ian J. Hayes, Larissa A. Meinicke, Patrick A. Meiring
2021 ch.
Theories of Programming
Ian J. Hayes, Steve King
2020 J jnl
Comput. J.
Alan Burns, Ian J. Hayes, Cliff B. Jones
2019 J jnl
Formal Aspects Comput.
Ian J. Hayes, Larissa A. Meinicke, Kirsten Winter, Robert J. Colvin
2019 B conf
MPC
Brijesh Dongol, Ian J. Hayes, Larissa Meinicke, Georg Struth
2019 conf
UTP
Ian J. Hayes, Larissa A. Meinicke
2019 J jnl
CoRR
Larissa A. Meinicke, Ian J. Hayes
2018 B conf
FM
Ian J. Hayes, Larissa A. Meinicke
2018 J jnl
CoRR
Ian J. Hayes, Larissa A. Meinicke
2018 C conf
ICFEM
Ian J. Hayes
2018 conf
Refine@FM
Ian J. Hayes
2018 C conf
ICFEM
Xi Wu, Yi Lu, Patrick A. Meiring, Ian J. Hayes, Larissa A. Meinicke
2017 conf
SETSS
Ian J. Hayes, Cliff B. Jones
2017 J jnl
CoRR
Ian J. Hayes, Larissa A. Meinicke, Kirsten Winter, Robert J. Colvin
2017 B conf
APLAS
Ian J. Hayes, Xi Wu, Larissa A. Meinicke
2017 J jnl
Formal Aspects Comput.
Robert J. Colvin, Ian J. Hayes, Larissa A. Meinicke
2017 J jnl
Arch. Formal Proofs
Brijesh Dongol, Victor B. F. Gomes, Ian J. Hayes, Georg Struth
2017 J jnl
CoRR
Brijesh Dongol, Ian J. Hayes, Georg Struth
2016 B conf
FM
Ian J. Hayes, Robert J. Colvin, Larissa A. Meinicke, Kirsten Winter, Andrius Velykis
2016 J jnl
CoRR
Ian J. Hayes, Robert Colvin, Larissa Meinicke, Kirsten Winter, Andrius Velykis
2016 J jnl
Arch. Formal Proofs
Julian Fell, Ian J. Hayes, Andrius Velykis
2016 J jnl
ACM Trans. Comput. Log.
Brijesh Dongol, Ian J. Hayes, Georg Struth
2016 J jnl
CoRR
Robert J. Colvin, Ian J. Hayes, Larissa A. Meinicke
2016 J jnl
CoRR
Ian J. Hayes
2016 J jnl
Formal Aspects Comput.
Ian J. Hayes
2016 J jnl
J. Log. Algebraic Methods Program.
Cliff B. Jones, Ian J. Hayes
2016 J jnl
CoRR
Cliff B. Jones, Ian J. Hayes
2015 J jnl
Formal Aspects Comput.
Cliff B. Jones, Ian J. Hayes, Robert J. Colvin
2014 J jnl
CoRR
Brijesh Dongol, Ian J. Hayes, Georg Struth
2014 J jnl
Sci. Comput. Program.
Brijesh Dongol, Ian J. Hayes, John Derrick
2014 B conf
FM
Ian J. Hayes, Larissa Meinicke
2014 J jnl
Formal Aspects Comput.
Brijesh Dongol, Ian J. Hayes, Peter J. Robinson
2013 J jnl
Comput. J.
Ian J. Hayes, Alan Burns, Brijesh Dongol, Cliff B. Jones
2013 J jnl
Sci. Comput. Program.
Brijesh Dongol, Ian J. Hayes
2013 J jnl
Sci. Comput. Program.
Ian J. Hayes, Steve Dunne, Larissa Meinicke
2013 C conf
ICFEM
Kirsten Winter, Chenyi Zhang, Ian J. Hayes, Nathan Keynes, Cristina Cifuentes, Lian Li
2013 conf
FTSCS
Ian J. Hayes
2013 B conf
VISSOFT
Daniel R. Bradley, Ian J. Hayes
2012 B conf
MPC
Brijesh Dongol, Ian J. Hayes
2012 J jnl
Electron. Commun. Eur. Assoc. Softw. Sci. Technol.
Brijesh Dongol, John Derrick, Ian J. Hayes
2012 C conf
ABZ
Ian J. Hayes, Robert Colvin
2012 B conf
IFM
Brijesh Dongol, Ian J. Hayes
2012 C conf
RAMiCS
Brijesh Dongol, Ian J. Hayes, Larissa Meinicke, Kim Solin
2011 J jnl
Sci. Comput. Program.
Robert Colvin, Ian J. Hayes
2011 J jnl
Electron. Commun. Eur. Assoc. Softw. Sci. Technol.
Brijesh Dongol, Ian J. Hayes
2011 conf
ICWE Workshops
Eban Escott, Paul A. Strooper, Paul King, Ian J. Hayes
2011 J jnl
J. Log. Algebraic Methods Program.
Robert Colvin, Ian J. Hayes
2010 J jnl
Real Time Syst.
Alan Burns, Ian J. Hayes
2010 B conf
MPC
Brijesh Dongol, Ian J. Hayes
2010 B conf
SEFM
Kirsten Winter, Ian J. Hayes, Robert Colvin
2010 C conf
ICTAC
Ian J. Hayes
2010 B conf
MPC
Ian J. Hayes, Steve Dunne, Larissa Meinicke
2009 B conf
IFM
Robert Colvin, Ian J. Hayes
2009 ch.
Methods, Models and Tools for Fault Tolerance
Ian J. Hayes
2009 conf
Australian Software Engineering Conference
Brijesh Dongol, Ian J. Hayes
2008 J jnl
Acta Informatica
Larissa Meinicke, Ian J. Hayes
2008 J jnl
Theory Pract. Log. Program.
Robert Colvin, Ian J. Hayes, Paul A. Strooper
2008 B conf
MPC
Larissa Meinicke, Ian J. Hayes
2008 conf
UTP
Steve Dunne, Ian J. Hayes, Andy Galloway
2008 conf
SERENE
Ian J. Hayes
2007 conf
Formal Methods and Hybrid Real-Time Systems
Cliff B. Jones, Ian J. Hayes, Michael A. Jackson
2007 J jnl
Sci. Comput. Program.
Ian J. Hayes
2006 J jnl
CoRR
Robert Colvin, Ian J. Hayes, Paul A. Strooper
2006 B conf
MPC
Larissa Meinicke, Ian J. Hayes
2006 C conf
ICFEM
Larissa Meinicke, Ian J. Hayes
2006 conf
UTP
Ian J. Hayes
2005 J jnl
Theor. Comput. Sci.
Karl Lermer, Colin J. Fidge, Ian J. Hayes
2005 B ed.
FM
John S. Fitzgerald, Ian J. Hayes, Andrzej Tarlecki
2005 conf
ACSC
Erica Glynn, Ian J. Hayes, Anthony MacDonald
2004 A* conf
ASE
Cameron Smith, Kirsten Winter, Ian J. Hayes, R. Geoff Dromey, Peter A. Lindsay, David A. Carrington
2004 ch.
Program Development in Computational Logic
Robert Colvin, Lindsay Groves, Ian J. Hayes, David Hemer, Ray Nickson, Paul A. Strooper
2004 conf
Australian Software Engineering Conference
Ian J. Hayes
2003 conf
FME
Ian J. Hayes, Michael A. Jackson, Cliff B. Jones
2003 conf
CATS
Karl Lermer, Colin J. Fidge, Ian J. Hayes
2003 J jnl
Formal Aspects Comput.
Karl Lermer, Colin J. Fidge, Ian J. Hayes
2003 C conf
ICFEM
Ian J. Hayes
2003 A conf
IPDPS
Sibylle Peuker, Ian J. Hayes
2002 J jnl
CoRR
Ian J. Hayes, Robert Colvin, David Hemer, Paul A. Strooper, Ray Nickson
2002 J jnl
Theory Pract. Log. Program.
Ian J. Hayes, Robert Colvin, David Hemer, Paul A. Strooper, Ray Nickson
2002 J jnl
Formal Aspects Comput.
Graeme Smith, Ian J. Hayes
2002 conf
CATS
David Hemer, Ian J. Hayes, Paul A. Strooper, Robert Colvin
2002 B conf
MPC
Ian J. Hayes
2002 J jnl
Sci. Comput. Program.
Ian J. Hayes
2002 C conf
LOPSTR
Robert Colvin, Ian J. Hayes, David Hemer, Paul A. Strooper
2002 C conf
APSEC
Jamie Shield, Ian J. Hayes
2002 conf
ICATPN
Ian J. Hayes
2002 C conf
ICFEM
Sibylle Peuker, Ian J. Hayes
2002 conf
ACSC
Robert Colvin, Ian J. Hayes, David Hemer, Paul A. Strooper
2001 J jnl
Acta Informatica
Ian J. Hayes, Mark Utting
2001 conf
TPHOLs
David Hemer, Ian J. Hayes, Paul A. Strooper
2001 J jnl
IEE Proc. Softw.
Ian J. Hayes, Colin J. Fidge, Karl Lermer
2001 conf
CATS
Jamie Shield, Ian J. Hayes, David A. Carrington
2000 conf
LOPSTR (LNCS 2042: Selected Papers)
Robert Colvin, Ian J. Hayes, Paul A. Strooper
2000 C conf
LOPSTR
Robert Colvin, Ian J. Hayes, Paul A. Strooper
2000 conf
FTRTFT
Ian J. Hayes
2000 B conf
MPC
Ian J. Hayes
2000 C conf
APSEC
Ian J. Hayes
2000 conf
ACSC
Robert Colvin, Ian J. Hayes, Paul A. Strooper
2000 B conf
IFM
Graeme Smith, Ian J. Hayes
1999 J jnl
IEE Proc. Softw.
Colin J. Fidge, Ian J. Hayes, Geoffrey Watson
1999 B conf
IFM
Graeme Smith, Ian J. Hayes
1998 J jnl
Formal Aspects Comput.
David A. Carrington, Ian J. Hayes, Ray Nickson, Geoffrey Watson, Jim Welsh
1998 B conf
MPC
Colin J. Fidge, Ian J. Hayes, Andrew P. Martin, Axel Wabenhorst
1998 conf
PROCOMET
Ian J. Hayes, Mark Utting
1998 C conf
ICFEM
Colin J. Fidge, Ian J. Hayes, Brendan P. Mahony
1998 J jnl
Formal Aspects Comput.
Ian J. Hayes
1997 J jnl
Sci. Comput. Program.
Ray Nickson, Ian J. Hayes
1996 conf
FME
Colin J. Fidge, Mark Utting, Peter Kearney, Ian J. Hayes
1996 C conf
LOPSTR
Ian J. Hayes, R. G. Nickson, Paul A. Strooper
1996 J jnl
Sci. Comput. Program.
Ian J. Hayes
1995 conf
ZUM
Peter Bancroft, Ian J. Hayes
1995 C conf
APSEC
Ian J. Hayes, Keijiro Araki, David J. Duke, Val E. Veraart
1995 J jnl
Formal Aspects Comput.
Ian J. Hayes, Jeff W. Sanders
1995 J jnl
Formal Aspects Comput.
Ian J. Hayes, Brendan P. Mahony
1994 J jnl
ACM SIGSOFT Softw. Eng. Notes
Ian J. Hayes, Cliff B. Jones, John E. Nicholls
1993 conf
SIGSOFT FSE
David A. Carrington, David J. Duke, Ian J. Hayes, Jim Welsh
1992 J jnl
IEEE Trans. Software Eng.
Brendan P. Mahony, Ian J. Hayes
1992 J jnl
Acta Informatica
Ian J. Hayes
1992 conf
Z User Workshop
Ian J. Hayes, Luke Wildman
1992 J jnl
Formal Aspects Comput.
Ian J. Hayes
1990 conf
Z User Workshop
Ian J. Hayes
1989 conf
Z User Workshop
Ian J. Hayes
1989 conf
PSTV
Ian J. Hayes, M. Mowbray, Gordon A. Rose
1987 J jnl
Commun. ACM
C. A. R. Hoare, Ian J. Hayes, Jifeng He, Carroll Morgan, A. W. Roscoe, Jeff W. Sanders, Ib Holm Sørensen, J. Michael Spivey, Bernard Sufrin
1986 J jnl
IEEE Trans. Software Eng.
Ian J. Hayes
1985 J jnl
IEEE Trans. Software Eng.
Ian J. Hayes
1978 J jnl
SIGARCH Comput. Archit. News
Ian J. Hayes
APK_FEATURES_PDD-Tech_Annex.md
← Index APK_FEATURES_PDD-Tech_Annex.md markdown
# APK Extractor — Technical Annex

**Companion to:** APK_FEATURES_PDD.md
**Audience:** Engineers implementing the APK extractors
**Date:** 2026-02-21

---

## Table of Contents

1. [File Structure](#1-file-structure)
2. [Dependencies Setup](#2-dependencies-setup)
3. [Base Class: APKExtractor](#3-base-class-apkextractor)
4. [Dataclass Definitions](#4-dataclass-definitions)
5. [Tag Enum Updates](#5-tag-enum-updates)
6. [Extractor Implementation Details](#6-extractor-implementation-details)
7. [Ingestor Integration](#7-ingestor-integration)
8. [Hashes Dataclass Update](#8-hashes-dataclass-update)
9. [ClickHouse Schema](#9-clickhouse-schema)
10. [Androguard API Reference](#10-androguard-api-reference)
11. [Known Packer/Protector Signatures](#11-known-packerprotector-signatures)
12. [Sensitive API Categorization Reference](#12-sensitive-api-categorization-reference)

---

## 1. File Structure

Create the following files:

```
redb/extractors/
├── apk_extractor.py                          # APKExtractor base class
├── apk_extractors/
│   ├── __init__.py
│   ├── apk_features.py                       # APKFeaturesExtractor
│   ├── apk_manifest.py                       # APKManifestExtractor
│   ├── apk_permissions.py                    # APKPermissionsExtractor
│   ├── apk_signature.py                      # APKSignatureExtractor
│   ├── apk_dex.py                            # APKDexExtractor
│   ├── apk_resources.py                      # APKResourceExtractor
│   ├── apk_native_libs.py                    # APKNativeLibExtractor
│   └── apk_inconsistency_tests.py            # APKInconsistencyTestsExtractor
```

Modified files:

```
redb/extractors/enum.py                       # Add APK tags
redb/models/dataclasses.py                    # Add APK dataclasses + permhash field in Hashes
redb/ingestor.py                              # Wire up APK dispatch
requirements.txt                              # Add androguard, permhash
```

---

## 2. Dependencies Setup

Add to `requirements.txt`:

```
androguard>=4.1
permhash
```

Install and verify:

```bash
pip install androguard permhash
python -c "from androguard.core.apk import APK; print('androguard OK')"
python -c "import permhash; print('permhash OK')"
```

---

## 3. Base Class: APKExtractor

**File:** `redb/extractors/apk_extractor.py`

Follow the exact pattern of `PEExtractor` (`redb/extractors/pe_extractor.py`) and `MachOExtractor` (`redb/extractors/macho_extractor.py`).

```python
import logging
from abc import ABCMeta, abstractmethod
import zipfile

from androguard.core.apk import APK

from redb.extractors.extractor import Extractor

logger = logging.getLogger(__name__)


@abstractmethod
class APKExtractor(Extractor, metaclass=ABCMeta):

    def __init__(
        self,
        filepath,
        log,
        exporters=None,
        index_prefix=None,
        elastic_index=None,
        known_benign=False,
        known_malicious=False,
        apk=None,
    ):
        super().__init__(
            filepath,
            log,
            exporters,
            index_prefix,
            elastic_index,
            known_benign,
            known_malicious
        )
        self.apk = apk if apk else self._generate_apk_object()

    def _generate_apk_object(self):
        """Parse APK using androguard."""
        apk = None
        try:
            apk = APK(self.filepath)
            if not apk.is_valid_APK():
                self.log.warning(
                    f"APK validation warning for {self.hash.sha256}"
                )
                # Still return the object — partial parsing may still work
        except Exception as e:
            self.log.error(
                f"Format error parsing APK {self.hash.sha256}: {e}"
            )
        return apk

    def _is_valid_apk(self):
        """Check if the APK object was parsed successfully."""
        return self.apk is not None

    def _get_zip_file(self):
        """Get a zipfile.ZipFile handle for direct archive inspection."""
        try:
            return zipfile.ZipFile(self.filepath, 'r')
        except (zipfile.BadZipFile, Exception) as e:
            self.log.error(f"Failed to open APK as ZIP: {e}")
            return None

    def _list_files(self):
        """List all files in the APK archive."""
        if not self._is_valid_apk():
            return []
        try:
            return self.apk.get_files()
        except Exception as e:
            self.log.error(f"Error listing APK files: {e}")
            return []
```

**Key design decisions:**
- The `apk=` parameter allows the ingestor to parse once and share across all APK extractors (same pattern as `pe=` in PEExtractor, `macho=` in MachOExtractor, `elf=` in ELFExtractor)
- `_get_zip_file()` provides direct ZIP access for extractors that need it (resources, native libs, inconsistency tests)
- The androguard `APK` object is the primary parsing interface; `zipfile` is secondary for archive-level inspection

---

## 4. Dataclass Definitions

**File:** `redb/models/dataclasses.py`

Add after the Mach-O dataclasses section. Follow the exact style of existing dataclasses (type annotations, `Optional` for nullable fields, `field(default_factory=list)` for list defaults).

```python
# ============================================================================
# APK Dataclasses
# ============================================================================

@dataclass
class APKFeatures:
    """Core APK metadata and properties."""
    package_name: str
    app_name: str
    version_code: int
    version_name: str
    min_sdk_version: Optional[int] = None
    target_sdk_version: Optional[int] = None
    compile_sdk_version: Optional[int] = None
    main_activity: Optional[str] = None
    is_debuggable: bool = False
    allow_backup: bool = True
    uses_cleartext_traffic: bool = False
    supported_abis: List[str] = field(default_factory=list)
    dex_count: int = 0
    total_dex_size: int = 0
    total_file_count: int = 0
    has_native_code: bool = False
    has_assets: bool = False
    uses_libraries: List[str] = field(default_factory=list)
    earliest_content_modification: Optional[str] = None  # ISO 8601
    latest_content_modification: Optional[str] = None    # ISO 8601
    contains_embedded_apk: bool = False


@dataclass
class APKManifestComponent:
    """Single Android component (activity, service, receiver, provider)."""
    component_type: str      # "activity", "service", "receiver", "provider"
    class_name: str
    is_exported: bool
    intent_actions: List[str] = field(default_factory=list)
    intent_categories: List[str] = field(default_factory=list)


@dataclass
class APKManifest:
    """Full AndroidManifest.xml analysis."""
    activity_count: int
    service_count: int
    receiver_count: int
    provider_count: int
    activities: List[str] = field(default_factory=list)
    services: List[str] = field(default_factory=list)
    receivers: List[str] = field(default_factory=list)
    providers: List[str] = field(default_factory=list)
    exported_components: List[str] = field(default_factory=list)
    intent_filters_by_action: List[str] = field(default_factory=list)
    intent_filters_by_category: List[str] = field(default_factory=list)
    uses_features: List[str] = field(default_factory=list)
    meta_data: Optional[Dict] = None
    manifest_xml: Optional[str] = None  # Full decompiled XML text


@dataclass
class APKPermission:
    """Single permission entry for ClickHouse export."""
    permission_name: str
    protection_level: str    # "normal", "dangerous", "signature", "signatureOrSystem", "unknown"
    is_custom: bool = False  # True if declared by the app itself


@dataclass
class APKPermissions:
    """APK permission summary."""
    total_permission_count: int
    dangerous_permission_count: int
    permissions: List[str] = field(default_factory=list)
    dangerous_permissions: List[str] = field(default_factory=list)
    custom_permissions: List[str] = field(default_factory=list)
    permission_details: List[APKPermission] = field(default_factory=list)
    permhash: Optional[str] = None


@dataclass
class APKCertificate:
    """X.509 certificate from APK signature."""
    subject: str
    issuer: str
    serial_number: str
    valid_from: str
    valid_to: str
    thumbprint_sha1: str
    thumbprint_sha256: str
    algorithm: Optional[str] = None
    key_size: Optional[int] = None
    is_self_signed: bool = False


@dataclass
class APKCodeSigningInfo:
    """Complete APK signing information."""
    _id: str  # sha256
    is_signed: bool
    signature_scheme_versions: List[int] = field(default_factory=list)  # [1], [1,2], [1,2,3], etc.
    number_of_certificates: int = 0
    x509_certificates: Optional[List[APKCertificate]] = None
    signer_subject: Optional[str] = None
    signer_issuer: Optional[str] = None


@dataclass
class APKDexFile:
    """Analysis of a single DEX file."""
    filename: str
    sha256: str
    class_count: int
    method_count: int
    string_count: int
    top_packages: Optional[List[Dict]] = None  # [{"package": str, "class_count": int}]
    api_usage: Optional[Dict[str, List[str]]] = None  # {"reflection": [...], "crypto": [...], ...}
    obfuscation_indicators: Optional[Dict] = None


@dataclass
class APKResource:
    """Embedded resource file entry."""
    path: str
    size: int
    sha256: str
    filetype_magika: str
    is_suspicious: bool = False  # True for executable types


@dataclass
class APKNativeLib:
    """Native .so library entry."""
    abi: str          # e.g., "arm64-v8a"
    filename: str     # e.g., "libfoo.so"
    size: int
    sha256: str
    is_known_packer: bool = False  # True if matches known packer lib name


@dataclass
class APKInconsistencyTests:
    """APK anomaly and anti-analysis detection results."""
    test_zip_bomb: Optional[bool] = None
    test_zip_duplicate_entries: Optional[bool] = None
    test_zip_path_traversal: Optional[bool] = None
    test_zip_suspicious_timestamps: Optional[bool] = None
    test_hidden_dex_files: Optional[bool] = None
    test_manifest_component_mismatch: Optional[bool] = None
    test_debuggable_release: Optional[bool] = None
    test_emulator_detection_strings: Optional[bool] = None
    test_debugger_detection: Optional[bool] = None
    test_root_detection: Optional[bool] = None
```

---

## 5. Tag Enum Updates

**File:** `redb/extractors/enum.py`

Add at the end of the `Tag` class, after the ELF section:

```python
    # APK
    APK_FEATURES = "apk_features"
    APK_MANIFEST = "apk_manifest"
    APK_PERMISSIONS = "apk_permissions"
    APK_SIGNATURE = "apk_signature"
    APK_DEX = "apk_dex"
    APK_RESOURCES = "apk_resources"
    APK_NATIVE_LIBS = "apk_native_libs"
    APK_INCONSISTENCY_TESTS = "apk_inconsistency_tests"
```

---

## 6. Extractor Implementation Details

### 6.1 APKFeaturesExtractor

**File:** `redb/extractors/apk_extractors/apk_features.py`

```python
import inspect
import zipfile
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 APKFeatures


class APKFeaturesExtractor(APKExtractor):

    def __init__(
        self, filepath, log, exporters=None, index_prefix=None,
        elastic_index=None, known_benign=False, known_malicious=False,
        apk=None,
    ):
        super().__init__(
            filepath, log, exporters, index_prefix,
            elastic_index, known_benign, known_malicious, apk,
        )
        self.apk_features = None
        self.elastic_index = self.index_prefix + "-apk_features"

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

    def _extract_zip_timestamps(self):
        """Extract earliest and latest content modification from ZIP entries."""
        # Use zipfile directly for this — androguard doesn't expose ZIP metadata
        earliest = None
        latest = None
        try:
            zf = self._get_zip_file()
            if zf:
                with zf:
                    for info in zf.infolist():
                        try:
                            # date_time is (year, month, day, hour, minute, second)
                            dt = datetime(*info.date_time)
                            if earliest is None or dt < earliest:
                                earliest = dt
                            if latest is None or dt > latest:
                                latest = dt
                        except (ValueError, TypeError):
                            continue
        except Exception as e:
            self.log.warning(f"Error extracting ZIP timestamps: {e}")
        return (
            earliest.isoformat() if earliest else None,
            latest.isoformat() if latest else None,
        )

    def _extract_supported_abis(self):
        """Determine supported ABIs from lib/ directory."""
        abis = set()
        for f in self._list_files():
            if f.startswith("lib/") and f.endswith(".so"):
                parts = f.split("/")
                if len(parts) >= 3:
                    abis.add(parts[1])
        return sorted(abis)

    def _count_dex_files(self):
        """Count DEX files and compute total size."""
        dex_count = 0
        total_size = 0
        try:
            zf = self._get_zip_file()
            if zf:
                with zf:
                    for info in zf.infolist():
                        if info.filename.endswith(".dex"):
                            dex_count += 1
                            total_size += info.file_size
        except Exception as e:
            self.log.warning(f"Error counting DEX files: {e}")
        return dex_count, total_size

    def _check_embedded_apk(self):
        """Check if the archive contains nested APK files."""
        for f in self._list_files():
            if f.lower().endswith(".apk"):
                return True
        return False

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

            # Core metadata from androguard
            package_name = self.apk.get_package() or ""
            app_name = self.apk.get_app_name() or ""

            # version_code may be string or int
            try:
                version_code = int(self.apk.get_androidversion_code() or 0)
            except (ValueError, TypeError):
                version_code = 0

            version_name = self.apk.get_androidversion_name() or ""

            # SDK versions
            min_sdk = self.apk.get_min_sdk_version()
            target_sdk = self.apk.get_target_sdk_version()
            # NOTE: compile_sdk may not be available in all androguard versions
            compile_sdk = None
            try:
                compile_sdk = self.apk.get_effective_target_sdk_version()
            except AttributeError:
                pass

            min_sdk = int(min_sdk) if min_sdk else None
            target_sdk = int(target_sdk) if target_sdk else None
            compile_sdk = int(compile_sdk) if compile_sdk else None

            # Main activity
            main_activity = self.apk.get_main_activity()

            # Flags from manifest
            # androguard provides get_attribute_value for manifest attributes
            is_debuggable = self.apk.get_attribute_value(
                "application", "debuggable"
            ) == "true"
            allow_backup = self.apk.get_attribute_value(
                "application", "allowBackup"
            ) != "false"  # default is true
            uses_cleartext = self.apk.get_attribute_value(
                "application", "usesCleartextTraffic"
            ) == "true"

            # Archive-level analysis
            supported_abis = self._extract_supported_abis()
            dex_count, total_dex_size = self._count_dex_files()
            all_files = self._list_files()
            total_file_count = len(all_files)
            has_native_code = any(
                f.startswith("lib/") and f.endswith(".so") for f in all_files
            )
            has_assets = any(f.startswith("assets/") for f in all_files)
            contains_embedded_apk = self._check_embedded_apk()

            # Uses-library
            uses_libraries = []
            try:
                libs = self.apk.get_libraries()
                uses_libraries = list(libs) if libs else []
            except Exception:
                pass

            # ZIP timestamps
            earliest_mod, latest_mod = self._extract_zip_timestamps()

            self.apk_features = APKFeatures(
                package_name=package_name,
                app_name=app_name,
                version_code=version_code,
                version_name=version_name,
                min_sdk_version=min_sdk,
                target_sdk_version=target_sdk,
                compile_sdk_version=compile_sdk,
                main_activity=main_activity,
                is_debuggable=is_debuggable,
                allow_backup=allow_backup,
                uses_cleartext_traffic=uses_cleartext,
                supported_abis=supported_abis,
                dex_count=dex_count,
                total_dex_size=total_dex_size,
                total_file_count=total_file_count,
                has_native_code=has_native_code,
                has_assets=has_assets,
                uses_libraries=uses_libraries,
                earliest_content_modification=earliest_mod,
                latest_content_modification=latest_mod,
                contains_embedded_apk=contains_embedded_apk,
            )
            return self.apk_features

        except Exception as e:
            self.log.error(
                f"Error extracting APK features {self.hash.sha256}: {e}"
            )
            return None

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

            f = self.apk_features
            data = [[
                self.sha256, self.md5, self.sha1,
                f.package_name,
                f.app_name,
                f.version_code,
                f.version_name,
                f.min_sdk_version,
                f.target_sdk_version,
                f.compile_sdk_version,
                f.main_activity,
                int(f.is_debuggable),
                int(f.allow_backup),
                int(f.uses_cleartext_traffic),
                f.supported_abis,
                f.dex_count,
                f.total_dex_size,
                f.total_file_count,
                int(f.has_native_code),
                int(f.has_assets),
                f.uses_libraries,
                f.earliest_content_modification,
                f.latest_content_modification,
                int(f.contains_embedded_apk),
                datetime.now(timezone.utc),
            ]]

            column_names = [
                "sha256", "md5", "sha1",
                "package_name", "app_name", "version_code", "version_name",
                "min_sdk_version", "target_sdk_version", "compile_sdk_version",
                "main_activity",
                "is_debuggable", "allow_backup", "uses_cleartext_traffic",
                "supported_abis",
                "dex_count", "total_dex_size", "total_file_count",
                "has_native_code", "has_assets",
                "uses_libraries",
                "earliest_content_modification", "latest_content_modification",
                "contains_embedded_apk",
                "analysis_date",
            ]

            column_type_names = [
                "FixedString(64)", "FixedString(32)", "FixedString(40)",
                "String", "String", "UInt32", "String",
                "Nullable(UInt16)", "Nullable(UInt16)", "Nullable(UInt16)",
                "Nullable(String)",
                "UInt8", "UInt8", "UInt8",
                "Array(String)",
                "UInt16", "UInt64", "UInt32",
                "UInt8", "UInt8",
                "Array(String)",
                "Nullable(String)", "Nullable(String)",
                "UInt8",
                "DateTime64(3, 'UTC')",
            ]

            return (data, column_names, column_type_names)

    def get_clickhouse_table(self) -> str:
        return "redb_apk_features"
```

### 6.2 APKManifestExtractor

**File:** `redb/extractors/apk_extractors/apk_manifest.py`

**Key androguard API calls:**
```python
# Activities, services, receivers, providers
self.apk.get_activities()       # Returns list of activity class names
self.apk.get_services()         # Returns list of service class names
self.apk.get_receivers()        # Returns list of receiver class names
self.apk.get_providers()        # Returns list of provider class names

# Exported check (per component)
self.apk.get_intent_filters(component_type, component_name)
# Returns dict: {"action": [...], "category": [...]}

# Uses-features
self.apk.get_features()         # Returns list of feature strings

# Full XML
self.apk.get_android_manifest_xml().toxml()
# OR
self.apk.get_android_manifest_axml().get_xml()
```

**Implementation notes:**
- For ClickHouse export of components, use a **one-row-per-component** table (`redb_apk_components`) with columns: `sha256`, `component_type`, `class_name`, `is_exported`, `intent_actions` (Array), `intent_categories` (Array), `analysis_date`
- For the aggregated manifest data (counts, feature list, full XML), use a **one-row-per-APK** table (`redb_apk_manifest`)
- To determine if a component is exported: check `android:exported` attribute. If not explicitly set, it's implicitly exported if the component has intent filters (Android default behavior pre-API 31). From API 31+, `android:exported` must be explicit

### 6.3 APKPermissionsExtractor

**File:** `redb/extractors/apk_extractors/apk_permissions.py`

**Key androguard API calls:**
```python
# Requested permissions
self.apk.get_permissions()              # Returns list of permission strings
self.apk.get_declared_permissions()     # Custom permissions defined by the app

# For protection level, use androguard's permission mapping:
from androguard.core.api_specific_resources import load_permission_mappings
permission_map = load_permission_mappings(target_sdk)
# permission_map[perm_name] -> {"protectionLevel": "dangerous", ...}
```

**Permhash computation:**
```python
import permhash
ph = permhash.permhash_apk(self.filepath)
# Returns SHA-256 hex string
```

**Known dangerous permissions** (reference list for classification fallback):
```python
DANGEROUS_PERMISSIONS = {
    "android.permission.READ_CALENDAR",
    "android.permission.WRITE_CALENDAR",
    "android.permission.CAMERA",
    "android.permission.READ_CONTACTS",
    "android.permission.WRITE_CONTACTS",
    "android.permission.GET_ACCOUNTS",
    "android.permission.ACCESS_FINE_LOCATION",
    "android.permission.ACCESS_COARSE_LOCATION",
    "android.permission.ACCESS_BACKGROUND_LOCATION",
    "android.permission.RECORD_AUDIO",
    "android.permission.READ_PHONE_STATE",
    "android.permission.READ_PHONE_NUMBERS",
    "android.permission.CALL_PHONE",
    "android.permission.ANSWER_PHONE_CALLS",
    "android.permission.ADD_VOICEMAIL",
    "android.permission.USE_SIP",
    "android.permission.BODY_SENSORS",
    "android.permission.SEND_SMS",
    "android.permission.RECEIVE_SMS",
    "android.permission.READ_SMS",
    "android.permission.RECEIVE_WAP_PUSH",
    "android.permission.RECEIVE_MMS",
    "android.permission.READ_EXTERNAL_STORAGE",
    "android.permission.WRITE_EXTERNAL_STORAGE",
    "android.permission.READ_MEDIA_IMAGES",
    "android.permission.READ_MEDIA_VIDEO",
    "android.permission.READ_MEDIA_AUDIO",
    "android.permission.POST_NOTIFICATIONS",
    "android.permission.NEARBY_WIFI_DEVICES",
    "android.permission.ACTIVITY_RECOGNITION",
    "android.permission.BLUETOOTH_SCAN",
    "android.permission.BLUETOOTH_ADVERTISE",
    "android.permission.BLUETOOTH_CONNECT",
}
```

**ClickHouse export:** One row per permission per APK in `redb_apk_permissions`:
- `sha256`, `permission_name`, `protection_level`, `is_custom`, `analysis_date`

The `permhash` value is also stored separately — see Section 8 for integration with `HashExtractor`.

### 6.4 APKSignatureExtractor

**File:** `redb/extractors/apk_extractors/apk_signature.py`

**Key androguard API calls:**
```python
# Get certificate objects
certs = self.apk.get_certificates()     # Returns list of asn1crypto Certificate objects

# For each certificate:
cert.subject.human_friendly              # Subject DN
cert.issuer.human_friendly               # Issuer DN
cert.serial_number                       # Serial
cert.not_valid_before                    # datetime
cert.not_valid_after                     # datetime
cert.hash_algo                           # e.g., "sha256"
cert.signature_algo                      # e.g., "rsassa_pkcs1v15"

# SHA-1 thumbprint
import hashlib
hashlib.sha1(cert.dump()).hexdigest()

# SHA-256 thumbprint
hashlib.sha256(cert.dump()).hexdigest()

# Self-signed check
cert.subject == cert.issuer

# Signature scheme detection:
# v1: Check META-INF/*.SF and META-INF/*.RSA/DSA/EC files exist
# v2/v3: androguard >= 4.x has is_signed_v2() / is_signed_v3()
self.apk.is_signed_v1()
self.apk.is_signed_v2()
self.apk.is_signed_v3()
```

**Implementation notes:**
- Mirror the structure of `PECodeSigningInfo` / `MachOCodeSigningInfo`
- The `_id` field should be `self.sha256` (same as PE/Mach-O signature extractors)

### 6.5 APKDexExtractor

**File:** `redb/extractors/apk_extractors/apk_dex.py`

This is the most complex extractor. Use `androguard.core.dex` for DEX parsing.

**Key androguard API calls:**
```python
from androguard.core.dex import DEX

# Get raw DEX data from APK
dex_files = self.apk.get_all_dex()  # Returns list of raw DEX bytes

for dex_name, dex_data in zip(self.apk.get_dex_names(), dex_files):
    d = DEX(dex_data)

    # Class enumeration
    classes = d.get_classes()
    for cls in classes:
        cls.get_name()          # e.g., "Lcom/example/MainActivity;"

    # Method enumeration
    methods = d.get_methods()
    for method in methods:
        method.get_class_name()  # e.g., "Lcom/example/MainActivity;"
        method.get_name()        # e.g., "onCreate"
        method.get_descriptor()  # e.g., "(Landroid/os/Bundle;)V"

    # String constants
    strings = d.get_strings()    # Returns list of string constants
```

**Computing `top_packages`:**
```python
from collections import Counter

package_counter = Counter()
for cls in classes:
    name = cls.get_name()  # "Lcom/example/foo/Bar;"
    # Convert to package: "com.example.foo"
    parts = name[1:].replace("/", ".").rsplit(".", 1)
    if len(parts) > 1:
        package = parts[0]
    else:
        package = "(default)"
    package_counter[package] += 1

top_packages = [
    {"package": pkg, "class_count": count}
    for pkg, count in package_counter.most_common(20)
]
```

**Computing `api_usage`:**
Iterate over all method cross-references (xrefs) or method invocations. For each invoked method, check if it matches any sensitive API pattern. See Section 12 for the full categorization reference.

```python
api_usage = {cat: [] for cat in SENSITIVE_API_CATEGORIES}

for method in d.get_methods():
    # Get the bytecode and look for invoke-* instructions
    code = method.get_code()
    if not code:
        continue
    # Use androguard's analysis or instruction iteration
    # to find method references that match sensitive patterns
```

**Computing `obfuscation_indicators`:**
```python
class_names = [cls.get_name() for cls in classes]
method_names = [m.get_name() for m in methods if m.get_name() not in ("<init>", "<clinit>")]

short_class = sum(1 for n in class_names if len(n.split("/")[-1].rstrip(";")) <= 2)
short_method = sum(1 for n in method_names if len(n) <= 2)

obfuscation_indicators = {
    "short_class_names_pct": round(short_class / max(len(class_names), 1), 4),
    "short_method_names_pct": round(short_method / max(len(method_names), 1), 4),
    "non_ascii_identifiers": sum(1 for n in class_names if not n.isascii()),
    "avg_class_name_length": round(
        sum(len(n) for n in class_names) / max(len(class_names), 1), 2
    ),
}
```

**ClickHouse export:**
- `redb_apk_dex`: One row per DEX file — `sha256`, `dex_sha256`, `dex_filename`, `class_count`, `method_count`, `string_count`, `top_packages` (JSON string), `obfuscation_indicators` (JSON string), `analysis_date`
- `redb_apk_dex_api_usage`: One row per API category per DEX — `sha256`, `dex_sha256`, `category`, `api_calls` (Array(String)), `analysis_date`

### 6.6 APKResourceExtractor

**File:** `redb/extractors/apk_extractors/apk_resources.py`

**Implementation approach:**
```python
import hashlib
from magika import Magika

magika = Magika()

resources = []
suspicious_files = []

zf = self._get_zip_file()
if zf:
    with zf:
        for info in zf.infolist():
            if info.is_dir():
                continue
            # Only scan res/ and assets/ directories
            if not (info.filename.startswith("res/") or
                    info.filename.startswith("assets/")):
                continue

            data = zf.read(info.filename)
            file_sha256 = hashlib.sha256(data).hexdigest()
            filetype = magika.identify_bytes(data).output.label

            resource = APKResource(
                path=info.filename,
                size=info.file_size,
                sha256=file_sha256,
                filetype_magika=filetype,
            )

            # Flag suspicious file types
            SUSPICIOUS_TYPES = {
                "elf", "pebin", "dex", "apk", "zip", "shell",
                "javascript", "python", "powershell", "batch",
            }
            if filetype in SUSPICIOUS_TYPES:
                resource.is_suspicious = True
                suspicious_files.append(resource)

            resources.append(resource)
```

**ClickHouse export:** One row per resource in `redb_apk_resources`:
- `sha256`, `resource_path`, `resource_size`, `resource_sha256`, `filetype_magika`, `is_suspicious`, `analysis_date`

**Performance note:** For APKs with very large resource directories (some apps have 10K+ files in `res/`), consider limiting to the top N resources by size or only scanning `assets/` fully while sampling `res/`. Document any limits applied.

### 6.7 APKNativeLibExtractor

**File:** `redb/extractors/apk_extractors/apk_native_libs.py`

**Implementation approach:**
```python
native_libs = []
abis = set()

zf = self._get_zip_file()
if zf:
    with zf:
        for info in zf.infolist():
            if info.filename.startswith("lib/") and info.filename.endswith(".so"):
                parts = info.filename.split("/")
                if len(parts) >= 3:
                    abi = parts[1]
                    filename = parts[-1]
                    abis.add(abi)

                    data = zf.read(info.filename)
                    lib_sha256 = hashlib.sha256(data).hexdigest()

                    native_libs.append(APKNativeLib(
                        abi=abi,
                        filename=filename,
                        size=info.file_size,
                        sha256=lib_sha256,
                        is_known_packer=filename in KNOWN_PACKER_LIBS,
                    ))
```

See Section 11 for the `KNOWN_PACKER_LIBS` reference list.

### 6.8 APKInconsistencyTestsExtractor

**File:** `redb/extractors/apk_extractors/apk_inconsistency_tests.py`

Each test is a private method returning `bool`:

```python
def _test_zip_bomb(self):
    """Check if any ZIP entry has compression ratio > 100:1."""
    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

def _test_zip_duplicate_entries(self):
    """Check for duplicate filenames in ZIP directory."""
    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))

def _test_zip_path_traversal(self):
    """Check for path traversal (../) in ZIP entry names."""
    for f in self._list_files():
        if ".." in f or f.startswith("/"):
            return True
    return False

def _test_zip_suspicious_timestamps(self):
    """Check for timestamps at epoch (1980) or in the future."""
    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

def _test_hidden_dex_files(self):
    """Check for DEX files not matching classes*.dex pattern."""
    import re
    standard_pattern = re.compile(r"^classes\d*\.dex$")
    for f in self._list_files():
        if f.endswith(".dex") and not standard_pattern.match(f.split("/")[-1]):
            return True
    return False

def _test_emulator_detection_strings(self):
    """Check for emulator detection patterns in DEX strings."""
    # Inspect DEX strings for known emulator detection indicators
    EMULATOR_INDICATORS = {
        "generic", "sdk", "google_sdk", "Emulator",
        "goldfish", "ranchu", "Andy", "Genymotion",
        "BlueStacks", "nox", "ttVM_Hdragon",
    }
    # Use androguard to extract strings from DEX, then check
    ...

def _test_debugger_detection(self):
    """Check for debugger detection API calls in DEX."""
    # Look for: Debug.isDebuggerConnected, Debug.waitingForDebugger
    ...

def _test_root_detection(self):
    """Check for root detection patterns in DEX."""
    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",
    }
    ...
```

---

## 7. Ingestor Integration

**File:** `redb/ingestor.py`

Replace the stub at line 1668:

```python
elif filetype == "apk":
    logger.debug("APK file detected")
```

With the full dispatch block:

```python
elif filetype == "apk":
    logger.debug("APK file detected")

    # Import APK extractors
    from redb.extractors.apk_extractor import APKExtractor
    from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor
    from redb.extractors.apk_extractors.apk_manifest import APKManifestExtractor
    from redb.extractors.apk_extractors.apk_permissions import APKPermissionsExtractor
    from redb.extractors.apk_extractors.apk_signature import APKSignatureExtractor
    from redb.extractors.apk_extractors.apk_dex import APKDexExtractor
    from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor
    from redb.extractors.apk_extractors.apk_native_libs import APKNativeLibExtractor
    from redb.extractors.apk_extractors.apk_inconsistency_tests import APKInconsistencyTestsExtractor

    apk_modules = [
        APKFeaturesExtractor,
        APKManifestExtractor,
        APKPermissionsExtractor,
        APKSignatureExtractor,
        APKDexExtractor,
        APKResourceExtractor,
        APKNativeLibExtractor,
        APKInconsistencyTestsExtractor,
    ]

    # Parse APK once with androguard, share across all extractors
    try:
        from androguard.core.apk import APK
        apk = APK(filepath)
    except Exception as e:
        logger.error(f"Failed to parse APK {filepath}: {e}")
        apk = None

    # Run format-agnostic extractors first (same as PE/ELF/MachO)
    # DIE
    if 'all' in selected_modules or 'DIEExtractor' in selected_modules:
        try:
            extractor = DIEExtractor(
                filepath, logger, exporters=exporters,
                index_prefix=index_prefix
            )
            result = extractor.export_data()
            is_packed = bool(result) if result is not None else None
            if result:
                results[ImportResult.CORRECTLY] += 1
            elif result is False:
                results[ImportResult.FAILED] += 1
        except Exception as e:
            logger.error(f"Error in DIEExtractor: {str(e)}")
            results[ImportResult.FAILED] += 1

    # BasicProperties
    if 'all' in selected_modules or 'BasicPropertiesExtractor' in selected_modules:
        try:
            extractor = BasicPropertiesExtractor(
                filepath, logger, exporters=exporters,
                index_prefix=index_prefix
            )
            if is_packed is not None:
                extractor.is_packed = is_packed
            result = extractor.export_data()
            if result:
                results[ImportResult.CORRECTLY] += 1
            elif result is False:
                results[ImportResult.FAILED] += 1
        except Exception as e:
            logger.error(f"Error in BasicPropertiesExtractor: {str(e)}")
            results[ImportResult.FAILED] += 1

    # Hashes
    if 'all' in selected_modules or 'HashExtractor' in selected_modules:
        try:
            extractor = HashExtractor(
                filepath, logger, exporters=exporters,
                index_prefix=index_prefix
            )
            result = extractor.export_data()
            if result:
                results[ImportResult.CORRECTLY] += 1
            elif result is False:
                results[ImportResult.FAILED] += 1
        except Exception as e:
            logger.error(f"Error in HashExtractor: {str(e)}")
            results[ImportResult.FAILED] += 1

    # YARA
    if 'all' in selected_modules or 'YaraExtractor' in selected_modules:
        try:
            extractor = YaraExtractor(
                filepath, logger, exporters=exporters,
                index_prefix=index_prefix
            )
            result = extractor.export_data()
            if result:
                results[ImportResult.CORRECTLY] += 1
            elif result is False:
                results[ImportResult.FAILED] += 1
        except Exception as e:
            logger.error(f"Error in YaraExtractor: {str(e)}")
            results[ImportResult.FAILED] += 1

    # APK-specific extractors
    for module in apk_modules:
        if 'all' in selected_modules or module.__name__ in selected_modules:
            try:
                logger.debug(f"Running {module.__name__}")
                extractor = module(
                    filepath,
                    logger,
                    exporters=exporters,
                    index_prefix=index_prefix,
                    apk=apk
                )
                result = extractor.export_data()
                if result:
                    results[ImportResult.CORRECTLY] += 1
                elif result is False:
                    results[ImportResult.FAILED] += 1
            except Exception as e:
                logger.error(f"Error in {module.__name__}: {str(e)}")
                results[ImportResult.FAILED] += 1
```

**Note:** Follow the exact error-handling and `selected_modules` check pattern used by the existing PE/ELF/Mach-O dispatch blocks. The code above mirrors the pattern from `ingestor.py:1540-1666`.

---

## 8. Hashes Dataclass Update

**File:** `redb/models/dataclasses.py`

Add to the `Hashes` dataclass after the existing Mach-O hashes:

```python
@dataclass
class Hashes:
    md5: str
    sha1: str
    sha256: str
    ssdeep_hash: str
    tlsh_hash: str
    authentihash: Optional[str] = None  # PE
    imphash: Optional[str] = None  # PE
    impfuzzy: Optional[str] = None  # PE TODO: implement
    typerefhash: Optional[str] = None  # DotNet
    richhash: Optional[str] = None  # PE
    richpe_hash: Optional[str] = None  # PE
    richpv_hash: Optional[str] = None  # PE
    richpv_hash_sorted: Optional[str] = None  # PE
    import_hash: Optional[str] = None  # ELF
    export_hash: Optional[str] = None  # ELF
    section_hash: Optional[str] = None  # ELF
    symbol_hash: Optional[str] = None  # ELF (symhash)
    dynamic_hash: Optional[str] = None  # ELF
    # Mach-O similarity hashes
    macho_dylib_hash: Optional[str] = None
    macho_import_hash: Optional[str] = None
    macho_export_hash: Optional[str] = None
    macho_entitlement_hash: Optional[str] = None
    macho_symhash: Optional[str] = None
    # APK similarity hashes
    permhash: Optional[str] = None  # APK: SHA-256 of sorted permissions (Mandiant/Google)
```

The `permhash` should be computed in `HashExtractor` when the filetype is APK, using the `permhash` library:

```python
# In HashExtractor, when filetype == "apk":
import permhash as permhash_lib
self.hashes.permhash = permhash_lib.permhash_apk(self.filepath)
```

The `permhash` column must be added to the existing `redb_hashes` table:

```sql
ALTER TABLE redb_hashes
    ADD COLUMN IF NOT EXISTS permhash Nullable(FixedString(64));
```

---

## 9. ClickHouse Schema

Create the following tables. Naming convention follows existing tables (`redb_pe_features`, `redb_elf_features`, etc.):

```sql
-- Core metadata (one row per APK)
CREATE TABLE redb_apk_features (
    sha256              FixedString(64),
    md5                 FixedString(32),
    sha1                FixedString(40),
    package_name        String,
    app_name            String,
    version_code        UInt32,
    version_name        String,
    min_sdk_version     Nullable(UInt16),
    target_sdk_version  Nullable(UInt16),
    compile_sdk_version Nullable(UInt16),
    main_activity       Nullable(String),
    is_debuggable       UInt8,
    allow_backup        UInt8,
    uses_cleartext_traffic UInt8,
    supported_abis      Array(String),
    dex_count           UInt16,
    total_dex_size      UInt64,
    total_file_count    UInt32,
    has_native_code     UInt8,
    has_assets          UInt8,
    uses_libraries      Array(String),
    earliest_content_modification Nullable(String),
    latest_content_modification   Nullable(String),
    contains_embedded_apk UInt8,
    analysis_date       DateTime64(3, 'UTC')
) ENGINE = ReplacingMergeTree()
ORDER BY sha256;

-- Manifest components (one row per component)
CREATE TABLE redb_apk_components (
    sha256              FixedString(64),
    component_type      LowCardinality(String),  -- "activity", "service", "receiver", "provider"
    class_name          String,
    is_exported         UInt8,
    intent_actions      Array(String),
    intent_categories   Array(String),
    analysis_date       DateTime64(3, 'UTC')
) ENGINE = ReplacingMergeTree()
ORDER BY (sha256, component_type, class_name);

-- Manifest summary (one row per APK)
CREATE TABLE redb_apk_manifest (
    sha256              FixedString(64),
    md5                 FixedString(32),
    sha1                FixedString(40),
    activity_count      UInt16,
    service_count       UInt16,
    receiver_count      UInt16,
    provider_count      UInt16,
    intent_filters_by_action   Array(String),
    intent_filters_by_category Array(String),
    uses_features       Array(String),
    manifest_xml        Nullable(String),
    analysis_date       DateTime64(3, 'UTC')
) ENGINE = ReplacingMergeTree()
ORDER BY sha256;

-- Permissions (one row per permission per APK)
CREATE TABLE redb_apk_permissions (
    sha256              FixedString(64),
    permission_name     String,
    protection_level    LowCardinality(String),
    is_custom           UInt8,
    analysis_date       DateTime64(3, 'UTC')
) ENGINE = ReplacingMergeTree()
ORDER BY (sha256, permission_name);

-- Signing info (one row per APK)
CREATE TABLE redb_apk_signature (
    sha256              FixedString(64),
    md5                 FixedString(32),
    sha1                FixedString(40),
    is_signed           UInt8,
    signature_scheme_versions Array(UInt8),
    number_of_certificates UInt8,
    signer_subject      Nullable(String),
    signer_issuer       Nullable(String),
    analysis_date       DateTime64(3, 'UTC')
) ENGINE = ReplacingMergeTree()
ORDER BY sha256;

-- Certificate details (one row per certificate per APK)
CREATE TABLE redb_apk_certificates (
    sha256              FixedString(64),
    subject             String,
    issuer              String,
    serial_number       String,
    valid_from          String,
    valid_to            String,
    thumbprint_sha1     FixedString(40),
    thumbprint_sha256   FixedString(64),
    algorithm           Nullable(String),
    key_size            Nullable(UInt16),
    is_self_signed      UInt8,
    analysis_date       DateTime64(3, 'UTC')
) ENGINE = ReplacingMergeTree()
ORDER BY (sha256, thumbprint_sha256);

-- DEX file analysis (one row per DEX file)
CREATE TABLE redb_apk_dex (
    sha256              FixedString(64),
    dex_sha256          FixedString(64),
    dex_filename        String,
    dex_tlsh            Nullable(String),
    class_count         UInt32,
    method_count        UInt32,
    string_count        UInt32,
    top_packages        String,       -- JSON
    obfuscation_indicators String,    -- JSON
    analysis_date       DateTime64(3, 'UTC')
) ENGINE = ReplacingMergeTree()
ORDER BY (sha256, dex_sha256);

-- DEX API usage (one row per category per DEX)
CREATE TABLE redb_apk_dex_api_usage (
    sha256              FixedString(64),
    dex_sha256          FixedString(64),
    category            LowCardinality(String),
    api_calls           Array(String),
    analysis_date       DateTime64(3, 'UTC')
) ENGINE = ReplacingMergeTree()
ORDER BY (sha256, dex_sha256, category);

-- Resource inventory (one row per resource)
CREATE TABLE redb_apk_resources (
    sha256              FixedString(64),
    resource_path       String,
    resource_size       UInt64,
    resource_sha256     FixedString(64),
    filetype_magika     LowCardinality(String),
    is_suspicious       UInt8,
    analysis_date       DateTime64(3, 'UTC')
) ENGINE = ReplacingMergeTree()
ORDER BY (sha256, resource_path);

-- Native libraries (one row per .so file)
CREATE TABLE redb_apk_native_libs (
    sha256              FixedString(64),
    abi                 LowCardinality(String),
    filename            String,
    size                UInt64,
    lib_sha256          FixedString(64),
    is_known_packer     UInt8,
    analysis_date       DateTime64(3, 'UTC')
) ENGINE = ReplacingMergeTree()
ORDER BY (sha256, abi, filename);

-- Inconsistency tests (one row per APK)
CREATE TABLE redb_apk_inconsistency_tests (
    sha256              FixedString(64),
    md5                 FixedString(32),
    sha1                FixedString(40),
    test_zip_bomb                    Nullable(UInt8),
    test_zip_duplicate_entries       Nullable(UInt8),
    test_zip_path_traversal          Nullable(UInt8),
    test_zip_suspicious_timestamps   Nullable(UInt8),
    test_hidden_dex_files            Nullable(UInt8),
    test_manifest_component_mismatch Nullable(UInt8),
    test_debuggable_release          Nullable(UInt8),
    test_emulator_detection_strings  Nullable(UInt8),
    test_debugger_detection          Nullable(UInt8),
    test_root_detection              Nullable(UInt8),
    analysis_date       DateTime64(3, 'UTC')
) ENGINE = ReplacingMergeTree()
ORDER BY sha256;
```

---

## 10. Androguard API Reference

Quick reference for the most commonly used androguard APIs. Full docs: https://androguard.readthedocs.io/

### APK class (`androguard.core.apk.APK`)

| Method | Returns | Description |
|--------|---------|-------------|
| `APK(filepath)` | `APK` | Parse an APK file |
| `.is_valid_APK()` | `bool` | Check if parsing succeeded |
| `.get_package()` | `str` | Package name |
| `.get_app_name()` | `str` | Application display name |
| `.get_androidversion_code()` | `str` | Version code |
| `.get_androidversion_name()` | `str` | Version name |
| `.get_min_sdk_version()` | `str` | Minimum SDK |
| `.get_target_sdk_version()` | `str` | Target SDK |
| `.get_main_activity()` | `str` | Main/launcher activity |
| `.get_activities()` | `list[str]` | All activity class names |
| `.get_services()` | `list[str]` | All service class names |
| `.get_receivers()` | `list[str]` | All receiver class names |
| `.get_providers()` | `list[str]` | All provider class names |
| `.get_permissions()` | `list[str]` | Requested permissions |
| `.get_declared_permissions()` | `list[str]` | Custom-defined permissions |
| `.get_features()` | `list[str]` | Uses-feature declarations |
| `.get_libraries()` | `list[str]` | Uses-library declarations |
| `.get_files()` | `list[str]` | All files in the APK |
| `.get_certificates()` | `list[Certificate]` | X.509 certificate objects |
| `.get_all_dex()` | `list[bytes]` | Raw DEX file contents |
| `.get_dex_names()` | `list[str]` | DEX filenames |
| `.is_signed_v1()` | `bool` | JAR signature present |
| `.is_signed_v2()` | `bool` | v2 signature present |
| `.is_signed_v3()` | `bool` | v3 signature present |
| `.get_attribute_value(tag, attr)` | `str` | Get manifest attribute |
| `.get_android_manifest_xml()` | `minidom` | Parsed manifest XML |
| `.get_intent_filters(type, name)` | `dict` | Intent filters for component |

### DEX class (`androguard.core.dex.DEX`)

| Method | Returns | Description |
|--------|---------|-------------|
| `DEX(raw_bytes)` | `DEX` | Parse raw DEX data |
| `.get_classes()` | `list[ClassDefItem]` | All class definitions |
| `.get_methods()` | `list[EncodedMethod]` | All methods |
| `.get_strings()` | `list[str]` | All string constants |

---

## 11. Known Packer/Protector Signatures

Native library names commonly associated with packers and protectors. Used by `APKNativeLibExtractor` to flag `is_known_packer`:

```python
KNOWN_PACKER_LIBS = {
    # Jiagu (360/Qihoo)
    "libjiagu.so", "libjiagu_a64.so", "libjiagu_x86.so", "libjiagu_x64.so",
    # Bangcle/SecNeo
    "libsecexe.so", "libsecmain.so", "libSecShell.so",
    # Baidu
    "libbaiduprotect.so",
    # Tencent (Legu)
    "libshell-super.2019.so", "libshella-*.so", "libtxAppProtect.so",
    "libBugly.so", "mix.dex",
    # iJiami
    "libexec.so", "libexecmain.so",
    # Alibaba
    "libmobisec.so", "libaliprotect.so",
    # APKProtect
    "libAPKProtect.so",
    # Pangxie (Pangolin)
    "libdexjni.so",
    # DexProtector
    "libdexprotector.so",
    # AppSolid
    "libAppSolid.so",
    # Kiwisec
    "libkwscmm.so",
    # DingXiang
    "libx3g.so",
    # NQ Shield
    "libnqshield.so",
    # Generic / other
    "libprotectClass.so",
    "libDexHelper.so",
    "libdexloader.so",
    "libfdog.so",
}
```

**Note:** This list should be maintained and expanded over time. For glob-style matching (e.g., `libshella-*.so`), use `fnmatch` instead of exact set lookup.

---

## 12. Sensitive API Categorization Reference

Used by `APKDexExtractor` to categorize API calls. Each category maps to a list of class/method patterns:

```python
SENSITIVE_API_CATEGORIES = {
    "reflection": [
        "Ljava/lang/reflect/Method;->invoke",
        "Ljava/lang/reflect/Field;->get",
        "Ljava/lang/reflect/Field;->set",
        "Ljava/lang/reflect/Constructor;->newInstance",
        "Ljava/lang/Class;->forName",
        "Ljava/lang/Class;->getMethod",
        "Ljava/lang/Class;->getDeclaredMethod",
        "Ljava/lang/Class;->getDeclaredField",
        "Ljava/lang/ClassLoader;->loadClass",
    ],
    "crypto": [
        "Ljavax/crypto/Cipher;->getInstance",
        "Ljavax/crypto/Cipher;->init",
        "Ljavax/crypto/spec/SecretKeySpec;-><init>",
        "Ljavax/crypto/spec/IvParameterSpec;-><init>",
        "Ljava/security/MessageDigest;->getInstance",
        "Ljava/security/KeyStore;->getInstance",
        "Ljavax/crypto/Mac;->getInstance",
    ],
    "dynamic_loading": [
        "Ldalvik/system/DexClassLoader;-><init>",
        "Ldalvik/system/PathClassLoader;-><init>",
        "Ldalvik/system/InMemoryDexClassLoader;-><init>",
        "Ldalvik/system/BaseDexClassLoader;-><init>",
        "Ljava/lang/Runtime;->exec",
        "Ljava/lang/ProcessBuilder;->start",
    ],
    "telephony": [
        "Landroid/telephony/TelephonyManager;->getDeviceId",
        "Landroid/telephony/TelephonyManager;->getSubscriberId",
        "Landroid/telephony/TelephonyManager;->getLine1Number",
        "Landroid/telephony/TelephonyManager;->getSimSerialNumber",
        "Landroid/telephony/TelephonyManager;->getNetworkOperator",
        "Landroid/telephony/TelephonyManager;->getSimOperator",
    ],
    "sms": [
        "Landroid/telephony/SmsManager;->sendTextMessage",
        "Landroid/telephony/SmsManager;->sendMultipartTextMessage",
        "Landroid/telephony/SmsManager;->sendDataMessage",
    ],
    "network": [
        "Ljava/net/HttpURLConnection;->connect",
        "Ljava/net/URL;->openConnection",
        "Lokhttp3/OkHttpClient;-><init>",
        "Lokhttp3/Request$Builder;->build",
        "Lorg/apache/http/client/HttpClient;->execute",
        "Landroid/webkit/WebView;->loadUrl",
        "Landroid/webkit/WebView;->setWebViewClient",
    ],
    "native": [
        "Ljava/lang/System;->loadLibrary",
        "Ljava/lang/System;->load",
        "Ljava/lang/Runtime;->loadLibrary",
    ],
    "device_info": [
        "Landroid/os/Build;->FINGERPRINT",
        "Landroid/os/Build;->MODEL",
        "Landroid/os/Build;->MANUFACTURER",
        "Landroid/os/Build;->PRODUCT",
        "Landroid/os/Build;->BRAND",
        "Landroid/os/Build;->DEVICE",
        "Landroid/os/Build;->HARDWARE",
        "Landroid/os/Build;->SERIAL",
        "Landroid/os/Build$VERSION;->SDK_INT",
        "Landroid/provider/Settings$Secure;->getString",  # ANDROID_ID
    ],
    "file_io": [
        "Ljava/io/FileOutputStream;-><init>",
        "Ljava/io/FileInputStream;-><init>",
        "Landroid/content/SharedPreferences;->edit",
        "Landroid/database/sqlite/SQLiteDatabase;->execSQL",
        "Landroid/database/sqlite/SQLiteDatabase;->rawQuery",
    ],
    "ipc": [
        "Landroid/content/ContentResolver;->query",
        "Landroid/content/ContentResolver;->insert",
        "Landroid/content/ContentResolver;->delete",
        "Landroid/content/Intent;-><init>",
        "Landroid/content/Context;->sendBroadcast",
        "Landroid/content/Context;->startService",
        "Landroid/content/Context;->bindService",
    ],
}
```

**Matching approach:** For each method invocation found in DEX bytecode, check if its full signature (class + method) starts with any of the patterns above. Store only the first match per unique API call (avoid duplicates from multiple call sites).

---

## Implementation Checklist

- [ ] Add `androguard` and `permhash` to `requirements.txt`
- [ ] Create `redb/extractors/apk_extractor.py` (base class)
- [ ] Create `redb/extractors/apk_extractors/__init__.py`
- [ ] Implement `APKFeaturesExtractor`
- [ ] Implement `APKManifestExtractor`
- [ ] Implement `APKPermissionsExtractor`
- [ ] Implement `APKSignatureExtractor`
- [ ] Implement `APKDexExtractor`
- [ ] Implement `APKResourceExtractor`
- [ ] Implement `APKNativeLibExtractor`
- [ ] Implement `APKInconsistencyTestsExtractor`
- [ ] Add APK tags to `redb/extractors/enum.py`
- [ ] Add APK dataclasses to `redb/models/dataclasses.py`
- [ ] Add `permhash` field to `Hashes` dataclass
- [ ] Wire up APK dispatch in `redb/ingestor.py`
- [ ] Integrate `permhash` computation in `HashExtractor`
- [ ] Create ClickHouse migration scripts for new tables
- [ ] Unit tests per extractor
- [ ] Integration test: full APK ingestion pipeline
- [ ] Cross-validate output against VirusTotal for reference samples