Raluca Ada Popa

111 papers A* 32A 5Misc 6Journal 59Unranked 7
YearRankTypeTitle / Venue / Authors
2025 J jnl
CoRR
Mikel Rodriguez, Raluca Ada Popa, Four Flynn, Lihao Liang, Allan Dafoe, Anna Wang
2025 J jnl
CoRR
Rohin Shah, Alex Irpan, Alexander Matt Turner, Anna Wang, Arthur Conmy, David Lindner, Jonah Brown-Cohen, Lewis Ho, Neel Nanda, Raluca Ada Popa, Rishub Jain, Rory Greig, Samuel Albanie, Scott Emmons, Sebastian Farquhar, Sébastien Krier, Senthooran Rajamanoharan, Sophie Bridgers, Tobi Ijitoye, Tom Everitt, Victoria Krakovna, Vikrant Varma, Vladimir Mikulik, Zachary Kenton, Dave Orr, Shane Legg, Noah D. Goodman, Allan Dafoe, Four Flynn, Anca D. Dragan
2025 J jnl
CoRR
Xiaoyuan Zhu, Yaowen Ye, Tianyi Qiu, Hanlin Zhu, Sijun Tan, Ajraf Mannan, Jonathan Michala, Raluca Ada Popa, Willie Neiswanger
2025 J jnl
CoRR
Kyle Montgomery, Sijun Tan, Yuqi Chen, Siyuan Zhuang, Tianjun Zhang, Raluca Ada Popa, Chenguang Wang
2025 A* conf
OSDI
Jinhao Zhu, Liana Patel, Matei Zaharia, Raluca Ada Popa
2025 A conf
EuroS&P
Vivian Fang, Emma Dauterman, Akshay Ravoor, Akshit Dewan, Raluca Ada Popa
2025 J jnl
IACR Cryptol. ePrint Arch.
Vivian Fang, Emma Dauterman, Akshay Ravoor, Akshit Dewan, Raluca Ada Popa
2025 J jnl
CoRR
Sam Kumar, Samyukta Yagati, Conor Power, David E. Culler, Raluca Ada Popa
2025 J jnl
CoRR
Jinhao Zhu, Kevin Tseng, Gil Vernik, Xiao Huang, Shishir G. Patil, Vivian Fang, Raluca Ada Popa
2025 A* conf
SP
Darya Kaviani, Deevashwer Rathee, Bhargav Annem, Raluca Ada Popa
2025 J jnl
IACR Cryptol. ePrint Arch.
Darya Kaviani, Deevashwer Rathee, Bhargav Annem, Raluca Ada Popa
2025 J jnl
CoRR
Julien Piet, Vivian Fang, Rishi Khare, Vern Paxson, Raluca Ada Popa, David A. Wagner
2025 J jnl
IACR Cryptol. ePrint Arch.
Mayank Rathee, Keewoo Lee, Raluca Ada Popa
2024 J jnl
IACR Cryptol. ePrint Arch.
Jinhao Zhu, Liana Patel, Matei Zaharia, Raluca Ada Popa
2024 J jnl
Commun. ACM
Raluca Ada Popa
2024 J jnl
ACM Queue
Raluca Ada Popa
2024 A* conf
OSDI
Darya Kaviani, Sijun Tan, Pravein Govindan Kannan, Raluca Ada Popa
2024 J jnl
IACR Cryptol. ePrint Arch.
Darya Kaviani, Sijun Tan, Pravein Govindan Kannan, Raluca Ada Popa
2024 J jnl
CoRR
Shishir G. Patil, Tianjun Zhang, Vivian Fang, Noppapon C., Roy Huang, Aaron Hao, Martin Casado, Joseph E. Gonzalez, Raluca Ada Popa, Ion Stoica
2024 J jnl
CoRR
Sijun Tan, Siyuan Zhuang, Kyle Montgomery, William Yuan Tang, Alejandro Cuadron, Chenguang Wang, Raluca Ada Popa, Ion Stoica
2024 J jnl
CoRR
Sijun Tan, Xiuyu Li, Shishir G. Patil, Ziyang Wu, Tianjun Zhang, Kurt Keutzer, Joseph E. Gonzalez, Raluca Ada Popa
2024 A* conf
SP
Jean-Luc Watson, Tess Despres, Alvin Tan, Shishir G. Patil, Prabal Dutta, Raluca Ada Popa
2024 J jnl
IACR Cryptol. ePrint Arch.
Jean-Luc Watson, Tess Despres, Alvin Tan, Shishir G. Patil, Prabal Dutta, Raluca Ada Popa
2024 A* conf
SP
Mayank Rathee, Yuwen Zhang, Henry Corrigan-Gibbs, Raluca Ada Popa
2024 J jnl
IACR Cryptol. ePrint Arch.
Mayank Rathee, Yuwen Zhang, Henry Corrigan-Gibbs, Raluca Ada Popa
2024 conf
IPSN
Jean-Luc Watson, Saharsh Agrawal, Ryan Tsang, Sherry Luo, Raluca Ada Popa, Prabal Dutta
2024 A* conf
OSDI
Graeme Connell, Vivian Fang, Rolfe Schmidt, Emma Dauterman, Raluca Ada Popa
2024 J jnl
IACR Cryptol. ePrint Arch.
Graeme Connell, Vivian Fang, Rolfe Schmidt, Emma Dauterman, Raluca Ada Popa
2023 A* conf
SP
Mayank Rathee, Conghao Shen, Sameer Wagh, Raluca Ada Popa
2023 A* conf
SP
Sijun Tan, Weikeng Chen, Ryan Deng, Raluca Ada Popa
2022 A conf
EuroS&P
Vivian Fang, Lloyd Brown, William Lin, Wenting Zheng, Aurojit Panda, Raluca Ada Popa
2022 J jnl
IACR Cryptol. ePrint Arch.
Vivian Fang, Lloyd Brown, William Lin, Wenting Zheng, Aurojit Panda, Raluca Ada Popa
2022 J jnl
IACR Cryptol. ePrint Arch.
Mayank Rathee, Conghao Shen, Sameer Wagh, Raluca Ada Popa
2022 J jnl
Commun. ACM
Luiz André Barroso, Tanzeem Choudhury, Manish Gupta, Oyekunle A. Olukotun, Raluca Ada Popa, Dawn Xiaodong Song, David A. Patterson
2022 A* conf
USENIX Security Symposium
Jean-Luc Watson, Sameer Wagh, Raluca Ada Popa
2022 J jnl
IACR Cryptol. ePrint Arch.
Jean-Luc Watson, Sameer Wagh, Raluca Ada Popa
2022 Misc conf
HotNets
Emma Dauterman, Vivian Fang, Natacha Crooks, Raluca Ada Popa
2022 J jnl
CoRR
Emma Dauterman, Vivian Fang, Natacha Crooks, Raluca Ada Popa
2022 J jnl
CoRR
Sarah E. Chasins, Alvin Cheung, Natacha Crooks, Ali Ghodsi, Ken Goldberg, Joseph E. Gonzalez, Joseph M. Hellerstein, Michael I. Jordan, Anthony D. Joseph, Michael W. Mahoney, Aditya G. Parameswaran, David A. Patterson, Raluca Ada Popa, Koushik Sen, Scott Shenker, Dawn Song, Ion Stoica
2022 A* conf
SP
Emma Dauterman, Mayank Rathee, Raluca Ada Popa, Ion Stoica
2021 A* conf
USENIX Security Symposium
Wenting Zheng, Ryan Deng, Weikeng Chen, Raluca Ada Popa, Aurojit Panda, Ion Stoica
2021 J jnl
IACR Cryptol. ePrint Arch.
Wenting Zheng, Ryan Deng, Weikeng Chen, Raluca Ada Popa, Aurojit Panda, Ion Stoica
2021 J jnl
IACR Cryptol. ePrint Arch.
Weikeng Chen, Katerina Sotiraki, Ian Chang, Murat Kantarcioglu, Raluca Ada Popa
2021 A* conf
OSDI
Sam Kumar, David E. Culler, Raluca Ada Popa
2021 J jnl
CoRR
Sam Kumar, David E. Culler, Raluca Ada Popa
2021 J jnl
IACR Cryptol. ePrint Arch.
Ryan Lehmkuhl, Pratyush Mishra, Akshayaram Srinivasan, Raluca Ada Popa
2021 A* conf
SP
Yuncong Hu, Kian Hooshmand, Harika Kalidhindi, Seung Jin Yang, Raluca Ada Popa
2021 J jnl
IACR Cryptol. ePrint Arch.
Yuncong Hu, Kian Hooshmand, Harika Kalidhindi, Seung Jin Yang, Raluca Ada Popa
2021 A* conf
USENIX Security Symposium
Ryan Lehmkuhl, Pratyush Mishra, Akshayaram Srinivasan, Raluca Ada Popa
2021 J jnl
IACR Cryptol. ePrint Arch.
Weikeng Chen, Ryan Deng, Raluca Ada Popa
2021 A* conf
USENIX Security Symposium
Jeongseok Son, Griffin Prechter, Rishabh Poddar, Raluca Ada Popa, Koushik Sen
2021 A* conf
USENIX Security Symposium
Rishabh Poddar, Sukrit Kalra, Avishay Yanai, Ryan Deng, Raluca Ada Popa, Joseph M. Hellerstein
2021 A* conf
SOSP
Emma Dauterman, Vivian Fang, Ioannis Demertzis, Natacha Crooks, Raluca Ada Popa
2021 J jnl
IACR Cryptol. ePrint Arch.
Emma Dauterman, Vivian Fang, Ioannis Demertzis, Natacha Crooks, Raluca Ada Popa
2021 J jnl
IACR Cryptol. ePrint Arch.
Emma Dauterman, Mayank Rathee, Raluca Ada Popa, Ion Stoica
2021 J jnl
Commun. ACM
Johann Schleier-Smith, Vikram Sreekanti, Anurag Khandelwal, João Carreira, Neeraja Jayant Yadwadkar, Raluca Ada Popa, Joseph E. Gonzalez, Ion Stoica, David A. Patterson
2020 A* conf
USENIX Security Symposium
Dayeol Lee, Dongha Jung, Ian T. Fang, Chia-Che Tsai, Raluca Ada Popa
2020 A* conf
USENIX Security Symposium
Chia-Che Tsai, Jeongseok Son, Bhushan Jain, John McAvey, Raluca Ada Popa, Donald E. Porter
2020 A* conf
OSDI
Emma Dauterman, Eric Feng, Ellen Luo, Raluca Ada Popa, Ion Stoica
2020 J jnl
IACR Cryptol. ePrint Arch.
Emma Dauterman, Eric Feng, Ellen Luo, Raluca Ada Popa, Ion Stoica
2020 A* conf
USENIX Security Symposium
Pratyush Mishra, Ryan Lehmkuhl, Akshayaram Srinivasan, Wenting Zheng, Raluca Ada Popa
2020 J jnl
IACR Cryptol. ePrint Arch.
Pratyush Mishra, Ryan Lehmkuhl, Akshayaram Srinivasan, Wenting Zheng, Raluca Ada Popa
2020 conf
PPMLP@CCS
Pratyush Mishra, Ryan Lehmkuhl, Akshayaram Srinivasan, Wenting Zheng, Raluca Ada Popa
2020 Misc conf
NSDI
Yuncong Hu, Sam Kumar, Raluca Ada Popa
2020 J jnl
IACR Cryptol. ePrint Arch.
Yuncong Hu, Sam Kumar, Raluca Ada Popa
2020 conf
PPMLP@CCS
Raluca Ada Popa
2020 A* conf
NDSS
Weikeng Chen, Raluca Ada Popa
2020 J jnl
IACR Cryptol. ePrint Arch.
Weikeng Chen, Raluca Ada Popa
2020 A conf
EuroSys
Ankur Dave, Chester Leung, Raluca Ada Popa, Joseph E. Gonzalez, Ion Stoica
2020 A* conf
CCS
Benyu Zhang, Matei Zaharia, Shouling Ji, Raluca Ada Popa, Guofei Gu
2020 ed.
PPMLP@CCS
Benyu Zhang, Raluca Ada Popa, Matei Zaharia, Guofei Gu, Shouling Ji
2020 A conf
EuroS&P
Rishabh Poddar, Stephanie Wang, Jianan Lu, Raluca Ada Popa
2020 J jnl
CoRR
Rishabh Poddar, Stephanie Wang, Jianan Lu, Raluca Ada Popa
2020 conf
PPMLP@CCS
Andrew Law, Chester Leung, Rishabh Poddar, Raluca Ada Popa, Chenyu Shi, Octavian Sima, Chaofan Yu, Xingmeng Zhang, Wenting Zheng
2020 J jnl
CoRR
Andrew Law, Chester Leung, Rishabh Poddar, Raluca Ada Popa, Chenyu Shi, Octavian Sima, Chaofan Yu, Xingmeng Zhang, Wenting Zheng
2020 J jnl
CoRR
Rishabh Poddar, Sukrit Kalra, Avishay Yanai, Ryan Deng, Raluca Ada Popa, Joseph M. Hellerstein
2020 J jnl
IACR Cryptol. ePrint Arch.
Rishabh Poddar, Sukrit Kalra, Avishay Yanai, Ryan Deng, Raluca Ada Popa, Joseph M. Hellerstein
2020 A* conf
USENIX Security Symposium
Rishabh Poddar, Ganesh Ananthanarayanan, Srinath T. V. Setty, Stavros Volos, Raluca Ada Popa
2020 J jnl
CoRR
Rishabh Poddar, Ganesh Ananthanarayanan, Srinath T. V. Setty, Stavros Volos, Raluca Ada Popa
2019 J jnl
CoRR
Dayeol Lee, Dongha Jung, Ian T. Fang, Chia-Che Tsai, Raluca Ada Popa
2019 J jnl
Proc. VLDB Endow.
Rishabh Poddar, Tobias Boelter, Raluca Ada Popa
2019 J jnl
CoRR
Eric Jonas, Johann Schleier-Smith, Vikram Sreekanti, Chia-Che Tsai, Anurag Khandelwal, Qifan Pu, Vaishaal Shankar, João Carreira, Karl Krauth, Neeraja Jayant Yadwadkar, Joseph E. Gonzalez, Raluca Ada Popa, Ion Stoica, David A. Patterson
2019 A* conf
IEEE Symposium on Security and Privacy
Wenting Zheng, Raluca Ada Popa, Joseph E. Gonzalez, Ion Stoica
2019 J jnl
CoRR
Wenting Zheng, Raluca Ada Popa, Joseph E. Gonzalez, Ion Stoica
2019 A* conf
USENIX Security Symposium
Sam Kumar, Yuncong Hu, Michael P. Andersen, Raluca Ada Popa, David E. Culler
2019 J jnl
CoRR
Sam Kumar, Yuncong Hu, Michael P. Andersen, Raluca Ada Popa, David E. Culler
2019 J jnl
IACR Cryptol. ePrint Arch.
Stephanie Wang, Rishabh Poddar, Jianan Lu, Raluca Ada Popa
2019 conf
SoCC
Raluca Ada Popa
2019 A* conf
USENIX Security Symposium
Michael P. Andersen, Sam Kumar, Moustafa AbdelBaky, Gabe Fierro, John Kolb, Hyung-Sin Kim, David E. Culler, Raluca Ada Popa
2018 A* conf
USENIX Security Symposium
Howard Wu, Wenting Zheng, Alessandro Chiesa, Raluca Ada Popa, Ion Stoica
2018 J jnl
IACR Cryptol. ePrint Arch.
Howard Wu, Wenting Zheng, Alessandro Chiesa, Raluca Ada Popa, Ion Stoica
2018 A* conf
IEEE Symposium on Security and Privacy
Pratyush Mishra, Rishabh Poddar, Jerry Chen, Alessandro Chiesa, Raluca Ada Popa
2018 Misc conf
NSDI
Rishabh Poddar, Chang Lan, Raluca Ada Popa, Sylvia Ratnasamy
2017 J jnl
CoRR
Ion Stoica, Dawn Song, Raluca Ada Popa, David A. Patterson, Michael W. Mahoney, Randy H. Katz, Anthony D. Joseph, Michael I. Jordan, Joseph M. Hellerstein, Joseph E. Gonzalez, Ken Goldberg, Ali Ghodsi, David E. Culler, Pieter Abbeel
2017 A conf
EuroSys
Wenting Zheng, Frank H. Li, Raluca Ada Popa, Ion Stoica, Rachit Agarwal
2017 Misc conf
NSDI
Wenting Zheng, Ankur Dave, Jethro G. Beekman, Raluca Ada Popa, Joseph E. Gonzalez, Ion Stoica
2016 J jnl
IACR Cryptol. ePrint Arch.
Tobias Boelter, Rishabh Poddar, Raluca Ada Popa
2016 J jnl
IACR Cryptol. ePrint Arch.
Rishabh Poddar, Tobias Boelter, Raluca Ada Popa
2016 J jnl
IACR Cryptol. ePrint Arch.
Raluca Ada Popa, Emily Stark, Jonas Helfer, Steven Valdez, Nickolai Zeldovich, M. Frans Kaashoek, Hari Balakrishnan
2016 Misc conf
NSDI
Chang Lan, Justine Sherry, Raluca Ada Popa, Sylvia Ratnasamy, Zhi Liu
2016 A* conf
IEEE Symposium on Security and Privacy
Nikolaos Karapanos, Alexandros Filios, Raluca Ada Popa, Srdjan Capkun
2015 A* conf
SIGCOMM
Justine Sherry, Chang Lan, Raluca Ada Popa, Sylvia Ratnasamy
2015 J jnl
IACR Cryptol. ePrint Arch.
Justine Sherry, Chang Lan, Raluca Ada Popa, Sylvia Ratnasamy
2015 J jnl
IACR Cryptol. ePrint Arch.
Raluca Ada Popa, Nickolai Zeldovich, Hari Balakrishnan
2015 A* conf
NDSS
Raphael Bost, Raluca Ada Popa, Stephen Tu, Shafi Goldwasser
2014 Misc conf
NSDI
Raluca Ada Popa, Emily Stark, Steven Valdez, Jonas Helfer, Nickolai Zeldovich, Hari Balakrishnan
2014 J jnl
login Usenix Mag.
Raluca Ada Popa, Emily Stark, Jonas Helfer, Steven Valdez, Nickolai Zeldovich, M. Frans Kaashoek, Hari Balakrishnan
2014
Raluca Ada Popa
2014 J jnl
IACR Cryptol. ePrint Arch.
Raphael Bost, Raluca Ada Popa, Stephen Tu, Shafi Goldwasser
2011 conf
USENIX ATC
Raluca Ada Popa, Jacob R. Lorch, David Molnar, Helen J. Wang, Li Zhuang
2009 conf
USENIX ATC
James A. Cowling, Dan R. K. Ports, Barbara Liskov, Raluca Ada Popa, Abhijeet Gaikwad
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