Oula Puonti

56 papers A* 1B 1Journal 39Unranked 14
YearRankTypeTitle / Venue / Authors
2026 J jnl
NeuroImage
Axel Thielscher, Dayana Hayek, Oula Puonti, Ulrike Grittner, Felix Blankenburg, Rico Fischer, Gesa Hartwigsen, Shu-Chen Li, Marcus Meinzer, Michael A. Nitsche, Dagmar Timmann, Agnes Flöel, Daria Antonenko
2025 J jnl
Medical Image Anal.
Karthik Gopinath, Douglas N. Greve, Colin G. Magdamo, Steven E. Arnold, Sudeshna Das, Oula Puonti, Juan Eugenio Iglesias
2025 J jnl
CoRR
Peirong Liu, Oula Puonti, Xiaoling Hu, Karthik Gopinath, Annabel Sorby-Adams, Daniel C. Alexander, W. Taylor Kimberly, Juan Eugenio Iglesias
2025 J jnl
Medical Image Anal.
Chiara Mauri, Stefano Cerri, Oula Puonti, Mark Mühlau, Koen Van Leemput
2025 conf
DGM4MICCAI@MICCAI
Ana Lawry Aguila, Peirong Liu, Oula Puonti, Juan Eugenio Iglesias
2025 J jnl
CoRR
Ana Lawry Aguila, Peirong Liu, Oula Puonti, Juan Eugenio Iglesias
2025 J jnl
CoRR
Ana Lawry Aguila, Dina Zemlyanker, You Cheng, Sudeshna Das, Daniel C. Alexander, Oula Puonti, Annabel Sorby-Adams, W. Taylor Kimberly, Juan Eugenio Iglesias
2025 conf
MLMI@MICCAI
Jesper Duemose Nielsen, Karthik Gopinath, Andrew Hoopes, Adrian V. Dalca, Colin G. Magdamo, Steven E. Arnold, Sudeshna Das, Axel Thielscher, Juan Eugenio Iglesias, Oula Puonti
2025 J jnl
CoRR
Jesper Duemose Nielsen, Karthik Gopinath, Andrew Hoopes, Adrian V. Dalca, Colin G. Magdamo, Steven E. Arnold, Sudeshna Das, Axel Thielscher, Juan Eugenio Iglesias, Oula Puonti
2025 J jnl
CoRR
Karthik Gopinath, Annabel Sorby-Adams, Jonathan Williams Ramirez, Dina Zemlyanker, Jennifer Guo, David Hunt, Christine L. Mac Donald, C. Dirk Keene, Timothy S. Coalson, Matthew F. Glasser, David C. Van Essen, Matthew S. Rosen, Oula Puonti, W. Taylor Kimberly, Juan Eugenio Iglesias
2025 J jnl
Artif. Intell. Medicine
Lívia Rodrigues, Martina Bocchetta, Oula Puonti, Douglas N. Greve, Ana Carolina Londe, Marcondes França, Simone Appenzeller, Juan Eugenio Iglesias, Letícia Rittner
2025 A* conf
ICLR
Xiaoling Hu, Karthik Gopinath, Peirong Liu, Malte Hoffmann, Koen Van Leemput, Oula Puonti, Juan Eugenio Iglesias
2025 J jnl
CoRR
Lin Tian, Sean I. Young, Jonathan Williams Ramirez, Dina Zemlyanker, Lucas J. Deden-Binder, Rogeny Herisse, Theresa R. Connors, Derek H. Oakley, Bradley T. Hyman, Oula Puonti, Matthew S. Rosen, Juan Eugenio Iglesias
2025 J jnl
CoRR
Xiaoling Hu, Peirong Liu, Dina Zemlyanker, Jonathan Williams Ramirez, Oula Puonti, Juan Eugenio Iglesias
2025 J jnl
NeuroImage
Tun Wiltgen, Julian McGinnis, Ronja C. Berg, Cui Ci Voon, Oula Puonti, Katrin Giglhuber, Carl Ganter, Claus Zimmer, Bernhard Hemmer, Benedikt Wiestler, Jan Kirschke, Christine Preibisch, Mark Mühlau
2024 conf
ECCV (12)
Peirong Liu, Oula Puonti, Xiaoling Hu, Daniel C. Alexander, Juan Eugenio Iglesias
2024 J jnl
CoRR
Lívia Rodrigues, Martina Bocchetta, Oula Puonti, Douglas N. Greve, Ana Carolina Londe, Marcondes França, Simone Appenzeller, Juan Eugenio Iglesias, Letícia Rittner
2024 J jnl
CoRR
Xiaoling Hu, Karthik Gopinath, Peirong Liu, Malte Hoffmann, Koen Van Leemput, Oula Puonti, Juan Eugenio Iglesias
2024 J jnl
CoRR
Lívia Rodrigues, Martina Bocchetta, Oula Puonti, Douglas N. Greve, Ana Carolina Londe, Marcondes França, Simone Appenzeller, Letícia Rittner, Juan Eugenio Iglesias
2024 J jnl
CoRR
Xiaoling Hu, Oula Puonti, Juan Eugenio Iglesias, Bruce Fischl, Yaël Balbastre
2024 conf
TGI3@MICCAI
Xiaoling Hu, Annabel Sorby-Adams, Frederik Barkhof, W. Taylor Kimberly, Oula Puonti, Juan Eugenio Iglesias
2024 J jnl
CoRR
Xiaoling Hu, Annabel Sorby-Adams, Frederik Barkhof, W. Taylor Kimberly, Oula Puonti, Juan Eugenio Iglesias
2024 conf
MICCAI (12)
Peirong Liu, Oula Puonti, Annabel Sorby-Adams, W. Taylor Kimberly, Juan Eugenio Iglesias
2024 J jnl
CoRR
Peirong Liu, Oula Puonti, Annabel Sorby-Adams, William T. Kimberly, Juan Eugenio Iglesias
2024 conf
ISBI
Pablo Laso, Stefano Cerri, Annabel Sorby-Adams, Jennifer Guo, Farrah Mateen, Philipp Goebl, Jiaming Wu, Peirong Liu, Hongwei Bran Li, Sean I. Young, Benjamin Billot, Oula Puonti, Gordon Sze, Sam Payabavash, Adam DeHavenon, Kevin N. Sheth, Matthew S. Rosen, John Kirsch, Nicola Strisciuglio, Jelmer M. Wolterink, Arman Eshaghi, Frederik Barkhof, W. Taylor Kimberly, Juan Eugenio Iglesias
2024 J jnl
CoRR
Karthik Gopinath, Douglas N. Greve, Colin G. Magdamo, Steven E. Arnold, Sudeshna Das, Oula Puonti, Juan Eugenio Iglesias
2024 conf
WBIR
Karthik Gopinath, Xiaoling Hu, Malte Hoffmann, Oula Puonti, Juan Eugenio Iglesias
2024 J jnl
CoRR
Karthik Gopinath, Xiaoling Hu, Malte Hoffmann, Oula Puonti, Juan Eugenio Iglesias
2023 J jnl
CoRR
Chiara Mauri, Stefano Cerri, Oula Puonti, Mark Mühlau, Koen Van Leemput
2023 J jnl
NeuroImage
SeyedSina Hosseini, Oula Puonti, Bradley E. Treeby, Lars G. Hanson, Axel Thielscher
2023 J jnl
NeuroImage
Kathleen Mantell, Nipun D. Perera, Sina Shirinpour, Oula Puonti, Ting Xu, Jan Zimmermann, Arnaud Falchier, Sarah R. Heilbronner, Axel Thielscher, Alexander Opitz
2023 J jnl
CoRR
Peirong Liu, Oula Puonti, Xiaoling Hu, Daniel C. Alexander, Juan Eugenio Iglesias
2023 J jnl
NeuroImage
Jesper Duemose Nielsen, Oula Puonti, Rong Xue, Axel Thielscher, Kristoffer Hougaard Madsen
2023 J jnl
CoRR
Pablo Laso, Stefano Cerri, Annabel Sorby-Adams, Jennifer Guo, Farrah Mateen, Philipp Goebl, Jiaming Wu, Peirong Liu, Hongwei Li, Sean I. Young, Benjamin Billot, Oula Puonti, Gordon Sze, Sam Payabavash, Adam DeHavenon, Kevin N. Sheth, Matthew S. Rosen, John Kirsch, Nicola Strisciuglio, Jelmer M. Wolterink, Arman Eshaghi, Frederik Barkhof, W. Taylor Kimberly, Juan Eugenio Iglesias
2023 J jnl
Medical Image Anal.
Benjamin Billot, Douglas N. Greve, Oula Puonti, Axel Thielscher, Koen Van Leemput, Bruce Fischl, Adrian V. Dalca, Juan Eugenio Iglesias
2022 conf
MICCAI (8)
Chiara Mauri, Stefano Cerri, Oula Puonti, Mark Mühlau, Koen Van Leemput
2022 J jnl
NeuroImage
Søren Asp Fuglsang, Kristoffer H. Madsen, Oula Puonti, Jens Hjortkjær, Hartwig R. Siebner
2021 J jnl
NeuroImage
Stefano Cerri, Oula Puonti, Dominik S. Meier, Jens Wuerfel, Mark Mühlau, Hartwig R. Siebner, Koen Van Leemput
2021 J jnl
NeuroImage
Hasan H. Eroglu, Oula Puonti, Cihan Göksu, Fróði Gregersen, Hartwig R. Siebner, Lars G. Hanson, Axel Thielscher
2021 J jnl
CoRR
Benjamin Billot, Douglas N. Greve, Oula Puonti, Axel Thielscher, Koen Van Leemput, Bruce Fischl, Adrian V. Dalca, Juan Eugenio Iglesias
2020 J jnl
CoRR
Stefano Cerri, Oula Puonti, Dominik S. Meier, Jens Wuerfel, Mark Mühlau, Hartwig R. Siebner, Koen Van Leemput
2020 J jnl
NeuroImage
Oula Puonti, Koen Van Leemput, Guilherme B. Saturnino, Hartwig R. Siebner, Kristoffer H. Madsen, Axel Thielscher
2020 J jnl
NeuroImage
Oula Puonti, Guilherme B. Saturnino, Kristoffer Hougaard Madsen, Axel Thielscher
2019 J jnl
Medical Image Anal.
Mikael Agn, Per Munck af Rosenschöld, Oula Puonti, Michael J. Lundemann, Laura Mancini, Anastasia Papadaki, Steffi Thust, John Ashburner, Ian Law, Koen Van Leemput
2019 conf
EMBC
Silvia Farcito, Oula Puonti, Hazael Montanaro, Guilherme B. Saturnino, Jesper Duemose Nielsen, Camilla Gøbel Madsen, Hartwig R. Siebner, Esra Neufeld, Niels Kuster, Bryn A. Lloyd, Axel Thielscher
2018 J jnl
CoRR
Mikael Agn, Per Munck af Rosenschöld, Oula Puonti, Michael J. Lundemann, Laura Mancini, Anastasia Papadaki, Steffi Thust, John Ashburner, Ian Law, Koen Van Leemput
2018 conf
Computer-Aided Diagnosis
Yashin Dicente Cid, Oula Puonti, Alexandra Platon, Koen Van Leemput, Henning Müller, Pierre-Alexandre Poletti
2018 J jnl
NeuroImage
Jesper Duemose Nielsen, Kristoffer Hougaard Madsen, Oula Puonti, Hartwig R. Siebner, Christian Bauer, Camilla Gøbel Madsen, Guilherme B. Saturnino, Axel Thielscher
2018 J jnl
F1000Research
Nya Mehnwolo Boayue, Gábor Csifcsák, Oula Puonti, Axel Thielscher, Matthias Mittner
2018 B conf
Image Processing
Oula Puonti, Koen Van Leemput, Jesper Duemose Nielsen, Christian Bauer, Hartwig Roman Siebner, Kristoffer Hougaard Madsen, Axel Thielscher
2016
Oula Puonti
2016 J jnl
NeuroImage
Oula Puonti, Juan Eugenio Iglesias, Koen Van Leemput
2015 conf
SCIA
Mark Lyksborg, Oula Puonti, Mikael Agn, Rasmus Larsen
2015 conf
Brainles@MICCAI
Mikael Agn, Oula Puonti, Per Munck af Rosenschöld, Ian Law, Koen Van Leemput
2015 conf
Brainles@MICCAI
Oula Puonti, Koen Van Leemput
2013 conf
MICCAI (1)
Oula Puonti, Juan Eugenio Iglesias, Koen Van Leemput
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"