Skip to content

Instantly share code, notes, and snippets.

@copumpkin
Created July 18, 2009 06:32
Show Gist options
  • Save copumpkin/149443 to your computer and use it in GitHub Desktop.
Save copumpkin/149443 to your computer and use it in GitHub Desktop.
Lots of iPhone protocols. Consider this BSD-licensed. I'd also appreciate if you made changes through the gist interface so I can see what you've done, but no pressure :)
# Very alpha still, but getting there...
# Yeah, I like it this way
require 'pp'
require 'set'
require 'zlib'
require 'base64'
require 'socket'
require 'openssl'
require 'stringio'
require 'net/http'
require 'net/https'
require 'osx/cocoa'
require 'rexml/document'
begin
require 'usb'
$usb_available = true
rescue
$usb_available = false
end
Thread.abort_on_exception = true
class String
def hexdump
buffer = ""
(0..length).step(16) do |i|
first_half = self[i, 8].unpack("C*").map{|x| "%02x" % x}.join(" ")
second_half = (self[i + 8, 8] || "").unpack("C*").map{|x| "%02x" % x}.join(" ")
first_half += " " * (8 * 2 + (8 - 1) - first_half.length)
second_half += " " * (8 * 2 + (8 - 1) - second_half.length)
buffer += first_half + " " + second_half + "\t" + self[i, 16].unpack("C*").map{|x| ((32..128).include?(x) ? x : ?.).chr}.join + "\n"
end
puts buffer
end
def printable?
self.unpack("C*").all?{|x| ((32..128).entries + [9, 10]).include?(x)}
end
def to_hex
self.unpack("C*").map{|x| "%02x" % x}.join
end
end
class Integer
@@host_is_little = "\x01\x02\x03\x04".unpack("N4")[0] == 0x01020304
def byteswap
value = 0
self.size.times do |i|
value |= (self & (0xFF << (8 * i))) << (8 * ((self.size - 1) - (2 * i)))
end
value
end
def big_to_host
@@host_is_little ? self.byteswap : self
end
def little_to_host
@@host_is_little ? self : self.byteswap
end
end
class PList
attr_reader :object
def initialize(data)
@object = data.printable? ? decode_xml_plist(data) : decode_binary_plist(data)
end
def get_int(size)
value = 0
size.times{value = (value << 8) | @io.read(1).unpack("C")[0]}
value
end
def check_int
marker = @io.read(1).unpack("C")[0]
raise "Int expected" unless marker & 0xf0 == 0x10
get_int(1 << (marker & 0xf))
end
def read_object
marker = @io.read(1).unpack("C")[0]
case marker & 0xf0
when 0x00 # Null
case marker & 0x0f
when 0: nil
when 8: false
when 9: true
when 15: "<fill>"
end
when 0x10 # Int
get_int(1 << marker & 0x0f)
when 0x20: "**** Real ****"
when 0x30: "**** Date ****"
when 0x40, 0x50 # Data / ASCII
size = marker & 0xf
size = check_int if size == 15
@io.read(size)
when 0x60 # Unicode16
when 0x80 # UID
when 0xa0 # Array
size = marker & 0x0f
size = check_int if size == 15
value_indices = []
size.times {value_indices << get_int(@offset_int_size)}
value_indices.map {|i| @io.seek(@offset_table[i]); read_object}
when 0xd0 # dict
size = marker & 0xf
size = check_int if size == 15
key_indices = []
value_indices = []
size.times {key_indices << get_int(@offset_int_size)}
size.times {value_indices << get_int(@offset_int_size)}
keys = key_indices.map{|i| @io.seek(@offset_table[i]); read_object}
values = value_indices.map{|i| @io.seek(@offset_table[i]); read_object}
keys.zip(values).inject({}){|accum, pair| accum[pair[0]] = pair[1]; accum}
end
end
def decode_binary_plist(data)
@io = StringIO.new(data)
raise "Invalid binary plist" unless @io.read(6) == "bplist" && (0..2).member?(@io.read(2).to_i )
@io.seek(-32, IO::SEEK_END)
unused, @offset_int_size, @object_ref_size, @num_objects, @top_object, @offset_table_offset = @io.read(32).unpack("a6CCQQQ")
@num_objects = @num_objects.big_to_host
@top_object = @top_object.big_to_host
@offset_table_offset = @offset_table_offset.big_to_host
@io.seek(@offset_table_offset)
@offset_table = []
@num_objects.times{@offset_table << get_int(@offset_int_size)}
@io.seek(@offset_table[@top_object])
read_object
end
def decode_xml_element(element)
return decode_xml_element(element.next_sibling_node) if element.class == REXML::Text && element.value.strip.empty?
case element.name
when "plist": decode_xml_element(element.children[0])
when "dict"
hash = {}
element.each_child do |child|
if child.kind_of?(REXML::Element) && child.name == "key"
hash[child.children[0].value] = decode_xml_element(child.next_sibling_node)
end
end
hash
when "array"
array = []
element.each_child{|child| array << decode_xml_element(child) if child.kind_of?(REXML::Element)}
array
when "integer": element.children[0].value.to_i
when "real": element.children[0].value.to_f
when "string": element.children[0].value
when "data": Base64.decode64(element.children[0].value)
when "true": true
when "false": false
end
end
def decode_xml_plist(data)
decode_xml_element(REXML::Document.new(data).root)
end
end
class CPIO
def initialize(input)
@io = input.kind_of?(String) ? StringIO.new(input) : input
while true do
magic, dev, ino, mode, uid, gid, nlink, rdev, mtime, namesize, filesize = @io.read(76).unpack("a6a6a6a6a6a6a6a6a11a6a11")
# Weird format uses octal ascii?
file_name = @io.read(namesize.to_i(8)).chop
file_data = @io.read(filesize.to_i(8))
break if file_name == "TRAILER!!!"
puts file_name
end
end
end
class SocketRelay
def self.[](a, b)
self.new(a, b)
end
def initialize(a, b)
@a, @b = a, b
# TODO: This should be done with a select
@a_to_b = Thread.new do
while true
value = @a.readpartial(4096) rescue break
p value
@b.write(value)
end
end
@b_to_a = Thread.new do
while true
value = @b.readpartial(4096) rescue break
p value
@a.write(value)
end
end
end
end
class USBTCPSocket
attr_reader :device_id, :product_id, :serial_no
def send_packet(socket, packet_type, data)
packet = [data.length + 16, 0, packet_type, @session_number].pack("V4") + data
socket.write(packet)
end
def recv_packet(socket)
header = socket.read(16)
packet_length, unk, packet_type, tag = header.unpack("V4")
data = socket.read(packet_length - 16)
[packet_type, tag, data]
end
def initialize(port, override_device_id = nil)
@session_number = 1
if override_device_id
@device_id = override_device_id
else
# This just grabs the device id and serial number. Once you've run the hello protocol it won't accept anything else
socket = UNIXSocket.new("/var/run/usbmuxd")
puts "Sending hello packet"
send_packet(socket, 3, "")
p recv_packet(socket)
@device_id, @product_id, @serial_no = recv_packet(socket)[2].unpack("Vva40")
end
puts "Device ID: 0x#{@device_id.to_s(16)}"
@use_ssl = false
done = false
until done do
@socket = UNIXSocket.new("/var/run/usbmuxd")
puts "Retrying connection to port #{port}..."
send_packet(@socket, 2, [device_id, port, 0].pack("Vnn"))
response = recv_packet(@socket)[2]
p response
done = response.unpack("N")[0] == 0
@socket.close unless done
@session_number += 1
end
puts "Connected to port #{port}"
end
def use_ssl=(value)
if value && !@use_ssl
@use_ssl = true
@plain_socket = @socket
@socket = OpenSSL::SSL::SSLSocket.new(@plain_socket, OpenSSL::SSL::SSLContext.new(:TLSv1))
@socket.connect
elsif !value && @use_ssl
@use_ssl = false
@socket.close
@socket = @plain_socket
end
end
def write(data)
@socket.write(data)
end
def read(size)
@socket.read(size)
end
def gets(separator = $/)
@socket.gets(separator)
end
def readpartial(size)
@socket.readpartial(size)
end
def close
@socket.close
end
end
class Service
def self.handle(type)
(@@handlers ||= Hash.new{|hash, key| hash[key] = USBTCPSocket})[type] = self
end
def self.[](type)
@@handlers[type]
end
def self.all
@@handlers.keys
end
end
class PropertyListService < Service
def initialize(port, device_id = nil)
@socket = USBTCPSocket.new(port, device_id)
end
def request_plist(data, format = OSX::NSPropertyListXMLFormat_v1_0)
write_plist(data, format)
read_plist
end
def write_plist(data, format = OSX::NSPropertyListXMLFormat_v1_0)
if data.kind_of?(Hash) || data.kind_of?(Array)
data = data.to_plist
end
@socket.write([data.length].pack("N") + data)
end
def read_plist
size = @socket.read(4).unpack("N")[0]
buffer = @socket.read(size)
OSX.load_plist(buffer)
end
end
class PortForwarderService < Service
def initialize(port)
super(port)
# TODO: fill me in
end
end
class LockdownService < PropertyListService
handle "com.apple.mobile.lockdown"
def initialize(port, label = "iTunes", host_id = nil, pair_record_path = nil)
super(port)
@label = label
pair_record_path ||= "#{File.expand_path("~")}/Library/Lockdown/#{@socket.serial_no}.plist"
raise "QueryType request failed" unless query_type["Result"] == "Success"
raise "Not talking to lockdownd! #{query_type["Type"]} instead!" unless query_type["Type"] == "com.apple.mobile.lockdown"
pair_record = Hash[*OSX.load_plist(File.read(pair_record_path)).select{|key, value| %w(DeviceCertificate HostCertificate HostID RootCertificate).include?(key)}.map{|key, value| [key, value.respond_to?(:rubyString) ? value.rubyString : value]}.flatten]
raise "Unable to validate pairing records" unless validate_pair(pair_record)["Result"] == "Success"
raise "Unable to start session" unless start_session(pair_record["HostID"])["Result"] == "Success"
end
def request(type, hash = {})
request_plist({"Label" => @label, "Request" => type}.merge(hash))
end
# The get value calls seem to return meaningful values for the following domains:
# com.apple.mobile.lockdown
# com.apple.mobile.iTunes
# com.apple.mobile.battery
# com.apple.springboard.curvedBatteryCapacity
# com.apple.mobile.internal
# com.apple.mobile.debug
# com.apple.mobile.restriction
# com.apple.mobile.sync_data_class
# com.apple.mobile.data_sync
# com.apple.mobile.nikita
# com.apple.fairplay
# com.apple.international
# com.apple.disk_usage
# and after xcode has done stuff: com.apple.xcode.developerdomain
def get_value(key, domain = "")
request("GetValue", {"Key" => key}.merge(domain == "" ? {} : {"Domain" => domain}))["Value"]
end
def pair(root_certificate, device_certificate, host_certificate, host_id)
request("Pair", "PairRecord" => {
"DeviceCertificate" => device_certificate,
"HostCertificate" => host_certificate,
"HostID" => host_id,
"RootCertificate" => root_certificate
})
end
def unpair
request("Unpair")
end
def activate(activation_record)
request("Activate", "ActivationRecord" => activation_record)
end
def deactivate
request("Deactivate")
end
def get_values(domain = "")
# TODO: check for success
request("GetValue", domain == "" ? {} : {"Domain" => domain})["Value"]
end
def query_type
# Might as well cache this one
@query_type ||= request("QueryType")
end
def validate_pair(pair_record)
request("ValidatePair", "PairRecord" => pair_record)
end
def enter_recovery
request("EnterRecovery")
end
def start_session(host_id)
response = request("StartSession", "HostID" => host_id)
@socket.use_ssl = response["EnableSessionSSL"]
response
end
def start_service(service_name, handler = nil)
response = request("StartService", "Service" => service_name)
raise "Unable to start service #{service_name}" unless response["Result"] == "Success"
(handler || Service[service_name]).new(response["Port"])
end
end
class DeviceLinkService < PropertyListService
def initialize(port)
super(port)
version_exchange = read_plist
raise "Version exchange message expected" unless version_exchange[0] == "DLMessageVersionExchange"
response = request_plist(["DLMessageVersionExchange", "DLVersionsOk"], OSX::NSPropertyListBinaryFormat_v1_0)
raise "Device not ready" unless response[0] == "DLMessageDeviceReady"
end
def close
write_plist(["DLMessageDisconnect", "All good things must come to an end."])
end
end
class ScreenShotService < DeviceLinkService
handle "com.apple.mobile.screenshotr"
def take_screenshot
response = request_plist(["DLMessageProcessMessage", {"MessageType" => "ScreenShotRequest"}])
raise "Invalid reply type" unless response[0] == "DLMessageProcessMessage"
raise "Invalid reply message type: #{response[1]["MessageType"]}" unless response[1]["MessageType"] == "ScreenShotReply"
response[1]["ScreenShotData"].rubyString
end
end
class CrashReportService < DeviceLinkService
handle "com.apple.crashreportcopy"
def system_info
request_plist(["DLMessageProcessMessage", {"VersionNumber" => 101, "CrashReportRequest" => "GetSysInfo"}])
end
end
class ImageMounterService < PropertyListService
handle "com.apple.mobile.mobile_image_mounter"
def lookup_image(image_type)
request("LookupImage", "ImageType" => "Developer")
end
def mount_image(path, signature, type)
response = request("MountImage", "ImagePath" => path, "ImageSignature" => signature, "ImageType" => type)
end
def close
request("Hangup")
end
def request(command, hash = {})
request_plist({"Command" => command}.merge(hash))
end
end
class NotificationProxyService < PropertyListService
handle "com.apple.mobile.notification_proxy"
attr_reader :listener
def initialize(port)
super(port)
@observers = Hash.new{|hash, key| hash[key] = Set[]}
@listener = Thread.start do
while true
plist = read_plist
if plist["Command"] == "RelayNotification"
@observers[plist["Name"]].each do |handler|
handler.call
end
end
end
end
end
# Valid notifications include:
# com.apple.language.changed
# com.apple.AddressBook.PreferenceChanged
# com.apple.mobile.data_sync.domain_changed
# com.apple.mobile.lockdown.device_name_changed
# com.apple.mobile.developer_image_mounted
# com.apple.mobile.lockdown.trusted_host_attached
# com.apple.mobile.lockdown.host_detached
# com.apple.mobile.lockdown.host_attached
# com.apple.mobile.lockdown.phone_number_changed
# com.apple.mobile.lockdown.registration_failed
# com.apple.mobile.lockdown.activation_state
# com.apple.mobile.lockdown.brick_state
# com.apple.itunes-client.syncCancelRequest
# com.apple.itunes-client.syncSuspendRequest
# com.apple.itunes-client.syncResumeRequest
# com.apple.springboard.attemptactivation
# com.apple.mobile.application_installed
# com.apple.mobile.application_uninstalled
def observe(name, &block)
write_plist("Command" => "ObserveNotification", "Name" => name)
@observers[name] << block
end
def post(name)
write_plist("Command" => "PostNotification", "Name" => name)
end
end
class InstallationProxyService < PropertyListService
handle "com.apple.mobile.installation_proxy"
def browse(options = {})
result = request("Browse", options)
done = false
until done do
plist = read_plist
done = plist["Status"] == "Complete"
end
# TODO: make it work better
result
end
def install(package_path, type = "User", &progress_callback)
request("Install", "PackagePath" => package_path, "ClientOptions" => {"PackageType" => type})
done = false
until done do
plist = read_plist
done = plist["Status"] == "Complete"
end
end
def request(command, hash = {})
request_plist({"Command" => command}.merge(hash))
end
end
class SyslogRelayService < Service
handle "com.apple.syslog_relay"
attr_reader :listener
def initialize(port)
@socket = USBTCPSocket.new(port)
@syslog = ""
@observers = Set[]
@listener = Thread.new do
while true
# syslog_relay will send at most 16383 bytes at a time
buffer = @socket.readpartial(16383)
@observers.each{|observer| observer.call(buffer)}
@syslog << buffer
end
end
end
def observe(&block)
@observers << block
end
end
class MISService < PropertyListService
handle "com.apple.misagent"
def initialize(port)
super(port)
end
# Only provisioning profiles seem to be supported in misagent for now (as of 5a345)
def install_profile(profile, profile_type = "Provisioning")
request("Install", "Profile" => profile, "ProfileType" => profile_type)
end
# Fixme: What does this do?
def copy_profile(profile_type = "Provisioning")
request("Copy", "ProfileType" => profile_type)
end
def remove_profile(profile_id, profile_type = "Provisioning")
request("Remove", "ProfileID" => profile_id, "ProfileType" => profile_type)
end
def request(message_type, hash = {})
request_plist({"MessageType" => message_type}.merge(hash))
end
end
class ApplistService < Service
handle "com.apple.debugserver.applist"
def initialize(port)
@socket = USBTCPSocket.new(port)
end
def applist
# Fixme: Hardcoded
OSX.load_plist(@socket.readpartial(128 * 1024))
end
end
class FileRelayService < PropertyListService
handle "com.apple.mobile.file_relay"
# The following are valid sources
# AppleSupport
# Network
# VPN
# WiFi
# UserDatabases
# CrashReporter
# tmp
# SystemConfiguration
def request_source(*sources)
result = request_plist("Sources" => sources.flatten)
raise "Unable to retrieve sources" unless result["Status"] == "Acknowledged"
gzip_reader = Zlib::GzipReader.new(@socket)
CPIO.new(gzip_reader)
end
end
class BackupService < DeviceLinkService
handle "com.apple.mobilebackup"
def initialize(port)
super(port)
# TODO: finish me
["DLMessageProcessMessage", {"BackupMessageTypeKey" => "BackupMessageBackupRequest",
"BackupComputerBasePathKey"=>"/",
"BackupManifestKey" =>
{"Data" => "moo",
"AuthVersion" => "1.0",
"AuthData" => "Forty Two",
"Auth Signature" => "SHA1"}}]
end
end
class AFCService < Service
handle "com.apple.afc"
handle "com.apple.afc2"
class AFCIO
def write(data)
end
def read(size)
end
end
def initialize(port)
@socket = USBTCPSocket.new(port)
@sequence_number = 0
end
def send_frame(type, data = "", header_size = data.length + 40)
frame = "CFA6LPAA" + [data.length + 40, 0, header_size, 0, @sequence_number += 1, 0, type, 0].pack("V*") + data
frame.hexdump
@socket.write(frame)
end
def recv_frame()
magic, size = @socket.read(16).unpack("a8V*")
raise "Invalid frame" unless magic == "CFA6LPAA"
header_size = @socket.read(8).unpack("V")[0] # Ignoring other 4 bytes
sequence_number = @socket.read(8).unpack("V")[0] # Ignoring other 4 bytes
header_rest = @socket.read(header_size - 32)
@socket.read(size - header_size)
end
def sysinfo()
send_frame(11)
Hash[*recv_frame().split("\x00").map{|x| x.to_i.to_s == x ? x.to_i : x}]
end
def stat(path)
send_frame(10, path + "\x00")
Hash[*recv_frame().split("\x00").map{|x| x.to_i.to_s == x ? x.to_i : x}]
end
def mkdir(path)
send_frame(9, path + "\x00")
recv_frame()
end
def ls(path)
send_frame(3, path + "\x00")
recv_frame.split("\x00")
end
# 2 appears to be readable, 3 seems to create the file
def open(path, mode = 2)
send_frame(13, [mode, 0].pack("V*") + path + "\x00")
recv_frame
end
def read(file_size)
send_frame(15, [1, 0, file_size, 0].pack("V*"))
recv_frame
end
def write(data)
send_frame(16, [1, 0].pack("V*") + data, 48)
end
end
class RestoreService < PropertyListService
handle "com.apple.mobile.restored"
def reboot
request("Reboot")
end
def start_restore(progress_callback = nil, &data_request_handler)
write_plist("Request" => "StartRestore", "RestoreProtocolVersion" => 11)
p "wrote plist"
while plist = read_plist do
p "got plist"
p plist
if plist["MsgType"] =="DataRequestMsg"
response = data_request_handler.call(plist["DataType"])
write_plist(response) if response
elsif progress_callback && plist["MsgType"] == "ProgressMsg"
progress_callback.call(plist["Operation"], plist["Progress"])
elsif plist["MsgType"] == "StatusMsg"
puts "Got status message: #{plist.inspect}"
break if plist["Status"] == 0
end
end
end
def goodbye
request("Goodbye")
end
# Valid keys (key cannot be empty):
# SerialNumber
# IMEI
# HardwareModel
def query_value(key)
request("QueryValue", "QueryKey" => key)
end
def query_type
request("QueryType")
end
def request(command, hash = {})
request_plist({"Request" => command}.merge(hash))
end
end
class ASRService < Service
def initialize(port, input)
@socket = USBTCPSocket.new(port)
if input.kind_of?(File)
@io = input
@size = input.stat.size
elsif input.kind_of?(String)
@io = StringIO.new(input)
@size = input.size
end
raise "Unexpected command" unless read_plist["Command"] == "Initiate"
end
def start
write_plist({
"FEC Slice Stride" => 40,
"Packet Payload Size" => 1450,
"Packets Per FEC" => 25,
"Payload" => {
"Port" => 1,
"Size" => @size
},
"Stream ID" => 1,
"Version" => 1
})
while plist = read_plist do
if plist["Command"] == "OOBData"
size = plist["OOB Length"]
offset = plist["OOB Offset"]
puts "Sending #{size} OOB bytes from offset #{offset}"
@io.seek(offset)
@socket.write(@io.read(size))
elsif plist["Command"] == "Payload"
puts "Sending payload"
@io.seek(0)
index = 0
while buffer = @io.read(0x10000) do
@socket.write(buffer)
index += 1
if index % 16 == 0
puts "#{index.to_f / (@size / 0x10000) * 100}% done"
end
end
break
else
puts "Unknown ASR command #{plist.inspect}"
end
end
end
def read_plist
buffer = ""
while read_buffer = @socket.gets do
puts "Read: #{read_buffer.inspect}"
buffer << read_buffer
break if read_buffer =~ /<\/plist>/
end
OSX.load_plist(buffer)
end
def write_plist(hash)
@socket.write(hash.to_plist)
end
end
class AppleDevice
@@matches = {}
def self.match(value)
[value[:vendor_id]].flatten.each do |vendor_id|
(@@matches ||= {})[vendor_id] ||= {}
[value[:product_id]].flatten.each do |product_id|
@@matches[vendor_id][product_id] = self
end
end
end
def self.[](vendor_id, product_id)
@@matches[vendor_id][product_id]
end
def self.available_devices
available_ids = {}
@@matches.each_pair do |vendor_id, value|
value.each_pair do |product_id, klass|
available_ids[[vendor_id, product_id]] = klass
end
end
USB.devices.map do |device|
available_ids[[device.idVendor, device.idProduct]].new(device) if available_ids.has_key?([device.idVendor, device.idProduct])
end.compact
end
def initialize(device)
@device = device
end
end
class NormalMode < AppleDevice
match :vendor_id => 0x5ac, :product_id => [0x1290, 0x1291, 0x1292] # iPhone, iPod Touch, iPhone3G
attr_reader :service
def initialize(device)
super(device)
end
def open
service = PropertyListService.new(62078)
@service = Service[service.request_plist("Request" => "QueryType")["Type"]].new(62078)
end
end
class RecoveryV1Mode < AppleDevice
match :vendor_id => 0x5ac, :product_id => 0x1280
end
class RecoveryV2Mode < AppleDevice
match :vendor_id => 0x5ac, :product_id => 0x1281
def initialize(device)
super(device)
end
def open
@handle = @device.open
@handle.set_configuration(@device.configurations[0])
@handle.claim_interface(1)
@handle.set_altinterface(1)
@receive_buffer = "\x00" * 0x1000
self
end
def recv_buffer
size = @handle.usb_interrupt_read(0x81, @receive_buffer, 0)
@receive_buffer[0, size]
end
def send_command(command)
@handle.usb_interrupt_write(0x02, command + "\n", 0)
end
def send_file(file)
@handle.usb_control_msg(0x21, 6, 1, 0, "", 0)
index = 0
size = 0
while buffer = file.read(0x1000) do
size += buffer.size
@handle.usb_control_msg(0x21, 1, index, 0, buffer, 0)
index += 1
end
@handle.usb_control_msg(0x21, 1, 1, 0, "", 0)
buffer = "\x00" * 6
status = @handle.usb_control_msg(0xa1, 3, 0, 0, buffer, 0)
buffer.hexdump
raise "Bad status" unless buffer[4] == 0x6
status = @handle.usb_control_msg(0xa1, 3, 0, 0, buffer, 0)
buffer.hexdump
raise "Bad status" unless buffer[4] == 0x7
status = @handle.usb_control_msg(0xa1, 3, 0, 0, buffer, 0)
buffer.hexdump
raise "Bad status" unless buffer[4] == 0x8
end
def close
@handle.release_interface(1)
@handle.usb_close
end
end
class DFUV2Mode < AppleDevice
match :vendor_id => 0x05ac, :product_id => [0x1222, 0x1227]
end
#pp Service.all
x = nil
p "finding devices"
def pair
devs = AppleDevice.available_devices
if devs[0].kind_of?(NormalMode)
x = devs[0].open
root_ca_cert = OpenSSL::X509::Certificate.new
root_private_key = OpenSSL::PKey::RSA.new(1024)
root_ca_cert.public_key = root_private_key.public_key
device_cert = OpenSSL::X509::Certificate.new
device_cert.public_key = OpenSSL::PKey::RSA.new(x.get_value("DevicePublicKey").rubyString)
device_cert.sign(root_private_key, OpenSSL::Digest::Digest.new("SHA1"))
host_private_key = OpenSSL::PKey::RSA.new(1024)
host_cert = OpenSSL::X509::Certificate.new
host_cert.public_key = host_private_key.public_key
host_cert.sign(root_private_key, OpenSSL::Digest::Digest.new("SHA1"))
root_pem = root_ca_cert.to_pem
device_pem = device_cert.to_pem
host_pem = host_cert.to_pem
root_data = OSX::NSData.alloc.initWithBytes_length(root_pem, root_pem.size)
host_data = OSX::NSData.alloc.initWithBytes_length(host_pem, host_pem.size)
device_data = OSX::NSData.alloc.initWithBytes_length(device_pem, device_pem.size)
p x.pair(root_data, device_data, host_data, "29942954112249261629052abcdef40")
end
end
def activate
devs = AppleDevice.available_devices
if devs[0].kind_of?(NormalMode)
x = devs[0].open
#x.deactivate
#abort
uid = x.get_value("UniqueDeviceID")
imei = x.get_value("InternationalMobileEquipmentIdentity")
iccid = x.get_value("IntegratedCircuitCardIdentity")
activation_info = x.get_value("ActivationInfo")
serial_number = x.get_value("SerialNumber")
imsi = x.get_value("InternationalMobileSubscriberIdentity")
#p uid
#p imei
#p iccid
#puts activation_info.to_plist
#p serial_number
#abort
url = URI("https://albert.apple.com/WebObjects/ALActivation.woa/wa/iPhoneRegistration")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.start do |h|
req = Net::HTTP::Post.new(url.path, "User-Agent" => "iTunes/7.7 (Macintosh; U; Intel Mac OS X 10.5.4)")
req.form_data = {
"AppleSerialNumber" => serial_number,
"IMSI" => imsi,
"InStoreActivation" => "false",
"machineName" => "macos",
"activation-info" => activation_info.to_plist,
"ICCID" => iccid,
"IMEI" => imei
}
#puts req.body
result = h.request(req)
puts result.body
document = REXML::Document.new(result.body)
activation = OSX.load_plist(document.root.elements[1].elements[1].to_s)
pp activation
activation_record = activation["iphone-activation"]["activation-record"]
x.activate(activation_record)
end
end
end
__END__
# Here's other random stuff
base_path = "/Users/pumpkin/Desktop/iPhone1,2_2.0_5A347_Restore"
if devs[0].kind_of?(RecoveryV2Mode)
x = devs[0].open
p "sending apple logo"
File.open("#{base_path}/Firmware/all_flash/all_flash.n82ap.production/applelogo.s5l8900x.img3") do |f|
x.send_file(f)
end
x.send_command("setpicture 1")
x.send_command("bgcolor 0 255 0")
p "sending device tree"
File.open("#{base_path}/Firmware/all_flash/all_flash.n82ap.production/DeviceTree.n82ap.img3") do |f|
x.send_file(f)
end
x.send_command("devicetree")
pp x.recv_buffer.split("\x00")
p "sending ramdisk"
File.open("#{base_path}/018-3783-2.dmg") do |f|
x.send_file(f)
end
x.send_command("ramdisk")
pp x.recv_buffer.split("\x00")
p "sending kernel"
File.open("#{base_path}/kernelcache.release.s5l8900x") do |f|
x.send_file(f)
end
p "booting"
x.send_command("bootx")
pp x.recv_buffer.split("\x00")
p "sleeping"
#sleep(60)
end
devs = AppleDevice.available_devices
p devs[0]
if devs[0].kind_of?(NormalMode)
restore = devs[0].open
progress_callback = proc do |operation, progress|
steps = {
28 => "Waiting for NAND",
11 => "Creating partition map",
12 => "Creating filesystem",
13 => "Restoring image",
14 => "Verifying restore",
15 => "Checking filesystems",
16 => "Mounting filesystems",
29 => "Fixing up /var",
29 => "Unmounting filesystems",
25 => "Modifying persistent boot-args",
35 => "Loading NOR data to flash",
18 => "Flashing NOR",
19 => "Updating baseband",
20 => "Finalizing NAND epoch update",
32 => "Waiting for Device..."
}
puts "#{steps[operation]} (#{operation}) with progress #{progress}"
end
p "starting restore"
restore.start_restore(progress_callback) do |data_type|
puts "DataRequest callback"
if data_type == "SystemImageData"
puts "Got request for system image data"
Thread.new do
puts "Started ASR thread"
File.open("#{base_path}/018-3782-2.dmg") do |f|
asr = ASRService.new(12345, f)
asr.start
end
end
nil
elsif data_type == "NORData"
puts "Got request for NOR data"
llb_data = File.read("#{base_path}/Firmware/all_flash/all_flash.n82ap.production/LLB.n82ap.RELEASE.img3")
other_nor_data = Dir["#{base_path}/Firmware/all_flash/all_flash.n82ap.production/*.img3"].reject{|x| x =~ /^LLB/}.map do |path|
buffer = File.read(path)
OSX::NSData.alloc.initWithBytes_length(buffer, buffer.size)
end
response = { "LlbImageData" => OSX::NSData.alloc.initWithBytes_length(llb_data, llb_data.size),
"NorImageData" => other_nor_data }
response
end
end
puts "Rebooting"
end
__END__
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment