Zum Hauptinhalt springen

LED Brille - Crypto-Analyse

Ghidra-Analyse von libAES.so

Setup

# Library aus APK extrahieren
unzip ledglasses.apk -d extracted/
cp extracted/lib/arm64-v8a/libAES.so .

# Ghidra starten
ghidraRun
# File → Import File → libAES.so
# Analyze → Auto Analyze

Funktionen identifizieren

Nach der Auto-Analyse suchen wir nach relevanten Funktionen:

Symbol Tree → Filter: "encrypt"

Gefunden:
- Java_com_xl_ledglasses_AESCipher_encrypt
- AES_encrypt (aus OpenSSL/BoringSSL)

JNI-Funktion analysieren

// Decompiled: Java_com_xl_ledglasses_AESCipher_encrypt
void Java_com_xl_ledglasses_AESCipher_encrypt
(JNIEnv *env, jclass cls, jbyteArray input)
{
// 1. Input-Array holen
jbyte *inputBytes = (*env)->GetByteArrayElements(env, input, NULL);
jsize length = (*env)->GetArrayLength(env, input);

// 2. Column-Major Transformation VORHER
byte transformed[16];
columnMajorTransform(inputBytes, transformed);

// 3. AES-Verschlüsselung mit hardcoded Key
byte encrypted[16];
AES_encrypt(transformed, encrypted, &aes_key); // ← Key hier!

// 4. Column-Major Transformation NACHHER
byte result[16];
columnMajorTransform(encrypted, result);

// 5. Ergebnis zurückgeben
jbyteArray output = (*env)->NewByteArray(env, 16);
(*env)->SetByteArrayRegion(env, output, 0, 16, result);
return output;
}

Key-Extraktion

Der Key ist als globale Variable definiert:

// Gefunden bei Adresse 0x00012340
unsigned char aes_key[16] = {
0x34, 0x52, 0x2a, 0x5b,
0x7a, 0x6e, 0x49, 0x2c,
0x08, 0x09, 0x0a, 0x9d,
0x8d, 0x2a, 0x23, 0xf8
};

Extrahierter Key (hex): 34522a5b7a6e492c08090a9d8d2a23f8

Column-Major Transformation

Eine ungewöhnliche Transformation wird vor und nach AES angewendet:

// columnMajorTransform - Rekonstruiert
void columnMajorTransform(byte *input, byte *output) {
// Behandelt 16 Bytes als 4x4 Matrix
// Konvertiert zwischen Row-Major und Column-Major

for (int i = 0; i < 16; i++) {
int row = i % 4;
int col = i / 4;
output[col + row * 4] = input[i];
}
}

Visualisierung:

Input (Row-Major):        Output (Column-Major):
0 1 2 3 0 4 8 12
4 5 6 7 → 1 5 9 13
8 9 10 11 2 6 10 14
12 13 14 15 3 7 11 15

Verifikation

Test mit bekanntem Plaintext/Ciphertext aus Wireshark:

from Crypto.Cipher import AES

KEY = bytes.fromhex("34522a5b7a6e492c08090a9d8d2a23f8")

def column_major_transform(data):
"""Convert between row-major and column-major"""
result = bytearray(16)
for i in range(16):
row = i % 4
col = i // 4
result[col + row * 4] = data[i]
return bytes(result)

def encrypt(plaintext):
# 1. Column-Major Transform
transformed = column_major_transform(plaintext)

# 2. AES-ECB Encrypt
cipher = AES.new(KEY, AES.MODE_ECB)
encrypted = cipher.encrypt(transformed)

# 3. Column-Major Transform (again)
return column_major_transform(encrypted)

# Test mit ATATS Command
plaintext = bytes([7, 0x41, 0x54, 0x41, 0x54, 0x53, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0])
ciphertext = encrypt(plaintext)

print(f"Calculated: {ciphertext.hex()}")
# Vergleich mit Wireshark-Capture...

Ergebnis: Berechneter Ciphertext stimmt mit Capture überein! ✓

Kryptographische Schwächen

1. Hardcoded Key (CWE-798)

Problem:  Key ist in jeder App-Installation identisch
Impact: Ein extrahierter Key kompromittiert ALLE Geräte
CVSS: 9.8 (Critical)

2. ECB Mode (CWE-327)

Problem:  ECB verschlüsselt gleiche Blöcke gleich
Impact: Muster im Ciphertext erkennbar
CVSS: 4.7 (Medium)

Demonstration:

# Gleicher Plaintext = Gleicher Ciphertext
plaintext1 = b"AAAAAAAAAAAAAAAA" # 16x 'A'
plaintext2 = b"AAAAAAAAAAAAAAAA" # 16x 'A'

cipher1 = encrypt(plaintext1)
cipher2 = encrypt(plaintext2)

assert cipher1 == cipher2 # ECB-Problem!

3. Keine Authentifizierung

Problem:  Kein HMAC, keine Signatur
Impact: Beliebige Commands können gesendet werden
CVSS: 8.8 (High)

Vollständiger Encryption Flow

Python-Implementation

from Crypto.Cipher import AES

class LEDGlassesEncryption:
KEY = bytes.fromhex("34522a5b7a6e492c08090a9d8d2a23f8")

@staticmethod
def _transform(data):
"""Column-major transformation"""
if len(data) != 16:
raise ValueError("Data must be 16 bytes")
result = bytearray(16)
for i in range(16):
row = i % 4
col = i // 4
result[col + row * 4] = data[i]
return bytes(result)

def encrypt(self, plaintext):
"""Encrypt with column-major + AES-ECB + column-major"""
# Pad to 16 bytes
padded = plaintext.ljust(16, b'\x00')[:16]

# Transform → Encrypt → Transform
transformed = self._transform(padded)
cipher = AES.new(self.KEY, AES.MODE_ECB)
encrypted = cipher.encrypt(transformed)
return self._transform(encrypted)

def decrypt(self, ciphertext):
"""Decrypt (reverse process)"""
transformed = self._transform(ciphertext)
cipher = AES.new(self.KEY, AES.MODE_ECB)
decrypted = cipher.decrypt(transformed)
return self._transform(decrypted)

Zusammenfassung

AspektDetails
AlgorithmusAES-128 ECB
Key34522a5b7a6e492c08090a9d8d2a23f8
BesonderheitColumn-Major Transformation vor/nach AES
SchwächenHardcoded Key, ECB Mode, keine Auth

Nächster Schritt

Weiter zum Proof of Concept um die Schwachstelle praktisch zu demonstrieren.