Catherine Duclos

56 papers C 1Misc 4Journal 17Unranked 34
YearRankTypeTitle / Venue / Authors
2025 J jnl
BMC Medical Informatics Decis. Mak.
Akoi Koïvogui, Christian Balamou, Robert Benamouzig, Catherine Duclos
2024 conf
MIE
Mirna El Ghosh, Varvara Kalokyri, Mélanie Sambres, Morgan Vaterkowski, Catherine Duclos, Xavier Tannier, Gianna Tsakou, Manolis Tsiknakis, Christel Daniel, Ferdinand Dhombres
2024 conf
JOWO
Mirna El Ghosh, Christel Daniel, Catherine Duclos, Varvara Kalokyri, Jean Charlet, Mélanie Sambres, Gianna Tsakou, Manolis Tsiknakis, Ferdinand Dhombres
2024 conf
MIE
Bénédicte Melot, Florian Drouet, Céline Gérard, Bérénice Mahé, Samuel Cousin, Julie Salomon, Julien Grosjean, Catherine Duclos
2024 C conf
FOIS
Mirna El Ghosh, Varvara Kalokyri, Mélanie Sambres, Morgan Vaterkowski, Catherine Duclos, Xavier Tannier, Gianna Tsakou, Manolis Tsiknakis, Christel Daniel, Ferdinand Dhombres
2023 J jnl
NeuroImage
Charlotte Maschke, Catherine Duclos, Adrian M. Owen, Karim Jerbi, Stefanie Blain-Moraes
2023 conf
EFMI-STC
Bénédicte Melot, Florian Drouet, Caroline Alvarez, Céline Grimshaw, Julien Grosjean, Catherine Duclos
2023 conf
MIE
Akoi Koïvogui, Robert Benamouzig, Catherine Duclos
2022 conf
EFMI-STC
Bénédicte Melot, M. Amsilli, Florian Drouet, L. Rodriguez, Julie Salomon, Julien Grosjean, Catherine Duclos
2022 conf
MIE
Catherine Duclos, Nicolas Griffon, Christel Daniel, Guillaume Bouzillé, Denis Delamarre, Stéfan Jacques Darmoni, Laurent Toubiana, Julien Grosjean
2022 conf
MIE
Romain Lelong, Badisse Dahamna, Hélène Berthelot, Willy Duville, Catherine Letord, Julien Grosjean, Catherine Duclos
2021 J jnl
NeuroImage
Catherine Duclos, Charlotte Maschke, Yacine Mahdid, Kathleen Berkun, Jason da Silva Castanheira, Vijay Tarnal, Paul Picton, Giancarlo Vanini, Goodarz Golmirzaie, Ellen Janke, Michael S. Avidan, Max B. Kelz, Lucrezia Liuzzi, Matthew J. Brookes, George A. Mashour, Stefanie Blain-Moraes
2021 conf
MedInfo
Ilan Zana, Julien Grosjean, Catherine Letord, Jean Charlet, Julien Rio, Elaï T. N. Darmoni, Catherine Duclos, Stéfan Jacques Darmoni
2019 J jnl
CoRR
Xavier Tannier, Nicolas Paris, Hugo Cisneros, Christel Daniel, Matthieu Doutreligne, Catherine Duclos, Nicolas Griffon, Claire Hassen-Khodja, Ivan Lerner, Adrien Parrot, Éric Sadou, Cyril Saussol, Pascal Vaillant
2019 conf
MedInfo
Iris Ternois, Typhaine Billard-Pomares, Etienne Carbonelle, Loriane Franchinard, Catherine Duclos
2018 conf
MIE
Adrien Ugon, Amel Imene Hadj Bouzid, Marie-Christine Jaulent, Madeleine Favre, Catherine Duclos, Emmanuel Jobez, Hector Falcoff, Jean-Baptiste Lamy, Rosy Tsopra
2018 conf
EFMI-STC
Iris Ternois, Jean-Baptiste Escudié, Robert Benamouzig, Catherine Duclos
2017 conf
IC
Jean-Baptiste Lamy, Adrien Ugon, Catherine Duclos, Alain Venot, Madeleine Favre, Hélène Berthelot
2017 J jnl
J. Biomed. Informatics
Jean-Baptiste Lamy, Hélène Berthelot, Madeleine Favre, Adrien Ugon, Catherine Duclos, Alain Venot
2016 J jnl
Rev. d'Intelligence Artif.
Jean-Baptiste Lamy, Lina Fatima Soualmia, Catherine Duclos, Alain Venot
2015 conf
ICIMTH
Adrien Ugon, Hélène Berthelot, Alain Venot, Madeleine Favre, Catherine Duclos, Jean-Baptiste Lamy
2015 J jnl
BMC Medical Informatics Decis. Mak.
Maia Iordatii, Alain Venot, Catherine Duclos
2015 conf
MIE
Jean-Baptiste Lamy, Alain Venot, Catherine Duclos
2014 conf
MIE
Karima Sedki, Catherine Duclos, Jean-Baptiste Lamy
2014 Misc conf
AMIA
Rosy Tsopra, Alain Venot, Catherine Duclos
2014 J jnl
BMC Medical Informatics Decis. Mak.
Nicolas Griffon, Gaétan Kerdelhué, Lina Fatima Soualmia, Tayeb Merabti, Julien Grosjean, Jean-Baptiste Lamy, Alain Venot, Catherine Duclos, Stéfan Jacques Darmoni
2014 J jnl
BMC Medical Informatics Decis. Mak.
Suzanne Pereira, Sylvain Hassler, Saliha Hamek, César Boog, Nicolas Leroy, Marie-Catherine Beuscart-Zéphir, Madeleine Favre, Alain Venot, Catherine Duclos, Jean-Baptiste Lamy
2014 conf
MIE
Jean-Baptiste Lamy, Hélène Berthelot, Madeleine Favre, Alain Venot, Catherine Duclos
2014 conf
MIE
Rosy Tsopra, Alain Venot, Catherine Duclos
2014 conf
MIE
Christian Simon, Sylvain Hassler, Marie-Catherine Beuscart-Zéphir, Madeleine Favre, Alain Venot, Catherine Duclos, Jean-Baptiste Lamy
2014 conf
IC
Jean-Baptiste Lamy, Lina Fatima Soualmia, Alain Venot, Catherine Duclos
2013 conf
MedInfo
Mobin Yasini, Catherine Duclos, Alain Venot, Eric Lepage, Jean-Baptiste Lamy
2013 conf
MedInfo
Jean-Baptiste Lamy, Rosy Tsopra, Alain Venot, Catherine Duclos
2013 J jnl
CoRR
Jean-Baptiste Lamy, Rosy Tsopra, Alain Venot, Catherine Duclos
2013 conf
EFMI-STC
Mobin Yasini, Vahid Ebrahiminia, Catherine Duclos, Alain Venot, Jean-Baptiste Lamy
2013 J jnl
BMC Medical Informatics Decis. Mak.
Maia Iordatii, Alain Venot, Catherine Duclos
2013 J jnl
J. Biomed. Informatics
Jean-Baptiste Lamy, Lina Fatima Soualmia, Gaétan Kerdelhué, Alain Venot, Catherine Duclos
2012 conf
MIE
Ariane Assélé Kama, Audi Primadhanty, Rémy Choquet, Douglas Teodoro, Frank Enders, Catherine Duclos, Marie-Christine Jaulent
2012 conf
MIE
Rosy Tsopra, Jean-Baptiste Lamy, Alain Venot, Catherine Duclos
2012 Misc conf
AMIA
Jean-Baptiste Lamy, Alain Venot, Catherine Duclos
2011 conf
MIE
Mobin Yasini, Catherine Duclos, Jean-Baptiste Lamy, Alain Venot
2010 conf
MedInfo
Catherine Duclos, Lina Fatima Soualmia, Sonia Krivine, Anne Jamet, Agnès Lillo-Le Louët
2010 conf
MedInfo
Jean-Baptiste Lamy, Catherine Duclos, Saliha Hamek, Marie-Catherine Beuscart-Zéphir, Gaétan Kerdelhué, Stéfan Jacques Darmoni, Madeleine Favre, Hector Falcoff, Christian Simon, Suzanne Pereira, Elisabeth Serrot, Thierry Mitouard, Etienne Hardouin, Yannick Kergosien, Alain Venot
2009 conf
Actes d'IC
Sonia Krivine, Jérôme Nobécourt, Lina Fatima Soualmia, Farid Cerbah, Catherine Duclos
2009 conf
Actes d'IC
Jean-Baptiste Lamy, Catherine Duclos, Alain Venot
2008 J jnl
BMC Medical Informatics Decis. Mak.
Jean-Baptiste Lamy, Catherine Duclos, Avner Bar-Hen, Patrick Ouvrard, Alain Venot
2008 J jnl
CoRR
Amanda Bouffier, Thierry Poibeau, Catherine Duclos
2008 conf
Actes d'IC
Amanda Bouffier, Catherine Duclos, Thierry Poibeau
2008 J jnl
BMC Medical Informatics Decis. Mak.
Jean-Baptiste Lamy, Alain Venot, Avner Bar-Hen, Patrick Ouvrard, Catherine Duclos
2008 conf
Actes d'IC
Jérôme Nobécourt, Catherine Duclos
2008 J jnl
Rev. d'Intelligence Artif.
Jean-Baptiste Lamy, Catherine Duclos, Vincent Rialle, Alain Venot
2007 Misc conf
AMIA
Catherine Duclos, Jérôme Nobécourt, Gian Luigi Cartolano, Anis Ellini, Alain Venot
2005 conf
MIE
Vahid Ebrahiminia, Catherine Duclos, Massoud E. Toussi, Christine Riou, Régis Cohen, Alain Venot
2005 conf
MIE
Jean-Baptiste Lamy, Catherine Duclos, Vincent Rialle, Alain Venot
2004 J jnl
J. Am. Medical Informatics Assoc.
Catherine Duclos, Gian Luigi Cartolano, Michael Ghez, Alain Venot
1999 Misc conf
AMIA
Alain Venot, Catherine Duclos
redb/extractors/apk_extractors/apk_resources.py
← Index redb/extractors/apk_extractors/apk_resources.py python
import hashlib
import inspect
import os
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 APKResource


