Files
revkit/packages/tweak/AppbootKeyExtract/Sources/AppbootKeyExtractC/Tweak.m
T
tkgstrator 03e4754243 feat(tweak): add HKDF hook and remove frida/proxyman skills
Hooks HKDF_extract and HKDF_expand in the tweak for improved key derivation
logging and analysis. Removes Frida and Proxyman skills, references, and
documentation for a more focused Agent Team workflow.

Adds several Python tools for systematic HKDF and session key derivation
analysis, supporting ongoing reverse engineering and debugging efforts.

Switches all agent workflow documentation and prompts to English, clarifies
inter-agent communication constraints, and updates the workflow to reflect
current team structure and supported features.

Relates to the need for more accurate key extraction and simplified agent
usage.
2026-04-08 10:32:32 +00:00

436 lines
18 KiB
Objective-C

#import <Orion/Orion.h>
#import <os/log.h>
#import <substrate.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <dlfcn.h>
static os_log_t g_log = NULL;
static NSString *g_logFile = nil;
static NSString *g_keyFile = nil;
// Key storage — accumulated during session
static NSMutableDictionary *g_keys = nil;
static NSMutableArray *g_aesKeys = nil;
static NSMutableArray *g_hmacKeys = nil;
static BOOL g_appbootDone = NO;
#define NFXKEY_LOG(fmt, ...) \
do { \
if (g_log) { os_log(g_log, fmt, ##__VA_ARGS__); } \
NSLog(@"[NFXKey] " fmt, ##__VA_ARGS__); \
} while (0)
static void file_log(NSString *msg) {
if (!g_logFile) return;
@try {
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.dateFormat = @"HH:mm:ss.SSS";
NSString *line = [NSString stringWithFormat:@"%@ %@\n", [df stringFromDate:[NSDate date]], msg];
NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:g_logFile];
if (!fh) {
[line writeToFile:g_logFile atomically:YES encoding:NSUTF8StringEncoding error:nil];
} else {
[fh seekToEndOfFile];
[fh writeData:[line dataUsingEncoding:NSUTF8StringEncoding]];
[fh closeFile];
}
} @catch (NSException *e) {}
}
static NSString *hexEncode(const uint8_t *data, int len) {
NSMutableString *s = [NSMutableString stringWithCapacity:len * 2];
for (int i = 0; i < len; i++) {
[s appendFormat:@"%02x", data[i]];
}
return s;
}
static void saveKeysToFile(void) {
if (!g_keyFile || !g_keys) return;
@try {
NSData *json = [NSJSONSerialization dataWithJSONObject:g_keys
options:NSJSONWritingPrettyPrinted
error:nil];
if (json) {
[json writeToFile:g_keyFile atomically:YES];
file_log([NSString stringWithFormat:@"[keys] saved to %@", g_keyFile]);
}
} @catch (NSException *e) {}
}
// ---------------------------------------------------------------------------
// OpenSSL function pointers (resolved via dlsym)
// ---------------------------------------------------------------------------
typedef struct dh_st DH;
typedef struct bignum_st BIGNUM;
static void (*fn_DH_get0_key)(const DH *, const BIGNUM **, const BIGNUM **);
static void (*fn_DH_get0_pqg)(const DH *, const BIGNUM **, const BIGNUM **, const BIGNUM **);
static int (*fn_BN_num_bits)(const BIGNUM *);
static int (*fn_BN_bn2bin)(const BIGNUM *, unsigned char *);
// ---------------------------------------------------------------------------
// Hook: DH_generate_key
// ---------------------------------------------------------------------------
static int (*orig_DH_generate_key)(DH *dh);
static int hook_DH_generate_key(DH *dh) {
int ret = orig_DH_generate_key(dh);
if (ret == 1 && fn_DH_get0_key && fn_BN_num_bits && fn_BN_bn2bin) {
const BIGNUM *pub = NULL, *priv = NULL;
fn_DH_get0_key(dh, &pub, &priv);
if (pub) {
int pubBytes = (fn_BN_num_bits(pub) + 7) / 8;
if (pubBytes > 0 && pubBytes <= 1024) {
uint8_t *buf = (uint8_t *)malloc(pubBytes);
fn_BN_bn2bin(pub, buf);
NSString *hex = hexEncode(buf, pubBytes);
file_log([NSString stringWithFormat:@"[DH_generate_key] pub_key (%d bytes)=%@",
pubBytes, hex]);
g_keys[@"dh_pub_key"] = hex;
free(buf);
}
}
if (priv) {
int privBytes = (fn_BN_num_bits(priv) + 7) / 8;
if (privBytes > 0 && privBytes <= 1024) {
uint8_t *buf = (uint8_t *)malloc(privBytes);
fn_BN_bn2bin(priv, buf);
NSString *hex = hexEncode(buf, privBytes);
file_log([NSString stringWithFormat:@"[DH_generate_key] priv_key (%d bytes)=%@",
privBytes, hex]);
g_keys[@"dh_priv_key"] = hex;
free(buf);
}
}
if (fn_DH_get0_pqg) {
const BIGNUM *p = NULL, *q = NULL, *g = NULL;
fn_DH_get0_pqg(dh, &p, &q, &g);
if (p) {
int pBytes = (fn_BN_num_bits(p) + 7) / 8;
if (pBytes > 0 && pBytes <= 4096) {
uint8_t *buf = (uint8_t *)malloc(pBytes);
fn_BN_bn2bin(p, buf);
NSString *hex = hexEncode(buf, pBytes);
file_log([NSString stringWithFormat:@"[DH_generate_key] dh_p (%d bytes) head=%02x%02x%02x%02x",
pBytes, buf[0], buf[1], buf[2], buf[3]]);
g_keys[@"dh_p"] = hex;
free(buf);
}
}
if (g) {
int gBytes = (fn_BN_num_bits(g) + 7) / 8;
if (gBytes > 0 && gBytes <= 4096) {
uint8_t *buf = (uint8_t *)malloc(gBytes);
fn_BN_bn2bin(g, buf);
NSString *hex = hexEncode(buf, gBytes);
file_log([NSString stringWithFormat:@"[DH_generate_key] dh_g (%d bytes) head=%02x",
gBytes, buf[0]]);
g_keys[@"dh_g"] = hex;
free(buf);
}
}
}
saveKeysToFile();
}
return ret;
}
// ---------------------------------------------------------------------------
// Hook: DH_compute_key
// ---------------------------------------------------------------------------
static int (*orig_DH_compute_key)(unsigned char *key, const BIGNUM *pub_key, DH *dh);
static int hook_DH_compute_key(unsigned char *key, const BIGNUM *pub_key, DH *dh) {
int ret = orig_DH_compute_key(key, pub_key, dh);
if (ret > 0) {
NSString *hex = hexEncode(key, ret);
file_log([NSString stringWithFormat:@"[DH_compute_key] shared_secret (%d bytes)=%@",
ret, hex]);
g_keys[@"dh_shared_secret"] = hex;
saveKeysToFile();
}
return ret;
}
// ---------------------------------------------------------------------------
// Hook: AES_set_encrypt_key / AES_set_decrypt_key
// ---------------------------------------------------------------------------
static int (*orig_AES_set_encrypt_key)(const unsigned char *userKey, int bits, void *key);
static int hook_AES_set_encrypt_key(const unsigned char *userKey, int bits, void *key) {
int keyLen = bits / 8;
if (keyLen == 16 || keyLen == 32) {
NSString *hex = hexEncode(userKey, keyLen);
file_log([NSString stringWithFormat:@"[AES_set_encrypt_key] bits=%d key=%@", bits, hex]);
// Track AES-128 keys for session key identification
if (bits == 128) {
NSString *phase = g_appbootDone ? @"post_appboot" : @"pre_appboot";
NSDictionary *entry = @{@"key": hex, @"bits": @(bits), @"phase": phase};
// Only add if not duplicate of last entry
if (g_aesKeys.count == 0 || ![g_aesKeys.lastObject[@"key"] isEqualToString:hex]) {
[g_aesKeys addObject:entry];
}
// Update current session enc_key
g_keys[g_appbootDone ? @"session_enc_key" : @"pre_session_enc_key"] = hex;
}
}
return orig_AES_set_encrypt_key(userKey, bits, key);
}
static int (*orig_AES_set_decrypt_key)(const unsigned char *userKey, int bits, void *key);
static int hook_AES_set_decrypt_key(const unsigned char *userKey, int bits, void *key) {
int keyLen = bits / 8;
if (keyLen == 16 || keyLen == 32) {
file_log([NSString stringWithFormat:@"[AES_set_decrypt_key] bits=%d key=%@",
bits, hexEncode(userKey, keyLen)]);
}
return orig_AES_set_decrypt_key(userKey, bits, key);
}
// ---------------------------------------------------------------------------
// Hook: HMAC
// ---------------------------------------------------------------------------
static unsigned char *(*orig_HMAC)(const void *evp_md, const void *key, int key_len,
const unsigned char *d, size_t n, unsigned char *md, unsigned int *md_len);
static unsigned char *hook_HMAC(const void *evp_md, const void *key, int key_len,
const unsigned char *d, size_t n, unsigned char *md, unsigned int *md_len) {
if (key_len == 32) {
NSString *hex = hexEncode((const uint8_t *)key, key_len);
file_log([NSString stringWithFormat:@"[HMAC] key_len=%d key=%@", key_len, hex]);
NSString *phase = g_appbootDone ? @"post_appboot" : @"pre_appboot";
NSDictionary *entry = @{@"key": hex, @"phase": phase};
if (g_hmacKeys.count == 0 || ![g_hmacKeys.lastObject[@"key"] isEqualToString:hex]) {
[g_hmacKeys addObject:entry];
}
// Update current session hmac_key
g_keys[g_appbootDone ? @"session_hmac_key" : @"pre_session_hmac_key"] = hex;
}
return orig_HMAC(evp_md, key, key_len, d, n, md, md_len);
}
// ---------------------------------------------------------------------------
// Hook: HKDF_extract / HKDF_Expand
// ---------------------------------------------------------------------------
static int (*orig_HKDF_extract)(uint8_t *out_key, size_t *out_len,
const void *digest,
const uint8_t *secret, size_t secret_len,
const uint8_t *salt, size_t salt_len);
static int hook_HKDF_extract(uint8_t *out_key, size_t *out_len,
const void *digest,
const uint8_t *secret, size_t secret_len,
const uint8_t *salt, size_t salt_len) {
int ret = orig_HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, salt_len);
if (ret == 1 && out_key && out_len) {
NSString *saltHex = (salt && salt_len > 0) ? hexEncode(salt, (int)salt_len) : @"(null)";
NSString *ikmHex = (secret && secret_len > 0) ? hexEncode(secret, (int)secret_len) : @"(null)";
NSString *prkHex = hexEncode(out_key, (int)*out_len);
file_log([NSString stringWithFormat:@"[HKDF_extract] salt(%zu)=%@ ikm(%zu)=%@ prk(%zu)=%@",
salt_len, saltHex, secret_len, ikmHex, *out_len, prkHex]);
NFXKEY_LOG("[HKDF_extract] salt(%zu)=%@ ikm(%zu)=%@ prk(%zu)=%@",
salt_len, saltHex, secret_len, ikmHex, *out_len, prkHex);
g_keys[@"hkdf_salt"] = saltHex;
g_keys[@"hkdf_ikm"] = ikmHex;
g_keys[@"hkdf_prk"] = prkHex;
saveKeysToFile();
}
return ret;
}
static int (*orig_HKDF_expand)(uint8_t *out_key, size_t out_len,
const void *digest,
const uint8_t *prk, size_t prk_len,
const uint8_t *info, size_t info_len);
static int hook_HKDF_expand(uint8_t *out_key, size_t out_len,
const void *digest,
const uint8_t *prk, size_t prk_len,
const uint8_t *info, size_t info_len) {
int ret = orig_HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len);
if (ret == 1 && out_key && out_len > 0) {
NSString *prkHex = (prk && prk_len > 0) ? hexEncode(prk, (int)prk_len) : @"(null)";
NSString *infoHex = (info && info_len > 0) ? hexEncode(info, (int)info_len) : @"(null)";
NSString *okmHex = hexEncode(out_key, (int)out_len);
file_log([NSString stringWithFormat:@"[HKDF_expand] prk(%zu)=%@ info(%zu)=%@ okm(%zu)=%@",
prk_len, prkHex, info_len, infoHex, out_len, okmHex]);
NFXKEY_LOG("[HKDF_expand] prk(%zu)=%@ info(%zu)=%@ okm(%zu)=%@",
prk_len, prkHex, info_len, infoHex, out_len, okmHex);
g_keys[@"hkdf_info"] = infoHex;
g_keys[@"hkdf_okm"] = okmHex;
g_keys[@"hkdf_okm_len"] = @(out_len);
saveKeysToFile();
}
return ret;
}
// ---------------------------------------------------------------------------
// Hook: IosMslClient.setDidAppboot:
// ---------------------------------------------------------------------------
static void (*orig_setDidAppboot)(id self, SEL _cmd, BOOL value);
static void hook_setDidAppboot(id self, SEL _cmd, BOOL value) {
file_log([NSString stringWithFormat:@"setDidAppboot: %d", value]);
NFXKEY_LOG("setDidAppboot: %d", value);
if (value) {
g_appbootDone = YES;
// Snapshot pre-appboot keys
g_keys[@"aes_key_history"] = [g_aesKeys copy];
g_keys[@"hmac_key_history"] = [g_hmacKeys copy];
}
orig_setDidAppboot(self, _cmd, value);
if (value) {
// Save after original method (which may trigger key exchange)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Final save with post-appboot keys
g_keys[@"aes_key_history"] = [g_aesKeys copy];
g_keys[@"hmac_key_history"] = [g_hmacKeys copy];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss";
g_keys[@"timestamp"] = [df stringFromDate:[NSDate date]];
saveKeysToFile();
file_log(@"[keys] final save after appboot");
});
}
}
// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
__attribute__((constructor)) static void init(void) {
orion_init();
g_log = os_log_create("dev.tkgstrator.charon", "keyextract");
g_logFile = [NSTemporaryDirectory() stringByAppendingPathComponent:@"nfxkey.log"];
g_keyFile = [NSTemporaryDirectory() stringByAppendingPathComponent:@"msl_keys.json"];
g_keys = [NSMutableDictionary dictionary];
g_aesKeys = [NSMutableArray array];
g_hmacKeys = [NSMutableArray array];
g_appbootDone = NO;
file_log(@"=== AppbootKeyExtract loaded ===");
NFXKEY_LOG("AppbootKeyExtract loaded");
// Hook IosMslClient.setDidAppboot:
Class cls = objc_getClass("IosMslClient");
if (cls) {
MSHookMessageEx(cls, @selector(setDidAppboot:),
(IMP)hook_setDidAppboot, (IMP *)&orig_setDidAppboot);
file_log(@"[+] setDidAppboot: hooked");
NFXKEY_LOG(" [+] setDidAppboot: hooked");
}
// Hook NFWebCrypto functions
void *nfwc = dlopen("@rpath/NFWebCrypto.framework/NFWebCrypto", RTLD_NOLOAD);
if (!nfwc) {
nfwc = dlopen("/usr/lib/libNFWebCrypto.dylib", RTLD_NOLOAD);
}
if (nfwc) {
// Resolve helper functions
fn_DH_get0_key = (void (*)(const DH *, const BIGNUM **, const BIGNUM **))dlsym(nfwc, "DH_get0_key");
fn_DH_get0_pqg = (void (*)(const DH *, const BIGNUM **, const BIGNUM **, const BIGNUM **))dlsym(nfwc, "DH_get0_pqg");
fn_BN_num_bits = (int (*)(const BIGNUM *))dlsym(nfwc, "BN_num_bits");
fn_BN_bn2bin = (int (*)(const BIGNUM *, unsigned char *))dlsym(nfwc, "BN_bn2bin");
// DH hooks
void *dhGenKey = dlsym(nfwc, "DH_generate_key");
void *dhCompKey = dlsym(nfwc, "DH_compute_key");
if (dhGenKey) {
MSHookFunction(dhGenKey, (void *)hook_DH_generate_key, (void **)&orig_DH_generate_key);
file_log(@"[+] DH_generate_key hooked");
NFXKEY_LOG(" [+] DH_generate_key hooked");
}
if (dhCompKey) {
MSHookFunction(dhCompKey, (void *)hook_DH_compute_key, (void **)&orig_DH_compute_key);
file_log(@"[+] DH_compute_key hooked");
NFXKEY_LOG(" [+] DH_compute_key hooked");
}
// AES hooks
void *aesEncKey = dlsym(nfwc, "AES_set_encrypt_key");
void *aesDecKey = dlsym(nfwc, "AES_set_decrypt_key");
void *hmacFn = dlsym(nfwc, "HMAC");
if (aesEncKey) {
MSHookFunction(aesEncKey, (void *)hook_AES_set_encrypt_key, (void **)&orig_AES_set_encrypt_key);
file_log(@"[+] AES_set_encrypt_key hooked");
NFXKEY_LOG(" [+] AES_set_encrypt_key hooked");
}
if (aesDecKey) {
MSHookFunction(aesDecKey, (void *)hook_AES_set_decrypt_key, (void **)&orig_AES_set_decrypt_key);
file_log(@"[+] AES_set_decrypt_key hooked");
NFXKEY_LOG(" [+] AES_set_decrypt_key hooked");
}
if (hmacFn) {
MSHookFunction(hmacFn, (void *)hook_HMAC, (void **)&orig_HMAC);
file_log(@"[+] HMAC hooked");
NFXKEY_LOG(" [+] HMAC hooked");
}
// HKDF hooks — try lowercase first (BoringSSL), then PascalCase
void *hkdfExtract = dlsym(nfwc, "HKDF_extract");
if (!hkdfExtract) {
hkdfExtract = dlsym(nfwc, "HKDF_Extract");
if (hkdfExtract) {
file_log(@"[!] HKDF_extract not found, using HKDF_Extract");
}
}
void *hkdfExpand = dlsym(nfwc, "HKDF_expand");
if (!hkdfExpand) {
hkdfExpand = dlsym(nfwc, "HKDF_Expand");
if (hkdfExpand) {
file_log(@"[!] HKDF_expand not found, using HKDF_Expand");
}
}
if (hkdfExtract) {
MSHookFunction(hkdfExtract, (void *)hook_HKDF_extract, (void **)&orig_HKDF_extract);
file_log(@"[+] HKDF_extract hooked");
NFXKEY_LOG(" [+] HKDF_extract hooked");
} else {
file_log(@"[-] HKDF_extract not found (tried HKDF_extract / HKDF_Extract)");
NFXKEY_LOG(" [-] HKDF_extract not found");
}
if (hkdfExpand) {
MSHookFunction(hkdfExpand, (void *)hook_HKDF_expand, (void **)&orig_HKDF_expand);
file_log(@"[+] HKDF_expand hooked");
NFXKEY_LOG(" [+] HKDF_expand hooked");
} else {
file_log(@"[-] HKDF_expand not found (tried HKDF_expand / HKDF_Expand)");
NFXKEY_LOG(" [-] HKDF_expand not found");
}
} else {
file_log(@"[-] NFWebCrypto not loaded");
NFXKEY_LOG(" [-] NFWebCrypto not loaded");
}
NFXKEY_LOG("AppbootKeyExtract: done");
}