Apple Lightning (cont.) - serial number reading

Created on 29.10.20
Here is my little article about (almost) everything I know about how serial number reading from dead devices is done. But first a tiny warning...
Read this article on your own risk! The information in this artcile is based on a lot of AppleInternal materials (leaked apps, magic cables and etc.). And of course on my own research and RE too. This write-up might use incorrect or just weird terms and turn out partially or completely wrong!

Why am I talking about this?

Recently several people contacted me with an issue where their Serial Number Reader cables randomly stopped working:
So in order to try to help them, I had to reverse engineer (just a little bit) Serial Number Reader application, sniff some HTTP-requests and try to understand how it's supposed to work and why it doesn't at all in some cases

Resulting research seems quite interesting to me, so I'm sharing it with anyone who might be interested in it as well

Please note, that since it's a client-server interaction that's discussed here, we can know for sure only what happens on the client side. What about the server side, we can only guess - successfully or not

First of all, let's sort out the major terms briefly:

What is Serial Number Reader cable?


Flamingo-styled Serial Number Reader cable

It's a cable that's used by Apple and some of their resellers to get serial number from a dead iOS-device (given its Tristar/Hydra is still alive)

Based on the Kanzi cable. It's not even based, it is Kanzi. Just with different USB Product ID (0x1621 on Kanzi vs. 0x1624 on SNR) and a little bit different housing (lacks the beautiful artwork on the front, blocks access to the female Lightning and ARM Cortex Debug 10-pin connector, which isn't even soldered to the board). Firmware is same

Also known as just SNR and Nova Cable

What is Kanzi cable?


Notice the external debugging port and the female Lightning

It's a cable that's used by Apple's own engineers to debug various hardware (mainly iOS-devices, of course) with SWD (Serial Wire Debug - JTAG for ARM cores)

Yes, you can use Kanzi to read serial numbers and use SNR to debug iPhones (since it's the same hardware and software). Requires little patches in Serial Number Reader.app (macOS app designed to make use of a SNR - that's the one to be RE'd) and astrisprobed (piece of Astris - debugger software designed to make use of Apple's SWD-probes, Kanzi is one of them) respectively

Based on STM32F407IGH6 MCU (ARM Cortex-M4, up to 168 MHz)

Also known as KanziSWD

What else should you know?

There're way too many aspects of Kanzi/SNR to be described in this little article, so we will only focus on those several of them we'll need later:
  1. Environment variables - Kanzi/SNR can store some persistent data (across reboots) that defines its behavior. Accessible via astrisctl tool:

    There are pretty many of those, but the most useful of them is debug. Setting certain bits of this var unlocks additional debug output to cable's VCOM (Virtual COM port):
    ...and even some features like diagnostics
  2. Relays - similar to env vars, but used to send commands, read some indicators and in some other ways control behavior of a cable. Non-persistent. Just like the env vars, accessible via astrisctl:

    You can also see diags functionality in action here

  3. I2C - Kanzi's MCU communicates with various other hardware like DAC, EEPROMs, Tristar (yes, it has Tristar - appears to be TI THS7383) and Mozart (that's the root of the problem by the way - details below) over I2C interface. Two I2C devices are available over USB from outside:
    • NVRAM EEPROM (address 0x56/0x57) - stores env vars
    • Buffer (address 0xAE/0xAF) - used for a data exchange between KFL (Kanzi Fraud Library - part of Serial Number Reader.app used to interact with Kanzi/SNR). Most likely it's kind of a pseudo-device

    Accessible over Astris:

The algorithm

