Fine, I'll Download It For You

I recently found myself needing to reset an iPhone 6 to its factory defaults. There is some useful stuff to have if you’re trying to do this:

  • the passcode for the phone
  • the AppleID with which the phone is associated
  • an iTunes installation
  • an Internet connection capable of downloading the iPhone software image in 15 minutes

It turns out the first two are optional given the last two, or at least a reasonable facsimile. If you don’t have the last one, you have to fake it. Here’s how I spent my first day of funemployment compensating for some Apple engineer failing to consider that it might be nice to download a file before you need to use it.

get iTunes, the lazy way

Apple’s instructions for resetting a phone to its factory defaults give several suggestions that aren’t applicable if you don’t have credentials for it, and finally bottom out telling the user to boot the phone in recovery mode while connected to a computer running iTunes. I managed to boot up an old 27” iMac of my housemate’s which had been sitting in our basement waiting for me to wipe its hard drive, luckily running a new enough OSX to get the right version of iTunes to get the right version of the iPhone software for this particular iPhone. Great!

Given a working iTunes installation, booting the phone in recovery mode gives the option to “restore and update”, which will remove all information from the phone and update it to the latest iPhone OS. That sounds like what we want! Choosing to that gives a dialogue box with the change notes for the latest iOS (nice!) and a EULA to agree to (ugh, fine). Then iTunes goes to get the phone software…

…and fails, because the iPhone will only stay in recovery mode for 15 minutes.

a side note: I do not have very fast home internet

I don’t pay for fast internet at my house. There are a bunch of reasons:

  • it encourages me to screw around on the Internet instead of doing other stuff
  • it costs a lot of money, pretty frequently
  • most of the companies that will take your money in exchange for it are pretty evil

I do have some access via an experimental citywide LTE network and the Calyx Institute’s Sprint network member perk. Neither are fast enough to download the entire iOS image in 15 minutes. The best download speed I’ve seen out of either network is around 5MBps, but that’s under the best possible conditions. The iMac in the basement (oops) is not working under the best possible conditions, and it’s getting more like 100KBps when trying to download this file.

when reasonable options are exhausted, the unreasonable seems less exhausting

After searching fruitlessly for some way to get iTunes to pre-download the file it wanted to flash the phone with, I decided to figure out what it was looking for, download it onto another device, and then trick the iMac into downloading it from the other (local, faster) computer.

step 0: put something I control in the traffic stream

Did I mention that I don’t have root on the iMac, and therefore can’t easily invoke tcpdump on it? Well, I don’t have root on the iMac. I also don’t have root on any of the LTE devices. But the iMac has an Ethernet port, and so does a machine I do have root on, so I can use the second machine (let’s call it good-computer, because unlike everything else involved in this process it’s cooperative and plays well with others) as a bridge between the WiFi network provided by the LTE device and the iMac.

On good-computer, I have interfaces wlan0 (the WiFi interface talking to the LTE uplink) and eth0 (the Ethernet interface talking to the iMac). good-computer is a non-ancient Linux machine, so I need to enable traffic forwarding across interfaces:

sudo sysctl -w net.ipv4.conf.wlan0.forwarding=1

and then, since I want to allow the iMac to do whatever weird stuff it thinks is necessary to update the phone, set up NAT for the private network on the Ethernet side:

sudo iptables -I POSTROUTING -o wlan0 -j MASQUERADE

I’ll set up static networking on the eth0 side so the iMac can contact us:

sudo ip link set eth0 up
sudo ip addr add 192.168.3.1/24 dev eth0

and then, because I don’t know whether I can configure a network interface on the iMac without root access and can’t be bothered to find out, start up dnsmasq with a configuration serving IP addresses in this range on eth0. Now the iMac detects a working Internet connection automatically when I connect the Ethernet cable between good-computer and it.

I turn off the WiFi on the iMac to prevent it from deciding that interface is preferable to the Ethernet link and test that outside access still works by loading a website on the iMac, and sure enough, we’re ready to go!

step 1: spy stuff!

Now that good-computer can see all of the iMac’s traffic, we’ll try the restore operation in iTunes again, but we’ll start a packet capture first. We don’t know what we’re looking for, so we’ll capture everything that goes over the Ethernet interface:

