Hamid Seridi

82 papers C 4Misc 1Journal 59Unranked 12
YearRankTypeTitle / Venue / Authors
2026 J jnl
Comput. Electr. Eng.
Abdelbacet Brahmia, Zineddine Kouahla, Ala-Eddine Benrazek, Brahim Farou, Hamid Seridi
2026 J jnl
Knowl. Inf. Syst.
Ilyas Ziaya, Brahim Farou, Zineddine Kouahla, Hamid Seridi, Muhammet Kurulay
2025 J jnl
Clust. Comput.
Ilyas Ziaya, Brahim Farou, Zineddine Kouahla, Hamid Seridi
2025 J jnl
Appl. Intell.
Karima Benhamza, Razane Benselim, Hanane Naidja, Hamid Seridi
2025 J jnl
ACM Trans. Asian Low Resour. Lang. Inf. Process.
Djalila Boughareb, Rima Boughareb, Samir Hallaci, Mohammed Raid Elislam Boukherouba, Hamid Seridi
2025 ed.
IAM
Zinnedine Kouahla, Karima Benhamza, Samir Hallaci, Brahim Farou, Hamid Seridi, Muhammet Kurulay
2024 J jnl
Scalable Comput. Pract. Exp.
Ouarda Zedadra, Amina Zedadra, Antonio Guerrieri, Hamid Seridi, Douaa Ghelis
2024 conf
RIF
Djalila Boughareb, Said Bouteldja, Hamid Seridi
2024 J jnl
Evol. Syst.
Karima Benhamza, Hamid Seridi, Meriem Agguini, Amel Bentagine
2024 conf
IAM
Djallel Hamouda, Mohamed Amine Ferrag, Nadjette Benhamida, Hamid Seridi
2024 J jnl
Evol. Intell.
Roguia Siouda, Mohamed Nemissi, Hamid Seridi
2024 J jnl
CoRR
Ala-Eddine Benrazek, Zineddine Kouahla, Brahim Farou, Hamid Seridi, Ibtissem Kemouguette
2024 conf
IAM
Rania Farah, Brahim Farou, Zineddine Kouahla, Hamid Seridi
2024 J jnl
Big Data Cogn. Comput.
Ouarda Zedadra, Antonio Guerrieri, Hamid Seridi, Aymen Benzaid, Giancarlo Fortino
2024 J jnl
Evol. Syst.
Samir Hallaci, Brahim Farou, Zineddine Kouahla, Hamid Seridi
2024 J jnl
Internet Things
Djallel Hamouda, Mohamed Amine Ferrag, Nadjette Benhamida, Hamid Seridi, Mohamed Chahine Ghanem
2023 J jnl
Internet Technol. Lett.
Karima Khettabi, Zineddine Kouahla, Brahim Farou, Hamid Seridi, Mohamed Amine Ferrag
2023 J jnl
Int. J. Sens. Networks
Zahia Mabrek, Brahim Farou, Zineddine Kouahla, Nadjette Benhamida, Hamid Seridi
2023 J jnl
Evol. Syst.
Roguia Siouda, Mohamed Nemissi, Hamid Seridi
2023 J jnl
Sensors
Amira Bourechak, Ouarda Zedadra, Mohamed Nadjib Kouahla, Antonio Guerrieri, Hamid Seridi, Giancarlo Fortino
2023 J jnl
Big Data Cogn. Comput.
Karima Khettabi, Zineddine Kouahla, Brahim Farou, Hamid Seridi, Mohamed Amine Ferrag
2023 J jnl
Pervasive Mob. Comput.
Aymen Dia Eddine Berini, Mohamed Amine Ferrag, Brahim Farou, Hamid Seridi
2023 J jnl
Internet Things
Ala-Eddine Benrazek, Brahim Farou, Zineddine Kouahla, Mohamed Amine Ferrag, Hamid Seridi
2023 J jnl
Pervasive Mob. Comput.
Djallel Hamouda, Mohamed Amine Ferrag, Nadjette Benhamida, Hamid Seridi
2023 ed.
IAM
Brahim Farou, Zinnedine Kouahla, Khaled Halimi, Hamid Seridi, Muhammet Kurulay
2023 J jnl
J. King Saud Univ. Comput. Inf. Sci.
Lynda Djakhdjakha, Brahim Farou, Hamid Seridi, Hamadoun Cisse
2023 J jnl
J. Supercomput.
Ala-Eddine Benrazek, Zineddine Kouahla, Brahim Farou, Hamid Seridi, Imane Allele, Mohamed Amine Ferrag
2022 C conf
ISPR
Karima Benhamza, Ines Guerziz, Amel Bentagine, Hamid Seridi
2022 J jnl
Future Internet
Zineddine Kouahla, Ala-Eddine Benrazek, Mohamed Amine Ferrag, Brahim Farou, Hamid Seridi, Muhammet Kurulay, Adeel Anjum, Alia Asheralieva
2022 J jnl
Internet Technol. Lett.
Aymen Dia Eddine Berini, Brahim Farou, Mohamed Amine Ferrag, Hamid Seridi, Herman Akdag
2022 J jnl
Trans. Emerg. Telecommun. Technol.
Karima Khettabi, Zineddine Kouahla, Brahim Farou, Hamid Seridi, Mohamed Amine Ferrag
2022 J jnl
CoRR
Ala-Eddine Benrazek, Zineddine Kouahla, Brahim Farou, Hamid Seridi, Imane Allele
2022 J jnl
Big Data Cogn. Comput.
Ouarda Zedadra, Antonio Guerrieri, Hamid Seridi
2021 J jnl
Evol. Syst.
Karima Benhamza, Hamid Seridi
2021 J jnl
Prog. Artif. Intell.
Roguia Siouda, Mohamed Nemissi, Hamid Seridi
2021 conf
ISC2
Karima Khettabi, Zineddine Kouahla, Brahim Farou, Hamid Seridi
2021 J jnl
IEEE Access
Wafa Nebili, Brahim Farou, Zineddine Kouahla, Hamid Seridi
2020 J jnl
Int. J. Cooperative Inf. Syst.
Djalila Boughareb, Abdennour Khobizi, Rima Boughareb, Nadir Farah, Hamid Seridi
2020 J jnl
Trans. Emerg. Telecommun. Technol.
Ala-Eddine Benrazek, Zineddine Kouahla, Brahim Farou, Mohamed Amine Ferrag, Hamid Seridi, Muhammet Kurulay
2020 J jnl
Multim. Tools Appl.
Wafa Nebili, Brahim Farou, Hamid Seridi
2020 conf
ICAASE
Rachid Zaghdoudi, Hamid Seridi, Adel Boudiaf, Slimane Ziani
2020 ed.
IAM
Hamid Seridi, Muhammet Kurulay, Mohamed Nadjib Kouahla, Zineddine Kouahla, Brahim Farou, Mohamed Amine Ferrag, Khaled Halimi
2020 J jnl
J. King Saud Univ. Comput. Inf. Sci.
Nadia Guerroui, Hamid Seridi
2020 ed.
CITSC
Hamid Seridi, Muhammet Kurulay, Zineddine Kouahla, Brahim Farou, Mohamed Nadjib Kouahla, Mohamed Amine Ferrag, Mohamed Amine Boudia
2019 conf
IDCS
Ouarda Zedadra, Antonio Guerrieri, Hamid Seridi, Giancarlo Fortino
2019 J jnl
IET Wirel. Sens. Syst.
Ala-Eddine Benrazek, Brahim Farou, Hamid Seridi, Zineddine Kouahla, Muhammet Kurulay
2019 J jnl
Int. J. Syst. Sci.
Houssem Eddine Rouabhia, Brahim Farou, Zine Eddine Kouahla, Hamid Seridi, Herman Akdag
2019 conf
JERI
Wafa Nebili, Brahim Farou, Hamid Seridi
2019 J jnl
J. Circuits Syst. Comput.
Karima Benhamza, Hamid Seridi
2019 J jnl
Int. J. Fuzzy Syst. Appl.
Halima Salah, Mohamed Nemissi, Hamid Seridi, Herman Akdag
2019 ch.
The Internet of Things for Smart Urban Ecosystems
Ouarda Zedadra, Antonio Guerrieri, Nicolas Jouandeau, Giandomenico Spezzano, Hamid Seridi, Giancarlo Fortino
2019 J jnl
Int. J. Strateg. Inf. Technol. Appl.
Wafa Nebili, Brahim Farou, Hamid Seridi
2018 J jnl
Informatica (Slovenia)
Hocine Merabti, Brahim Farou, Hamid Seridi
2018 J jnl
J. Intell. Fuzzy Syst.
Zineddine Kouahla, Adeel Anjum, Hamid Seridi
2018 J jnl
J. Parallel Distributed Comput.
Ouarda Zedadra, Antonio Guerrieri, Nicolas Jouandeau, Giandomenico Spezzano, Hamid Seridi, Giancarlo Fortino
2017 J jnl
Int. Arab J. Inf. Technol.
Rachid Zaghdoudi, Hamid Seridi
2017 J jnl
Eng. Appl. Artif. Intell.
Brahim Farou, Mohamed Nadjib Kouahla, Hamid Seridi, Herman Akdag
2017 J jnl
Int. Arab J. Inf. Technol.
Amine Benzenache, Hamid Seridi, Herman Akdag
2017 J jnl
Complex Adapt. Syst. Model.
Ouarda Zedadra, Nicolas Jouandeau, Hamid Seridi, Giancarlo Fortino
2017 J jnl
Comput. Informatics
Brahim Farou, Houssam Rouabhia, Hamid Seridi, Herman Akdag
2017 conf
IDCS
Ouarda Zedadra, Claudio Savaglio, Nicolas Jouandeau, Antonio Guerrieri, Hamid Seridi, Giancarlo Fortino
2016 J jnl
Eng. Appl. Artif. Intell.
Ouarda Zedadra, Hamid Seridi, Nicolas Jouandeau, Giancarlo Fortino
2016 J jnl
Int. J. Knowl. Based Intell. Eng. Syst.
Hocine Merabti, Mohamed Nadjib Kouahla, Hamid Seridi
2016 Misc conf
IDC
Ouarda Zedadra, Nicolas Jouandeau, Hamid Seridi, Giancarlo Fortino
2016 J jnl
Int. Arab J. Inf. Technol.
Brahim Farou, Hamid Seridi, Herman Akdag
2016 J jnl
Int. J. Web Based Learn. Teach. Technol.
Safia Bendjebar, Yacine Lafifi, Hamid Seridi
2015 J jnl
J. Intell. Fuzzy Syst.
Karima Benhamza, Hamid Seridi
2015 J jnl
Scalable Comput. Pract. Exp.
Ouarda Zedadra, Hamid Seridi, Nicolas Jouandeau, Giancarlo Fortino
2015 C conf
CSCWD
Ouarda Zedadra, Hamid Seridi, Nicolas Jouandeau, Giancarlo Fortino
2015 C conf
FedCSIS
Ouarda Zedadra, Hamid Seridi, Nicolas Jouandeau, Giancarlo Fortino
2015 conf
CIIA
Brahim Farou, Hamid Seridi, Herman Akdag
2015 J jnl
Int. J. Comput. Intell. Syst.
Ismahane Souici, Hamid Seridi, Herman Akdag
2014 conf
WOA
Giancarlo Fortino, Ouarda Zedadra, Nicolas Jouandeau, Hamid Seridi
2014 J jnl
J. Intell. Fuzzy Syst.
Mohamed Nemissi, Hamid Seridi, Herman Akdag
2014 C conf
FedCSIS
Ouarda Zedadra, Nicolas Jouandeau, Hamid Seridi, Giancarlo Fortino
2013 ch.
Modeling Approaches and Algorithms for Advanced Computer Applications
Brahim Farou, Hamid Seridi, Herman Akdag
2009 conf
CIIA
Mohamed lamine Semassel, Ismahane Souici, Hamid Seridi
2009 J jnl
J. Adv. Comput. Intell. Intell. Informatics
Houria Boudouda, Mohamed Nemissi, Hamid Seridi, Herman Akdag
2009 conf
CIIA
Brahim Farou, Samir Hallaci, Hamid Seridi
2008 J jnl
Int. J. Neural Syst.
Mohamed Nemissi, Hamid Seridi, Herman Akdag
2006 J jnl
J. Adv. Comput. Intell. Intell. Informatics
Hamid Seridi, Herman Akdag, Rachid Mansouri, Mohamed Nemissi
2001 J jnl
J. Adv. Comput. Intell. Intell. Informatics
Hamid Seridi, Herman Akdag
redb/extractors/decompiler/apk/analyzer.py
← Index redb/extractors/decompiler/apk/analyzer.py python
"""APK Code Analyzer — orchestrates androguard + JADX + apktool analysis.

This is the APK equivalent of BinaryNinjaDecompiler in
redb/extractors/decompiler/bninja/decompiler.py.
"""