All the information below is based on the RE of Serial Number Reader.app (its binary is fully symbolicated, by the way, and this is very convinient) and sniffing a traffic between my machine and ttrs.apple.com (I'm using a program called Charles for this purpose)

To understand what's going on below, you might want to read this first

Reading ESN

First of all, Serial Number Reader.app reads ESN (Electronic Serial Number). Here is how it's done:
  1. Write "\x02Get ESN" to the data exchange buffer
  2. Write 0x1 to authcmd relay and wait until it becomes 0x0 again - from now on we will name this procedure - writing a number to authcmd relay and waiting - like this: to execute command
  3. Read 16 bytes from the data exchange buffer. First 8 bytes are an ESN
Now let's see what happens on IDBUS meanwhile:
  1. WAKE is initiated by a cable:

    74 00 02 1f -> 75 60 00 00 00 00 00 df

    From now on data portions of IDBUS requests/responses are colored for convinience

    If we decode the response, we'll see that such sequence must enable debug UART on ACC lines. What?! Well, the decoding tables from my previous article were grabbed from TI THS7383 Tristar datasheet, which was one of the first of them. On Tristar 2 and Hydra this sequence enables SNR mode
  2. Now, it sends "\x02Get ESN":

    7e 09 aa -> 7d 02 47 65 74 20 45 53 4e 00 fb

  3. Finally, it receives ESN (first 8 bytes, in reversed order) along with something else (later 8 bytes):

    7c R E D A C T E D R E D A C T E D aa -> 7f 8c

First request

After acquiring ESN it sends it to Apple:

GET /TRISTAR/FGSN?VERSION=1&DEVICE_ID=B64_ESN_REDACTED HTTP/1.1
Host: ttrs.apple.com
Accept: */*
Accept-Language: en-us
Connection: keep-alive
Accept-Encoding: gzip, deflate
User-Agent: Serial%20Number%20Reader/105 CFNetwork/978.3 Darwin/18.7.0 (x86_64)

ESN is encoded as Base64 here

Response is a JSON:

{
 "SESSION_ID": "9112f805-74da7718-0175-6609296b-42de",
 "MESSAGE": "ok",
 "STATUS": "200",
 "KEYSET_ID": "2",
 "CHALLENGE": "B64_CHALLENGE_REDACTED",
 "DEVICE_ID": "B64_ESN_REDACTED",
 "VERSION": "1"
}

The most meaningful fields here are CHALLENGE (8 bytes blob) and KEYSET_ID (an integer in range of 1 and 3)

Get verifier

The next step is to get something called VERIFIER (another 8 bytes blob)

To accomplish this task we have to do the same thing like in the step 1 (reading ESN), but instead of "\x02Get ESN" we need to send the following structure:

struct {
 uint8_t keyset; //subtract 1 from the server one!
 uint64_t challenge;
}

Where keyset is the KEYSET_ID from the server response with 1 subtracted and challenge is CHALLENGE from the same entity

As result, we can see this on IDBUS:

7e 09 aa -> 7d 02 R E D A C T E D d3
7c R E D A C T E D R E D A C T E D 67 -> 7f 8c

The latter 8 bytes are our VERIFIER

Compose a final request's payload

This step is quite simple - add the VERIFIER to the previous request's response body. Also remove MESSAGE, STATUS and VERSION from there. So in the end it looks like this:

{
 "KEYSET_ID":"2",
 "CHALLENGE":"B64_CHALLENGE_REDACTED",
 "VERIFIER":"B64_VERIFIER_REDACTED",
 "DEVICE_ID":"B64_ESN_REDACTED",
 "SESSION_ID":"9112f805-74da7718-0175-6609296b-42de"
}

Sign the payload

Everything described above can be done with your own hardware. Apple couldn't let this happen, so they added another layer of protection to this mechanism - they sign the final request. Here is how it's done:
  1. Take a SHA1 hash of the JSON payload we prepared in the previous step
  2. Write that hash to the exchange buffer
  3. Execute the command 0x4, this will make it remember the hash
  4. Execute the command 0x5, then read 2 bytes from the exchange buffer - this is a length of the signature [UPD: 24/10/21] in big endian, must always be 128
  5. Execute the command 0x6, then read 128 bytes from the exchange buffer - this is our signature
Now we need to read a public certificate:
  1. Execute the command 0x2, then read 4 bytes from the exchange buffer - this is a length of the certificate [UPD: 24/10/21] this is a struct that consists of two big-endian 16-bit integers - the first is length, the the second is offset (returns some garbage for this command). We obviously need to extract the length
  2. Given the certificate length, make a loop that will read the certificate itself with portions of 128 bytes: write (offset & 0xFF00) >> 8 (basically offset / 128) as 32-bit integer to the exchange buffer, execute the command 0x3 - this sets current offset for reading, and read from the exchange buffer - this is the requested portion of the public certificate [UPD: 24/10/21] write a similar struct as in step 1 with length of (obviously) 128 and your current offset to the exchange buffer, execute the command 0x3 - this makes it extract a portion you requested with the struct, and read from the exchange buffer - this is the actual part of the public certificate
Here is how it looks like:

Compose and send the final request

Time to compose everything we gathered in the previous steps in a single request and finally get our SN. Compose a POST request with a form data: with the following fields (all values encoded as Base64):
  • B64_PAYLOAD - the JSON we prepared before
  • B64_SIGNATURE - the signature
  • B64_CERTIFICATE - the certificate
Here is how it might look like:

POST /TRISTAR/FGSN?VERSION=1 HTTP/1.1
Host: ttrs.apple.com
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Accept: */*
User-Agent: Serial%20Number%20Reader/105 CFNetwork/978.3 Darwin/18.7.0 (x86_64)
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Content-Length: 1696

B64_PAYLOAD=REDACTED&
B64_SIGNATURE=REDACTED&
B64_CERTIFICATE=REDACTED

And here is what we receive:

{
 "SESSION_ID": "9112f805-74da7718-0175-6609296b-42de",
 "MESSAGE": "ok",
 "STATUS": "200",
 "VERSION": "1",
 "FGSN": "SN_REDACTED"
}

The serial number is in the FGSN field

So what can go wrong?

The public certificate can expire, like this one:

In this case the Apple's server will return code 403 (Forbidden) and the status message will tell you about a signature verification failure

According to my information, all the 2XXXXX series of cables (and supposedly everything older) and even some of the 3XXXXX one (definetely 310XXX) have certificates that expired

Can we sign requests with a new certificate?

I suppose not, as this will also require a hash to be signed with a new private key

Can we extract a private key?

That's a very interesting question, indeed. The problem is that main MCU of Kanzi or SNR (which we can fully control) is not in charge of signature generation

Instead, it's done via Mozart, which can be talked to via I2C (its address appears to be 0x20/0x21). What it is by its nature is currently unknown [UPD: 24/10/21] @el1ng0 suggested this might be an MFI chip, and other sources confirmed this. On some of the previous screenshots in this article you could notice a special diagnostic message:

authInit:AUTH_DEV_VER = 5 AUTH_FW_VER = 1 AUTH_PRO_MAJ_VER = 2 AUTH_PRO_MIN_VER = 0 AUTH CERT LEN = 908 authSelfTestProcControl: Passed [0xc0]

The AUTH_FW_VER bit though is telling us that it might be something that runs some sort of firmware

So, nothing to be done?

Mozart doesn't require any auth to sign a hash. So you can theoretically abuse it - I've created a little application that takes advantage of this flaw (is it?) and allows to sign arbitrary hash given a Kanzi or SNR (requires patched astrisprobed in the current implementation) is connected:

My tests show that Mozart does have some kind of throttling. Well, maybe the tests are just bad:

while true; do kanzifraudctl 7f2db00ae1cb2a543d65ccf699185b44d6bc5266; done

...but at some point the procedure begins to take more and more time, and when this time is around 0.52 seconds it stops generating signatures at all:

If there's any demand for this tool, I'll publish its source code to my GitHub