# ─── Suspicious file types ──────────────────────────────────────────────
# File types that are suspicious when found inside res/ or assets/.
# Excludes javascript/html (extremely common in legitimate hybrid apps)
# and common media/font types that are normal APK content.
SUSPICIOUS_TYPES = {
    # Executables — no legitimate reason in assets/res
    "elf", "pebin", "macho", "dex", "apk",
    # Java containers — DexClassLoader target
    "jar",
    # Archives — rare in legitimate assets (~135:1 malware-to-benign ratio)
    "zip", "gzip", "7z", "xz", "tar", "bzip2", "rar", "7zip", "lzma",
    # Scripts with system execution capability
    "shell", "python", "powershell", "batch",
}

# ─── Entropy thresholds ─────────────────────────────────────────────────
# For unrecognized/unknown types: encrypted payloads typically land > 7.0
ENTROPY_HIGH_UNKNOWN = 7.0
# For recognized-but-non-image types: stricter threshold
ENTROPY_EXTREME = 7.85

# ─── Android-specific binary format magic bytes ─────────────────────────
# These formats are common in legitimate APKs but unknown to Magika,
# causing misclassification (e.g., AXML → "gzip", profm → "unknown").
AXML_MAGIC = b'\x03\x00\x08\x00'       # Android Binary XML (compiled res/*.xml)
ARSC_MAGIC = b'\x02\x00\x0c\x00'       # Android compiled resource table
ART_PROF_MAGIC = b'pro\x00'            # ART baseline profile
ART_PROFM_MAGIC = b'prm\x00'           # ART baseline profile metadata

