Nabil Belacel

45 papers A 1B 2C 1Misc 1Journal 18Unranked 21
YearRankTypeTitle / Venue / Authors
2026 J jnl
CoRR
Nabil Belacel, Mohamed Rachid Boulassel
2026 J jnl
Comput. Oper. Res.
Mohammadreza Nematollahi, Adel Guitouni, Nafiseh Izadyar, Nabil Belacel, Andrew Park
2025 J jnl
Algorithms
Nabil Belacel
2024 conf
IEEE SENSORS
Zara Cook, Chengzong Zhao, Livia Murray, Jivan Kesan, Nabil Belacel, Sam M. Doesburg, George Medvedev, Vasily A. Vakorin, Pengcheng Xi
2024 conf
I2MTC
Zara Cook, Grant Sinha, Jack Wang, Chengzong Zhao, Nabil Belacel, Sam M. Doesburg, George Medvedev, Urs Ribary, Vasily A. Vakorin, Pengcheng Xi
2024 conf
IEEE SENSORS
Chengzong Zhao, Zara Cook, Livia Murray, Jivan Kesan, Nabil Belacel, Sam M. Doesburg, George Medvedev, Vasily A. Vakorin, Pengcheng Xi
2023 J jnl
CoRR
Rene Richard, Nabil Belacel
2023 conf
SENSORS
Grant Sinha, Nabil Belacel, Zhiyang Gu, Sam M. Doesburg, George Medvedev, Urs Ribary, Vasily A. Vakorin, Pengcheng Xi
2022 B conf
IEEE Big Data
Nabil Belacel, René Richard, Zhicheng Max Xu
2020 conf
Canadian AI
Nabil Belacel, Cheng Duan, Diana Inkpen
2020 conf
ICAART (2)
Nabil Belacel, Guangze Wei, Yassine Bouslimani
2019 J jnl
Comput. Ind. Eng.
Youcef Djeddi, Hacène Aït Haddadène, Nabil Belacel
2019 ch.
Enhanced Living Environments
Feras N. Al-Obeidat, Nabil Belacel, Bruce Spencer
2018 A conf
LAK
Guillaume Durand, Cyril Goutte, Nabil Belacel, Yassine Bouslimani, Serge Léger
2018 conf
ICAART (Revised Selected Papers)
Nabil Belacel, Guillaume Durand, Serge Léger, Cajetan Bouchard
2018 conf
ICAART (2)
Nabil Belacel, Guillaume Durand, Serge Léger, Cajetan Bouchard
2016 J jnl
Int. J. Inf. Technol. Decis. Mak.
Si He, Nabil Belacel, Alan Chan, Habib Hamam, Yassine Bouslimani
2016 conf
WWW (Companion Volume)
Guillaume Durand, Nabil Belacel, Cyril Goutte
2016 J jnl
Unmanned Syst.
Cyrus Minwalla, Dan Tulpan, Nabil Belacel, Fazel Famili, Kristopher Ellis
2015 conf
ANT/SEIT
Feras N. Al-Obeidat, Ahmad T. Al-Taani, Nabil Belacel, Leo Feltrin, Neil R. Banerjee
2015 B conf
EC-TEL
Guillaume Durand, Nabil Belacel, Cyril Goutte
2015 conf
ICIAR
François LaPlante, Mustapha Kardouchi, Nabil Belacel
2014 conf
EDM (Workshops)
Nabil Belacel, Guillaume Durand, François LaPlante
2014 conf
ICAART (Revised Selected Papers)
François LaPlante, Nabil Belacel, Mustapha Kardouchi
2014 conf
ICAART (1)
François LaPlante, Nabil Belacel, Mustapha Kardouchi
2013 J jnl
Inf. Sci.
Guillaume Durand, Nabil Belacel, François LaPlante
2012 conf
BIOSIG
Abdel Ilah Salhi, Mustapha Kardouchi, Nabil Belacel
2011 conf
Canadian AI
Nabil Belacel, Feras N. Al-Obeidat
2011 J jnl
Int. J. Intell. Syst.
Feras N. Al-Obeidat, Nabil Belacel
2011 J jnl
Appl. Soft Comput.
Feras N. Al-Obeidat, Nabil Belacel, Juan A. Carretero, Prabhat Mahanti
2010 conf
Canadian AI
Feras N. Al-Obeidat, Nabil Belacel, Juan A. Carretero, Prabhat Mahanti
2010 J jnl
Knowl. Based Syst.
Feras N. Al-Obeidat, Nabil Belacel, Juan A. Carretero, Prabhat Mahanti
2009 C conf
ICMLA
Feras N. Al-Obeidat, Nabil Belacel, Prabhat Mahanti, Juan A. Carretero
2009 conf
CSO (2)
Si He, Nabil Belacel, Habib Hamam, Yassine Bouslimani
2009 conf
SITIS
Wassim Bouachir, Mustapha Kardouchi, Nabil Belacel
2009 conf
CNSR
Imen Sassi, Nabil Belacel, Yassine Bouslimani, Habib Hamam
2008 conf
MCO
Christa Wang, Nabil Belacel
2007 J jnl
Comput. Oper. Res.
Nabil Belacel, Hiral Bhasker Raval, Abraham P. Punnen
2004 J jnl
Bioinform.
Nabil Belacel, Miroslava Cuperlovic-Culf, Mark Laflamme, Rodney Ouellette
2004 J jnl
Fuzzy Sets Syst.
Nabil Belacel, Mohamed Rachid Boulassel
2003 Misc conf
AI
Yu Guan, Ali A. Ghorbani, Nabil Belacel
2002 J jnl
Pattern Recognit.
Nabil Belacel, Pierre Hansen, Nenad Mladenovic
2001 J jnl
Comput. Methods Programs Biomed.
Nabil Belacel, Philippe Vincke, J. M. Scheiff, Mohamed Rachid Boulassel
2001 J jnl
Artif. Intell. Medicine
Nabil Belacel, Mohamed Rachid Boulassel
2000 J jnl
Eur. J. Oper. Res.
Nabil Belacel
redb/extractors/apk_extractors/apk_inconsistency_tests.py
← Index redb/extractors/apk_extractors/apk_inconsistency_tests.py python
import inspect
import re
from datetime import datetime, timezone
from typing import Any

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

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

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

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


class APKInconsistencyTestsExtractor(APKExtractor):

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

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

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

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

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

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

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

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

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

            if not dex_classes:
                return None

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

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

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

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

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

            if not is_debuggable:
                return False

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            return (data, column_names, column_type_names)

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