Mahmood Jasim

43 papers A* 7A 4B 3Journal 19Unranked 10
YearRankTypeTitle / Venue / Authors
2026 A* conf
CHI
Matthew Brehmer, Maxime Cordeil, Christophe Hurter, Takayuki Itoh, Wolfgang Büschel, Mahmood Jasim, Arnaud Prouzeau, David Saffo, Lyn Bartram, Sheelagh Carpendale, Chen Zhu-Tian, Andrew Cunningham, Tim Dwyer, Samuel Huron, Masahiko Itoh, Alark Joshi, Kiyoshi Kiyokawa, Hideaki Kuzuoka, Bongshin Lee, Gabriela Molina León, Harald Reiterer, Bektur Ryskeldiev, Jonathan Schwabish, Brian A. Smith, Yasuyuki Sumi, Ryo Suzuki, Anthony Tang, Yalong Yang, Jian Zhao
2026 J jnl
CoRR
Matthew Brehmer, Maxime Cordeil, Christophe Hurter, Takayuki Itoh, Wolfgang Büschel, Mahmood Jasim, Arnaud Prouzeau, David Saffo, Lyn Bartram, Sheelagh Carpendale, Chen Zhu-Tian, Andrew Cunningham, Tim Dwyer, Samuel Huron, Masahiko Itoh, Alark Joshi, Kiyoshi Kiyokawa, Hideaki Kuzuoka, Bongshin Lee, Gabriela Molina León, Harald Reiterer, Bektur Ryskeldiev, Jonathan Schwabish, Brian A. Smith, Yasuyuki Sumi, Ryo Suzuki, Anthony Tang, Yalong Yang, Jian Zhao
2026 A* conf
CHI
Erica C. Babb, Chang Dae Lee, Mahmood Jasim, Hee-Tae Jung
2026 A* conf
CHI
Hessam Djavaherpour, Leni Yang, Yvonne Jansen, Pierre Dragicevic, Narges Mahyar, Mahmood Jasim
2026 J jnl
CoRR
Sarmistha Sarna Gomasta, Mahmood Jasim, Hossein Hadisi, Yvonne Jansen, Pierre Dragicevic, Narges Mahyar, Ali Sarvghad
2026 A* conf
CHI
Margaret Hughes, Cassandra Overney, Mahmood Jasim, Deb Roy
2026 J jnl
ACM Trans. Comput. Hum. Interact.
Jonathan Wang Liu, Mahmood Jasim, Jeong-Heon Song, So-Hwi Ha, Jun-Su Kim, Dayoung Kim, Hyunsuk Lee, Byeong Il Kim, Hee-Tae Jung
2025 A* conf
UIST
Ziwei Gu, Joyce Zhou, Ning-Er (Nina) Lei, Jonathan K. Kummerfeld, Mahmood Jasim, Narges Mahyar, Elena L. Glassman
2025 J jnl
CoRR
Mahmood Jasim, Narges Mahyar
2025 A conf
IUI
Cassandra Overney, Daniel T. Kessler, Suyash Pradeep Fulay, Mahmood Jasim, Deb Roy
2025 conf
CHI Extended Abstracts
Mehrasa Amiri Besheli, Mahmood Jasim
2025 conf
CHI Extended Abstracts
Nushrat Jahan Ria, Mahmood Jasim
2025 J jnl
CoRR
Wolfgang Büschel, Gabriela Molina León, Arnaud Prouzeau, Mahmood Jasim, Christophe Hurter, Maxime Cordeil, Matthew Brehmer
2025 conf
CHI Extended Abstracts
Vimal Thomas Joseph, Mahmood Jasim
2025 conf
CSCW Companion
Margaret Hughes, Cassandra Overney, Ashima Kamra, Jasmin Tepale, Elizabeth Hamby, Mahmood Jasim, Deb Roy
2025 J jnl
CoRR
Maggie Hughes, Cassandra Overney, Ashima Kamra, Jasmin Tepale, Elizabeth Hamby, Mahmood Jasim, Deb Roy
2024 A conf
IEEE VIS
Vyri Yang, Mahmood Jasim
2024 A conf
Conference on Designing Interactive Systems
Mashrur Rashik, Mahmood Jasim, Kostiantyn Kucher, Ali Sarvghad, Narges Mahyar
2023 J jnl
Proc. ACM Hum. Comput. Interact.
Zhiqiu Jiang, Mashrur Rashik, Kunjal Panchal, Mahmood Jasim, Ali Sarvghad, Pari Riahi, Erica Dewitt, Fey Thurber, Narges Mahyar
2023 J jnl
CoRR
Sheshera Mysore, Mahmood Jasim, Andrew McCallum, Hamed Zamani
2023 A* conf
SIGIR
Sheshera Mysore, Mahmood Jasim, Andrew McCallum, Hamed Zamani
2023 B conf
CHIIR
Sheshera Mysore, Mahmood Jasim, Haoru Song, Sarah Akbar, Andre Kenneth Chase Randall, Narges Mahyar
2023 J jnl
CoRR
Sheshera Mysore, Mahmood Jasim, Haoru Song, Sarah Akbar, Andre Kenneth Chase Randall, Narges Mahyar
2023 conf
EMBC
Jeong-Heon Song, Mahmood Jasim, Dawn Roberts, Seon-Chil Kim, Byeongil Kim, Gabriela Marie Rodriguez, Melissa Kay Hord, Hee-Tae Jung
2022 J jnl
Comput. Graph. Forum
Eric P. S. Baumer, Mahmood Jasim, Ali Sarvghad, Narges Mahyar
2022 A* conf
CHI
Mahmood Jasim, Christopher Collins, Ali Sarvghad, Narges Mahyar
2022 J jnl
CoRR
Mahmood Jasim, Christopher Collins, Ali Sarvghad, Narges Mahyar
2021 A conf
Conference on Designing Interactive Systems
Mahmood Jasim, Enamul Hoque, Ali Sarvghad, Narges Mahyar
2021 B conf
Creativity & Cognition
Carolina Aragón, Mahmood Jasim, Narges Mahyar
2020 J jnl
IEEE Trans. Biomed. Eng.
Brandon Oubre, Jean-Francois Daneault, Katherine Boyer, Jae Hyun Kim, Mahmood Jasim, Paolo Bonato, Sunghoon Ivan Lee
2020 J jnl
Frontiers Comput. Sci.
Muhammad Aminur Rahaman, Mahmood Jasim, Md. Haider Ali, Md. Hasanuzzaman
2020 J jnl
Proc. ACM Hum. Comput. Interact.
Mahmood Jasim, Pooya Khaloo, Somin Wadhwa, Amy X. Zhang, Ali Sarvghad, Narges Mahyar
2020 J jnl
CoRR
Mahmood Jasim, Pooya Khaloo, Somin Wadhwa, Amy X. Zhang, Ali Sarvghad, Narges Mahyar
2020 conf
CSCW Companion
Mahmood Jasim, Pooya Khaloo, Somin Wadhwa, Amy X. Zhang, Ali Sarvghad, Narges Mahyar
2020 J jnl
IEEE Computer Graphics and Applications
Narges Mahyar, Mahmood Jasim, Ali Sarvghad
2020 conf
CHI Extended Abstracts
Tamanna Motahar, Mahmood Jasim, Syed Ishtiaque Ahmed, Narges Mahyar
2020 conf
CHI Extended Abstracts
Mahmood Jasim, Ali Sarvghad, Enamul Hoque, Narges Mahyar
2019 J jnl
CoRR
Shruti Jadon, Mahmood Jasim
2018 J jnl
Frontiers Comput. Sci.
Muhammad Aminur Rahaman, Mahmood Jasim, Md. Haider Ali, Tao Zhang, Md. Hasanuzzaman
2016 B conf
CANS
Bashira Akter Anima, Mahmood Jasim, Khandaker Abir Rahman, Adam Rulapaugh, Mohammed Hasanuzzaman
2015 conf
ROBIO
Muhammad Aminur Rahaman, Mahmood Jasim, Tao Zhang, Md. Haider Ali, Md. Hasanuzzaman
2014 J jnl
Int. J. Image Graph.
Mahmood Jasim, Tao Zhang, Md. Hasanuzzaman
2013 conf
ROBIO
Mahmood Jasim, Tao Zhang, M. Hasanuzzaman
redb/extractors/js_extractors/js_strings.py
← Index redb/extractors/js_extractors/js_strings.py python
import base64
import bisect
import inspect
import re
from datetime import datetime, timezone
from typing import Any

