Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.
For the MAS one, can someone try intercepting the http requests/responses to see if there's some sort of decryption key in one of the responses?
 
If it is over your budget just post a PayPal donation address or something similar and people who are interested in the project can contribute.
 

There you go. You can’t beat that price 😀
The price is definitely reasonable. Tbqh the reason I haven't bought it is that if I do, I'm going to feel bad just getting rid of it afterwards, and I don't want another computer in my life.

Also it supports Mountain Lion minimum (as opposed to Mavericks minimum), I'm not sure if it's going to work?
 
Last edited:
I've stated several times that you must have a machine that shipped with Mavericks to use this patch.
 
  • Like
Reactions: startergo
Yes that is a very narrow pool. Not sure if it is worth it sacrificing a MacBook Air (the cheapest alternative from this pool) for this. Unless there is a dirt cheap MacBook Air for sale with a broken screen or without screen (but still operational). I had one MBP without screen which shipped out with Mavericks but I sold it cheap 5 years ago.
Still post a donation link maybe you can raise enough money?
 
For the MAS one, can someone try intercepting the http requests/responses to see if there's some sort of decryption key in one of the responses?
I emailed you what MITMProxy captured. However, I actually can't get Mavericks (or anything else) to download from the App Store when connected to MITMProxy. I know I installed the cert correctly because other things work.

Screen Shot 2024-12-07 at 8.16.10 PM.png


The download works if I disconnect from the proxy, and it works with Squid—but Squid is configured to exclude certain Apple domains from ssl_bump. I think there is some certificate pinning going on.
 
>I think there is some certificate pinning going on.
Most likely. Anything in console?
 
Does anyone know what I'm doing wrong here?

Bash:
#!/bin/sh

set -x

CID=XXXXXXXXXXXXXXXX
SN=XXXXXXXXXXXXXXXXX
BID=Mac-35C1E88140C3E6CF
KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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

curl \
    -H 'Content-Type: text/plain' \
    -H 'Accept: */*' \
    -H "Cookie: session=$SESSION" \
    -H 'Accept-Language: en-us' \
    -H 'User-Agent: Install%20OS%20X/478.1 CFNetwork/673.3 Darwin/13.4.0 (x86_64) (MacBookAir6%2C1)' \
    -X POST http://osrecovery.apple.com/InstallationPayload/OSInstaller \
    -d "cid=$CID\x0asn=$SN\x0abid=$BID\x0ak=$KEY\x0a\x0a"

In the real script, CID, SN, and KEY are real values instead of Xs. Please PM me if you'd like the real values.

I keep getting back "Request parameters malformed."

The second curl command was basically copied verbatim out of mitmproxy, except I replaced the inline CID, SN, BID, and SESSION with variables. The SESSION I'm getting from the first curl command is in the exact same format as what I captured.

This seems so simple but I'm totally stuck.

GPT4 / o1-preview seemed to think the issue has something to do with line endings, but the solutions it suggested were extremely strange and unsurprisingly didn't work.
 
Last edited:
In the SESSION= line, awk's print will be putting a newline at the end. Could that be the problem? Try using:
Code:
awk '{printf "%s", $NF}'
 
In the SESSION= line, awk's printf will be putting a newline at the end. Could that be the problem? Try using:
Code:
awk '{printf "%s", $NF}'
Thanks, unfortunately I'm still getting back Request parameters malformed.
 
Okay, well I've just noticed that CID and K are regenerated every time verification is requested in Internet Recovery...

Once CID and K are generated, they seem to be reusable until they expire at some point (under 10 minutes). After that, the server rejects all requests with a 401 error.

I was already looking at reversing the CID and K generators in the IA binary, but I guess it would need to be done in order to proceed.

If anyone is a disassembled-Objective C whiz and would like to help out, the code responsible is in -[OSInstallRecoveryAuthSession _loadAssetForName:] in IA. From a quick glance, CID just seems to be some SHA hashing, but I can't figure out how K is generated.

Below is a cURL request that works for me:

Code:
curl 'http://osrecovery.apple.com/InstallationPayload/OSInstaller' \
-X POST \
-H 'Host: osrecovery.apple.com' \
-H 'Accept: */*' \
-H 'Pragma: no-cache' \
-H 'Accept-Language: en-us' \
-H 'User-Agent: Install%20OS%20X/478.1 CFNetwork/673.3 Darwin/13.4.0 (x86_64) (MacBookAir5%2C1)' \
-H 'Connection: close' \
-H 'Content-Type: text/plain' \
--cookie 'session=sessionfromthepreviousrequest' \
--data-raw 'cid=X
sn=X
bid=Mac-66F35F19FE2A0D05
k=X

