Skip to content

Instantly share code, notes, and snippets.

@0xilis
Last active March 25, 2025 03:24
Show Gist options
  • Select an option

  • Save 0xilis/776d873475a5626aa612804fa9821199 to your computer and use it in GitHub Desktop.

Select an option

Save 0xilis/776d873475a5626aa612804fa9821199 to your computer and use it in GitHub Desktop.
Hacky reforming signed shortcut AEA from apple archive
struct libshortcutsign_header_info {
char *header;
int keyCount;
uint32_t fieldKeys[30];
uint32_t fieldKeyPositions[30];
uint32_t currentPos;
};
uint32_t get_aa_header_field_key(struct libshortcutsign_header_info info, uint32_t i) {
if (i >= info.keyCount) {
fprintf(stderr, "libshortcutsign: get_aa_header_field_key index %d out of bounds of %d\n", i, info.keyCount);
exit(1);
}
/* return *(info.fieldKeys + (i << 2)); */
return info.fieldKeys[i];
}
int aa_header_field_key_exists(struct libshortcutsign_header_info info, const char *key) {
uint32_t key_ugly_hack = *(uint32_t *)&key;
int keyCount = info.keyCount;
for (int i = 0; i < keyCount; i++) {
if (get_aa_header_field_key(info, i) == key_ugly_hack) {
return 1;
}
}
/* key not found */
return 0;
}
int aa_header_field_key_index_by_name(struct libshortcutsign_header_info info, const char *key) {
uint32_t key_ugly_hack = *(uint32_t *)&key;
int keyCount = info.keyCount;
for (int i = 0; i < keyCount; i++) {
if (get_aa_header_field_key(info, i) == key_ugly_hack) {
return i;
}
}
/* key not found */
return -1;
}
void *register_aa_header_field_key(struct libshortcutsign_header_info *info, const char *key, uint32_t valueSize) {
int index = aa_header_field_key_index_by_name(*info, key);
if (index == -1) {
/* Add key */
int keyCount = info->keyCount;
info->fieldKeys[keyCount] = *(uint32_t *) &key;
uint32_t currentPos = info->currentPos;
char *header = info->header;
strncpy(header + currentPos, key, 4);
info->fieldKeyPositions[keyCount] = currentPos;
info->keyCount = keyCount + 1;
uint32_t valuePos = currentPos + 4;
currentPos = valueSize + valuePos;
info->currentPos = currentPos;
/* Return pointer to value of key in aa header */
struct libshortcutsign_header_info info_real = *info;
return (info->header + valuePos);
}
printf("key %s already existed\n",key);
return 0;
}
/*
* fill_aa_file_header_with_field_keys
*
* Fills the header with the field keys
* that signed shortcut files have.
*/
void fill_aa_file_header_with_field_keys(char *header, time_t currentTime) {
struct libshortcutsign_header_info info;
memset(&info, 0, sizeof(info));
info.header = header;
info.keyCount = 0;
info.currentPos = 6;
uint8_t aaEntryTypeRegularFile = 'F';
struct libshortcutsign_header_info *info_ptr = &info;
/* memcpy(register_aa_header_field_key(info_ptr, "TYP1", 1), &aaEntryTypeRegularFile, 1); */
*(uint8_t *)register_aa_header_field_key(info_ptr, "TYP1", 1) = 'F';
void *patp_ptr = register_aa_header_field_key(info_ptr, "PATP", 16);
*(*(uint8_t **) &patp_ptr) = 14;
strcpy(patp_ptr + 2, "Shortcut.wflow");
uint32_t filePermMode = 0x1a4;
memcpy(register_aa_header_field_key(info_ptr, "MOD2", 2), &filePermMode, 2);
register_aa_header_field_key(info_ptr, "FLG1", 1);
/* use currentTime for creation and modification time */
memcpy(register_aa_header_field_key(info_ptr, "CTMT", 12), &currentTime, 4);
memcpy(register_aa_header_field_key(info_ptr, "MTMT", 12), &currentTime, 4);
register_aa_header_field_key(info_ptr, "DATA", 2);
}
/*
* fill_aa_dir_header_with_field_keys
*
* Fills the header with the field keys
* that signed shortcut directories have.
*/
void fill_aa_dir_header_with_field_keys(char *header, time_t currentTime) {
struct libshortcutsign_header_info info;
memset(&info, 0, sizeof(info));
info.header = header;
info.keyCount = 0;
info.currentPos = 6;
uint8_t aaEntryTypeRegularFile = 'D';
struct libshortcutsign_header_info *info_ptr = &info;
*(uint8_t *)register_aa_header_field_key(info_ptr, "TYP1", 1) = 'D';
register_aa_header_field_key(info_ptr, "PATP", 2);
uint32_t filePermMode = 0x1ed;
memcpy(register_aa_header_field_key(info_ptr, "MOD2", 2), &filePermMode, 2);
register_aa_header_field_key(info_ptr, "FLG1", 1);
/* use currentTime for creation and modification time */
memcpy(register_aa_header_field_key(info_ptr, "CTMT", 12), &currentTime, 4);
memcpy(register_aa_header_field_key(info_ptr, "MTMT", 12), &currentTime, 4);
}
unsigned char *create_shortcuts_apple_archive(const char *unsignedShortcutPath, size_t *sz) {
size_t unsignedShortcutSize = get_binary_size(unsignedShortcutPath);
time_t currentDate = time(NULL);
char *defaultFileHeader = malloc(82);
uint32_t magic = 0x31304141; /* AA01 */
memset(defaultFileHeader, 0, 82);
memcpy(defaultFileHeader, &magic, 4);
unsigned short aaHeaderSize;
fill_aa_file_header_with_field_keys(defaultFileHeader, currentDate);
if (unsignedShortcutSize > USHRT_MAX) {
memcpy(defaultFileHeader + 78, &unsignedShortcutSize, 4);
defaultFileHeader[77] = 'B';
aaHeaderSize = 82;
} else {
/* Shortcuts smaller than USHRT_MAX are DATA not DATB */
memcpy(defaultFileHeader + 78, &unsignedShortcutSize, 2);
defaultFileHeader[77] = 'A';
aaHeaderSize = 80;
}
memcpy(defaultFileHeader + 4, &aaHeaderSize, 2);
size_t appleArchiveSize = aaHeaderSize + 60 + unsignedShortcutSize;
char *appleArchive = malloc(appleArchiveSize);
memcpy(appleArchive, &magic, 4);
appleArchive[4] = 60;
fill_aa_dir_header_with_field_keys(appleArchive, currentDate);
memcpy(appleArchive + 60, defaultFileHeader, aaHeaderSize);
free(defaultFileHeader);
char *unsignedShortcut = load_binary(unsignedShortcutPath);
memcpy(appleArchive + 60 + aaHeaderSize, unsignedShortcut, unsignedShortcutSize);
free(unsignedShortcut);
if (sz) {
*sz = 60 + aaHeaderSize + unsignedShortcutSize;
}
return *(uint8_t **)&appleArchive;
}
#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonCryptor.h>
#include <compression.h>
#import <CommonCrypto/CommonHMAC.h>
#import <CommonCrypto/CommonDigest.h>
void dhexPrint(uint8_t *buf, size_t sz) {
printf("buf: ");
int byteOfLine = 0;
for (int i = 0; i < sz; i++) {
printf("%02x",buf[i]);
byteOfLine++;
if (byteOfLine == 16) {
byteOfLine = 0;
printf("\n");
}
}
printf("\n\nEND\n");
}
/* External CommonCrypto headers */
/* available macOS 10.15, iOS 13.0+ */
#ifndef _CC_RSACRYPTOR_H_
enum {
kCCDigestNone = 0,
kCCDigestSHA1 = 8,
kCCDigestSHA224 = 9,
kCCDigestSHA256 = 10,
kCCDigestSHA384 = 11,
kCCDigestSHA512 = 12,
};
typedef uint32_t CCDigestAlgorithm;
enum {
ccRSAKeyPublic = 0,
ccRSAKeyPrivate = 1
};
typedef uint32_t CCRSAKeyType;
enum {
ccPKCS1Padding = 1001,
ccOAEPPadding = 1002,
ccRSAPSSPadding = 1005
};
typedef uint32_t CCAsymmetricPadding;
typedef struct CCKDFParameters *CCKDFParametersRef;
#endif
enum {
kCCKDFAlgorithmPBKDF2_HMAC = 0,
kCCKDFAlgorithmCTR_HMAC,
kCCKDFAlgorithmCTR_HMAC_FIXED,
kCCKDFAlgorithmFB_HMAC, // UNIMP
kCCKDFAlgorithmFB_HMAC_FIXED, // UNIMP
kCCKDFAlgorithmDPIPE_HMAC, // UNIMP
kCCKDFAlgorithmHKDF,
kCCKDFAlgorithmAnsiX963
};
typedef uint32_t CCKDFAlgorithm;
/*!
@function CCKDFParametersCreateHkdf
@abstract Creates a CCKDFParameters object that will hold parameters for
key derivation with HKDF as defined by RFC 5869.
@param params A CCKDFParametersRef pointer.
@param salt Salt.
@param saltLen Length of salt.
@param context Data shared between entities.
@param contextLen Length of context.
@result Possible error returns are kCCParamError and kCCMemoryFailure.
*/
CCStatus CCKDFParametersCreateHkdf(CCKDFParametersRef *params,const void *salt, size_t saltLen,const void *context, size_t contextLen);
/*!
@function CCDeriveKey
@abstract Generic key derivation function supporting multiple key
derivation algorithms.
@param params A CCKDFParametersRef with pointers to the
chosen KDF's parameters.
@param digest The digest algorithm to use.
@param keyDerivationKey The input key material to derive from.
@param keyDerivationKeyLen Length of the input key material.
@param derivedKey Output buffer for the derived key.
@param derivedKeyLen Desired length of the derived key.
@result Possible error returns are kCCParamError, kCCMemoryFailure, and
kCCUnimplemented.
*/
CCStatus CCDeriveKey(const CCKDFParametersRef params, CCDigestAlgorithm digest,const void *keyDerivationKey, size_t keyDerivationKeyLen,void *derivedKey, size_t derivedKeyLen);
void CCKDFParametersDestroy(CCKDFParametersRef params);
/* make these structs later like i should */
size_t get_binary_size(const char *signedShortcutPath) {
FILE *fp = fopen(signedShortcutPath,"r");
if (!fp) {
fprintf(stderr,"libshortcutsign: extract_signed_shortcut failed to find path\n");
return 0;
}
fseek(fp, 0, SEEK_END);
size_t binary_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
fclose(fp);
return binary_size;
}
char *load_binary(const char *signedShortcutPath) {
/* load AEA archive into memory */
FILE *fp = fopen(signedShortcutPath,"r");
if (!fp) {
fprintf(stderr,"libshortcutsign: extract_signed_shortcut failed to find path\n");
return 0;
}
fseek(fp, 0, SEEK_END);
size_t binary_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *aeaShortcutArchive = malloc(binary_size * sizeof(char));
/* copy bytes to binary, byte by byte... */
int c;
size_t n = 0;
while ((c = fgetc(fp)) != EOF) {
aeaShortcutArchive[n++] = (char) c;
}
fclose(fp);
return aeaShortcutArchive;
}
void *do_hkdf(void *context, size_t contextLen, void *key) {
void *derivedKey = malloc(0x100);
CCKDFParametersRef p;
CCKDFParametersCreateHkdf(&p, 0, 0, context, contextLen);
CCDeriveKey(p, kCCDigestSHA256, key, 32, derivedKey, 32);
CCKDFParametersDestroy(p);
return derivedKey;
}
void *hmac_derive(void *hkdf_key, void *data1, size_t data1Len, void *data2, size_t data2Len) {
void *hmac = malloc(0x2800);
CCHmacContext context;
CCHmacInit(&context, kCCHmacAlgSHA256, hkdf_key, 32);
CCHmacUpdate(&context, data2, data2Len);
CCHmacUpdate(&context, data1, data1Len);
CCHmacUpdate(&context, &data2Len, 8);
CCHmacFinal(&context, hmac);
/* memset_s(&arg0[9], 224, 0, 224);
memset_s(&context, 384, 0, 384); */
return hmac;
}
void resign_shortcut_prolouge(char *aeaShortcutArchive, void *privateKey) {
/*
* For right now, I just resign the prolouge in a shell script
* Then call copy_signature_to_shortcut
*/
printf("implement later\n");
}
void resign_shortcut_with_new_aa(const char *signedShortcutPath, const char *archivedDirectoryPath, const char *outputPath, void *privateKey) {
char *aeaShortcutArchive = load_binary(signedShortcutPath);
if (!aeaShortcutArchive) {
return;
}
size_t archivedDirSize = get_binary_size(archivedDirectoryPath);
size_t compressed_size = archivedDirSize * 2;
uint8_t *buffer = malloc(compressed_size);
void *archivedDir = load_binary(archivedDirectoryPath);
compressed_size = compression_encode_buffer(buffer, compressed_size, archivedDir, archivedDirSize, nil, COMPRESSION_LZFSE);
free(archivedDir);
if (!buffer) {
fprintf(stderr,"libshortcutsign: failed to compress LZFSE\n");
exit(1);
}
/* find the size of AEA_CONTEXT_FIELD_AUTH_DATA field blob */
/* We assume it's located at 0x8-0xB */
register const char *sptr = aeaShortcutArchive + 0xB;
size_t auth_data_size = *sptr << 24;
auth_data_size += *(sptr - 1) << 16;
auth_data_size += *(sptr - 2) << 8;
auth_data_size += *(sptr - 3);
/*
* fix auth_data_size+0xec and, auth_data_size+0x13c
* with the archivedDirSize
*/
memcpy(aeaShortcutArchive + auth_data_size + 0xec, &archivedDirSize, 4);
memcpy(aeaShortcutArchive + auth_data_size + 0x13c, &archivedDirSize, 4);
/* Set compressed LZFSE */
aeaShortcutArchive = realloc(aeaShortcutArchive, auth_data_size + 0x495c + compressed_size);
memcpy(aeaShortcutArchive + auth_data_size + 0x495c, buffer, compressed_size);
free(buffer);
CCKDFParametersRef p;
const void *salt = aeaShortcutArchive + auth_data_size + 0xac;
/* hardcoding this is bad, will not be at right offset for non SELF_SIGNED */
size_t contextLen = 0x4c;
void *context = malloc(0x4c);
memcpy(context, "AEA_AMK", 7);
memset(context + 7, 0, 4);
memcpy(context + 11, privateKey, 0x41);
CCStatus hkdfCreateError = CCKDFParametersCreateHkdf(&p, salt, 32, context, contextLen);
if (!hkdfCreateError) {
printf("CCKDFParametersCreateHkdf success\n");
const void *keyDerivationKey = aeaShortcutArchive + auth_data_size + 0x8c;
/* derivedKey will be filled */
void *derivedKey = malloc(0x100);
CCStatus deriveKeyError = CCDeriveKey(p, kCCDigestSHA256, keyDerivationKey, 32, derivedKey, 32);
if (!deriveKeyError) {
printf("derivedKey: \n");
dhexPrint(derivedKey, 64);
/* we got the hkdf 256bit prolouge key */
/*
* before doing hmac, update the size in prolouge
*/
memcpy(aeaShortcutArchive + auth_data_size + 0x13c + 4, &compressed_size, 4);
size_t resigned_shortcut_size = auth_data_size + 0x495c + compressed_size;
memcpy(aeaShortcutArchive + auth_data_size + 0xec + 8, &resigned_shortcut_size, 4);
void *aea_ck_ctx = malloc(10);
memcpy(aea_ck_ctx, "AEA_CK", 6);
memset(aea_ck_ctx + 6, 0, 4);
void *aea_ck = do_hkdf(aea_ck_ctx, 10, derivedKey);
free(aea_ck_ctx);
printf("aea_ck: \n");
dhexPrint(aea_ck, 64);
void *aea_sk_ctx = malloc(10);
memcpy(aea_sk_ctx, "AEA_SK", 6);
memset(aea_sk_ctx + 6, 0, 4);
void *aea_sk = do_hkdf(aea_sk_ctx, 10, aea_ck);
free(aea_sk_ctx);
printf("aea_sk: \n");
dhexPrint(aea_sk, 64);
void *hmac = hmac_derive(aea_sk, aeaShortcutArchive + auth_data_size + 0x495c, compressed_size, 0, 0);
//printf("compressed_size: %zx\n",compressed_size);
/*
* replace old hmac with new in binary
*
*/
dhexPrint(hmac, 64);
memcpy(aeaShortcutArchive + auth_data_size + 0x295c, hmac, 0x2000);
free(hmac);
free(aea_sk);
/*
* re-hmac for AEA_CHEK
*/
void *aea_chek_ctx = malloc(8);
memcpy(aea_chek_ctx, "AEA_CHEK", 8);
void *aea_chek = do_hkdf(aea_chek_ctx, 8, aea_ck);
free(aea_chek_ctx);
free(aea_ck);
printf("aea_chek: \n");
dhexPrint(aea_chek, 64);
hmac = hmac_derive(aea_chek, aeaShortcutArchive + auth_data_size + 0x13c, 0x2800, aeaShortcutArchive + auth_data_size + 0x293c, 0x2020);
dhexPrint(hmac, 64);
memcpy(aeaShortcutArchive + auth_data_size + 0x11c, hmac, 32);
free(hmac);
/* re-hmac for AEA_RHEK */
free(aea_chek);
void *aea_rhek_ctx = malloc(8);
memcpy(aea_rhek_ctx, "AEA_RHEK", 8);
void *aea_rhek = do_hkdf(aea_rhek_ctx, 8, derivedKey);
free(aea_rhek_ctx);
printf("aea_rhek: \n");
dhexPrint(aea_rhek, 32);
void *chekPlusAuthData = malloc(auth_data_size + 32);
memcpy(chekPlusAuthData, aeaShortcutArchive + auth_data_size + 0x11c, 32);
memcpy(chekPlusAuthData + 32, aeaShortcutArchive + 0xc, auth_data_size);
hmac = hmac_derive(aea_rhek, aeaShortcutArchive + auth_data_size + 0xec, 0x30, chekPlusAuthData, 0x59f);
free(chekPlusAuthData);
dhexPrint(hmac, 64);
memcpy(aeaShortcutArchive + auth_data_size + 0xcc, hmac, 32);
free(hmac);
free(aea_rhek);
} else {
printf("deriveKeyError\n");
}
free(derivedKey);
}
if (p) {
CCKDFParametersDestroy(p);
}
free(context);
/* write aeaShortcutArchive to file */
resign_shortcut_prolouge(aeaShortcutArchive, privateKey);
FILE *fp = fopen(outputPath, "w");
if (!fp) {
free(aeaShortcutArchive);
fprintf(stderr,"libshortcutsign: resign_shortcut_prolouge failed to open destPath\n");
exit(1);
}
fwrite(aeaShortcutArchive, auth_data_size + 0x495c + compressed_size, 1, fp);
fclose(fp);
free(aeaShortcutArchive);
}
void copy_signature_to_shortcut(const char *signedShortcutPath, void *signature, const char *outputPath) {
char *aeaShortcutArchive = load_binary(signedShortcutPath);
if (!aeaShortcutArchive) {
return;
}
/* find the size of AEA_CONTEXT_FIELD_AUTH_DATA field blob */
/* We assume it's located at 0x8-0xB */
register const char *sptr = aeaShortcutArchive + 0xB;
size_t auth_data_size = *sptr << 24;
auth_data_size += *(sptr - 1) << 16;
auth_data_size += *(sptr - 2) << 8;
auth_data_size += *(sptr - 3);
memcpy(aeaShortcutArchive + auth_data_size + 0xc, signature, 72);
FILE *fp = fopen(outputPath, "w");
if (!fp) {
free(aeaShortcutArchive);
fprintf(stderr,"libshortcutsign: resign_shortcut_prolouge failed to open destPath\n");
exit(1);
}
fwrite(aeaShortcutArchive, get_binary_size(signedShortcutPath), 1, fp);
fclose(fp);
free(aeaShortcutArchive);
}
void extract_apple_archive_of_shortcut(const char *signedShortcutPath, const char *outputPath) {
char *aeaShortcutArchive = load_binary(signedShortcutPath);
if (!aeaShortcutArchive) {
fprintf(stderr,"extract_apple_archive_of_shortcut: extraction aa fail\n");
exit(1);
}
printf("extract_apple_archive_of_shortcut: starting extracting...\n");
/* find the size of AEA_CONTEXT_FIELD_AUTH_DATA field blob */
/* We assume it's located at 0x8-0xB */
register const char *sptr = aeaShortcutArchive + 0xB;
size_t auth_data_size = *sptr << 24;
auth_data_size += *(sptr - 1) << 16;
auth_data_size += *(sptr - 2) << 8;
auth_data_size += *(sptr - 3);
uint8_t *encoded_buf = (uint8_t *)((aeaShortcutArchive + auth_data_size + 0x495c));
size_t decode_size = 0x100000;
uint8_t *apple_archive = malloc(decode_size);
if (!apple_archive) {
free(aeaShortcutArchive);
fprintf(stderr,"extract_apple_archive_of_shortcut: cannot malloc apple_archive\n");
exit(1);
}
size_t compressed_size = *((uint32_t *)((aeaShortcutArchive + auth_data_size + 0x13c + 4)));
decode_size = compression_decode_buffer(apple_archive, decode_size, encoded_buf, compressed_size, nil, COMPRESSION_LZFSE);
free(aeaShortcutArchive);
if (!apple_archive) {
free(apple_archive);
fprintf(stderr,"extract_apple_archive_of_shortcut: error fail\n");
exit(1);
}
FILE *fp = fopen(outputPath, "w");
if (!fp) {
free(apple_archive);
fprintf(stderr,"extract_apple_archive_of_shortcut: outputPath fail\n");
exit(1);
}
fwrite(apple_archive, decode_size, 1, fp);
fclose(fp);
free(apple_archive);
printf("done with extract_apple_archive_of_shortcut\n");
return;
}
# Script for re-signing prolouge of reformed aea file with your private key
# In a hex editor, go to auth_data_size+0xc, and 0 out the signature.
# Also, delete auth_data_size+0x13c and everything after, so the file should be auth_data_size+0x13c size.
# Then, shasum -a 256 on the file
# Then echo 'sha256hashhere' | xxd -ps -r >> resigned_hash.bin
# Place it in the same directory as this script, and cd to the directory of this script.
# Make sure to place ShortcutsSigningPrivateKey.pem and ShortcutsSigningPublicKey.pem here
# Then run script
# Sign
openssl pkeyutl -sign -inkey ShortcutsSigningPrivateKey.pem -in resigned_hash.bin -out resigned_sig.bin
# Check signature
openssl pkeyutl -in resigned_hash.bin -inkey ShortcutsSigningPublicKey.pem -pubin -verify -sigfile resigned_sig.bin
@0xilis
Copy link
Author

0xilis commented Apr 19, 2024

Also FYI I don't recommend using this as-is for anything, this was really just as a PoC; I'll likely implement this into libshortcutsign now however.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment