mirror of
https://github.com/azenla/AppleCache.git
synced 2026-02-12 16:32:45 +00:00
Initial Commit
This commit is contained in:
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
.vscode/
|
||||
.idea/
|
||||
.atom/
|
||||
|
||||
|
||||
build/
|
||||
packages
|
||||
pubspec.lock
|
||||
.idea/
|
||||
docs/
|
||||
dartdoc-viewer/
|
||||
.project
|
||||
.settings
|
||||
.buildlog
|
||||
.pub
|
||||
out/
|
||||
*.iml
|
||||
.packages
|
||||
.dart-tool/
|
||||
27
README.md
Normal file
27
README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Apple Cache
|
||||
|
||||
This is a reverse engineering attempt of the Apple Content Caching system.
|
||||
|
||||
Currently the locator service is implemented, but I am in the process of
|
||||
writing documentation on how the cache registration and server work.
|
||||
|
||||
The goal of this project is to challenge myself in a serious reverse engineering
|
||||
attempt while also creating something I want to make: an Apple Content Cache that works on Linux servers.
|
||||
|
||||
## Content Caching
|
||||
|
||||
Content Caching is available in the Sharing section of System Preferences.
|
||||
It is used to cache content on your local network for public Apple content or
|
||||
iCloud content. The `/usr/libexec/AssetCache/AssetCache` is responsible for a majority
|
||||
of the work. It has an HTTP server that has an API that allows fetching and uploading of content from the server.
|
||||
|
||||
## Research
|
||||
|
||||
* [Locator Service](locator/README.md)
|
||||
|
||||
## Methodology
|
||||
|
||||
The work here was done by using [Charles Proxy](https://www.charlesproxy.com/) and [Frida](https://www.frida.re/).
|
||||
|
||||
The `tools/frida-ssl-pin.js` file is a Frida script that can attach to any macOS process and disable all SSL verification and SSL certificate pinning. This has allowed me to deeply examine the requests going to Apple's servers. This script is likely useful
|
||||
for many other use cases. If anyone else uses it, I'd love to hear about how it was used (I'm a super huge nerd and am quite interested in reverse engineering).
|
||||
89
locator/README.md
Normal file
89
locator/README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Content Cache Locator
|
||||
|
||||
The locator is the service that provides a list of registered local caches.
|
||||
|
||||
Oddly, Apple chose to register the caches based on the public IP address of the
|
||||
registered cache.
|
||||
|
||||
The locator service URL is: `https://lcdn-locator.apple.com/lcdn/locate`
|
||||
|
||||
The request JSON to locate all the caches is:
|
||||
|
||||
```json
|
||||
{
|
||||
"ranked-results": true,
|
||||
"locator-tag": "#f5cfd4d0", // Seems to be different each time.
|
||||
"local-addresses": [
|
||||
"10.0.0.60" // My local address.
|
||||
],
|
||||
"public-address-ranges": [
|
||||
[]
|
||||
],
|
||||
"locator-software": [
|
||||
{
|
||||
"build": "19A578c",
|
||||
"type": "system",
|
||||
"name": "Mac OS X",
|
||||
"version": "10.15"
|
||||
},
|
||||
{
|
||||
"id": "com.apple.AssetCacheLocatorService",
|
||||
"executable": "AssetCacheLocatorService",
|
||||
"type": "bundle",
|
||||
"name": "AssetCacheLocatorService",
|
||||
"version": "106.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The response looks similar to:
|
||||
|
||||
```json
|
||||
{
|
||||
"connect-timeout": 0.75,
|
||||
"servers": [
|
||||
{
|
||||
"address": "10.0.0.60",
|
||||
"port": 54459,
|
||||
"guid": "C881402D-001C-49BD-8672-7BA84A69C9AB",
|
||||
"version": "233",
|
||||
"connect-timeout": 0.5,
|
||||
"details": {
|
||||
"capabilities": {
|
||||
"ur": true,
|
||||
"sc": true,
|
||||
"pc": true,
|
||||
"im": true,
|
||||
"ns": true,
|
||||
"query-parameters": true
|
||||
},
|
||||
"cache-size": 150000000000,
|
||||
"ac-power": true,
|
||||
"is-portable": true,
|
||||
"local-network": [
|
||||
{
|
||||
"speed": 30,
|
||||
"wired": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"rank": 1
|
||||
}
|
||||
],
|
||||
"validity-interval": 3600
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
Given the usage of public IPs as the way to differentiate between caches,
|
||||
I wonder if it would be possible to use shared VPNs and receive a list
|
||||
of caches on the shared VPN. This is quite concerning, though likely has
|
||||
no actual security impact given that the local clients should not be available
|
||||
between VPN clients.
|
||||
|
||||
Also, the reason that Apple seems to register content caches at all is related to
|
||||
region restriction control of cached content (movies that aren't available in certain regions.)
|
||||
|
||||
Frankly, I believe that Apple should reconsider this system and use mDNS to discover content caches. It just makes more sense, and Apple already uses mDNS (because of Bonjour), so why not?
|
||||
38
locator/locate.dart
Normal file
38
locator/locate.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
var client = new HttpClient();
|
||||
var request = await client.postUrl(Uri.parse("https://lcdn-locator.apple.com/lcdn/locate"));
|
||||
request.writeln(json.encode({
|
||||
"ranked-results": true,
|
||||
"locator-tag": "#f5cfd4d0",
|
||||
"local-addresses": [
|
||||
args[0]
|
||||
],
|
||||
"public-address-ranges": [
|
||||
[]
|
||||
],
|
||||
"locator-software": [
|
||||
{
|
||||
"build": "19A578c",
|
||||
"type": "system",
|
||||
"name": "Mac OS X",
|
||||
"version": "10.15"
|
||||
},
|
||||
{
|
||||
"id": "com.apple.AssetCacheLocatorService",
|
||||
"executable": "AssetCacheLocatorService",
|
||||
"type": "bundle",
|
||||
"name": "AssetCacheLocatorService",
|
||||
"version": "106.1"
|
||||
}
|
||||
]
|
||||
}));
|
||||
var response = await request.close();
|
||||
var content = await utf8.decodeStream(response);
|
||||
var data = json.decode(content);
|
||||
print(const JsonEncoder.withIndent(" ").convert(data));
|
||||
client.close();
|
||||
}
|
||||
79
tools/frida-ssl-pin.js
Normal file
79
tools/frida-ssl-pin.js
Normal file
@@ -0,0 +1,79 @@
|
||||
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.')
|
||||
}
|
||||
Reference in New Issue
Block a user