Skip to content

Instantly share code, notes, and snippets.

@MCJack123
Last active February 6, 2022 14:50
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MCJack123/943eaca762730ca4b7ae460b731b68e7 to your computer and use it in GitHub Desktop.
Save MCJack123/943eaca762730ca4b7ae460b731b68e7 to your computer and use it in GitHub Desktop.
A tool to read/write BSD/macOS chunklist files, such as BaseSystem.dmg.chunklist. (Signing is not supported yet.)
/* Requires C99 or C++14 or later */
/* Chunklist file format */
#include <stdint.h>
#define CHUNKLIST_MAGIC 0x4C4B4E43
#define CHUNKLIST_FILE_VERSION_10 1
#define CHUNKLIST_CHUNK_METHOD_10 1
#define CHUNKLIST_SIGNATURE_METHOD_10 1
#define CHUNKLIST_SIG_LEN 256
#define CHUNKLIST_PUBKEY_LEN (2048/8)
#define SHA256_DIGEST_LENGTH 32
struct chunklist_hdr {
uint32_t cl_magic;
uint32_t cl_header_size;
uint8_t cl_file_ver;
uint8_t cl_chunk_method;
uint8_t cl_sig_method;
uint8_t __unused1;
uint64_t cl_chunk_count;
uint64_t cl_chunk_offset;
uint64_t cl_sig_offset;
} __attribute__((packed));
struct chunklist_chunk {
uint32_t chunk_size;
uint8_t chunk_sha256[SHA256_DIGEST_LENGTH];
} __attribute__((packed));
#define strequ(s, t) strcmp(s, t) == 0
#include "sha256.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
int main(int argc, const char * argv[]) {
if (argc < 4) {
printf("Usage: %s <create|check> <chunklist> <file> [-v]\n", argv[0]);
return -1;
}
int verbose = argc > 4;
if (strequ(argv[1], "check")) {
FILE * cl = fopen(argv[2], "r");
if (cl == NULL) {
printf("Error: %s could not be opened.\n", argv[2]);
return -2;
}
struct chunklist_hdr header;
fread(&header, sizeof(struct chunklist_hdr), 1, cl);
if (header.cl_magic != CHUNKLIST_MAGIC) {
printf("Error: %s is not a chunklist file.\n", argv[2]);
return -3;
}
if (header.cl_file_ver != CHUNKLIST_FILE_VERSION_10 ||
header.cl_sig_method != CHUNKLIST_SIGNATURE_METHOD_10 ||
header.cl_chunk_method != CHUNKLIST_CHUNK_METHOD_10) {
printf("Error: %s is not supported.\n", argv[2]);
return -4;
}
fseek(cl, (long int)header.cl_chunk_offset, SEEK_SET);
FILE * fp = fopen(argv[3], "r");
if (fp == NULL) {
printf("Error: %s could not be opened.\n", argv[3]);
return -2;
}
for (int i = 0; i < header.cl_chunk_count; i++) {
struct chunklist_chunk chunk;
fread(&chunk, sizeof(struct chunklist_chunk), 1, cl);
if (verbose) printf("Checking chunk %d (size %d)...\n", i, chunk.chunk_size);
BYTE * data = (BYTE*)malloc(chunk.chunk_size);
fread(data, chunk.chunk_size, 1, fp);
SHA256_CTX ctx;
BYTE hash[SHA256_DIGEST_LENGTH];
sha256_init(&ctx);
sha256_update(&ctx, data, chunk.chunk_size);
sha256_final(&ctx, hash);
free(data);
if (memcmp(chunk.chunk_sha256, hash, SHA256_DIGEST_LENGTH) != 0) {
printf("Verify failed.\n");
fclose(fp);
fclose(cl);
return 1;
}
}
fclose(fp);
fclose(cl);
} else if (strequ(argv[1], "create")) {
struct chunklist_hdr header;
FILE * fp = fopen(argv[3], "r");
if (fp == NULL) {
printf("Error: %s could not be opened.\n", argv[3]);
return -2;
}
header.cl_magic = CHUNKLIST_MAGIC;
header.cl_header_size = 36;
header.cl_file_ver = CHUNKLIST_FILE_VERSION_10;
header.cl_chunk_method = CHUNKLIST_CHUNK_METHOD_10;
header.cl_sig_method = CHUNKLIST_SIGNATURE_METHOD_10;
header.cl_chunk_offset = 36;
fseek(fp, 0, SEEK_END);
uint32_t file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
header.cl_chunk_count = (uint64_t)ceil(file_size / 10485760);
struct chunklist_chunk chunks[header.cl_chunk_count];
for (int i = 0; i*10485760 < file_size && !feof(fp) && i < header.cl_chunk_count; i++) {
chunks[i].chunk_size = (i == header.cl_chunk_count - 1 ? file_size % 10485760 : 10485760);
if (verbose) printf("Creating chunk %d (size %d)...\n", i, chunks[i].chunk_size);
BYTE * data = malloc(chunks[i].chunk_size);
fread(data, chunks[i].chunk_size, 1, fp);
SHA256_CTX ctx;
sha256_init(&ctx);
sha256_update(&ctx, data, chunks[i].chunk_size);
sha256_final(&ctx, chunks[i].chunk_sha256);
free(data);
}
fclose(fp);
FILE * cl = fopen(argv[2], "w");
fwrite(&header, sizeof(struct chunklist_hdr), 1, cl);
fwrite(chunks, sizeof(struct chunklist_chunk)*header.cl_chunk_count, 1, cl);
fclose(fp);
printf("Wrote chunklist to %s\n", argv[2]);
return 0;
} else {
printf("Usage: %s <create|check> <chunklist> <file> [-v]\n", argv[0]);
return -1;
}
printf("Verify succeeded.\n");
return 0;
}
/*********************************************************************
* Filename: sha256.c
* Author: Brad Conte (brad AT bradconte.com)
* Copyright:
* Disclaimer: This code is presented "as is" without any guarantees.
* Details: Implementation of the SHA-256 hashing algorithm.
SHA-256 is one of the three algorithms in the SHA2
specification. The others, SHA-384 and SHA-512, are not
offered in this implementation.
Algorithm specification can be found here:
* http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
This implementation uses little endian byte order.
*********************************************************************/
/*************************** HEADER FILES ***************************/
#include <stdlib.h>
#include <memory.h>
#include "sha256.h"
/****************************** MACROS ******************************/
#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))
#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))
/**************************** VARIABLES *****************************/
static const WORD k[64] = {
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
};
/*********************** FUNCTION DEFINITIONS ***********************/
void sha256_transform(SHA256_CTX *ctx, const BYTE data[])
{
WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
for (i = 0, j = 0; i < 16; ++i, j += 4)
m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
for ( ; i < 64; ++i)
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
a = ctx->state[0];
b = ctx->state[1];
c = ctx->state[2];
d = ctx->state[3];
e = ctx->state[4];
f = ctx->state[5];
g = ctx->state[6];
h = ctx->state[7];
for (i = 0; i < 64; ++i) {
t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
t2 = EP0(a) + MAJ(a,b,c);
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
ctx->state[0] += a;
ctx->state[1] += b;
ctx->state[2] += c;
ctx->state[3] += d;
ctx->state[4] += e;
ctx->state[5] += f;
ctx->state[6] += g;
ctx->state[7] += h;
}
void sha256_init(SHA256_CTX *ctx)
{
ctx->datalen = 0;
ctx->bitlen = 0;
ctx->state[0] = 0x6a09e667;
ctx->state[1] = 0xbb67ae85;
ctx->state[2] = 0x3c6ef372;
ctx->state[3] = 0xa54ff53a;
ctx->state[4] = 0x510e527f;
ctx->state[5] = 0x9b05688c;
ctx->state[6] = 0x1f83d9ab;
ctx->state[7] = 0x5be0cd19;
}
void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len)
{
WORD i;
for (i = 0; i < len; ++i) {
ctx->data[ctx->datalen] = data[i];
ctx->datalen++;
if (ctx->datalen == 64) {
sha256_transform(ctx, ctx->data);
ctx->bitlen += 512;
ctx->datalen = 0;
}
}
}
void sha256_final(SHA256_CTX *ctx, BYTE hash[])
{
WORD i;
i = ctx->datalen;
// Pad whatever data is left in the buffer.
if (ctx->datalen < 56) {
ctx->data[i++] = 0x80;
while (i < 56)
ctx->data[i++] = 0x00;
}
else {
ctx->data[i++] = 0x80;
while (i < 64)
ctx->data[i++] = 0x00;
sha256_transform(ctx, ctx->data);
memset(ctx->data, 0, 56);
}
// Append to the padding the total message's length in bits and transform.
ctx->bitlen += ctx->datalen * 8;
ctx->data[63] = ctx->bitlen;
ctx->data[62] = ctx->bitlen >> 8;
ctx->data[61] = ctx->bitlen >> 16;
ctx->data[60] = ctx->bitlen >> 24;
ctx->data[59] = ctx->bitlen >> 32;
ctx->data[58] = ctx->bitlen >> 40;
ctx->data[57] = ctx->bitlen >> 48;
ctx->data[56] = ctx->bitlen >> 56;
sha256_transform(ctx, ctx->data);
// Since this implementation uses little endian byte ordering and SHA uses big endian,
// reverse all the bytes when copying the final state to the output hash.
for (i = 0; i < 4; ++i) {
hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
}
}
/*********************************************************************
* Filename: sha256.h
* Author: Brad Conte (brad AT bradconte.com)
* Copyright:
* Disclaimer: This code is presented "as is" without any guarantees.
* Details: Defines the API for the corresponding SHA1 implementation.
*********************************************************************/
#ifndef SHA256_H
#define SHA256_H
/*************************** HEADER FILES ***************************/
#include <stddef.h>
/****************************** MACROS ******************************/
#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest
/**************************** DATA TYPES ****************************/
typedef unsigned char BYTE; // 8-bit byte
typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines
typedef struct {
BYTE data[64];
WORD datalen;
unsigned long long bitlen;
WORD state[8];
} SHA256_CTX;
/*********************** FUNCTION DECLARATIONS **********************/
void sha256_init(SHA256_CTX *ctx);
void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len);
void sha256_final(SHA256_CTX *ctx, BYTE hash[]);
#endif // SHA256_H
@zhangyoufu
Copy link

