Configuring iOS WiFi and Proxy with Frida

About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.

This research is published for educational and defensive purposes. The target sample is anonymized throughout.


Configuring iOS WiFi and Proxy with Frida

While analyzing an app I looked into how to attach a proxy to the iOS system WiFi, and I’ve turned the pieces into reusable functions for debugging.

The functions

1. Toggle airplane mode

function toggleAirplaneMode(mode) {
    var p = ObjC.classes.RadiosPreferences.alloc().init();
    p.setAirplaneMode_(mode)
    p.synchronize()
    p.release();
}

2. Toggle WiFi

function toggleWifiMode(mode) {
    var wfc = ObjC.classes.WFClient.sharedInstance();
    wfc.setPowered_(mode);
}

3. Read saved WiFi passwords

function readWifiPasswords() {
    var knownNet = ObjC.classes.WFKnownNetworkStore.sharedInstance();
    var nets = knownNet.knownNetworks();
    var objs = nets.allObjects();
    var count = objs.count();

    var result = []
    for (var n = 0; n < count; n++) {
        var networkProfile = objs.objectAtIndex_(n);

        result.push(
            {
                HS20AccountName: networkProfile.HS20AccountName(),
                HS20Badge: networkProfile.HS20Badge(),
                isHS20Network: networkProfile.isHS20Network(),
                isHS20NetworkProvisioned: networkProfile.isHS20NetworkProvisioned(),
                TLSIdentity: networkProfile.TLSIdentity()+'',
                addedDate: networkProfile.addedDate()+'',
                adhoc: networkProfile.isAdhoc(),
                autoJoinEnabled: networkProfile.isAutoJoinEnabled(),
                autoLoginEnabled: networkProfile.isAutoLoginEnabled(),
                bssid: networkProfile.bssid()+'',
                canExposeIMSI: networkProfile.canExposeIMSI(),
                captive: networkProfile.isCaptive(),
                carPlay: networkProfile.isCarPlay(),
                carPlayType: networkProfile.carPlayType(),
                carPlayUUID: networkProfile.carPlayUUID(),
                carrierBased: networkProfile.isCarrierBased(),
                certificateChain: networkProfile.certificateChain(),
                enterpriseProfile: networkProfile.enterpriseProfile(),
                fetchedPassword: networkProfile.fetchedPassword(),
                hidden: networkProfile.isHidden(),
                lastAutoJoinDate: networkProfile.lastAutoJoinDate()+'',
                managed: networkProfile.isManaged(),
                originatorBundleIdentifier: networkProfile.originatorBundleIdentifier(),
                password: networkProfile.password()+'',
                policyUUID: networkProfile.policyUUID(),
                previousPassword: networkProfile.previousPassword(),
                requiresPassword: networkProfile.requiresPassword(),
                scanAttributes: valueOf(networkProfile.scanAttributes()),
                securityMode: networkProfile.securityMode()+'',
                ssid: networkProfile.ssid()+'',
                username: networkProfile.username()+''
            }
        );
    }
    return result;

}

4. Get the current WiFi SSID

function currentWifiSSID() {
    return currentNetwork().ssid();
}

5. Get the current WiFi Network instance

function currentNetwork() {
    return ObjC.classes.WFClient.sharedInstance().interface().currentNetwork();
}

6. Read the current WiFi configuration

function readWifiSettings(_ssid) {
    // Read the configuration
    var WFGetSettingsOperation = ObjC.classes.WFGetSettingsOperation.alloc();
    //10684 ms  -[WFGetSettingsOperation initWithSSID:xxxWiFiSSID]
    var wfGetSet = WFGetSettingsOperation.initWithSSID_(_ssid);
    /* TID 0x23c2f */
    //10732 ms  -[WFGetSettingsOperation start]
    wfGetSet.start();
    //10737 ms     | -[WFGetSettingsOperation ssid]
    //10740 ms     | -[WFGetSettingsOperation keychainQueue]
    /* TID 0x303 */
    //10759 ms  -[WFGetSettingsOperation settings]
    //10759 ms  -[WFGetSettingsOperation dealloc]
    //10759 ms     | -[WFGetSettingsOperation .cxx_destruct]
    console.log('WIFI-SSID', wfGetSet.ssid());
    console.log('keychainQueue', wfGetSet.keychainQueue());
    var settings = wfGetSet.settings();
    return settings;//__NSArrayM
}

