Cognithesis — an instrument for curiosity

cognithesis.← Return to the engine
← Systems

SILICON / 01

Inkpacket: Scamming the Scammers

Every cracker has an LLM now, and the LLM has structural weaknesses that no amount of scaling fixes: no memory across sessions, success verified only by 'does it run,' and an incorrigible taste for the shortest path. You can't patch those out of the model. You can decide where the path leads.

In this entry

Everything below is real, measured, and public — github.com/joshmorgan1000/inkpacket, Apache-2.0. This is the offense half of a pair; the defense half is The Analyst Is the Attack Surface. Same observation, opposite direction: the model takes the shortest path to success. The question is who chose where the path leads.

1. The attacker changed, and nobody updated the lock

The traditional binary-protection playbook — obfuscation, anti-debug, packers, dongle checks — was calibrated against one specific adversary: an expert human reverser with a week of patience and a hex editor. Every trick in that playbook is a time tax on a mind that gets bored.

That attacker is going extinct. Today’s attacker is anyone with an LLM, and the LLM does not get bored. Skill levels flattened; time-to-crack collapsed; the guy who couldn’t read assembly last year can now ask a model to do it. The industry’s response has been to pile on more of the same obfuscation, which is like responding to metal detectors by hiding the treasure more creatively — eventually the detector reads every hiding spot fluently, because reading is the one thing the new attacker is superhuman at.

So let’s ask the question that matters, the one everyone’s too scared to ask out loud:

LLMs are big and scary. But are they, actually?

Big, yes. But their cracking game has three structural weaknesses, and none of them are bugs that next year’s model fixes — they’re the same properties that make the models useful at all:

  1. Statelessness across sessions. The model that cracks your binary today has no memory of it tomorrow. Each session starts from zero context.
  2. Verification ceiling: “does it run.” An LLM-guided crack verifies success the only way it can — the program launches, the license dialog goes away. It cannot verify correctness of what happens next week.
  3. The shortest-path instinct. A model will take the most direct route to a legible success signal, every time. It’s not lazy; it’s optimized — and optimization toward a target someone else defined is a lever.

You cannot patch these out. But you don’t have to. Feed the model what it wants. Let it think it has succeeded. Do so on your terms.

That’s inkpacket — an application tamper-resistant builder, and a revenge fantasy executed as cold engineering.

2. Layer one: the envelope

The foundation is cryptographic, and it’s built backwards from a simple observation: the application’s own bytes should be the key. The patcher appends one or more AEAD-encrypted payloads (shared libraries) to the binary and derives every key from the application’s own SHA-256:

IKM     = application_hash || optional_key_file_bytes
info_i  = "ink_packet_v5_" + decimal(algorithm_id) + "_" + payload_id
key_i   = HKDF-SHA256(IKM, empty salt, info_i, key_length)
aad_i   = metadata_header(checksum=0) || entire_descriptor_table
frame_i = 12-byte nonce field || ciphertext || 16-byte authentication tag

Change any hashed byte of the application — flip one NOP in place, patch one jump — and key derivation breaks. Authentication fails, no plaintext comes out, no error message leaks why. The tamper is its own denial.

The default cipher is AES-256-SIV, chosen deliberately: it’s the construction that makes deterministic encryption safe, so identical inputs produce identical protected releases and no keystream is ever reused. An optional key file mixes into every HKDF context — two-factor offline decryption, binary plus file, no license server, nothing phones home.

3. Layer two: the trap system

The envelope protects the payload. The traps protect the program around it — and they are aimed squarely at the model in the cracker’s pocket.

The application sprinkles trap primitives through its own code. Each has a distinct call shape so that no single pattern survives decompilation into something a regex — or a model scanning for regex-shaped things — can catch in one pass:

Primitive What it looks like What it does
INK_CANARY(feat) a routine integrity spot-check compares the authenticated startup hash against the stored baseline
INK_TRIP(feat) an explicit alarm trips on honeypot handlers and known-bad branches
INK_COUNTER_FUSE(feat, n) a usage limit trips when total launches reach n
INK_HONEYPOT_GUARD(expr, feat) a defensive assertion trips when expr is false — looks like error checking, is a landmine
INK_FN_BIND(fn, bytes, feat) version binding hashes a function’s body; a patched function trips it
INK_DATA_SEAL(name, fn, ..., bytes) encrypted constants real data, AES-SIV sealed, key bound to a function’s bytes

Detection does nothing visible. That’s the point. A trip writes an encrypted flag into a state slot — and here’s the move that defeats the grep: the storage is your application’s own settings store. You provide a TrapStateProvider that forwards reads and writes to the SQLite database, plist, or registry you already use. inkpacket writes AEAD-encrypted blobs under HKDF-derived slot names. On disk it’s a few more opaque kilobytes in a settings file you already had. There is no “inkpacket” string to search for, no suspicious file to notice, nothing for the cracker’s model to get suspicious about.

4. The punishment: delayed, variable, and memoryless-proof

A trip today does nothing today. Some configurable number of launches later, an unrelated code path starts asking INK_DEGRADED(feature, 50) and quietly changes its behavior:

INK_CANARY(FEATURE_LICENSE);          // detection, somewhere random

double compute_export_dpi(int requested) {   // consequence, different file
    if (INK_DEGRADED(FEATURE_LICENSE, 50)) {
        return requested * 0.95;       // quietly wrong, 50 launches later
    }
    return requested;
}

