Use the tree to jump between collections without leaving the reader.

archive Select writeup Open tree
HackTheBox/Challenges/Challenge Jigsaw.en.md READ_ONLY

Challenge Jigsaw

This challenge consists of analyzing an APK file housing a Flutter-based application where the flag is encrypted using AES-CBC and the solution consists of decompiling the application to study its Dart, C, and Java layers, extracting the encryption key and IV fragments, and assembling them to decrypt the flag locally.

Initial Reconnaissance

Analyzing the application's manifest file in jadx-gui indicates that the APK is built using the Flutter framework:

android:name="flutterEmbedding"

A deeper look into the compiled native libraries involves examining libmenascyber.so using Cutter. This analysis unveils two interesting C functions. The first function executes right bit rotations:

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
    ;
}

The second function reveals an XOR decryption loop:

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;
}

Extracting the hexadecimal arrays associated with these functions directly from Cutter provides the raw data needed for part of the decryption:

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

However, these arrays alone are insufficient to recover the flag, pointing to the existence of additional components distributed across other layers of the application.

Flutter and Dart Analysis

Extracting strings from the kernel_blob.bin file, located in the assets directory, unveils the encrypted flag encoded in Base64:

$ strings kernel_blob.bin
...
aZ/KF0GsnN81j5XStQyKz3vXtktTVN5zFqy5lwTmub6fx5w70c+p08O0OWcn/9nh
...

Furthermore, this extraction exposes the encryption logic architecture. The final AES key and IV are constructed by concatenating three distinct parts fetched from different sources:

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)
    ];
...

The first part is hardcoded within the Dart logic. The strings reveal a _deterministicShuffle function applied to pre-generated arrays:

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);

The _deterministicShuffle method simply performs positional array rotations:

List<int> _deterministicShuffle(List<int> input, int shift) {
    return List<int>.generate(input.length, (i) {
        return input[(i + shift) % input.length];
    });
}

Calculating these rotations manually yields the initial bytes of the puzzle:

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...]

Converting these values to hexadecimal provides the first eight bytes of the Key (060708090a0b0c0d) and the first four bytes of the IV (0d0e0f10).

Native Android Logic (Part Two)

The Dart code indicates that the second part is retrieved from the Android platform layer. Revisiting jadx and analyzing the MainActivityKt files uncovers the Java logic responsible for generating this piece. By isolating the code and printing the results, the second set of bytes is revealed:

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();
        }
}

Executing this code provides the required bytes:

Part 2 Key (8 bytes): 47caf2f3f1acf8ef
Part 2 IV (4 bytes): f745c7c9

=== Code Execution Successful ===

To extract the final piece, a Python script is developed to replicate the logic identified in the native C library (libmenascyber.so). This script incorporates the XOR operations, the 3-bit rotate right (ROR) function, and the hexadecimal arrays extracted during the initial reconnaissance stage.

#!/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())

Running the script successfully recovers the third and final fragments:

$ python3 exploit.py
Part 3 Key (hex): 0b4dcfd15355d6d85b5ddfc14345c6c8494fcdd35157d4da0323436383a3c3e3
Part 3 IV (hex): 5751d3dd5f59dac44741c3cd4f49cad4

With all three parts recovered, the final AES-CBC parameters can be constructed by concatenating the respective strings. The resulting key is 060708090a0b0c0d47caf2f3f1acf8ef0b4dcfd15355d6d85b5ddfc14345c6c8, and the IV is 0d0e0f10f745c7c95751d3dd5f59dac4.

Applying these parameters to the previously extracted Base64 string, utilizing a tool like CyberChef, successfully decrypts the payload and reveals the flag.

HTB{FLAG}