Squashed commit of lots of dev stuff that may have sensitive info

This commit is contained in:
JJTech0130 2023-04-21 21:47:13 -04:00
parent 22899ee98e
commit d9feada49c
No known key found for this signature in database
GPG key ID: 23C92EBCCF8F93D6
14 changed files with 659 additions and 114 deletions

2
.gitignore vendored
View file

@ -2,6 +2,8 @@
ids.key
ids.crt
keychain-extract
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

25
apns.py
View file

@ -1,12 +1,33 @@
from __future__ import annotations
import random
import socket
import threading
import time
from hashlib import sha1
import tlslite
import albert
import courier
COURIER_HOST = "windows.courier.push.apple.com" # TODO: Get this from config
COURIER_PORT = 5223
ALPN = [b"apns-security-v2"]
# Connect to the courier server
def _connect(private_key: str, cert: str) -> tlslite.TLSConnection:
# Connect to the courier server
sock = socket.create_connection((COURIER_HOST, COURIER_PORT))
# Wrap the socket in TLS
sock = tlslite.TLSConnection(sock)
# Parse the certificate and private key
cert = tlslite.X509CertChain([tlslite.X509().parse(cert)])
private_key = tlslite.parsePEMKey(private_key, private=True)
# Handshake with the server
sock.handshakeClientCert(cert, private_key, alpn=ALPN)
return sock
class APNSConnection:
@ -47,7 +68,7 @@ class APNSConnection:
else:
self.private_key, self.cert = private_key, cert
self.sock = courier.connect(self.private_key, self.cert)
self.sock = _connect(self.private_key, self.cert)
# Start the queue filler thread
self.queue_filler_thread = threading.Thread(

View file

@ -1,29 +0,0 @@
import socket
import tlslite
COURIER_HOST = "windows.courier.push.apple.com" # TODO: Get this from config
COURIER_PORT = 5223
ALPN = [b"apns-security-v2"]
# Connect to the courier server
def connect(private_key: str, cert: str) -> tlslite.TLSConnection:
# Connect to the courier server
sock = socket.create_connection((COURIER_HOST, COURIER_PORT))
# Wrap the socket in TLS
sock = tlslite.TLSConnection(sock)
# Parse the certificate and private key
cert = tlslite.X509CertChain([tlslite.X509().parse(cert)])
private_key = tlslite.parsePEMKey(private_key, private=True)
# Handshake with the server
sock.handshakeClientCert(cert, private_key, alpn=ALPN)
return sock
if __name__ == "__main__":
sock = connect()
sock.write(b"Hello World!")
print(sock.read())
sock.close()

View file

@ -12,4 +12,6 @@ conn1.connect()
conn1.filter(["com.apple.madrid"])
print(ids.lookup(conn1, ["mailto:jjtech@jjtech.dev"]))
#print(ids.lookup(conn1, ["mailto:jjtech@jjtech.dev"]))
print(ids.register(conn1, "user_test2@icloud.com", "wowSecure1"))

82
development/test.py Normal file
View file

@ -0,0 +1,82 @@
import ids
import hashlib
from base64 import b64decode
import zlib
import plistlib
with open('body.txt', 'r') as f:
BODY = f.read()
CERT = "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"
SIG = "AQEZs/u9Ptb8AmFpCv5XgzUsskvcleZDBxYTe5JOoshFCxpnByTwFA0mxplklHqT2rTEeF+Bu0Bo0vEPlh9KslmIoQLo6ej25bbtFN07dnHNwd84xzQzWBa4VHLQE1gNjSpcorppxpAUon/eFRu5yRxmwQVmqo+XmmxSuFCzxUaAZAPFPDna+8tvRwd0q3kuK9b0w/kuT16X1SL166fFNzmsQGcBqob9C9xX0VlYGqSd4K975gWdYsPo/kiY0ni4Q130oc6oAANr8ATN0bEeAO6/AfVM2aqHJTGlYlekBFWf8Tp8AJLUc4cm676346IEBST+l4rYGxYYStV2PEmp9cZ+"
TOKEN = "5V7AY+ikHr4DiSfq1W2UBa71G3FLGkpUSKTrOLg81yk="
NONCE = "AQAAAYeBb0XwKDMBW5PfAPM="
def extract_hash(sig: bytes, cert: str) -> str:
#sig = b64decode(SIG)[2:]
sig = int.from_bytes(sig)
# Get the correct hash
from cryptography.hazmat.backends import default_backend
from cryptography.x509 import load_pem_x509_certificate
#key, cert = load_keys()
cert = "-----BEGIN CERTIFICATE-----\n" + cert + "\n-----END CERTIFICATE-----"
cert = load_pem_x509_certificate(cert.encode(), default_backend())
modulus = cert.public_key().public_numbers().n
power = cert.public_key().public_numbers().e
#print(hex(pow(sig, power, modulus)))
return pow(sig, power, modulus)
# from cryptography import x509
# cert = "-----BEGIN CERTIFICATE-----\n" + cert + "\n-----END CERTIFICATE-----"
# cert = x509.load_pem_x509_certificate(cert.encode())
# pub = cert.public_key()
# modulus = pub.public_numbers().n
# # Get the power
# power = pub.public_numbers().e
# # Get the hash from the sig
# sig = b64decode(sig)
# sig = sig[2:]
# sig = int.from_bytes(sig)
# hash = pow(sig, power, modulus)
# print(hex(hash))
# return hash
body = plistlib.dumps(plistlib.loads(BODY.encode()))
body = zlib.compress(BODY.encode(), wbits=16+zlib.MAX_WBITS)
p = ids._create_payload('id-register', '', TOKEN, body, b64decode(NONCE))[0]
s = hashlib.sha1(p).digest()
print(s.hex())
#extract_hash(SIG, CERT)
# Loop through all POSSIBLE ranges
# sig = b64decode(SIG)
# print(sig[:2])
# for i in range(len(sig)):
# for j in range(i, len(sig)):
# h = extract_hash(sig[i:j], CERT)
# t = hex(h)
# if 'ffffff' in t:
# print(i, j, t)
# sig = b64decode(SIG)[2:]
# for i in range(len(sig)):
# h = extract_hash(sig[:i], CERT)
# t = hex(h)
# if 'ffff' in t:
# print(i, t)
# #print(hex(extract_hash(SIG, CERT)))
#CERT2 = "MIICnjCCAgegAwIBAgIKBAr40/DyW42YxjANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEVMBMGA1UECxMMQXBwbGUgaVBob25lMR8wHQYDVQQDExZBcHBsZSBpUGhvbmUgRGV2aWNlIENBMB4XDTIzMDQwNzE0MTUwNVoXDTI0MDQwNzE0MjAwNVowLzEtMCsGA1UEAxYkOUNCOTkzMTYtNkJERi00REYzLUFCRjUtNzcxNDU5MjFFQkY1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwsLiv8cifPdQZJQZtWvoD0WoTekSGwRj7KhxOi+AC1EUTdByWna8l7DDnixqww01FyA9pCBwottv0Xk9lOsrJrK05RXS+A7IieycejnUMdmRkgS7AsHIXOUSjtlkg2sfz5eYV9cqemTJnhdOvKtbqb9lYVN/8EehXD5JuogN+vwIDAQABo4GVMIGSMB8GA1UdIwQYMBaAFLL+ISNEhpVqedWBJo5zENinTI50MB0GA1UdDgQWBBQCl798/NQ3s5KywbJjoCjfjvWvmDAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIFoDAgBgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEAYKKoZIhvdjZAYKBAQCBQAwDQYJKoZIhvcNAQEFBQADgYEAfBwkujrswCn+wtu0eKCa39Cv58YC3AhK24Aj2iwXbddHaj9B9ye6HDy1BHPG21LKNGqm4X/XEtJQ3ZY/hGr4eenmtYjOI4a/oi127mrSt7uZmoib9x5S6w68eCCKkO+DD2JqDbMr2ATUhVNUxMegrzYdju8LofYqXBKzkoZ0/nk="
#SIG2 = "AQGOTyyRBWMxoGWqEUl5bZJXssL6bkK4acxIDOCJTUy0MMavNEwtFThZkqVpQFqjB7eXNBM6PxwPtmwHmf/5IWgIkBUthIwhGJV3pLUkhDHTVX5YjbUSF7Z4y+Y39BQ2hhYjfcz1bw2KH40MByt+bnk28Xv2XaKWYuBinH9PVajp3g=="
SIG3 = "AQEZs/u9Ptb8AmFpCv5XgzUsskvcleZDBxYTe5JOoshFCxpnByTwFA0mxplklHqT2rTEeF+Bu0Bo0vEPlh9KslmIoQLo6ej25bbtFN07dnHNwd84xzQzWBa4VHLQE1gNjSpcorppxpAUon/eFRu5yRxmwQVmqo+XmmxSuFCzxUaAZAPFPDna+8tvRwd0q3kuK9b0w/kuT16X1SL166fFNzmsQGcBqob9C9xX0VlYGqSd4K975gWdYsPo/kiY0ni4Q130oc6oAANr8ATN0bEeAO6/AfVM2aqHJTGlYlekBFWf8Tp8AJLUc4cm676346IEBST+l4rYGxYYStV2PEmp9cZ+"
CERT3 = "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"
print(hex(extract_hash(b64decode(SIG)[2:], CERT)))

View file

@ -1,79 +0,0 @@
var SecTrustEvaluate_handle =
Module.findExportByName('Security', 'SecTrustEvaluate');
var SecTrustEvaluateWithError_handle =
Module.findExportByName('Security', 'SecTrustEvaluateWithError');
var SSL_CTX_set_custom_verify_handle =
Module.findExportByName('libboringssl.dylib', 'SSL_CTX_set_custom_verify');
var SSL_get_psk_identity_handle =
Module.findExportByName('libboringssl.dylib', 'SSL_get_psk_identity');
var boringssl_context_set_verify_mode_handle = Module.findExportByName(
'libboringssl.dylib', 'boringssl_context_set_verify_mode');
if (SecTrustEvaluateWithError_handle) {
var SecTrustEvaluateWithError = new NativeFunction(
SecTrustEvaluateWithError_handle, 'int', ['pointer', 'pointer']);
Interceptor.replace(
SecTrustEvaluateWithError_handle,
new NativeCallback(function(trust, error) {
console.log('[*] Called SecTrustEvaluateWithError()');
SecTrustEvaluateWithError(trust, NULL);
Memory.writeU8(error, 0);
return 1;
}, 'int', ['pointer', 'pointer']));
console.log('[+] SecTrustEvaluateWithError() hook installed.');
}
if (SecTrustEvaluate_handle) {
var SecTrustEvaluate = new NativeFunction(
SecTrustEvaluate_handle, 'int', ['pointer', 'pointer']);
Interceptor.replace(
SecTrustEvaluate_handle, new NativeCallback(function(trust, result) {
console.log('[*] Called SecTrustEvaluate()');
SecTrustEvaluate(trust, result);
Memory.writeU8(result, 1);
return 0;
}, 'int', ['pointer', 'pointer']));
console.log('[+] SecTrustEvaluate() hook installed.');
}
if (SSL_CTX_set_custom_verify_handle) {
var SSL_CTX_set_custom_verify = new NativeFunction(
SSL_CTX_set_custom_verify_handle, 'void', ['pointer', 'int', 'pointer']);
var replaced_callback = new NativeCallback(function(ssl, out) {
console.log('[*] Called custom SSL verifier')
return 0;
}, 'int', ['pointer', 'pointer']);
Interceptor.replace(
SSL_CTX_set_custom_verify_handle,
new NativeCallback(function(ctx, mode, callback) {
console.log('[*] Called SSL_CTX_set_custom_verify()');
SSL_CTX_set_custom_verify(ctx, 0, replaced_callback);
}, 'int', ['pointer', 'int', 'pointer']));
console.log('[+] SSL_CTX_set_custom_verify() hook installed.')
}
if (SSL_get_psk_identity_handle) {
Interceptor.replace(
SSL_get_psk_identity_handle, new NativeCallback(function(ssl) {
console.log('[*] Called SSL_get_psk_identity_handle()');
return 'notarealPSKidentity';
}, 'pointer', ['pointer']));
console.log('[+] SSL_get_psk_identity() hook installed.')
}
if (boringssl_context_set_verify_mode_handle) {
var boringssl_context_set_verify_mode = new NativeFunction(
boringssl_context_set_verify_mode_handle, 'int', ['pointer', 'pointer']);
Interceptor.replace(
boringssl_context_set_verify_mode_handle,
new NativeCallback(function(a, b) {
console.log('[*] Called boringssl_context_set_verify_mode()');
return 0;
}, 'int', ['pointer', 'pointer']));
console.log('[+] boringssl_context_set_verify_mode() hook installed.')
}

511
gsa.py Normal file
View file

@ -0,0 +1,511 @@
from base64 import b64encode, b64decode
from datetime import datetime
from random import randbytes
import uuid
import locale
import plistlib as plist
import json
import hashlib
import hmac
import requests
import srp._pysrp as srp
import pbkdf2
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
import getpass
# Constants
DEBUG = False # Allows using a proxy for debugging (disables SSL verification)
# Server to use for anisette generation
#ANISETTE = "https://sign.rheaa.xyz/"
ANISETTE = 'http://45.132.246.138:6969/'
# ANISETTE = 'https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx'
# ANISETTE = "http://jkcoxson.com:2052/"
# Configure SRP library for compatibility with Apple's implementation
srp.rfc5054_enable()
srp.no_username_in_x()
# Disable SSL Warning
import urllib3
urllib3.disable_warnings()
def generate_anisette() -> dict:
r = requests.get(ANISETTE, verify=False if DEBUG else True, timeout=5)
r = json.loads(r.text)
return r
class Anisette:
@staticmethod
def _fetch(url: str) -> dict:
"""Fetches anisette data that we cannot calculate from a remote server"""
r = requests.get(url, verify=False if DEBUG else True, timeout=5)
r = json.loads(r.text)
return r
def __init__(self, url: str = ANISETTE, name: str = "") -> None:
self._name = name
self._url = url
self._anisette = self._fetch(self._url)
# Generate a "user id": just a random UUID
# TODO: Figure out how to tie it to the user's account on the device
self._user_id = str(uuid.uuid4()).upper()
self._device_id = str(uuid.uuid4()).upper()
# override string printing
def __str__(self) -> str:
return f"{self._name} ({self.backend})"
@property
def url(self) -> str:
return self._url
@property
def backend(self) -> str:
if (
self._anisette["X-MMe-Client-Info"]
== "<MacBookPro15,1> <Mac OS X;10.15.2;19C57> <com.apple.AuthKit/1 (com.apple.dt.Xcode/3594.4.19)>"
):
return "AltServer"
elif (
self._anisette["X-MMe-Client-Info"]
== "<iMac11,3> <Mac OS X;10.15.6;19G2021> <com.apple.AuthKit/1 (com.apple.dt.Xcode/3594.4.19)>"
):
return "Provision"
else:
return f"Unknown ({self._anisette['X-MMe-Client-Info']})"
# Getters
@property
def timestamp(self) -> str:
"""'Timestamp'
Current timestamp in ISO 8601 format
"""
# We only want sencond precision, so we set the microseconds to 0
# We also add 'Z' to the end to indicate UTC
# An alternate way to write this is strftime("%FT%T%zZ")
return datetime.utcnow().replace(microsecond=0).isoformat() + "Z"
@property
def timezone(self) -> str:
"""'Time Zone'
Abbreviation of the timezone of the device (e.g. EST)"""
return str(datetime.utcnow().astimezone().tzinfo)
@property
def locale(self) -> str:
"""'Locale'
Locale of the device (e.g. en_US)
"""
return locale.getdefaultlocale()[0] or "en_US"
@property
def otp(self) -> str:
"""'One Time Password'
A seemingly random base64 string containing 28 bytes
TODO: Figure out how to generate this
"""
return self._anisette["X-Apple-I-MD"]
@property
def local_user(self) -> str:
"""'Local User ID'
There are 2 possible implementations of this value
1. Uppercase hex of the SHA256 hash of some unknown value (used by Windows based servers)
2. Base64 encoding of an uppercase UUID (used by android based servers)
I picked the second one because it's more fully understood.
"""
return b64encode(self._user_id.encode()).decode()
@property
def machine(self) -> str:
"""'Machine ID'
This is a base64 encoded string of 60 'random' bytes
We're not sure how this is generated, we have to rely on the server
TODO: Figure out how to generate this
"""
return self._anisette["X-Apple-I-MD-M"]
@property
def router(self) -> str:
"""'Routing Info'
This is a number, either 17106176 or 50660608
It doesn't seem to matter which one we use,
17106176 is used by Sideloadly and Provision (android) based servers
50660608 is used by Windows iCloud based servers
"""
return "17106176"
@property
def serial(self) -> str:
"""'Device Serial Number'
This is the serial number of the device
You can use a legitimate serial number, but Apple accepts '0' as well (for andriod devices)
See https://github.com/acidanthera/OpenCorePkg/blob/master/Utilities/macserial/macserial.c for how to generate a legit serial
"""
return "0"
@property
def device(self) -> str:
"""'Device Unique Identifier'
This is just an uppercase UUID"""
return self._device_id
def _build_client(self, emulated_device: str, emulated_app: str) -> str:
# TODO: Update OS version and app versions
model = emulated_device
if emulated_device == "PC":
# We're emulating a PC, so we run Windows (Vista?)
os = "Windows"
os_version = "6.2(0,0);9200"
else:
# We're emulating a Mac, so we run macOS (What is 15.6?)
os = "Mac OS X"
os_version = "10.15.6;19G2021"
if emulated_app == "Xcode":
app_bundle = "com.apple.dt.Xcode"
app_version = "3594.4.19"
else:
app_bundle = "com.apple.iCloud"
app_version = "7.21"
if os == "Windows":
authkit_bundle = "com.apple.AuthKitWin"
else:
authkit_bundle = "com.apple.AuthKit"
authkit_version = "1"
return f"<{model}> <{os};{os_version}> <{authkit_bundle}/{authkit_version} ({app_bundle}/{app_version})>"
@property
def client(self) -> str:
"""'Client Information'
String in the following format:
<%MODEL%> <%OS%;%MAJOR%.%MINOR%(%SPMAJOR%,%SPMINOR%);%BUILD%> <%AUTHKIT_BUNDLE_ID%/%AUTHKIT_VERSION% (%APP_BUNDLE_ID%/%APP_VERSION%)>
Where:
MODEL: The model of the device (e.g. MacBookPro15,1 or 'PC'
OS: The OS of the device (e.g. Mac OS X or Windows)
MAJOR: The major version of the OS (e.g. 10)
MINOR: The minor version of the OS (e.g. 15)
SPMAJOR: The major version of the service pack (e.g. 0) (Windows only)
SPMINOR: The minor version of the service pack (e.g. 0) (Windows only)
BUILD: The build number of the OS (e.g. 19C57)
AUTHKIT_BUNDLE_ID: The bundle ID of the AuthKit framework (e.g. com.apple.AuthKit)
AUTHKIT_VERSION: The version of the AuthKit framework (e.g. 1)
APP_BUNDLE_ID: The bundle ID of the app (e.g. com.apple.dt.Xcode)
APP_VERSION: The version of the app (e.g. 3594.4.19)
"""
return self._build_client("iMac11,3", "Xcode")
def generate_headers(self, client_info: bool = False) -> dict:
h = {
# Current Time
"X-Apple-I-Client-Time": self.timestamp,
"X-Apple-I-TimeZone": self.timezone,
# Locale
# Some implementations only use this for locale
"loc": self.locale,
"X-Apple-Locale": self.locale,
# Anisette
"X-Apple-I-MD": self.otp, # 'One Time Password'
# 'Local User ID'
"X-Apple-I-MD-LU": self.local_user,
"X-Apple-I-MD-M": self.machine, # 'Machine ID'
# 'Routing Info', some implementations convert this to an integer
"X-Apple-I-MD-RINFO": self.router,
# Device information
# 'Device Unique Identifier'
"X-Mme-Device-Id": self.device,
# 'Device Serial Number'
"X-Apple-I-SRL-NO": self.serial,
}
# Additional client information only used in some requests
if client_info:
h["X-Mme-Client-Info"] = self.client
h["X-Apple-App-Info"] = "com.apple.gs.xcode.auth"
h["X-Xcode-Version"] = "11.2 (11B41)"
return h
def generate_cpd(self) -> dict:
cpd = {
# Many of these values are not strictly necessary, but may be tracked by Apple
# I've chosen to match the AltServer implementation
# Not sure what these are for, needs some investigation
"bootstrap": True, # All implementations set this to true
"icscrec": True, # Only AltServer sets this to true
"pbe": False, # All implementations explicitly set this to false
"prkgen": True, # I've also seen ckgen
"svct": "iCloud", # In certian circumstances, this can be 'iTunes' or 'iCloud'
# Not included, but I've also seen:
# 'capp': 'AppStore',
# 'dc': '#d4c5b3',
# 'dec': '#e1e4e3',
# 'prtn': 'ME349',
}
cpd.update(self.generate_headers())
return cpd
def authenticated_request(parameters, anisette: Anisette) -> dict:
body = {
"Header": {
"Version": "1.0.1",
},
"Request": {
"cpd": anisette.generate_cpd(),
},
}
body["Request"].update(parameters)
# print(plist.dumps(body).decode('utf-8'))
headers = {
"Content-Type": "text/x-xml-plist",
"Accept": "*/*",
"User-Agent": "akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0",
"X-MMe-Client-Info": anisette.client,
}
resp = requests.post(
#"https://17.32.194.2/grandslam/GsService2",
"https://gsa.apple.com/grandslam/GsService2",
headers=headers,
data=plist.dumps(body),
verify=False, # TODO: Verify Apple's self-signed cert
timeout=5,
)
return plist.loads(resp.content)["Response"]
def check_error(r):
# Check for an error code
if "Status" in r:
status = r["Status"]
else:
status = r
if status["ec"] != 0:
print(f"Error {status['ec']}: {status['em']}")
return True
return False
def encrypt_password(password: str, salt: bytes, iterations: int) -> bytes:
p = hashlib.sha256(password.encode("utf-8")).digest()
return pbkdf2.PBKDF2(p, salt, iterations, hashlib.sha256).read(32)
def create_session_key(usr: srp.User, name: str) -> bytes:
k = usr.get_session_key()
if k is None:
raise Exception("No session key")
return hmac.new(k, name.encode(), hashlib.sha256).digest()
def decrypt_cbc(usr: srp.User, data: bytes) -> bytes:
extra_data_key = create_session_key(usr, "extra data key:")
extra_data_iv = create_session_key(usr, "extra data iv:")
# Get only the first 16 bytes of the iv
extra_data_iv = extra_data_iv[:16]
# Decrypt with AES CBC
cipher = Cipher(algorithms.AES(extra_data_key), modes.CBC(extra_data_iv))
decryptor = cipher.decryptor()
data = decryptor.update(data) + decryptor.finalize()
# Remove PKCS#7 padding
padder = padding.PKCS7(128).unpadder()
return padder.update(data) + padder.finalize()
def trusted_second_factor(dsid, idms_token, anisette: Anisette):
identity_token = b64encode((dsid + ":" + idms_token).encode()).decode()
headers = {
"Content-Type": "text/x-xml-plist",
"User-Agent": "Xcode",
"Accept": "text/x-xml-plist",
"Accept-Language": "en-us",
"X-Apple-Identity-Token": identity_token,
}
headers.update(anisette.generate_headers(client_info=True))
# This will trigger the 2FA prompt on trusted devices
# We don't care about the response, it's just some HTML with a form for entering the code
# Easier to just use a text prompt
requests.get(
"https://gsa.apple.com/auth/verify/trusteddevice",
headers=headers,
verify=False,
timeout=10,
)
# Prompt for the 2FA code. It's just a string like '123456', no dashes or spaces
code = getpass.getpass("Enter 2FA code: ")
# code = input("Enter 2FA code: ")
headers["security-code"] = code
# Send the 2FA code to Apple
resp = requests.get(
"https://gsa.apple.com/grandslam/GsService2/validate",
headers=headers,
verify=False,
timeout=10,
)
r = plist.loads(resp.content)
if check_error(r):
return
print("2FA successful")
def sms_second_factor(dsid, idms_token, anisette: Anisette):
# TODO: Figure out how to make SMS 2FA work correctly
raise NotImplementedError("SMS 2FA is not yet implemented")
identity_token = b64encode((dsid + ":" + idms_token).encode()).decode()
headers = {
"Content-Type": "text/x-xml-plist",
"User-Agent": "Xcode",
# "Accept": "text/x-xml-plist",
"Accept": "application/x-buddyml",
"Accept-Language": "en-us",
"X-Apple-Identity-Token": identity_token,
}
headers.update(anisette.generate_headers(client_info=True))
body = {"serverInfo": {"phoneNumber.id": "1"}}
# This will send the 2FA code to the user's phone over SMS
# We don't care about the response, it's just some HTML with a form for entering the code
# Easier to just use a text prompt
requests.post(
"https://gsa.apple.com/auth/verify/phone/put?mode=sms",
data=plist.dumps(body),
headers=headers,
verify=False,
timeout=5,
)
# Prompt for the 2FA code. It's just a string like '123456', no dashes or spaces
code = input("Enter 2FA code: ")
body = {
"securityCode.code": code,
"serverInfo": {"mode": "sms", "phoneNumber.id": "1"},
}
# headers["security-code"] = code
# Send the 2FA code to Apple
resp = requests.post(
"https://gsa.apple.com/auth/verify/phone/securitycode?referrer=/auth/verify/phone/put",
headers=headers,
data=plist.dumps(body),
verify=False,
timeout=5,
)
print(resp.content.decode())
# r = plist.loads(resp.content)
# if check_error(r):
# return
# print("2FA successful")
def authenticate(username, password, anisette: Anisette):
# Password is None as we'll provide it later
usr = srp.User(username, bytes(), hash_alg=srp.SHA256, ng_type=srp.NG_2048)
_, A = usr.start_authentication()
r = authenticated_request(
{
"A2k": A,
"ps": ["s2k", "s2k_fo"],
#"ps": ["s2k"],
"u": username,
"o": "init",
},
anisette,
)
# Check for an error code
if check_error(r):
return
if r["sp"] != "s2k":
print(f"This implementation only supports s2k. Server returned {r['sp']}")
return
# Change the password out from under the SRP library, as we couldn't calculate it without the salt.
usr.p = encrypt_password(password, r["s"], r["i"]) # type: ignore
M = usr.process_challenge(r["s"], r["B"])
# Make sure we processed the challenge correctly
if M is None:
print("Failed to process challenge")
return
r = authenticated_request(
{
"c": r["c"],
"M1": M,
"u": username,
"o": "complete",
},
anisette,
)
if check_error(r):
return
# Make sure that the server's session key matches our session key (and thus that they are not an imposter)
usr.verify_session(r["M2"])
if not usr.authenticated():
print("Failed to verify session")
return
spd = decrypt_cbc(usr, r["spd"])
# For some reason plistlib doesn't accept it without the header...
PLISTHEADER = b"""\
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'>
"""
spd = plist.loads(PLISTHEADER + spd)
if "au" in r["Status"] and r["Status"]["au"] == "trustedDeviceSecondaryAuth":
print("Trusted device authentication required")
# Replace bytes with strings
for k, v in spd.items():
if isinstance(v, bytes):
spd[k] = b64encode(v).decode()
trusted_second_factor(spd["adsid"], spd["GsIdmsToken"], anisette)
return authenticate(username, password, anisette)
elif "au" in r["Status"] and r["Status"]["au"] == "secondaryAuth":
print("SMS authentication required")
sms_second_factor(spd["adsid"], spd["GsIdmsToken"], anisette)
elif "au" in r["Status"]:
print(f"Unknown auth value {r['Status']['au']}")
return
else:
print("Assuming 2FA is not required")
return spd

41
ids.py
View file

@ -42,10 +42,15 @@ def load_keys() -> tuple[str, str]:
def _create_payload(
bag_key: str, query_string: str, push_token: str, payload: bytes
bag_key: str,
query_string: str,
push_token: str,
payload: bytes,
nonce: bytes = None,
) -> tuple[str, bytes]:
# Generate the nonce
nonce = generate_nonce()
if nonce is None:
nonce = generate_nonce()
push_token = b64decode(push_token)
return (
@ -79,7 +84,7 @@ def sign_payload(
return sig, nonce
global_key, global_cert = load_keys()
#global_key, global_cert = load_keys()
def _send_request(conn: apns.APNSConnection, bag_key: str, body: bytes) -> bytes:
@ -129,3 +134,33 @@ def lookup(conn: apns.APNSConnection, query: list[str]) -> any:
resp = zlib.decompress(resp["b"], 16 + zlib.MAX_WBITS)
resp = plistlib.loads(resp)
return resp
def get_auth_token(username: str, password: str) -> str:
# Get a PET from GSA
import gsa
g = gsa.authenticate(username, password, gsa.Anisette())
#print(g['t']['com.apple.gs.idms.pet'])
pet = g['t']['com.apple.gs.idms.pet']['token']
# Turn the PET into an auth token
import requests
import uuid
data = {
'apple-id': username,
'client-id': str(uuid.uuid4()),
'delegates': {
'com.apple.private.ids': {
'protocol-version': '4'
}
},
'password': pet,
}
data = plistlib.dumps(data)
headers = {'Content-Type': 'text/plist'}.update(gsa.Anisette().generate_headers())
r = requests.post("https://setup.icloud.com/setup/prefpane/loginDelegates", headers=headers, auth=(username, pet), data=data, verify=False)
r = plistlib.loads(r.content)
service_data = r['delegates']['com.apple.private.ids']['service-data']
realm_user_id = service_data['realm-user-id']
auth_token = service_data['auth-token']
print(f"Auth token for {realm_user_id}: {auth_token}")