References

  1. 一种获取 FortiGate 权限的方法 & License 授权分析 - CataLpa: This blog post reverse-engineered the FortiGate firmware and licensing mechanism, and published a tool to generate the license keys.
  2. Reversing ArubaOS Firmware - SerializingMe: This blog post explores the ArubaOS firmware using Binwalk, but it doesn’t go into the details.
  3. Aruba Authentication Bypass / Insecure Transport / Tons Of Issues ≈ Packet Storm: This blog post published a collection of vulnerabilities in ArubaOS, including arbitrary modification of /etc/ntp.conf, static password of privileged “support” account and hardcoded “arubasecretadmin” account in /etc/passwd. The most important part is:
    1. It says the IV required for 3DES consists of 8 random bytes, and is stored as the first 8 byte of the encrypted password, which may also be used in ArubaOS for lisensing mechanism, I guess.
    2. It leaked the hardcoded 3DES key in the firmware.
  4. Remote Code Execution in Aruba Mobility Controller (ArubaOS) - CVE-2018-7081: This blog post describes simulating the ArubaOS in QEMU and intercepting the communication of PAPI.

Exploration

By chance, I obtained a brand new Aruba 650 controller with full licenses, at a few years ago with an irresistible price. The licensing mechanism is complete off-line, which means that all necessary information is hashed/encoded as a part of the license key, including the serial number, feature set, expiration date, and so on.

  • Device serial number: AR00XXXXX
  • License: XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX-XXX
    • Length with hyphens: 48
    • Length without hyphens: 43
    • Length of license without hyphens in base64: 64
filename: ArubaOS_6xx_6.4.4.25_79899
SHA-1: defbb8709b90b14c4f78fa5ae64b0c39276d90df
MD5: f038d7810c4f036005a6a4ef86a18137

By simply running binwalk -re --dd=".*" ArubaOS_6xx_6.4.4.25_79899, we can extract the firmware from the image.

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
512           0x200           ELF, 32-bit MSB MIPS-IV executable, MIPS, version 1 (SYSV)

Then, do the same thing for the extracted ELF file 200: binwalk -e 200, now we obtained a compressed file 5B4000.7z. On Windows 10, we can use Bandizip to extract it, and we got a core_image_files.tar, which is the root filesystem of the ArubaOS. This step seems cannot be done on macOS, for unknown reasons. The file tree is attached.

As for its name, core_image_files/mswitch/bin/licensemgr, it seems to be the license manager of the ArubaOS. Based on the existing knowledge and with the help of ChatGPT, we can perform deobfucation to some extent.

filename: licensemgr
SHA-1: a3f12c8357297a361ec3d112313aa4cc8af0e7e0
MD5: be48ae6c4cb5822885237001a847f2d9

IDA window of the main function

By looking into IDA, we have the pseudo code as follows:

_BYTE *__fastcall _license_user_to_base64(char *a1, int a2)
{
    char v2;       // $v1
    int v4;        // $v0
    int i;         // $a1
    _BYTE *v6;     // $a2
    int v7;        // $v1
    int v8;        // $v1
    _BYTE *result; // $v0
 
    v2 = *a1;
    v4 = *a1;
    for (i = 0; *a1; v4 = *a1)
    {
        v6 = (_BYTE *)(i + a2);
        if (v4 != 45)
        {
            ++i;
            *v6 = v2;
        }
        v2 = *++a1;
    }
    if (i == 32)
        goto LABEL_10;
    v7 = 1;
    if (i != 43)
        v7 = 6 - i % 6;
    v8 = v7 - 1;
    result = (_BYTE *)(a2 + i);
    if (v8 != -1)
    {
        do
        {
            --v8;
            *(_BYTE *)(i + a2) = 61;
            ++i;
        } while (v8 != -1);
    LABEL_10:
        result = (_BYTE *)(a2 + i);
    }
    *result = 0;
    return result;
}

With the help of ChatGPT, we obtained the following deobfuscated code, with some comments generated by ChatGPT as well:

// Function to convert a string to base64 with certain conditions
// Parameters:
// - a1: Input string
// - a2: Base address for output
 
_BYTE *__fastcall _license_user_to_base64(char *a1, int a2)
{
    char currentChar; // Current character in the input string
    int i;            // Counter for the loop
    _BYTE *output;    // Pointer to the output buffer
    int paddingCount; // Number of '=' padding characters to be added
 
    currentChar = *a1;          // Initialize currentChar with the first character of the input string
    i = 0;                      // Initialize the loop counter
    output = (_BYTE *)(i + a2); // Initialize the output pointer based on the provided base address
 
    // Iterate through the characters of the input string
    while (*a1)
    {
        // If the current character is not '-', copy it to the output buffer
        if (currentChar != 45)
        {
            ++i;                   // Increment the counter
            *output = currentChar; // Copy the character to the output buffer
        }
        currentChar = *++a1; // Move to the next character in the input string
    }
 
    // Check if the total count is 32
    if (i == 32)
        goto LABEL_10;
 
    // Calculate the number of '=' padding characters to be added
    paddingCount = 1;
    if (i != 43) // the length without hyphens
        paddingCount = 6 - i % 6;
 
    // Add the required '=' padding characters to the output buffer
    while (paddingCount != -1)
    {
        --paddingCount;
        *(_BYTE *)(i + a2) = 61; // '=' character
        ++i;                     // Increment the counter
    }
 
LABEL_10:
    result = (_BYTE *)(a2 + i); // Set the result pointer to the end of the output buffer
    *result = 0;                // Null-terminate the output buffer
    return result;              // Return the result pointer
}

So basically, it removes the hyphens from the input string, and adds padding for making it as a valid base64 string. By checking XREF information, we can found that this function is called by _license_decrypt_bundle_key, _license_decrypt_feature_key and _license_decrypt_platform_key.

Let’s focus on _license_decrypt_bundle_key first, it first calls _license_user_to_base64 to convert the input string to base64, then calls _license_str_to_byte to convert the base64 string to byte array and check format validity, and finally calls _license_decrypt to decrypt the byte array.

license_decrypt((int)licenseInBytes, lic_b64_length_int_64 / 2, (int)output, 51)

Since then I stucked, the _license_base64_dec is so complicated.