import math
import os
import tempfile
from collections import Counter
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Any, Dict, List, Optional, Set

from redb.extractors.decompiler.apk.apktool_wrapper import ApktoolDisassembler
from redb.extractors.decompiler.apk.jadx_wrapper import JADXDecompiler
from redb.extractors.decompiler.apk.library_filter import LibraryFilter
from redb.extractors.decompiler.apk.method_extractor import (
    compute_minhash,
    compute_prime_product_smali,
    compute_sha256,
    compute_ssdeep,
    compute_tlsh,
    count_call_instructions,
    dalvik_to_java_class,
    dalvik_to_java_prototype,
    detect_obfuscation_indicators,
)
from redb.extractors.decompiler.apk.smali_normalization import normalize_method_body
from redb.extractors.decompiler.apk.smali_cfg import compute_cfg_metrics
from redb.extractors.decompiler.apk.smali_parser import SmaliParser


class APKCodeAnalyzer:
    """Orchestrates APK code analysis combining three tools.

    Combines androguard (call graphs, xrefs, method enumeration),
    JADX (Java decompilation), and apktool (smali disassembly).
    """

    def __init__(
        self,
        filepath: str,
        timeout: int = 600,
        log=None,
        decompile_modules: Set[str] = None,
    ):
        self.filepath = filepath
        self.timeout = timeout
        self.log = log
        self.decompile_modules = decompile_modules or {"all"}
        self.min_instructions = int(
            os.getenv("APK_MIN_METHOD_INSTRUCTIONS", "5")
        )
        self.library_filter = LibraryFilter()
        self.jadx = JADXDecompiler(log=log)
        self.apktool = ApktoolDisassembler(log=log)

        self._temp_dirs = []

    def extract(self) -> Dict[str, Any]:
        """Run full APK code analysis.

        Returns dict with keys:
            decompiled_content, decompiled_refs,
            smali_content, smali_refs,
            similarity_metrics, strings, analysis_errors
        """
        results = {
            "decompiled_content": [],
            "decompiled_refs": [],
            "smali_content": [],
            "smali_refs": [],
            "similarity_metrics": [],
            "cfg": [],
            "strings": [],
            "analysis_errors": [],
        }

        # Create temp directories
        jadx_dir = tempfile.mkdtemp(prefix="redb_jadx_")
        apktool_dir = tempfile.mkdtemp(prefix="redb_apktool_")
        self._temp_dirs.extend([jadx_dir, apktool_dir])

        # Step 1-3: Run JADX, apktool, and androguard in parallel
        jadx_success = False
        apktool_success = False
        androguard_result = None

        with ThreadPoolExecutor(max_workers=3) as executor:
            futures = {}
            futures[executor.submit(self.jadx.decompile, self.filepath, jadx_dir)] = "jadx"
            futures[executor.submit(self.apktool.disassemble, self.filepath, apktool_dir)] = "apktool"
            futures[executor.submit(self._run_androguard)] = "androguard"

            for future in as_completed(futures):
                tool = futures[future]
                try:
                    result = future.result()
                    if tool == "jadx":
                        jadx_success = result
                    elif tool == "apktool":
                        apktool_success = result
                    elif tool == "androguard":
                        androguard_result = result
                except Exception as e:
                    if self.log:
                        self.log.error(f"{tool} failed: {e}")
                    results["analysis_errors"].append({
                        "class_name": None,
                        "method_name": None,
                        "error_location": tool,
                        "error_message": str(e),
                        "error_type": type(e).__name__,
                    })

        if androguard_result is None:
            if self.log:
                self.log.error("Androguard analysis failed — cannot proceed")
            return results

        apk_obj, dexs, analysis = androguard_result

        # Step 4b: Extract strings from DEX files
        try:
            results["strings"] = self._extract_strings(dexs)
        except Exception as e:
            if self.log:
                self.log.error(f"String extraction failed: {e}")
            results["analysis_errors"].append({
                "class_name": None,
                "method_name": None,
                "error_location": "strings",
                "error_message": f"String extraction failed: {e}",
                "error_type": type(e).__name__,
            })

        # Step 5: Parse smali output
        smali_methods = {}
        if apktool_success:
            try:
                smali_dirs = self.apktool.get_smali_directories(apktool_dir)
                for sdir in smali_dirs:
                    smali_methods.update(SmaliParser.parse_smali_directory(sdir))
            except Exception as e:
                if self.log:
                    self.log.error(f"Smali parsing failed: {e}")
                results["analysis_errors"].append({
                    "class_name": None,
                    "method_name": None,
                    "error_location": "apktool",
                    "error_message": f"Smali parsing failed: {e}",
                    "error_type": type(e).__name__,
                })

        # Step 6: Parse Java output
        java_methods = {}
        if jadx_success:
            try:
                java_methods = self.jadx.parse_java_methods(jadx_dir)
            except Exception as e:
                if self.log:
                    self.log.error(f"Java parsing failed: {e}")
                results["analysis_errors"].append({
                    "class_name": None,
                    "method_name": None,
                    "error_location": "jadx",
                    "error_message": f"Java parsing failed: {e}",
                    "error_type": type(e).__name__,
                })

        if not apktool_success and self.log:
            self.log.info(
                "apktool failed — falling back to androguard disassembly"
            )

        # Step 7-8: Process each method from androguard
        seen_decompiled_hashes = set()
        seen_smali_hashes = set()

        try:
            for method in analysis.get_methods():
                try:
                    self._process_method(
                        method,
                        smali_methods,
                        java_methods,
                        results,
                        seen_decompiled_hashes,
                        seen_smali_hashes,
                    )
                except Exception as e:
                    method_name = "unknown"
                    class_name = "unknown"
                    try:
                        if not method.is_external():
                            enc = method.get_method()
                            class_name = enc.get_class_name()
                            method_name = enc.get_name()
                    except Exception:
                        pass
                    results["analysis_errors"].append({
                        "class_name": class_name,
                        "method_name": method_name,
                        "error_location": "analysis",
                        "error_message": str(e),
                        "error_type": type(e).__name__,
                    })
        except Exception as e:
            if self.log:
                self.log.error(f"Method enumeration failed: {e}")
            results["analysis_errors"].append({
                "class_name": None,
                "method_name": None,
                "error_location": "androguard",
                "error_message": f"Method enumeration failed: {e}",
                "error_type": type(e).__name__,
            })

        if self.log:
            stats = self.library_filter.get_filter_stats()
            self.log.info(
                f"APK analysis complete: {stats['user']} user methods, "
                f"{stats['library']} library methods filtered, "
                f"{len(results['decompiled_content'])} decompiled, "
                f"{len(results['smali_content'])} smali, "
                f"{len(results['strings'])} strings"
            )

        return results

    def _run_androguard(self):
        """Run androguard analysis on the APK."""
        from androguard.misc import AnalyzeAPK
        return AnalyzeAPK(self.filepath)

    def _process_method(
        self,
        method,
        smali_methods: Dict,
        java_methods: Dict,
        results: Dict,
        seen_decompiled: set,
        seen_smali: set,
    ):
        """Process a single method from androguard analysis."""
        # Skip external methods (no code body)
        if method.is_external():
            return

        encoded = method.get_method()
        class_name = encoded.get_class_name()
        method_name = encoded.get_name()
        descriptor = encoded.get_descriptor()

        # Build method key for cross-tool matching
        method_key = SmaliParser.make_method_key(class_name, method_name, descriptor)

        # Check if library
        is_lib = self.library_filter.is_library(class_name)
        method_type = "LIBRARY" if is_lib else "USER"

        # Skip library methods for content tables (but they're still in xrefs)
        if is_lib:
            return

        # Get xrefs
        # In androguard 4.x, xref tuples are (ClassAnalysis, MethodAnalysis, offset).
        # The MethodAnalysis wrapper doesn't expose get_class_name()/get_name()
        # directly — we need to unwrap via .get_method() first.
        callers = []
        callees = []
        try:
            for ref_class, ref_method, offset in method.get_xref_from():
                try:
                    enc = ref_method.get_method()
                    caller_key = f"{enc.get_class_name()}->{enc.get_name()}"
                except AttributeError:
                    # Fallback for older androguard where ref_method is EncodedMethod
                    caller_key = f"{ref_method.get_class_name()}->{ref_method.get_name()}"
                callers.append(caller_key)
        except Exception:
            pass

        try:
            for ref_class, ref_method, offset in method.get_xref_to():
                try:
                    enc = ref_method.get_method()
                    callee_key = f"{enc.get_class_name()}->{enc.get_name()}"
                except AttributeError:
                    callee_key = f"{ref_method.get_class_name()}->{ref_method.get_name()}"
                callees.append(callee_key)
        except Exception:
            pass

        # Look up smali body
        smali_method = smali_methods.get(method_key)
        smali_body = None
        smali_normalized = None
        smali_hash = None
        instruction_count = 0
        register_count = 0

        if smali_method:
            smali_body = smali_method.body
            instruction_count = smali_method.instruction_count
            register_count = smali_method.register_count
        else:
            # Fallback: use androguard's own disassembler
            smali_body, instruction_count, register_count = (
                self._disassemble_with_androguard(encoded)
            )

        # Apply minimum instruction filter
        if instruction_count < self.min_instructions:
            return

        if smali_body:
            smali_normalized = SmaliParser.normalize_smali_body(smali_body)
            if smali_normalized:
                smali_hash = compute_sha256(smali_normalized)

        if not smali_hash:
            return

        # Compute obfuscation indicators from smali
        obfuscation = detect_obfuscation_indicators(
            method_name, class_name, smali_body, instruction_count
        )

        # Compute CFG metrics early — needed for both content tables and cfg table
        cfg_metrics = None
        if smali_body:
            try:
                cfg_metrics = compute_cfg_metrics(smali_body)
            except Exception:
                pass

        # Smali content (deduplicated)
        if smali_hash and smali_hash not in seen_smali:
            seen_smali.add(smali_hash)
            results["smali_content"].append({
                "smali_method_hash": smali_hash,
                "smali_method": smali_body,
                "smali_method_type": method_type,
                "smali_instructions_count": instruction_count,
                "smali_register_count": register_count,
                "smali_has_string_encryption": obfuscation.get("has_string_encryption", False),
                "smali_has_reflection_calls": obfuscation.get("has_reflection_calls", False),
                "smali_excessive_goto_count": obfuscation.get("excessive_goto_count", False),
                "smali_flattened_score": cfg_metrics.flattened_score if cfg_metrics else 0.0,
                "smali_mba_score": cfg_metrics.mba_score if cfg_metrics else 0.0,
            })

        # Smali reference (per-binary)
        ssdeep_val = compute_ssdeep(smali_normalized) if smali_normalized else None
        tlsh_val = compute_tlsh(smali_normalized) if smali_normalized else None

        # Semantically normalized ssdeep/TLSH (analogous to Binja's ssdeep_llil/tlsh_llil)
        ssdeep_normalized_val = None
        tlsh_normalized_val = None
        if smali_normalized:
            normalized_lines = normalize_method_body(smali_normalized, level="opcode_api")
            if normalized_lines:
                normalized_text = "\n".join(normalized_lines)
                ssdeep_normalized_val = compute_ssdeep(normalized_text)
                tlsh_normalized_val = compute_tlsh(normalized_text)

        # Look up Java source
        java_class = dalvik_to_java_class(class_name)
        java_key_candidates = [
            f"{java_class}.{method_name}",
        ]
        java_source = None
        decompiled_hash = None

        for jk in java_key_candidates:
            for key, source in java_methods.items():
                if key.startswith(jk):
                    java_source = source
                    break
            if java_source:
                break

        if java_source:
            # Normalize and hash decompiled content
            normalized_java = _normalize_java(java_source)
            decompiled_hash = compute_sha256(normalized_java)

            # Decompiled content (deduplicated)
            if decompiled_hash not in seen_decompiled:
                seen_decompiled.add(decompiled_hash)
                results["decompiled_content"].append({
                    "decompiled_method_hash": decompiled_hash,
                    "decompiled_method": java_source,
                    "decompiled_method_type": method_type,
                    "decompiled_has_string_encryption": obfuscation.get("has_string_encryption", False),
                    "decompiled_has_reflection_calls": obfuscation.get("has_reflection_calls", False),
                    "decompiled_excessive_goto_count": obfuscation.get("excessive_goto_count", False),
                })

            # Decompiled reference
            method_prototype = dalvik_to_java_prototype(
                method_name, descriptor, class_name
            )
            results["decompiled_refs"].append({
                "decompiled_method_hash": decompiled_hash,
                "smali_method_hash": smali_hash,
                "decompiled_class_name": dalvik_to_java_class(class_name),
                "decompiled_method_name": method_name,
                "decompiled_method_signature": descriptor,
                "decompiled_method_prototype": method_prototype,
                "functions_caller": callers,
                "functions_call": callees,
            })

        # Smali reference
        if smali_hash:
            results["smali_refs"].append({
                "smali_method_hash": smali_hash,
                "decompiled_method_hash": decompiled_hash,
                "smali_class_name": dalvik_to_java_class(class_name),
                "smali_method_name": method_name,
                "smali_method_signature": descriptor,
                "ssdeep_smali": ssdeep_val,
                "tlsh_smali": tlsh_val,
            })

        # Similarity metrics (content-based fuzzy matching only)
        if smali_hash and smali_normalized:
            minhash_sig = compute_minhash(smali_normalized)
            sim_entry = {
                "smali_method_hash": smali_hash,
                "cyclomatic_complexity": cfg_metrics.cyclomatic_complexity if cfg_metrics else None,
                "ssdeep_smali": ssdeep_val,
                "tlsh_smali": tlsh_val,
                "ssdeep_smali_normalized": ssdeep_normalized_val,
                "tlsh_smali_normalized": tlsh_normalized_val,
                "minhash": minhash_sig or [],
            }
            results["similarity_metrics"].append(sim_entry)

        # CFG entry (structural/topological features)
        if smali_hash and cfg_metrics and smali_body:
            prime_product = compute_prime_product_smali(smali_body)
            call_count = count_call_instructions(smali_body)
            cfg_entry = {
                "smali_method_hash": smali_hash,
                "cfg_topology_hash": cfg_metrics.cfg_topology_hash,
                "block_count": cfg_metrics.block_count,
                "edge_count": cfg_metrics.edge_count,
                "cfg_instructions_count": instruction_count,
                "call_count": call_count,
                "cyclomatic_complexity": cfg_metrics.cyclomatic_complexity,
                "loop_count": cfg_metrics.loop_count,
                "max_depth": cfg_metrics.max_depth,
                "max_fan_out": cfg_metrics.max_fan_out,
                "md_index_topdown": cfg_metrics.md_index_topdown,
                "md_index_bottomup": cfg_metrics.md_index_bottomup,
                "prime_product_smali": prime_product,
                "cfg_feature_tlsh": cfg_metrics.cfg_feature_tlsh,
                "wl_minhash": cfg_metrics.wl_minhash,
                "bb_features": cfg_metrics.block_features,
                "cfg_adjacency": cfg_metrics.cfg_adjacency,
            }
            results["cfg"].append(cfg_entry)

    @staticmethod
    def _string_entropy(s: str) -> float:
        """Compute Shannon entropy of a string."""
        if not s:
            return 0.0
        freq = Counter(s)
        length = len(s)
        return -sum(
            (count / length) * math.log2(count / length)
            for count in freq.values()
        )

    def _extract_strings(self, dexs) -> List[Dict[str, Any]]:
        """Extract deduplicated strings from all DEX objects.

        Uses androguard's get_strings() on each DEX, deduplicates by value,
        and computes entropy — matching the Binja StringAnalysis output format
        so IOCExtractorFromResults can consume them identically.

        Returns list of dicts with keys:
            string, string_encoding, string_offset, string_length, string_entropy
        """
        seen = set()
        strings = []
        offset_counter = 0

        for dex in dexs:
            try:
                dex_strings = dex.get_strings()
            except Exception:
                continue

            if not dex_strings:
                continue

            for s in dex_strings:
                if not s or s in seen:
                    continue
                # DEX MUTF-8 strings may contain unpaired surrogates (e.g. \ud800)
                # that are invalid UTF-8 and will fail ClickHouse insert
                s = s.encode('utf-8', errors='replace').decode('utf-8')
                if not s or s in seen:
                    continue
                seen.add(s)

                strings.append({
                    "string": s,
                    "string_encoding": "UTF8",
                    "string_offset": offset_counter,
                    "string_length": len(s),
                    "string_entropy": self._string_entropy(s),
                })
                offset_counter += 1

        return strings

    @staticmethod
    def _disassemble_with_androguard(encoded):
        """Fallback disassembly using androguard when apktool fails.

        Returns (smali_body, instruction_count, register_count).
        """
        code = encoded.get_code()
        if not code:
            return None, 0, 0

        register_count = code.get_registers_size()
        lines = []
        instruction_count = 0

        try:
            bytecode = code.get_bc()
            if not bytecode:
                return None, 0, register_count

            for instruction in bytecode.get_instructions():
                op_name = instruction.get_name()
                output = instruction.get_output()
                if output:
                    output = _normalize_androguard_operands(op_name, output)
                    lines.append(f"    {op_name} {output}")
                else:
                    lines.append(f"    {op_name}")
                instruction_count += 1
        except Exception:
            return None, 0, register_count

        if not lines:
            return None, 0, register_count

        smali_body = "\n".join(lines)
        return smali_body, instruction_count, register_count

    def cleanup(self):
        """Remove temporary directories."""
        import shutil
        for d in self._temp_dirs:
            try:
                if os.path.isdir(d):
                    shutil.rmtree(d)
            except Exception:
                pass
        self._temp_dirs.clear()


def _normalize_androguard_operands(op_name: str, output: str) -> str:
    """Normalize androguard instruction output to match apktool smali format.

    Androguard's get_output() differs from apktool in two key ways:
    1. invoke-* operands lack {braces} around registers:
       androguard: 'v0, v1, Lcom/Foo;->bar()V'
       apktool:    '{v0, v1}, Lcom/Foo;->bar()V'
    2. Field instructions use a space instead of colon between name and type:
       androguard: 'v0, v1, LA;->field Ljava/lang/String;'
       apktool:    'v0, v1, LA;->field:Ljava/lang/String;'
    """
    # invoke-* instructions: wrap register args in {braces}
    if op_name.startswith("invoke-"):
        # Find the class/method reference (starts with L or [)
        # Split on ', ' and find where the reference begins
        parts = output.split(", ")
        reg_parts = []
        ref_idx = None
        for i, part in enumerate(parts):
            stripped = part.strip()
            if stripped.startswith("L") or stripped.startswith("["):
                ref_idx = i
                break
            reg_parts.append(part)

        if ref_idx is not None and reg_parts:
            regs = ", ".join(reg_parts)
            ref = ", ".join(parts[ref_idx:])
            return "{" + regs + "}, " + ref
        elif reg_parts:
            # No reference found — just wrap all as registers
            return "{" + ", ".join(reg_parts) + "}"

    # Field instructions: fix 'field Ltype;' → 'field:Ltype;'
    if op_name.startswith(("iget", "iput", "sget", "sput")):
        # Pattern: '... ClassName;->fieldName Ltype;' or '... ClassName;->fieldName [Ltype;'
        # The space between fieldName and the type descriptor should be a colon
        arrow_idx = output.find("->")
        if arrow_idx != -1:
            after_arrow = output[arrow_idx + 2:]
            # Find the space before the type descriptor
            space_idx = after_arrow.find(" ")
            if space_idx != -1:
                remaining = after_arrow[space_idx + 1:]
                # Check that what follows is a type descriptor
                if remaining.startswith(("L", "[", "Z", "B", "S", "C",
                                         "I", "J", "F", "D")):
                    after_arrow = after_arrow[:space_idx] + ":" + remaining
                    output = output[:arrow_idx + 2] + after_arrow

    return output


def _normalize_java(source: str) -> str:
    """Normalize Java source for consistent hashing.

    Strip leading/trailing whitespace, normalize indentation.
    """
    lines = []
    for line in source.split("\n"):
        stripped = line.strip()
        if stripped:
            lines.append(stripped)
    return "\n".join(lines)