Vicki L. Almstrum

40 papers A 13B 9Journal 13Unranked 5
YearRankTypeTitle / Venue / Authors
2025 conf
SIGCSE (2)
Vicki L. Almstrum, Kim W. Tracy
2012 A conf
SIGCSE
Vicki L. Almstrum, Barbara Boucher Owens, Mary Z. Last, Deepa Muralidhar
2011 B conf
ITiCSE
Vicki L. Almstrum, Deepa Muralidhar, Mary Z. Last, Barbara Boucher Owens
2009 A conf
SIGCSE
Jon Beck, Vicki L. Almstrum, Heidi J. C. Ellis, Massood Towhidnejad
2008 J jnl
ACM SIGCSE Bull.
Vicki L. Almstrum, E. Anne G. Applin, Barbara Boucher Owens, Elizabeth S. Adams, Lecia Jane Barker, John Impagliazzo, Patricia A. Joseph, Amardeep Kahlon, Mary Z. Last, Andrea Lawrence, Alison Young
2008 A conf
ICER
Kate Sanders, Brad Richards, Jan Erik Moström, Vicki L. Almstrum, Stephen H. Edwards, Sally Fincher, Katherine Gunion, Mark Hall, Brian Hanks, Stephen Lonergan, Robert McCartney, Briana B. Morrison, Jaime Spacco, Lynda Thomas
2008 A conf
SIGCSE
Vicki L. Almstrum, William Aspray, Mary Anne L. Egan, John Impagliazzo
2008 B conf
ITiCSE
Alison Young, Arnold Pears, Pedro de Miguel Anasagasti, Ralf Romeike, Michael Goldweber, Michael H. Goldwasser, Vicki L. Almstrum
2007 B conf
ITiCSE
Barbara Boucher Owens, Vicki L. Almstrum, Lecia Jane Barker
2006 A conf
SIGCSE
Vicki L. Almstrum, David Klappholz, Steven Condly, John M. Clement
2006 J jnl
ACM SIGCSE Bull.
Vicki L. Almstrum, Peter B. Henderson, Valerie J. Harvey, Cinda Heeren, William A. Marion, Charles Riedesel, Leen-Kiat Soh, Allison Elliott Tew
2006 B conf
ITiCSE
Vicki L. Almstrum, Mary Z. Last
2006 conf
CSEE&T
David Klappholz, Vicki L. Almstrum, Kenneth L. Modesitt
2005 J jnl
ACM SIGCSE Bull.
Vicki L. Almstrum, Lecia Jane Barker, Barbara Boucher Owens, Elizabeth S. Adams, William Aspray, Nell B. Dale, Wanda P. Dann, Andrea Lawrence, Leslie Schwartzman
2005 A conf
SIGCSE
Vicki L. Almstrum, Orit Hazzan, Mark Guzdial, Marian Petre
2005 B conf
ITiCSE
Vicki L. Almstrum, Mary Z. Last
2004 J jnl
Comput. Sci. Educ.
Vicki L. Almstrum, Orit Hazzan, David Ginat
2003 J jnl
ACM SIGCSE Bull.
Thomas L. Naps, Guido Rößling, Vicki L. Almstrum, Wanda P. Dann, Rudolf Fleischer, Christopher D. Hundhausen, Ari Korhonen, Lauri Malmi, Myles F. McNally, Susan H. Rodger, J. Ángel Velázquez-Iturbide
2003 A conf
SIGCSE
Vicki L. Almstrum, David Ginat, Orit Hazzan, John M. Clement
2003 J jnl
Commun. ACM
Vicki L. Almstrum
2002 B conf
ITiCSE
Vicki L. Almstrum, David Ginat, Orit Hazzan, Tom Morley
2002 A conf
SIGCSE
Peter B. Henderson, Paul De Palma, Vicki L. Almstrum, Orit Hazzan, Kim Potter Kihlstrom
2001 J jnl
ACM SIGCSE Bull.
Michael McCracken, Vicki L. Almstrum, Danny Diaz, Mark Guzdial, Dianne Hagan, Yifat Ben-David Kolikant, Cary Laxer, Lynda Thomas, Ian Utting, Tadeusz Wilusz
2001 J jnl
Inf. Process. Lett.
Vicki L. Almstrum, David Gries
2001 B conf
ITiCSE
Martha Lucia Hause, Vicki L. Almstrum, Mary Z. Last, Mark R. Woodroffe
2001 J jnl
ACM SIGCSE Bull.
Vicki L. Almstrum, C. Neville Dean, Don Goelman, Thomas B. Hilburn, Jan Smith
2000 B conf
ITiCSE
Mary Z. Last, Mats Daniels, Vicki L. Almstrum, Carl Erickson, Bruce J. Klein
1998 J jnl
ACM SIGCSE Bull.
Vicki L. Almstrum
1997 conf
ACSE
Anders Berglund, Mats Daniels, Vicki L. Almstrum
1997 J jnl
Inf. Softw. Technol.
Barbara A. Kitchenham, Pearl Brereton, David Budgen, Stephen G. Linkman, Vicki L. Almstrum, Shari Lawrence Pfleeger
1997 A conf
SIGCSE
Michael J. Clancy, Ed Dubinsky, Richard A. Duggan, Marian Petre, Vicki L. Almstrum, Mickey McDonald
1996 B conf
ITiCSE
Vicki L. Almstrum, Nell B. Dale, Anders Berglund, Mary J. Granger, Joyce Currie Little, Diane M. Miller, Marian Petre, Paul Schragger, Frederick N. Springsteel
1995 conf
CSEE
Vicki L. Almstrum, Debra Burton, Ann E. Fleury
1994 A conf
SIGCSE
Vicki L. Almstrum, Cheng-Chih Wu, Debra Burton
1993 conf
ACM Conference on Computer Science
Vicki L. Almstrum, Anita Borg, J. Paul Myers Jr.
1993 A conf
SIGCSE
Vicki L. Almstrum, Anita Borg, J. Paul Myers Jr.
1993 A conf
SIGCSE
Vicki L. Almstrum, Debra Burton, Cheng-Chih Wu
1993 J jnl
ACM SIGCSE Bull.
Vicki L. Almstrum, Cheng-Chih Wu
1991 J jnl
ACM SIGCSE Bull.
Vicki L. Almstrum, David C. Platt
1991 A conf
SIGCSE
Vicki L. Almstrum
redb/extractors/ioc_extractor/ioc_extractor.py
← Index redb/extractors/ioc_extractor/ioc_extractor.py python
"""
IOC Extractor - Extractor class for extracting IOCs from decompilation results.

This extractor works with in-memory data from DecompileBinja, following the
standard Extractor pattern to support both ClickHouse and PrintExporter (dry-run).

Usage:
    # After DecompileBinja completes:
    ioc_extractor = IOCExtractorFromResults(
        analysis_results=decompiler.analysis_results,
        sha256=sha256,
        log=logger,
        exporters=exporters,
        index_prefix=index_prefix
    )
    ioc_extractor.export_data()
"""

