Hillstone Firewall Internals (2) - License

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


This article mainly describes Hillstone license formats.


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


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


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
  • Using big endian, mentioned in Firmware Format Big Picture
  • License Version: either of 1, 2, 3; this is version 2
  • License Length: the length of license string license:BASE64
  • “Time” fields are all timestamps in seconds
  • License Start Time - End Time: indicating when the user is allowed to use some features
  • License Support End Time: indicating when the additional software support ends (e.g. anti-virus database update, url database update, firmware update)
  • License Type + Parameter:
    Type Name Type Parameter
    Platform base 0x03E9 0x000003EB
    Anti-virus 0x000B 0xFFFFFFFF
    IPS 0x0019 (unused)
    QOS 0x000E (unused)
    Application DB 0x001C (unused)
  • License Expiration Days: typically set to 0x0000000F, it is only used by “trial” type license (not listed)
  • HMAC: 20 bytes in total, generated from an onboard SHA-1 coprocessor DS2460, the detailed algorithms are secretly kept.

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:

  • Waiting until prompting “Boot OS…”
  • About 5–10 seconds later, type “entershell” plus new line
  • A Linux shell will be available after a while

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";
        case 1:
            dev = "/dev/i2c-1";
    int fd = open(dev, 2);
    if(fd < 0) { return -1; }
        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");
        return buffer[0];
    return result;
int i2c_close(int 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);
    uint8_t buffer[0x100];
    for(uint8_t i = 0; i < 0x14; i++)
        out[i] = (uint8_t) i2c_read(fd, 0x40 + i);

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