Open skiman6010 opened 5 months ago
@skiman6010 Thanks for raising this. I'd have to do some manual reversing to find the offsets and see what changed/how big the change is to understand the specifics of why; the patterns are a bit fragile in general, and it wouldn't surprise me if a major version release outright broke them.
Just so I can name the test binary appropriately, are you able to give the exact version + build this came from? I'm guessing probably this?
macOS 15 beta (24A5264n) June 10, 2024
Assuming that is correct:
⇒ file samples/macos-15.0-beta-24A5264n-sequoia-identityservicesd
samples/macos-15.0-beta-24A5264n-sequoia-identityservicesd: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64e]
samples/macos-15.0-beta-24A5264n-sequoia-identityservicesd (for architecture x86_64): Mach-O 64-bit executable x86_64
samples/macos-15.0-beta-24A5264n-sequoia-identityservicesd (for architecture arm64e): Mach-O 64-bit executable arm64e
⇒ sha256sum samples/macos-15.0-beta-24A5264n-sequoia-identityservicesd
fb40ddabafb508c6ad000560709abd40ac3d91aa212105278046c625c9fdf0b7 samples/macos-15.0-beta-24A5264n-sequoia-identityservicesd
And the output of the current auto offset finder (which obviously gets basically none of them):
⇒ ./find_fat_binary_offsets.py samples/macos-15.0-beta-24A5264n-sequoia-identityservicesd
-= Universal Binary Sections =-
Architecture 0 (x86_64):
CPU Type: 16777223 (0x1000007)
CPU Subtype: 3 (0x3)
CPU Subtype Capability: 0 (0x0)
Offset: 0x4000 (Valid Mach-O Header: Yes)
Size: 10158960
Align: 14
Architecture 1 (arm64e):
CPU Type: 16777228 (0x100000c)
CPU Subtype: 2 (0x2)
CPU Subtype Capability: 128 (0x80)
Offset: 0x9b8000 (Valid Mach-O Header: Yes)
Size: 11717616
Align: 14
-= Found Symbol Offsets =-
Offset of _IDSProtoKeyTransparencyTrustedServiceReadFrom in architecture x86_64: 0x0ef611
Offset of _IDSProtoKeyTransparencyTrustedServiceReadFrom in architecture arm64e: 0x0d1248
-= Found Hex Offsets (with pure python fixed sequence search + regex) =-
Architecture 0 (x86_64):
ReferenceAddress (_IDSProtoKeyTransparencyTrustedServiceReadFrom): 0xef611
NACInitAddress:
NACKeyEstablishmentAddress: 0x618370
NACSignAddress:
Architecture 1 (arm64e):
ReferenceAddress (_IDSProtoKeyTransparencyTrustedServiceReadFrom): 0xd1248; 0x333688; 0x3607d4; 0x378da4
NACInitAddress:
NACKeyEstablishmentAddress:
NACSignAddress:
Yes that is the correct build. Sorry I should have put that in the issue.
Sorry I should have put that in the issue.
@skiman6010 No worries, thanks for confirming :)
Hey I got a hash of the beta 2 build (24A5279h) and it seemed to be different from the build I uploaded earlier. Not sure if it is useful for you but I uploaded it just in case. Thanks! identityservicesd.zip
beta 2 build (24A5279h)
@skiman6010 Thanks for that :) Not sure if/when I (or someone else) will be able to get to looking at reversing it, but will be helpful at that stage.
⇒ sha256sum samples/macos-15.0-beta2-24A5279h-sequoia-identityservicesd
42332e0f43bf82e20e14508bb3660e164a5d789c4257ddb0824107a81f941a3c samples/macos-15.0-beta2-24A5279h-sequoia-identityservicesd
And the output of the current auto offset finder (which obviously gets basically none of them):
⇒ ./find_fat_binary_offsets.py samples/macos-15.0-beta2-24A5279h-sequoia-identityservicesd
-= Universal Binary Sections =-
Architecture 0 (x86_64):
CPU Type: 16777223 (0x1000007)
CPU Subtype: 3 (0x3)
CPU Subtype Capability: 0 (0x0)
Offset: 0x4000 (Valid Mach-O Header: Yes)
Size: 10192848
Align: 14
Architecture 1 (arm64e):
CPU Type: 16777228 (0x100000c)
CPU Subtype: 2 (0x2)
CPU Subtype Capability: 128 (0x80)
Offset: 0x9c0000 (Valid Mach-O Header: Yes)
Size: 11768048
Align: 14
-= Found Symbol Offsets =-
Offset of _IDSProtoKeyTransparencyTrustedServiceReadFrom in architecture x86_64: 0x0eff2d
Offset of _IDSProtoKeyTransparencyTrustedServiceReadFrom in architecture arm64e: 0x0d4a28
-= Found Hex Offsets (with pure python fixed sequence search + regex) =-
Architecture 0 (x86_64):
ReferenceAddress (_IDSProtoKeyTransparencyTrustedServiceReadFrom): 0xeff2d
NACInitAddress:
NACKeyEstablishmentAddress: 0x61f560
NACSignAddress:
Architecture 1 (arm64e):
ReferenceAddress (_IDSProtoKeyTransparencyTrustedServiceReadFrom): 0xd4a28; 0x337afc; 0x364cb8; 0x37d284
NACInitAddress:
NACKeyEstablishmentAddress:
NACSignAddress:
Yeah hopefully someone has the time/generosity to jump in and reverse this! I have been trying to keep the Go registration provider working for me as much as possible, but unfortunately I don't have the skills/knowledge for reversing these binaries for symbols.
but unfortunately I don't have the skills/knowledge for reversing these binaries for symbols.
@skiman6010 If it's something you're interested in learning, it might not be too hard for you to learn enough to get by. For the x86 version I was using the free version of Binary Ninja:
By loading up one of the older binaries with known/correct offsets, we can see how the decompiled code looks at that point, and then we can try and find similar in the newer binary. Usually this was by searching for part of a log message/similar, and then finding the pattern we were looking for after that.
For the ARM version, Binary Ninja free doesn't support it (would need a paid licence), so I was using Ghidra (which is also free, but IMO a bit harder to use/not as nice GUI/etc):
Then we basically want to locate functions using log strings:
Calling NACInit with:
.NACInit
function.Received validation initialization request
.IMRGLog
to identify the function call.failed building validation
.0
parameters is nac_sign
.I recommend doing this on one of the older binaries with known offsets first, as that way you can 'double check' the offsets you find with the 'correct answer' to know you've got the process figured out; then you can apply that same process to the newer binaries.
Some other random notes I have locally, not sure if they will help or just add noise, but in case they are useful..
Load the binary, then go to
Analysis -> Rebase -> 0x0
to ensure all of the memory addresses are using the proper offsets directly (Binary Ninja loads at0x100000000
by default). You could also just set the load address to0x0
in the first analysis options screen too.ReferenceSymbol
Find
IDSProtoKeyTransparencyTrustedServiceReadFrom
symbol, get the address of that function
nac_init
Find the string
Calling NACInit with:
, this is referenced in the code that’s about to call NACInit, there is no symbol so you just have to look for the next function call after that log line is called. If I remember correctly, it will be in anif
statement, with a bunch of_objc_msgSend
's, and some strings likebytes
/length
/etc in it:x86:
if (sub_1004132e0(_objc_msgSend(rax_16, "bytes"), zx.q(_objc_msgSend(rax_16, "length")), var_50.q + 0x18, &var_88, &var_7c) != 0)
arm64:
if (iVar2 != 0) { local_c0 = 0x8400102; local_bc = lVar7; __os_log_impl(0x100000000,uVar6,0,"Calling NACInit with: %@",&local_c0,0xc); } _objc_release(uVar6); uVar6 = _objc_retainAutorelease(lVar7); uVar9 = _objc_msgSend(uVar6,"bytes"); uVar6 = _objc_msgSend(uVar6,"length"); uVar6 = FUN_10043d408(uVar9,uVar6,CONCAT62(uStack_a6,local_a8) + 0x18,&local_f8,&local_fc);
nac_key_establishment
Find the string
Received validation initialization request
, look aboveIMRGLog
:x86:
if (sub_100465e00(r12_1, _objc_msgSend(rax_22, "bytes"), zx.q(_objc_msgSend(rax_22, "length"))) != 0)
arm64:
if (iVar3 != 0) { local_9c = *(cfstringStruct **)(param_1 + 0x20); local_a0 = 0x8400202; local_94 = 0x840; local_92 = pcVar5; __os_log_impl(0x100000000,uVar7,0, " Received validation initialization request response: %@ error: %@",&local_a0, 0x16); } _objc_release(uVar7); if ((param_4 < 2) || (param_4 == 200)) { if (*(long *)(*(long *)(*(long *)(param_1 + 0x38) + 8) + 0x18) == 0) goto LAB_100221d3c; if ((*(char *)(*(long *)(*(long *)(param_1 + 0x40) + 8) + 0x18) == '\0') && (lVar10 = _objc_msgSend(uVar6,"length"), lVar10 != 0)) { uVar15 = *(undefined8 *)(*(long *)(*(long *)(param_1 + 0x38) + 8) + 0x18); uVar7 = _objc_retainAutorelease(uVar6); uVar14 = _objc_msgSend(uVar7,"bytes"); uVar7 = _objc_msgSend(uVar7,"length"); uVar14 = FUN_1003fdafc(uVar15,uVar14,uVar7);
nac_sign
In the 14.2 binary (x86), the instructions for finding
nac_sign
viafailed building validation
doesn't seem to work; but working backwards from the offsets you already have for this one, seems there are some other strings like_sendValidationRequestForSubsystem:
that can get us in the right function to find the call we need. If I remember correctly, searching forfailed building validation
, then go to the caller of that function, and look a few lines up for something like this:x86:
var_a8[0].d = sub_10049f390(_objc_msgSend(rax_13, "validationContext"), 0, 0, &var_58, &var_ac)
arm64:
if (iVar1 == 0) { local_90 = 0; uStack_8e = 0; uStack_8c._0_4_ = 0; local_94 = 0; uVar10 = _objc_msgSend(uVar7,"validationContext"); uVar11 = FUN_1003f2844(uVar10,0,0,&local_90,&local_94);
Then not sure exactly which version/architecture/etc these are from, but I think I was trying to point out to myself exactly which bit of those above code blocks was what I was meant to be using for the offsets, or something like that?
Calling NACInit with:
:0x4b55a0
(FUN_1004b55a0(uVar8,uVar5,p_Var16,&local_f8,&local_fc)
)if (iVar1 != 0) { local_c0 = 0x8400102; local_bc = lVar6; __os_log_impl(0x100000000,uVar5,0,"Calling NACInit with: %@",&local_c0,0xc); } _objc_release(uVar5); uVar5 = _objc_retainAutorelease(lVar6); uVar8 = FUN_10063b7a0(); uVar5 = FUN_10064cb60(uVar5); p_Var16 = (_NSZone *)(CONCAT62(uStack_a6,local_a8) + 0x18); auVar17 = FUN_1004b55a0(uVar8,uVar5,p_Var16,&local_f8,&local_fc); SVar15 = auVar17._8_8_;
Received validation initialization request
:0x4a2e04
(FUN_1004a2e04(uVar15,uVar13,uVar7);
)if ((*(char *)(*(long *)(*(long *)(param_1 + 0x40) + 8) + 0x18) == '\0') && (auVar16 = FUN_10064cb60(uVar6), uVar7 = auVar16._8_8_, auVar16._0_8_ != 0)) { uVar15 = *(undefined8 *)(*(long *)(*(long *)(param_1 + 0x38) + 8) + 0x18); uVar7 = _objc_retainAutorelease(uVar6); uVar13 = FUN_10063b7a0(); uVar7 = FUN_10064cb60(uVar7); uVar13 = FUN_1004a2e04(uVar15,uVar13,uVar7); if ((int)uVar13 != 0) { FUN_100668640(&_OBJC_CLASS_$_IMRGLog);
failed building validation
:0x47d010
(FUN_10047d010(uVar5,0,0,&local_90,&local_94);
)if (iVar1 == 0) { local_90 = 0; uStack_8e = 0; uStack_8c._0_4_ = 0; local_94 = 0; uVar5 = FUN_100667e60(IVar7); auVar20 = FUN_10047d010(uVar5,0,0,&local_90,&local_94);
Also interesting app I found that seems to "ask nicely" for registration data instead of reversing binaries. https://github.com/TaeHagen/Mac-Hardware-Info
Also interesting app I found that seems to "ask nicely" for registration data instead of reversing binaries. TaeHagen/Mac-Hardware-Info
@skiman6010 From a quick skim through the code, it looks like this is probably the file doing the 'main work' of gathering system info:
Which then is seemingly encoded into a QR code, which I imagine may then be passed back to whatever app consumes it.
Looking at the BlueBubbles FAQ, it seems like the basic functionality just reads from the iMessage database using full disk access. It also briefly mentions the QR code part:
Q: Why does the MacOS server require Full Disk Access? MacOS does not allow app's to read from iMessage's chat database unless they have Full Disk Access.
Q: Can I share my QR Code? No, your QR code should be private because it contains your server password and Firebase credentials. If it gets leaked, you should redo your Firebase setup and change your server password to avoid compromising your messages.
Then it goes deeper into enabling the 'private API' by disabling SIP; which from a quick skim, sounds like that allows it to inject a helper process into the iMesssage app that allows calling internal functions:
In order to get Private API features, you must disable MacOS extra security measures, called System Integrity Protection (SIP). The reason for this is because Apple does not let us access the internal iMessage code to do things like send reactions if SIP is enabled. When disabled, we can inject a helper process into the iMessage app to call the internal functions for us. In a way, disabling SIP is similar to Jailbreaking your iPhone.
Based on the fact that it reads the database in the 'normal' mode, or injects a co-process in the 'private API' mode; I wonder if this is actually doing the same sort of things as the Beeper version was; as I believe that was actually talking directly to Apple's remote API once it had the required auth creds; whereas BlueBubbles seems as though it's just piggybacking off/manipulating the Messages app itself.
Probably out of scope of this issue but I did find it interesting. This app is actually used for a fork of BlueBubbles, that's currently being called OpenBubbles. I should have added the extra context in my original comment.
You are correct, BlueBubbles by itself uses a Mac as a bridge like the old Beeper bridge.
OpenBubbles uses the rustpush repo to authenticate itself as a Mac. It is also supposedly compatible with Beeper registration codes. https://github.com/TaeHagen/bluebubbles-app/tree/rustpush
OpenBubbles uses the rustpush repo to authenticate itself as a Mac. It is also supposedly compatible with Beeper registration codes. TaeHagen/bluebubbles-app@
rustpush
@skiman6010 Ah, true; fair enough, that makes sense.
Just wanted to report that this tool can't seem to get the offsets in the
identityservicesd
for MacOS 15. A couple of the addresses are blank.Here is the binary. identityservicesd.zip