剑庭安
首页
黑客安全首页全部安全栏目 黑客技术黑客技术、渗透测试、漏洞利用工具与教程 安全软件安全工具、扫描器、检测与审计软件 病毒分析恶意软件分析、样本、逆向与沙箱 CTF思路CTF 题目、writeup、解题思路与工具 防护思路防御技术、加固、检测响应与蓝队 红蓝攻防红队攻击模拟、蓝队对抗、攻防演练 漏洞知识库漏洞、CVE、PoC、EXP 与安全研究
逆向|反编译
PHP源码PHP 网站源码、程序、系统 APP源码安卓/iOS APP 源码 ASP源码ASP 网站源码与程序 NET源码.NET 网站源码与系统 小程序源码微信/支付宝小程序源码 Dedecms风格模板Dedecms 织梦风格模板 Empirecms风格模板EmpireCMS 帝国风格模板 Discuz商业模板/插件Discuz 商业模板与插件 WordPress主题/插件WordPress 主题与插件 其它源码、模板其它各类源码与模板 游戏源码游戏源码与引擎项目 加密货币源码加密货币、区块链、钱包、USDT/比特币相关源码与工具 手游源码手机游戏源码、服务端、客户端与引擎项目 传奇源码热血传奇、传奇世界、复古传奇等客户端/服务端/引擎源码 页游源码网页游戏、H5 游戏、浏览器游戏源码 端游源码电脑单机游戏、PC 网游客户端/服务端源码
最新万能免驱动支持所有型号打印机下载程序 日历天气老黄历 · 每天同步真实日期与天气
站长工具站长工具 自助开通会员浏览该分类 其他资源qtziy SEO工具SEO工具 站群源码站群源码 泛程序泛程序 蜘蛛模板蜘蛛模板 蜘蛛池蜘蛛池 外链工具外链工具
搜索 自助开通会员🔓 破甲 API
登录 注册
首页 分类列表 CTF思路 unbuned - Bun decompiler-sty…
unbuned - Bun decompiler-style JavaScript extractor for Bun- CTF思路 点击放大
作者:admin 发布:2026-10-06

unbuned - Bun decompiler-style JavaScript extractor for Bun-

项目说明<di="center"><img src="assets/unbuned.png" alt="unbuned logo" width="200"/>unbunedExtract Ja f…
资源可下载 免费下载 0 条讨论
会员资源
开通会员后即可下载全部资源。
登录后开通会员
会员方案
123浏览量
0讨论
2下载链接
可下载资源状态
内容可验证
更新 2026-10-06
人工审核发布
评论区答疑

unbuned - Bun decompiler-style JavaScript extractor for Bun-

项目说明

<di="center">

<img src="assets/unbuned.png" alt="unbuned logo" width="200"/>

unbuned

Extract Ja from Bun-compiled executables

The easiest Bun decompiler-style Ja extractor for reverse engineering, malware analysis, security research, and code recovery.

![Python]()
![License](LICENSE)
![Release]()
![Platforms]()
![Dependencies]()
![Stars]()
![Forks]()

Why It Exists | Quick Start | Features | Real-World Samples | How It Works

</div>

---

What is unbuned?

unbuned is a zero-dependency Python extractor for Bun-compiled executables. It locates the embedded Bun bundle, strips the binary noise around it, and writes clean Ja back to disk.

If you want to reverse engineer a Bun CLI, inspect a suspicious Bun-packed binary, recover lost app logic, or study how a production Bun app is bundled, this is the tool.

Why It Exists

I built unbuned for the exact moment where a Bun executable lands on disk and you do not want a full re project just to see the application logic. Most of the time, the thing you actually need is the Ja bundle, fast, with as little friction as possible.

That is the whole point of this repo: one Python file, no dependencies, no install ceremony, and output you can immediately grep, diff, beautify, or audit.

If this tool saves you time, star the repo. That is what helps it reach more re, researchers, and malware analysts.

---

Installation

No dependencies required. Just Python 3.6+.

---

Quick Start

Examples:

Output lands here:

Options

