Challenge Jigsaw
Este reto consiste en analizar un archivo APK basado en Flutter donde la flag está cifrada mediante AES-CBC y la solución consiste en decompilar la aplicación para estudiar sus capas Dart, C y Java, extraer los fragmentos de la clave y el IV, y finalmente ensamblarlos para descifrar la flag de manera local.
Reconocimiento Inicial
El análisis del archivo manifest de la aplicación en jadx-gui indica que el APK fue construido utilizando el framework Flutter:
android:name="flutterEmbedding"
Una inspección más profunda de las librerías nativas compiladas implica examinar libmenascyber.so empleando Cutter. Este análisis descubre dos funciones en C de gran interés. La primera ejecuta rotaciones de bits hacia la derecha:
uint32_t randFunc1(unsigned char, unsigned int)(undefined8 placeholder_0, int64_t arg2)
{
uint8_t in_DIL;
int64_t var_9h;
// randFunc1(unsigned char, unsigned int)
return ((int32_t)(uint32_t)in_DIL >> ((uint8_t)arg2 & 0x1f) | (uint32_t)in_DIL << (8 - (uint8_t)arg2 & 0x1f)) & 0xff
;
}
La segunda función expone un bucle de descifrado basado en XOR:
void randFunc2(unsigned char const*, unsigned char*, unsigned char const*, unsigned long)
(int64_t arg1, int64_t arg2, int64_t arg3, unsigned long long arg4)
{
undefined uVar1;
int64_t var_30h;
unsigned long long var_28h;
for (var_30h = 0; (uint64_t)var_30h < arg4; var_30h = var_30h + 1) {
uVar1 = fcn.00000920(*(uint8_t *)(arg1 + var_30h) ^ *(uint8_t *)(arg3 + var_30h), 3);
*(undefined *)(arg2 + var_30h) = uVar1;
}
return;
}
La extracción de los arreglos hexadecimales asociados con estas funciones directamente desde Cutter provee los datos crudos necesarios para una parte del descifrado:
0x05c: 02 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
0x600: 5a 6b 7c 8d 9e af b0 c1 d2 e3 f4 05 16 27 38 49 5a 6b 7c 8d 9e af b0 c1 00 00 00 00 00 00 00 00
0x5e0: a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af
0x5f0: 1a 2b 3c 4d 5e 6f 70 81 92 a3 b4 c5 d6 e7 f8 09
Sin embargo, estos arreglos por sí solos resultan insuficientes para recuperar la flag, lo que apunta a la existencia de componentes adicionales distribuidos en otras capas de la aplicación.
Análisis de Flutter y Dart
La extracción de cadenas de texto desde el archivo kernel_blob.bin, ubicado en el directorio de assets, desvela la flag cifrada codificada en Base64:
$ strings kernel_blob.bin
...
aZ/KF0GsnN81j5XStQyKz3vXtktTVN5zFqy5lwTmub6fx5w70c+p08O0OWcn/9nh
...
Además, esta extracción expone la arquitectura empleada en la lógica de cifrado. La clave AES final y su respectivo IV se construyen concatenando tres partes distintas obtenidas desde diferentes orígenes:
Future<Map<String, List<int>>> getflag() async {
final partoneData = await partone();
final parttwoData = await _aesService.getparttwo();
final partthreeKey = _nativeLib.getAESKey();
final partthreeIV = _nativeLib.getAESIV();
// Combine and slice the key and IV from each part
final combinedKey = [
...partoneData['key']!.sublist(0, 8),
...parttwoData['key']!.sublist(0, 8),
...partthreeKey.sublist(0, 16)
];
final combinedIV = [
...partoneData['iv']!.sublist(0, 4),
...parttwoData['iv']!.sublist(0, 4),
...partthreeIV.sublist(0, 8)
];
...
La primera parte se encuentra directamente programada dentro de la lógica de Dart. Las cadenas de texto revelan una función _deterministicShuffle que se aplica sobre arreglos generados previamente:
final List<int> _hardcodedKey = List<int>.generate(32, (i) => (i + 1) % 256);
final List<int> _hardcodedIV = List<int>.generate(16, (i) => (i + 10) % 256);
final shuffledKey = _deterministicShuffle(_hardcodedKey, 5);
final shuffledIV = _deterministicShuffle(_hardcodedIV, 3);
El método _deterministicShuffle simplemente ejecuta rotaciones posicionales en el arreglo:
List<int> _deterministicShuffle(List<int> input, int shift) {
return List<int>.generate(input.length, (i) {
return input[(i + shift) % input.length];
});
}
Al calcular estas rotaciones de forma manual, se obtienen los bytes iniciales del rompecabezas:
Key base array: [1, 2, 3, 4, 5, 6, 7, 8...]
Shifted left by 5: [6, 7, 8, 9, 10, 11, 12, 13...]
IV base array: [10, 11, 12, 13, 14, 15...]
Shifted left by 3: [13, 14, 15, 16...]
La conversión de estos valores a formato hexadecimal proporciona los primeros ocho bytes de la clave (060708090a0b0c0d) y los primeros cuatro bytes del IV (0d0e0f10).
Lógica Nativa en Android (Segunda Parte)
El código Dart indica que la segunda parte se recupera desde la capa de la plataforma Android. Al retomar jadx y analizar los archivos de MainActivityKt, se identifica la lógica Java encargada de generar esta pieza. Aislando el código e imprimiendo los resultados, el segundo conjunto de bytes queda al descubierto:
public class Main {
private static final byte rR(byte v, int c) {
return (byte) ((v >> c) | (v << (8 - c)));
}
private static final byte[] tB(byte[] i, byte[] p) {
int length = i.length;
byte[] bArr = new byte[length];
for (int i2 = 0; i2 < length; i2++) {
bArr[i2] = rR((byte) (i[i2] ^ p[i2 % p.length]), 3);
}
return bArr;
}
public static void main(String[] args) {
byte[] bArr = {90, 107, 124, -115, -98, -81, -80, -63, -46, -29, -12, 5, 22, 39, 56, 73};
byte[] bArr2 = {26, 43, 60, 77, 94, 111, 112, -127, -110, -93, -76, -59, -42, -25, -8, 9};
byte[] bArr3 = {96, 61, -21, 16, 21, -54, 113, -66, 43, 115, -82, -16, -123, 125, 119, -127, 31, 53, 44, 7, 59, 97, 8, -41, 45, -104, 16, -93, 9, 20, -33, -12};
byte[] bArr4 = {-96, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
byte[] parttwo_1 = tB(bArr3, bArr);
byte[] parttwo_2 = tB(bArr4, bArr2);
System.out.print("Part 2 Key (8 bytes): ");
for (int i = 0; i < 8; i++) {
System.out.printf("%02x", parttwo_1[i]);
}
System.out.println();
System.out.print("Part 2 IV (4 bytes): ");
for (int i = 0; i < 4; i++) {
System.out.printf("%02x", parttwo_2[i]);
}
System.out.println();
}
}
La ejecución de este código proporciona los bytes necesarios:
Part 2 Key (8 bytes): 47caf2f3f1acf8ef
Part 2 IV (4 bytes): f745c7c9
=== Code Execution Successful ===
Para extraer la pieza final, se desarrolla un script en Python que replica la lógica identificada en la librería nativa en C (libmenascyber.so). Este script incorpora las operaciones XOR, la rotación de bits hacia la derecha (ROR) de 3 posiciones y los arreglos hexadecimales extraídos durante la etapa de reconocimiento.
#!/usr/bin/env python3
def ror_3(val):
# ROR (Rotate Right) by 3 bits for an 8-bit integer
return ((val >> 3) | (val << 5)) & 0xFF
def decrypt_part(arg1, arg3):
result = []
for i in range(len(arg1)):
xor_val = arg1[i] ^ arg3[i]
final_val = ror_3(xor_val)
result.append(final_val)
return bytes(result)
# Key Arrays (32 bytes)
key_arg1 = [
0x02, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
]
key_arg3 = [
0x5a, 0x6b, 0x7c, 0x8d, 0x9e, 0xaf, 0xb0, 0xc1,
0xd2, 0xe3, 0xf4, 0x05, 0x16, 0x27, 0x38, 0x49,
0x5a, 0x6b, 0x7c, 0x8d, 0x9e, 0xaf, 0xb0, 0xc1,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
# IV Arrays (16 bytes)
iv_arg1 = [
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf
]
iv_arg3 = [
0x1a, 0x2b, 0x3c, 0x4d, 0x5e, 0x6f, 0x70, 0x81,
0x92, 0xa3, 0xb4, 0xc5, 0xd6, 0xe7, 0xf8, 0x09
]
part3_key = decrypt_part(key_arg1, key_arg3)
part3_iv = decrypt_part(iv_arg1, iv_arg3)
print("Part 3 Key (hex):", part3_key.hex())
print("Part 3 IV (hex):", part3_iv.hex())
Al ejecutar el script, se logran recuperar los fragmentos tercero y final:
$ python3 exploit.py
Part 3 Key (hex): 0b4dcfd15355d6d85b5ddfc14345c6c8494fcdd35157d4da0323436383a3c3e3
Part 3 IV (hex): 5751d3dd5f59dac44741c3cd4f49cad4
Habiendo recuperado las tres partes, es posible construir los parámetros definitivos para AES-CBC mediante la concatenación de las cadenas respectivas. La clave resultante es 060708090a0b0c0d47caf2f3f1acf8ef0b4dcfd15355d6d85b5ddfc14345c6c8 y el IV queda como 0d0e0f10f745c7c95751d3dd5f59dac4.
La aplicación de estos parámetros sobre la cadena en Base64 extraída anteriormente, utilizando una herramienta como CyberChef, permite descifrar exitosamente el payload y revelar la flag.
HTB{FLAG}