libimobiledevice / libirecovery

Library and utility to talk to iBoot/iBSS via USB on Mac OS X, Windows, and Linux
https://libimobiledevice.org
GNU Lesser General Public License v2.1
550 stars 207 forks source link

MacBook Pro M3 2023 Unable to connect to device #123

Open shadowwalkersteam opened 10 months ago

shadowwalkersteam commented 10 months ago

I've M3 Mac connected in DFU mode, Apple Configurator is showing the DFU Mac connected but irecovery is not showing it. Below are the logs coming up when I executed with -v -q parameters.

Attempting to connect... 
Attempting to connect... 
Attempting to connect... 
Attempting to connect... 
Attempting to connect... 
Attempting to connect... 
ERROR: Unable to connect to device

I want to get the ECID of the connected Mac. I've latest compiled binaries. need your help @nikias

nikias commented 10 months ago

I assume that you are trying with the latest code?

shadowwalkersteam commented 10 months ago

I assume that you are trying with the latest code?

Yes I'm trying with the latest code.

shadowwalkersteam commented 10 months ago

This is how it is showing in USB Tree and Apple Configurator

Screenshot 2023-11-17 at 5 24 56 PM

Screenshot 2023-11-17 at 5 24 16 PM

Screenshot 2023-11-17 at 5 26 11 PM

nikias commented 10 months ago

Ahhh I see. It's using KIS DFU. I have pending commits to push that add support for it (macOS and Linux -- windows not yet). Will push it later today and then you can try again.

nikias commented 10 months ago

I actually pushed the commits now. Let me know if this works now.

shadowwalkersteam commented 10 months ago

Sure, I'll test this.

shadowwalkersteam commented 9 months ago

@nikias it is working with the new commits. But the thing is if I have 2 Macs connected in DFU it only gives 1 device, Is there a way to list them all? Screenshot 2023-11-20 at 3 34 32 PM

nikias commented 9 months ago

Thanks for confirming. The irecovery tool hasn't been designed to support multiple devices; the underlying library code could handle it though. The enumeration of devices is missing, then it would be just a matter of calling irecv_open_with_ecid with the given ECID of the devices found. I will see what we can do here.

shadowwalkersteam commented 9 months ago

@nikias I got M3 Pro (15,6) as well and that is not showing in irecovery -q and this one looks different from the simple M3 in terms of response from System report. Please check the below screenshots. However the simple M3 (15,3) is showing perfectly fine with the last changes you made.

Screenshot 2023-12-07 at 4 42 46 PM Screenshot 2023-12-07 at 4 43 12 PM

nikias commented 9 months ago

I see. The USB Product ID is different, 0xf014 as you can see in the screenshot. This is Port DFU mode, not sure how this is different...

shadowwalkersteam commented 9 months ago

I see. The USB Product ID is different, 0xf014 as you can see in the screenshot. This is Port DFU mode, not sure how this is different...

So what change you recommend should I do to add this product support?

shadowwalkersteam commented 9 months ago

Also look at the CPID and BDID they look totally different, and this Mac15,6 doesn't have these CPID and BDID.

nikias commented 9 months ago

Yes it's a totally different mode it seems... you could try to add the 0xf014 here: https://github.com/libimobiledevice/libirecovery/blob/1a12fc2877a5aac5ef28b196753c83188fc13ef0/src/libirecovery.c#L1644 but I don't really think this will work

nikias commented 9 months ago

Just for reference, adding this here: https://infosec.exchange/@siguza/111118479751581819

Siguza commented 9 months ago

Also FWIW if you use macvdmtool to put the target into DFU, then the AP will be in DFU but the USB-C port will not be, so it will enumerate as KIS (Debug USB).

scwgoire commented 9 months ago

Hi guys, we started hacking around with idevicerestore and M3 Pro laptop. Our findings are the same:

Not sure yet what we can do with idevicerestore from either mode, we'll start digging into that. We may end up with the oscilloscope to reverse any new VDM that may be required.

Happy to share our future findings to contribute to M3 (not yet tried) and M3 Pro (M3 Max unlikely at this stage) support to idevicerestore as we did with M1 a few years back. Please reach out to me.

Siguza commented 9 months ago

@scwgoire KIS DFU support is tracked in #106 and should be supported on macOS and Linux hosts already. Port DFU mode seems to want a personalised USB-C firmware uploaded first. The CPID and BDID of Port DFU Mode are also found in the BuildManifest.plist of devices that have it, and the BDID of Port DFU is just 00000000[BDID][CPID]00 of the AP anyway. The lower two zeroes may also be SCEP of the AP... or just zeroes, I can't tell. Either way, MobileDevice.framework has the code to deal with Port DFU somewhere, as always.

tbellavia commented 8 months ago

Following the @scwgoire message, when testing against M3 in KIS mode, we have a segfault on _irecv_handle_device_add on the strcpy(serial_str, client->device_info.serial_string) call while running idevicrestore -ey file.ipsw.

The reason is that when calling the libusb_usb_open_handle_with_descriptor_and_ecid function to get client infos it never try to get the serial_str because it does not validate the product_id != KIS_PRODUCT_ID condition. Thus client->device_info.serial_string is null.

We tried to forcefully enter the client->mode != KIS_PRODUCT_ID condition in the libusb_usb_open_handle_with_descriptor_and_ecid function, but irecv_get_string_descriptor_ascii does not fill the serial_str buffer, and the function returns with status LIBUSB_ERROR_INVALID_PARAM (-2). This is expected because when doing an lsusb, iSerial is empty :

  idVendor           0x05ac Apple, Inc.
  idProduct          0x1881
  bcdDevice            4.00
  iManufacturer           1 Apple Inc.
  iProduct                2 Debug USB
  iSerial                 0

When recovering using apple configurator 2 the usb "sequence" seems to be :

The question now seems to be, how to transition from KIS to Recovery mode 2 (our setup is to go to KIS directly with USB VDM messages, so we are not yet blocked by port DFU) ?

How can we help ?

tbellavia commented 8 months ago

For the MobileDevice.framework, I don't believe the source is open, can you give me some insight on where to start exploring ?

Siguza commented 8 months ago

@Cardiox12 file path is /Library/Apple/System/Library/PrivateFrameworks/MobileDevice.framework/MobileDevice. The distinction between Port DFU, USB DFU, Recovery, etc. happens in _deviceConnected, and for Port DFU it calls _AMPortDFUModeDeviceCreate. I haven't looked into it much, but from there it seems to go through multiple layers, look for symbols with PortDFU, Ace3 and UARP/uarp in their name.

nikias commented 8 months ago

@Cardiox12 in KIS mode, the serial string is taken from a buffer (KIS_device_info) read from the device, that contains a usb descriptor buffer. It's not read from a typical string descriptor.

nikias commented 8 months ago

You can turn on debug info for libirecovery by setting an environment variable, so you can run

LIBIRECOVERY_DEBUG_LEVEL=1 idevicerestore file.ipsw

Look out for a line Loading device info in KIS mode... and see what follows.

scwgoire commented 8 months ago

You can turn on debug info for libirecovery by setting an environment variable, so you can run LIBIRECOVERY_DEBUG_LEVEL=1 idevicerestore file.ipsw Look out for a line Loading device info in KIS mode... and see what follows.

idevicerestore SEGFAULTs (in libirecovery.c:2437) before we can get a chance to get this message. This is because it uses libusb_usb_open_handle_with_descriptor_and_ecid which doesn't implement reading serial string from KIS. (This is a piece of code that is located in irecv_open_with_ecid).

We managed to leverage code from irecv_open_with_ecid to get the serial string and move on. We are now stuck further down the process in idevicerestore_start, we need to check out why. Will get back to you in January with more findings, this is holidays time now 😁.

nikias commented 8 months ago

Just tested on macOS and it has the same issue when the _irecv_handle_device_add code is used; the KIS init is missing. Working on a fix.

tbellavia commented 8 months ago

@nikias I tried what you merged for M3 support on M3, install was ok, we'll try on M3 Pro tomorrow.

nikias commented 8 months ago

Thanks, excited to see the results.

tbellavia commented 8 months ago

We tried on M3-Pro, it does not work yet, we may not be very far from making it. It is probably caused by how idevicerestore handles the 4 USBPortControllers (those ones are not present on M3 "not pro" variant).

See the log file m3-pro.log

First clue is line 198 (not sure what RestoreRequestRules are) :

DEBUG: tss_request_add_ap_tags: Skipping 'USBPortController1,USBFirmware' as it doesn't have RestoreRequestRules
DEBUG: tss_request_add_ap_tags: Skipping 'USBPortController2,USBFirmware' as it doesn't have RestoreRequestRules
DEBUG: tss_request_add_ap_tags: Skipping 'USBPortController3,USBFirmware' as it doesn't have RestoreRequestRules
DEBUG: tss_request_add_ap_tags: Skipping 'USBPortController4,USBFirmware' as it doesn't have RestoreRequestRules

