起因是前几天在Github上刷到了这个 https://github.com/amineross/showcase 仓库。大概意思是找一个旧平板,停掉系统BTStack还有Wifi自己接管。然后就能和iPhone建立iAP2连接,最后协商连接手机Wifi,就能拿到Carplay画面。

这让我非常震惊,因为在 上一篇文章 里面,我写过关于MFi改定位的一小个想法,但是由于MFi改定位会需要手机和真实MFi chip进行交互认证,而这个iPhone可以直接不用MFi chip里的证书就通过认证?

于是我找到了MFi的specification:

4.1 Accessory Authentication Requirements

可以看到这里五步:

  1. RequestAuthenticationCertificate:请求证书(iPhone → MFi)0xAA00
  2. AuthenticationCertificate:发送证书(MFi → iPhone) 0xAA01
  3. RequestAuthenticationChallengeResponse :请求challenge,这里会发送一个nonce交给MFi进行签名(iPhone → MFi)0XAA02
  4. AuthenticationResponse:返回签名完成的challenge(MFi → iPhone)0xAA03
  5. AuthenticationFailed:失败(iPhone → MFi)0xAA04

然后我就去对比了下showcase里的MFi认证的逻辑函数,是这个:

c
static void handle_ctrl_msg(uint16_t msg_id, uint8_t *params, int plen) {
    printf("[CTRL] msg=0x%04X (%d bytes)\n", msg_id, plen);
 
    switch (msg_id) {
    /* ── Authentication ── */
    case 0xAA00:
        printf("[CP] *** RequestAuthCert ***\n");
        send_auth_cert();
        break;
    case 0xAA02: {
        printf("[CP] *** RequestAuthChallenge ***\n");
        if (plen>=4) {
            int dlen=((params[0]<<8)|params[1])-4;
            if (dlen>0&&dlen<=1024) send_auth_response(&params[4],dlen);
        }
        break;
    }
    case 0xAA04: printf("[CP] *** AUTH FAILED ***\n"); break;
    case 0xAA05:
        mfi_auth_succeeded = 1;
        printf("[CP] *** AUTH SUCCEEDED! ***\n");
        maybe_start_handoff_watchdog();
        break;
....
 

然后是证书申请:

objective-c
static void issue_baa_cert(void) {
    printf("[BAA] Issuing certificate...\n");
    void *di = dlopen("/System/Library/PrivateFrameworks/DeviceIdentity.framework/DeviceIdentity", RTLD_NOW);
    if (!di) di = dlopen("/System/Library/PrivateFrameworks/MobileActivation.framework/MobileActivation", RTLD_NOW);
    if (!di) { printf("[BAA] Cannot load framework!\n"); return; }
    typedef void (^DIBlock)(id, id, id);
    typedef void (*DIFunc)(id, id, DIBlock);
    DIFunc fn = (DIFunc)dlsym(di, "DeviceIdentityIssueClientCertificateWithCompletion");
    if (!fn) { printf("[BAA] No issuance func!\n"); return; }
 
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    fn(nil, [NSDictionary dictionary], ^(id k, id c, id e) {
        if (e) { NSLog(@"[BAA] Error: %@", e); dispatch_semaphore_signal(sem); return; }
        NSArray *ca = (NSArray *)c;
        if ([ca count] < 2) { dispatch_semaphore_signal(sem); return; }
        baa_private_key = (SecKeyRef)CFBridgingRetain(k);
        CFDataRef d1 = SecCertificateCopyData((__bridge SecCertificateRef)ca[0]);
        baa_leaf_len = (int)CFDataGetLength(d1);
        baa_leaf_der = malloc(baa_leaf_len); memcpy(baa_leaf_der, CFDataGetBytePtr(d1), baa_leaf_len);
        CFRelease(d1);
        CFDataRef d2 = SecCertificateCopyData((__bridge SecCertificateRef)ca[1]);
        baa_inter_len = (int)CFDataGetLength(d2);
        baa_inter_der = malloc(baa_inter_len); memcpy(baa_inter_der, CFDataGetBytePtr(d2), baa_inter_len);
        CFRelease(d2);
        baa_ready = 1;
        printf("[BAA] Leaf=%d Inter=%d — ready\n", baa_leaf_len, baa_inter_len);
        dispatch_semaphore_signal(sem);
    });
    dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 30LL * NSEC_PER_SEC));
}

这里是用的DeviceIdentity/MobileActivation的私有框架完成的。诶你他娘的还真是个天才

这样居然能过iPhone的证书信任链,那AnyGo这个东西我就再也不用付费了嘿嘿嘿。

接下来简单实验了下发现,一般adhoc的二进制过不了amfi,过了之后函数内还有其他的白名单限制。逆向发现这三个进程名字都可以过

  • CarPlay Simulator
  • CarDisplaySim
  • Carplay Sim

于是我们可以简易写一个小注入去绕一下AnyGo的注册登录逻辑,然后把他们的服务端模拟一下,就可以正常修改定位了。并且这个修改在大部分情况下不会被过滤:

过滤的绝大部分是isSimulatedBySoftware,但是这里用的是isProducedByAccessory 。

然后是关于Location Information的Specs:同样是手机侧先请求,后续再发送位置信息。

非常不错啊,但是我懒得处理细节了,于是就有了这个~

AnyGo

What’s More

同时我们可以注意到苹果自带的Additional Tool里有一个Carplay Simulator:它的连接/认证方式是usb。也就是,理论上这个MFi连接是可以从手机到浏览器的,那么接下来下一步就是把做成有线修改Accessory定位了。

检查之后发现这里Carplay Simulator用的是lockdownd的com.apple.carkit.service 并且在这上面封装了iAP2流。