Karl Berntorp

112 papers A* 3C 42Misc 2Journal 29Unranked 36
YearRankTypeTitle / Venue / Authors
2025 J jnl
CoRR
Yuri Shimane, Karl Berntorp, Stefano Di Cairano, Avishai Weiss
2025 C conf
ACC
Yingjie Hu, Stefano Di Cairano, Karl Berntorp
2025 J jnl
IEEE Trans. Control. Syst. Technol.
Marcus Greiff, Himani Sinhmar, Avishai Weiss, Karl Berntorp, Stefano Di Cairano
2025 J jnl
CoRR
Ankush Chakrabarty, Gordon Wichern, Vedang M. Deshpande, Abraham P. Vinod, Karl Berntorp, Christopher R. Laughman
2025 J jnl
Neurocomputing
Ankush Chakrabarty, Gordon Wichern, Vedang M. Deshpande, Abraham P. Vinod, Karl Berntorp, Christopher R. Laughman
2025 conf
CCTA
Yingjie Hu, Stefano Di Cairano, Karl Berntorp
2025 C conf
ACC
Andres S. Chavez Armijos, Karl Berntorp, Stefano Di Cairano
2025 A* conf
ICRA
Ziyi Zhou, Stefano Di Cairano, Yebin Wang, Karl Berntorp
2025 J jnl
CoRR
Ziyi Zhou, Stefano Di Cairano, Yebin Wang, Karl Berntorp
2024 conf
ECC
Marcus Greiff, Avishai Weiss, Karl Berntorp, Stefano Di Cairano
2024 J jnl
IEEE Trans. Control. Syst. Technol.
Marcus Greiff, Stefano Di Cairano, Kyeong Jin Kim, Karl Berntorp
2024 conf
CDC
Marcus Greiff, Karl Berntorp
2024 conf
CDC
Marcus Greiff, Stefano Di Cairano, Karl Berntorp
2024 conf
CDC
Ji Yin, Panagiotis Tsiotras, Karl Berntorp
2024 J jnl
CoRR
Ji Yin, Panagiotis Tsiotras, Karl Berntorp
2024 C conf
ACC
Yanyu Zhang, Marcus Greiff, Wei Ren, Karl Berntorp
2024 conf
CDC
Ankush Chakrabarty, Vedang M. Deshpande, Gordon Wichern, Karl Berntorp
2024 conf
ECC
Yanbing Wang, Karl Berntorp, Marcel Menner
2024 conf
ECC
Marcel Menner, Karl Berntorp
2024 J jnl
CoRR
Marcel Menner, Karl Berntorp
2024 conf
ECC
Karl Berntorp, Marcus Greiff
2023 J jnl
IEEE Trans. Control. Syst. Technol.
Marcel Menner, Karl Berntorp, Stefano Di Cairano
2023 C conf
FUSION
Karl Berntorp, Marcus Greiff, Stefano Di Cairano, Pedro Miraldo
2023 C conf
FUSION
Karl Berntorp, Marcel Menner
2023 J jnl
CoRR
Sean Vaskov, Rien Quirynen, Marcel Menner, Karl Berntorp
2023 conf
CCTA
Marcel Menner, Karl Berntorp
2023 J jnl
CoRR
Marcel Menner, Karl Berntorp
2023 C conf
ACC
Sixiong You, Marcus Greiff, Rien Quirynen, Shuangxuan Ran, Yebin Wang, Karl Berntorp, Ran Dai, Stefano Di Cairano
2023 conf
CCTA
Hassan Iqbal, Stefano Di Cairano, Karl Berntorp
2023 conf
CCTA
Anuj Tiwari, Karl Berntorp, Stefano Di Cairano, Marcel Menner
2022 C conf
FUSION
Karl Berntorp, Marcus Greiff, Stefano Di Cairano
2022 conf
CDC
Marcus Greiff, Karl Berntorp
2022 C conf
FUSION
Marcus Greiff, Stefano Di Cairano, Karl Berntorp
2022 A* conf
CVPR
Peizhao Li, Pu Wang, Karl Berntorp, Hongfu Liu
2022 J jnl
CoRR
Peizhao Li, Pu Wang, Karl Berntorp, Hongfu Liu
2022 C conf
ACC
Sean Vaskov, Rien Quirynen, Marcel Menner, Karl Berntorp
2022 conf
CCTA
Marcel Menner, Ankush Chakrabarty, Karl Berntorp, Stefano Di Cairano
2022 conf
ECC
Marcel Menner, Ziyi Ma, Karl Berntorp, Stefano Di Cairano
2022 C conf
IV
Md. Ferdous Pervej, Jianlin Guo, Kyeong Jin Kim, Kieran Parsons, Philip V. Orlik, Stefano Di Cairano, Marcel Menner, Karl Berntorp, Yukimasa Nagai, Huaiyu Dai
2022 J jnl
CoRR
Md. Ferdous Pervej, Jianlin Guo, Kyeong Jin Kim, Kieran Parsons, Philip V. Orlik, Stefano Di Cairano, Marcel Menner, Karl Berntorp, Yukimasa Nagai, Huaiyu Dai
2022 C conf
ACC
Karl Berntorp, Marcel Menner
2021 conf
CCTA
Marcel Menner, Karl Berntorp, Stefano Di Cairano
2021 J jnl
CoRR
Marcel Menner, Karl Berntorp, Stefano Di Cairano
2021 C conf
ACC
Sean Vaskov, Karl Berntorp, Rien Quirynen
2021 Misc conf
ICASSP
Gang Yao, Pu Wang, Karl Berntorp, Hassan Mansour, Petros Boufounos, Philip V. Orlik
2021 C conf
FUSION
Gang Yao, Pu Wang, Karl Berntorp, Hassan Mansour, Petros Boufounos, Philip V. Orlik
2021 J jnl
IEEE Trans. Intell. Veh.
Marcel Menner, Karl Berntorp, Melanie N. Zeilinger, Stefano Di Cairano
2021 C conf
ACC
Karl Berntorp, Rien Quirynen, Sean Vaskov
2021 J jnl
IEEE J. Sel. Top. Signal Process.
Yuxuan Xia, Pu Wang, Karl Berntorp, Lennart Svensson, Karl Granström, Hassan Mansour, Petros Boufounos, Philip V. Orlik
2021 conf
CCTA
Marcus Greiff, Karl Berntorp, Stefano Di Cairano, Kyeong Jin Kim
2021 J jnl
Autom.
Karl Berntorp
2021 J jnl
IEEE Trans. Control. Syst. Technol.
Heejin Ahn, Karl Berntorp, Pranav Inani, Arjun Jagdish Ram, Stefano Di Cairano
2021 C conf
ACC
Karl Berntorp, Ankush Chakrabarty, Stefano Di Cairano
2021 conf
CDC
Karl Berntorp, Ankush Chakrabarty, Stefano Di Cairano
2020 conf
CDC
Heejin Ahn, Karl Berntorp, Stefano Di Cairano
2020 J jnl
CoRR
Marcus Greiff, Karl Berntorp, Anders Robertsson
2020 conf
CDC
Marcus Greiff, Anders Robertsson, Karl Berntorp
2020 Misc conf
ICASSP
Yuxuan Xia, Pu Wang, Karl Berntorp, Toshiaki Koike-Akino, Hassan Mansour, Milutin Pajovic, Petros Boufounos, Philip V. Orlik
2020 conf
SAM
Yuxuan Xia, Pu Wang, Karl Berntorp, Hassan Mansour, Petros Boufounos, Philip V. Orlik
2020 J jnl
IEEE Trans. Aerosp. Electron. Syst.
Karl Berntorp, Avishai Weiss, Stefano Di Cairano
2020 C conf
ACC
Rien Quirynen, Karl Berntorp, Karthik Kambam, Stefano Di Cairano
2020 C conf
ACC
Ankush Chakrabarty, Karl Berntorp, Stefano Di Cairano
2020 conf
CCTA
Marcus Greiff, Anders Robertsson, Karl Berntorp
2020 C conf
ACC
Claus Danielson, Karl Berntorp, Stefano Di Cairano, Avishai Weiss
2020 C conf
ACC
Marcus Greiff, Karl Berntorp
2020 J jnl
IEEE Trans. Intell. Veh.
Karl Berntorp, Richard Bai, Karl F. Erliksson, Claus Danielson, Avishai Weiss, Stefano Di Cairano
2020 J jnl
IEEE Trans. Autom. Control.
Claus Danielson, Karl Berntorp, Avishai Weiss, Stefano Di Cairano
2019 conf
CDC
Karl Berntorp, Kitano Hiroaki
2019 conf
ECC
Karl Berntorp
2019 conf
CDC
Marcel Menner, Karl Berntorp, Melanie N. Zeilinger, Stefano Di Cairano
2019 J jnl
IEEE Trans. Intell. Veh.
Karl Berntorp, Tru Hoang, Stefano Di Cairano
2019 C conf
ACC
Karl Berntorp, Pranav Inani, Rien Quirynen, Stefano Di Cairano
2019 C conf
FUSION
Karl Berntorp, Stefano Di Cairano
2019 C conf
FUSION
Marcus Greiff, Anders Robertsson, Karl Berntorp
2019 conf
ECC
Karl Berntorp
2019 C conf
ACC
Karl Berntorp, Rien Quirynen, Stefano Di Cairano
2019 J jnl
IEEE Trans. Control. Syst. Technol.
Karl Berntorp, Stefano Di Cairano
2018 C conf
ACC
Karl Berntorp, Stefano Di Cairano
2018 C conf
FUSION
Karl Berntorp
2018 conf
CCTA
Karl Berntorp, Tru Hoang, Rien Quirynen, Stefano Di Cairano
2018 C conf
ACC
Rien Quirynen, Karl Berntorp, Stefano Di Cairano
2018 C conf
ACC
Marcus Greiff, Karl Berntorp
2018 J jnl
IEEE Trans. Aerosp. Electron. Syst.
Karl Berntorp, Piyush Grover
2018 C conf
FUSION
Karl Berntorp, Avishai Weiss, Stefano Di Cairano
2018 C conf
ACC
Karl Berntorp, Stefano Di Cairano
2018 conf
CDC
Karl Berntorp, Claus Danielson, Avishai Weiss, Stefano Di Cairano
2018 C conf
ACC
Heejin Ahn, Karl Berntorp, Stefano Di Cairano
2017 conf
ITSC
Karl Berntorp, Avishai Weiss, Claus Danielson, Ilya V. Kolmanovsky, Stefano Di Cairano
2017 conf
CCTA
Uros Kalabic, Karl Berntorp, Stefano Di Cairano
2017 conf
CCTA
Avishai Weiss, Claus Danielson, Karl Berntorp, Ilya V. Kolmanovsky, Stefano Di Cairano
2017 J jnl
CoRR
Claus Danielson, Avishai Weiss, Karl Berntorp, Stefano Di Cairano
2017 C conf
ACC
Karl Berntorp
2017 A* conf
ICRA
Oktay Arslan, Karl Berntorp, Panagiotis Tsiotras
2017 C conf
ACC
Kazuhide Okamoto, Karl Berntorp, Stefano Di Cairano
2016 C conf
ACC
Karl Berntorp, Piyush Grover
2016 J jnl
IEEE Trans. Control. Syst. Technol.
Karl Berntorp
2016 C conf
ACC
Karl Berntorp, Stefano Di Cairano
2016 conf
CDC
Claus Danielson, Avishai Weiss, Karl Berntorp, Stefano Di Cairano
2016 conf
CDC
Karl Berntorp, Stefano Di Cairano
2016 J jnl
CoRR
Oktay Arslan, Karl Berntorp, Panagiotis Tsiotras
2016 conf
CDC
Karl Berntorp, Stefano Di Cairano
2016 conf
CDC
Stefano Di Cairano, Uros V. Kalabic, Karl Berntorp
2015 C conf
FUSION
Karl Berntorp
2015 C conf
ACC
Karl Berntorp, Fredrik Magnusson
2015 C conf
ACC
Karl Berntorp
2014 C conf
ACC
Karl Berntorp, Bjorn Olofsson, Anders Robertsson
2014 J jnl
IEEE Trans. Signal Process.
Karl Berntorp, Anders Robertsson, Karl-Erik Årzén
2013 C conf
ACC
Karl Berntorp, Bjorn Olofsson, Bo Bernhardsson, Kristoffer Lundahl, Lars Nielsen
2013 C conf
FUSION
Karl Berntorp, Anders Robertsson, Karl-Erik Årzén
2012 conf
SyRoCo
Jerker Nordh, Karl Berntorp
2012 C conf
CCA
Karl Berntorp, Karl-Erik Årzén, Anders Robertsson
2012 C conf
FUSION
Karl Berntorp, Karl-Erik Årzén, Anders Robertsson
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"