npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@closed-systems/strangerstrings

v1.0.0

Published

A little tool to filter the stranger strings from a binary so you can analyze the good ones

Readme

Stranger Strings

Stranger Strings

A TypeScript module for extracting human-readable strings from binary files and determining which are most likely to be useful for human analysts. This implementation is compatible with the Ghidra string analysis algorithm and uses trigram-based scoring to filter out random character sequences while preserving meaningful strings.

Features

  • Trigram-based scoring: Uses character trigram probabilities to score string quality
  • Ghidra compatibility: Compatible with Ghidra's .sng model files and scoring algorithm.
  • Binary analysis: Extract and analyze strings directly from binary files
  • Adaptive thresholds: Length-based scoring thresholds (shorter strings need higher scores)
  • ASCII normalization: Handles non-ASCII characters and space normalization
  • TypeScript: Full type safety and modern ES features, but I'll likely cut it over to Rust
  • No Runtime Dependencies: We don't drag in the kitchen sink, just the Typescript basics and a testing library.

Effectiveness

I'm not going over 9600 failed strings to find FN, but spot checking was all strangely perfect.

$ strings ./tasmota-UK.bin|wc -l
   12695

$ strangerstrings -v ./tasmota-UK.bin |head -n 20
Loading model: ./StringModel.sng
Model type: lowercase, Lowercase: true
Analyzing file: ./tasmota-UK.bin
Extracted 11011 candidate strings (min length: 4)
String              Score       Threshold   Offset    Valid
----------------------------------------------------------------------
" tER"              -2.026      10.000      0x69DD5   ✗
"AND "              -2.274      10.000      0x96982   ✗
"tele"              -2.386      -2.710      0x97BD2   ✓
"none"              -2.395      -2.710      0x9A02B   ✓
"none"              -2.395      -2.710      0xA4CF8   ✓
" GET"              -2.398      10.000      0x98116   ✗
"STATE"             -2.415      -3.260      0x97B80   ✓
"  tRa"             -2.420      10.000      0x49A16   ✗
"Action"            -2.423      -3.520      0x96DCF   ✓
"user"              -2.443      -2.710      0x9830B   ✓
"scan"              -2.453      -2.710      0x99310   ✓
"center"            -2.472      -3.520      0x97A76   ✓
"POST"              -2.498      -2.710      0xA5186   ✓
"       aRA"              -2.505      10.000      0x69849   ✗
"Done"              -2.506      -2.710      0x9B8AB   ✓
"stat"              -2.518      -2.710      0x97BD7   ✓
"BASE"              -2.524      -2.710      0x9BC91   ✓
"Mode"              -2.542      -2.710      0x95F91   ✓

Summary:
  Accepted: 1375 strings
  Rejected: 9636 strings
  Total: 11011 strings
  Acceptance rate: 12.5%

Just use it as a CLI app

npx closed-systems/strangerstrings --help
npm install -g closed-systems/strangerstrings
strangerstrings --help

Installation in a project

npm install closed-systems/strangerstrings

or with pnpm:

pnpm add closed-systems/strangerstrings

Quick Start

import { StrangerStrings } from 'strangerstrings';
import * as fs from 'fs';

// Initialize analyzer with model
const analyzer = new StrangerStrings();
await analyzer.loadModel({ modelPath: './StringModel.sng' });

// Analyze individual strings
const result = analyzer.analyzeString('hello world');
console.log(`Valid: ${result.isValid}, Score: ${result.score}`);

// Analyze binary file
const binaryData = fs.readFileSync('./program.exe');
const validStrings = analyzer.analyzeBinaryFile(binaryData);
console.log(`Found ${validStrings.length} valid strings`);

API Reference

StrangerStrings Class

Constructor

const analyzer = new StrangerStrings();

loadModel(options)

Load a trigram model from file or string content.

// From file
await analyzer.loadModel({ modelPath: './StringModel.sng' });

// From string content  
await analyzer.loadModel({ modelContent: modelFileContent });

analyzeString(candidateString)

Analyze a single string and return detailed scoring information.

