//ClippyCrippyV2
>Challenge Summary
- Artifact:
ClippyCrippyV2.jar - Objective: Determine whether the upgraded Lytton Labs password generator is vulnerable and recover the password whose SHA-512 hash is
cc2b7d09f0a7732319328eb5dd4a1167ac34957489f384d68586a7d23909ed7654d2c3b7f50f33289aeaecf8685f0a2eb60ba269aeb448e9173fa16b6073ca14. - Success Criteria: Produce the preimage password and document the full kill-chain that weakens the RNG.
>Reverse Engineering Findings
1. Main Application (ClippyCrippyV2.class)
- The static initializer base64-decodes four string fragments that form the URL
http://random.lyttonlabs.org/clippycrippyv2/security-update.jarand immediately downloads it. - The downloaded jar is written to disk, dynamically loaded, and the class
SecurityUpdateLoaderis invoked via reflection. - The call to
generateSecurePassword()first checks the system propertyclippy.security.module.loaded. If true, it usesClippySecurityModule.getOptimizedSecureRandom();otherwise it falls back to a regularSecureRandom. - The password alphabet is embedded as
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?and the code generates 20 characters.
2. Security Update Loader (security-update.jar)
SecurityUpdateLoaderdownloads an additional config blob:http://random.lyttonlabs.org/clippycrippyv2/clippycrippyv2-securityconfig.dat.- The config is XORed with a bundled resource
cosmic-random-01.dat, yielding another jar:clippy-security-module.jar. - That jar is loaded and
ClippySecurityModule.initializeSecurity()is executed, while system propertiesclippy.security.module.classandclippy.security.module.loadedare set. - The loader reports success by logging
Clippy Security Module loaded, giving a runtime indicator that the weakened RNG is active.
3. Clippy Security Module (clippy-security-module.jar)
- The module exposes
ClippySecurityModule$ClippySecureRandom, a subclass ofjava.security.SecureRandomthat simply wrapsjava.util.Random. ClippySecureRandom.refreshEntropy()seeds the PRNG with the value returned bycalculateTemporalEntropyCoefficient(), which evaluates to:javaseed = Math.abs( (((System.currentTimeMillis() ^ (System.nanoTime() >>> 10)) / 22118400) - 18356) ) & 0xFFFFFF;- Because of the trailing
& 0xFFFFFF, the seed space is limited to 2^24 (16,777,216) possibilities. java.util.Randomis not cryptographically secure; with only 24 bits of entropy the generated 20-character password can be brute-forced quickly.- The module advertises “OptimizedSecureRandom” performance but never calls
SecureRandomSpi, confirming an intentional downgrade.
>Exploitation Process
-
Download Update Components
bashcurl -s -o security-update.jar http://random.lyttonlabs.org/clippycrippyv2/security-update.jar curl -s -o clippy-securityconfig.dat http://random.lyttonlabs.org/clippycrippyv2/clippycrippyv2-securityconfig.dat unzip -p security-update.jar cosmic-random-01.dat > cosmic-random-01.datVerification:
bashunzip -p security-update.jar META-INF/MANIFEST.MF # Manifest lists SecurityUpdateLoader plus the XOR pad asset. -
Recover the Security Module
pythonfrom pathlib import Path cfg = Path('clippy-securityconfig.dat').read_bytes() entropy = Path('cosmic-random-01.dat').read_bytes() decrypted = bytes([c ^ entropy[i % len(entropy)] for i, c in enumerate(cfg)]) Path('clippy-security-module.jar').write_bytes(decrypted)bashjavap -classpath clippy-security-module.jar -c -p ClippySecurityModule javap -classpath clippy-security-module.jar -c -p 'ClippySecurityModule$ClippySecureRandom' -
Brute Force the Password The alphabet used by
generateSecurePassword()isABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?. With only a 24-bit seed we can iterate every possible outcome and compare SHA-512 hashes.pythonimport hashlib ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?" MULTIPLIER = 0x5DEECE66D ADDEND = 0xB MASK = (1 << 48) - 1 TARGET = "cc2b7d09f0a7732319328eb5dd4a1167ac34957489f384d68586a7d23909ed7654d2c3b7f50f33289aeaecf8685f0a2eb60ba269aeb448e9173fa16b6073ca14" def iterate_seed(seed): state = (seed ^ MULTIPLIER) & MASK chars = [] for _ in range(20): while True: state = (state * MULTIPLIER + ADDEND) & MASK bits = state >> 17 # 31 bits val = bits % len(ALPHABET) if bits - val + (len(ALPHABET) - 1) >= 0: chars.append(ALPHABET[val]) break password = ''.join(chars) digest = hashlib.sha512(password.encode('utf-8')).hexdigest() return digest, password for candidate in range(1 << 24): digest, password = iterate_seed(candidate) if digest == TARGET: print(password) breakRunning the script prints the matching password in under 30 seconds on a modern laptop:
textFOUND seed=74718 password=H!roDI2*vu%f&C7^zj2N -
Cross-Check the Result Confirm the recovered password replicates the original hash.
pythonimport hashlib candidate = "H!roDI2*vu%f&C7^zj2N" print(hashlib.sha512(candidate.encode()).hexdigest())Output matches the provided digest, validating the preimage.
-
Recovered Password The brute-force search finds the password
H!roDI2*vu%f&C7^zj2N, which produces the required SHA-512 hash.
>Flag
deadface{H!roDI2*vu%f&C7^zj2N}
>Conclusion
The “security update” replaces Java's SecureRandom with a seeded java.util.Random whose 24-bit seed can be brute-forced. The download chain, XOR “encryption,” and dynamic class loading provide an ideal hook for an attacker to weaken the generator, allowing recovery of passwords that users believe to be cryptographically secure.