| Flag | Effect |
|---|---|
| -o, --output DIR | Write to DIR instead of output/<name> |
| --format | Reformat the extracted Ja for reading |
| --indent N | Spaces per indentation level with --format (default 2) |
| --wrap-at N | Column at which --format wraps long lines (default 100, 0 disables) |
| -a, --all | Dump e: modules, sources, assets, manifests and bytecode |
| -m, --modules | Also write one file per compiled module plus manifest.json |
| --sources | Also write the original sources recovered from the source maps |
| --assets | Also extract the non-Ja files the executable embeds |
| --bytecode | Also dump the bytecode regions that surround the Ja |
| --inspect | Report on the executable without writing anything |
| --json | Emit the inspection report as JSON |
| --arch CPU | Pick a slice from a uni Mach-O by CPU type |
| --chunk-size N | Boundary scan granularity (default 1000) |
| --threshold F | Non-text ratio that ends the Ja (default 0.3) |
| --skip-existing | Ne an existing output file |
| -q, --quiet | Suppress progress output |

E In One Dump

--all walks the whole bundle and writes a tree:

On claude.exe that is 2,299 files in about three seconds.

---

Features

  • Extract Ja from Bun-compiled executables with pure Python 3.6+

  • Reformat the recovered Ja with a built-in, dependency-free beautifier

  • Parse Windows PE .bun sections directly

  • Parse macOS Mach-O __BUN,__bun sections directly, thin or uni

  • Parse Linux/ELF .bun sections directly, 32-bit or 64-bit, either endianness

  • Fall back to Bun magic-byte discovery when section metadata is una

  • Split the bundle into its indi compiled modules with a JSON manifest

  • Reco module's real embedded path from Bun's own module graph

  • Recover the original pre-bundle sources and their real paths from the

  • embedded source maps, Zstandard compressed, into a mirrored tree
  • Extract every embedded non-Ja file, compressed or not, and name

  • it from the graph
  • Dump e in one pass with --all

  • Report whether the executable runs Ja bytecode instead of source

  • Write byte-exact output with no newline translation or lossy re-encoding

  • Memory-map the input instead of loading it into RAM

  • Sa UTF-8 Ja to output/<name>/<name>.js

  • Stay readable, hackable, and dependency-free
  • Supported Targets

    unbuned currently handles:

  • Windows PE Bun executables

  • macOS thin and uni (FAT) Mach-O Bun executables

  • Linux/ELF Bun executables

  • Other Bun-packed binaries when the bundle can be located through the Bun magic-byte fallback
  • Splitting Into Modules

    A Bun bundle is not one file. It is a NUL-delimited list of compiled modules,
    each introduced by a // @bun header line. --modules writes them out
    separately so you can grep, diff, and open them indi:

    Module names come from Bun's own module graph, so they are the real embedded
    paths rather than guesses (see Real File Names). When a
    binary ships no readable graph, the name falls back to the module's first
    import specifier. CommonJS modules get a .cjs extension, ES modules .js.

    Real File Names

    Bun does not just concatenate your source. It compiles the whole program into
    a single blob and, before that blob, writes a table describing every file it
    embedded: the original path, the source text, any source map, the JSC bytecode,
    and the loader and format it was compiled with.

    That table is a StandaloneModuleGraph, and it is the reason unbuned can put
    real names on things. Bun's build step appends it to the end of the .bun
    section in a fixed order: the file records, a 32 byte Offsets struct, then
    the literal marker \n---- Bun! ----\n.

    Finding the trailer from the end of the section locates the header, the header
    points at the table, and the table is 52 bytes per file:

    One detail matters more than it looks: every one of those offsets is relative
    to the start of the section *plus* the section's own eight byte length header.
    Missing that +8 makes every pointer land eight bytes early, which is enough
    to turn a whole table into garbage.

    Records are written in load order, entry point first, then its static imports,
    then dynamic imports breadth first. So a module's contents offset is exactly
    where that module's text begins in the bundle, and matching a module to its
    name is a dict lookup rather than a guess:

    The graph also answers questions the bundle text cannot. For claude.exe:

    entry_point_id and the startup count come from the same header, and the
    remaining flags decode to the compiler's own feature set (has_source_hashes,
    has_builtin_bytecode, has_bytecode_string_table, and so on). All of it is
    written to manifest.json under module_graph, and every module entry gains
    the path the graph recorded for it.

    What the graph cannot give you is the original import graph, or a name better
    than the one the bundler chose. Bun has already hashed chunks to
    chunk-9fxe9jf7.js by the time it writes the table. unbuned reports what the
    compiler recorded and does not pretend to recover more.

    Formatting The Output

    Bun ships minified code, and minified code is one enormous line per module.
    --format runs the bundle through a built-in beautifier so the output is
    readable without installing anything:

    Why It Is Built In

    Prettier, js-beautify and Babel are Node packages, and they all build a full
    syntax tree before printing anything. That is the wrong shape for a Python tool
    that has to stay one file with no dependencies, and it is the wrong shape for a
    40 MB bundle. unbuned instead ships a single-pass lexer: it scans for the
    constructs that change layout (strings, template literals, comments, regular
    expressions, operators, brackets) and copies e between them through
    untouched.

    That design has one hard guarantee, and it is the important one: **the formatter
    only ever adds or remo.** No token is rewritten, no string is
    re-quoted, no operator is re-ordered. The printed file is the extracted file
    with different whitespace, so the two are identical once whitespace is
    collapsed:

    That guarantee is checked two ways. The test suite pins the cases that used to
    break it: a quote that must not pair across code, a regular expression that
    starts with =, a di that must not be scanned for a pattern, and a
    template literal that has to survive a run of minified code in front of it. On
    top of that, the whole 40 MB claude.exe bundle is formatted end to end and
    compared, module by module, and every module is then parsed as an ECMAScript
    module by node itself.

    Node only honours node --check on a .js file when that file has no
    import or export statement in it. A Bun bundle always does, so checking
    one by its path silently succeeds without parsing anything. Pipe the module
    in through standard input with --input-type=module to make node parse it
    for real:

    What It Does

  • indents by brace depth, with a cap so unbalanced input cannot inflate the file

  • breaks statements, object and array literals, and switch cases

  • pads operators, and keeps unary uses, ++/--, and ?. glued correctly

  • separates / di from /regex/ literals, including after if (...) heads

  • and after any other value, and reads a pattern such as /=/g from the source
    before accepting /= as an assignment operator
  • pairs quotes strictly, so a string can never run away through the rest of a

  • module and shift every template literal after it
  • keeps import/export specifier lists on one line, empty braces inline

  • formats small ${...} interpolations, and copies large ones

  • wraps long lines at commas, binary operators, and member accesses

  • resets layout state at each // @bun module header, so one unbalanced module

  • cannot push e after it deeper and deeper

    It preserves the author's own line breaks where they exist and is deliberately
    conser about the rest. On claude.exe the whole 40 MB bundle formats in
    about 21 seconds, every one of the 2156 modules comes back with its content
    untouched, and every module is then parsed as an ECMAScript module by node
    itself.

    The trade-offs are honest ones:

  • it is a lexer, not a parser, so it ne, reorders, or rewrites

  • a single token longer than the wrap width stays on its own long line

  • interpolation bodies that are huge or deeply nested are copied

  • a statement with no comma, operator, or bracket cannot be split at all
  • Bytecode-Compiled Binaries

    When a binary is built with --bytecode, the Ja you extract is the
    *fallback* source. The bundle also carries a Ja bytecode payload,
    and that is what actually executes at runtime. unbuned reports this:

    Use --bytecode to write the bytecode out. It writes one blob per module into
    bytecode/, taken from the bytecode pointer and length the module graph
    already records for each file:

    Slicing the section into a "head" before the Ja and a "tail" after it,
    which is what this used to do, produces blobs that are mostly the module graph
    and the source rather than bytecode. On claude.exe that head was about
    109 MB of mostly metadata. The pointer-driven path writes 2164 real blobs and
    bytecode/index.json, and falls back to head and tail only when a binary's
    graph carries no pointers at all.

    Every blob starts with the same header, and unbuned checks it:

    The declared length matches the graph's recorded length for every module in
    claude.exe, which is a useful integrity check on the extraction.

    Each module's own string pool is read too and recorded per module. In practice
    those pools are dominated by Ja runtime names such as generator,
    iterator and homeObject, rather than application identifiers, and a
    printable run sometimes runs one byte past the name it holds. Treat them as
    leads. The literals that actually name things live in a shared table the
    compiler appends once per binary, which unbuned does not read yet.

    What unbuned does not do is turn bytecode back into Ja. JSC bytecode
    is a register machine whose opcodes, register allocation and calling con
    are undocumented and change between releases, so reconstructing source from it
    is a compiler backend rather than a parser. The blobs are written in a form a
    disassembler can work from, but no disassembler is bundled.

    Embedded Assets

    A Bun executable is not only Ja. Non-Ja files are embedded as
    Zstandard frames inside the same section and ne in the source at
    all. --assets finds and extracts them:

    claude.exe turns out to ship 138 of them, about 10 MB decompressed: skill
    definitions with YAML front matter, HTML templates, and browser
    bundles.

    Their names come from the module graph, which records the real path for every
    embedded file, so all 138 are named exactly. Two suffixes are Bun's plumbing
    rather than part of the name and are removed: the trailing .zst that marks
    the compressed frame, and the .txt Bun appends to keep a non-text asset on
    its text pipeline. The content hash stays, because it is part of the name the
    bundler recorded and dropping it would collide distinct assets that share a
    stem:

    Not every embedded file is compressed. droid.exe stores its native
    helpers and its skills as UTF-16, so each file is sliced out at the
    length the graph records, and the recorded encoding decides whether it is
    written as stored or transcoded to UTF-8. A helper the bundler could not give
    an extension ends its recorded name in a bare dot, which is why the payload's
    own magic decides the extension:

    The last one is Bun's placeholder extension for files whose original one is
    not part of the bundle, which strands the real extension inside the stem. The
    recorded name is still used; only the extension is recovered.

    A binary with no readable graph falls back to naming each file from its own
    contents, taking the name: field of front matter, an HTML <title>, a
    top-le, or a banner comment. Either way the exact section offset
    lands in assets.json, and the graph's own path is recorded per asset under
    name, so anything left as asset-NNNN can still be traced back.

    The Ja refers to these files by their hashed names, so all of those
    references are listed in assets.json under referenced_names.

    Zstandard support is optional. When no binding is installed the frames are
    still extracted, just left compressed as .zst:

    ---

    Original Sources

    --sources goes one le than the bundle. Bun keeps the text every
    module was built from, and the path of that text, inside the source map region
    each module record points at. This is the only place the original TypeScript
    survives, because the bundle itself has already been flattened into chunks.

    The record is not the JSON source map the bundler produced. Bun walks that
    JSON once while compiling and writes a compact table instead, laid out by
    serialize_json_source_map_for_standalone in StandaloneModuleGraph.rs:

    | Offset | Contents |
    |---|---|
    | 0 | u32 source count |
    | 4 | u32 length of the raw |
    | 8 | one {offset, length} pointer per source path |
    | then | one {offset, length} pointer per source text |
    | then | the blob |
    | then | every path, then every source text Zstandard compressed |

    Every offset is absolute within the record, which gives a usable anchor: the
    first path must begin exactly where the pointers and the VLQ blob end. A
    record that does not line up is rejected rather than half-read.

    Paths are recorded relative to whichever file imported them, so a shared
    dependency arrives as ../../node_modules/.... Dropping the leading ..
    segments turns that back into the path the file actually had, and the tree
    lands the way it was compiled:

    For droid.exe that is 4,914 files and 42.6 MB of original source: 2,487
    node_modules dependencies, 1,281 files of droid's own src, and 1,146
    workspace packages.

    The same file often backs more than one chunk. Those are written once and
    listed in sources/sources.json under duplicate, alongside the chunk each
    copy came from, so nothing is silently o.

    Not every build carries them. claude.exe embeds no source maps, so
    --sources reports nothing there, and that is a property of the binary
    rather than a failure.

    ---

    How Do You Extract JS From a Bun Executable?

    Run the executable through unbuned:

    Output

    The extracted Ja will be saved to output/<executable-name>/<executable-name>.js.

    ---

    Real-World Samples

    Extracted bundles from real Bun applications are committed under
    output/ so you can see exactly what unbuned pulls
    out of production binaries before running it yourself.

    1. Claude Code (claude.exe) in full

    The most complete run, produced by one command:

    | | |
    |---|---|
    | Executable | 247.7 MB PE64, .bun section of 153.9 MB |
    | Ja | 40.9 MB, 2,156 compiled modules |
    | Module graph | 2,384 embedded files, all named |
    | Entry point | B:/~BUN/root/cli |
    | Assets | 138 Zstandard frames, about 10 MB decompressed |

    That run writes 229 MB across 2,299 files, so it is not committed. The
    browsable extraction above is the same bundle unformatted; run the command to
    get the formatted output, the per-module files, and the manifests locally.

    The asset names are the clearest e the module graph parse works. Bun
    stores each asset as a compressed, content-hashed frame with a useless name, and
    all 138 come back with the name the compiler recorded:

    Module names come from the same table, so --modules writes real source paths
    like 0006-cli.js instead of 0000-fs.js guesses.

    2. Factory Droid CLI (droid.exe)

  • Extracted: 14.1 MB of Ja

  • Contains: agent logic, model configuration, application workflows

  • Location: output/droid/droid.js
  • The tracked sample abo from an earlier build. Factory CLI
    v0.232.0 (275 MB, installed to ~/bin/droid.exe) is worth a full dump,
    and it is the binary that exercises source recovery:

    That is 5,623 files and 364 MB: 623 bundled chunks named from the graph
    (entry point B:/~BUN/root/droid), 80 named assets including rg.exe,
    keytar, the rust_pty libraries for four platforms, two sound effects
    and the SKILL.md files, and 4,914 original sources. The full tree is
    untracked because it is large; regenerate it with the command above.

    3. Freebuff (freebuff)

  • Extracted: 11.4 MB of Ja

  • Contains: telemetry events, model routing, product and CLI flows

  • Location: output/freebuff/freebuff.js
  • 4. Slate (slate.exe), proprietary

  • Extracted: 22.4 MB of formatted Ja, 2 compiled modules

  • Contains: the full agent runtime, terminal emulator, and TUI

  • Location: output/slate/slate.js

  • Full dump: output/slate/slate-full.7z, 3.1 MB
  • Slate is the one target here that ships no public source. Its npm package is
    marked Proprietary and carries no repository field, so the only way to read
    its code is to extract it. Both compiled modules are ESM, and the module
    graph names every one of the 16 embedded files, which is why the assets come
    back with real names instead of content hashes:

    The strings show what it is: an OpenCode fork with its own API host, carrying
    OPENCODE_ identifiers and api.randomlabs.ai endpoints throughout.

    5. Claude Agent SDK (claude.exe), 0.3.220

  • Extracted: 33.8 MB of formatted Ja, 3 compiled modules, all CJS

  • Contains: the agent loop, tool definitions, and the full CLI surface

  • Location: output/claude-sdk/claude.js

  • Full dump: output/claude-sdk/claude-sdk-full.7z, 12.8 MB
  • This build is different from the standalone claude.exe above in a way worth
    recording: it is bytecode compiled, and all three modules carry the
    // @bun @bun-cjs header. The Ja you get back is the
    fallback source, not what actually runs. The graph names the entry point as
    B:/~BUN/root/src/entrypoints/cli.js and two nati, and all five
    assets are recovered by name:

    The bundle also carries the largest regular expressions this project has met,
    a 10 KB emoji alternation table that a nai reads as di. See
    Formatting The Output for why a whitespace-only
    guarantee is not the same as a parse.

    6. Amp CLI (amp.exe), 0.0.1790956876

  • Extracted: 13.2 MB of formatted Ja, 2 compiled modules

  • Contains: the full agent CLI, and the keyring bridge it loads natively

  • Location: output/amp/amp.js

  • Source binary: 103,919,952 bytes, .bun section of 17,745,205 bytes
  • This is the one sample in this repository whose Ja is UTF-16, and
    it is the reason unbuned detects the encoding rather than
    assuming plain bytes. The bundle marker sits eight bytes into the section and
    every ASCII character behind it is followed by a padding NUL, so a byte-level
    search for // @bun finds nothing and every text ratio reads as half binary.

    The recovered bundle is ordinary Ja once decoded: it is valid UTF-8
    with no NUL padding left in it, and node --check parses it cleanly. The
    module graph names the entry point B:/~BUN/root/amp-windows-x64.exe, and the
    one recovered asset is the nati the CLI publishes to the page:

    The graph also confirms the extraction independently. It records the entry
    point at section offset 8 with a length of exactly 17,317,422 bytes, which is
    precisely what unbuned emits, and the only other graph file is the keyring
    addon. That is a much stronger check than "the output looks like Ja".

    One thing to know if you use --modules on this build: the extractor reports
    two module headers where the graph records one Ja file. The second
    // @bun is the first line of a script held inside a template literal
    (xei = \#!/usr/bin/env bun\n...), so it starts a line without being a
    module boundary. The concatenated
    amp.js is unaffected, because the boundary
    between the two is inside a string and the file still parses. See
    Limitations for why that is left alone rather than special
    cased.

    These samples are the proof point. unbuned is built to rip useful code out of
    real shipped Bun executables, not just synthetic fixtures.

    Why These Samples Matter

    Re tools live or die on credibility. Including extracted bundles from Droid, Claude Code, Slate, the Claude Agent SDK, Freebuff, and the Amp CLI makes the value concrete:

  • you can inspect real output before running the tool

  • you can use the repo as a search surface for Bun internals

  • you can that the extraction stays readable at multi-megabyte scale

  • you can benchmark your own re workflow against real targets
  • What You Get Back

    When extraction succeeds, you get:

  • a clean .js file on disk

  • UTF-8 text without the surrounding binary junk

  • output that is ready for grep, static analysis, beautification, or manual review
  • That makes unbuned useful for both fast triage and deeper re sessions.

    If you want to judge the extractor before running it yourself, open a sample bundle such as output/claude/claude.js and search through it. The repo is meant to prove the claim, not just make it.

    ---

    How It Works

    unbuned uses a multi-stage extraction process:

    1. Format Detection: detect the executable container and choose the best extraction path.
    2. Section Discovery: locate
    .bun in PE and ELF files, or __BUN,__bun in Mach-O files.
    3. Uni Slice Selection: pick an architecture from a FAT Mach-O, or the first one.
    4. Magic-Byte Fallback: search for the Bun bundle marker when section metadata is not enough.
    5. Ja Marker Detection: find the
    // @bun marker that denotes the bundle start.
    6. Module Discovery: walk the NUL-delimited
    // @bun headers to index every module.
    7. Boundary Detection: measure non-printable byte ratios to find the transition from code to binary data.
    8. Boundary Refinement: use markers like
    //# debugId=, //# sourceMappingURL=, and })(); to stop cleanly.
    9. Tail Trimming: place the exact end of the last module and drop trailing bytecode.
    10. Extraction: write the recovered Ja as raw bytes to
    output/<name>/<name>.js.

    UTF-16 Bundles

    Most Bun builds store the bundle as plain bytes, but some ship the whole
    Ja region as UTF-16, where every ASCII character is followed by a
    padding NUL.
    amp.exe (the Amp CLI) is one of these. Such a bundle defeats a
    plain byte search for the
    // @bun marker, and every byte-level text ratio
    reads as roughly half binary.

    unbuned detects this without being told. It looks for the marker as plain
    bytes first and then as UTF-16 in either byte order, and threads the detected
    encoding through the boundary scan, module splitting and header parsing.
    Classification switches from
    bytes.translate over raw bytes to
    str.translate o code units, because the low byte of an accented
    Latin letter or a Cyrillic character is itself an unprintable value, and
    sampling low bytes alone would stop the scan early inside ordinary source.

    Offsets stay in the original byte space throughout, so module offsets, the
    module graph and the manifest still describe the file as it sits on disk. The
    text is decoded to UTF-8 only where it is actually consumed: filenames, asset
    paths, and the files written out.
    manifest.json records what was found under
    ja.encoding.

    This holds for every container format, not just PE. Mach-O and uni
    Mach-O terminate their bundle with a NUL instead of padding it out to the end
    of the section, and in UTF-16 that padding NUL follows every ASCII character,
    so the terminator has to be located on the code unit grid or the scan stops one
    byte in. The test suite covers PE, Mach-O, uni Mach-O and ELF in both
    byte orders.

    Boundary Detection Algorithm

    The extractor uses a practical heuristic tuned for real Bun payloads:

  • analyzes chunks for non-printable character ratios

  • looks for source map comments (//# sourceMappingURL=)

  • detects debug markers (//# debugId=)

  • identifies IIFE closures (})();)

  • binary-looking data after potential boundaries

  • narrows to the transition with a coarse pass, then a fine pass, then lands on

  • the exact code unit rather than stopping at a window edge
  • byte classification runs through bytes.translate, so a 150 MB section is

  • scanned inside CPython's C layer rather than a Python-level loop
  • UTF-16 bundles are classified with str.translate o code units

  • instead, because a low-byte sample misreads ordinary non-ASCII source

    Why the Tail Is Trimmed Forwards

    The structured data that follows the Ja is not always dense binary. Bun
    interlea bytes into its metadata, so the trailing region can sit
    around 60% non-text while ordinary source sits under 10%. A backwards window
    scan can therefore only ever land within one window of the true boundary, and
    some compiled modules legitimately contain NUL bytes, so a NUL terminator is
    not a reliable stop either.

    unbuned instead scans *forwards* from the last module header, which is known
    to be text, and stops where the density rises.

    Clamping that result to the statistical cap is not good enough on its own. The
    cap lands on a chunk boundary, and the chunk that trips the threshold usually
    *begins* with real source and ends with the blob, so the clamp throws that
    source away.
    unbuned therefore distinguishes two cases. If a binary window
    was located before the cap, the boundary is settled there and refined to the
    exact code unit. If none was, the cap is sitting inside source, and the
    boundary is the first non-text code unit after it. Either way the result is a
    code-unit boundary, never a window boundary, and a trailing sweep removes any
    blob that rode along with it.

    The practical benefit is that the output ends where the source ends. On
    amp.exe this recovered the final export default b3n(); that a cap-aligned
    boundary cut mid-token, and the bundle now parses under
    node --check. The
    practical cost is bounded: when a bundle genuinely ends on a long run of
    control characters, the boundary can land a few bytes early.

    ---

    Use Cases

  • Reverse Engineering: understand how a Bun application works without building a full decompiler first

  • Security Research: inspect shipped Bun executables for risky logic, secrets, or attack surface

  • Malware Analysis: peel back Bun-packed droppers, loaders, or suspicious tooling

  • Code Recovery: reco when the original project is gone but the executable survives

  • Learning: study how real Bun apps package code, dependencies, and runtime behavior

  • CTF / Challenge Work: speed up bundle extraction during time-boxed re tasks
  • ---

    Requirements

  • Python 3.6 or higher

  • No external dependencies
  • ---

    Limitations

  • Extracts bundled Ja and embedded assets, not nati

  • Module and asset names are the paths the bundler recorded, so a hashed

  • chunk-9fxe9jf7.js stays hashed and the original import graph is not
    recovered
  • Original sources only exist when the build embedded source maps, and

  • what comes back is the pre-bundle text, not the original repository
  • When a binary ships no readable module graph, filenames fall back to being

  • inferred from content
  • Module headers are found by anchoring to line starts, so a // @bun line that

  • sits inside a template literal can be mistaken for a boundary. This never
    corrupts the concatenated
    .js, which stays a single correct stream, but it
    can produce a bad slice in
    --modules. Telling the two apart properly needs
    Ja string tracking, so
    unbuned does not guess: on builds like
    the Amp CLI, cross-check
    --modules against the module graph, which is
    authoritative
  • --format reformats layout only; it ne, reorders, or rewrites code

  • Very long single tokens, and huge or deeply nested template interpolations,

  • stay on one line
  • JSC bytecode is dumped raw, not decompiled

  • The end of the Ja region is found statistically and then snapped to

  • a code-unit boundary, so it can still land a few bytes early when a bundle
    ends on a run of control characters
  • Some obfuscated code will still require manual analysis
  • ---

    Contributing

    Contributions are welcome, especially around:

  • more fixture co

  • JSC bytecode analysis

  • better boundary heuristics

  • more formatter layout rules

  • additional real-world Bun samples
  • Run the tests with:

    Bug reports, PRs, and extraction results are all useful.

    ---

    Ethical Use

    This tool is meant for legitimate reverse engineering, malware analysis, security research, learning, and recovery work. Respect licenses, contracts, and local law. Use it responsibly.

    ---

    Related Projects

  • asar - Extract Electron app.asar` archives

  • pkg - Package Node.js apps into executables

  • nexe - Create standalone Node.js executables
  • ---

    <di="center">

    Built for people who would rather read the Bun app than speculate about it

    Star this repo if you find it useful.

    </div>

    ---

    License

    MIT License - see LICENSE for details.

    ---

    Author

    If this repo helps you crack open a Bun executable faster, that is exactly what it was built for.

    Currently open to work. If you're looking for someone with security research, reverse engineering, malware analysis, or full-stack de experience, hit me up.

  • X/Twitter: @Im

  • Website: .com

  • Security Blog: opendoors.wtf

  • GitHub:
  • ---

    _Remember: with great power comes great responsibility. Use this tool ethically and legally._

    项目信息

    开发语言:JavaScript

    Star 数:56

    技术要点

    该项目属于CTF思路方向的开源资源,代码结构清晰,适合安全研究人员学习参考。项目围绕Bun decompiler-style JavaScript extractor for Bun-compiled executables. Zero-dependency Python tool for reverse engineering, malware analysis, and code recovery.展开,提供了可运行的实现,对理解相关安全机制、工具原理与攻防技术具有较高的研究价值。

    从技术实现角度看,此类项目的核心在于对底层原理的深入理解与工程化落地。开发者在研究过程中可以结合官方文档与社区讨论,梳理项目的初始化流程、核心模块职责与数据流转,逐步掌握其中的技术细节与设计思路。

    使用与学习建议

    1. 下载源码压缩包,解压到本地环境。

    2. 根据项目类型准备对应的运行环境与依赖。

    3. 按照 README 中的步骤完成配置与部署。

    4. 在隔离的测试环境中运行验证,避免影响生产系统。

    免责声明

    本资源来源于开源社区,仅供安全研究、技术学习与合法授权范围内的攻防演练使用,请勿用于任何非法或侵害他人权益的用途。使用本项目产生的一切后果由使用者自行承担。

    资源标题unbuned - Bun decompiler-style JavaScript extractor for Bun-
    所属分类CTF思路
    发布作者admin
    发布时间2026-10-06 00:05:27
    下载权限 免费下载
    附件数量2 个下载链接
    浏览次数123
    讨论数量0
    主题状态正常讨论中

    本资源免费,登录后即可在本页「下载链接」区域获取全部附件。

    请在评论区留言说明具体链接,或联系作者补档。楼主更新后本页会同步最新的下载地址。

    请先阅读正文中的授权说明;未注明的默认仅限站内学习交流,禁止直接搬运到其他站点二次售卖。
    还没有讨论,快来抢沙发~
    登录 后才能参与讨论
    下载链接
    会员才能下载
    登录后开通会员
    同分类推荐
    暂无同类资源
    免费登录即可下载
    查看会员
    放大预览

    © 2026 剑庭安 - 网站开发定制 SEO 优化蜘蛛池代做 在线看手相算命. 保留所有权利。

    Powered by 酷像SEOv3.0

    网站地图 Sitemap.xml
    首页 全部分类 黑客安全 逆向|反编译 精品源码 站长工具自助开通会员其他资源SEO工具站群源码泛程序蜘蛛模板蜘蛛池外链工具创意乐园 搜索 会员服务 登录 注册