zhangyoufu commented Oct 21, 2019

v1_prod_pubkey = 0xC3E748CAD9CD384329E10E25A91E43E1A762FF529ADE578C935BDDF9B13F2179D4855E6FC89E9E29CA12517D17DFA1EDCE0BEBF0EA7B461FFE61D94E2BDF72C196F89ACD3536B644064014DAE25A15DB6BB0852ECBD120916318D1CCDEA3C84C92ED743FC176D0BACA920D3FCF3158AFF731F88CE0623182A8ED67E650515F75745909F07D415F55FC15A35654D118C55A462D37A3ACDA08612F3F3F6571761EFCCBCC299AEE99B3A4FD6212CCFFF5EF37A2C334E871191F7E1C31960E010A54E86FA3F62E6D6905E1CD57732410A3EB0C6B4DEFDABE9F59BF1618758C751CD56CEF851D1C0EAA1C558E37AC108DA9089863D20E2E7E4BF475EC66FE6B3EFDCF
# v2_prod_pubkey = 0xCB45C5E53217D4499FB80B2D96AA4F964EB551F1DA4EBFA4F5E23F87BFE82FC113590E536757F329D6EAD1F267771EE342F5A5E61514DD3D3383187E663929D577D94648F262EBA1157E152DB5273D10AE3A6A058CB9CD64D01267DAC82ED3B7BC1631D078C911414129CDAAA0FFB0A8E2A7ADD6F32FB09A7E98D259BFF6ED10808D1BDA58CAF7355DFF1A085A18B11657D2617447BF657140D599364E5AC8E626276AC03BC2417831D9E61B25154AFE9F2D8271E9CE22D2783803083A5A7A575774688721097DC5E4B32D118CF6317A7083BA15BA608430A8C8C6B7DA2D932D81F571603A9363AC0197AB670242D9C9180D97A10900F11FE3D9246CF14F0883
# v2_dev_pubkey  = 0xB372CEC9E05E71FB3FAA08C34E3256FB312EA821638A243EF8A5DEA46FCDA33F00F88FC2933FB276D37B914F89BAD5B5D75771E342265B771995AE8F43B4DFF3F21A877FE777A8B419587C8718D36204FA1922A575AD5207D5D6B8C10F84DDCA661B731E7E7601D64D4A894F487FE1AA1DDC2A1697A3553B1DD85D5750DF2AA9D988E83C4C70BBBE4747219F9B92B199FECB16091896EBB441606DEC20F446249D5568BB51FC87BA7F85E6295FBE811B0A314408CD31921C360608A0FF7F87BD733560FE1C96E472834CAB6BE016C35727754273125089BE043FD3B26F0B2DE141E05990CE922F1702DA0A2F4E9F8760D0FA712DDB9928E0CDAC14501ED5E2C3

ChunkListHeader = struct.Struct('<4sIBBBxQQQ')
assert ChunkListHeader.size == 0x24

Chunk = struct.Struct('<I32s')
assert Chunk.size == 0x24

def parse_chunklist(path):
    with open(path, 'rb') as f:
        hash_ctx = hashlib.sha256()
        data = f.read(ChunkListHeader.size)
        hash_ctx.update(data)
        magic, header_size, file_version, chunk_method, signature_method, chunk_count, chunk_offset, signature_offset = ChunkListHeader.unpack(data)
        assert magic == b'CNKL'
        assert header_size == ChunkListHeader.size
        assert file_version == 1
        assert chunk_method == 1
        assert signature_method in [1, 2]
        assert chunk_count > 0
        assert chunk_offset == 0x24
        assert signature_offset == chunk_offset + Chunk.size * chunk_count
        for i in range(chunk_count):
            data = f.read(Chunk.size)
            hash_ctx.update(data)
            chunk_size, chunk_sha256 = Chunk.unpack(data)
            yield chunk_size, chunk_sha256
        digest = hash_ctx.digest()
        if signature_method == 1:
            data = f.read(256)
            assert len(data) == 256
            signature = int.from_bytes(data, 'little')
            plaintext = 0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004200000000000000000000000000000000000000000000000000000000000000000 | int.from_bytes(digest, 'big')
            assert pow(signature, 0x10001, v1_prod_pubkey) == plaintext
        elif signature_method == 2:
            data = f.read(32)
            assert data == digest
        else:
            raise NotImplementedError
        assert f.read(1) == b''

def check_chunklist(path, chunklist_path):
    with open(path, 'rb') as f:
        for chunk_size, chunk_sha256 in parse_chunklist(chunklist_path):
            chunk = f.read(chunk_size)
            assert len(chunk) == chunk_size
            assert hashlib.sha256(chunk).digest() == chunk_sha256
        assert f.read(1) == b''

@jspraul
Copy link

jspraul commented Oct 8, 2021

https://github.com/sadponyguerillaboy/Apple-Chunklist-Verification-Creation-Toolkit (MIT licensed Python) can verify chunklist signatures and create signed chunklists.

Edit: wow @zhangyoufu, thanks!

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