Get self-signed Hillstone licenses with their onboard DS2460 SHA-1 coprocessors.

License

This article mainly describes Hillstone license formats.

Analysis

Mentioned in Firmware Format Big Picture, I have already extracted its rootfs, therefore I can certainly find how the firewall manages its licenses. Most parts are hidden in the daemon /usr/local/sbin/mgd, but there are also some displayed parts in /usr/local/sbin/cli.

License-related Functions in Ghidra

License String

A license string / file is what Hillstone will give to their customers if they bought a license, in the form of

license:BASE64

where BASE64 is a Base64 encoded string.

Signed License

License string got decoded through base64 -d or atob, which is however another encrypted data. Through decompiling, the original data are signed by Hillstone license private key (or encrypted, openssl rsautl uses the word “sign”, however, the data will not be hashed).

One-liner:

cut -f2 -d':' license_file | base64 -d > signed_license

License Contents

The public one of Hillstone license keypairs is located at rootfs /.etc/license_rsa_pub.key. This key cannot be directly used through openssl rsautl, it should first be converted to general public key formats through openssl rsa -RSAPublicKey_in -in license_rsa_pub.key -out license_pub.key.

RSA Public Key Conversion

Then by running openssl rsautl -pubin -inkey license_key.pub -in signed_license, the real license contents are displayed:

License Content (Sample)

This is the description of license content format:

Size Description
4B License Version
4B License Length
16B License Name
16B Device SN
4B License End Time
4B License Expiration Days
4B License Start Time
4B Unknown
4B License Support End Time
4B Unknown
2B Unknown
2B License Type
4B License Parameter (for some types)
128B License Customer Name
20B HMAC

License HMAC

Enter the shell

There is no public hacks on DS2460, however, I could write a small program to calculate license HMAC. Fortunately, Hillstone firmwares provides a Linux shell “backdoor” through console:

Hillstone Entershell

After the firewall initializes, all network features will be available, so files can be transferred by wget, curl, ftpget, ftpput etc.

HMAC Program

The following program can be used to perform DS2460 HMAC calculation.

The DS SN is stored at the onboard EEPROM, typically with Linux device path /sys/devices/platform/i2c-octeon.0/i2c-0/0-0054/eeprom, at the offset 0x7C.

For the license contents, it first gets MD5 (16 bytes) from the first 0xC8 bytes (i.e. all fields before HMAC), then select its 11-th, 12-th, 13-th byte. Together with DS SN, it writes packed data through I2C SMBUS.

License HMAC Diagram

#include <openssl/md5.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>

int i2c_open(int param)
{
    const char *dev;
    switch(param >> 7)
    {
        case 0:
            dev = "/dev/i2c-0";
            break;
        case 1:
            dev = "/dev/i2c-1";
            break;
    }
    int fd = open(dev, 2);
    if(fd < 0) { return -1; }
    else
    {
        int result = ioctl(fd, 0x706, param & 0x7f);
        if(result < 0)
        {
            printf("Error I2C slave.\n");
            return result;
        }
    }
    return fd;
}

struct i2c_smbus_ioctl_data
{
    uint8_t read_write;
    uint8_t command;
    uint32_t size;
    void *data;
};

int i2c_write(int fd, uint8_t command, uint8_t value)
{
    struct i2c_smbus_ioctl_data data;
    uint8_t buffer[56]; buffer[0] = value;
    data.read_write = 0;
    data.command = command;
    data.size = 2;
    data.data = buffer;
    int result = ioctl(fd, 0x720, &data);
    return result;
}
int i2c_read(int fd, uint8_t command)
{
    struct i2c_smbus_ioctl_data data;
    uint8_t buffer[56];
    data.read_write = 1;
    data.command = command;
    data.size = 2;
    data.data = buffer;
    int result = ioctl(fd, 0x720, &data);
    if(result < 0)
    {
        printf("Error reading!\n");
    }
    else
    {
        return buffer[0];
    }
    return result;
}
int i2c_close(int fd)
{
    close(fd);
}

uint8_t hmac_data[] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
    0x40, 0xAA, 0xBB, 0xCC, 0xDD, 0x00, 0x00, 0x00, // 0x40, SN1, SN2, SN3, SN4
    0x00, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0x80, // MD5[10], [11], [12], 0x80
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xb8
};

void hmac(uint8_t i1, uint8_t i2, uint8_t i3, uint8_t *out)
{

    hmac_data[48 + 4] = i1;
    hmac_data[48 + 5] = i2;
    hmac_data[48 + 6] = i3;
    int fd = i2c_open(0x40);
    if(fd < 0) { printf("Error opening I2C!\n"); return; }
    for(int i = 0; i < 0x40; i++) { i2c_write(fd, (uint8_t) i, hmac_data[i]); }
    for(int i = 0; i < 0x40; i++) { i2c_read(fd, (uint8_t) i); }
    i2c_write(fd, 0x5c, 0x94);
    usleep(5000);
    uint8_t buffer[0x100];
    for(uint8_t i = 0; i < 0x14; i++)
    {
        out[i] = (uint8_t) i2c_read(fd, 0x40 + i);
    }
    i2c_close(fd);
}

uint32_t read_dssn(const char *path)
{
    uint32_t dssn;
    FILE *f = fopen(path, "rb");
    fseek(f, 0x7C, SEEK_SET);
    fread(&dssn, 1, 4, f);
    return dssn;
}

uint32_t get_hash(const char *path)
{
    uint8_t buffer[0xC8];
    uint8_t md5[0x10];
    MD5_CTX ctx;
    FILE *f = fopen(path, "rb");
    fread(buffer, 1, 0xC8, f);
    MD5_Init(&ctx);
    MD5_Update(&ctx, buffer, 0xC8);
    MD5_Final(md5, &ctx);
    return (uint32_t)(md5[10]) << 24 | (uint32_t)(md5[11]) << 16 | (uint32_t)(md5[12]) << 8;
}

int main(int argc, char *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "dssha1 (EEPROM FILE / DS SN) (LICENSE FILE / PARTED HASH)\n");
        return 1;
    }
    uint32_t dssn = strtoul(argv[1], NULL, 16);
    uint32_t hash = strtoul(argv[2], NULL, 16);
    uint8_t *hash_p = (uint8_t *) &hash;
    uint8_t hmac_result[0x14];

    if(!dssn) { dssn = read_dssn(argv[1]); }
    if(!hash) { hash = get_hash(argv[2]); }
   
    printf("DS SN: %08X\n", dssn);
    printf("HASH: %08X\n", hash);
    *((uint32_t *) &hmac_data[41]) = dssn;

    hmac(hash_p[0], hash_p[1], hash_p[2], hmac_result);
    puts("HMAC Result:");
    for(int i = 0; i < 0x14; i++)
    {
        printf("%02X", hmac_result[i]);
    }
    puts("");
    return 0;
}

To compile this, a proper MIPS64 (big endian) toolchain should be prepared, then

mips64-unknown-linux-gnu-gcc dssha1.c -o dssha1 -lssl -lcrypto -Wl,--unresolved-symbols=ignore-all

The binary is also available at here.

Using the Program

Get HMAC