Skip to content

Instantly share code, notes, and snippets.

@apkunpacker
Forked from MhmRdd/provision_device_ids.c
Created March 1, 2026 05:17
Show Gist options
  • Select an option

  • Save apkunpacker/11b881193020fe2a2de0f84725c7fa90 to your computer and use it in GitHub Desktop.

Select an option

Save apkunpacker/11b881193020fe2a2de0f84725c7fa90 to your computer and use it in GitHub Desktop.
Standalone Device ID provisioning utility for Qualcomm Keymaster
/*
* provision_device_ids.c
* Copyright (c) 2026 mhmrdd. All rights reserved.
*
* Standalone Device ID provisioning utility for Qualcomm Keymaster.
*
* This tool provisions Android attestation identifiers (brand, device,
* product, serial, IMEI/MEID, manufacturer, model) into secure storage
* through QSEECom and finalizes Device ID provisioning state.
*
* Runtime sequence:
* 1) GET_VERSION (0x0200)
* 2) SET_VERSION (0x0207)
* 3) PROVISION_DEVICE_IDS (0x220A)
* 4) SET_PROVISIONING_DEVICE_ID_SUCCESS (0x2218)
*
* Build (Android NDK):
* $NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android31-clang \
* -O2 -Wall -Wextra -o provision_device_ids provision_device_ids.c -ldl
*/
#include <dlfcn.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define KM_TAG_ATTESTATION_ID_BRAND 0x900002C6u
#define KM_TAG_ATTESTATION_ID_DEVICE 0x900002C7u
#define KM_TAG_ATTESTATION_ID_PRODUCT 0x900002C8u
#define KM_TAG_ATTESTATION_ID_SERIAL 0x900002C9u
#define KM_TAG_ATTESTATION_ID_IMEI 0x900002CAu
#define KM_TAG_ATTESTATION_ID_MEID 0x900002CBu
#define KM_TAG_ATTESTATION_ID_MANUFACTURER 0x900002CCu
#define KM_TAG_ATTESTATION_ID_MODEL 0x900002CDu
#define CMD_GET_VERSION 0x0200u
#define CMD_SET_VERSION 0x0207u
#define CMD_PROVISION_DEVICE_IDS 0x220Au
#define CMD_SET_PROVISIONING_DEVICE_ID_SUCCESS 0x2218u
#define SHARED_BUF_SIZE 0xA000u
#define MAX_PROP_LEN 128
#define MAX_IDS 10
#define DEFAULT_LIB_PATH "/vendor/lib64/libQSEEComAPI.so"
#define DEFAULT_LIB_PATH_ALT "/vendor/lib64/hw/libQSEEComAPI.so"
#define DEFAULT_TA_PATH "/vendor/firmware_mnt/image"
#define DEFAULT_TA_NAME "keymaster64"
struct QSEECom_handle {
unsigned char *ion_sbuffer;
};
typedef int (*fn_start_app)(struct QSEECom_handle **, const char *, const char *, uint32_t);
typedef int (*fn_send_cmd)(struct QSEECom_handle *, void *, uint32_t, void *, uint32_t);
typedef int (*fn_shutdown_app)(struct QSEECom_handle **);
struct device_id {
uint32_t tag;
char value[MAX_PROP_LEN];
};
struct km_version {
uint32_t ta_api_major;
uint32_t ta_api_minor;
uint32_t ta_major;
uint32_t ta_minor;
};
struct km_cmd_response {
int32_t status;
uint32_t data_len;
const uint8_t *data;
};
static fn_start_app qseecom_start_app;
static fn_send_cmd qseecom_send_cmd;
static fn_shutdown_app qseecom_shutdown_app;
static void *g_qseecom_lib;
static char g_loaded_lib_path[256];
static struct device_id g_ids[MAX_IDS];
static int g_id_count;
#ifdef __ANDROID__
extern int __system_property_get(const char *name, char *value);
#else
static int __system_property_get(const char *name, char *value)
{
(void)name;
value[0] = '\0';
return 0;
}
#endif
static uint32_t align4(uint32_t v)
{
return (v + 3u) & ~3u;
}
static const char *tag_name(uint32_t tag)
{
switch (tag) {
case KM_TAG_ATTESTATION_ID_BRAND: return "BRAND";
case KM_TAG_ATTESTATION_ID_DEVICE: return "DEVICE";
case KM_TAG_ATTESTATION_ID_PRODUCT: return "PRODUCT";
case KM_TAG_ATTESTATION_ID_SERIAL: return "SERIAL";
case KM_TAG_ATTESTATION_ID_IMEI: return "IMEI";
case KM_TAG_ATTESTATION_ID_MEID: return "MEID";
case KM_TAG_ATTESTATION_ID_MANUFACTURER: return "MANUFACTURER";
case KM_TAG_ATTESTATION_ID_MODEL: return "MODEL";
default: return "UNKNOWN";
}
}
static void hexdump(const uint8_t *data, int len)
{
int i;
for (i = 0; i < len; ++i) {
if (i > 0 && (i % 16) == 0) {
printf("\n");
}
printf("%02X ", data[i]);
}
printf("\n");
}
static const char *get_prop(const char *name, char *buf)
{
buf[0] = '\0';
__system_property_get(name, buf);
return buf;
}
static void get_first_prop(const char *const *keys, size_t nkeys, char *out)
{
char tmp[MAX_PROP_LEN];
size_t i;
out[0] = '\0';
for (i = 0; i < nkeys; ++i) {
const char *v = get_prop(keys[i], tmp);
if (v[0]) {
strncpy(out, v, MAX_PROP_LEN - 1);
out[MAX_PROP_LEN - 1] = '\0';
return;
}
}
}
static void add_id(uint32_t tag, const char *value)
{
if (!value || !value[0] || g_id_count >= MAX_IDS) {
return;
}
g_ids[g_id_count].tag = tag;
strncpy(g_ids[g_id_count].value, value, MAX_PROP_LEN - 1);
g_ids[g_id_count].value[MAX_PROP_LEN - 1] = '\0';
g_id_count++;
}
static int cbor_encode_uint(uint8_t *buf, uint32_t val)
{
if (val <= 23) {
buf[0] = (uint8_t)val;
return 1;
}
if (val <= 0xFF) {
buf[0] = 0x18;
buf[1] = (uint8_t)val;
return 2;
}
if (val <= 0xFFFF) {
buf[0] = 0x19;
buf[1] = (uint8_t)(val >> 8);
buf[2] = (uint8_t)val;
return 3;
}
buf[0] = 0x1A;
buf[1] = (uint8_t)(val >> 24);
buf[2] = (uint8_t)(val >> 16);
buf[3] = (uint8_t)(val >> 8);
buf[4] = (uint8_t)val;
return 5;
}
static int cbor_encode_nint(uint8_t *buf, uint32_t n)
{
if (n <= 23) {
buf[0] = (uint8_t)(0x20 | n);
return 1;
}
if (n <= 0xFF) {
buf[0] = 0x38;
buf[1] = (uint8_t)n;
return 2;
}
if (n <= 0xFFFF) {
buf[0] = 0x39;
buf[1] = (uint8_t)(n >> 8);
buf[2] = (uint8_t)n;
return 3;
}
buf[0] = 0x3A;
buf[1] = (uint8_t)(n >> 24);
buf[2] = (uint8_t)(n >> 16);
buf[3] = (uint8_t)(n >> 8);
buf[4] = (uint8_t)n;
return 5;
}
static int cbor_encode_tag_key(uint8_t *buf, uint32_t tag)
{
int32_t s = (int32_t)tag;
if (s >= 0) {
return cbor_encode_uint(buf, (uint32_t)s);
}
return cbor_encode_nint(buf, (uint32_t)(-1 - s));
}
static int cbor_encode_bstr(uint8_t *buf, const void *data, size_t len)
{
int hdr;
if (len <= 23) {
buf[0] = (uint8_t)(0x40 | len);
hdr = 1;
} else if (len <= 0xFF) {
buf[0] = 0x58;
buf[1] = (uint8_t)len;
hdr = 2;
} else {
buf[0] = 0x59;
buf[1] = (uint8_t)(len >> 8);
buf[2] = (uint8_t)len;
hdr = 3;
}
memcpy(buf + hdr, data, len);
return hdr + (int)len;
}
static int build_cbor_payload(uint8_t *buf, size_t cap)
{
int i, off = 0;
int map_count = g_id_count + 1;
if (g_id_count == 0) {
return -1;
}
buf[off++] = (uint8_t)(0xA0 | map_count);
off += cbor_encode_uint(buf + off, 22);
off += cbor_encode_uint(buf + off, (uint32_t)g_id_count);
for (i = 0; i < g_id_count; ++i) {
size_t vlen = strlen(g_ids[i].value);
off += cbor_encode_tag_key(buf + off, g_ids[i].tag);
off += cbor_encode_bstr(buf + off, g_ids[i].value, vlen);
if ((size_t)off >= cap - 16) {
return -1;
}
}
return off;
}
static int load_qseecom(void)
{
const char *cands[2];
int n = 0;
int i;
cands[n++] = DEFAULT_LIB_PATH;
cands[n++] = DEFAULT_LIB_PATH_ALT;
for (i = 0; i < n; ++i) {
g_qseecom_lib = dlopen(cands[i], RTLD_NOW | RTLD_LOCAL);
if (g_qseecom_lib) {
strncpy(g_loaded_lib_path, cands[i], sizeof(g_loaded_lib_path) - 1);
g_loaded_lib_path[sizeof(g_loaded_lib_path) - 1] = '\0';
break;
}
}
if (!g_qseecom_lib) {
fprintf(stderr, "Failed to load QSEEComAPI from %s or %s. Last error: %s\n",
DEFAULT_LIB_PATH, DEFAULT_LIB_PATH_ALT, dlerror());
return -1;
}
qseecom_start_app = (fn_start_app)dlsym(g_qseecom_lib, "QSEECom_start_app");
qseecom_send_cmd = (fn_send_cmd)dlsym(g_qseecom_lib, "QSEECom_send_cmd");
qseecom_shutdown_app = (fn_shutdown_app)dlsym(g_qseecom_lib, "QSEECom_shutdown_app");
if (!qseecom_start_app || !qseecom_send_cmd || !qseecom_shutdown_app) {
fprintf(stderr, "Failed resolving QSEECom symbols.\n");
return -1;
}
return 0;
}
static int start_ta(struct QSEECom_handle **h, const char *ta_path, const char *ta_name)
{
int ret = qseecom_start_app(h, ta_path, ta_name, SHARED_BUF_SIZE);
if (ret == 0 && *h) {
return 0;
}
if (strcmp(ta_name, DEFAULT_TA_NAME) == 0) {
ret = qseecom_start_app(h, ta_path, "keymaster", SHARED_BUF_SIZE);
}
return ret;
}
static int send_raw(struct QSEECom_handle *h, const void *req, uint32_t req_len, uint8_t **rsp_out, uint32_t *rsp_len_out)
{
uint8_t *sbuf;
uint32_t rsp_off = align4(req_len);
uint32_t rsp_len;
int ret;
if (!h || !h->ion_sbuffer || rsp_off >= SHARED_BUF_SIZE) {
return -1;
}
rsp_len = SHARED_BUF_SIZE - rsp_off;
sbuf = h->ion_sbuffer;
memset(sbuf, 0, SHARED_BUF_SIZE);
memcpy(sbuf, req, req_len);
ret = qseecom_send_cmd(h, sbuf, req_len, sbuf + rsp_off, rsp_len);
if (ret != 0) {
return ret;
}
*rsp_out = sbuf + rsp_off;
*rsp_len_out = rsp_len;
return 0;
}
static int km_get_version(struct QSEECom_handle *h, struct km_version *ver)
{
uint32_t req = CMD_GET_VERSION;
uint8_t *rsp;
uint32_t rsp_len;
uint32_t *u32;
int ret = send_raw(h, &req, 4, &rsp, &rsp_len);
if (ret != 0 || rsp_len < 20) {
return ret ? ret : -1;
}
u32 = (uint32_t *)rsp;
if ((int32_t)u32[0] != 0) {
return (int32_t)u32[0];
}
ver->ta_api_major = u32[1];
ver->ta_api_minor = u32[2];
ver->ta_major = u32[3];
ver->ta_minor = u32[4];
return 0;
}
static int km_set_version(struct QSEECom_handle *h)
{
uint32_t req[6] = {CMD_SET_VERSION, 4, 5, 4, 5, 0};
uint8_t *rsp;
uint32_t rsp_len;
int32_t status;
int ret = send_raw(h, req, sizeof(req), &rsp, &rsp_len);
if (ret != 0 || rsp_len < 4) {
return ret ? ret : -1;
}
memcpy(&status, rsp, 4);
return status;
}
static int send_km_status(struct QSEECom_handle *h, const void *req, uint32_t req_len)
{
uint8_t *rsp;
uint32_t rsp_len;
int32_t status;
int ret = send_raw(h, req, req_len, &rsp, &rsp_len);
if (ret != 0 || rsp_len < 4) {
return ret ? ret : -1;
}
memcpy(&status, rsp, 4);
return status;
}
static int send_km_response(struct QSEECom_handle *h, const void *req, uint32_t req_len, struct km_cmd_response *out)
{
uint8_t *rsp;
uint32_t rsp_len;
int ret = send_raw(h, req, req_len, &rsp, &rsp_len);
if (ret != 0) {
return ret;
}
if (rsp_len < 8) {
return -1;
}
memcpy(&out->status, rsp, 4);
memcpy(&out->data_len, rsp + 4, 4);
out->data = rsp + 8;
if (out->data_len > rsp_len - 8) {
out->data_len = rsp_len - 8;
}
return 0;
}
static void wait_listeners(void)
{
char v[MAX_PROP_LEN];
int i;
for (i = 0; i < 50; ++i) {
get_prop("vendor.sys.listeners.registered", v);
if (strcmp(v, "true") == 0) {
return;
}
usleep(100000);
}
}
static void usage(const char *p)
{
fprintf(stderr,
"Usage: %s [options]\n"
" -b BRAND -d DEVICE -p PRODUCT -s SERIAL\n"
" -m MANUFACTURER -M MODEL\n"
" -i IMEI -I IMEI2 -e MEID -E MEID2\n"
" -t TA_NAME -P TA_PATH\n"
" -n dry-run\n", p);
}
int main(int argc, char **argv)
{
char brand[MAX_PROP_LEN] = {0}, device[MAX_PROP_LEN] = {0}, product[MAX_PROP_LEN] = {0};
char serial[MAX_PROP_LEN] = {0}, manufacturer[MAX_PROP_LEN] = {0}, model[MAX_PROP_LEN] = {0};
char imei[MAX_PROP_LEN] = {0}, imei2[MAX_PROP_LEN] = {0}, meid[MAX_PROP_LEN] = {0}, meid2[MAX_PROP_LEN] = {0};
const char *ta_name = DEFAULT_TA_NAME, *ta_path = DEFAULT_TA_PATH;
struct QSEECom_handle *h = NULL;
struct km_version ver;
struct km_cmd_response rsp;
uint8_t req[4096];
uint32_t cmd;
int cbor_len, req_len, dry = 0, opt, ret;
while ((opt = getopt(argc, argv, "b:d:p:s:m:M:i:I:e:E:t:P:nh")) != -1) {
switch (opt) {
case 'b': strncpy(brand, optarg, MAX_PROP_LEN - 1); break;
case 'd': strncpy(device, optarg, MAX_PROP_LEN - 1); break;
case 'p': strncpy(product, optarg, MAX_PROP_LEN - 1); break;
case 's': strncpy(serial, optarg, MAX_PROP_LEN - 1); break;
case 'm': strncpy(manufacturer, optarg, MAX_PROP_LEN - 1); break;
case 'M': strncpy(model, optarg, MAX_PROP_LEN - 1); break;
case 'i': strncpy(imei, optarg, MAX_PROP_LEN - 1); break;
case 'I': strncpy(imei2, optarg, MAX_PROP_LEN - 1); break;
case 'e': strncpy(meid, optarg, MAX_PROP_LEN - 1); break;
case 'E': strncpy(meid2, optarg, MAX_PROP_LEN - 1); break;
case 't': ta_name = optarg; break;
case 'P': ta_path = optarg; break;
case 'n': dry = 1; break;
case 'h': usage(argv[0]); return 0;
default: usage(argv[0]); return 1;
}
}
if (!brand[0]) { const char *k[] = {"ro.product.brand", "ro.product.vendor.brand"}; get_first_prop(k, 2, brand); }
if (!device[0]) { const char *k[] = {"ro.product.device", "ro.product.vendor.device"}; get_first_prop(k, 2, device); }
if (!product[0]) { const char *k[] = {"ro.product.name", "ro.product.vendor.name"}; get_first_prop(k, 2, product); }
if (!serial[0]) { const char *k[] = {"ro.serialno", "ro.boot.serialno"}; get_first_prop(k, 2, serial); }
if (!manufacturer[0]) { const char *k[] = {"ro.product.manufacturer", "ro.product.vendor.manufacturer"}; get_first_prop(k, 2, manufacturer); }
if (!model[0]) { const char *k[] = {"ro.product.model", "ro.product.vendor.model"}; get_first_prop(k, 2, model); }
if (!imei[0]) { const char *k[] = {"persist.vendor.radio.imei", "persist.radio.imei", "vendor.ril.imei", "ril.gsm.imei", "ro.ril.oem.imei", "ro.ril.oem.imei1"}; get_first_prop(k, 6, imei); }
if (!imei2[0]) { const char *k[] = {"persist.vendor.radio.imei2", "persist.radio.imei2", "vendor.ril.imei2", "ril.gsm.imei2", "ro.ril.oem.imei2"}; get_first_prop(k, 5, imei2); }
if (!meid[0]) { const char *k[] = {"persist.vendor.radio.meid", "persist.radio.meid", "vendor.ril.meid", "ro.ril.oem.meid"}; get_first_prop(k, 4, meid); }
if (!meid2[0]) { const char *k[] = {"persist.vendor.radio.meid2", "persist.radio.meid2", "vendor.ril.meid2", "ro.ril.oem.meid2"}; get_first_prop(k, 4, meid2); }
add_id(KM_TAG_ATTESTATION_ID_BRAND, brand);
add_id(KM_TAG_ATTESTATION_ID_DEVICE, device);
add_id(KM_TAG_ATTESTATION_ID_PRODUCT, product);
add_id(KM_TAG_ATTESTATION_ID_SERIAL, serial);
add_id(KM_TAG_ATTESTATION_ID_IMEI, imei);
add_id(KM_TAG_ATTESTATION_ID_IMEI, imei2);
add_id(KM_TAG_ATTESTATION_ID_MEID, meid);
add_id(KM_TAG_ATTESTATION_ID_MEID, meid2);
add_id(KM_TAG_ATTESTATION_ID_MANUFACTURER, manufacturer);
add_id(KM_TAG_ATTESTATION_ID_MODEL, model);
if (!brand[0] || !device[0] || !product[0] || !serial[0] || !manufacturer[0] || !model[0]) {
fprintf(stderr, "Missing required IDs (brand/device/product/serial/manufacturer/model).\n");
return 1;
}
printf("Device IDs to provision (%d entries):\n", g_id_count);
for (int i = 0; i < g_id_count; ++i) {
printf(" %-14s = \"%s\"\n", tag_name(g_ids[i].tag), g_ids[i].value);
}
printf("\n");
cmd = CMD_PROVISION_DEVICE_IDS;
memcpy(req, &cmd, 4);
cbor_len = build_cbor_payload(req + 4, sizeof(req) - 4);
if (cbor_len < 0) {
fprintf(stderr, "Failed to build CBOR payload.\n");
return 1;
}
req_len = 4 + cbor_len;
printf("Command: 0x%04X (%d bytes cmd_id + %d bytes CBOR = %d total)\n",
CMD_PROVISION_DEVICE_IDS, 4, cbor_len, req_len);
printf("CBOR payload (%d bytes):\n", cbor_len);
hexdump(req + 4, cbor_len);
printf("\nFull command buffer:\n");
hexdump(req, req_len);
if (dry) {
printf("\nDry run: no TA commands sent.\n");
return 0;
}
printf("\nLoading QSEECom...\n");
if (load_qseecom() != 0) {
return 1;
}
printf("Loaded QSEEComAPI: %s\n", g_loaded_lib_path);
wait_listeners();
printf("Starting TA \"%s\" from \"%s\"...\n", ta_name, ta_path);
ret = start_ta(&h, ta_path, ta_name);
if (ret != 0 || !h) {
fprintf(stderr, "QSEECom_start_app failed: %d\n", ret);
return 1;
}
printf("Running keymaster pre-sequence: GET_VERSION (0x200)...\n");
ret = km_get_version(h, &ver);
if (ret != 0) {
fprintf(stderr, "GET_VERSION failed: %d\n", ret);
return 1;
}
printf(" TA API: %u.%u TA: %u.%u\n", ver.ta_api_major, ver.ta_api_minor, ver.ta_major, ver.ta_minor);
printf("Running keymaster pre-sequence: SET_VERSION (0x207)...\n");
ret = km_set_version(h);
if (ret != 0) {
fprintf(stderr, "SET_VERSION failed: %d\n", ret);
return 1;
}
printf("Sending PROVISION_DEVICE_IDS (0x220A)...\n");
ret = send_km_response(h, req, (uint32_t)req_len, &rsp);
if (ret != 0) {
fprintf(stderr, "PROVISION_DEVICE_IDS transport failed: %d\n", ret);
return 1;
}
printf("\nResponse:\n");
printf(" Status: %d (0x%08X)%s\n", rsp.status, (uint32_t)rsp.status, rsp.status == 0 ? " - SUCCESS" : " - FAILED");
printf(" Data len: %u\n", rsp.data_len);
if (rsp.data_len > 0 && rsp.data_len <= 64) {
printf(" Data: ");
hexdump(rsp.data, (int)rsp.data_len);
}
if (rsp.status != 0) {
fprintf(stderr, "\nProvisioning failed with status %d.\n", rsp.status);
return 1;
}
cmd = CMD_SET_PROVISIONING_DEVICE_ID_SUCCESS;
printf("Sending device-id success marker (0x2218)...\n");
ret = send_km_status(h, &cmd, 4);
if (ret != 0) {
fprintf(stderr, "SET_PROVISIONING_DEVICE_ID_SUCCESS failed: %d\n", ret);
return 1;
}
qseecom_shutdown_app(&h);
dlclose(g_qseecom_lib);
printf("\nProvisioning flow finished successfully.\n");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment