# Fake SSH Play — arXiv submission package

Author: yxpil (Yang Daozhendong, Yunnan Technology and Business University, hi@yxpil.com)
Artifact: <https://github.com/yxpil/Fake_SSH_Play>

## Title

**Fake SSH Play: Playing *Bad Apple* to SSH Clients, and Session Hold as a Measurable Honeypot Property**

## Suggested arXiv classification

- Primary: `cs.CR` (Cryptography and Security)
- Cross-lists: `cs.NI` (Networking and Internet Architecture), `cs.CY` (Computers and Society)

`cs.CR` is the right primary: the artifact is an SSH honeypot and the paper's
subject is honeypot design. `cs.NI` covers the SSH channel/pacing analysis
(Section 5), and `cs.CY` covers the legal, retention and disclosure analysis
(Section 7), which is a substantial part of the paper rather than an
afterthought. If a reviewer prefers the measurement framing as primary,
`cs.NI` works with `cs.CR` as a cross-list.

## Abstract (verbatim, as in `main.tex`)

> Fake SSH Play is a small SSH responder that accepts every authentication
> attempt and, instead of a shell, plays a 2,193-frame ASCII rendering of the
> *Bad Apple* shadow-art video over the SSH channel at 33 ms per frame, then
> closes the session. It is written in Rust on `russh` and ships a web
> dashboard. Accepting any credential is textbook honeypot behaviour and we do
> not present it as a contribution. The narrower question is what a responder
> with *server-determined* content and duration is good for. In a fake shell the
> session length is chosen by the client, so duration measures the client; in a
> fixed animation the server removes its own variance from the same quantity,
> which makes "how long did the client stay" comparable across responders. That
> is a modest methodological claim, and this paper treats it as one. We document
> the system from its source, report its measured envelope — 5,219,755 bytes of
> channel payload, 2,380 bytes per frame, a largest frame that is 7.2% of the
> RFC 4253 maximum packet, 72.4 s of nominal playback, 5.2 MB of resident memory
> per concurrent session, and 1.07 µs of CPU per frame against a 33 ms budget —
> and specify, without running, a controlled comparison against an interactive
> fake shell. The repository contains no captured sessions, so this paper
> reports no engagement results, no detection or deterrence claim, and no
> attribution of any kind.

## Status

The LaTeX source is complete and lints clean under `papers/lint.py`
(citations, labels, environments, include paths, ASCII-only source). **It has
not been compiled in the authoring environment**, which has no TeX
distribution; run the build commands below once before submitting, and check
that both figures that span the page (`figure*`) land where intended.

## Files

```
main.tex                     the paper (two-column, article, 10pt)
refs.bib                     bibliography (abbrvnat)
common.tex                   shared preamble, copied from papers/_shared/
figures/frame_sizes.pdf      payload distribution + headroom vs SSH limits
figures/payload_timeline.pdf per-frame payload and cumulative bytes
figures/pacing.pdf           encode cost vs 33 ms budget; 10 FPS vs 33 ms
scripts/measure_frames.py    counts the committed asset with the Rust loader's
                             exact semantics -> scripts/measured.json
scripts/measured.json        all asset/payload/timing/memory numbers
scripts/frame_probe.rs       std-only Rust probe: working set + encode cost
scripts/probe_frames.sh      builds and runs the probe on the LF blob and on
                             the CRLF working tree -> scripts/probe.json
scripts/probe.json           measured working set, per-frame CPU, clone cost
scripts/make_figures.py      regenerates all three PDFs from the two JSONs
scripts/analyze_sessions.py  session-hold analysis for the protocol in §6
                             (Kaplan-Meier input table + log-rank); refuses to
                             fabricate, has a clearly labelled self-test mode
README.md                    this file
```

## Build

```bash
cd papers/fake-ssh-play
cp ../_shared/common.tex common.tex      # already vendored; keeps them in sync

pdflatex main
bibtex main
pdflatex main
pdflatex main
```

or, with `tectonic` (fetches packages on demand and runs bibtex itself):

```bash
tectonic -X compile main.tex --keep-logs
```

## Package for arXiv

The collection's packaging command is:

```bash
bash papers/make_package.sh fake-ssh-play
```

It expects the paper path to be resolvable from the working directory, so in
this workspace the tested invocation is:

```bash
cd ..                                  # directory that contains papers/
bash papers/make_package.sh papers/fake-ssh-play
```

The tarball is written to `papers/dist/fake-ssh-play.tar.gz` and contains
`main.tex`, `refs.bib`, `common.tex`, `figures/` and `scripts/`. Build
artifacts (`.aux`, `.log`, `.bbl`, `.out`) are stripped.

## Submit

<https://arxiv.org/submit> — choose `cs.CR` as the primary category and add
`cs.NI` and `cs.CY` as cross-lists. Upload the source tarball (not the PDF) so
that arXiv compiles it; verify the resulting PDF page count and that both
full-width figures render.

