James Taylor

31 papers Journal 20Unranked 10
YearRankTypeTitle / Venue / Authors
2022 J jnl
Nucleic Acids Res.
Enis Afgan, Anton Nekrutenko, Björn A. Grüning, Daniel J. Blankenberg, Jeremy Goecks, Michael C. Schatz, Alexander E. Ostrovsky, Alexandru Mahmoud, Andrew J. Lonie, Anna Syme, Anne Fouilloux, Anthony Bretaudeau, Anup Kumar, Arthur C. Eschenlauer, Assunta D. Desanto, Aysam Guerler, Beatriz Serrano-Solano, Bérénice Batut, Bradley W. Langhorst, Bridget Carr, Bryan A. Raubenolt, Cameron J. Hyde, Catherine J. Bromhead, Christopher B. Barnett, Coline Royaux, Cristóbal Gallardo, Daniel J. Fornika, Dannon Baker, Dave Bouvier, Dave Clements, David A. de Lima Morais, David Lopez Tabernero, Delphine Larivière, Engy Nasr, Federico Zambelli, Florian Heyl, Fotis E. Psomopoulos, Frederik Coppens, Gareth R. Price, Gianmauro Cuccuru, Gildas Le Corguillé, Gregory Von Kuster, Gulsum Gudukbay, Helena Rasche, Hans-Rudolf Hotz, Ignacio Eguinoa, Igor V. Makunin, Isuru Ranawaka, James Taylor, Jayadev Joshi, Jennifer Hillman-Jackson, John Chilton, Kaivan Kamali, Keith Suderman, Krzysztof Poterlowicz, Yvan Le Bras, Lucille Lopez-Delisle, Luke Sargent, Madeline E. Bassetti, Marco Antonio Tangaro, Marius van den Beek, Martin Cech, Matthias Bernt, Matthias Fahrner, Mehmet Tekman, Melanie Christine Föll, Michael R. Crusoe, Miguel Roncoroni, Natalie Kucher, Nate Coraor, Nicholas Stoler, Nick Rhodes, Nicola Soranzo, Niko Pinter, Nuwan Goonasekera, Pablo A. Moreno, Pavankumar Videm, Petera Melanie, Pietro Mandreoli, Pratik D. Jagtap, Qiang Gu, Ralf J. M. Weber, Ross Lazarus, Ruben H. P. Vorderman, Saskia D. Hiltemann, Sergey Golitsynskiy, Shilpa Garg, Simon A. Bray, Simon L. Gladman, Simone Leo, Subina P. Mehta, Timothy J. Griffin, Vahid Jalili, Yves Vandenbrouck, Victor Wen, Vijay K. Nagampalli, Wendi A. Bacon, Willem L. De Koning, Wolfgang Maier, Peter J. Briggs
2020 J jnl
Bioinform.
Vahid Jalili, Enis Afgan, James Taylor, Jeremy Goecks
2020 J jnl
Nucleic Acids Res.
Vahid Jalili, Enis Afgan, Qiang Gu, Dave Clements, Daniel J. Blankenberg, Jeremy Goecks, James Taylor, Anton Nekrutenko
2019 J jnl
Future Gener. Comput. Syst.
Enis Afgan, Andrew Lonie, James Taylor, Nuwan Goonasekera
2018 J jnl
CoRR
Enis Afgan, Andrew Lonie, James Taylor, Nuwan Goonasekera
2018 conf
IEEE CLOUD
Enis Afgan, Vahid Jalili, Nuwan Goonasekera, James Taylor, Jeremy Goecks
2018 J jnl
Nucleic Acids Res.
Enis Afgan, Dannon Baker, Bérénice Batut, Marius van den Beek, Dave Bouvier, Martin Cech, John Chilton, Dave Clements, Nate Coraor, Björn A. Grüning, Aysam Guerler, Jennifer Hillman-Jackson, Saskia D. Hiltemann, Vahid Jalili, Helena Rasche, Nicola Soranzo, Jeremy Goecks, James Taylor, Anton Nekrutenko, Daniel J. Blankenberg
2017 J jnl
PLoS Comput. Biol.
Björn A. Grüning, Eric Rasche, Boris Rebolledo-Jaramillo, Carl Eberhard, Torsten Houwaart, John Chilton, Nate Coraor, Rolf Backofen, James Taylor, Anton Nekrutenko
2016 J jnl
F1000Research
Nitesh Turaga, Mallory Ann Freeberg, Dannon Baker, John Chilton, The Galaxy Team, Anton Nekrutenko, James Taylor
2016 conf
MIPRO
Enis Afgan, Andrew Lonie, James Taylor, Karolj Skala, Nuwan Goonasekera
2016 conf
XSEDE
Nuwan Goonasekera, Andrew Lonie, James Taylor, Enis Afgan
2016 conf
XSEDE
Craig A. Stewart, David Y. Hancock, Matthew Vaughn, Jeremy Fischer, Tim Cockerill, Liming Lee, Nirav C. Merchant, Therese Miller, John Michael Lowe, Daniel C. Stanzione Jr., James Taylor, Edwin Skidmore
2016 J jnl
Nucleic Acids Res.
Enis Afgan, Dannon Baker, Marius van den Beek, Daniel J. Blankenberg, Dave Bouvier, Martin Cech, John Chilton, Dave Clements, Nate Coraor, Carl Eberhard, Björn A. Grüning, Aysam Guerler, Jennifer Hillman-Jackson, Gregory Von Kuster, Eric Rasche, Nicola Soranzo, Nitesh Turaga, James Taylor, Anton Nekrutenko, Jeremy Goecks
2015 conf
MIPRO
Enis Afgan, Konstantinos Krampis, Nuwan Goonasekera, Karolj Skala, James Taylor
2015 J jnl
Concurr. Comput. Pract. Exp.
Enis Afgan, Nathan Coraor, John Chilton, Dannon Baker, James Taylor, The Galaxy Team
2015 conf
XSEDE
Craig A. Stewart, Timothy M. Cockerill, Ian T. Foster, David Y. Hancock, Nirav C. Merchant, Edwin Skidmore, Daniel Stanzione, James Taylor, Steven Tuecke, George W. Turner, Matthew Vaughn, Niall I. Gaffney
2014 conf
GCE@SC
Enis Afgan, Dannon Baker, John Chilton, Nathan Coraor, James Taylor
2014 J jnl
J. Am. Medical Informatics Assoc.
Richard D. LeDuc, Matthew Vaughn, John M. Fonner, Michael Sullivan, James G. Williams, Philip D. Blood, James Taylor, William K. Barnett
2014 J jnl
Nat.
Benjamin D. Pope, Tyrone Ryba, Vishnu Dileep, Feng Yue, Wei-Sheng Wu, Olgert Denas, Daniel L. Vera, Yanli Wang, R. Scott Hansen, Theresa K. Canfield, Robert E. Thurman, Yong Cheng, Günhan Gülsoy, Jonathan H. Dennis, Michael P. Snyder, John A. Stamatoyannopoulos, James Taylor, Ross C. Hardison, Tamer Kahveci, Bing Ren, David M. Gilbert
2014 J jnl
Bioinform.
Daniel J. Blankenberg, James E. Johnson, The Galaxy Team, James Taylor, Anton Nekrutenko
2013 conf
XSEDE
James Taylor, Anton Nekrutenko, Nathan Coraor, Philip D. Blood, Alex Ropelewski, Zhihui Zhang, Josephine Palencia, Sergiu Sanielevici, Jared Yanovich, Robert Budden
2013 J jnl
PLoS Comput. Biol.
Geir Kjetil Sandve, Anton Nekrutenko, James Taylor, Eivind Hovig
2012 J jnl
Concurr. Comput. Pract. Exp.
Enis Afgan, Dannon Baker, Anton Nekrutenko, James Taylor
2012 J jnl
BMC Bioinform.
Enis Afgan, Brad Chapman, James Taylor
2012 conf
eScience
Jeremy Goecks, The Galaxy Team, Anton Nekrutenko, James Taylor
2011 ch.
Guide to e-Science
Enis Afgan, Jeremy Goecks, Dannon Baker, Nate Coraor, Anton Nekrutenko, James Taylor
2011 J jnl
Database J. Biol. Databases Curation
Daniel J. Blankenberg, Nathan Coraor, Gregory Von Kuster, James Taylor, Anton Nekrutenko
2011 J jnl
Bioinform.
Daniel J. Blankenberg, James Taylor, Anton Nekrutenko
2011 conf
BioVis
Jeremy Goecks, Kanwei Li, Dave Clements, The Galaxy Team, James Taylor
2010 J jnl
BMC Bioinform.
Enis Afgan, Dannon Baker, Nathan Coraor, Brad Chapman, Anton Nekrutenko, James Taylor
2010 J jnl
Bioinform.
Daniel J. Blankenberg, Assaf Gordon, Gregory Von Kuster, Nathan Coraor, James Taylor, Anton Nekrutenko
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"