Second clue, is line 2386-2421:

Third clue:

We are working on it, if you have any ideas on where we can start, we would love to hear about it.

scwgoire commented 8 months ago

About lin 4180, the culprit is in restore_get_generic_firmware_data:

        const char* response_ticket = NULL;
    if (PLIST_IS_ARRAY(response_tags)) {
        plist_t tag0 = plist_array_get_item(response_tags, 0);
        if (tag0) {
            response_ticket = plist_get_string_ptr(tag0, NULL);
        }
    }

If multiple tickets are requested, we only keep track of the first one to verify the response is correct. However this simply is a warning, no big deal.

Some oddities:

Keeping up with digging... 😄

scwgoire commented 8 months ago

Little update: I couldn't find something suspicious in the code today. idevicerestore seems to be performing the job, properly forwarding Ace3 TSS requests from the mac to the TSS server and sending back the tickets to the mac.

The following questions remain unanswered:

The TicketName query parameter for 4 tickets being USBPortController1,Firmware may indicate a confusion in the firmware request order (the first usb port controller firmware request being for controller 4) or just a "why not? there is only room for one value there"

Still pretty much in the dark there.

scwgoire commented 8 months ago

Allright, time for a little update. I have been skimming through a USB dump of the communication between an M2 Pro being restored and Apple Configurator 2 restoring it. The interesting fact is the same happens there (USBPortController4,USBFirmware is asked twice, asks for 4 tickets the first time, then 3 tickets only).

In this dump, after the 3 tickets are send back to the mac, I get 7 ProgressMsg in a row and then CHECKPOINT_COMPLETE for update_ac3.

With idevicerestore I can't reach this checkpoint, it fails before (see my previous log's ending, ReverseProxy[Ctrl]: Connection closed)

I now think the problem is somewhere else. If the Ace3 TSS process was incorrect, it would probably fail before asking for the 3 tickets.

scwgoire commented 8 months ago

Allright, I found something promising!

Maybe restore_device() function doesn't provide enough data to the mac, here are the RestoreOptions I can't find any reference for in idevicerestore source code:

AsyncDataRequestMsg looks important because here is the plist requesting the RecoveryOS image. Not sure it will be used by the mac if not advertised by idevicerestore though.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>DataPort</key>
    <integer>12346</integer>
    <key>DataType</key>
    <string>RecoveryOSASRImage</string>
    <key>MsgType</key>
    <string>AsyncDataRequestMsg</string>
</dict>
</plist>

Some more fun next time... see ya! 😄

scwgoire commented 6 months ago

Allright guys, I'm back with some new, I hope you didn't miss me 😄

Looks like this is a usbmuxd problem after all. After lots of attempts I could sometimes have idevicerestore to finish. Every time but once, it happened just after a fresh reboot of the system running idevicerestore (a RaspberryPI).

So, here is how it goes in idevicerestore when it fails (see previously posted log):

Now, on the usbmuxd side (in very verbose mode), we can also observe the 2 pauses (I guess the mac has stuff to do there), but after the second one an error happens. (with errno -104 or -71).

