From 9ef2c4a87d6fa816639b610313b67b98e983388a Mon Sep 17 00:00:00 2001 From: Cameron Katri Date: Thu, 16 Jun 2022 10:17:00 -0400 Subject: [PATCH] Add support for new version 2 trustcaches Thanks to Linus Henze for reversing the new format https://gist.github.com/LinusHenze/4cd5d7ef057a144cda7234e2c247c056 --- .gitignore | 2 ++ README.txt | 11 ++++++----- append.c | 34 +++++++++++++++++++++++++++++----- cache_from_tree.c | 6 ++++++ create.c | 22 ++++++++++++++++++---- info.c | 40 ++++++++++++++++++++++++++++++++++++---- remove.c | 7 +++++++ trustcache.1 | 7 ++++--- trustcache.c | 6 ++++++ trustcache.h | 11 ++++++++++- 10 files changed, 124 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 2ee8367..9e8345a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ a.out *.bin trustcache .vscode +*.tc +linux-sysroot diff --git a/README.txt b/README.txt index 12da2b3..3f81ef8 100644 --- a/README.txt +++ b/README.txt @@ -28,15 +28,16 @@ DESCRIPTION Create a trustcache at outfile. Each Mach-O found in the specified inputs will be scanned for a code signature and hashed. Any malformed or unsigned Mach-O will be ignored. Each slice of - a FAT binary will have its hash included. Versions 0 and 1 are - supported, if not specified, 1 is assumed. If uuid is specified, - that will be used instead of a randomly generated one. + a FAT binary will have its hash included. Versions 0, 1, and 2 + are supported, if not specified, 1 is assumed. If uuid is + specified, that will be used instead of a randomly generated one. info [-c] [-h] [-e entrynum] file Print information about file. The output for each hash will be - in the format: + in one of these formats: [] + [] [] If the -c is given, only the hashes will be printed. If -h is given, only the header will be printed. If entrynum is @@ -57,4 +58,4 @@ HISTORY The trustcache utility was written by Cameron Katri . -FreeBSD 14.0-CURRENT May 19, 2022 FreeBSD 14.0-CURRENT +FreeBSD 14.0-CURRENT June 16, 2022 FreeBSD 14.0-CURRENT diff --git a/append.c b/append.c index 5bc468d..fc0cb85 100644 --- a/append.c +++ b/append.c @@ -54,9 +54,10 @@ tcappend(int argc, char **argv) uuid_t uuid; const char *errstr = NULL; uint8_t flags = 0; + uint16_t category = 0; int ch; - while ((ch = getopt(argc, argv, "u:f:")) != -1) { + while ((ch = getopt(argc, argv, "u:f:c:")) != -1) { switch (ch) { case 'u': if (strlen(optarg) == 1 && *optarg == '0') { @@ -75,6 +76,13 @@ tcappend(int argc, char **argv) exit(1); } break; + case 'c': + category = strtonum(optarg, 0, UINT16_MAX, &errstr); + if (errstr != NULL) { + fprintf(stderr, "category number is %s: %s\n", errstr, optarg); + exit(1); + } + break; } } @@ -98,10 +106,14 @@ tcappend(int argc, char **argv) append.hashes = calloc(1, sizeof(trust_cache_hash0)); for (size_t j = 0; j < CS_CDHASH_LEN; j++) sscanf(argv[i] + 2 * j, "%02hhx", &append.hashes[0][j]); - } else { + } else if (append.version == 1) { append.entries = calloc(1, sizeof(struct trust_cache_entry1)); for (size_t j = 0; j < CS_CDHASH_LEN; j++) sscanf(argv[i] + 2 * j, "%02hhx", &append.entries[0].cdhash[j]); + } else if (append.version == 2) { + append.entries2 = calloc(1, sizeof(struct trust_cache_entry2)); + for (size_t j = 0; j < CS_CDHASH_LEN; j++) + sscanf(argv[i] + 2 * j, "%02hhx", &append.entries2[0].cdhash[j]); } } else { append = cache_from_tree(argv[i], cache.version); @@ -122,15 +134,27 @@ tcappend(int argc, char **argv) cache.entries[cache.num_entries + j].flags = flags != 0 ? flags : append.entries[j].flags; memcpy(cache.entries[cache.num_entries + j].cdhash, append.entries[j].cdhash, CS_CDHASH_LEN); } + } else if (append.version == 2) { + if ((cache.entries2 = realloc(cache.entries, sizeof(struct trust_cache_entry2) * + (cache.num_entries + append.num_entries))) == NULL) + exit(1); + for (uint32_t j = 0; j < append.num_entries; j++) { + cache.entries2[cache.num_entries + j].hash_type = append.entries[j].hash_type; + cache.entries2[cache.num_entries + j].flags = flags != 0 ? flags : append.entries[j].flags; + cache.entries2[cache.num_entries + j].category = category != 0 ? category : append.entries2[j].category; + memcpy(cache.entries2[cache.num_entries + j].cdhash, append.entries2[j].cdhash, CS_CDHASH_LEN); + } } free(append.hashes); cache.num_entries += append.num_entries; } - if (cache.version == 1) - qsort(cache.entries, cache.num_entries, sizeof(*cache.entries), ent_cmp); - else if (cache.version == 0) + if (cache.version == 0) qsort(cache.hashes, cache.num_entries, sizeof(*cache.hashes), hash_cmp); + else if (cache.version == 1) + qsort(cache.entries, cache.num_entries, sizeof(*cache.entries), ent_cmp); + else if (cache.version == 2) + qsort(cache.entries, cache.num_entries, sizeof(*cache.entries2), ent_cmp); switch (keepuuid) { case 0: diff --git a/cache_from_tree.c b/cache_from_tree.c index 610653c..723fbac 100644 --- a/cache_from_tree.c +++ b/cache_from_tree.c @@ -60,6 +60,12 @@ tccallback(const char *path, const struct stat *sb, __attribute__((unused)) int cache.entries[cache.num_entries].hash_type = c.h[i].hash_type; cache.entries[cache.num_entries].flags = 0; memcpy(cache.entries[cache.num_entries].cdhash, c.h[i].cdhash, CS_CDHASH_LEN); + } else if (cache.version == 2) { + if ((cache.entries2 = realloc(cache.entries, sizeof(struct trust_cache_entry2) * (cache.num_entries + 1))) == NULL) + exit(1); + cache.entries2[cache.num_entries].hash_type = c.h[i].hash_type; + cache.entries2[cache.num_entries].category = 0; + memcpy(cache.entries2[cache.num_entries].cdhash, c.h[i].cdhash, CS_CDHASH_LEN); } cache.num_entries++; } diff --git a/create.c b/create.c index c88abc4..7c73a05 100644 --- a/create.c +++ b/create.c @@ -56,7 +56,7 @@ tccreate(int argc, char **argv) fprintf(stderr, "Failed to parse %s as a UUID\n", optarg); break; case 'v': - if (strlen(optarg) != 1 || (optarg[0] != '0' && optarg[0] != '1')) { + if (strlen(optarg) != 1 || (optarg[0] != '0' && optarg[0] != '1' && optarg[0] != '2')) { fprintf(stderr, "Unsupported trustcache version %s\n", optarg); return 1; } @@ -64,6 +64,8 @@ tccreate(int argc, char **argv) cache.version = 0; else if (optarg[0] == '1') cache.version = 1; + else if (optarg[0] == '2') + cache.version = 2; break; } } @@ -92,15 +94,27 @@ tccreate(int argc, char **argv) cache.entries[cache.num_entries + j].flags = append.entries[j].flags; memcpy(cache.entries[cache.num_entries + j].cdhash, append.entries[j].cdhash, CS_CDHASH_LEN); } + } else if (append.version == 2) { + if ((cache.entries2 = realloc(cache.entries2, sizeof(struct trust_cache_entry2) * + (cache.num_entries + append.num_entries))) == NULL) + exit(1); + for (uint32_t j = 0; j < append.num_entries; j++) { + cache.entries2[cache.num_entries + j].hash_type = append.entries2[j].hash_type; + cache.entries2[cache.num_entries + j].flags = append.entries2[j].flags; + cache.entries2[cache.num_entries + j].category = append.entries2[j].category; + memcpy(cache.entries2[cache.num_entries + j].cdhash, append.entries2[j].cdhash, CS_CDHASH_LEN); + } } free(append.hashes); cache.num_entries += append.num_entries; } - if (cache.version == 1) - qsort(cache.entries, cache.num_entries, sizeof(*cache.entries), ent_cmp); - else if (cache.version == 0) + if (cache.version == 0) qsort(cache.hashes, cache.num_entries, sizeof(*cache.hashes), hash_cmp); + else if (cache.version == 1) + qsort(cache.entries, cache.num_entries, sizeof(*cache.entries), ent_cmp); + else if (cache.version == 2) + qsort(cache.entries, cache.num_entries, sizeof(*cache.entries2), ent_cmp); if (writetrustcache(cache, argv[0]) == -1) return 1; diff --git a/info.c b/info.c index 4f74d62..b4249b1 100644 --- a/info.c +++ b/info.c @@ -77,8 +77,10 @@ tcinfo(int argc, char **argv) for (uint32_t i = 0; i < cache.num_entries; i++) { if (cache.version == 0) print_hash(cache.hashes[i], true); - else + else if (cache.version == 1) print_hash(cache.entries[i].cdhash, true); + else if (cache.version == 2) + print_hash(cache.entries2[i].cdhash, true); } goto done; } @@ -87,10 +89,12 @@ tcinfo(int argc, char **argv) fprintf(stderr, "no entry %i\n", entrynum); exit(1); } - if (cache.version == 1) { - print_entry(cache.entries[entrynum - 1]); - } else if (cache.version == 0) { + if (cache.version == 0) { print_hash(cache.hashes[entrynum - 1], true); + } else if (cache.version == 1) { + print_entry(cache.entries[entrynum - 1]); + } else if (cache.version == 2) { + print_entry2(cache.entries2[entrynum - 1]); } } else { print_entries(cache); @@ -121,6 +125,8 @@ print_entries(struct trust_cache cache) print_hash(cache.hashes[i], true); else if (cache.version == 1) print_entry(cache.entries[i]); + else if (cache.version == 2) + print_entry2(cache.entries2[i]); } } @@ -150,6 +156,32 @@ print_entry(struct trust_cache_entry1 entry) printf("[%i]\n", entry.hash_type); } +void +print_entry2(struct trust_cache_entry2 entry) +{ + print_hash(entry.cdhash, false); + + switch (entry.flags) { + case CS_TRUST_CACHE_AMFID: + printf(" CS_TRUST_CACHE_AMFID "); + break; + case CS_TRUST_CACHE_ANE: + printf(" CS_TRUST_CACHE_ANE "); + break; + case CS_TRUST_CACHE_AMFID|CS_TRUST_CACHE_ANE: + printf(" CS_TRUST_CACHE_AMFID|CS_TRUST_CACHE_ANE "); + break; + case 0: + printf(" [none] "); + break; + default: + printf(" [%i] ", entry.flags); + break; + } + + printf("[%i] [%i]\n", entry.hash_type, entry.category); +} + void print_hash(uint8_t cdhash[CS_CDHASH_LEN], bool newline) { diff --git a/remove.c b/remove.c index 5f31848..4f30f8a 100644 --- a/remove.c +++ b/remove.c @@ -89,6 +89,13 @@ tcremove(int argc, char **argv) numremoved++; continue; } + } else if (cache.version == 2) { + if (memcmp(cache.entries2[j].cdhash, hash, CS_CDHASH_LEN) == 0) { + memmove(&cache.entries2[j], &cache.entries2[j + 1], (cache.num_entries - j - 1) * sizeof(struct trust_cache_entry2)); + cache.num_entries--; + numremoved++; + continue; + } } j++; } diff --git a/trustcache.1 b/trustcache.1 index 68ec6e5..f5aaf5f 100644 --- a/trustcache.1 +++ b/trustcache.1 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 19, 2022 +.Dd June 16, 2022 .Dt TRUSTCACHE 1 .Os .Sh NAME @@ -95,7 +95,7 @@ Each Mach-O found in the specified inputs will be scanned for a code signature and hashed. Any malformed or unsigned Mach-O will be ignored. Each slice of a FAT binary will have its hash included. -Versions 0 and 1 are supported, if not specified, 1 is assumed. +Versions 0, 1, and 2 are supported, if not specified, 1 is assumed. If .Ar uuid is specified, that will be used instead of a randomly generated one. @@ -108,9 +108,10 @@ is specified, that will be used instead of a randomly generated one. .Xc Print information about .Ar file . -The output for each hash will be in the format: +The output for each hash will be in one of these formats: .Pp .Dl [] +.Dl [] [] .Pp If the .Fl c diff --git a/trustcache.c b/trustcache.c index cb6c7b0..f9de404 100644 --- a/trustcache.c +++ b/trustcache.c @@ -91,6 +91,10 @@ opentrustcache(const char *path) if ((cache.entries = calloc(cache.num_entries, sizeof(struct trust_cache_entry1))) == NULL) exit(EX_OSERR); fread(cache.entries, sizeof(struct trust_cache_entry1), cache.num_entries, f); + } else if (cache.version == 2) { + if ((cache.entries = calloc(cache.num_entries, sizeof(struct trust_cache_entry2))) == NULL) + exit(EX_OSERR); + fread(cache.entries, sizeof(struct trust_cache_entry2), cache.num_entries, f); } else { fprintf(stderr, "%s: Unsupported version %i\n", path, cache.version); exit(1); @@ -120,6 +124,8 @@ writetrustcache(struct trust_cache cache, const char *path) fwrite(&cache.hashes[i], sizeof(trust_cache_hash0), 1, f); else if (cache.version == 1) fwrite(&cache.entries[i], sizeof(struct trust_cache_entry1), 1, f); + else if (cache.version == 2) + fwrite(&cache.entries2[i], sizeof(struct trust_cache_entry2), 1, f); } fclose(f); diff --git a/trustcache.h b/trustcache.h index 3b1696a..2ac1746 100644 --- a/trustcache.h +++ b/trustcache.h @@ -17,11 +17,18 @@ #include "machoparse/cs_blobs.h" #include "uuid/uuid.h" +struct trust_cache_entry2 { + uint8_t cdhash[CS_CDHASH_LEN]; + uint8_t hash_type; + uint8_t flags; + uint16_t category; +} __attribute__((__packed__)); + struct trust_cache_entry1 { uint8_t cdhash[CS_CDHASH_LEN]; uint8_t hash_type; uint8_t flags; -}; +} __attribute__((__packed__)); typedef uint8_t trust_cache_hash0[CS_CDHASH_LEN]; @@ -30,6 +37,7 @@ struct trust_cache { uuid_t uuid; uint32_t num_entries; union { + struct trust_cache_entry2 *entries2; struct trust_cache_entry1 *entries; trust_cache_hash0 *hashes; }; @@ -54,6 +62,7 @@ int hash_cmp(const void * vp1, const void * vp2); void print_header(struct trust_cache cache); void print_hash(uint8_t cdhash[CS_CDHASH_LEN], bool newline); void print_entry(struct trust_cache_entry1 entry); +void print_entry2(struct trust_cache_entry2 entry); void print_entries(struct trust_cache cache); #endif -- 2.47.1