# ─── Allowlisted paths ──────────────────────────────────────────────────
# Fixed, hardcoded paths in the Android build system that are always benign.
# ART profiles at these exact paths are shipped by Jetpack ProfileInstaller.
ALLOWLISTED_PATHS = {
    "assets/dexopt/baseline.prof",
    "assets/dexopt/baseline.profm",
}

# ─── Image handling ─────────────────────────────────────────────────────
# Magika-confirmed image types: high entropy is expected (lossy codecs
# like VP8/JPEG arithmetic-code toward entropy ~7.95-8.0 by design).
IMAGE_MAGIKA_TYPES = {"png", "webp", "jpeg", "gif", "bmp", "tiff", "ico"}
IMAGE_EXTENSIONS = {".png", ".webp", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico"}

# ─── Types Magika assigns when it can't identify the content ────────────
UNRECOGNIZED_MAGIKA_TYPES = {"unknown", "empty"}

# ─── Resource scan limits ───────────────────────────────────────────────
MAX_RESOURCE_FILES = 5000


class APKResourceExtractor(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.resources = []
        self.suspicious_files = []
        self.log.debug(inspect.currentframe().f_code.co_name)

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

    # ─── Core classification logic ──────────────────────────────────────

    def _identify_android_format(self, header: bytes) -> str | None:
        """
        Identify Android-specific binary formats that Magika doesn't know.
        Returns a corrected type label, or None to fall through to Magika.
        """
        if len(header) < 4:
            return None

        magic4 = header[:4]

        # Android Binary XML — all res/*.xml in a compiled APK.
        # Magika often misclassifies this as "gzip".
        if magic4 == AXML_MAGIC:
            return "android_binary_xml"

        # Android compiled resource table (resources.arsc chunks)
        if magic4 == ARSC_MAGIC:
            return "android_resource_table"

        # ART baseline profiles — high entropy (zlib inside) but benign.
        # The format is inert (method reference bitmaps/metadata, not
        # executable code) and some build configs place them at varying paths.
        if magic4 == ART_PROF_MAGIC:
            return "android_art_profile"
        if magic4 == ART_PROFM_MAGIC:
            return "android_art_profile_metadata"

        return None

    def _is_suspicious_resource(
        self, path: str, magika_type: str, entropy: float,
        android_type: str | None,
    ) -> bool:
        """
        Determine if a resource file is suspicious.

        Detection layers:
        1. Allowlisted paths → always benign
        2. Android-specific format override → reclassify Magika mislabels
        3. Image extension vs Magika type mismatch → encrypted blob detection
        4. Magika-confirmed images → benign regardless of entropy
        5. Suspicious type match → flag known-dangerous types
        6. High-entropy unknown blobs → likely encrypted payloads
        """

        # ── Layer 1: Allowlisted paths (hardcoded Android build artifacts) ──
        if path in ALLOWLISTED_PATHS:
            return False

        # ── Layer 2: Android-specific format detection ──────────────────────
        # Override Magika's label for formats it doesn't recognize.
        # All Android-specific formats (AXML, ARSC, ART profiles) are
        # legitimate build artifacts — never suspicious.
        if android_type is not None:
            return False

        # ── Layer 3: Image extension / Magika type mismatch ─────────────────
        # If the file extension claims "image" but Magika's content analysis
        # disagrees, this is a strong signal for an encrypted payload with
        # a fake image extension (e.g., ErrorFather's "rbyypivsnw.png").
        ext = os.path.splitext(path)[1].lower()
        if ext in IMAGE_EXTENSIONS and magika_type not in IMAGE_MAGIKA_TYPES:
            # Exception: Magika might label a valid image as "unknown" if
            # the file is very small (< ~16 bytes). Don't flag tiny files.
            if entropy > 5.0:
                return True

        # ── Layer 4: Magika-confirmed images → benign ───────────────────────
        # Lossy codecs (VP8, JPEG) produce entropy up to ~8.0 by design.
        # If Magika confirms image structure, high entropy is expected.
        if magika_type in IMAGE_MAGIKA_TYPES:
            return False

        # ── Layer 5: Known suspicious file types ────────────────────────────
        if magika_type in SUSPICIOUS_TYPES:
            return True

        # ── Layer 6: High-entropy unrecognized blobs ────────────────────────
        # Files Magika can't identify with high entropy are likely encrypted
        # payloads. Most Android malware packers store encrypted DEX/SO
        # payloads as opaque blobs with random names and no valid magic.
        if magika_type in UNRECOGNIZED_MAGIKA_TYPES and entropy > ENTROPY_HIGH_UNKNOWN:
            return True

        # ── Layer 7: Extreme entropy on any non-image recognized type ───────
        # Catches edge cases where Magika assigns a benign label (e.g.,
        # "xml", "txt") but the entropy is impossibly high for that format.
        if magika_type not in IMAGE_MAGIKA_TYPES and entropy > ENTROPY_EXTREME:
            return True

        return False

    # ─── Extraction pipeline ────────────────────────────────────────────

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

        try:
            from magika import Magika
            magika = Magika()
        except Exception as e:
            self.log.error(f"Failed to initialize Magika for {self.hash.sha256}: {e}")
            magika = None

        self.resources = []
        self.suspicious_files = []
        scanned = 0

        zf = self._get_zip_file()
        if not zf:
            return None

        with zf:
            for info in zf.infolist():
                if info.is_dir():
                    continue
                if not (info.filename.startswith("res/") or
                        info.filename.startswith("assets/")):
                    continue

                if scanned >= MAX_RESOURCE_FILES:
                    self.log.warning(
                        f"Resource scan limit reached ({MAX_RESOURCE_FILES}), "
                        f"stopping resource enumeration"
                    )
                    break
                scanned += 1

                try:
                    data = zf.read(info.filename)
                except Exception as e:
                    self.log.warning(
                        f"Error reading resource {info.filename}: {e}"
                    )
                    continue

                try:
                    file_sha256 = hashlib.sha256(data).hexdigest()
                    file_entropy = round(self.calculate_entropy(data), 3)

                    # Read first bytes for Android-specific format detection
                    header = data[:16] if len(data) >= 16 else data

                    if magika:
                        try:
                            filetype = magika.identify_bytes(data).output.label
                        except Exception:
                            filetype = "unknown"
                    else:
                        filetype = "unknown"

                    # Identify Android-specific formats once, reuse for
                    # both stored type and suspicion classification
                    android_type = self._identify_android_format(header)
                    stored_type = android_type if android_type else filetype

                    suspicious = self._is_suspicious_resource(
                        path=info.filename,
                        magika_type=filetype,
                        entropy=file_entropy,
                        android_type=android_type,
                    )

                    resource = APKResource(
                        path=info.filename,
                        size=info.file_size,
                        sha256=file_sha256,
                        filetype_magika=stored_type,
                        entropy=file_entropy,
                    )

                    if suspicious:
                        resource.is_suspicious = True
                        self.suspicious_files.append(resource)

                    self.resources.append(resource)
                except Exception as e:
                    self.log.warning(
                        f"Error processing resource {info.filename}: {e}"
                    )
                    continue

        if not self.resources:
            return None

        return {
            "total_resource_count": len(self.resources),
            "total_resource_size": sum(r.size for r in self.resources),
            "suspicious_file_count": len(self.suspicious_files),
            "resources": self.resources,
            "suspicious_files": self.suspicious_files,
        }

    # ─── Export ──────────────────────────────────────────────────────────

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

            current_time = datetime.now(timezone.utc)
            data = []
            for res in self.resources:
                data.append([
                    self.sha256,
                    res.path,
                    res.size,
                    res.sha256,
                    res.filetype_magika,
                    res.entropy,
                    int(res.is_suspicious),
                    current_time,
                ])

            column_names = [
                'sha256', 'resource_path', 'resource_size',
                'resource_sha256', 'resource_magika', 'resource_entropy',
                'is_suspicious', 'analysis_date',
            ]

            column_type_names = [
                'FixedString(64)', 'String', 'UInt64',
                'FixedString(64)', 'LowCardinality(String)', 'Float32',
                'UInt8', "DateTime64(3, 'UTC')",
            ]

            return (data, column_names, column_type_names)

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