[105.956251] [000005c9] libusb: debug [libusb_handle_events_timeout_completed] doing our own event handling
[105.956324] [000005c9] libusb: debug [usbi_wait_for_events] poll() 3 fds with timeout in 0ms
[105.956385] [000005c9] libusb: debug [usbi_wait_for_events] poll() returned 1
[105.956446] [000005c9] libusb: debug [reap_for_handle] urb type=3 status=-104 transferred=0
[105.956481] [000005c9] libusb: debug [handle_bulk_completion] handling completion status -104 of bulk urb 1/1
[105.956513] [000005c9] libusb: debug [handle_bulk_completion] all URBs in transfer reaped --> complete!
[105.956546] [000005c9] libusb: debug [usbi_handle_transfer_completion] transfer 0x5593878a68 has callback 0x557efe869c
[105.956583] [000005c9] libusb: debug [libusb_submit_transfer] transfer 0x5593878a68
[105.956616] [000005c9] libusb: debug [submit_bulk_transfer] need 1 urbs for new transfer with length 16384
[105.961133] [000005c9] libusb: debug [submit_bulk_transfer] first URB failed, easy peasy
[105.961194] [000005c9] libusb: debug [reap_for_handle] urb type=3 status=-104 transferred=0
[105.961228] [000005c9] libusb: debug [handle_bulk_completion] handling completion status -104 of bulk urb 1/1
[105.961255] [000005c9] libusb: debug [handle_bulk_completion] all URBs in transfer reaped --> complete!
[105.961281] [000005c9] libusb: debug [usbi_handle_transfer_completion] transfer 0x5593877ba8 has callback 0x557efe869c
[105.961309] [000005c9] libusb: debug [libusb_submit_transfer] transfer 0x5593877ba8
[105.961339] [000005c9] libusb: debug [submit_bulk_transfer] need 1 urbs for new transfer with length 16384
[105.965804] [000005c9] libusb: debug [submit_bulk_transfer] first URB failed, easy peasy
[105.965869] [000005c9] libusb: debug [reap_for_handle] urb type=3 status=-104 transferred=0
[105.965902] [000005c9] libusb: debug [handle_bulk_completion] handling completion status -104 of bulk urb 1/1
[105.965928] [000005c9] libusb: debug [handle_bulk_completion] all URBs in transfer reaped --> complete!
[105.965955] [000005c9] libusb: debug [usbi_handle_transfer_completion] transfer 0x5593877c98 has callback 0x557efe869c
[105.965985] [000005c9] libusb: debug [libusb_submit_transfer] transfer 0x5593877c98
[105.966011] [000005c9] libusb: debug [submit_bulk_transfer] need 1 urbs for new transfer with length 16384
[105.970442] [000005c9] libusb: debug [submit_bulk_transfer] first URB failed, easy peasy

Right after that, clients are disconnected.

[105.993208] [000005c9] libusb: debug [libusb_handle_events_timeout_completed] doing our own event handling
[105.993266] [000005c9] libusb: debug [usbi_wait_for_events] poll() 3 fds with timeout in 0ms
[105.993309] [000005c9] libusb: debug [usbi_wait_for_events] poll() returned 1
[105.993340] [000005c9] libusb: debug [usbi_remove_event_source] remove fd 10
[105.993391] [000005c9] libusb: debug [usbi_handle_disconnect] device 3.19
[105.993459] [000005c9] libusb: debug [libusb_handle_events_timeout_completed] doing our own event handling
[105.993493] [000005c9] libusb: debug [handle_events] event sources modified, reallocating event data
[105.993531] [000005c9] libusb: debug [usbi_wait_for_events] poll() 2 fds with timeout in 0ms
[105.993568] [000005c9] libusb: debug [usbi_wait_for_events] poll() returned 1
[105.993594] [000005c9] libusb: debug [handle_event_trigger] event triggered
[105.993618] [000005c9] libusb: debug [handle_event_trigger] hotplug message received
[17:59:23.050][3] Removed device 1 on location 0x30013
[17:59:23.050][5] connection_teardown dev 1 sport 2 dport 62078
[17:59:23.050][4] Client 11 is going to be disconnected: idevicerestore[1469]
[17:59:23.050][5] connection_teardown dev 1 sport 5 dport 62078
[17:59:23.050][4] Client 12 is going to be disconnected: idevicerestore[1469]
[17:59:23.050][5] connection_teardown dev 1 sport 6 dport 1082
[17:59:23.050][4] Client 13 is going to be disconnected: idevicerestore[1469]
[17:59:23.051][5] client_device_remove: id 1
[17:59:23.051][5] Client 9 output buffer got tag 0 msg 8 payload_length 282
[17:59:23.051][2] Cannot find device entry while removing USB device 0x5593877940 on location 0x30013
[17:59:23.051][5] usb_disconnect: cancelling RX xfer 0x5593878a68
[105.995436] [000005c9] libusb: debug [libusb_cancel_transfer] transfer 0x5593878a68
[17:59:23.052][5] usb_disconnect: cancelling RX xfer 0x5593877ba8
[105.995498] [000005c9] libusb: debug [libusb_cancel_transfer] transfer 0x5593877ba8
[17:59:23.052][5] usb_disconnect: cancelling RX xfer 0x5593877c98
[105.995558] [000005c9] libusb: debug [libusb_cancel_transfer] transfer 0x5593877c98

Then usbmuxd goes in an infinite loop, it has to be kill -9'ed and restarted.