7. Write the WiFi configuration back to the system

function writeWifiSettings(_ssid, settings) {
    // Write the configuration
    var WFSaveSettingsOperation = ObjC.classes.WFSaveSettingsOperation.alloc()
    // /* TID 0x303 */
    // 6803 ms  -[WFSaveSettingsOperation initWithSSID:0x2822acdc0 settings:0x282c15dd0]
    var wfs = WFSaveSettingsOperation.initWithSSID_settings_(_ssid, settings);
    // 6806 ms  -[WFSaveSettingsOperation setCurrentNetwork:0x1]
    wfs.setCurrentNetwork_(1); // YES(0x1)
    // /* TID 0x1307 */
    // 6807 ms  -[WFSaveSettingsOperation start]
    wfs.start();

    console.log('new-settings', readWifiSettings(_ssid));
}

8. Print the WiFi configuration

function printWifiSettings(settings) {
    var count = settings.count();
    for (var n = 0; n < count; n++) {
        var o = settings.objectAtIndex_(n);
        console.log(o.handle, o.$className, valueOf(o));
    }
}

9. Set the system WiFi proxy

function setWifiProxy(_ssid, server, port, username, password) {
    const { NSString, NSNumber } = ObjC.classes

    var ps = ObjC.classes.WFSettingsProxy.alloc().initWithDictionary_(ObjC.classes.WFSettingsProxy.defaultProxyConfiguration());
    var items = ObjC.classes.NSMutableDictionary.alloc().initWithDictionary_(ps.items())
    items.setObject_forKey_(NSNumber.numberWithInt_(1), NSString.stringWithString_('HTTPEnable'));
    items.setObject_forKey_(NSNumber.numberWithInt_(port), NSString.stringWithString_('HTTPPort'));
    items.setObject_forKey_(NSString.stringWithString_(server), NSString.stringWithString_('HTTPProxy'));
    items.setObject_forKey_(NSNumber.numberWithInt_(1), NSString.stringWithString_('HTTPProxyAuthenticated'));
    items.setObject_forKey_(NSString.stringWithString_(username), NSString.stringWithString_('HTTPProxyUsername'));
    items.setObject_forKey_(NSNumber.numberWithInt_(1), NSString.stringWithString_('HTTPSEnable'));
    items.setObject_forKey_(NSNumber.numberWithInt_(port), NSString.stringWithString_('HTTPSPort'));
    items.setObject_forKey_(NSString.stringWithString_(server), NSString.stringWithString_('HTTPSProxy'));

    var wifiProxySet = ObjC.classes.WFSettingsProxy.alloc().initWithDictionary_(items);
    wifiProxySet.setPassword_(password);
    var settings = ObjC.classes.NSMutableArray.alloc().init();
    settings.addObject_(wifiProxySet);

    writeWifiSettings(_ssid, settings);

}

10. Helpers

///////////////
// Helper functions
///////////////

function typeOf(raw) {
    try {
        var o = new ObjC.Object(ptr(raw));
        return o.$className
    } catch {
        return ''
    }
}

function valueOf(raw) {
    try {
        var o = new ObjC.Object(ptr(raw));
        return o + ''
    } catch {
        return ''
    }
}

Testing

Inject Frida into the SpringBoard process, and make sure WiFiKit.framework is loaded. Test code:

// Load the WiFiKit framework
Module.load('/System/Library/PrivateFrameworks/WiFiKit.framework/WiFiKit'); 
// WiFi network name
var ssid = 'MyWiFi-123';
setWifiProxy(ssid, "proxy-ip",8080,"proxy-user","password");

Frida command:

frida -U -n SpringBoard -l ./wifi.js

That’s it.


Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).

Get in touch