' \
Screenshot 2024-12-08 at 3.34.49 PM.png

(Ran a fresh cURL using information from my 2012 MBA)

The session cookie is in format xxx~xxxxxxxxxxx (not the actual amount of letters), and it seems like the model ID in the user agent doesn't matter.
 
  • Like
Reactions: Wowfunhappy
I wonder if we could write a program that links IA, swizzles whatever methods that are responsible for inserting the MLB and such that are used to generate CID/K and calls the generator method to get values. Wouldn't be ideal, but someone could then build a program around it to authenticate.
 
After the curl -d flag, the
Thanks, unfortunately I'm still getting back Request parameters malformed.
It might be moot after Jazzzny's post, but the argument to the curl -d flag contains '\x0a' bits which the shell is seeing as newlines. It might work better to use:
Code:
-d "'cid=$CID\x0asn=$SN\x0abid=$BID\x0ak=$KEY\x0a\x0a'"
The inner single-quotes force one long string after the -d flag, with the outer double-quotes causing variable interpolation inside the string.
 
>From a quick glance, CID just seems to be some SHA hashing, but I can't figure out how K is generated.

I think it's the other way, CID seems to be some bitscrambling of _authInfo, while k is bitscrambling of the sha256 of authinfo. Here's sort of my attempt to clean up and munge the ghidra decomp

I don't know where authinfo comes from though. Edit: It's populated during _startSession

C:
ID OSInstallRecoveryAuthSession::_loadAssetForName:(ID self, SEL param_2,ID assetName)

