Repository: x56/airpyrt-tools
Branch: main
Commit: 64f526b3e0a9
Files: 19
Total size: 77.6 KB
Directory structure:
gitextract_nzg9t11t/
├── .gitignore
├── LICENSE
├── README.md
├── acp/
│ ├── __init__.py
│ ├── __main__.py
│ ├── basebinary.py
│ ├── cflbinary.py
│ ├── cli.py
│ ├── clibs/
│ │ ├── AppleSRP.py
│ │ └── __init__.py
│ ├── client.py
│ ├── encryption.py
│ ├── exception.py
│ ├── keystream.py
│ ├── message.py
│ ├── misc.py
│ ├── property.py
│ └── session.py
└── setup.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
*.pyc
*.egg-info
build/
dist/
================================================
FILE: LICENSE
================================================
Copyright (c) 2016 Vince Cali
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# AirPyrt Tools
### License
See LICENSE
### Requirements
- python 2.7
- pycrypto
### Installation
`python setup.py install [--user]`
### Usage
`python [-B] -m acp`
usage: __main__.py [-h] [-t address] [-p password] [--listprop]
[--helpprop property] [--getprop property]
[--setprop property value] [--dumpprop] [--acpprop]
[--dump-syslog] [--reboot] [--factory-reset]
[--flash-primary firmware_path] [--do-feat-command]
[--decrypt inpath outpath] [--extract inpath outpath]
[--srp-test]
optional arguments:
-h, --help show this help message and exit
AirPort client parameters:
-t address, --target address
IP address or hostname of the target router
-p password, --password password
router admin password
AirPort client commands:
--listprop list supported properties
--helpprop property print the description of the specified property
--getprop property get the value of the specified property
--setprop property value
set the value of the specified property
--dumpprop dump values of all supported properties
--acpprop get acp acpprop list
--dump-syslog dump the router system log
--reboot reboot device
--factory-reset RESET EVERYTHING and reboot; you have been warned!
--flash-primary firmware_path
flash primary partition firmware
--do-feat-command send 0x1b (feat) command
Basebinary commands:
--decrypt inpath outpath
decrypt the basebinary
--extract inpath outpath
extract the gzimg contents
Test arguments:
--srp-test SRP (requires OS X)
### Notes
**IMPORTANT**
This still uses the old ACP protocol implementation, which puts the admin password
of the device over the wire in a trivially recoverable format. This was fixed by
in the new protocol which uses SRP authentication and better encryption of requests
to/from the device. Until this is implemented this tool is entirely unsafe to use,
especially for remote administration (which you should have disabled anyway...).
This project grew organically out of my understanding of various pieces of the ACP
protocol. I've restructured the code a few times as it has improved, but there are
still many gaps in the implementation, and a lot of code smell. Between sitting on
this indefinitely making incremental improvements (and probably never releasing a
"finished" product) and releasing it in a rougher state for others to explore, the
latter made far more sense.
Return value of 0xfffffff6 when using --getprop means the property is not avaliable/readable
## TODO (very incomplete list in no particular order)
- add IP address type for properties, make sure it supports IPv4 and IPv6
- specify RO/WO/RW attribute for properties
- exception handling:
- invalid struct fields aren't handled well in many cases
- finish adding custom exception classes and make sure we're using them
- logging (mostly done, still looks horrible) with verbosity controls
- review and update docstrings
- SRP support (fix pysrp because ctypes hax, while fun, are horrible and non-portable)
- ACP protocol version 2 (full session encryption)
- handle encrypted property elements
- basebinary repacking/reencryption
- basebinary rootfs mounting
- threaded server
- handle protocol v1 (for old firmwares/devices)
- bonjour announcement/discovery
- options to specify no encryption, old method, and new (SRP) method
- ACPMonitorSession support
- ACPRPC support
================================================
FILE: acp/__init__.py
================================================
================================================
FILE: acp/__main__.py
================================================
import cli
cli.main()
================================================
FILE: acp/basebinary.py
================================================
import logging
import os.path
import struct
import zlib
from Crypto.Cipher import AES
#XXX: ugh...
from .misc import cast_u32
# hardcoded 128 bit values
# 107: 52 49 C3 51 02 8B F1 FD 2B D1 84 9E 28 B2 3F 24
# 108: BB 7D EB 09 70 D8 EE 2E 00 FA 46 CB 1C 3C 09 8E
# 115: 10 75 E8 06 F4 77 0C D4 76 3B D2 85 A6 4E 91 74
# 120: 68 8C DD 3B 1B 6B DD A2 07 B6 CE C2 73 52 92 D2
_basebinary_keys = {
#3 : "",
#102 : "",
#104 : "",
#105 : "",
#106 : "",
107 : "5249c351028bf1fd2bd1849e28b23f24",
108 : "bb7deb0970d8ee2e00fa46cb1c3c098e",
#109 : "",
#113 : "",
#114 : "",
115 : "1075e806f4770cd4763bd285a64e9174",
#116 : "",
#117 : "",
#119 : "",
120 : "688cdd3b1b6bdda207b6cec2735292d2",
}
def _derive_key(model):
if model not in _basebinary_keys:
return None
key = _basebinary_keys[model].decode("hex")
derived_key = ""
for i in range(len(key)):
derived_key += chr(ord(key[i]) ^ (i + 0x19))
logging.debug("derived key {0}".format(derived_key.encode("hex")))
return derived_key
class BasebinaryError(Exception):
pass
class Basebinary(object):
_header_magic = "APPLE-FIRMWARE\x00"
#XXX: do we need to fix shitty Python struct member signdness things here too?
_header_format = struct.Struct(">15sB2I4BI")
header_size = _header_format.size
@classmethod
def parse(cls, data):
if len(data) < (cls.header_size + 4):
raise BasebinaryError("not enough data to parse")
header_data = data[:cls.header_size]
inner_data = data[cls.header_size:-4]
#XXX: and here??
stored_checksum, = struct.unpack(">I", data[-4:])
(byte_0x0F, model, version, byte_0x18, byte_0x19, byte_0x1A, flags, unk_0x1C) = cls.parse_header(header_data)
if flags & 2:
inner_data = cls.decrypt(inner_data, model, byte_0x0F)
#XXX: why is Python so shitty about this comparison <.<
checksum = cast_u32(zlib.adler32(header_data+inner_data))
logging.debug("stored checksum {0:#x}".format(stored_checksum))
logging.debug("calculated checksum {0:#x}".format(checksum))
logging.debug("data length {0:#x}".format(len(header_data+inner_data)))
if stored_checksum != checksum:
raise BasebinaryError("bad checksum")
return inner_data
@classmethod
def compose(cls, data):
#TODO
pass
@classmethod
def parse_header(cls, data):
magic, byte_0x0F, model, version, byte_0x18, byte_0x19, byte_0x1A, flags, unk_0x1C = cls._header_format.unpack(data)
if magic != cls._header_magic:
raise BasebinaryError("bad header magic")
return (byte_0x0F, model, version, byte_0x18, byte_0x19, byte_0x1A, flags, unk_0x1C)
@classmethod
def compose_header(cls, byte_0x0F, model, version, byte_0x18, byte_0x19, byte_0x1A, flags, unk_0x1C):
#TODO
pass
@classmethod
def decrypt(cls, data, model, byte_0x0F):
iv = cls._header_magic+chr(byte_0x0F)
key = _derive_key(model)
if key is None:
raise BasebinaryError("key missing for model {0}".format(model))
decrypted_data = ""
remaining_length = len(data)
chunk_length = 0x8000
while remaining_length:
if remaining_length > chunk_length:
decrypted_data += cls.decrypt_chunk(data[-remaining_length:-(remaining_length-chunk_length)], key, iv)
remaining_length -= chunk_length
else:
decrypted_data += cls.decrypt_chunk(data[-remaining_length:], key, iv)
remaining_length = 0
return decrypted_data
@classmethod
def decrypt_chunk(cls, encrypted_data, key, iv):
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_data = ""
bytes_left = len(encrypted_data)
while bytes_left:
#logging.debug("bytes left: {0:#x}".format(bytes_left))
if bytes_left > 0x10:
decrypted_data += cipher.decrypt(encrypted_data[-bytes_left:-(bytes_left-0x10)])
bytes_left -= 0x10
elif bytes_left == 0x10:
decrypted_data += cipher.decrypt(encrypted_data[-bytes_left:])
bytes_left = 0
else: # bytes_left < 0x10
#LOL: odd-sized chunk at the end is left unencrypted
decrypted_data += encrypted_data[-bytes_left:]
bytes_left = 0
return decrypted_data
@classmethod
def encrypt(cls, data):
#TODO: not really necessary; router doesn't require an encrypted firmware if flag is unset
pass
@classmethod
def extract(cls, data):
#TODO: proper gzip header validation?
gzip_offset = data.index("\x1f\x8b\x08")
gzdata = data[gzip_offset:]
return zlib.decompress(gzdata, 16+zlib.MAX_WBITS)
================================================
FILE: acp/cflbinary.py
================================================
import logging
import struct
from collections import OrderedDict
from math import log
from types import *
_header_magic = "CFB0"
_footer_magic = "END!"
_header_size = len(_header_magic)
_footer_size = len(_footer_magic)
def _lslice(data, length):
""" Slice object into two counting from the left
Returns:
(left_slice, right_slice)
"""
return data[:length], data[length:]
class CFLBinaryPListComposeError(Exception):
"""Exception raised for errors composing a cflbinary format property list"""
pass
class CFLBinaryPListParseError(Exception):
"""Exception raised for errors parsing a cflbinary format property list"""
pass
class CFLBinaryPListComposer(object):
"""Write cflbinary format property list"""
@classmethod
def _pack_object(cls, obj):
""" Pack a supported Python built-in object
Note:
this function is super hacky and needs to be fixed
Returns:
data
Raises:
CFLBinaryPListComposeError
"""
data = ""
object_type = type(obj)
if object_type == NoneType:
data += "\x00"
elif object_type == BooleanType:
if not obj:
data += "\x08"
else:
data += "\x09"
elif object_type == IntType:
object_marker = 0x10
buf = ""
#XXX: need to actually catch unsupported packed sizes
for fmt in [">B", ">H", ">I", ">Q"]:
try:
buf = struct.pack(fmt, obj)
except struct.error:
logging.debug("XXX: skipping {0}".format(fmt))
pass
else:
break
object_marker += int(log(len(buf), 2))
data += chr(object_marker)
data += buf
elif object_type == FloatType:
object_marker = 0x20
buf = ""
#XXX: need to actually catch unsupported packed sizes
for fmt in ["!f", "!d"]:
try:
buf = struct.pack(fmt, obj)
except struct.error:
logging.debug("XXX: skipping {0}".format(fmt))
pass
else:
break
object_marker += int(log(len(buf), 2))
data += chr(object_marker)
data += buf
#XXX: DateType?
elif object_type == StringType:
object_marker = 0x40
data_len = len(obj)
if data_len < 0xF:
object_marker += data_len
data += chr(object_marker)
else:
object_marker += 0xF
data += chr(object_marker)
data += cls._pack_object(data_len)
data += obj
elif object_type == UnicodeType:
data += "\x70"
data += obj.encode("utf-8")
data += "\x00"
elif object_type == ListType:
data += "\xA0"
for element in obj:
data += cls._pack_object(element)
data += "\x00"
elif object_type in [DictType, OrderedDict]:
data += "\xD0"
for k, v in obj.iteritems():
data += cls._pack_object(k)
data += cls._pack_object(v)
data += "\x00"
else:
raise CFLBinaryPListComposeError("unsupported Python built-in type: {0}".format(type(obj)))
return data
@classmethod
def compose(cls, object):
""" Compose Python object into equivalent plist
Returns:
plist_data
Raises:
CFLBinaryPListComposeError
"""
data = _header_magic
# assume one root object
data += cls._pack_object(object)
data += _footer_magic
return data
class CFLBinaryPListParser(object):
"""Read cflbinary format property list"""
@classmethod
def _unpack_int(cls, size_exponent, data):
""" Unpack an int object as a Python int from the provided data
Returns:
(int, remaining_data)
Raises:
CFLBinaryPListParseError
"""
int_size = 2**size_exponent
int_bytes, data = _lslice(data, int_size)
#XXX: are these supposed to be signed or unsigned?
if int_size == 1:
int_fmt = ">B"
elif int_size == 2:
int_fmt = ">H"
elif int_size == 4:
int_fmt = ">I"
elif int_size == 8:
int_fmt = ">Q"
else:
raise CFLBinaryPListParseError("unsupported int packed object size of {0} bytes")
try:
(int_val, ) = struct.unpack(int_fmt, int_bytes)
except struct.error:
raise CFLBinaryPListParseError("failed to unpack int value")
return int_val, data
@classmethod
def _unpack_real(cls, size_exponent, data):
""" Unpack a real object as a Python float from the provided data
Returns:
(float, remaining_data)
Raises:
CFLBinaryPListParseError
"""
real_size = 2**size_exponent
real_bytes, data = _lslice(data, real_size)
if real_size == 4:
real_fmt = ">f"
elif real_size == 8:
real_fmt = ">d"
else:
raise CFLBinaryPListParseError("unsupported real packed object size of {0} bytes")
try:
(float_val, ) = struct.unpack(real_fmt, real_bytes)
except struct.error:
raise CFLBinaryPListParseError("failed to unpack float value")
return float_val, data
@classmethod
def _unpack_count(cls, object_info, data):
""" Unpack count from object info nibble and/or packed int value
Returns:
(count, remaining_data)
Raises:
CFLBinaryPListParseError
"""
if object_info == 0x0F:
# count is the following packed int object
marker, data = cls._unpack_object_marker(data)
count_object_type = marker & 0xF0
count_object_info = marker & 0x0F
if count_object_type != 0x10:
raise CFLBinaryPListParseError("expected count to be a packed int object")
count, data = cls._unpack_int(count_object_info, data)
else:
count = object_info
return count, data
@classmethod
def _unpack_object_marker(cls, data):
""" Unpack an object marker from the provided data
Returns:
(marker, remaining_data)
Raises:
CFLBinaryPListParseError
"""
marker_byte, data = _lslice(data, 1)
try:
(marker, ) = struct.unpack(">B", marker_byte)
except struct.error:
raise CFLBinaryPListParseError("failed to unpack object marker")
return marker, data
@classmethod
def _unpack_object(cls, data):
""" Unpack an object from the provided data
Returns:
(obj, remaining_data)
Raises:
CFLBinaryPListParseError
"""
obj = None
marker, data = cls._unpack_object_marker(data)
object_type = marker & 0xF0
object_info = marker & 0x0F
if object_type == 0x00:
if object_info == 0x00: # null, null object
return None, data
elif object_info == 0x08: # bool, false
return False, data
elif object_info == 0x09: # bool, true
return True, data
else:
raise CFLBinaryPListParseError("unsupported object info value for object type 0x00: {0:#x}".format(object_info))
elif object_type == 0x10: # int, big-endian
return cls._unpack_int(object_info, data)
elif object_type == 0x20: # real, big-endian
return cls._unpack_real(object_info, data)
elif object_type == 0x30: # date
#XXX: not sure if this is actually used
raise CFLBinaryPListParseError("date support not implemented")
elif object_type == 0x40: # data
size, data = cls._unpack_count(object_info, data)
#XXX: we return data as str type, is this ok?
return _lslice(data, size)
elif object_type == 0x50: # string, ASCII
raise CFLBinaryPListParseError("ASCII string support not implemented")
elif object_type == 0x60: # string, Unicode
raise CFLBinaryPListParseError("Unicode string support not implemented")
elif object_type == 0x70: # string, UTF8, NULL terminated
raw = ""
while True:
byte, data = _lslice(data, 1)
if byte == "\x00":
break
raw += byte
#XXX: what exceptions could we get here?
obj = raw.decode("utf-8")
return obj, data
elif object_type == 0x80: # uid
raise CFLBinaryPListParseError("uid support not implemented")
elif object_type == 0xA0: # array
obj = []
while True:
element, data = cls._unpack_object(data)
if element == None:
break
obj.append(element)
return obj, data
elif object_type == 0xB0: # ordset
raise CFLBinaryPListParseError("ordset support not implemented")
elif object_type == 0xC0: # set
raise CFLBinaryPListParseError("set support not implemented")
elif object_type == 0xD0: # dict
keys = []
values = []
while True:
key, data = cls._unpack_object(data)
if key == None:
break
keys.append(key)
value, data = cls._unpack_object(data)
values.append(value)
obj = OrderedDict()
for i in range(len(keys)):
obj[keys[i]] = values[i]
return obj, data
else:
raise CFLBinaryPListParseError("unsupported object type: {0:#x}".format(object_type))
@classmethod
def parse(cls, data):
""" Parse plist data into equivalent Python built-in object type
Returns:
obj
Raises:
CFLBinaryPListParseError
"""
# bail now if there isn't enough data for header, footer, and at least one object
if len(data) < (_header_size + _footer_size + 1):
raise CFLBinaryPListParseError("not enough data to parse")
header_data, data = _lslice(data, _header_size)
if header_data != _header_magic:
raise CFLBinaryPListParseError("bad header magic")
# read object stream (assume one root object)
obj, remaining_data = cls._unpack_object(data)
if len(remaining_data) > _footer_size:
raise CFLBinaryPListParseError("extra data found after unpacking root object")
if remaining_data != _footer_magic:
raise CFLBinaryPListParseError("bad footer magic")
return obj
================================================
FILE: acp/cli.py
================================================
import argparse
import logging
import os.path
import sys
import time
from collections import OrderedDict
from .basebinary import *
from .client import ACPClient
from .exception import *
from .property import ACPProperty
class _ArgParser(argparse.ArgumentParser):
def error(self, message):
sys.stderr.write("error: {0}\n".format(message))
#self.print_help()
sys.exit(2)
def _cmd_not_implemented(*unused):
raise ACPCommandLineError("command handler not implemented")
def _cmd_listprop(unused):
print "\nSupported properties:\n"
prop_names = ACPProperty.get_supported_property_names()
for name in prop_names:
print "{0}: {1}".format(name, ACPProperty.get_property_info_string(name, "description"))
print
def _cmd_helpprop(args):
prop_name = args.pop()
description = ACPProperty.get_property_info_string(prop_name, "description")
prop_type = ACPProperty.get_property_info_string(prop_name, "type")
validation = ACPProperty.get_property_info_string(prop_name, "validation")
s = "{0} ({1}".format(description, prop_type)
if validation:
s += ", {0})".format(validation)
else:
s += ")"
print s
def _cmd_getprop(client, args):
prop_name = args.pop()
prop = client.get_properties([prop_name])
if len(prop):
print ACPProperty(prop_name, prop[0].value)
def _cmd_setprop(client, args):
prop_name, prop_value = args
prop_type = ACPProperty.get_property_info_string(prop_name, "type")
prop = ACPProperty()
if prop_type == "dec":
try:
prop = ACPProperty(prop_name, int(prop_value))
except ValueError:
logging.error("value for \"{0}\" has the wrong type, should be {0}".format(prop_name, prop_type))
elif prop_type == "hex":
try:
#XXX: this is not the right way to do exceptions
prop = ACPProperty(prop_name, int(prop_value, 16))
except ValueError:
logging.error("value for \"{0}\" has the wrong type, should be {0}".format(prop_name, prop_type))
elif prop_type == "mac":
#XXX: not catching our exception
prop = ACPProperty(prop_name, prop_value)
elif prop_type == "bin":
prop = ACPProperty(prop_name, prop_value.decode("hex"))
elif prop_type == "str":
prop = ACPProperty(prop_name, prop_value)
elif prop_type in ["cfb", "log"]:
logging.error("unsupported prop type: {0}".format(prop_type))
client.set_properties({prop_name : prop})
def _cmd_dumpprop(client, unused):
prop_names = ACPProperty.get_supported_property_names()
properties = client.get_properties(prop_names)
for prop in properties:
padded_description = ACPProperty.get_property_info_string(prop.name, "description").ljust(32, " ")
print "{0}: {1}".format(padded_description, prop)
def _cmd_acpprop(client, unused):
props_reply = client.get_properties(["prop"])
props_raw = props_reply[0].value
props = ""
for i in range(len(props_raw) / 4):
props += "{0}\n".format(props_raw[i*4:i*4+4])
print props
def _cmd_dump_syslog(client, unused):
print "{0}".format(client.get_properties(["logm"])[0])
def _cmd_reboot(client, unused):
print "Rebooting device"
client.set_properties({"acRB" : ACPProperty("acRB", 0)})
def _cmd_factory_reset(client, unused):
print "Performing factory reset"
client.set_properties(OrderedDict([("acRF",ACPProperty("acRF", 0)), ("acRB",ACPProperty("acRB", 0))]))
def _cmd_flash_primary(client, args):
fw_path = args.pop()
if os.path.exists(fw_path):
with open(fw_path, "rb") as fw_file:
fw_data = fw_file.read()
print "Flashing primary firmware partition"
client.flash_primary(fw_data)
else:
logging.error("Basebinary not readable at path: {0}".format(fw_path))
def _cmd_do_feat_command(client, unused):
print client.get_features()
def _cmd_decrypt(args):
(inpath, outpath) = args
with open(inpath, "rb") as infile:
indata = infile.read()
#XXX: lazy, fixme
try:
outdata = Basebinary.parse(indata)
except BasebinaryError:
raise
else:
with open(outpath, "wb") as outfile:
outfile.write(outdata)
def _cmd_extract(args):
(inpath, outpath) = args
with open(inpath, "rb") as infile:
indata = infile.read()
#XXX: lazy, fixme
try:
outdata = Basebinary.extract(indata)
except BasebinaryError:
raise
else:
with open(outpath, "wb") as outfile:
outfile.write(outdata)
def _cmd_srp_test(client, unused):
print "SRP testing"
client.authenticate_AppleSRP()
client.close()
def main():
#TODO: add CLI arg for verbosity
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
parser = _ArgParser()
parameters_group = parser.add_argument_group("AirPort client parameters")
parameters_group.add_argument("-t", "--target", metavar="address", help="IP address or hostname of the target router")
parameters_group.add_argument("-p", "--password", metavar="password", help="router admin password")
airport_client_group = parser.add_argument_group("AirPort client commands")
airport_client_group.add_argument("--listprop", action="store_const", const=True, help="list supported properties")
airport_client_group.add_argument("--helpprop", metavar="property", nargs=1, help="print the description of the specified property")
airport_client_group.add_argument("--getprop", metavar="property", nargs=1, help="get the value of the specified property")
airport_client_group.add_argument("--setprop", metavar=("property", "value"), nargs=2, help="set the value of the specified property")
airport_client_group.add_argument("--dumpprop", action="store_const", const=True, help="dump values of all supported properties")
airport_client_group.add_argument("--acpprop", action="store_const", const=True, help="get acp acpprop list")
airport_client_group.add_argument("--dump-syslog", action="store_const", const=True, help="dump the router system log")
airport_client_group.add_argument("--reboot", action="store_const", const=True, help="reboot device")
airport_client_group.add_argument("--factory-reset", action="store_const", const=True, help="RESET EVERYTHING and reboot; you have been warned!")
airport_client_group.add_argument("--flash-primary", metavar="firmware_path", nargs=1, help="flash primary partition firmware")
airport_client_group.add_argument("--do-feat-command", action="store_const", const=True, help="send 0x1b (feat) command")
basebinary_group = parser.add_argument_group("Basebinary commands")
basebinary_group.add_argument("--decrypt", metavar=("inpath", "outpath"), nargs=2, help="decrypt the basebinary")
basebinary_group.add_argument("--extract", metavar=("inpath", "outpath"), nargs=2, help="extract the gzimg contents")
test_group = parser.add_argument_group("Test arguments")
test_group.add_argument("--srp-test", action="store_const", const=True, help="SRP (requires OS X)")
args_dict = vars(parser.parse_args())
#TODO: give each element a dict containing parameter requirements/argparse infos, then generate parser based on this
commands = {
"listprop": "local",
"helpprop": "local",
"getprop": "remote_admin",
"setprop": "remote_admin",
"dumpprop": "remote_admin",
"acpprop": "remote_admin",
"dump_syslog": "remote_admin",
"reboot": "remote_admin",
"factory_reset": "remote_admin",
"flash_primary": "remote_admin",
"do_feat_command": "remote_noauth",
"decrypt": "local",
"extract": "local",
"srp_test": "remote_admin",
}
target = args_dict["target"]
password = args_dict["password"]
command_args = {k: v for k, v in args_dict.items() if k in commands and v is not None}
if len(command_args) == 0:
logging.error("must specify a command")
elif len(command_args) == 1:
#TODO: clean this up a bit
cmd, arg = command_args.popitem()
assert commands[cmd] in ["local", "remote_noauth", "remote_admin"], "unknown command type \"{0}\"".format(commands[cmd])
cmd_handler_name = "_cmd_{0}".format(cmd)
cmd_handler = globals().get(cmd_handler_name, _cmd_not_implemented)
if commands[cmd] == "local":
cmd_handler(arg)
if commands[cmd] == "remote_noauth":
if target is not None:
c = ACPClient(target)
c.connect()
cmd_handler(c, arg)
c.close()
else:
logging.error("must specify a target")
if commands[cmd] == "remote_admin":
if target is not None and password is not None:
c = ACPClient(target, password)
c.connect()
cmd_handler(c, arg)
c.close()
else:
logging.error("must specify a target and administrator password")
else:
logging.error("multiple commands not supported, choose only one")
================================================
FILE: acp/clibs/AppleSRP.py
================================================
from ctypes import *
#XXX: hax to display NULL pointer thingies
def _fmt_void_ptr(value):
if value is None:
return 0
return value
def _fmt_cstr(value):
if cast(value, c_void_p).value is None:
return "<null cstr>"
return value.contents.get_data_buffer().encode("hex")
def _fmt_ccz_class(value):
if cast(value, c_void_p).value is None:
return "<null ccz_class>"
return value.contents
def _fmt_ccz(value):
if cast(value, c_void_p).value is None:
return "<null ccz>"
return value.contents
class cstr(Structure):
_fields_ = [("data", c_void_p),
("length", c_long),
("cap", c_long),
("ref", c_int),
("allocator", c_void_p)]
def __str__(self):
s = "cstr: {0!r}\n".format(self)
s += "data: {0}\n".format(self.get_data_buffer().encode("hex"))
s += "len: {0}\n".format(self.length)
s += "cap: {0}\n".format(self.cap)
s += "ref: {0}\n".format(self.ref)
s += "alloc: {0:#x}".format(self.allocator)
return s
def get_data_buffer(self):
return string_at(self.data, self.length)
# define SHA_LBLOCK 16
'''
typedef struct SHAstate_st {
SHA_LONG h0, h1, h2, h3, h4;
SHA_LONG Nl, Nh;
SHA_LONG data[SHA_LBLOCK];
unsigned int num;
} SHA_CTX;
'''
class SHA1_CTX(Structure):
_fields_ = [("h0", c_uint),
("h1", c_uint),
("h2", c_uint),
("h3", c_uint),
("h4", c_uint),
("Nl", c_uint),
("Nh", c_uint),
("data", c_uint * 16), # uninitialized data???
("num", c_uint)]
def __str__(self):
s = "SHA1_CTX: {0!r}\n".format(self)
s += "h0: {0:#x}\n".format(self.h0)
s += "h1: {0:#x}\n".format(self.h1)
s += "h2: {0:#x}\n".format(self.h2)
s += "h3: {0:#x}\n".format(self.h3)
s += "h4: {0:#x}\n".format(self.h4)
s += "Nl: {0}\n".format(self.Nl)
s += "Nh: {0}\n".format(self.Nh)
s += "data: {0}\n".format("".join(["{0:08x}".format(self.data[i]) for i in range(16)])) # uninitialized data???
s += "num: {0}".format(self.num)
return s
#define RFC2945_KEY_LEN 40 /* length of session key (bytes) */
#define RFC2945_RESP_LEN 20 /* length of proof hashes (bytes) */
'''
struct client_meth_st {
SHA1_CTX hash;
SHA1_CTX ckhash;
unsigned char k[RFC2945_KEY_LEN];
};
'''
class client_meth_st(Structure):
_fields_ = [("hash", SHA1_CTX),
("ckhash", SHA1_CTX),
("k", c_ubyte * 40)]
def __str__(self):
s = "client_meth_st: {0!r}\n".format(self)
s += "hash: {0}\n".format(self.hash)
s += "ckhash: {0}\n".format(self.ckhash)
s += "k: {0}".format(self.k)
return s
'''
struct server_meth_st {
SHA1_CTX hash;
SHA1_CTX ckhash;
SHA1_CTX oldhash;
SHA1_CTX oldckhash;
unsigned char k[RFC2945_KEY_LEN];
unsigned char r[RFC2945_RESP_LEN];
};
'''
class server_meth_st(Structure):
_fields_ = [("hash", SHA1_CTX),
("ckhash", SHA1_CTX),
("oldhash", SHA1_CTX),
("oldckhash", SHA1_CTX),
("k", c_ubyte * 40),
("r", c_ubyte * 20)]
def __str__(self):
s = "server_meth_st: {0!r}\n".format(self)
s += "hash: {0}\n".format(self.hash)
s += "ckhash: {0}\n".format(self.ckhash)
s += "oldhash: {0}\n".format(self.oldhash)
s += "oldckhash: {0}\n".format(self.oldckhash)
s += "k: {0}\n".format(self.k)
s += "r: {0}".format(self.r)
return s
'''
struct ccz_class {
void *ctx;
void *(*ccz_alloc)(void *, size_t);
void *(*ccz_realloc)(void *, size_t, void *, size_t);
void (*ccz_free)(void *, size_t, void *);
};
'''
class ccz_class(Structure):
_fields_ = [("ctx", c_void_p),
("ccz_alloc", c_void_p),
("ccz_realloc", c_void_p),
("ccz_free", c_void_p)]
def __str__(self):
s = "ccz_class: {0!r}\n".format(self)
s += "ctx: {0:#x}\n".format(_fmt_void_ptr(self.ctx))
s += "ccz_alloc: {0:#x}\n".format(self.ccz_alloc)
s += "ccz_realloc: {0:#x}\n".format(self.ccz_realloc)
s += "ccz_free: {0:#x}".format(self.ccz_free)
return s
'''
struct ccz {
size_t n;
struct ccz_class *isa;
int sac;
cc_unit *u;
};
typedef struct ccz ccz;
'''
class ccz(Structure):
_fields_ = [("n", c_size_t),
("isa", POINTER(ccz_class)),
("sac", c_int),
("u", c_void_p)]
def __str__(self):
s = "ccz: {0!r}\n".format(self)
s += "n: {0}\n".format(self.n)
s += "isa: {0}\n".format(_fmt_ccz_class(self.isa))
s += "sac: {0}\n".format(self.sac)
s += "u: {0:#x}".format(_fmt_void_ptr(self.u))
return s
'''
struct srp_st {
int magic; /* To distinguish client from server (and for sanity) */
int flags;
cstr * username;
BigInteger modulus;
BigInteger generator;
cstr * salt;
BigInteger verifier;
BigInteger password;
BigInteger pubkey;
BigInteger secret;
BigInteger u;
BigInteger key;
cstr * ex_data;
SRP_METHOD * meth;
void * meth_data;
BigIntegerCtx bctx; /* to cache temporaries if available */
BigIntegerModAccel accel; /* to accelerate modexp if available */
SRP_CLIENT_PARAM_VERIFY_CB param_cb; /* to verify params */
SRP_SERVER_LOOKUP * slu; /* to look up users */
};
'''
class srp_st(Structure):
_fields_ = [("magic", c_int),
("flags", c_int),
("username", POINTER(cstr)),
("modulus", POINTER(ccz)),
("generator", POINTER(ccz)),
("salt", POINTER(cstr)),
("verifier", POINTER(ccz)),
("password", POINTER(ccz)),
("pubkey", POINTER(ccz)),
("secret", POINTER(ccz)),
("u", POINTER(ccz)),
("key", POINTER(ccz)),
("ex_data", POINTER(cstr)),
("meth", c_void_p),
#XXXXXXXXXXXXXXXX
("meth_data", POINTER(client_meth_st)),
#("meth_data", POINTER(server_meth_st)),
#XXXXXXXXXXXXXXXX
("bctx", c_void_p),
("accel", c_void_p),
("param_cb", c_void_p),
("slu", c_void_p)]
def __str__(self):
s = "*** START ***\n"
s += "srp_st: {0!r}\n".format(self)
s += "magic: {0}\n".format(self.magic)
s += "flags: {0}\n".format(self.flags)
s += "username: {0}\n".format(_fmt_cstr(self.username))
s += "modulus: {0}\n".format(_fmt_ccz(self.modulus))
s += "generator: {0}\n".format(_fmt_ccz(self.generator))
s += "salt: {0}\n".format(_fmt_cstr(self.salt))
s += "verifier: {0}\n".format(_fmt_ccz(self.verifier))
s += "password: {0}\n".format(_fmt_ccz(self.password))
s += "pubkey: {0}\n".format(_fmt_ccz(self.pubkey))
s += "secret: {0}\n".format(_fmt_ccz(self.secret))
s += "u: {0}\n".format(_fmt_ccz(self.u))
s += "key: {0}\n".format(_fmt_ccz(self.key))
s += "ex_data: {0}\n".format(_fmt_cstr(self.ex_data))
s += "meth: {0:#x}\n".format(_fmt_void_ptr(self.meth))
s += "meth_data: {0}\n".format(self.meth_data.contents) #XXXXXXXXXXXXXXXX
s += "bctx: {0:#x}\n".format(_fmt_void_ptr(self.bctx))
s += "accel: {0:#x}\n".format(_fmt_void_ptr(self.accel))
s += "param_cb: {0:#x}\n".format(_fmt_void_ptr(self.param_cb))
s += "slu: {0:#x}\n".format(_fmt_void_ptr(self.slu))
s += "**** END ****"
return s
__AppleSRP = cdll.LoadLibrary("/System/Library/PrivateFrameworks/AppleSRP.framework/Versions/A/AppleSRP")
#print "AppleSRP:", __AppleSRP
# SRP_METHOD *SRP6a_client_method(void)
SRP6a_client_method = __AppleSRP.SRP6a_client_method
SRP6a_client_method.restype = c_void_p
# SRP_METHOD *SRP6a_server_method(void)
SRP6a_server_method = __AppleSRP.SRP6a_server_method
SRP6a_server_method.restype = c_void_p
# SRP *SRP_new(SRP_METHOD *meth)
SRP_new = __AppleSRP.SRP_new
#SRP_new.restype = c_void_p
SRP_new.restype = POINTER(srp_st)
SRP_new.argtypes = [ c_void_p ]
# SRP_RESULT SRP_set_username(SRP *srp, const char *username)
SRP_set_username = __AppleSRP.SRP_set_username
SRP_set_username.argtypes = [ c_void_p, c_char_p ]
# SRP_RESULT SRP_set_params(SRP *srp, const unsigned char *modulus, int modlen,
# const unsigned char *generator, int genlen,
# const unsigned char *salt, int saltlen)
SRP_set_params = __AppleSRP.SRP_set_params
SRP_set_params.argtypes = [ c_void_p, c_char_p, c_int, c_char_p, c_int, c_char_p, c_int ]
# SRP_RESULT SRP_gen_pub(SRP *srp, cstr **result)
SRP_gen_pub = __AppleSRP.SRP_gen_pub
SRP_gen_pub.argtypes = [ c_void_p, POINTER(POINTER(cstr)) ]
# SRP_RESULT SRP_set_auth_password(SRP *srp, const unsigned char *password, int passlen)
SRP_set_auth_password = __AppleSRP.SRP_set_auth_password
SRP_set_auth_password.argtypes = [ c_void_p, c_char_p, c_int ]
# SRP_RESULT SRP_compute_key(SRP *srp, cstr **result, const unsigned char *pubkey, int pubkeylen)
SRP_compute_key = __AppleSRP.SRP_compute_key
SRP_compute_key.argtypes = [ c_void_p, POINTER(POINTER(cstr)), c_char_p, c_int ]
# SRP_RESULT SRP_respond(SRP *srp, cstr **proof)
SRP_respond = __AppleSRP.SRP_respond
SRP_respond.argtypes = [ c_void_p, POINTER(POINTER(cstr)) ]
# SRP_RESULT SRP_verify(SRP *srp, const unsigned char *proof, int prooflen)
SRP_verify = __AppleSRP.SRP_verify
SRP_verify.argtypes = [ c_void_p, c_char_p, c_int ]
# SRP_RESULT SRP_free(SRP *srp)
SRP_free = __AppleSRP.SRP_free
SRP_free.argtypes = [ c_void_p ]
# cstr *cstr_new(void)
cstr_new = __AppleSRP.cstr_new
cstr_new.restype = POINTER(cstr)
# void cstr_free(cstr *str)
cstr_free = __AppleSRP.cstr_free
cstr_free.restype = None
cstr_free.argtypes = [ POINTER(cstr) ]
================================================
FILE: acp/clibs/__init__.py
================================================
================================================
FILE: acp/client.py
================================================
import logging
import os
import struct
import time
from .cflbinary import CFLBinaryPListComposer, CFLBinaryPListParser
from .message import ACPMessage
from .property import ACPProperty
from .session import ACPClientSession
class ACPClient(object):
def __init__(self, target, password=""):
self.target = target
self.password = password
self.session = ACPClientSession(target, password)
def connect(self):
self.session.connect()
def close(self):
self.session.close()
def send(self, data):
self.session.send(data)
def recv(self, size):
return self.session.recv(size)
def recv_message_header(self):
return self.recv(ACPMessage.header_size)
def recv_property_element_header(self):
return self.recv(ACPProperty.element_header_size)
def get_properties(self, prop_names=[]):
# request property by sending name and "null" value
payload = ""
for name in prop_names:
payload += ACPProperty.compose_raw_element(0, ACPProperty(name))
request = ACPMessage.compose_getprop_command(4, self.password, payload)
self.send(request)
raw_reply = self.recv_message_header()
reply_header = ACPMessage.parse_raw(raw_reply)
if reply_header.error_code != 0:
print "get_properties error code: {0:#x}".format(reply_header.error_code)
#XXX: blah, what to do...
return []
props = []
while True:
prop_header = self.recv_property_element_header()
name, flags, size = ACPProperty.parse_raw_element_header(prop_header)
logging.debug("name ".format(name))
logging.debug("flags ".format(flags))
logging.debug("size ".format(size))
prop_data = self.recv(size)
logging.debug("prop_data {0!r}".format(prop_data))
if flags & 1:
(error_code, ) = struct.unpack(">I", prop_data)
print "error requesting value for property \"{0}\": {1:#x}".format(name, error_code)
continue
prop = ACPProperty(name, prop_data)
logging.debug("prop {0!r}".format(prop))
#XXX: this is still a bit ugly
if prop.name is None and prop.value is None:
logging.debug("found empty prop end marker")
break
#XXX: should we should return dict(name=name, prop=ACPProperty(name, value)) instead?
props.append(prop)
return props
def set_properties(self, props_dict={}):
payload = ""
for name, prop in props_dict.iteritems():
logging.debug("prop: {0!r}".format(prop))
payload += ACPProperty.compose_raw_element(0, prop)
request = ACPMessage.compose_setprop_command(0, self.password, payload)
self.send(request)
raw_reply = self.recv_message_header()
reply_header = ACPMessage.parse_raw(raw_reply)
if reply_header.error_code != 0:
print "set_properties error code: {0:#x}".format(reply_header.error_code)
#XXX: blah, what to do...
return
prop_header = self.recv_property_element_header()
name, flags, size = ACPProperty.parse_raw_element_header(prop_header)
logging.debug("name {0!r}".format(name))
logging.debug("flags {0!r}".format(flags))
logging.debug("size {0!r}".format(size))
prop_data = self.recv(size)
logging.debug("prop_data {0!r}".format(prop_data))
if flags & 1:
(error_code, ) = struct.unpack(">I", prop_data)
print "error setting value for property \"{0}\": {1:#x}".format(name, error_code)
return
prop = ACPProperty(name, prop_data)
logging.debug("prop {0!r}".format(prop))
#XXX: this is still a bit ugly
if prop.name is None and prop.value is None:
logging.debug("found empty prop end marker")
def get_features(self):
self.send(ACPMessage.compose_feat_command(0))
reply_header = ACPMessage.parse_raw(self.recv_message_header())
reply = self.recv(reply_header.body_size)
return CFLBinaryPListParser.parse(reply)
def flash_primary(self, payload):
self.send(ACPMessage.compose_flash_primary_command(0, self.password, payload))
reply_header = ACPMessage.parse_raw(self.recv_message_header())
return self.recv(reply_header.body_size)
def authenticate_AppleSRP(self):
#XXX: STILL TESTING SHIT
import ctypes
from .clibs import AppleSRP
from collections import OrderedDict
username = u"admin"
dic = OrderedDict([(u"state", 1), (u"username", username)])
payload = CFLBinaryPListComposer.compose(dic)
raw_message = ACPMessage.compose_auth_command(4, payload)
self.send(raw_message)
raw_reply_header = self.recv_message_header()
reply_header = ACPMessage.parse_raw(raw_reply_header)
if reply_header.error_code != 0:
logging.error("authenticate error code: {0:#x}".format(reply_header.error_code))
#XXX: blah, what to do...
return
logging.debug("recv_size: {0}".format(reply_header.body_size))
raw_message = self.recv(reply_header.body_size)
logging.debug("raw_message: {0!r}".format(raw_message))
params1 = CFLBinaryPListParser.parse(raw_message)
logging.debug(params1)
n = params1[u"modulus"]
g = params1[u"generator"]
salt = params1[u"salt"]
server_pkey = params1[u"publicKey"]
nhex = n.encode("hex")
ghex = g.encode("hex")
logging.debug("nhex: {0}".format(nhex))
logging.debug("ghex: {0}".format(ghex))
logging.debug("salt: {0}".format(salt.encode("hex")))
logging.debug("server_pkey: {0}".format(server_pkey.encode("hex")))
# create SRP context
asrp = AppleSRP.SRP_new(AppleSRP.SRP6a_client_method())
#logging.debug(asrp.contents)
# set username
logging.debug("SRP_set_username: {0}".format(AppleSRP.SRP_set_username(asrp, username)))
#logging.debug(asrp.contents)
# set parameters from server
logging.debug("SRP_set_params: {0}".format(AppleSRP.SRP_set_params(asrp, n, len(n), g, len(g), salt, len(salt))))
#logging.debug(asrp.contents)
# generate public key
client_gen_pubkey_ptr = AppleSRP.cstr_new()
logging.debug("SRP_gen_pub: {0}".format(AppleSRP.SRP_gen_pub(asrp, ctypes.byref(client_gen_pubkey_ptr))))
client_gen_pubkey = client_gen_pubkey_ptr.contents
logging.debug(client_gen_pubkey)
#logging.debug(asrp.contents)
# set password
logging.debug("SRP_set_auth_password: {0}".format(AppleSRP.SRP_set_auth_password(asrp, self.password, len(self.password))))
#logging.debug(asrp.contents)
# compute key
client_computed_key_ptr = AppleSRP.cstr_new()
logging.debug("SRP_compute_key: {0}".format(AppleSRP.SRP_compute_key(asrp, ctypes.byref(client_computed_key_ptr), server_pkey, len(server_pkey))))
client_computed_key = client_computed_key_ptr.contents
logging.debug(client_computed_key)
client_computed_key_buf = client_computed_key.get_data_buffer()
#logging.debug(asrp.contents)
# generate challenge response
client_proof_ptr = AppleSRP.cstr_new()
logging.debug("SRP_respond: {0}".format(AppleSRP.SRP_respond(asrp, ctypes.byref(client_proof_ptr))))
client_proof = client_proof_ptr.contents
logging.debug(client_proof)
#logging.debug(asrp.contents)
client_iv = os.urandom(0x10)
client_pkey = client_gen_pubkey.get_data_buffer()
client_proof = client_proof.get_data_buffer()
dic = OrderedDict([(u"iv", client_iv), (u"publicKey", client_pkey), (u"state", 3), (u"response", client_proof)])
payload = CFLBinaryPListComposer.compose(dic)
raw_message = ACPMessage.compose_auth_command(4, payload)
self.send(raw_message)
raw_reply_header = self.recv_message_header()
reply_header = ACPMessage.parse_raw(raw_reply_header)
if reply_header.error_code != 0:
logging.debug("authenticate error code: {0:#x}".format(reply_header.error_code))
#XXX: blah, what to do...
return
logging.debug("recv_size: {0}".format(reply_header.body_size))
raw_message = self.recv(reply_header.body_size)
logging.debug("raw_message: {0!r}".format(raw_message))
params2 = CFLBinaryPListParser.parse(raw_message)
logging.debug(params2)
server_proof = params2[u"response"]
server_iv = params2[u"iv"]
# verify server response
logging.debug("SRP_verify: {0}".format(AppleSRP.SRP_verify(asrp, server_proof, len(server_proof))))
#logging.debug(asrp.contents)
# cleanup
logging.debug("Freeing cstr(s)")
AppleSRP.cstr_free(client_gen_pubkey_ptr)
AppleSRP.cstr_free(client_computed_key_ptr)
AppleSRP.cstr_free(client_proof_ptr)
logging.debug("SRP_free: {0}".format(AppleSRP.SRP_free(asrp)))
###self.session.enable_encryption(client_computed_key_buf, client_iv, server_iv)
================================================
FILE: acp/encryption.py
================================================
from Crypto.Cipher import AES
from Crypto.Protocol import KDF
from Crypto.Util import Counter
PBKDF_salt0 = "F072FA3F66B410A135FAE8E6D1D43D5F".decode("hex")
PBKDF_salt1 = "BD0682C9FE79325BC73655F4174B996C".decode("hex")
class _ACPEncryptionContext(object):
def __init__(self, key, iv):
self.key = key
self.iv = iv
self.ctr = Counter.new(128, initial_value=int(iv.encode("hex"), 16))
self.cipher = AES.new(key, AES.MODE_CTR, counter=self.ctr)
class ACPEncryption(object):
def __init__(self, key, client_iv, server_iv):
self._client_context = self._init_client_context(key, client_iv)
self._server_context = self._init_server_context(key, server_iv)
def _init_client_context(cls, key, iv):
derived_key = KDF.PBKDF2(key, PBKDF_salt0, 16, 5)
return _ACPEncryptionContext(derived_key, iv)
def _init_server_context(cls, key, iv):
derived_key = KDF.PBKDF2(key, PBKDF_salt1, 16, 7)
return _ACPEncryptionContext(derived_key, iv)
def client_decrypt(self, data):
return self._client_context.cipher.decrypt(data)
def client_encrypt(self, data):
return self._client_context.cipher.encrypt(data)
def server_decrypt(self, data):
return self._server_context.cipher.decrypt(data)
def server_encrypt(self, data):
return self._server_context.cipher.encrypt(data)
================================================
FILE: acp/exception.py
================================================
#TODO: put other exceptions in here...
class ACPError(Exception):
"""Base class for exceptions in this module."""
pass
class ACPClientError(ACPError):
"""Exception raised for errors in the ACP client"""
pass
class ACPCommandLineError(ACPError):
"""Exception raised for command line invocation errors"""
pass
class ACPMessageError(ACPError):
"""Exception raised for errors processing ACP packets"""
pass
class ACPPropertyError(ACPError):
"""Exception raised for errors processing ACP properties"""
pass
================================================
FILE: acp/keystream.py
================================================
"""Static key/seed for keystream generation"""
ACP_STATIC_KEY = "5b6faf5d9d5b0e1351f2da1de7e8d673".decode("hex")
def generate_acp_keystream(length):
"""Get key used to encrypt the header key (and some message data?)
Args:
length (int): length of keystream to generate
Returns:
String of requested length
Note:
Keystream repeats every 256 bytes
"""
key = ""
key_idx = 0
while (key_idx < length):
key += chr((key_idx + 0x55 & 0xFF) ^ ord(ACP_STATIC_KEY[key_idx % len(ACP_STATIC_KEY)]))
key_idx += 1
return key
================================================
FILE: acp/message.py
================================================
import logging
import struct
import zlib
from .exception import ACPMessageError
from .keystream import *
def _generate_acp_header_key(password):
"""
Encrypt password for ACP message header key field
Note:
Truncates the password at 0x20 bytes, not sure if this is the right thing to use in all cases
Args:
password (str): system password of the router (syAP)
Returns:
String containing encrypted password of proper length for the header field
"""
pw_len = 0x20
pw_key = generate_acp_keystream(pw_len)
# pad with NULLs
pw_buf = password[:pw_len].ljust(pw_len, "\x00")
enc_pw_buf = ""
for i in range(pw_len):
enc_pw_buf += chr(ord(pw_key[i]) ^ ord(pw_buf[i]))
return enc_pw_buf
class ACPMessage(object):
"""ACP message composition and parsing"""
#XXX: struct is stupid about unpacking unsigned ints > 0x7fffffff, so treat everything as signed and
# "cast" where necessary. Should we switch to using ctypes?
_header_format = struct.Struct("!4s8i12x32s48x")
_header_magic = "acpp"
header_size = _header_format.size
def __init__(self, version, flags, unused, command, error_code, key, body=None, body_size=None):
self.version = version
self.flags = flags
self.unused = unused
self.command = command
self.error_code = error_code
# body is not specified, this is a stream header
if body == None:
# the body size is already specified, don't override it
self.body_size = body_size if body_size != None else -1
self.body_checksum = 1 # equivalent to zlib.adler32("")
else:
# the body size is already specified, don't override it
self.body_size = body_size if body_size != None else len(body)
self.body_checksum = zlib.adler32(body)
self.key = key
self.body = body
def __str__(self):
s = "ACPMessage: {0!r}\n".format(self)
s += "body_checksum: {0:#x}\n".format(self.body_checksum)
s += "body_size: {0:#x}\n".format(self.body_size)
s += "flags: {0:#x}\n".format(self.flags)
s += "unused: {0:#x}\n".format(self.unused)
s += "command: {0:#x}\n".format(self.command)
s += "error_code: {0:#x}\n".format(self.error_code)
s += "key: {0!r}".format(self.key)
return s
@classmethod
def parse_raw(cls, data):
# bail early if there is not enough data
if len(data) < cls.header_size:
raise ACPMessageError("need to pass at least {0} bytes".format(cls.header_size))
header_data = data[:cls.header_size]
# make sure there's data beyond the header before we try to access it
body_data = data[cls.header_size:] if len(data) > cls.header_size else None
(magic, version, header_checksum, body_checksum, body_size, flags, unused, command, error_code, key) = cls._header_format.unpack(header_data)
logging.debug("ACP message header fields, parsed not validated")
logging.debug("magic {0!r}".format(magic))
logging.debug("header_checksum {0:#x}".format(header_checksum))
logging.debug("body_checksum {0:#x}".format(body_checksum))
logging.debug("body_size {0:#x}".format(body_size))
logging.debug("flags {0:#x}".format(flags))
logging.debug("unused {0:#x}".format(unused))
logging.debug("command {0:#x}".format(command))
logging.debug("error_code {0:#x}".format(error_code))
logging.debug("key {0!r}".format(key))
if magic != cls._header_magic:
raise ACPMessageError("bad header magic")
if version not in [0x00000001, 0x00030001]:
raise ACPMessageError("invalid version")
#TODO: can we zero the header_checksum field without recreating the struct (how?)
tmphdr = cls._header_format.pack(magic, version, 0, body_checksum, body_size, flags, unused, command, error_code, key)
if header_checksum != zlib.adler32(tmphdr):
raise ACPMessageError("header checksum does not match")
if body_data and body_size == -1:
raise ACPMessageError("cannot handle stream header with data attached")
if body_data and body_size != len(body_data):
raise ACPMessageError("message body size does not match available data")
if body_data and body_checksum != zlib.adler32(body_data):
raise ACPMessageError("body checksum does not match")
#TODO: check flags
#TODO: check status
if command not in [1, 3, 4, 5, 6, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b]:
raise ACPMessageError("unknown command")
#TODO: check error code
return cls(version, flags, unused, command, error_code, key, body_data, body_size)
@classmethod
def compose_echo_command(cls, flags, password, payload):
return cls(0x00030001, flags, 0, 1, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
@classmethod
def compose_flash_primary_command(cls, flags, password, payload):
return cls(0x00030001, flags, 0, 3, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
@classmethod
def compose_flash_secondary_command(cls, flags, password, payload):
return cls(0x00030001, flags, 0, 5, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
@classmethod
def compose_flash_bootloader_command(cls, flags, password, payload):
return cls(0x00030001, flags, 0, 6, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
@classmethod
def compose_getprop_command(cls, flags, password, payload):
return cls(0x00030001, flags, 0, 0x14, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
@classmethod
def compose_setprop_command(cls, flags, password, payload):
return cls(0x00030001, flags, 0, 0x15, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
@classmethod
def compose_perform_command(cls, flags, password, payload):
return cls(0x00030001, flags, 0, 0x16, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
@classmethod
def compose_monitor_command(cls, flags, password, payload):
return cls(0x00030001, flags, 0, 0x18, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
@classmethod
def compose_rpc_command(cls, flags, password, payload):
return cls(0x00030001, flags, 0, 0x19, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
@classmethod
def compose_auth_command(cls, flags, payload):
return cls(0x00030001, flags, 0, 0x1a, 0, _generate_acp_header_key(""), payload)._compose_raw_packet()
@classmethod
def compose_feat_command(cls, flags):
return cls(0x00030001, flags, 0, 0x1b, 0, _generate_acp_header_key(""))._compose_raw_packet()
@classmethod
def compose_message_ex(cls, version, flags, unused, command, error_code, password, payload, payload_size):
return cls(version, flags, unused, command, error_code, _generate_acp_header_key(password), payload, payload_size)._compose_raw_packet()
def _compose_raw_packet(self):
"""Compose a request from the client to ACP daemon
Returns:
String containing message to send
"""
reply = self._compose_header()
if self.body:
reply += self.body
return reply
def _compose_header(self):
"""Compose the message header
Returns:
String containing header data
"""
tmphdr = self._header_format.pack(self._header_magic,
self.version,
0,
self.body_checksum,
self.body_size,
self.flags,
self.unused,
self.command,
self.error_code,
self.key)
header = self._header_format.pack(self._header_magic,
self.version,
zlib.adler32(tmphdr),
self.body_checksum,
self.body_size,
self.flags,
self.unused,
self.command,
self.error_code,
self.key)
return header
================================================
FILE: acp/misc.py
================================================
#XXX: this file exists until I think of a better way to do this
def cast_u32(value):
#XXX: lazy, how do we do this correctly?
if value < -0x80000000 or value > 0x7FFFFFFF:
raise Exception("value outside u32 range")
return value & 0xFFFFFFFF
================================================
FILE: acp/property.py
================================================
import logging
import pprint
import struct
from .cflbinary import CFLBinaryPListParser
from .exception import ACPPropertyError
_acp_properties = [
# Uncomment and fill out relevant fields to add support for a property
# Properties must be in the following format:
# (name, type, description, validation), where
# name (required) is a 4 character string,
# type (required) is a valid property type (str, dec, hex, log, mac, cfb, bin)
# description (required) is a short, one-line description of the property
# validation (optional) is eval()d to verify the input value for setting a property
("buil","str","Build string?",""),
("DynS","cfb","DNS",""),
#("cfpf","","",""),
#("cloC","","",""),
#("cloD","","",""),
#("conf","","",""),
("fire","cfb","Firewall???",""),
#("prob","","",""),
("srcv","str","Source Version",""),
("syNm","str","Device name",""),
#("syDN","","",""),
#("syPI","","",""),
("syPW","str","Router administration password",""),
("syPR","str","syPR string???",""),
("syGP","str","Router guest password???",""),
#("syCt","","",""),
#("syLo","","",""),
("syDs","str","System description",""),
("syVs","str","System version",""),
("syVr","str","System version???",""),
("syIn","str","System information???",""),
("syFl","hex","????",""),
("syAM","str","Model Identifier",""),
("syAP","dec","Product ID",""),
("sySN","str","Apple Serial Number",""),
#("ssSN","","",""),
#("sySK","","",""),
("ssSK","str","Apple SKU",""),
#("syRe","","",""),
("syLR","cfb","syLR Blob",""),
("syAR","cfb","syAR Blob",""),
("syUT","dec","System Uptime",""),
#("minV","","",""),
("minS","str","apple-minver",""),
("chip","str","SoC Description",""),
#("card","","",""),
#("memF","","",""),
#("pool","","",""),
#("tmpC","","",""),
#("RPMs","","",""),
("sySI","cfb","System Info Blob?",""),
#("fDCY","","",""),
("TMEn","hex","TMEn???",""),
("CLTM","cfb","CLTM???",""),
#("sPLL","","",""),
#("syTL","","",""),
#("syST","","",""),
("sySt","cfb","System Status",""),
#("syIg","","",""),
("syBL","str","Bootloader vesrsion string",""),
("time","dec","System time",""),
("timz","cfb","Timezone Config Blob",""),
("usrd","cfb","usrd???",""),
#("uuid","","",""),
#("drTY","","",""),
#("sttE","","",""),
#("sttF","","",""),
#("stat","","",""),
#("sRnd","","",""),
#("Accl","","",""),
("dSpn","cfb","Disk spin status?",""),
("syMS","str","MLB Serial Number",""),
#("IGMP","","",""),
("diag","bin","diag???",""),
#("paFR","","",""),
("raNm","str","Radio Name",""),
#("raCl","","",""),
#("raSk","","",""),
#("raWM","","",""),
#("raEA","","",""),
#("raWE","","",""),
#("raCr","","",""),
#("raKT","","",""),
#("raNN","","",""),
#("raGK","","",""),
#("raHW","","",""),
#("raCM","","",""),
#("raRo","","",""),
#("raCA","","",""),
#("raCh","","",""),
#("rCh2","","",""),
#("raWC","","",""),
#("raDe","","",""),
#("raMu","","",""),
#("raLC","","",""),
#("raLF","","",""),
#("ra1C","","",""),
#("raVs","","",""),
("raMA","mac","Radio MAC Address",""),
#("raM2","","",""),
#("raMO","","",""),
#("raLO","","",""),
#("raDS","","",""),
#("raNA","","",""),
#("raWB","","",""),
#("raIS","","",""),
#("raMd","","",""),
#("raPo","","",""),
#("raPx","","",""),
#("raTr","","",""),
#("raDt","","",""),
#("raFC","","",""),
#("raEC","","",""),
#("raMX","","",""),
#("raIE","","",""),
#("raII","","",""),
#("raB0","","",""),
#("raB1","","",""),
#("raB2","","",""),
#("raSt","","",""),
#("APSR","","",""),
#("raTX","","",""),
#("raRX","","",""),
#("raAC","","",""),
("raSL","cfb","Radios list?",""),
#("raMI","","",""),
#("raST","","",""),
#("raDy","","",""),
#("raEV","","",""),
#("rTSN","","",""),
("raSR","cfb","Radio scan results?",""),
#("eaRA","","",""),
("WiFi","cfb","Wifi configuration?",""),
("rCAL","cfb","Radio calibration data?",""),
#("moPN","","",""),
#("moAP","","",""),
#("moUN","","",""),
#("moPW","","",""),
#("moIS","","",""),
#("moLS","","",""),
#("moLI","","",""),
#("moID","","",""),
#("moDT","","",""),
#("moPD","","",""),
#("moAD","","",""),
#("moCC","","",""),
#("moCR","","",""),
#("moCI","","",""),
#("^moM","","",""),
#("moVs","","",""),
#("moMP","","",""),
#("moMF","","",""),
#("moFV","","",""),
#("pdFl","","",""),
#("pdUN","","",""),
#("pdPW","","",""),
#("pdAR","","",""),
#("pdID","","",""),
#("pdMC","","",""),
#("peSN","","",""),
#("peUN","","",""),
#("pePW","","",""),
#("peSC","","",""),
#("peAC","","",""),
#("peID","","",""),
#("peAO","","",""),
("waCV","bin","WAN Config Mode?",""),
("waIn","bin","WAN Interface Mode?",""),
#("waD1","","",""),
#("waD2","","",""),
#("waD3","","",""),
#("waC1","","",""),
#("waC2","","",""),
#("waC3","","",""),
("waIP","bin","WAN IP",""),
#("waSM","","",""),
("waRA","bin","WAN Upstream Gateway IP",""),
#("waDC","","",""),
#("waDS","","",""),
("waMA","mac","WAN MAC Address",""),
#("waMO","","",""),
#("waDN","","",""),
#("waCD","","",""),
#("waIS","","",""),
#("waNM","","",""),
#("waSD","","",""),
#("waFF","","",""),
#("waRO","","",""),
#("waW1","","",""),
#("waW2","","",""),
#("waW3","","",""),
#("waLL","","",""),
#("waUB","","",""),
("waDI","cfb","WAN DHCP Info?",""),
#("laCV","","",""),
#("laIP","","",""),
#("laSM","","",""),
#("laRA","","",""),
#("laDC","","",""),
#("laDS","","",""),
#("laNA","","",""),
("laMA","mac","LAN MAC Address",""),
#("laIS","","",""),
#("laSD","","",""),
#("laIA","","",""),
#("gn6?","","",""),
#("gn6A","","",""),
#("gn6P","","",""),
#("dhFl","","",""),
#("dhBg","","",""),
#("dhEn","","",""),
#("dhSN","","",""),
#("dhRo","","",""),
#("dhLe","","",""),
#("dhMg","","",""),
#("dh95","","",""),
("DRes","cfb","DHCP Reservations",""),
#("dhWA","","",""),
#("dhDS","","",""),
#("dhDB","","",""),
#("dhDE","","",""),
#("dhDL","","",""),
("dhSL","cfb","DHCP Server leases?",""),
#("gnFl","","",""),
#("gnBg","","",""),
#("gnEn","","",""),
#("gnSN","","",""),
#("gnRo","","",""),
#("gnLe","","",""),
#("gnMg","","",""),
#("gn95","","",""),
#("gnDi","","",""),
#("naFl","","",""),
#("naBg","","",""),
#("naEn","","",""),
#("naSN","","",""),
#("naRo","","",""),
#("naAF","","",""),
#("nDMZ","","",""),
#("pmPI","","",""),
#("pmPS","","",""),
#("pmPR","","",""),
#("pmTa","","",""),
#("acEn","","",""),
#("acTa","","",""),
("tACL","cfb","Timed Access Control",""),
#("wdFl","","",""),
#("wdLs","","",""),
#("dWDS","","",""),
#("cWDS","","",""),
#("dwFl","","",""),
#("raFl","","",""),
#("raI1","","",""),
#("raTm","","",""),
#("raAu","","",""),
#("raAc","","",""),
#("raSe","","",""),
#("raRe","","",""),
#("raF2","","",""),
#("raI2","","",""),
#("raT2","","",""),
#("raU2","","",""),
#("raC2","","",""),
#("raS2","","",""),
#("raR2","","",""),
#("raCi","","",""),
("ntSV","str","NTP Server Hostname",""),
#("ntpC","","",""),
#("smtp","","",""),
#("slog","","",""),
#("slgC","","",""),
#("slCl","","",""),
("slvl","dec","System log severity level?",""),
#("slfl","","",""),
("logm","log","System log data",""),
#("snAF","","",""),
#("snLW","","",""),
#("snLL","","",""),
#("snRW","","",""),
#("snWW","","",""),
#("snRL","","",""),
#("snWL","","",""),
#("snCS","","",""),
#("srtA","","",""),
#("srtF","","",""),
#("upsF","","",""),
#("usbF","","",""),
("USBi","cfb","USB Info",""),
#("USBL","","",""),
#("USBR","","",""),
#("USBO","","",""),
#("USBs","","",""),
#("USBo","","",""),
#("USBh","","",""),
#("USBb","","",""),
#("USBn","","",""),
("prni","cfb","Printer Info?",""),
#("prnM","","",""),
#("prnI","","",""),
#("prnR","","",""),
#("RUdv","","",""),
#("RUfl","","",""),
("MaSt","cfb","USB Mass Storage Info",""),
#("SMBw","","",""),
#("SMBs","","",""),
#("fssp","","",""),
#("diSD","","",""),
#("diCS","","",""),
#("deSt","","",""),
#("daSt","","",""),
#("dmSt","","",""),
#("adNm","","",""),
#("adBD","","",""),
#("adAD","","",""),
#("adHU","","",""),
#("IDNm","","",""),
("seFl","bin","????",""), #????
#("nvVs","","",""),
#("dbRC","","",""),
("dbug","hex","Debug flags","0 <= value <= 0xFFFFFFFF"),
#("dlvl","","",""),
#("dcmd","","",""),
#("dsps","","",""),
#("logC","","",""),
#("cver","","",""),
("ctim","hex","ctim???",""),
#("svMd","","",""),
#("serM","","",""),
#("serT","","",""),
#("emNo","","",""),
#("effF","","",""),
#("LLnk","","",""),
#("WLnk","","",""),
#("PHYS","","",""),
#("PHYN","","",""),
#("Rnfo","","",""),
#("evtL","","",""),
#("isAC","","",""),
#("Adet","","",""),
("Prof","cfb","Restore Profile Blob",""),
#("maAl","","",""),
#("maPr","","",""),
#("leAc","","",""),
#("APID","","",""),
#("AAU ","","",""),
("lcVs","str","lcVs Version String?",""),
#("lcVr","","",""),
#("lcmV","","",""),
#("lcMV","","",""),
#("iMTU","","",""),
("wsci","cfb","wsci Blob",""),
#("FlSu","","",""),
("OTPR","hex","machdep.otpval",""),
("acRB","dec","Reboot device flag","value == 0"),
("acRI","dec","Reload services??","value == 0"),
#("acPC","","",""),
#("acDD","","",""),
#("acPD","","",""),
#("acPG","","",""),
#("acDS","","",""),
#("acFN","","",""),
#("acRP","","",""),
("acRN","dec","Resets something... (?)","value == 0"),
("acRF","dec","Reset to factory defaults","value == 0"),
#("MdmH","","",""),
#("dirf","","",""),
#("Afrc","","",""),
#("lebl","","",""),
#("lebs","","",""),
("LEDc","dec","LED color/pattern","0 <= value <= 3"),
#("acEf","","",""),
#("invr","","",""),
#("FLSH","","",""),
#("acPL","","",""),
#("rReg","","",""),
#("dReg","","",""),
("GPIs","bin","GPIOs values","len(value) == 8"),
#("play","","",""),
#("paus","","",""),
#("ffwd","","",""),
#("rwnd","","",""),
#("itun","","",""),
#("plls","","",""),
#("User","","",""),
#("Pass","","",""),
#("itIP","","",""),
#("itpt","","",""),
#("daap","","",""),
#("song","","",""),
#("arti","","",""),
#("albm","","",""),
#("volm","","",""),
#("rvol","","",""),
#("Tcnt","","",""),
#("Bcnt","","",""),
#("shfl","","",""),
#("rept","","",""),
#("auPr","","",""),
#("auJD","","",""),
#("auNN","","",""),
#("auNP","","",""),
#("aFrq","","",""),
#("aChn","","",""),
#("aLvl","","",""),
#("aPat","","",""),
#("aSta","","",""),
#("aStp","","",""),
#("auCC","","",""),
#("acmp","","",""),
#("aenc","","",""),
#("anBf","","",""),
#("aWan","","",""),
#("auRR","","",""),
#("auMt","","",""),
#("aDCP","","",""),
#("DCPc","","",""),
#("DACP","","",""),
#("DCPi","","",""),
#("auSl","","",""),
#("auFl","","",""),
("fe01","hex","????",""),
("feat","str","Supported features?",""),
("prop","str","Valid acp properties",""),
("hw01","hex","????",""),
#("fltr","","",""),
#("wdel","","",""),
#("plEB","","",""),
#("rWSC","","",""),
#("uDFS","","",""),
#("dWPA","","",""),
#("dpFF","","",""),
#("duLF","","",""),
#("ieHT","","",""),
#("dwlX","","",""),
#("dd11","","",""),
#("dRdr","","",""),
#("dotD","","",""),
#("dotH","","",""),
#("dPwr","","",""),
#("wlBR","","",""),
#("iTIM","","",""),
#("idAG","","",""),
#("mvFL","","",""),
#("mvFM","","",""),
#("dPPP","","",""),
#("!mta","","",""),
#("minR","","",""),
#("SpTr","","",""),
#("dRBT","","",""),
#("dRIR","","",""),
("pECC","cfb","PCIe ECC Blob?",""),
#("fxEB","","",""),
#("fxID","","",""),
#("fuup","","",""),
#("fust","","",""),
#("fuca","","",""),
("fugp","str","Firmware upgrade progress",""),
("cks0","hex","Bootloader Flash Checksum",""),
("cks1","hex","Primary Flash Checksum",""),
("cks2","hex","Secondary Flash Checksum",""),
#("ddBg","","",""),
#("ddEn","","",""),
#("ddIn","","",""),
#("ddSm","","",""),
#("ddEC","","",""),
#("ddFE","","",""),
#("ddSR","","",""),
#("6cfg","","",""),
#("6aut","","",""),
#("6Qpd","","",""),
#("6Wad","","",""),
#("6Wfx","","",""),
#("6Wgw","","",""),
#("6Wte","","",""),
#("6Lfw","","",""),
#("6Lad","","",""),
#("6Lfx","","",""),
#("6sfw","","",""),
#("6pmp","","",""),
#("6trd","","",""),
#("6sec","","",""),
#("6fwl","","",""),
#("6NS1","","",""),
#("6NS2","","",""),
#("6NS3","","",""),
#("6ahr","","",""),
#("6dhs","","",""),
#("6dso","","",""),
#("6PDa","","",""),
#("6PDl","","",""),
#("6vlt","","",""),
#("6plt","","",""),
#("6CWa","","",""),
#("6CWp","","",""),
#("6CWg","","",""),
#("6CLa","","",""),
#("6NSa","","",""),
#("6NSb","","",""),
#("6NSc","","",""),
#("6CPa","","",""),
#("6CPl","","",""),
#("6!at","","",""),
("rteI","cfb","rteI Blob",""),
#("PCLI","","",""),
#("dxEM","","",""),
#("dxID","","",""),
#("dxAI","","",""),
#("dxIP","","",""),
#("dxOA","","",""),
#("dxIA","","",""),
#("dxC1","","",""),
#("dxP1","","",""),
#("dxC2","","",""),
#("dxP2","","",""),
#("bjFl","","",""),
#("bjSd","","",""),
#("bjSM","","",""),
#("wbEn","","",""),
#("wbHN","","",""),
#("wbHU","","",""),
#("wbHP","","",""),
#("wbRD","","",""),
#("wbRU","","",""),
#("wbRP","","",""),
#("wbAC","","",""),
#("dMac","","",""),
#("iCld","","",""),
#("iCLH","","",""),
#("iCLB","","",""),
#("SUEn","","",""),
#("SUAI","","",""),
#("SUFq","","",""),
#("SUSv","","",""),
#("suPR","","",""),
#("msEn","","",""),
#("trCo","","",""),
#("EZCF","","",""),
#("ezcf","","",""),
#("gVID","","",""),
#("wcfg","","",""),
#("awce","","",""),
#("wcgu","","",""),
#("wcgs","","",""),
#("awcc","","",""),
]
def _generate_acp_property_dict():
props = {}
for (name, type, description, validation) in _acp_properties:
# basic validation of tuples
assert len(name) == 4, "bad name in _acp_properties list: {0}".format(name)
assert type in ["str", "dec", "hex", "log", "mac", "cfb", "bin"], "bad type in _acp_properties list for name: {0}".format(name)
assert description, "missing description in _acp_properties list for name: {0}".format(name)
props[name] = dict(type=type, description=description, validation=validation)
return props
class ACPPropertyInitValueError(ACPPropertyError):
pass
class ACPProperty(object):
_acpprop = _generate_acp_property_dict()
_element_header_format = struct.Struct("!4s2I")
element_header_size = _element_header_format.size
def __init__(self, name=None, value=None):
# handle "null" property packed name and value first
if name == "\x00\x00\x00\x00" and value == "\x00\x00\x00\x00":
name = None
value = None
if name and name not in self.get_supported_property_names():
raise ACPPropertyError("invalid property name passed to initializer: {0}".format(name))
if value is not None:
# accept value as packed binary string or Python type
prop_type = self.get_property_info_string(name, "type")
_init_handler_name = "_init_{0}".format(prop_type)
assert hasattr(self, _init_handler_name), "missing init handler for \"{0}\" property type".format(prop_type)
_init_handler = getattr(self, _init_handler_name)
logging.debug("old value: {0!r} type: {1}".format(value, type(value)))
try:
value = _init_handler(value)
except ACPPropertyInitValueError as e:
raise ACPPropertyError("{0!s} provided for \"{1}\" property type: {2!r}".format(e, prop_type, value))
logging.debug("new value: {0!r} type: {1}".format(value, type(value)))
#XXX: this is still really hacky, should probably do something with anonymous functions or introspection
validation_expr = self.get_property_info_string(name, "validation")
if validation_expr and not eval(validation_expr):
raise ACPPropertyError("invalid value passed to initializer for property \"{0}\": {1}".format(name, repr(value)))
self.name = name
self.value = value
def _init_dec(self, value):
if type(value) == int:
return value
elif type(value) == str:
try:
return struct.unpack("!I", value)[0]
except:
raise ACPPropertyInitValueError("invalid packed binary string")
else:
raise ACPPropertyInitValueError("invalid built-in type")
def _init_hex(self, value):
if type(value) == int:
return value
elif type(value) == str:
try:
return struct.unpack("!I", value)[0]
except:
raise ACPPropertyInitValueError("invalid packed binary string")
else:
raise ACPPropertyInitValueError("invalid built-in type")
def _init_mac(self, value):
if type(value) == str:
# first, try as packed binary value
if len(value) == 6:
return value
# second, attempt to unpack colon delimited value
mac_bytes = value.split(":")
if len(mac_bytes) == 6:
try:
return "".join(mac_bytes).decode("hex")
except TypeError:
raise ACPPropertyInitValueError("non-hex digit in value")
# fallthrough
raise ACPPropertyInitValueError("invalid value")
else:
raise ACPPropertyInitValueError("invalid built-in type")
def _init_bin(self, value):
if type(value) == str:
return value
else:
raise ACPPropertyInitValueError("invalid built-in type")
def _init_cfb(self, value):
if type(value) == str:
return value
else:
raise ACPPropertyInitValueError("invalid built-in type")
def _init_log(self, value):
if type(value) == str:
return value
else:
raise ACPPropertyInitValueError("invalid built-in type")
def _init_str(self, value):
if type(value) == str:
return value
else:
raise ACPPropertyInitValueError("invalid built-in type")
def __repr__(self):
#XXX: return tuple or dict?
return repr((self.name, self.value))
#TODO: make this function return something other than the formatted value of the property...I keep getting confused by its current shittiness
def __str__(self):
#XXX: is this the correct thing to do?
if self.name is None or self.value is None:
return ""
prop_type = self.get_property_info_string(self.name, "type")
_format_handler_name = "_format_{0}".format(prop_type)
assert hasattr(self, _format_handler_name), "missing format handler for \"{0}\" property type".format(prop_type)
return getattr(self, _format_handler_name)(self.value)
def _format_dec(self, value):
return str(value)
def _format_hex(self, value):
return hex(value)
def _format_mac(self, value):
mac_bytes = []
for i in range(6):
mac_bytes.append(value[i].encode("hex"))
return "{0}:{1}:{2}:{3}:{4}:{5}".format(*mac_bytes)
def _format_bin(self, value):
return value.encode("hex")
def _format_cfb(self, value):
return pprint.pformat(CFLBinaryPListParser.parse(value))
def _format_log(self, value):
s = ""
for line in value.strip("\x00").split("\x00"):
s += "{0}\n".format(line)
return s
def _format_str(self, value):
return value
@classmethod
def get_supported_property_names(cls):
props = []
for name in cls._acpprop:
props.append(name)
return props
@classmethod
def get_property_info_string(cls, prop_name, key):
#XXX: should we do this differently?
if prop_name is None:
return None
if prop_name not in cls._acpprop:
logging.error("property \"{0}\" not supported".format(prop_name))
return None
prop_info = cls._acpprop[prop_name]
if key not in prop_info:
logging.error("invalid property info key \"{0}\"".format(key))
return None
return prop_info[key]
@classmethod
def parse_raw_element(cls, data):
name, flags, size = cls.parse_raw_element_header(data[:cls.element_header_size])
#TODO: handle flags!???
return cls(name, data[cls.element_header_size:])
@classmethod
def parse_raw_element_header(cls, data):
try:
return cls._element_header_format.unpack(data)
except struct.error:
raise ACPPropertyError("failed to parse property element header")
@classmethod
def compose_raw_element(cls, flags, property):
#TODO: handle flags!???
#XXX: handles "null" name or value first, but this is currently garbage
name = property.name if property.name is not None else "\x00\x00\x00\x00"
value = property.value if property.value is not None else "\x00\x00\x00\x00"
if type(value) == int:
st = struct.Struct(">I")
#XXX: this could throw an exception, we need to range check int/hex values to ensure they pack into 32 bits still
return cls.compose_raw_element_header(name, flags, st.size) + st.pack(value)
elif type(value) == str:
return cls.compose_raw_element_header(name, flags, len(value)) + value
else:
raise ACPPropertyError("unhandled property type for raw element composition")
@classmethod
def compose_raw_element_header(cls, name, flags, size):
try:
return cls._element_header_format.pack(name, flags, size)
except struct.error:
raise ACPPropertyError("failed to compose property header")
================================================
FILE: acp/session.py
================================================
import logging
import socket
from .encryption import ACPEncryption
ACP_SERVER_PORT = 5009
class _ACPSession(object):
def __init__(self, target, password):
#XXX: how should we make this abstract enough to cover client and server?
self.target = target
self.password = password
self.sock = None
#self.encryption_context = None
self.encrypt_method = None
self.decrypt_method = None
#XXX: AppleSRP hax
#self.SRP = None
#self.state = 0
def connect(self, port=ACP_SERVER_PORT):
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
logging.info("connecting to host {0}:{1}".format(self.target, self.port))
self.sock.connect((self.target, self.port))
def close(self):
if self.sock:
self.sock.close()
self.sock = None
def send(self, data):
if self.encrypt_method:
data = self.encrypt_method(data)
if self.sock:
self.sock.sendall(data)
#TODO: else? what if sock is not None but not valid in some other way?
def _recv_size(self, size):
recvd_chunks = []
recvd_size = 0
while True:
if recvd_size == size:
break
#XXX: this is broken for server receiving stream headers
data = self.sock.recv(size - recvd_size)
if not data:
break
recvd_chunks.append(data)
recvd_size += len(data)
return "".join(recvd_chunks)
def _recv_size_timeout(self, size, timeout):
#XXX: blargh
self.sock.setblocking(0)
recvd_chunks = []
recvd_size = 0
begin=time.time()
while True:
if recvd_size == size:
break
if recvd_chunks and time.time()-begin > timeout:
break
if time.time()-begin > timeout*2:
break
try:
#XXX: this is broken for server receiving stream headers
data = self.sock.recv(size - recvd_size)
if data:
recvd_chunks.append(data)
recvd_size += len(data)
begin = time.time()
else:
time.sleep(0.1)
except socket.error:
pass
#XXX: should non-blocking just be the default?
self.sock.setblocking(1)
return "".join(recvd_chunks)
def recv(self, size, timeout=0):
if not self.sock:
#XXX: do nothing? throw an exception?
return ""
data = ""
if timeout:
data = self._recv_size_timeout(size, timeout)
else:
data = self._recv_size(size)
if self.decrypt_method:
data = self.decrypt_method(data)
return data
class ACPClientSession(_ACPSession):
def enable_encryption(self, key, client_iv, server_iv):
self.encryption_context = ACPEncryption(key, client_iv, server_iv)
self.encrypt_method = encryption_context.client_encrypt
self.decrypt_method = encryption_context.server_decrypt
class ACPServerSession(_ACPSession):
def enable_encryption(self, key, client_iv, server_iv):
self.encryption_context = ACPEncryption(key, client_iv, server_iv)
self.encrypt_method = self.encryption_context.server_encrypt
self.decrypt_method = self.encryption_context.client_decrypt
================================================
FILE: setup.py
================================================
from setuptools import setup
setup(
name="acp",
version="1.0",
description="AirPyrt Tools",
author="Vince Cali",
author_email="0x56.0x69.0x6e.0x63.0x65@gmail.com",
packages=["acp"],
entry_points = {
"console_scripts": ["acp=acp.cli:main"],
},
install_requires=[
"pycrypto",
]
)
gitextract_nzg9t11t/ ├── .gitignore ├── LICENSE ├── README.md ├── acp/ │ ├── __init__.py │ ├── __main__.py │ ├── basebinary.py │ ├── cflbinary.py │ ├── cli.py │ ├── clibs/ │ │ ├── AppleSRP.py │ │ └── __init__.py │ ├── client.py │ ├── encryption.py │ ├── exception.py │ ├── keystream.py │ ├── message.py │ ├── misc.py │ ├── property.py │ └── session.py └── setup.py
SYMBOL INDEX (148 symbols across 12 files)
FILE: acp/basebinary.py
function _derive_key (line 35) | def _derive_key(model):
class BasebinaryError (line 46) | class BasebinaryError(Exception):
class Basebinary (line 49) | class Basebinary(object):
method parse (line 58) | def parse(cls, data):
method compose (line 85) | def compose(cls, data):
method parse_header (line 91) | def parse_header(cls, data):
method compose_header (line 101) | def compose_header(cls, byte_0x0F, model, version, byte_0x18, byte_0x1...
method decrypt (line 107) | def decrypt(cls, data, model, byte_0x0F):
method decrypt_chunk (line 128) | def decrypt_chunk(cls, encrypted_data, key, iv):
method encrypt (line 149) | def encrypt(cls, data):
method extract (line 155) | def extract(cls, data):
FILE: acp/cflbinary.py
function _lslice (line 13) | def _lslice(data, length):
class CFLBinaryPListComposeError (line 22) | class CFLBinaryPListComposeError(Exception):
class CFLBinaryPListParseError (line 26) | class CFLBinaryPListParseError(Exception):
class CFLBinaryPListComposer (line 31) | class CFLBinaryPListComposer(object):
method _pack_object (line 35) | def _pack_object(cls, obj):
method compose (line 134) | def compose(cls, object):
class CFLBinaryPListParser (line 150) | class CFLBinaryPListParser(object):
method _unpack_int (line 154) | def _unpack_int(cls, size_exponent, data):
method _unpack_real (line 185) | def _unpack_real(cls, size_exponent, data):
method _unpack_count (line 211) | def _unpack_count(cls, object_info, data):
method _unpack_object_marker (line 234) | def _unpack_object_marker(cls, data):
method _unpack_object (line 252) | def _unpack_object(cls, data):
method parse (line 349) | def parse(cls, data):
FILE: acp/cli.py
class _ArgParser (line 15) | class _ArgParser(argparse.ArgumentParser):
method error (line 16) | def error(self, message):
function _cmd_not_implemented (line 22) | def _cmd_not_implemented(*unused):
function _cmd_listprop (line 25) | def _cmd_listprop(unused):
function _cmd_helpprop (line 32) | def _cmd_helpprop(args):
function _cmd_getprop (line 44) | def _cmd_getprop(client, args):
function _cmd_setprop (line 50) | def _cmd_setprop(client, args):
function _cmd_dumpprop (line 76) | def _cmd_dumpprop(client, unused):
function _cmd_acpprop (line 83) | def _cmd_acpprop(client, unused):
function _cmd_dump_syslog (line 91) | def _cmd_dump_syslog(client, unused):
function _cmd_reboot (line 94) | def _cmd_reboot(client, unused):
function _cmd_factory_reset (line 98) | def _cmd_factory_reset(client, unused):
function _cmd_flash_primary (line 102) | def _cmd_flash_primary(client, args):
function _cmd_do_feat_command (line 112) | def _cmd_do_feat_command(client, unused):
function _cmd_decrypt (line 115) | def _cmd_decrypt(args):
function _cmd_extract (line 129) | def _cmd_extract(args):
function _cmd_srp_test (line 143) | def _cmd_srp_test(client, unused):
function main (line 149) | def main():
FILE: acp/clibs/AppleSRP.py
function _fmt_void_ptr (line 5) | def _fmt_void_ptr(value):
function _fmt_cstr (line 10) | def _fmt_cstr(value):
function _fmt_ccz_class (line 15) | def _fmt_ccz_class(value):
function _fmt_ccz (line 20) | def _fmt_ccz(value):
class cstr (line 26) | class cstr(Structure):
method __str__ (line 33) | def __str__(self):
method get_data_buffer (line 42) | def get_data_buffer(self):
class SHA1_CTX (line 55) | class SHA1_CTX(Structure):
method __str__ (line 66) | def __str__(self):
class client_meth_st (line 89) | class client_meth_st(Structure):
method __str__ (line 94) | def __str__(self):
class server_meth_st (line 112) | class server_meth_st(Structure):
method __str__ (line 120) | def __str__(self):
class ccz_class (line 139) | class ccz_class(Structure):
method __str__ (line 145) | def __str__(self):
class ccz (line 163) | class ccz(Structure):
method __str__ (line 169) | def __str__(self):
class srp_st (line 211) | class srp_st(Structure):
method __str__ (line 235) | def __str__(self):
FILE: acp/client.py
class ACPClient (line 12) | class ACPClient(object):
method __init__ (line 13) | def __init__(self, target, password=""):
method connect (line 20) | def connect(self):
method close (line 24) | def close(self):
method send (line 28) | def send(self, data):
method recv (line 32) | def recv(self, size):
method recv_message_header (line 36) | def recv_message_header(self):
method recv_property_element_header (line 40) | def recv_property_element_header(self):
method get_properties (line 44) | def get_properties(self, prop_names=[]):
method set_properties (line 91) | def set_properties(self, props_dict={}):
method get_features (line 129) | def get_features(self):
method flash_primary (line 139) | def flash_primary(self, payload):
method authenticate_AppleSRP (line 147) | def authenticate_AppleSRP(self):
FILE: acp/encryption.py
class _ACPEncryptionContext (line 10) | class _ACPEncryptionContext(object):
method __init__ (line 11) | def __init__(self, key, iv):
class ACPEncryption (line 19) | class ACPEncryption(object):
method __init__ (line 20) | def __init__(self, key, client_iv, server_iv):
method _init_client_context (line 24) | def _init_client_context(cls, key, iv):
method _init_server_context (line 28) | def _init_server_context(cls, key, iv):
method client_decrypt (line 33) | def client_decrypt(self, data):
method client_encrypt (line 37) | def client_encrypt(self, data):
method server_decrypt (line 41) | def server_decrypt(self, data):
method server_encrypt (line 45) | def server_encrypt(self, data):
FILE: acp/exception.py
class ACPError (line 3) | class ACPError(Exception):
class ACPClientError (line 8) | class ACPClientError(ACPError):
class ACPCommandLineError (line 13) | class ACPCommandLineError(ACPError):
class ACPMessageError (line 18) | class ACPMessageError(ACPError):
class ACPPropertyError (line 23) | class ACPPropertyError(ACPError):
FILE: acp/keystream.py
function generate_acp_keystream (line 4) | def generate_acp_keystream(length):
FILE: acp/message.py
function _generate_acp_header_key (line 9) | def _generate_acp_header_key(password):
class ACPMessage (line 35) | class ACPMessage(object):
method __init__ (line 46) | def __init__(self, version, flags, unused, command, error_code, key, b...
method __str__ (line 67) | def __str__(self):
method parse_raw (line 80) | def parse_raw(cls, data):
method compose_echo_command (line 133) | def compose_echo_command(cls, flags, password, payload):
method compose_flash_primary_command (line 138) | def compose_flash_primary_command(cls, flags, password, payload):
method compose_flash_secondary_command (line 143) | def compose_flash_secondary_command(cls, flags, password, payload):
method compose_flash_bootloader_command (line 148) | def compose_flash_bootloader_command(cls, flags, password, payload):
method compose_getprop_command (line 153) | def compose_getprop_command(cls, flags, password, payload):
method compose_setprop_command (line 158) | def compose_setprop_command(cls, flags, password, payload):
method compose_perform_command (line 163) | def compose_perform_command(cls, flags, password, payload):
method compose_monitor_command (line 168) | def compose_monitor_command(cls, flags, password, payload):
method compose_rpc_command (line 173) | def compose_rpc_command(cls, flags, password, payload):
method compose_auth_command (line 178) | def compose_auth_command(cls, flags, payload):
method compose_feat_command (line 183) | def compose_feat_command(cls, flags):
method compose_message_ex (line 188) | def compose_message_ex(cls, version, flags, unused, command, error_cod...
method _compose_raw_packet (line 192) | def _compose_raw_packet(self):
method _compose_header (line 206) | def _compose_header(self):
FILE: acp/misc.py
function cast_u32 (line 3) | def cast_u32(value):
FILE: acp/property.py
function _generate_acp_property_dict (line 553) | def _generate_acp_property_dict():
class ACPPropertyInitValueError (line 564) | class ACPPropertyInitValueError(ACPPropertyError):
class ACPProperty (line 568) | class ACPProperty(object):
method __init__ (line 575) | def __init__(self, name=None, value=None):
method _init_dec (line 606) | def _init_dec(self, value):
method _init_hex (line 617) | def _init_hex(self, value):
method _init_mac (line 628) | def _init_mac(self, value):
method _init_bin (line 645) | def _init_bin(self, value):
method _init_cfb (line 651) | def _init_cfb(self, value):
method _init_log (line 657) | def _init_log(self, value):
method _init_str (line 663) | def _init_str(self, value):
method __repr__ (line 670) | def __repr__(self):
method __str__ (line 676) | def __str__(self):
method _format_dec (line 686) | def _format_dec(self, value):
method _format_hex (line 689) | def _format_hex(self, value):
method _format_mac (line 692) | def _format_mac(self, value):
method _format_bin (line 698) | def _format_bin(self, value):
method _format_cfb (line 701) | def _format_cfb(self, value):
method _format_log (line 704) | def _format_log(self, value):
method _format_str (line 710) | def _format_str(self, value):
method get_supported_property_names (line 715) | def get_supported_property_names(cls):
method get_property_info_string (line 723) | def get_property_info_string(cls, prop_name, key):
method parse_raw_element (line 738) | def parse_raw_element(cls, data):
method parse_raw_element_header (line 745) | def parse_raw_element_header(cls, data):
method compose_raw_element (line 753) | def compose_raw_element(cls, flags, property):
method compose_raw_element_header (line 769) | def compose_raw_element_header(cls, name, flags, size):
FILE: acp/session.py
class _ACPSession (line 9) | class _ACPSession(object):
method __init__ (line 10) | def __init__(self, target, password):
method connect (line 26) | def connect(self, port=ACP_SERVER_PORT):
method close (line 33) | def close(self):
method send (line 39) | def send(self, data):
method _recv_size (line 48) | def _recv_size(self, size):
method _recv_size_timeout (line 63) | def _recv_size_timeout(self, size, timeout):
method recv (line 95) | def recv(self, size, timeout=0):
class ACPClientSession (line 112) | class ACPClientSession(_ACPSession):
method enable_encryption (line 113) | def enable_encryption(self, key, client_iv, server_iv):
class ACPServerSession (line 120) | class ACPServerSession(_ACPSession):
method enable_encryption (line 121) | def enable_encryption(self, key, client_iv, server_iv):
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (92K chars).
[
{
"path": ".gitignore",
"chars": 41,
"preview": ".DS_Store\n\n*.pyc\n*.egg-info\nbuild/\ndist/\n"
},
{
"path": "LICENSE",
"chars": 1053,
"preview": "Copyright (c) 2016 Vince Cali\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this soft"
},
{
"path": "README.md",
"chars": 3854,
"preview": "# AirPyrt Tools\n\n### License\n\nSee LICENSE\n\n\n### Requirements\n\n- python 2.7\n- pycrypto\n\n\n### Installation\n\n`python setup."
},
{
"path": "acp/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "acp/__main__.py",
"chars": 22,
"preview": "import cli\ncli.main()\n"
},
{
"path": "acp/basebinary.py",
"chars": 4396,
"preview": "import logging\nimport os.path\nimport struct\nimport zlib\n\nfrom Crypto.Cipher import AES\n\n#XXX: ugh...\nfrom .misc import c"
},
{
"path": "acp/cflbinary.py",
"chars": 9185,
"preview": "import logging\nimport struct\nfrom collections import OrderedDict\nfrom math import log\nfrom types import *\n\n\n_header_mag"
},
{
"path": "acp/cli.py",
"chars": 8406,
"preview": "import argparse\nimport logging\nimport os.path\nimport sys\nimport time\n\nfrom collections import OrderedDict\n\nfrom .basebin"
},
{
"path": "acp/clibs/AppleSRP.py",
"chars": 9675,
"preview": "from ctypes import *\n\n\n#XXX: hax to display NULL pointer thingies\ndef _fmt_void_ptr(value):\n\tif value is None:\n\t\treturn "
},
{
"path": "acp/clibs/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "acp/client.py",
"chars": 8355,
"preview": "import logging\nimport os\nimport struct\nimport time\n\nfrom .cflbinary import CFLBinaryPListComposer, CFLBinaryPListParser\n"
},
{
"path": "acp/encryption.py",
"chars": 1310,
"preview": "from Crypto.Cipher import AES\nfrom Crypto.Protocol import KDF\nfrom Crypto.Util import Counter\n\n\nPBKDF_salt0 = \"F072FA3F6"
},
{
"path": "acp/exception.py",
"chars": 522,
"preview": "#TODO: put other exceptions in here...\n\nclass ACPError(Exception):\n\t\"\"\"Base class for exceptions in this module.\"\"\"\n\tpas"
},
{
"path": "acp/keystream.py",
"chars": 540,
"preview": "\"\"\"Static key/seed for keystream generation\"\"\"\nACP_STATIC_KEY = \"5b6faf5d9d5b0e1351f2da1de7e8d673\".decode(\"hex\")\n\ndef ge"
},
{
"path": "acp/message.py",
"chars": 8201,
"preview": "import logging\nimport struct\nimport zlib\n\nfrom .exception import ACPMessageError\nfrom .keystream import *\n\n\ndef _generat"
},
{
"path": "acp/misc.py",
"chars": 247,
"preview": "#XXX: this file exists until I think of a better way to do this\n\ndef cast_u32(value):\n\t#XXX: lazy, how do we do this cor"
},
{
"path": "acp/property.py",
"chars": 20415,
"preview": "import logging\nimport pprint\nimport struct\n\nfrom .cflbinary import CFLBinaryPListParser\nfrom .exception import ACPProper"
},
{
"path": "acp/session.py",
"chars": 2932,
"preview": "import logging\nimport socket\n\nfrom .encryption import ACPEncryption\n\n\nACP_SERVER_PORT = 5009\n\nclass _ACPSession(object):"
},
{
"path": "setup.py",
"chars": 296,
"preview": "from setuptools import setup\n\nsetup(\n\tname=\"acp\",\n\tversion=\"1.0\",\n\tdescription=\"AirPyrt Tools\",\n\tauthor=\"Vince Cali\",\n\ta"
}
]
About this extraction
This page contains the full source code of the x56/airpyrt-tools GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (77.6 KB), approximately 23.5k tokens, and a symbol index with 148 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.