- Published on
PCC CTF - Flag Generator Reverse Engineering Writeup
- Authors
- Name
- Muhammad Huzaifa
PCC Flag Generator - Reverse Engineering Writeup
Challenge Overview
Challenge: PCC Flag Generator
URL: http://c1.arena.airoverflow.com:39357/
Type: Java Reverse Engineering
Flag Format: PCC{}
Initial Analysis
The challenge provides a Spring Boot application (application.jar
) that requires a premium license key to generate flags. The application is protected with ClassFinal obfuscation.
Files Provided
application.jar
- Main Spring Boot applicationlicense.key
- Existing license fileflag.txt
- Contains test flag:PCC{
fak3_fl4g_f0r_73st1ng}
Dockerfile
- Shows the application runs with ClassFinal agent
Reverse Engineering Process
1. Understanding the Application Structure
The JAR file contains a Spring Boot application with the following key components:
com.ctf.premium.controller.PremiumController
- Handles web requestscom.ctf.premium.util.LicenseManager
- Manages license validationcom.ctf.premium.config.SecurityConfig
- Security configuration
2. Bypassing ClassFinal Obfuscation
The application uses ClassFinal obfuscation with password pcc2025
. I created a decryption utility to extract the real bytecode:
// DecryptJar.java - ClassFinal bypass utility
import java.io.*;
import java.util.jar.*;
import java.nio.file.*;
public class DecryptJar {
public static void main(String[] args) throws Exception {
String jarPath = "application.jar";
String password = "pcc2025";
net.roseboy.classfinal.JarDecryptor decryptor = net.roseboy.classfinal.JarDecryptor.getInstance();
String[] classNames = {
"com.ctf.premium.controller.PremiumController",
"com.ctf.premium.util.LicenseManager",
"com.ctf.premium.config.SecurityConfig"
};
for (String className : classNames) {
byte[] decryptedBytes = decryptor.doDecrypt(jarPath, className, password.toCharArray());
if (decryptedBytes != null) {
String outputPath = "decrypted_" + className.replace('.', '_') + ".class";
Files.write(Paths.get(outputPath), decryptedBytes);
System.out.println("Decrypted " + className);
}
}
}
}
3. Analyzing the License Validation Logic
After decompiling the decrypted classes, I discovered the validation mechanism:
LicenseManager.validateLicense()
public static boolean validateLicense(String licenseKey) {
String decrypted = decrypt(licenseKey);
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> jsonMap = mapper.readValue(decrypted, Map.class);
boolean ispremium = Boolean.TRUE.equals(jsonMap.get("ispremium"));
boolean isnoob = Boolean.TRUE.equals(jsonMap.get("isnoob"));
// Check valid_until timestamp
Object validUntilObj = jsonMap.get("valid_until");
boolean isValidTime = true;
if (validUntilObj instanceof Number) {
long validUntil = ((Number) validUntilObj).longValue();
long currentTime = System.currentTimeMillis() / 1000;
isValidTime = currentTime <= validUntil;
}
return ispremium && isnoob && isValidTime;
}
AES Key Discovery
The encryption key is constructed by concatenating 4 character arrays:
private static byte[] getSecretKeyBytes() {
// Array 1: [80, 67, 67, 50] = "PCC2"
// Array 2: [53, 80, 82, 69] = "5PRE"
// Array 3: [77, 73, 85, 77] = "MIUM"
// Array 4: [35, 49, 65, 50] = "#1A2"
// Final key: "PCC25PREMIUM#1A2"
}
4. Existing License Analysis
The provided license key decrypts to:
{
"ispremium": false,
"valid_until": 1792245400,
"version": "1.0",
"isnoob": true
}
The ispremium: false
explains why it doesn't grant access.
Exploitation
License Key Generator
I created a utility to generate valid license keys:
// GenerateLicense.java - License key generator
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class GenerateLicense {
public static void main(String[] args) throws Exception {
long currentTime = System.currentTimeMillis() / 1000;
long futureTime = currentTime + 31536000; // 1 year from now
String jsonPayload = String.format(
"{\"ispremium\":true,\"isnoob\":true,\"valid_until\":%d}",
futureTime
);
String encryptedLicense = encrypt(jsonPayload);
System.out.println("Valid License Key: " + encryptedLicense);
}
private static byte[] getSecretKeyBytes() {
return "PCC25PREMIUM#1A2".getBytes(StandardCharsets.UTF_8);
}
private static String encrypt(String plaintext) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(getSecretKeyBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
}
Generated Valid License Keys
- Short-term:
yTeTwj8e+3/t4kjREsgXZh3SfQki/lJVxafCK4NBCVUHZB1ODYnv5JEHtmmSDgyD+mCwfSU0wUgJgfTetSNkBA==
- Long-term:
yTeTwj8e+3/t4kjREsgXZh3SfQki/lJVxafCK4NBCVUHZB1ODYnv5JEHtmmSDgyDs/+wB6cDcsTOBSNwt84DKw==
- Exact format:
NAVYhRIvUivQJrx5sTYbpMe5+lONt9Kg/n7KEBkm0vaVXDkTZVKN7SpGwcuWkhtaJTKtJbui9s//S8QouEnRddH1qjNp7q1yScRsWpGdt+OYqLu4446nV9Tja0VUgstW
All keys decrypt to valid JSON:
{
"ispremium": true,
"isnoob": true,
"valid_until": [future_timestamp]
}
Application Flow
- Home Page: Displays current license and form for validation
- License Validation: POST to
/validate
endpoint - Success: Redirects with flash attributes containing flag
- Flag Display: Shows flag from
flag.txt
whenpremiumFeatures
is true
Technical Details
- Encryption: AES-128 ECB mode with PKCS5 padding
- Key: "PCC25PREMIUM#1A2" (16 bytes)
- Encoding: Base64
- Validation: JSON parsing with Jackson ObjectMapper
- Obfuscation: ClassFinal with password "pcc2025"
Conclusion
This challenge demonstrates:
- Java bytecode reverse engineering
- ClassFinal obfuscation bypass techniques
- AES encryption/decryption analysis
- JSON validation logic understanding
- Spring Boot application structure analysis
The reverse engineering process successfully identified the validation mechanism and generated cryptographically valid license keys. The application reads the flag from flag.txt
when a valid premium license is provided.
Files
DecryptJar.java
- ClassFinal bypass utilityGenerateLicense.java
- License key generatorDecryptExistingLicense.java
- Existing license analyzerTestLicense.java
- License validation tester