{
  byte bVar1;
  ulong uVar2;
  undefined *puVar3;
  undefined8 uVar4;
  undefined8 uVar5;
  undefined8 uVar6;
  undefined *puVar7;
  char *data;
  char *boardId;
  size_t sVar8;
  long lVar9;
  undefined8 uVar10;
  long lVar11;
  ID IVar12;
  cfstringStruct *pcVar13;
  undefined *puVar14;
  CC_SHA256_CTX local_140;
  long local_d8;
  undefined8 local_d0;
  undefined local_c8 [16];
  undefined local_b8 [16];
  undefined local_a8 [16];
  undefined local_98 [16];
  undefined local_88;
  byte authinfo_sha256 [32];
  undefined local_58 [16];
  undefined local_48 [16];
  long local_38;
 
  puVar3 = __got::_objc_msgSend;
  local_38 = *(long *)__got::___stack_chk_guard;
  uVar4 = [NSAutoreleasePool new];
  uVar5 = [self->_authServerURL absoluteString]
  uVar5 = [NSURL URLWithString:[uVar5 stringByAppendingPathComponent: assetName]];
  lVar9 = _urlDataLock;
  local_d8 = 0;
  local_98 = ZEXT816(0);
  local_a8 = ZEXT816(0);
  local_b8 = ZEXT816(0);
  local_c8 = ZEXT816(0);
  local_88 = 0;
  [self->_urlDataLock lock];
  puVar7 = [[[self->_urlToAssetPart objectForKey:arg2] retain] autorelease];
  [self->_urlDataLock unlock];
  local_48 = ZEXT816(0);
  local_58 = ZEXT816(0);
  snString = [self->_data2 cStringUsingEncoding: 1];
  boardId = [self->_boardID cStringUsingEncoding: 1]
  ;
  CC_SHA256_Init(&local_140);
  sVar8 = __stubs::_strlen(snString);
  CC_SHA256_Update(&local_140,snString,(CC_LONG)sVar8);
  sVar8 = __stubs::_strlen(boardId);
  CC_SHA256_Update(&local_140,boardId,(CC_LONG)sVar8);
  CC_SHA256_Final(local_58,&local_140);
  [self->_authInfoLock lock];
  uVar2 = *(ulong *)(self->_authInfo);
  cidString = [NSString stringWithFormat: @"%*0llX" 0x10,
                            uVar2 >> 0x38 | (uVar2 & 0xff000000000000) >> 0x28 |
                            (uVar2 & 0xff0000000000) >> 0x18 | (uVar2 & 0xff00000000) >> 8 |
                            (uVar2 & 0xff000000) << 8 | (uVar2 & 0xff0000) << 0x18 |
                            (uVar2 & 0xff00) << 0x28 | uVar2 << 0x38];

  *(undefined8 *)(self->_authInfo->0x46)  = 0xcccccccccccccccc;
  *(undefined8 *)(self->_authInfo->0x3e)  = 0xcccccccccccccccc;
  *(undefined8 *)(self->_authInfo->0x36)  = 0xcccccccccccccccc;
  *(undefined8 *)(self->_authInfo->0x2e)  = 0xcccccccccccccccc;
  *(undefined8 *)(self->_authInfo->0x46)  = local_48._8_8_;
  *(undefined8 *)(self->_authInfo->0x3e)  = local_48._0_8_;
  *(undefined8 *)(self->_authInfo->0x36)  = local_58._8_8_;
  *(undefined8 *)(self->_authInfo->0x2e)  = local_58._0_8_;
  CC_SHA256(self->_authInfo, 0x58, authinfo_sha256);
  [self->_authInfoLock unlock];

  idx = 0;
  do {
    bVar1 = authinfo_sha256[idx];
    local_c8[idx * 2] = "0123456789ABCDEF-0223="[bVar1 >> 4];
    local_c8[idx * 2 + 1] = "0123456789ABCDEF-0223="[(ulong)bVar1 & 0xf];
    idx = idx + 1;
  } while ((int)idx != 0x20);
  uVar10 = _objc_msgSend_fixup(&_OBJC_CLASS_$_NSMutableURLRequest,&alloc_message_ref);
  uVar5 = (*(code *)puVar3)(0,uVar10,"initWithURL:cachePolicy:timeoutInterval:",uVar5,1);
  (*(code *)puVar3)(uVar5,"setHTTPMethod:",&cf_POST);
  (*(code *)puVar3)(uVar5,"setValue:forHTTPHeaderField:",&cf_text/plain,&cf_Content-Type);
  puVar14 = local_c8;
  if (puVar7 == (undefined *)0x0) {
    formatString = @"&cf_cid=%@sn=%sbid=%@k=%s";
    puVar7 = self->_boardID;
  }
  else {
    formatString = @"&cf_cid=%@sn=%spn=%@bid=%@k=%s";
    puVar14 = self->_boardID;
  }
  uVar6 = [NSString stringWithFormat:formatString cidString,snString,puVar7,puVar14];
  uVar6 = (*(code *)puVar3)(uVar6,"dataUsingEncoding:",1);
  (*(code *)puVar3)(uVar5,"setHTTPBody:",uVar6);
  lVar9 = (*(code *)puVar3)(&_OBJC_CLASS_$_NSURLConnection,
                            "sendSynchronousRequest:returningResponse:error:",uVar5,&local_d0,
                            &local_d8);
  if (((lVar9 == 0) || (local_d8 != 0)) ||
     (lVar11 = (*(code *)__got::_objc_msgSend)(local_d0,"statusCode"), 399 < lVar11)) {
    if (local_d8 == 0) {
      uVar5 = _objc_msgSend_fixup(&_OBJC_CLASS_$_NSError,&alloc_message_ref);
      puVar7 = _kOSInstallRecoveryAuthSessionErrorDomain;
      uVar6 = (*(code *)puVar3)(local_d0,"statusCode");
      uVar6 = (*(code *)puVar3)(&_OBJC_CLASS_$_NSNumber,"numberWithInteger:",uVar6);
      uVar6 = (*(code *)puVar3)(&_OBJC_CLASS_$_NSDictionary,"dictionaryWithObject:forKey:",uVar6,
                                &cf_HTTPStatusCode);
      local_d8 = (*(code *)puVar3)(uVar5,"initWithDomain:code:userInfo:",puVar7,6,uVar6);
      goto LAB_0003fef7;
    }
  }
  else {
    uVar5 = _objc_msgSend_fixup(&_OBJC_CLASS_$_NSString,&alloc_message_ref);
    uVar6 = (*(code *)puVar3)(lVar9,"bytes");
    uVar10 = _objc_msgSend_fixup(lVar9,&length_message_ref);
    uVar5 = (*(code *)puVar3)(uVar5,"initWithBytes:length:encoding:",uVar6,uVar10,4);
    uVar5 = _objc_msgSend_fixup(uVar5,&autorelease_message_ref);
    local_d8 = (*(code *)puVar3)(self, "_parseServerResponse:forAssetNamed:",uVar5,assetName);
    if (local_d8 == 0) goto LAB_0003fef7;
  }
  _objc_msgSend_fixup(local_d8,&retain_message_ref);
LAB_0003fef7:
  (*(code *)__got::_objc_msgSend)(uVar4,"drain");
  IVar12 = _objc_msgSend_fixup(local_d8,&autorelease_message_ref);
  if (*(long *)__got::___stack_chk_guard != local_38) {
                    /* WARNING: Subroutine does not return */
    __stubs::___stack_chk_fail();
  }
  return IVar12;
}
 
  • Like