sudo tcpdump -enn -w itunes_upgrade_phone.pcap -i eth0`

and wait. While the iMac is solidly in the middle of its “downloading” step, I take another peek to see what host it’s busy communicating with:

sudo tcpdump -Xvvenni eth0 tcp

and notice that I see a lot of traffic on port 80, which is a bit surprising. Port 80 is unauthenticated HTTP, and I would have really expected this request to be authenticated and encrypted with TLS, traveling over port 443! This is great news for us, because the GET request from the iMac will be unencrypted and we can just look in the traffic stream to find out what it is.

To start, let’s use tshark to get the full URIs of all of the HTTP requests made to hosts in apple.com. The -r itunes_upgrade_phone.pcap tells tshark to read from the packet capture we made previously. -Y 'http.request.full_uri contains "apple.com"' tells tshark that we’re only interested in packets which contain HTTP requests, where the full URI contains apple.com (we’re betting that most of the interesting traffic goes there). -Tfields -e 'http.request.full_uri tells tshark that we want to see the full URI of any such packets it finds.

The syntax of the arguments to -Y and -Tfields -e is the same as filter expressions in wireshark, which provides more immediate and helpful feedback on constructing queries but is more annoying to get information out of. This was my first time using tshark and I have to say that I much prefer it to fussing around in wireshark for tasks like this one.

And indeed, we find a few promising-looking requests:

$ tshark -r itunes_upgrade_phone.pcap -Y 'http.request.full_uri contains "apple.com"' -Tfields -e 'http.request.full_uri'
http://init-p01st.push.apple.com/bag
http://appldnld.apple.com/ios10.3.2/[redacted]/iPhoneiTunesUpdateReadMe.ipd
http://appldnld.apple.com/ios10.3.2/[redacted]/iPhone_5.5_10.3.2_14F89_Restore.ipsw

(I’ve redacted some UUID-looking numbers from the above URIs, because I have no idea whether they’re sensitive.)

Now that we have filenames, we can try to download them on good-computer, which won’t give up so easily.

step 2: more waiting

wget http://appldnld.apple.com/ios10.3.2/[redacted]/iPhone_5.5_10.3.2_14F89_Restore.ipsw takes about 4 hours to complete, so in that time I port this blog from Hugo to Octopress. I grab the ReadMe file too for good measure, since good-computer will be pretending to be appldnld.apple.com for all requests.

step 3: serve up these files on port 80

I hear a lot of people like using nginx, so I install it and configure it to serve these files up on port 80. I don’t want to make the directory structure reflecting the UUID in the request and I want to still serve the files if the host makes requests to a different directory, so I throw in some rewrite rules:

http {
	# default stuff elided
        server {
                listen 80;
                server_name appldnld.apple.com; # yes, that's definitely us
                rewrite ^/ios10.3.2/.*/iPhoneiTunesUpdateReadMe.ipd$ /iPhoneiTunesUpdateReadMe.ipd last;
                rewrite ^/ios10.3.2/.*/iPhone_5.5_10.3.2_14F89_Restore.ipsw$ /iPhone_5.5_10.3.2_14F89_Restore.ipsw last;
                root /home/user/itunes; # this is where I dropped the .ipsw and ReadMe
        }
}

I also need to remove some default stuff in /etc/nginx/sites-available and /etc/nginx/sites-enabled, which if present will take precedence over my port 80 server above.

sudo service start nginx

and test it from good-computer with:

wget -q -O - http://localhost/ios10.3.2/please/iPhoneiTunesUpdateReadMe.ipd

to see a whole bunch of hex dump to the console! Yay!

step 4: convince the iMac that good-computer is appldnld.apple.com

If I had root on the iMac, there would be a really easy way to do this – drop a line in /etc/hosts saying that 192.168.3.1 is appldnld.apple.com. Since I don’t, I’ll have to convince the iMac in another way.

In step 1 above, I set up dnsmasq to serve DHCP leases on the Ethernet interface. dnsmasq is also a DNS server, and by default it will answer requests from the /etc/hosts on the server where it’s running, if they’re available. dnsmasq is also telling the iMac to ask 192.168.3.1 for name resolution. I can drop echo "192.168.3.1 appldnld.apple.com" >> /etc/hosts on good-computer, restart the DNS server with service dnsmasq restart, and good-computer will start answering DNS queries for that address with its own IP, where we’re running nginx.

We don’t need root to check whether this is working on the iMac – we can open up a browser, try to navigate to http://appldnld.apple.com , and see that the page is a (very quick!) 403 forbidden error page from good-computer.

step 5: profit

So we’re finally ready to try to restore the phone again. Boot it into recovery mode, ask iTunes to update and restore it, and watch nginx’s access logs on good-computer:

192.168.3.13 - - [13/Jun/2017:09:10:39 -0500] "GET /ios10.3.2/[redacted]/iPhoneiTunesUpdateReadMe.ipd HTTP/1.1" 200 748288 "-" "iTunes/12.6.1 (Macintosh; OS X 10.10.5) AppleWebKit/600.8.9"
192.168.3.13 - - [13/Jun/2017:09:11:22 -0500] "GET /ios10.3.2/[redacted]/iPhone_5.5_10.3.2_14F89_Restore.ipsw HTTP/1.1" 200 2852594287 "-" "iTunes/12.6.1 (Macintosh; OS X 10.10.5) AppleWebKit/600.8.9"

Our download completes quickly and the phone boots up in its new OS!

afterthoughts

A smarter way to solve this problem would have been to use a caching web proxy like squid or whatever the hip kids are caching with these days. Cacheing proxies are designed to solve this exact problem, but by the time this occurred to me I had already downloaded the iOS image and couldn’t see an easy way to prepopulate the cache with it.

I did this whole convoluted thing to avoid having to ask the person who gave me this phone for any of their passcodes via email. This morning, about 45 minutes after I finally succeeding in reflashing the phone, its previous owner emailed me their AppleID credentials in cleartext, unsolicited. Oh well.