Skip to content

Commit

Permalink
Merge pull request #11 from dhinakg/hpke
Browse files Browse the repository at this point in the history
  • Loading branch information
dhinakg authored Sep 3, 2024
2 parents bc62ae3 + 9d2e33c commit 46945f2
Show file tree
Hide file tree
Showing 15 changed files with 693 additions and 123 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
aastuff
aastuff_standalone
*.dSYM/
obj/

.DS_Store
tests/
Expand Down
11 changes: 11 additions & 0 deletions .swift-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 1,
"lineLength": 140,
"indentation": {
"spaces": 4
},
"maximumBlankLines": 2,
"respectsExistingLineBreaks": true,
"lineBreakBeforeControlFlowKeywords": true,
"lineBreakBeforeEachArgument": true
}
36 changes: 28 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
.PHONY: all clean test deploy

SRC_FILES = src/aastuff.m src/args.m src/extract.m src/extract_standalone.m
HDR_FILES = include/AppleArchivePrivate.h include/extract.h include/extract_standalone.h
SRC_FILES = src/aastuff.m src/aea.m src/args.m src/extract.m src/extract_standalone.m src/utils.m
SRC_FILES_SWIFT =
OBJ_FILES_SWIFT = $(patsubst src/%.swift,obj/%.o,$(SRC_FILES_SWIFT))
HDR_FILES = include/AppleArchivePrivate.h include/OSISAEAExtractor.h \
include/aea.h include/args.h include/extract.h include/extract_standalone.h include/utils.h $(HDR_SWIFT)

CFLAGS = -fmodules -fobjc-arc -Iinclude -Wall -Werror -Wunreachable-code
CFLAGS = -fmodules -fobjc-arc -Iinclude -Iobj -Wall -Werror -Wunreachable-code
LDLIBS = -framework Foundation -lAppleArchive
LDFLAGS = -Llib

Expand All @@ -12,15 +15,32 @@ ifeq ($(DEBUG), 1)
CFLAGS += -g -DDEBUG=1 -O0
endif

HPKE ?= 0
ifeq ($(HPKE), 1)
CFLAGS += -DHAS_HPKE=1
SRC_FILES_SWIFT += src/hpke.swift
HDR_SWIFT = obj/aastuff-Swift.h
endif

all: aastuff aastuff_standalone

aastuff: $(SRC_FILES) $(HDRS_FILES)
clang++ $(CFLAGS) $(LDFLAGS) $(LDLIBS) $(SRC_FILES) -o $@
obj:
mkdir -p obj

obj/%.o: src/%.swift | obj
swiftc -module-name aastuff -parse-as-library -emit-module -emit-module-path obj/$*.swiftmodule -c $< -o $@

$(HDR_SWIFT): $(OBJ_FILES_SWIFT)
swiftc -module-name aastuff -parse-as-library -emit-objc-header -emit-objc-header-path $@ -emit-module -emit-module-path obj/aastuff.swiftmodule $(subst .o,.swiftmodule,$^)

aastuff: $(SRC_FILES) $(HDR_FILES) $(HDR_SWIFT)
clang++ $(CFLAGS) $(LDFLAGS) $(LDLIBS) $(SRC_FILES) $(OBJ_FILES_SWIFT) -o $@
codesign -f -s - $@

aastuff_standalone: $(SRC_FILES) $(HDRS_FILES)
clang++ -DAASTUFF_STANDALONE=1 $(CFLAGS) $(LDFLAGS) $(LDLIBS) $(SRC_FILES) -o $@
aastuff_standalone: $(SRC_FILES) $(OBJ_FILES_SWIFT) $(HDR_FILES) $(HDR_SWIFT)
clang++ -DAASTUFF_STANDALONE=1 $(CFLAGS) $(LDFLAGS) $(LDLIBS) $(SRC_FILES) $(OBJ_FILES_SWIFT) -o $@
codesign -f -s - $@

clean:
rm -f aastuff aastuff_standalone
rm -rf obj *.dSYM
rm -f aastuff aastuff_standalone
68 changes: 55 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,82 @@

AEA OTA/IPSW decryption

## Grabbing keys with `get_key.py`
## Prerequisites

Gets a key from the key URL embedded in an AEA's auth data blob.
- `get_key.py`
- Python 3.10+ (might work with older, but not tested)
- `requests`
- `pyhpke`
- `aastuff`
- macOS 13+
- macOS 14+ for HPKE support
- `aastuff_standalone`
- macOS 12+
- macOS 14+ for HPKE support

> [!NOTE]
> OTAs before iOS 18.0 beta 3 did not have embedded auth data; for these OTAs, you must use the key provided with your response. macOS is the exception.
## Building and Installing

### `get_key.py`

