How the endorphin Learning Edition key check works
NaturalMotion's endorphin Learning Edition asks for an email address and a product key on first run. This is a writeup of what that check actually does, reverse-engineered from the shipped binary. It turns out to be a textbook example of a weak licensing scheme: the "key" is nothing more than a hash of your email address with a fixed secret glued on, and because that secret ships inside the executable, anyone can compute a valid key for any email. No server, no dongle, no asymmetric crypto. This page walks through exactly how it works and why it falls over.
The short version
The product key endorphin expects for a given email is:
key = md5(email + "0595a1f6d702238164e5b3bd121759bf")
expressed as a 32-character lowercase hex string. That constant is a fixed salt baked into the program. Feed it any email and the matching MD5 digest, and the app is satisfied.
That's the entire scheme. The rest of this page explains how we know, and why a design like this can never actually keep anyone out.
Where the check lives
The relevant code is one function in the application binary. In the decompiler
it has no name (the release build is stripped of symbols), so it's just its
address, FUN_00426a00. Reading through it, the flow is:
- Read a previously-stored blob out of the Windows registry, under the app's
Settings\Flagsvalue. - Decrypt that blob and split it on a
:character into two parts: an email and a key. - Derive what the key should be from the email.
- Compare the derived key against the stored key. If they match, you're registered. If not, pop the "enter your email and product key" dialog and compare again against whatever you type.
Step 3 is the interesting one, and it's where the whole design gives itself away. The program never checks your key against a server or a signature. It recomputes the expected key locally, from data you control, using an algorithm that's sitting right there in the binary. Anything the program can compute, you can compute.
The key derivation, step by step
Two helper functions do the work. The first, FUN_00426980, builds the string
that gets hashed:
void FUN_00426980(string *email, string *out) {
string salt = "0595a1f6d702238164e5b3bd121759bf";
*out = *email; // copy the email
out->append(salt); // stick the salt on the end
}
So it takes your email and appends the fixed 32-character salt. If your email is
you@example.com, the string it produces is:
you@example.com0595a1f6d702238164e5b3bd121759bf
The second function, FUN_006781f0, hashes that string:
void FUN_006781f0(string *input, string *out) {
md5_ctx ctx;
md5_init(&ctx); // FUN_006a4b20
md5_update(&ctx, input->c_str(), input->len); // FUN_006a4b50
byte digest[16];
md5_final(&ctx, digest); // FUN_006a4c20
char hex[36];
char *p = hex;
for (int i = 0; i < 16; i++) {
sprintf(p, "%02x", digest[i]); // each byte -> two hex chars
p += 2;
}
*out = hex;
}
I've renamed the inner functions to what they actually are. How do we know they're MD5 and not some other 128-bit hash? Because the init function hands us the fingerprint directly:
void md5_init(uint *ctx) {
ctx[0] = 0;
ctx[1] = 0;
ctx[2] = 0x67452301; // MD5 initial state word A
ctx[3] = 0xefcdab89; // B
ctx[4] = 0x98badcfe; // C
ctx[5] = 0x10325476; // D
}
Those four magic numbers, 0x67452301 / 0xefcdab89 / 0x98badcfe / 0x10325476,
are the standard MD5 initialisation vector defined in RFC 1321. Any MD5
implementation on earth starts from exactly those values. The update function
does the classic 64-byte block buffering with a bit-length counter, and the
finalize function appends the 0x80 padding byte, tacks on the message length,
and writes the four state words out little-endian into 16 bytes. It is stock,
unmodified MD5.
The loop at the end converts those 16 raw bytes into text with %02x, i.e.
two lowercase hex digits per byte, giving the 32-character key string.
So the full derivation is:
key = lowercase_hex( md5( email + salt ) )
The comparison
Once the program has derived the expected key, the check is just a string compare:
expected = derive_key(email); // the two functions above
valid = (expected == your_key);
If valid is true, the app writes your email:key pair back to the registry
(so it won't ask again) and lets you in. That's it. There's no second factor,
no timing check, no phone-home. The equality test is the only gate.
Why this is trivially defeatable
The fatal flaw is that the key is derived from public inputs using a public algorithm and a secret that isn't secret. Walk through what an attacker needs:
- The email is something you type. You own it.
- The algorithm is MD5, a standard everyone has.
- The salt is the only "secret", and it's stored as a plain string constant
inside a binary that gets shipped to every user. Ten minutes with a
disassembler (or even
strings) and it's yours.
Once you have the salt, you can compute the correct key for any email without ever touching the program again. That's exactly what the keygen does: it takes an email, appends the salt, runs MD5, and prints the hex digest. The value it produces is byte-for-byte identical to what endorphin computes internally, which is why the app's own unmodified check accepts it.
This is the difference between a check that verifies and a check that recomputes. A robust scheme uses asymmetric cryptography: the vendor signs a licence with a private key they never ship, and the program verifies it with the matching public key. The program can check a signature without being able to forge one, because it never holds the private key. endorphin's scheme has no such asymmetry. The program holds everything it needs to generate a valid key, so anyone reading the program does too. Symmetric secrets embedded in client-side software are not secrets; they're just obfuscated public values.
Worked example
Take the email test@example.com. Concatenate the salt:
test@example.com0595a1f6d702238164e5b3bd121759bf
MD5 that string:
2684054a2dfbf84c48839c35eeaa497a
That hex string is the product key for test@example.com. You can verify it
yourself on any machine:
printf '%s' 'test@example.com0595a1f6d702238164e5b3bd121759bf' | md5sum
The keygen does precisely this calculation in your browser, for whatever email you enter.
A note on why this is fine to publish
endorphin was discontinued by NaturalMotion in 2012. The Learning Edition was a free, functionality-limited version whose activation relied on infrastructure that no longer exists, so there is no longer any legitimate way to obtain a key even though the software was meant to be free to use. This writeup exists to document a dead product's licensing internals and as a teaching example of how not to design a key check. It doesn't unlock any paid tier or defeat any active protection.