## Reproduce the numbers

Requires Python 3 with matplotlib, `git`, and `rustc` (any recent stable).
The repository is expected at `../../_repos/Fake_SSH_Play` relative to
`papers/`; pass an explicit path if yours differs.

```bash
cd papers/fake-ssh-play

# 1. frame data, payload, pacing, allocation accounting
python scripts/measure_frames.py --repo ../../_repos/Fake_SSH_Play

# 2. working set, per-frame encode cost, per-connection copy cost (Rust)
bash scripts/probe_frames.sh ../../_repos/Fake_SSH_Play

# 3. figures
python scripts/make_figures.py

# 4. protocol analysis script (synthetic self-test; prints no results)
python scripts/analyze_sessions.py --self-test
```

`measure_frames.py` reads the asset from the git blob at
`HEAD:old-js/badapple_ascii.txt` as well as from the working tree, which is why
`measured.json` has a `committed` and a `checked_out_on_windows` section: the
blob has LF endings, a Windows checkout with `core.autocrlf=true` has CRLF, and
the loader behaves differently on each. That difference is a finding in §5.6,
not an artefact of the harness.

## Where each headline number comes from

| Number in the paper | Source |
|---|---|
| 5,399,021 B blob; 70,150 lines; 2,193 separators | `measured.json` → `git_blob_bytes`, `line_count`, `separator_occurrences` |
| 2,193 frames loaded (2,192 declared) | `measured.json` → `committed.frame_count`, `declared_header` |
| 5,219,755 B payload; 2,380.19 B mean; 2,514 B max | `measured.json` → `committed.payload.*` |
| 7.18% of the RFC 4253 packet | `measured.json` → `payload.largest_as_pct_of_rfc_max_packet` |
| 72.369 s nominal; 3.029× authored rate | `measured.json` → `committed.pacing.*` |
| 5,167,028 B steady; 10,566,049 B peak | `measured.json` → `memory_allocation.*` |
| 4,456,448 B measured working-set delta | `probe.json` → `committed_lf.working_set_delta` |
| 1.07 µs mean encode; 0.0032% of budget | `probe.json` → `committed_lf.encode.*` |
| 5,208,320 B and 1.516 ms per session copy | `probe.json` → `committed_lf.per_connection_clone.*` |
| 5,539,356 B single frame on CRLF | `measured.json` → `checked_out_on_windows.payload.max` |

## What this paper does not claim

- **No engagement results.** The repository contains no captured sessions and
  none were collected for this paper. Section 6 is a protocol; every result
  field in it is a `\todo{}`.
- **No detection, deterrence, or "trap" claim.** The paper does not argue that
  the animation detects, deceives, slows, or deters an adversary. It explicitly
  refuses that framing in §7.4.
- **No attribution.** The system sees an IP address, a username, an
  authentication method, and timestamps. It cannot fingerprint or attribute an
  actor, and the paper says so in §3.5.
- **No claim of novelty for accepting any credential.** That is textbook
  honeypot behaviour (Kippo, Cowrie, HoneyD). The only candidate contribution is
  the narrower methodological one: a server-determined session duration as a
  comparison instrument. It is presented as a hypothesis, not a result.
- **No end-to-end measurement.** No SSH client was connected while measuring.
  The payload figures exclude SSH framing, padding and MAC; the timing figures
  are nominal; the CPU figure covers the encoding expression only, not
  encryption.
- **No security improvement claim.** Section 7 states that each held session
  costs the operator 5.21 MB of memory and a live socket, and that the
  committed build has no connection, channel, or rate limit.
- **No credential data.** The paper reports on no real captured credential.
  The password callback discards the password, and the artifact contains no
  logs.
- **No legal advice.** §7.1 states the jurisdiction and authorisation
  assumptions (GDPR, PIPL) and notes that the repository documents no
  authorisation basis, retention policy, or dashboard access control.

## Honest notes about the artifact

- The committed frame asset is the JavaScript-era 80×30, 10 FPS file. The
  Rust-era converter (`mp4_to_txt.py`) targets 100 columns at 30 FPS with a
  hard-coded local input path, and its output is not committed. All frame
  numbers describe the former.
- The README advertises "auto-restarts on crash"; the supervisor loop restarts
  a panicked task, not a task that returns an error (for example, a bind
  failure). The paper states the actual behaviour in §3.1.
- The README and `Cargo.toml` say MIT; `LICENSE` is Apache-2.0. The status of
  the derived ASCII artwork is unresolved. §7.5 flags this without resolving it.
- `TESTING.md` claims the dashboard renders log values as text; the code uses
  `innerHTML` with a string returned by a third-party geolocation API. §7.3
  reports the code path, not the claim.
