Skip to content

Repository files navigation

shieldbreak

CI codecov

A tool for extracting OpenSSH ssh-agent shielded private keys from a process core dump, offline, on x86_64 Linux.

It parses the core dump directly, recovers the shielded key blob and its 16 KB prekey, and reconstructs the plaintext private key.

Credit

The underlying technique is not mine. It was described by Piergiovanni Cipolloni of HN Security (Humanativa) in 2021:

OpenSSH ssh-agent Shielded Private Key Extraction (x86_64 Linux) by Piergiovanni Cipolloni, HN Security, 23 July 2021.

That article explains the vulnerability class, the sshkey struct layout, and the use of sshkey_unshield_private() to recover the key. Read it first. It is the source, and this repository is a reimplementation of the method it describes.

What this adds

The original proof of concept attaches gdb to the live ssh-agent process, scans /proc/<pid>/maps, and locates the key by searching the heap for the key comment string. shieldbreak reworks the extraction step into a standalone tool:

  • Offline. It reads a saved ELF core dump, not a live process. Nothing has to be attached to a running agent.
  • Structured parsing. A Go program walks the core dump's ELF segments and reads the sshkey struct by field offset, rather than driving an interactive debugger.
  • Reproducible. The whole flow (build a debug ssh-keygen, generate a test dump, extract, unshield) runs in Docker.

The final unshield step still uses gdb against a debug build of ssh-keygen to call sshkey_unshield_private(), which is the OpenSSH function the original article identified. The contribution here is engineering: turning a manual, live-process procedure into a repeatable offline tool.

This is not a vulnerability report

OpenSSH key shielding is designed to resist partial memory disclosure, for example a speculative-execution side channel that leaks a fraction of RAM. It is not designed to resist an attacker who can read the entire process memory. A full core dump of ssh-agent is exactly that: anyone who can produce one already has the privileges needed to compromise the process.

So this recovers a key from a full memory image within the threat model, not by defeating it. It is a demonstration and a tool, not a CVE.

How it works

  1. shielded-key-parser reads the core dump, finds the sshkey struct, and writes out two files: the shielded private key blob and the 16 KB prekey.
  2. A gdb script (shielded-key-decoder/decrypt.gdb) loads a debug build of ssh-keygen, reconstructs a sshkey from those two files, calls sshkey_unshield_private(), and saves the plaintext key.

The struct field offsets in shielded-key-parser/parser/ssh_key_reader.go are specific to a particular OpenSSH build and architecture (x86_64). Different builds may lay the struct out differently, so adjust the offsets accordingly.

Usage

Everything is containerised so you do not need a matching OpenSSH build on the host.

# Build the image (compiles a debug ssh-keygen and the parser)
make build

# Generate a test ssh-agent core dump to work against
make run-test-dump-generator

# Extract and unshield a key from a dump
docker run --rm \
  --cap-add=SYS_PTRACE \
  --security-opt seccomp=unconfined \
  -e KEY_COMMENT=test@key \
  -e DUMP_PATH=/data/<dump-file>.mem \
  -v "$(pwd)/dumps:/data:ro" \
  -v "$(pwd)/dumps:/out" \
  shieldbreak

KEY_COMMENT is the comment string of the target key, used to locate it in the dump. The recovered plaintext key is written to the mounted /out directory.

The --cap-add=SYS_PTRACE and --security-opt seccomp=unconfined flags are required because the unshield step drives gdb, which makes calls into a live ssh-keygen process. gdb needs ptrace to do that; without these flags it fails on restricted sandboxes with Couldn't write extended state status: Bad address.

Testing

The parser has structural unit tests (Go's testing package) covering the ELF segment math and the sshkey struct offset parsing, plus an end-to-end test that generates a real ssh-agent core dump, extracts the key, and verifies the recovered key matches the original by fingerprint.

# Fast structural unit tests
make test-unit

# Unit tests with a coverage profile and summary
make test-cover

# End-to-end test: generate a dump, extract, unshield, and verify recovery
make test

# Both unit and end-to-end
make test-all

Continuous integration runs on GitHub Actions (.github/workflows/ci.yml): the unit tests run with the race detector and coverage on every push and pull request, coverage is uploaded to Codecov (the badge above), and the end-to-end test runs as a separate job.

Intended use

For research, education, and authorised testing only, for example understanding the limits of ssh-agent key shielding, or recovering your own keys from a crash dump. Do not use it against systems or data you are not authorised to access.

I wrote this after forgetting the passphrase of one of my own SSH keys. The key was still loaded in a running ssh-agent, which holds it in memory in shielded form, so I dumped the agent's memory and used this tool to recover the plaintext key. If your key is loaded in the agent, you never need the passphrase again to get the key back.

License

MIT for the code in this repository.

The extraction technique and the sshkey_unshield_private() approach are the work of Piergiovanni Cipolloni / HN Security (see Credit); this repository claims no originality over the method, only over its implementation.

About

Offline extraction of OpenSSH ssh-agent shielded private keys from an x86_64 Linux process core dump. Parses the ELF dump, recovers the shielded key blob and 16 KB prekey, and reconstructs the plaintext private key. Reimplementation of HN Security's technique. Research/education only.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages