Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.
>anyone can access via the API.
o1-preview was replaced by full o1 just a few days back (and o1 pro if you pay them $200 or something). I'm assuming you have o1 through their plus subscription.

DM"d. I already kind of got the vague idea of how it works (take the response to the auth server endpoint, split response by tilde, take first object as bytes, then fill authInfo field from those bytes) -- but it'd be easier to have gpt reverse it into more grokkable C code.
 
I think this should be the reversing of the relevant methods
Code:
#import <Foundation/Foundation.h>
#include <CommonCrypto/CommonDigest.h>


// Starts initialized to all 0xCC, total size of 88 bytes
typedef struct {
    uint64_t random; // At offset 0 bytes
    unsigned char token[32]; // At offset 8 bytes
    unsigned char data1[6]; // At offset 40
    unsigned char snStringBoardHash[16]; // At offset 46
    unsigned char zeroes[16]; // At offset 62. Edit: THIS IS WRONG, see Post #91 by WFH for corrected version
    unsigned char padding[10]; // At offset 78
 
} __attribute__((packed)) auth_info;

uint64_t generateRandomValue(void) {
    // Generate two 32-bit random numbers
    uint32_t high = arc4random();
    arc4random_stir();  // Stir the random number generator
    uint32_t low = arc4random();
 
    // Combine into 64-bit value
    uint64_t value = ((uint64_t)high << 32) | low;
    // Byte swap the 64-bit value
    return __builtin_bswap64(value);
}

NSData *getData1() {
    // Setup initial bytes with XOR operation
    // Initialize a buffer with specific values
    unsigned char buffer[4] = {0x2d, 0x30, 0x32, 0};

    // XOR each byte with 0x7F
    for (int i = 0; i < 3; i++) {
        buffer[i] ^= 0x7F;
    }
 
    // Create the registry path key
    NSString *registryKey = [NSString stringWithFormat:@"%@:%s",
        @"4D1EDE05-38C7-4a6a-9CC6-4BCCA8B38C14",
        buffer];
 
    // Get registry entry
    io_registry_entry_t entry = IORegistryEntryFromPath(kIOMasterPortDefault,
        "IODeviceTree:/options");
    NSData *result = nil;
 
    if (entry != 0) {
        // Fetch property from registry
        CFTypeRef property = IORegistryEntryCreateCFProperty(entry,
            (__bridge CFStringRef) registryKey,
            kCFAllocatorDefault,
            0);
   
        // Release the registry entry
        IOObjectRelease(entry);
   
        if (property != NULL) {
            // Check if the property is of type CFData
            if (CFGetTypeID(property) == CFDataGetTypeID()) {
                result = (__bridge_transfer NSData *) property;
            } else {
                CFRelease(property);
            }
        }
    }
 
    return result;
}

NSString *getBoardID() {
    // Get the root IOService entry
    io_registry_entry_t registryEntry = IORegistryEntryFromPath(kIOMasterPortDefault,
        "IOService:/");
    NSString *boardID = nil;
 
    if (registryEntry != 0) {
        // Get the board-id property
        CFTypeRef property = IORegistryEntryCreateCFProperty(registryEntry,
            CFSTR("board-id"),
            kCFAllocatorDefault,
            0);
   
        // Release the registry entry as we no longer need it
        IOObjectRelease(registryEntry);
   
        if (CFGetTypeID(property) == CFDataGetTypeID()) {
            const char *bytes = (const char *)CFDataGetBytePtr((CFDataRef)property);
            boardID = [NSString stringWithCString:bytes
                encoding:NSUTF8StringEncoding];
        } else {
            CFRelease(property);
        }
    }
 
    return boardID;
}

NSString *getData2() {
    // Setup initial bytes and XOR them
    char bytes[4] = {0x32, 0x33, 0x3d, 0};  // "23="
 
    // XOR each byte with 0x7F
    for (int i = 0; i < 3; i++) {
        bytes[i] ^= 0x7F;
    }
 
    // Create the registry key
    NSString *registryKey = [NSString stringWithFormat:@"%@:%s",
        @"4D1EDE05-38C7-4a6a-9CC6-4BCCA8B38C14",
        bytes];
 
    // Get registry entry
    io_registry_entry_t entry = IORegistryEntryFromPath(kIOMasterPortDefault,
        "IODeviceTree:/options");
    NSString *result = nil;
 
    if (entry != 0) {
        // Get property from registry
        CFTypeRef property = IORegistryEntryCreateCFProperty(entry,
            (__bridge CFStringRef)registryKey,
            kCFAllocatorDefault,
            0);
   
        // Release registry entry
        IOObjectRelease(entry);
   
        if (property != NULL) {
            if (CFGetTypeID(property) == CFDataGetTypeID()) {
                // Convert CFData to NSData
                NSData *propertyData = (__bridge_transfer NSData *)property;
           
                // Try to deserialize as property list
                NSError *error = nil;
                id propertyList = [NSPropertyListSerialization
                    propertyListWithData:propertyData
                    options:0
                    format:NULL
                    error:&error];
           
                // Check if result is a string
                if ([propertyList isKindOfClass:[NSString class]]) {
                    result = propertyList;
                }
            } else {
                CFRelease(property);
            }
        }
    }
 
    return result;
}

int main(int argc, char *argv[]) {
    @autoreleasepool {
        auth_info authInfo;
        assert(sizeof(auth_info) == 0x58);
        memset(&authInfo, 0xCC, sizeof(auth_info));
   
        authInfo.random = generateRandomValue();
   
        // Expect 64 hex characters (converts to 32 bytes)
        unsigned char authToken[32];
        // Take from session key of set-cookie header for initial request
        const char *hexCString = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
        for (int i = 0; i < 32; i++) {
            unsigned int value;
            sscanf(hexCString + i * 2, "%2x", &value);
            authToken[i] = (unsigned char)value;
        }
        memcpy(authInfo.token, authToken, 32);
   
        // Copy first 6 bytes of data1 into authInfo.data1

        NSData *data1 = getData1();
        NSLog(@"Got data1 %@", data1);
        memcpy(authInfo.data1, [data1 bytes], MIN([data1 length], 6));

        // Get C strings for data2 and boardID
        const char *snString = [getData2() cStringUsingEncoding:NSASCIIStringEncoding];
        printf("Got snString %s\n", snString);
        const char *boardId = [getBoardID() cStringUsingEncoding:NSASCIIStringEncoding];
        printf("Got boardId %s\n", boardId);
   
        // Compute SHA256 hash of snString and boardId
        CC_SHA256_CTX shaCtx;
        CC_SHA256_Init(&shaCtx);
        CC_SHA256_Update(&shaCtx, snString, (CC_LONG)strlen(snString));
        CC_SHA256_Update(&shaCtx, boardId, (CC_LONG)strlen(boardId));
        unsigned char hash[CC_SHA256_DIGEST_LENGTH];
        CC_SHA256_Final(hash, &shaCtx);

        memcpy(authInfo.snStringBoardHash, hash, 16);
        memset(authInfo.zeroes, 0x0, 16);
   
        NSString *cidString = [NSString stringWithFormat:@"%016llX", CFSwapInt64(authInfo.random)];
   
        // Compute SHA256 hash of authInfo
        unsigned char authinfo_sha256[CC_SHA256_DIGEST_LENGTH];

        CC_SHA256(&authInfo, sizeof(auth_info), authinfo_sha256);
   
        // Convert the hash to a hex string. This becomes our K value
        NSMutableString *authinfoHex = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
        for (int idx = 0; idx < CC_SHA256_DIGEST_LENGTH; idx++) {
            [authinfoHex appendFormat:@"%02X", authinfo_sha256[idx]];
        }
   
        NSLog(@"%@", [NSString stringWithFormat:@"&cf_cid=%@sn=%sbid=%sk=%s",
            cidString,
            snString,
            boardId,
            [authinfoHex cStringUsingEncoding:NSASCIIStringEncoding]]);

    }
}

Also if I'm reading this right, since the CID is effectively random, there's no way for the server to actually verify a given CID value. K is a pure function of response token, rom, serial number, board ID, and CID, where CID is just randomness. Your request contains the sn, board ID, CID, and response token. So fixing those values, the results must be reproducible.

I tried comparing against known values WFH gave me but it differed when running on my machine because it tries to query ROM which probably differs between board models. If it still differs even when running on same machine, I think next step would be to try link against the binary, invoke the function and memory dump the authInfo struct to see how its structure compares to what I reversed. I'm a bit suspicious of the padding.
 
Last edited:
K and CID seem to be different every time the machine is verified by Internet Recovery. Could that be an explanation?
 
CID is basically random, so it's expected that K and CID will be different each session.

> K is a pure function of response token, rom, serial number, board ID, and CID, where CID is just randomness. Your request contains the sn, board ID, CID, and response token. So fixing those values, the results must be reproducible.
 
  • Like
Reactions: Jazzzny
Btw https://www.tonymacx86.com/threads/how-to-fix-imessage.110471/#TOP7.1 for info on ROM & MLB values.

One thing I don't understand though is that ROM is supposed to be unique per machine apparently (it's not per family). So how exactly does apple verify the value of CID/K? If CID is truly random, then that implies there is some way to derive the ROM from sn+boardID, otherwise Apple could not recompute K on their end to see if it matches the K we provide. On the other hand given that this is exactly the info that's usually used for iMessage validation, I guess it makes sense they would have some way to form the correspondence.
 
Btw https://www.tonymacx86.com/threads/how-to-fix-imessage.110471/#TOP7.1 for info on ROM & MLB values.

One thing I don't understand though is that ROM is supposed to be unique per machine apparently (it's not per family). So how exactly does apple verify the value of CID/K? If CID is truly random, then that implies there is some way to derive the ROM from sn+boardID, otherwise Apple could not recompute K on their end to see if it matches the K we provide. On the other hand given that this is exactly the info that's usually used for iMessage validation, I guess it makes sense they would have some way to form the correspondence.
The ROM reflects the MAC address. The MAC address Apple uses can be found here:
 
Code:
# check environment for macOS using sw_vers
if [[ -n "$(sw_vers 2>/dev/null)" && "${get_parameters_from_macOS_host}" =~ [Yy] ]]; then
    # These values are taken from a genuine Mac...
    hardware_overview="$(system_profiler SPHardwareDataType)"
    model_name="${hardware_overview##*Model Name: }"; model_name="${model_name%%$'\n'*}"
    model_identifier="${hardware_overview##*Model Identifier: }"; model_identifier="${model_identifier%%$'\n'*}"
    boot_rom_ver="${hardware_overview##*Boot ROM Version: }"; boot_rom_ver="${boot_rom_ver%%$'\n'*}"
    sn_system="${hardware_overview##*Serial Number (system): }"; sn_system="${sn_system%%$'\n'*}"
    hardware_uuid="${hardware_overview##*Hardware UUID: }"; hardware_uuid="${hardware_uuid%%$'\n'*}"
    nvram_rom="$(nvram 4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14:ROM)"; nvram_rom="${nvram_rom##*$'\t'}"
    nvram_mlb="$(nvram 4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14:MLB)"; nvram_mlb="${nvram_mlb##*$'\t'}"
    ioreg_board_id="$(ioreg -p "IODeviceTree" -r -n / -d 1)"; ioreg_board_id="${ioreg_board_id##*board-id\" = <\"}"; ioreg_board_id="${ioreg_board_id%%\">*}"
    ioreg_system_id="$(ioreg -p "IODeviceTree" -n platform -r)"; ioreg_system_id="${ioreg_system_id##*system-id\" = <}"; ioreg_system_id="${ioreg_system_id%%>*}"
    # ...and set in VirtualBox EFI and NVRAM...
    DmiSystemFamily="${model_name}"         # Model Name
    DmiSystemProduct="${model_identifier}"  # Model Identifier
    DmiBIOSVersion="string:${boot_rom_ver}" # Boot ROM Version
    DmiSystemSerial="${sn_system}"          # Serial Number (system)
    DmiSystemUuid="${hardware_uuid}"        # Hardware UUID
    ROM="${nvram_rom}"                      # ROM identifier, stored in NVRAM
    MLB="${nvram_mlb}"                      # MLB SN, stored in NVRAM
    DmiBoardSerial="${nvram_mlb}"           # MLB SN, stored in EFI
    DmiBoardProduct="${ioreg_board_id}"     # Product (board) identifier
    SystemUUID="${ioreg_system_id}"         # System UUID, stored in NVRAM
else
    # ...or they can be set manually.
    DmiSystemFamily="MacBook Pro"          # Model Name
    DmiSystemProduct="MacBookPro11,2"      # Model Identifier
    DmiBIOSVersion="string:MBP7.89"        # Boot ROM Version
    DmiSystemSerial="NO_DEVICE_SN"         # Serial Number (system)
    DmiSystemUuid="CAFECAFE-CAFE-CAFE-CAFE-DECAFFDECAFF" # Hardware UUID
    ROM='%aa*%bbg%cc%dd'                   # ROM identifier
    MLB="NO_LOGIC_BOARD_SN"                # MLB SN stored in NVRAM
    DmiBoardSerial="${MLB}"                # MLB SN stored in EFI
    DmiBoardProduct="Mac-3CBD00234E554E41" # Product (board) identifier
    SystemUUID="aabbccddeeff00112233445566778899" # System UUID
fi
system_integrity_protection='10'  # '10' - enabled, '77' - disabled
 
>The ROM reflects the MAC address. The MAC address Apple uses can be found here:
It clearly doesn't? People in the hackintosh community end up using the mac address as a unique value but as that thread I quoted mentioned and as can be easily empirically verified it doesn't actually align with the mac address of your primary interface.

It's almost a mystery to me exactly what role CID plays in this protocol. Assuming I didn't overlook anything and it is indeed just randomness, it seems quite redundant. Replay attack is already prevented by server side randomizing the session token. Having a CID technically adds entropy (same way TLS has both client and server random) but it seems really pointless.
 
On a Mac, the MAC address is typically stored within the device's Read-Only Memory (ROM), meaning it's permanently embedded in the hardware and cannot be easily changed, essentially acting as a unique identifier for the device at the physical level; this is why the MAC address is often referred to as a "burned-in address.".

Key points about the relationship:
  • Storage location:
    The MAC address is physically stored within the ROM chip on the device's motherboard.

  • Immutability:
    Due to its location in ROM, the MAC address is generally considered unchangeable and cannot be easily modified by software.

  • Uniqueness:
    Each device has a unique MAC address assigned by the manufacturer, ensuring no two devices on a network have the same address.

Choose a MAC Address​

Select a MAC Address with an Organizationally Unique Identifier (OUI) that corresponds to a real Apple, Inc. interface.

See the following list:

https://gitlab.com/wireshark/wireshark/-/raw/master/manuf(opens new window)

For example:

00:16:CB Apple Apple, Inc.

Make up the last 3 octets.

For example:

00:16:CB:00:11:22

#Derive the corresponding ROM Value​

ROM is calculated from your MAC Address.

Lowercase your MAC Address, and remove each colon : between the octets.

For example:

MAC: 00:16:CB:00:11:22

ROM: 0016cb001122
 
Don't care what opencore says, it's trivial to verify yourself that en0 (or any other interface's) mac address does not align with ROM on a legitimate mac (on mine at least)

`nvram 4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14:ROM`
`ifconfig | grep "ether"`

I suspect that the practice of having ROM as mac address is just an example of hackintosh cargo culting. Derivation of ROM is also not strictly relevant assuming we're going to salvage a real mac for this anyway

(That being said, the value of ROM is a valid mac address in the sense that it belongs to the range of registered addresses for Apple. Technically I guess since it's not networking related it's thus just a EUI-48 identifier.)
 
Last edited:
I am not sure why it does not match the MAC address, but this is how it can be queried:
Code:
nvram 4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14:ROM | awk '{split($NF,chars,""); for(n=0;n<256;n++){ord[sprintf("%c",n)]=n}; i=1; j=0; while(i<=length($NF)){if(substr($NF,i,1)=="%"){printf "%s",toupper(substr($NF,i+1,2)); i=i+2} else {printf "%x",toupper(ord[chars[i]])} j=j+2; i++} print("")}'
 
I am not sure why it does not match the MAC address, but this is how it can be queried:
Code:
nvram 4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14:ROM | awk '{split($NF,chars,""); for(n=0;n<256;n++){ord[sprintf("%c",n)]=n}; i=1; j=0; while(i<=length($NF)){if(substr($NF,i,1)=="%"){printf "%s",toupper(substr($NF,i+1,2)); i=i+2} else {printf "%x",toupper(ord[chars[i]])} j=j+2; i++} print("")}'
one liner output on my MBP11,1: 685bxx6c56xx

I can confirm that, with what my Dumper reads:

LBSN: xxx BD: xxx Firmware MAC: 68:5b:xx:6c:56:xx

I read that from the LBSN Bootblock of the Mac firmware.
Also there is a Firewire firmware dump in ioreg, of machines what have firewire, containing the MAC address.
can be read with ioreg -p IOFireWire -l -w 0 | grep "FireWire Device ROM"

Firmware MAC of the machine, with FireWire, 1st interface: FireWire_MAC_this_machine="$(ifconfig fw0 2>/dev/null | grep lladdr | awk '{print $2}')"

I use that to compare the firmware dump's bootblock is matching with the machine, before flashing.
People manage to spoof serials, but not the MAC address content of the bootblock...


bootblock MBP11,1 - MAC address
Screenshot 2024-12-09 at 19.53.31.png
 
Last edited:
  • Like
Reactions: f54da and startergo
On the other hand given that this is exactly the info that's usually used for iMessage validation, I guess it makes sense they would have some way to form the correspondence.
No, it doesn't make sense, because speaking from lots of experience (and contrary to what you may read), you do not need a real serial number in order to use iMessage! What you need is a Product Name, Serial Number, Board Serial Number, Board ID, etc which all coherently match each other. (And I've always operated under the assumption you need to get it right the first time Apple sees your computer—you can't go tweaking it later on—but that might be my superstition.)

I find it kind of strange that Apple is protecting Mavericks recovery downloads better than they do iMessage...
 
>What you need is a Product Name, Serial Number, Board Serial Number, Board ID, etc which all coherently match each other.

Right, which is why I was curious about the ROM derivation. Because the hackintosh community uses ROM derived from mac address, with mac address chosen randomly to anything within the assigned apple blocks. If that were sufficient to truly pass the most stringent of checks, then there would be no way for apple to rederive the sha256 K value and perform validation. So this implies there must be some additional constraint tying ROM to MLB + model + Serial Number. Either apple just has a lookup table on their end, or there is some derivation function (e.g. keyed hash function).
 
Here is f54da's reverse-engineered Objective-C code to generate the k, with just the pieces we'd need. Note the constants at the top of main which are Xs need to be replaced with real values, as usual PM me if you need them. (The session token is "real" but won't be useful, since it will have expired by the time you read this.)

Objective-C:
#import <Foundation/Foundation.h>
#include <CommonCrypto/CommonDigest.h>


// Starts initialized to all 0xCC, total size of 88 bytes
typedef struct {
    uint64_t random; // At offset 0 bytes
    unsigned char token[32]; // At offset 8 bytes
    unsigned char data1[6]; // At offset 40
    unsigned char snStringBoardHash[CC_SHA256_DIGEST_LENGTH]; // At offset 46
    unsigned char padding[10]; // At offset 78
 
} __attribute__((packed)) auth_info;

uint64_t generateRandomValue(void) {
    // Generate two 32-bit random numbers
    uint32_t high = arc4random();
    arc4random_stir();  // Stir the random number generator
    uint32_t low = arc4random();
 
    // Combine into 64-bit value
    uint64_t value = ((uint64_t)high << 32) | low;
    // Byte swap the 64-bit value
    return __builtin_bswap64(value);
}

int main(int argc, char *argv[]) {
    @autoreleasepool {
        const char *session_token = "FE6E18E867EEEF38A6E55CBE340574CD89158607FB541BA320DDAF93979ADD8A";
        unsigned char rom_bytes[] = { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX };
        const char *snString = "C0XXXXXXXXXXXXXXX";
        const char *boardId = "Mac-35C1E88140C3E6CF";
  
        auth_info authInfo;
        assert(sizeof(auth_info) == 0x58);
        memset(&authInfo, 0xCC, sizeof(auth_info));
  
        authInfo.random = generateRandomValue();
  
        unsigned char authToken[32];
        for (int i = 0; i < 32; i++) {
            unsigned int value;
            sscanf(session_token + i * 2, "%2x", &value);
            authToken[i] = (unsigned char)value;
        }
        memcpy(authInfo.token, authToken, 32);
  

        NSData *rom = [NSData dataWithBytes:rom_bytes length:sizeof(rom_bytes)];
        memcpy(authInfo.data1, [rom bytes], MIN([rom length], 6));
  
        // Compute SHA256 hash of snString and boardId
        CC_SHA256_CTX shaCtx;
        CC_SHA256_Init(&shaCtx);
        CC_SHA256_Update(&shaCtx, snString, (CC_LONG)strlen(snString));
        CC_SHA256_Update(&shaCtx, boardId, (CC_LONG)strlen(boardId));
        unsigned char hash[CC_SHA256_DIGEST_LENGTH];
        CC_SHA256_Final(hash, &shaCtx);
  
        memcpy(authInfo.snStringBoardHash, hash, 32);

        NSString *cidString = [NSString stringWithFormat:@"%016llX", CFSwapInt64(authInfo.random)];
  
        // Compute SHA256 hash of authInfo
        unsigned char authinfo_sha256[CC_SHA256_DIGEST_LENGTH];
        CC_SHA256(&authInfo, sizeof(auth_info), authinfo_sha256);
  
        // Convert the hash to a hex string. This becomes our K value
        NSMutableString *authinfoHex = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
        for (int idx = 0; idx < CC_SHA256_DIGEST_LENGTH; idx++) {
            [authinfoHex appendFormat:@"%02X", authinfo_sha256[idx]];
        }
  
        NSLog(@"%@", [NSString stringWithFormat:@"\ncid=%@\nk=%s",
            cidString,
            [authinfoHex cStringUsingEncoding:NSASCIIStringEncoding]]);
    }
}

And here is that logic converted to sh and added to the larger shell script. This script will, in one go, request a session token from Apple, use that token to generate k, and finally get the DMG download link from Apple.

(The logical next step is to write the curl request to actually download the DMG, it shouldn't be hard but I really need to stop for the evening. Please feel free to run with this if you feel inclined.)

One thing I'm annoyed I couldn't get working in the shell script is CID generation. You need to plug a CID into the top of the script. The Objective-C code written by f54da can actually generate unique CIDs (and they are valid).

Bash:
#!/bin/sh

set -x

hex_to_bin() {
    printf "$(echo "$1" | sed 's/\(..\)/\\x\1/g')"
}

BOARD_SERIAL_NUMBER="C0XXXXXXXXXXXXXXX"
BOARD_ID="Mac-35C1E88140C3E6CF"
CID="7C236FA375BF581E"
ROM="XXXXXXXXXXXX"

SESSION=$(curl -s -c - http://osrecovery.apple.com/ | tail -n 1 | awk '{print $NF}')

# Compute SHA256 hash of BOARD_SERIAL_NUMBER + BOARD_ID
sn_board_input="${BOARD_SERIAL_NUMBER}${BOARD_ID}"
sn_board_hash=$(printf "%s" "$sn_board_input" | openssl dgst -sha256 -binary)

# Convert session_token and ROM from hex to binary
token_bin=$(hex_to_bin $(echo $SESSION | awk -F'~' '{print $2}'))
data1_bin=$(hex_to_bin "$ROM")

random_bin='\x7C\x23\x6F\xA3\x75\xBF\x58\x1E'
padding_bin='\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC'

auth_info=$(printf '%b%b%b%b%b' \
    "$random_bin" \
    "$token_bin" \
    "$data1_bin" \
    "$sn_board_hash" \
    "$padding_bin")

# Compute SHA256 hash of auth_info
# The -binary flag outputs raw binary, which is then converted to uppercase hex
K=$(printf "%s" "$auth_info" | openssl dgst -sha256 -binary | xxd -p -u | tr -d '\n')

echo $K

curl 'http://osrecovery.apple.com/InstallationPayload/OSInstaller' -X POST \
-H 'Content-Type: text/plain' \
--cookie "session=$SESSION" \
-d "cid=$CID
sn=$BOARD_SERIAL_NUMBER
bid=$BOARD_ID
k=$K
" \

And then we need to either figure out how to generate an artificial board serial number and ROM combination Apple will accept, or find a donor computer.
 
Last edited:
  • Like
Reactions: f54da
It's probably better to keep the randomization for CID. It does seem unnecessary but presumably it's there in the protocol for a reason. Randomizing it also helps make us look more like the real thing.

>The Objective-C code written by f54da can actually generate unique CIDs (and they are valid).
CID is just a random int64 though. Taking two random int32s, concat them, then byteswap is no different than generating a random int64. So all your shell script needs to do is generate a random int64. The CID is then just that random integer byteswapped and converted to hex (padded to 16 digits).

Btw since the download is done over HTTP you should probably also include an md5 for the download. I think the official installer does something more clever and it has a list of chunk hashes which is signed with apple's public key so can be verified that way. Replicating that verification logic seems overkill. There's some irony though that for the paranoid person who wouldn't trust random dmgs of mavericks, this method technically isn't any better since the download is over HTTP.

--

Also the way they generate the random is really weird. I don't know why they do a byteswap on the random result, or why they use arc4random_stir when the man page explicitly indicates that stirring is unnecessary (and newer openbsd even have it be a noop I think, in addition to actually using chacha20 instead of rc4 cipher)
 
Last edited:
It's probably better to keep the randomization for CID.
I agree!

So all your shell script needs to do is generate a random int64
I tried just generating a random 16 character HEX and it didn't seem to be working, I'll try again though.

There's some irony though that for the paranoid person who wouldn't trust random dmgs of mavericks, this method technically isn't any better since the download is over HTTP.
I'd argue it's still much better because your ISP probably isn't trying to hack you. (I'm kind of skeptical of https, I don't think it's bad per se I just think the risks IRL are lower than people make them out to be.) But a hash check is definitely a good idea!
 
>I tried just generating a random 16 character HEX

did you byteswap? CID should be a byteswapped & hex-converted version of auth.random.
 
Also the way they generate the random is really weird. I don't know why they do a byteswap on the random result, or why they use arc4random_stir when the man page explicitly indicates that stirring is unnecessary (and newer openbsd even have it be a noop I think, in addition to actually using chacha20 instead of rc4 cipher)
Could this be something the compiler did, or does it look explicit?
 
It looks explicit to me. But it seems pointless, possibly done by someone who didn't read the man pages (or was used to some ancient API where such explicit stirring was needed)
 
Okay, here's tonight's progress. (I'm starting to feel silly for not switching to Github or similar, but I also like using the forum for discussion. I've really appreciated everyone contributing to this!)

Bash:
#!/bin/sh

hex_to_bin() {
    printf "$(echo "$1" | sed 's/\(..\)/\\x\1/g')"
}

BOARD_SERIAL_NUMBER="C0XXXXXXXXXXXXXXX"
BOARD_ID="Mac-35C1E88140C3E6CF"
ROM="XXXXXXXXXXXX"

SESSION=$(curl -s -c - http://osrecovery.apple.com/ | tail -n 1 | awk '{print $NF}')
if [ -z $(echo "$SESSION" | tr -d '[:space:]') ]
then
    echo "Error: Server did not respond with a session token." 1>&2
    exit 1
fi

# Pausing between initial session retrieval and OSInstaller POST request may increase the success rate.
# (It also may be completely pointless and not do anything. I can't tell.)
sleep 5

#CID="9E9F9E7E5D65330D"
CID=$(dd if=/dev/urandom bs=8 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n' | tr '[:lower:]' '[:upper:]')
echo "Generated CID:" $CID

auth_info=$(printf '%b%b%b%b%b' \
    $(hex_to_bin "$CID") \
    $(hex_to_bin $(echo $SESSION | awk -F'~' '{print $2}')) \
    $(hex_to_bin "$ROM") \
    $(printf "%s" "${BOARD_SERIAL_NUMBER}${BOARD_ID}" | openssl dgst -sha256 -binary) \
    '\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC')

# Compute SHA256 hash of auth_info
# The -binary flag outputs raw binary, which is then converted to uppercase hex
K=$(printf "%s" "$auth_info" | openssl dgst -sha256 -binary | od -An -tx1 | tr -d ' \n' | tr '[:lower:]' '[:upper:]')

sleep 5 # occasionally necessary to wait before making next request?

INSTALL_ESD_INFO=$(curl -s 'http://osrecovery.apple.com/InstallationPayload/OSInstaller' -X POST \
-H 'Content-Type: text/plain' \
--cookie "session=$SESSION" \
-d "cid=$CID
sn=$BOARD_SERIAL_NUMBER
bid=$BOARD_ID
k=$K

")

if [ -z $(echo "$INSTALL_ESD_INFO" | tr -d '[:space:]') ]
then
    echo "Error: Server did not respond with InstallESD information." 1>&2
    exit 1
fi

INSTALL_ESD_URL=$(echo "$INSTALL_ESD_INFO" | grep AU | awk -F': ' '{print $2}')
INSTALL_ESD_ASSET_TOKEN=$(echo "$INSTALL_ESD_INFO" | grep AT | awk -F': ' '{print $2}')

curl "$INSTALL_ESD_URL" -H "Cookie: AssetToken=$INSTALL_ESD_ASSET_TOKEN" > InstallESD.dmg

if type md5 >/dev/null 2>&1
then
    echo "Verifying file integrity..."
    if [ $(md5 InstallESD.dmg | awk -F' = ' '{print $2}') != "763e96e1821f7afae1ec653e87a1c400" ]
    then
        rm InstallESD.dmg
        echo "Error: Download failed (mismatched checksum)" 1>&2
        exit 1
    fi
else
    echo "Warning: Could not verify file integrity! This is dangerous because data is downloaded over insecure HTTP."
fi

This script is capable of successfully downloading InstallESD.dmg. It will also verify the md5 hash, and I added some basic error checking.

(MD5 is noticeably faster than sha256, and the utilities to generate it are more cross platform. I did some research and it looks like it should be safe, because although MD5 is broken in some ways there is no known "pre image attack" against it. Correct?)

Unfortunately, it's a little inconsistent—I'd say that maybe 60% of the POST requests go through. For a while, I thought this had to do with the CID value, but then some of the CIDs which I know previously did not work randomly started working, and vice versa, so now I don't know. The thing is, I don't want to run tons and tons of tests lest I make Apple's servers angry...

----------

Other outstanding items:
  • Create working board serial number / ROM combination, or find a donor.
  • Package InstallESD.dmg in a useful way
For the latter—I'm suddenly realizing I'm not sure what I want to do here!
  • Do we want to create a DMG or ISO that the user can then write to a USB drive?
    • This is more flexible—the user could also use the image in a Virtual Machine, or hold onto it somewhere for safe keeping.
  • Do we want to ask the user to insert a USB drive, and prepare it for them?
  • All of the above, and let the user choose?
    • This seems like the obvious choice, but I'd rather not... it's more work to program and it makes the UX more complicated.
I'm realizing this probably can't be done in a cross platform way... up until this point I've gone out of my way to be super POSIX compliant, I guess that was pointless.

Edit:

Also, @f54da, I did try byteswapping the CID:

Bash:
byteswap() {
    swapped_hex=""

    i=$((${#1} - 2))
    while [ $i -ge 0 ]
    do
        swapped_hex="${swapped_hex}${1:$i:2}"
        i=$(($i - 2))
    done
 
    echo "$swapped_hex"
}

[...]

AUTH_CID="$(byteswap $CID)"
auth_info=$(printf '%b%b%b%b%b' \
    $(hex_to_bin "$AUTH_CID") \
    $(hex_to_bin $(echo $SESSION | awk -F'~' '{print $2}')) \
    $(hex_to_bin "$ROM") \
    $(printf "%s" "${BOARD_SERIAL_NUMBER}${BOARD_ID}" | openssl dgst -sha256 -binary) \
    '\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC')

As far as I can tell (unless I've been extraordinarily unlucky during testing) this doesn't ever work.
 
Last edited:
you're not doing the byteswap at the right place? read the objc version carefully. cid is byteswapped version of random field in authInfo.

Edit: Nvmind in your version I didn't see that you have new variable AUTH_CID vs CID.

Does my objc version work properly? It seems like it should be easy to verify the two implementations are equal without making calls to Apple. It is after all just a pure function.
 
It seems like it should be easy to verify the two implementations are equal without making calls to Apple. It is after all just a pure function.
I think they're the same, yes?

Bash:
#!/bin/sh

hex_to_bin() {
    printf "$(echo "$1" | sed 's/\(..\)/\\x\1/g')"
}

BOARD_SERIAL_NUMBER="C0XXXXXXXXXXXXXXX"
BOARD_ID="Mac-35C1E88140C3E6CF"
ROM="XXXXXXXXXXXX"
CID="7C236FA375BF581E"
SESSION="1733959893~1381AAB15D38BF35297B7F079C94C54460B69DB3E788D53A0215AFBE1B3E8A12"

auth_info=$(printf '%b%b%b%b%b' \
    $(hex_to_bin "$CID") \
    $(hex_to_bin $(echo $SESSION | awk -F'~' '{print $2}')) \
    $(hex_to_bin "$ROM") \
    $(printf "%s" "${BOARD_SERIAL_NUMBER}${BOARD_ID}" | openssl dgst -sha256 -binary) \
    '\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC')

K=$(printf "%s" "$auth_info" | openssl dgst -sha256 -binary | od -An -tx1 | tr -d ' \n' | tr '[:lower:]' '[:upper:]')

echo "K: $K"

Objective-C:
#import <Foundation/Foundation.h>
#include <CommonCrypto/CommonDigest.h>


// Starts initialized to all 0xCC, total size of 88 bytes
typedef struct {
    uint64_t random; // At offset 0 bytes
    unsigned char token[32]; // At offset 8 bytes
    unsigned char data1[6]; // At offset 40
    unsigned char snStringBoardHash[CC_SHA256_DIGEST_LENGTH]; // At offset 46
    unsigned char padding[10]; // At offset 78
    
} __attribute__((packed)) auth_info;

int main(int argc, char *argv[]) {
    @autoreleasepool {
        const char *session_token = "1381AAB15D38BF35297B7F079C94C54460B69DB3E788D53A0215AFBE1B3E8A12";
        unsigned char rom_bytes[] = { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX };
        const char *snString = "C0XXXXXXXXXXXXXXX";
        const char *boardId = "Mac-35C1E88140C3E6CF";
        
        auth_info authInfo;
        assert(sizeof(auth_info) == 0x58);
        memset(&authInfo, 0xCC, sizeof(auth_info));
        
        authInfo.random = CFSwapInt64(0x7C236FA375BF581E); // chosen by fair dice roll, guaranteed to be random.
        
        unsigned char authToken[32];
        for (int i = 0; i < 32; i++) {
            unsigned int value;
            sscanf(session_token + i * 2, "%2x", &value);
            authToken[i] = (unsigned char)value;
        }
        memcpy(authInfo.token, authToken, 32);
        

        NSData *rom = [NSData dataWithBytes:rom_bytes length:sizeof(rom_bytes)];
        memcpy(authInfo.data1, [rom bytes], MIN([rom length], 6));
        
        // Compute SHA256 hash of snString and boardId
        CC_SHA256_CTX shaCtx;
        CC_SHA256_Init(&shaCtx);
        CC_SHA256_Update(&shaCtx, snString, (CC_LONG)strlen(snString));
        CC_SHA256_Update(&shaCtx, boardId, (CC_LONG)strlen(boardId));
        unsigned char hash[CC_SHA256_DIGEST_LENGTH];
        CC_SHA256_Final(hash, &shaCtx);
        
        memcpy(authInfo.snStringBoardHash, hash, 32);

        NSString *cidString = [NSString stringWithFormat:@"%016llX", CFSwapInt64(authInfo.random)];
        
        // Compute SHA256 hash of authInfo
        unsigned char authinfo_sha256[CC_SHA256_DIGEST_LENGTH];
        CC_SHA256(&authInfo, sizeof(auth_info), authinfo_sha256);
        
        // Convert the hash to a hex string. This becomes our K value
        NSMutableString *authinfoHex = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
        for (int idx = 0; idx < CC_SHA256_DIGEST_LENGTH; idx++) {
            [authinfoHex appendFormat:@"%02X", authinfo_sha256[idx]];
        }
        
        NSLog(@"%@", [NSString stringWithFormat:@"\ncid=%@\nk=%s",
            cidString,
            [authinfoHex cStringUsingEncoding:NSASCIIStringEncoding]]);
    }
}

Both of these will return the same value for K (F35FB06BF260808A24741D9681C699089883C9C639CFB1AF08EA13D6C1CD71CB). I'm also not really sure how that's possible since the Objective-C is clearly byteswapping authInfo.random to generate the CID, whereas the shell script does not.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.