Reactions: Jazzzny
>Which kind of sucks, but I guess we could pre-generate it?

See my post above, I think ghidra did a slightly better job at disassembly - Although I think that complicated bitswap of authInfo is just bswap as hopper recognized, i'm surprised ghidra didn't emit an intrinsic for that.

Probably the idea of linking to the binary directly so we can access the methods (e.g. generation of _authInfo) is a good idea. On the other hand it probably also is not too hard to reverse it.
 
I would rather not link to the binary if we can help it. In my head, what I ultimately want to have is a cross platform shell script that people can run from virtually any UNIX machine via curl http://get-mavericks.sh | sh or some such.

If we have to run the IA binary, we're limited to running on macOS—which isn't really a big loss, but more importantly we'll have to deal with modern macOS's code signing and notarization requirements. We'd also be dependent on Rosetta 2 sticking around.

That said, I'm ultimately not the one who would be capable of reverse engineering this.
 
Last edited:
Another idea is to host an endpoint that generates the values when requested. However, I fully agree with f54da in that _authInfo generation should be reversible, none of the code is obfuscated and it isn't too long either.
 
>In my head, what I ultimately want to have is a cross platform shell script that people can run from virtually any UNIX machine via
Sure that's possible as well. Actually data2 you mentioned is just the snString, we already know that.

Really all we need is authinfo, both cid & k are derived from that. The easiest way I mentioned is just to link (or dlopen) the binary and blindly re-use its functions, but it's probably not too difficult to reverse it statically.

AuthInfo gets reset to the following fields (rcx

Code:
   *(self + rcx + 0x50) = 0xcccccccccccccccc;
    *(self + rcx + 0x48) = 0xcccccccccccccccc;
    *(self + rcx + 0x40) = 0xcccccccccccccccc;
    *(self + rcx + 0x38) = 0xcccccccccccccccc;
    *(self + rcx + 0x30) = 0xcccccccccccccccc;
    *(self + rcx + 0x28) = 0xcccccccccccccccc;
    *(self + rcx + 0x20) = 0xcccccccccccccccc;
    *(self + rcx + 0x18) = 0xcccccccccccccccc;
    *(self + rcx + 0x10) = 0xcccccccccccccccc;
    *(self + rcx + 0x8) = 0xcccccccccccccccc;
    *(self + rcx + 0x0) = 0xcccccccccccccccc;

and then during _startSession it makes a GET request to some URL then populates some fields

Code:
    *(r12 + r14 + 0x20) = var_38;
    *(r12 + r14 + 0x18) = var_40;
    *(r12 + r14 + 0x10) = var_48;
    *(r12 + r14 + 0x8) = var_50;
    LODWORD(rbx) = LODWORD(arc4random());
    arc4random_stir();
    rax = arc4random();
    asm{ bswap      rax };
    var_1E8 = LODWORD(rax) | rbx << 0x20;
    *(r14 + r12) = var_1E8;
    rbx = [r14 _getData1];
    r15 = [rbx bytes];
    rax = [rbx length];
    LODWORD(rdx) = 0x6;
    if (rax < 0x6) {
            rdx = rax;
    }
    r13 = *objc_msgSend;
    r12 = *_OBJC_IVAR_$_OSInstallRecoveryAuthSession._authInfo;
    rbx = r14;
    memcpy(r12 + r14 + 0x28, r15, rdx);
    rax = [rbx _getData2];

Where var_40, var_48, and var_50 are obtained by munging the HTTP response a bit. _getData1 is yet another ioregistry field, I think it's board id. _getData2 does not seem to be stored anywhere "but I can't immediately see if that's stored anywhere within the authInfo struct.

Reversing this feels like the kind of thing that one of the LLMs should be decent at, it's mostly just grunge work... @Wowfunhappy since you have an o1 access, if I PM you the full ghidra decomp for _startSession can you ask it to clean it up?
 
>In my head, what I ultimately want to have is a cross platform shell script that people can run from virtually any UNIX machine via
Sure that's possible as well. Actually data2 you mentioned is just the snString, we already know that.
@Wowfunhappy since you have an o1 access, if I PM you the full ghidra decomp for _startSession can you ask it to clean it up?
Sure! Just to be clear, what I have o1-preview which (I think?) anyone can access via the API.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.