[105.995593] [000005c9] libusb: debug [libusb_handle_events_timeout_completed] doing our own event handling
[105.995621] [000005c9] libusb: debug [usbi_wait_for_events] poll() 2 fds with timeout in 1ms
[105.996742] [000005c9] libusb: debug [usbi_wait_for_events] poll() returned 0
[105.996790] [000005c9] libusb: debug [libusb_handle_events_timeout_completed] doing our own event handling
[105.996822] [000005c9] libusb: debug [usbi_wait_for_events] poll() 2 fds with timeout in 1ms
[105.997936] [000005c9] libusb: debug [usbi_wait_for_events] poll() returned 0
[105.997976] [000005c9] libusb: debug [libusb_handle_events_timeout_completed] doing our own event handling
[105.998009] [000005c9] libusb: debug [usbi_wait_for_events] poll() 2 fds with timeout in 1ms
[105.999127] [000005c9] libusb: debug [usbi_wait_for_events] poll() returned 0
[105.999174] [000005c9] libusb: debug [libusb_handle_events_timeout_completed] doing our own event handling
[105.999206] [000005c9] libusb: debug [usbi_wait_for_events] poll() 2 fds with timeout in 1ms
[106.000338] [000005c9] libusb: debug [usbi_wait_for_events] poll() returned 0
...

As for when the operation succeeds, I suspect this is a lucky timing, instead of failing after sending the USBFirmware with the following log lines,

...
Sending FirmwareResponse data now...
Done sending FirmwareUpdater data
ReverseProxy[Ctrl]: Connection closed
ReverseProxy[Ctrl]: Terminating
ReverseProxy[Ctrl]: (status=2) Terminated
ERROR: Could not read data (-256). Aborting.
ERROR: Unable to restore device

the restored_receive call fails with a timeout which somehow allows it to recover and keep going on until the restore succeeds.

...
Sending FirmwareResponse data now...
Done sending FirmwareUpdater data
No data to read (timeout)
Unhandled progress operation 85 (85)
Checkpoint completed id: 0x131D (update_ace3) result=0
Checkpoint started   id: 0x1318 (update_usbcretimer)
Unknown operation (82)
About to send BuildIdentity Dict...
Sending BuildIdentityDict now...
Done sending BuildIdentityDict
...

Does this ring any bell?

scwgoire commented 6 months ago

Ok folks, this seems to be harder than I thought. Here is a kernel log for the event:

Feb 22 14:51:10 brashat kernel: dwc2 fe980000.usb: Not connected
Feb 22 14:51:10 brashat kernel: usb 3-1: usbfs: usb_submit_urb returned -19
Feb 22 14:51:10 brashat kernel: usb 3-1: USB disconnect, device number 42
Feb 22 14:51:13 brashat kernel: usb 3-1: new high-speed USB device number 43 using dwc2
Feb 22 14:51:13 brashat kernel: usb 3-1: New USB device found, idVendor=05ac, idProduct=1905, bcdDevice=15.07
Feb 22 14:51:13 brashat kernel: usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
Feb 22 14:51:13 brashat kernel: usb 3-1: Product: Mac
Feb 22 14:51:13 brashat kernel: usb 3-1: Manufacturer: Apple Inc.
Feb 22 14:51:13 brashat kernel: usb 3-1: SerialNumber: 00006030-001031811A28001C

As you can see, when the restore process fails after sending USBPortController firmware, there is an EPROTO error on the USB bus.

My current guess is the USB controller on the mac is restarted after new firmware is applied. Sometimes this generates a protocol error (due to an USB timeout?) and Linux removes the device, and sometimes the device has better luck and this is only seen as a "USB lag" by the kernel.

This really sucks because usbmuxd is made to disconnect clients when the device is removed (and that shouldn't change IMHO) and idevicerestore relies on a stable connection to usbmuxd during the restore operation.

Do you think we could and/or should expect usbmuxd disconnections during the restore process and implement a retry logic somewhere there?

nikias commented 5 months ago

Hi all, I added port DFU support to libirecovery with the latest commit. However this does not allow port DFU restoring, this will be implemented in idevicerestore.

I already figured out a part of the process, namely:

I can create that file now with the required checksum. This file is sent to the port DFU device. Not sure yet what is the next step. I just wanted to document this somewhere.

nikias commented 5 months ago

In case you missed it, I got Port DFU restore working: https://github.com/libimobiledevice/idevicerestore/commit/e4a5ac4114177293e3a1b555ee767377b21d4432.

The only thing wrong was that the nonce I get via USB string descriptor has to be reversed in order... took me only a day to see that 🤦