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