Hillstone Firewall Internals (2) - License
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 |
- 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";
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
