Skip to content

Instantly share code, notes, and snippets.

@0xilis
Last active April 20, 2024 00:58
Show Gist options
  • Save 0xilis/776d873475a5626aa612804fa9821199 to your computer and use it in GitHub Desktop.
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

Note: You'll have to dump your own shortcuts private key and auth data. Keep in mind this doesn't reform the auth data so you need to have the signed shortcut you're inheriting the key from use the same signing private key as the one that complies with the auth data; if you really need, just edit the existing signed shortcut that resign_shortcut_with_new_aa uses to swap the auth data with your own. I do have some hacky stuff for dumping signing keys and auth data here: https://github.com/0xilis/QMCDumper-Simulator

@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