import inspect
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, List, Dict, Optional

from redb.extractors.enum import Tag
from redb.extractors.database_exporters import DatabaseExporter

# Import the IOCScraper and related classes from standalone module
from redb.extractors.ioc_extractor.standalone_ioc_extractor import (
    IOCScraper,
    IOCType,
    SourceType,
    ExtractedIOC,
)
from typing import Set


class IOCExtractorFromResults:
    """
    Extracts IOCs from in-memory decompilation results.

    This follows a simplified Extractor pattern but doesn't inherit from Extractor
    since it doesn't read from a binary file - instead it takes already-processed
    analysis results from DecompileBinja.
    """

    def __init__(
        self,
        analysis_results: Dict[str, Any],
        sha256: str,
        log: Any,
        exporters: Optional[List[DatabaseExporter]] = None,
        index_prefix: Optional[str] = None,
        tld_file: Optional[Path] = None,
        suppress_types: Optional[Set[IOCType]] = None,
        js_context: bool = False,
    ):
        """
        Initialize IOC Extractor with analysis results.

        Args:
            analysis_results: Dict containing 'strings' and 'decompiled' lists from DecompileBinja
            sha256: Sample SHA256 hash
            log: Logger instance
            exporters: List of database exporters (ClickHouse, Print, etc.)
            index_prefix: Index prefix for database
            tld_file: Optional path to TLD list file
            js_context: When True, the underlying IOCScraper rejects FQDN
                candidates that match JS object-access syntax (see
                JS_FP_TLDS / JS_FP_SLDS). Set this for the JS pipeline only;
                APK suppresses FQDN entirely via suppress_types and binary
                callers leave it disabled.
        """
        self.log = log
        self.log.debug(f"Creating {self.__class__.__name__}")
        self.analysis_results = analysis_results
        self.sha256 = sha256
        self.exporters = exporters or []
        self.index_prefix = index_prefix
        self.scraper = IOCScraper(
            tld_file, suppress_types=suppress_types, js_context=js_context,
        )
        self.extracted_iocs: List[ExtractedIOC] = []

    def extract(self) -> List[ExtractedIOC]:
        """
        Extract IOCs from strings and decompiled functions in analysis_results.

        Returns:
            List of ExtractedIOC objects
        """
        self.log.debug(inspect.currentframe().f_code.co_name)
        self.extracted_iocs = []

        # Extract from strings
        strings_count = self._extract_from_strings()

        # Extract from decompiled functions
        functions_count = self._extract_from_decompiled()

        # Extract from text-based artefact surfaces (JS, PowerShell, etc.)
        text_count = self._extract_from_text()

        self.log.info(
            f"Extracted {len(self.extracted_iocs)} IOCs for {self.sha256[:16]}... "
            f"(strings: {strings_count}, functions: {functions_count}, "
            f"text: {text_count})"
        )

        return self.extracted_iocs

    def _extract_from_strings(self) -> int:
        """Extract IOCs from sample's strings."""
        count = 0
        strings = self.analysis_results.get("strings", [])

        for s in strings:
            string_value = s.get("string", "")
            string_offset = s.get("string_offset", 0)

            if isinstance(string_value, bytes):
                string_value = string_value.decode('utf-8', errors='replace')

            for ioc in self.scraper.scrape(string_value, SourceType.STRING, str(string_offset)):
                self.extracted_iocs.append(ioc)
                count += 1

        return count

    def _extract_from_decompiled(self) -> int:
        """Extract IOCs from sample's decompiled functions.

        Supports both Binja format (key: "decompiled", fields: "decompiled_function",
        "decompiled_function_hash", "function_type") and APK format (key:
        "decompiled_content", fields: "decompiled_method", "decompiled_method_hash",
        "method_type").
        """
        count = 0

        # Binja format
        decompiled = self.analysis_results.get("decompiled", [])
        for func in decompiled:
            func_type = func.get("function_type", "UNKNOWN")
            if func_type in ("LIBRARY", "THUNK"):
                continue

            func_content = func.get("decompiled_function", "")
            func_hash = func.get("decompiled_function_hash", "unknown")

            if isinstance(func_content, bytes):
                func_content = func_content.decode('utf-8', errors='replace')

            for ioc in self.scraper.scrape(func_content, SourceType.DECOMPILED_FUNCTION, func_hash):
                self.extracted_iocs.append(ioc)
                count += 1

        # APK format (decompiled_content with method-level fields)
        decompiled_content = self.analysis_results.get("decompiled_content", [])
        for func in decompiled_content:
            func_type = func.get("method_type", "UNKNOWN")
            if func_type in ("LIBRARY", "THUNK"):
                continue

            func_content = func.get("decompiled_method", "")
            func_hash = func.get("decompiled_method_hash", "unknown")

            if isinstance(func_content, bytes):
                func_content = func_content.decode('utf-8', errors='replace')

            for ioc in self.scraper.scrape(func_content, SourceType.DECOMPILED_FUNCTION, func_hash):
                self.extracted_iocs.append(ioc)
                count += 1

        return count

    def _extract_from_text(self) -> int:
        """Extract IOCs from text-based artefact surfaces.

        Walks `analysis_results["text_raw"]` and `analysis_results["text_normalized"]`,
        each a list of `{"content": str, "content_hash": str}` dicts. Each
        list is routed through its own SourceType (`TEXT_RAW` /
        `TEXT_NORMALIZED`) so analysts can distinguish IOCs that were already
        present in the raw source from those exposed only after normalisation
        (deobfuscation/beautification). Generic across text-based formats —
        used by JS today, intended for PowerShell, Python, email body,
        extracted PDF/Office text in the future.
        """
        count = 0

        for key, source_type in (
            ("text_raw", SourceType.TEXT_RAW),
            ("text_normalized", SourceType.TEXT_NORMALIZED),
        ):
            for entry in self.analysis_results.get(key, []):
                content = entry.get("content", "")
                content_hash = entry.get("content_hash", "unknown")

                if isinstance(content, bytes):
                    content = content.decode('utf-8', errors='replace')

                for ioc in self.scraper.scrape(content, source_type, content_hash):
                    self.extracted_iocs.append(ioc)
                    count += 1

        return count

    def prepare_export_data(self, exporter_type: str) -> Any:
        """
        Prepare data for specific export type.

        Returns tuple for ClickHouse or list of dicts for Print/Elasticsearch.
        """
        self.log.debug(inspect.currentframe().f_code.co_name)

        if not self.extracted_iocs:
            return None

        now = datetime.now(timezone.utc)

        if exporter_type == "ClickHouseExporter":
            data = [
                [
                    self.sha256,
                    ioc.ioc_type.value,
                    ioc.ioc_value,
                    ioc.source_type.value,
                    ioc.source_identifier,
                    now,
                ]
                for ioc in self.extracted_iocs
            ]

            column_names = [
                "sha256",
                "ioc_type",
                "ioc_value",
                "source_type",
                "source_identifier",
                "extracted_at",
            ]

            column_type_names = [
                "FixedString(64)",
                "Enum8('ipv4'=1, 'ipv6'=2, 'fqdn'=3, 'url'=4, 'email'=5, 'server'=6, "
                "'hash_md5'=10, 'hash_sha1'=11, 'hash_sha256'=12, 'cve'=20, 'cwe'=21, 'cpe'=22, "
                "'crypto_btc'=30, 'crypto_eth'=31, 'crypto_xrp'=32, 'crypto_bch'=33, "
                "'crypto_ada'=34, 'crypto_substrate'=35, 'path_linux'=40, 'path_windows'=41, "
                "'registry_key'=42, 'onion'=50)",
                "String",
                "Enum8('decompiled_function'=1, 'disassembled_function'=2, 'string'=3, "
                "'text_raw'=4, 'text_normalized'=5)",
                "String",
                "DateTime64(3, 'UTC')",
            ]

            return (data, column_names, column_type_names)

        else:
            # For PrintExporter and others - return list of dicts
            return [
                {
                    "sha256": self.sha256,
                    "ioc_type": ioc.ioc_type.value,
                    "ioc_value": ioc.ioc_value,
                    "source_type": ioc.source_type.value,
                    "source_identifier": ioc.source_identifier,
                    "extracted_at": now.isoformat(),
                }
                for ioc in self.extracted_iocs
            ]

    def get_clickhouse_table(self) -> str:
        """Return the ClickHouse table name for IOCs."""
        return "redb_iocs"

    def tag(self) -> str:
        """Return the tag for this extractor."""
        return Tag.IOC.value if hasattr(Tag, 'IOC') else "ioc"

    def export_data(self) -> bool:
        """
        Export extracted IOCs to all configured exporters.

        Returns:
            True if export succeeded, False if failed, None if no data
        """
        self.log.debug(inspect.currentframe().f_code.co_name)

        # First extract the IOCs
        extracted = self.extract()

        if not extracted:
            self.log.debug("No IOCs extracted, skipping export")
            return None

        success = True

        from redb.extractors.database_exporters import PrintExporter, ClickHouseExporter

        for exporter in self.exporters:
            try:
                if isinstance(exporter, PrintExporter):
                    # For PrintExporter, pass the list of dicts
                    export_data = self.prepare_export_data("PrintExporter")
                    success &= exporter.export(export_data)

                elif isinstance(exporter, ClickHouseExporter):
                    # For ClickHouse, pass tuple with table info
                    export_data = self.prepare_export_data("ClickHouseExporter")
                    if export_data:
                        success &= exporter.export(
                            export_data,
                            table=self.get_clickhouse_table(),
                            column_names=export_data[1],
                            column_type_names=export_data[2]
                        )

            except Exception as e:
                self.log.error(f"Error exporting IOCs to {exporter.__class__.__name__}: {e}")
                success = False

        return success