The export renders at 95% scale. Forever. No error message — the docs are blunt about this: never gate user-visible errors on the degraded flag, because the whole point is that the user cannot tell they’re in a degraded branch.

Now walk through the cracker’s week with his model:

Day 1. The model reads the decompiled binary, finds a gate, patches it, forces the license check to pass. The program runs. The model reports success — truthfully! Everything does run. The cracker ships the cracked binary.

Day 3. Features start misbehaving. Subtly. The export DPI is off. A calculation drifts. Nothing crashes; nothing announces itself.

Day 4. The cracker asks his model: “what went wrong?” And the model — stateless, no memory of the Day-1 session, no log to consult, staring at a binary containing additional trips it hasn’t found yet — has nothing. It guesses. It patches something. Maybe that trip fires too. Maybe the fix breaks a third thing.

Repeat. Crack, wait, break, ask, guess, patch. Variable-ratio delayed punishment is a slot machine played against the house: the reinforcement schedule collapses the attacker’s confidence in his own tools. He cannot distinguish “the crack failed” from “the crack worked and this is the punishment” — and a defense that makes the attacker’s own intelligence untrustworthy to him has done more than any obfuscator ever did.

The model’s two weaknesses — statelessness and “does it run” verification — aren’t incidental to this trap. They are the trap’s parameters.

5. Layer three: kill the shortcut (the parameter is the receipt)

A smart cracker’s model eventually reasons: stop fighting the traps; make the program accept credentials directly. Force the gate, silence every trip, done — all success checks pass.

Version 5 closes that door, and it’s my favorite part of the whole design. The authorization function no longer just returns “yes.” It establishes the actual work parameters — the multiplier and increment, say — encoded in receipts. The computation itself decodes those parameters from the receipts to produce its result. There is no direct path to correct output:

  • Force the gate open? The receipts are garbage; the computation decodes garbage.
  • Suppress every trap and the shared trip() function? The computation never reads them; nothing changes.
  • The training cracker’s favorite move — NOP out the checks and hardcode success — now produces a program that runs perfectly and computes wrongly, which is precisely the failure mode the model’s “does it run” verification cannot see.

The parameter is the receipt. The shortcut leads to a decoy.

6. The measurement (because this blog measures things)

“This sounds like it should work” is not a result. The trap-diversification experiment generates 50 seeded builds of a trap-instrumented application: 10 training builds, 40 held-out. Frozen byte-signature and mnemonic-signature attacks are learned only from the training builds — the way a cracker’s model studies binaries it has — then executed as real patches against the held-out binaries. Every attacked program is run through five fresh processes; a lasting bypass must accept invalid credentials, produce correct output, and survive five healthy legitimate launches.

On the version 1 design — traps, but work that didn’t depend on the receipts — the gate-and-guard attack achieved 40 lasting bypasses out of 40 executions. Found the functions, patched them, ran clean. The traps alone were not enough.

On the version 5 design — same locator success, same patches executed, all forty patched programs accepted invalid credentials and suppressed every trip — zero lasting bypasses, with incorrect output in every single executed attack. And the experiment log is careful about why: the mnemonic gate-and-guard attack still located both functions on all forty held-out builds. The improvement did not come from making anything harder to find. It came from making the find irrelevant. The gate is findable. The gate is patchable. The gate was never the thing.

7. Disclose your own wounds first

In September 2026 a security review of inkpacket found a real cryptographic flaw: mutable trap-state writes were deriving nonces deterministically, which meant successive updates to the same slot reused a key/nonce pair — the classic GCM two-time-pad failure. The review reproduced it with the library itself: XOR two ciphertexts, recover the XOR of the plaintexts.

Version 5 fixed it — fresh random nonces for mutable state, deterministic-safe SIV as the default for payloads and seals — and the whole finding, reproduction, and fix is published in the repo docs. I’m telling you this for one reason: in a post whose thesis is “the attacker’s model cannot verify success,” the honest posture is to prove you verify your own. We found it before you did. The receipt, as always, is in the work.

8. The honest ceiling

None of this is magic DRM. A determined human with enough wall-clock time defeats any software-only protection on hardware they control — that’s physics, not a roadmap item. What inkpacket buys is attacker-hours and attribution failure, in the regime where most attackers give up rather than push through. The 2026 “bad guy with an AI” lives exactly in that regime: he has a model, not patience. The whole design is a bet on which of those two he runs out of first.

(And to pre-empt the other question — yes, the same trick works on the defense side of the fence. That’s the companion piece.)

9. Close: the path was always yours to pave

Wax poetic for a second, because this generalizes past binaries.

The models are on both sides now — the analyst’s and the cracker’s, the scammer’s and the mark’s. Everyone’s first instinct is to fight the enemy’s model: out-obfuscate it, out-prompt it, out-refuse it. That’s a treadmill, because the model you’re fighting improves every six months.

The move that doesn’t treadmill is the one the scammers themselves pioneered, ironically, on us: stop fighting the mark’s judgment and script the decision instead. An LLM will always take the shortest path to success — it’s not a flaw, it’s what the optimization is. You will never remove that path. You can only decide where it leads.

The scammer feeds the victim’s model a shortest path to “helpful.” inkpacket feeds the cracker’s model a shortest path to “cracked.” Same physics. Different receipts.

Scamming the scammers isn’t hacking them back. It’s letting their model win — repeatedly, verifiably, uselessly — until the human holding it learns what the machine never will: it doesn’t remember losing, and you can’t afford to.

← Back to systems