const result = analyzer.analyzeString('hello world');
// Returns: StringAnalysisResult
// {
//   originalString: 'hello world',
//   score: -4.123,
//   threshold: -5.42,
//   isValid: true,
//   normalizedString: 'hello world'
// }

analyzeStrings(candidateStrings)

Analyze multiple strings at once.

const results = analyzer.analyzeStrings(['hello', 'world', 'xZ#@$%']);

extractValidStrings(candidateStrings)

Get only the valid strings from a list of candidates.

const validOnly = analyzer.extractValidStrings(['hello', 'world', 'xZ#@$%']);
// Returns only strings that pass the scoring threshold

analyzeBinaryFile(buffer, options?)

Extract and analyze strings from binary data.

const binaryData = fs.readFileSync('./program.exe');
const validStrings = analyzer.analyzeBinaryFile(binaryData, { 
  minLength: 6  // minimum string length to extract
});

extractStringsFromBinary(buffer, minLength?)

Extract raw strings from binary data without scoring.

const strings = analyzer.extractStringsFromBinary(binaryData, 4);

Convenience Functions

import { analyzeStringsWithModel, analyzeBinaryWithModel } from 'strangerstrings';

// Quick analysis without creating class instance
const results = await analyzeStringsWithModel(
  ['string1', 'string2'], 
  './StringModel.sng'
);

const validStrings = await analyzeBinaryWithModel(
  binaryBuffer, 
  './StringModel.sng'
);

Model Files

The module uses .sng model files containing trigram frequency data. These are tab-delimited text files with the format:

# Model Type: lowercase
# Training file: words.txt
# [^] denotes beginning of string  
# [$] denotes end of string
# [SP] denotes space

char1	char2	char3	count
[^]	h	e	1234
h	e	l	5678
l	l	o	9012
o	[$]	[$]	3456

Algorithm Details

The scoring algorithm works as follows:

  1. Normalization: Convert to lowercase (if using lowercase model), trim spaces, replace non-ASCII with spaces
  2. Trigram Analysis: Calculate probability for each 3-character sequence
  3. Scoring: Sum log probabilities and divide by string length
  4. Thresholding: Compare against length-based thresholds
  5. Smoothing: Apply Laplace smoothing for unseen trigrams

Scoring Formula

score = (Σ log10(P(trigram))) / string_length

Length-based Thresholds

  • Length 4: -2.71
  • Length 5: -3.26
  • Length 10: -4.55
  • Length 50+: -6.13
  • Length 100+: -6.3

Strings shorter than 4 characters receive a default score of -20 and thresholds impossible to pass.

Examples

See the examples/ directory for complete usage examples:

  • basic-usage.ts - Basic string analysis
  • binary-analysis.ts - Binary file processing

Testing

# Run tests
pnpm test

# Run tests with UI
pnpm test:ui

# Run tests once
pnpm test:run

Compatibility

This implementation produces scoring results compatible with the Java implementation from Ghidra. The algorithm uses:

  • Base-10 logarithms (Math.log10)
  • Identical smoothing and probability calculations
  • Same threshold values and length-based scoring
  • Compatible .sng model file format
  • Needs some proper kicking but spot checking seems right

Future Ideas

  • I could nitpick the corpus that the Ghidra team used to make the dataset but I can't think of anything better with any baseline of truth so I stick with theirs. As noted in https://github.com/NationalSecurityAgency/ghidra/issues/2106 it's a bit tricky to adapt or extend their model but with a good idea of finding the wheat and chaffe for a training set a good multilingual approach could likely be found (I'm looking at you stats nerds).
  • it could benefit in a re-write in Rust
  • Multilanguage is definitely a gap, as is base64.
  • If it was going to be really fancy it could examine the binary to determine the base and look for pointers as string offsets (for languages without C strings)

License

Lets say Apache License 2.0, as thats what StringModel.sng is under, most likely

Contributing

Contributions welcome! Please ensure tests pass and new features include appropriate test coverage.

Claude helped with this implementation but if you're going to submit LLM aided code you better understand it lest you be mocked.