```shell
pip3 install -r requirements.txt
python3 get_key.py <path to AEA>
```

Note: it is highly recommended to use a virtual environment:
> [!NOTE]
> It is highly recommended to use a virtual environment:
>
> ```shell
> python3 -m venv .env # only needed once
> source .env/bin/activate
> pip3 install -r requirements.txt # only needed once
> ```
>
> On future runs, you only need to activate the virtual environment:
>
> ```shell
> source .env/bin/activate
> ```
### `aastuff`/`aastuff_standalone`
You can pass two options to the makefile:
- `DEBUG=1`: build debug (debug prints, no optimizations, debug information)
- `HPKE=1`: build with HPKE support (needs macOS 14.0+)
```shell
make [DEBUG=1] [HPKE=1]
```
## Grabbing keys with `get_key.py`

Unwrap the decryption key using the data embedded in an AEA's auth data blob.

> [!NOTE]
> OTAs before iOS 18.0 beta 3 did not have embedded auth data; for these OTAs, you must use the decryption key provided with your response. macOS is the exception and has always had embedded auth data.
```shell
python3 -m venv .env # only needed once
source .env/bin/activate
pip3 install -r requirements.txt # only needed once
source .env/bin/activate # if you used a virtual environment
python3 get_key.py <path to AEA>
```

## Decrypting an AEA

```shell
aea decrypt -i <path to AEA> -o <decrypted output file> -key-value 'base64:<key in base64>'
# or
./aastuff -i <path to AEA> -o <decrypted output folder> -d -k <key in base64>
# or, to use the network to grab the private key
./aastuff -i <path to AEA> -o <decrypted output folder> -d -n
```

For IPSWs, you will get the unwrapped file (ie. `090-34187-052.dmg.aea` will decrypt to `090-34187-052.dmg`).

For assets, you will get specially crafted AppleArchives (see next section).
For assets, you will get specially crafted Apple Archives (see next section).

## Extracting assets

Assets (including OTA updates) are constructed specially and cannot be extracted with standard (`aa`) tooling. They can be decrypted normally, which will result in an AppleArchive that is not extractable with `aa` (we will call these "asset archives"). `aastuff` must be used to extract them.
Assets (including OTA updates) are constructed specially and cannot be extracted with standard (`aa`) tooling. They can be decrypted normally, which will result in an Apple Archive that is not extractable with `aa` (we will call these "asset archives"). `aastuff` must be used to extract them.

```shell
# Decrypt if necessary
Expand Down Expand Up @@ -68,7 +110,7 @@ For now, both are built and used in the same way. Once `aastuff_standalone` is f

## Credits

- Siguza - auth data parsing strategy, AppleArchive extraction sample code
- Siguza - auth data parsing strategy, Apple Archive extraction sample code
- Nicolas - original HPKE code
- Snoolie - auth data parsing strategy
- Flagers - AppleArchive assistance
- Flagers - Apple Archive assistance
84 changes: 51 additions & 33 deletions get_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@

# Requirements: pip3 install requests pyhpke

import argparse
import base64
import json
from pathlib import Path
import argparse
import sys
from pathlib import Path
from pprint import pprint

import requests
from pyhpke import AEADId, CipherSuite, KDFId, KEMId, KEMKey
Expand All @@ -24,48 +25,49 @@ def error(msg):
exit(1)


def main(aea_path: Path, verbose: bool = False):
def get_key(f, verbose: bool = False):
fields = {}
with aea_path.open("rb") as f:
header = f.read(12)
if len(header) != 12:
error(f"Expected 12 bytes, got {len(header)}")
header = f.read(12)
if len(header) != 12:
error(f"Expected 12 bytes, got {len(header)}")

magic = header[:4]
if magic != b"AEA1":
error(f"Invalid magic: {magic.hex()}")

magic = header[:4]
if magic != b"AEA1":
error(f"Invalid magic: {magic.hex()}")
profile = int.from_bytes(header[4:7], "little")
if profile != AEA_PROFILE__HKDF_SHA256_AESCTR_HMAC__SYMMETRIC__NONE:
error(f"Invalid AEA profile: {profile}")

profile = int.from_bytes(header[4:7], "little")
if profile != AEA_PROFILE__HKDF_SHA256_AESCTR_HMAC__SYMMETRIC__NONE:
error(f"Invalid AEA profile: {profile}")
auth_data_blob_size = int.from_bytes(header[8:12], "little")

auth_data_blob_size = int.from_bytes(header[8:12], "little")
if auth_data_blob_size == 0:
error("No auth data blob")

if auth_data_blob_size == 0:
error("No auth data blob")
auth_data_blob = f.read(auth_data_blob_size)
if len(auth_data_blob) != auth_data_blob_size:
error(f"Expected {auth_data_blob_size} bytes, got {len(auth_data_blob)}")

auth_data_blob = f.read(auth_data_blob_size)
if len(auth_data_blob) != auth_data_blob_size:
error(f"Expected {auth_data_blob_size} bytes, got {len(auth_data_blob)}")
assert auth_data_blob[:4]

assert auth_data_blob[:4]
while len(auth_data_blob) > 0:
field_size = int.from_bytes(auth_data_blob[:4], "little")
field_blob = auth_data_blob[:field_size]

while len(auth_data_blob) > 0:
field_size = int.from_bytes(auth_data_blob[:4], "little")
field_blob = auth_data_blob[:field_size]
key, value = field_blob[4:].split(b"\x00", 1)

key_end = field_blob.index(b"\x00", 4)
key = field_blob[4:key_end].decode()
value = field_blob[key_end + 1 :].decode()
fields[key] = value
fields[key.decode()] = value.decode()

auth_data_blob = auth_data_blob[field_size:]
auth_data_blob = auth_data_blob[field_size:]

if verbose:
print(fields, "\n", file=sys.stderr)
pprint(fields, stream=sys.stderr)

if "com.apple.wkms.fcs-response" not in fields:
error("No fcs-response field found, is this from an OTA?")
error("No fcs-response field found!")

if "com.apple.wkms.fcs-key-url" not in fields:
error("No fcs-key-url field found!")

fcs_response = json.loads(fields["com.apple.wkms.fcs-response"])
enc_request = base64.b64decode(fcs_response["enc-request"])
Expand All @@ -86,10 +88,26 @@ def main(aea_path: Path, verbose: bool = False):
print(base64.b64encode(pt).decode())


def main(path: str, verbose: bool = False):
if path.startswith("http://") or path.startswith("https://"):
with requests.get(path, timeout=10, stream=True) as response:
response.raise_for_status()

get_key(response.raw, verbose)

else:
aea_path = Path(path)
if not aea_path.exists():
error(f"File {path} does not exist")

with aea_path.open("rb") as f:
get_key(f, verbose)


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Get the key for an AEA file")
parser.add_argument("path", help="Path to the AEA file")
parser = argparse.ArgumentParser(description="Get the key for an AEA file or URL")
parser.add_argument("path", help="Path or URL to the AEA file")
parser.add_argument("-v", "--verbose", action="store_true", help="Show verbose output")
args = parser.parse_args()

main(Path(args.path), args.verbose)
main(args.path, args.verbose)
15 changes: 15 additions & 0 deletions include/aea.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef AEA_H
#define AEA_H

#import <AppleArchive/AppleArchive.h>
#import <Foundation/Foundation.h>

#import "args.h"

#if HAS_HPKE

int fetchKey(AEAContext stream, ExtractionConfiguration* config);

#endif

#endif /* AEA_H */
14 changes: 13 additions & 1 deletion include/args.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,27 @@

#import <Foundation/Foundation.h>

// This needs to be manually synced with the enum in hpke.swift
typedef NS_ENUM(NSInteger, PrivateKeyFormat) {
PrivateKeyFormatAll,
PrivateKeyFormatPEM,
PrivateKeyFormatDER,
PrivateKeyFormatX963,
};

@interface ExtractionConfiguration : NSObject <NSCopying>

@property(nonatomic, assign) bool encrypted;
@property(nonatomic, assign) bool list;
@property(nonatomic, strong) NSString* archivePath;
@property(nonatomic, strong) NSString* outputDirectory;
@property(nonatomic, strong) NSString* outputPath;
@property(nonatomic, assign) bool decryptOnly;
@property(nonatomic, strong) NSData* key;
@property(nonatomic, strong) NSString* filter;
@property(nonatomic, strong) NSRegularExpression* regex;
@property(nonatomic, assign) bool network;
@property(nonatomic, strong) NSData* unwrapKey;
@property(nonatomic, assign) PrivateKeyFormat unwrapKeyFormat;

@property(nonatomic, strong) NSString* function;

Expand Down
5 changes: 3 additions & 2 deletions include/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#define UTILS_H

#import <Foundation/Foundation.h>
#import <objc/NSObjCRuntime.h>
#import <stdio.h>

#define LOG(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ##__VA_ARGS__] UTF8String])
Expand All @@ -20,6 +19,8 @@
#define NAME @"aastuff"
#endif

#define VERSION @"1.0.0"
#define VERSION @"2.0.0"

NSData* makeSynchronousRequest(NSURLRequest* request, NSHTTPURLResponse** response, NSError** error);

#endif /* UTILS_H */
Loading

0 comments on commit 46945f2

Please sign in to comment.