from redb.extractors.enum import Tag
from redb.extractors.js_extractor import JSExtractor
from redb.extractors.js_extractors.js_patterns import STRING_PATTERNS, line_offsets

# Local aliases for the compiled patterns this extractor uses. Defined and
# compiled exactly once in js_patterns.STRING_PATTERNS.
_HEX_STRING_RE = STRING_PATTERNS["hex_escape_seq"]
_UNICODE_STRING_RE = STRING_PATTERNS["unicode_escape_seq"]
_CHARCODE_RE = STRING_PATTERNS["charcode_call"]
_BASE64_STRING_RE = STRING_PATTERNS["base64_quoted"]
_CONCAT_STRING_RE = STRING_PATTERNS["concat_chain"]

# Tokeniser used inside _reconstruct_concat to pull each quoted part out of a
# matched concat chain. Compiled once at module load (was recompiled on every
# concat match before).
_CONCAT_TOKEN_RE = re.compile(r'["\']([^"\']*)["\']')


class JSStringsExtractor(JSExtractor):

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

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

    def _decode_hex_string(self, hex_str):
        """Decode \\x41\\x42 style hex strings."""
        try:
            # Remove \\x prefix and decode
            clean = hex_str.replace('\\x', '')
            return bytes.fromhex(clean).decode('utf-8', errors='replace')
        except Exception:
            return None

    def _decode_unicode_string(self, uni_str):
        """Decode \\u0041\\u0042 style unicode strings."""
        try:
            return uni_str.encode('utf-8').decode('unicode_escape')
        except Exception:
            return None

    def _decode_charcode(self, charcode_str):
        """Decode String.fromCharCode(72, 101, 108, ...) sequences."""
        try:
            codes = [int(c.strip()) for c in charcode_str.split(',') if c.strip().isdigit()]
            return ''.join(chr(c) for c in codes if 0 <= c <= 0x10FFFF)
        except Exception:
            return None

    def _decode_base64(self, b64_str):
        """Attempt to decode base64 string."""
        try:
            decoded = base64.b64decode(b64_str)
            # Check if result is printable text
            text = decoded.decode('utf-8', errors='strict')
            # Only return if it looks like text (>80% printable)
            printable = sum(1 for c in text if c.isprintable() or c in '\n\r\t')
            if printable / len(text) > 0.8:
                return text
        except Exception:
            pass
        return None

    def _reconstruct_concat(self, concat_match):
        """Reconstruct concatenated string parts."""
        try:
            parts = _CONCAT_TOKEN_RE.findall(concat_match)
            return ''.join(parts)
        except Exception:
            return None

    def _find_line_number(self, match_start):
        """1-indexed line number for `match_start`, looked up in O(log L) via
        bisect over `self._line_offsets` (built once per extract() call).

        Replaces the historical `self.js_source[:match_start].count('\\n') + 1`
        which was O(N) per call and quadratic across all matches in a sample.
        """
        return bisect.bisect_right(self._line_offsets, match_start)

    def _scan_text(self, text):
        """Run every encoded-string pattern over `text` and return a list of
        finding dicts. Stateless apart from the per-call `_line_offsets` cache,
        which `_find_line_number` reads — callers must reset it before invoking
        this so line numbers reference the text being scanned, not the previous
        one.
        """
        findings = []

        # Hex-encoded strings
        for m in _HEX_STRING_RE.finditer(text):
            raw = m.group()
            decoded = self._decode_hex_string(raw)
            if decoded and len(decoded) >= 4:
                findings.append({
                    'string': decoded[:4000],
                    'string_raw': raw[:4000],
                    'string_encoding': 'hex',
                    'string_offset': self._find_line_number(m.start()),
                    'string_length': len(decoded),
                    'string_raw_length': len(raw),
                    'string_entropy': self._calculate_text_entropy(decoded),
                })

        # Unicode-encoded strings
        for m in _UNICODE_STRING_RE.finditer(text):
            raw = m.group()
            decoded = self._decode_unicode_string(raw)
            if decoded and len(decoded) >= 3:
                findings.append({
                    'string': decoded[:4000],
                    'string_raw': raw[:4000],
                    'string_encoding': 'unicode',
                    'string_offset': self._find_line_number(m.start()),
                    'string_length': len(decoded),
                    'string_raw_length': len(raw),
                    'string_entropy': self._calculate_text_entropy(decoded),
                })

        # String.fromCharCode sequences
        for m in _CHARCODE_RE.finditer(text):
            raw = m.group()
            decoded = self._decode_charcode(m.group(1))
            if decoded and len(decoded) >= 4:
                findings.append({
                    'string': decoded[:4000],
                    'string_raw': raw[:4000],
                    'string_encoding': 'charcode',
                    'string_offset': self._find_line_number(m.start()),
                    'string_length': len(decoded),
                    'string_raw_length': len(raw),
                    'string_entropy': self._calculate_text_entropy(decoded),
                })

        # Base64-encoded strings
        for m in _BASE64_STRING_RE.finditer(text):
            raw = m.group(0)
            b64_val = m.group(1)
            decoded = self._decode_base64(b64_val)
            if decoded and len(decoded) >= 10:
                findings.append({
                    'string': decoded[:4000],
                    'string_raw': raw[:4000],
                    'string_encoding': 'base64',
                    'string_offset': self._find_line_number(m.start()),
                    'string_length': len(decoded),
                    'string_raw_length': len(raw),
                    'string_entropy': self._calculate_text_entropy(decoded),
                })

        # Concatenated strings (reassembled)
        for m in _CONCAT_STRING_RE.finditer(text):
            raw = m.group()
            reconstructed = self._reconstruct_concat(raw)
            if reconstructed and len(reconstructed) >= 20:
                findings.append({
                    'string': reconstructed[:4000],
                    'string_raw': raw[:4000],
                    'string_encoding': 'concat',
                    'string_offset': self._find_line_number(m.start()),
                    'string_length': len(reconstructed),
                    'string_raw_length': len(raw),
                    'string_entropy': self._calculate_text_entropy(reconstructed),
                })

        return findings

    def extract(self):
        src = self.js_source
        if not src:
            return None

        # Pass 1: raw source. _line_offsets is keyed off whichever text is
        # currently being scanned so _find_line_number resolves to that text.
        self._line_offsets = line_offsets(src)
        findings = self._scan_text(src)

        # Pass 2: deobfuscated text, when the deobfuscator produced something
        # meaningfully different. Same patterns, but a different surface — for
        # samples where the encoded payload is hidden behind an outer wrapper
        # (e.g. array.join() + eval in Vjw0rm/WSH-RAT) only this pass yields
        # any rows at all.
        deobf_text, _ = self._context.deobfuscated
        if deobf_text and deobf_text != src:
            self._line_offsets = line_offsets(deobf_text)
            findings.extend(self._scan_text(deobf_text))

        if not findings:
            return None

        # Deduplicate by decoded string value (raw pass wins on collision: it
        # comes first in `findings`). A string that surfaces only in the
        # deobfuscated text still gets persisted, which is the whole point of
        # the second pass.
        seen_values = set()
        deduped = []
        for f in findings:
            val_key = f['string'][:100]
            if val_key not in seen_values:
                seen_values.add(val_key)
                deduped.append(f)

        self.string_findings = deduped[:500]  # Limit per file
        # Publish to the shared context so post-loop consumers (notably the IOC
        # plumbing in workers.py) can scrape the decoded strings without
        # holding a reference to this extractor instance.
        self._context.decoded_strings = self.string_findings
        return self.string_findings

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

            data = []
            for f in self.string_findings:
                data.append([
                    self.sha256,
                    f['string'],
                    f['string_raw'],
                    f['string_encoding'],
                    f['string_offset'],
                    f['string_length'],
                    f['string_raw_length'],
                    f['string_entropy'],
                ])

            column_names = [
                "sha256",
                "string",
                "string_raw",
                "string_encoding",
                "string_offset",
                "string_length",
                "string_raw_length",
                "string_entropy",
            ]

            column_type_names = [
                "FixedString(64)",
                "String",
                "String",
                "LowCardinality(String)",
                "UInt64",
                "UInt32",
                "UInt32",
                "Float32",
            ]

            return (data, column_names, column_type_names)

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