user2308258
user2308258

Reputation:

How to inspect websocket traffic with charlesproxy for iOS simulator/devices

I would like to inspect network traffic going through web sockets, I have no control over the networking code as this is a binary lib for which I do not have the source code, so I cannot do any log/breakpoint in the networking part of the code.

I have tried using the latest version of CharlesProxy which claim to be able to sniff websockets however when I tried the url and apis using websockets were not even mentionned in the list of endpoints called from my iPhone.

Version 3.11 release notes

I have verified that CharlesProxy is configured correctly as I am able to inspect non-websocket traffic even under SSL.

So my question is: did anyone find a solution to inspect traffic going through websockets with CharlesProxy?

Note: I have ATS disabled when using iOS9

Thanks!

Upvotes: 43

Views: 54573

Answers (6)

Noah Tran
Noah Tran

Reputation: 3614

All above answer doesn't work with new APIs for WebSocket: URLSessionWebSocketTask

New Solution for iOS 17/18 with URLSessionWebSocketTask.

Charles Proxy

  1. Make sure you setup Charles Proxy's certificate correctly on your Simulator or devices. It's quite tricky, you can find a lot tutorials on the Internet

  2. Go to Proxy Menu -> Proxy Settings -> Enable SOCKS Proxy at port 8889

  3. On your iOS app, use this code when initializing the URLSession. Make sure to replace your current IP with SOCKS Port

     private static var urlSession: URLSession = {
     let config = URLSessionConfiguration.default
     if #available(iOS 17.0, *) {
         let socksv5Proxy = NWEndpoint.hostPort(host: "192.168.1.9", port: 8889) // Replace with your Proxyman SOCKS Proxy Server IP address
         let proxyConfig = ProxyConfiguration.init(socksv5Proxy: socksv5Proxy)
    
         config.proxyConfigurations = [proxyConfig]
     }
    
     return URLSession(configuration: config, delegate: nil, delegateQueue: nil)}()
    
  4. At this point, Charles Proxy can capture and decrypt WSS.

Proxyman

  1. If you don't know how to setup your Simulator from Charles Proxy, try Proxyman -> Open Certificate Menu -> Install certificate for iOS -> Simulator
  2. Follow the instruction to prepare your Simulator with few clicks

Install Proxyman Certificate to iOS Simulator

  1. On Proxyman, Go to Tools menu -> Proxy Settings -> SOCKS Proxy Setting -> Enable it and save

  2. Use the same code, and replace your current IP and port (You can find the current IP on the top of the Proxyman Toolbar)

     private static var urlSession: URLSession = {
     let config = URLSessionConfiguration.default
     if #available(iOS 17.0, *) {
         let socksv5Proxy = NWEndpoint.hostPort(host: "192.168.1.9", port: 8889) // Replace with your Proxyman SOCKS Proxy Server IP address
         let proxyConfig = ProxyConfiguration.init(socksv5Proxy: socksv5Proxy)
    
         config.proxyConfigurations = [proxyConfig]
     }
    
     return URLSession(configuration: config, delegate: nil, delegateQueue: nil)}()
    
  3. Done, Proxyman will capture and decrypt websocket

Capture websocket from URLSessionWebSocketTask

Sample Code:

https://github.com/ProxymanApp/websocket-example-ios-app/blob/main/WebsocketExample/ContentView.swift

P/S: I'm a creator of Proxyman. I built this app to make it's easier to debug traffic on iOS.

Upvotes: 0

yoAlex5
yoAlex5

Reputation: 34471

CharlesProxy with iOS WebSocket connection

var rd: InputStream
var wr: OutputStream
let dict: NSDictionary = [
    StreamSOCKSProxyConfiguration.hostKey.rawValue : <ip>,
    StreamSOCKSProxyConfiguration.portKey.rawValue : <port>
]
rd.setProperty(dict, forKey: Stream.PropertyKey.socksProxyConfigurationKey)
wr.setProperty(dict, forKey: Stream.PropertyKey.socksProxyConfigurationKey)

or

let proxyDict = CFNetworkCopySystemProxySettings()
let prop = CFDictionaryCreateMutableCopy(nil, 0, proxyDict!.takeRetainedValue())
rd.setProperty(prop, forKey: Stream.PropertyKey(rawValue: kCFStreamPropertySOCKSProxy as String as String))
wr.setProperty(prop, forKey: Stream.PropertyKey(rawValue: kCFStreamPropertySOCKSProxy as String as String))

CharlesProxy setup

Proxy -> Proxy Settings... -> Proxies -> Enable SOCKS Proxy

also you can check/review socket connection using websocket-tester

enter image description here

Please note that it should be used only in debug mode for security reasons and there is can be additional logic(e.g. checking signature...) which prevents intercept a traffic

Upvotes: 0

Jonathan Ellis
Jonathan Ellis

Reputation: 5479

UPDATE JUNE 2022: Whilst socket.io-client-swift has an API to enableSOCKSProxy, it seems Starscream v4 has actually removed the built-in support for SOCKS proxying, so this option doesn't actually do anything!

So it looks like we're back to manually patching Starscream to enable SOCKS proxying.

Just to recap: we've gone from Starscream supporting SOCKS proxy but socket.io-client-swift not having an API to enable it, to now socket.io-client-swift having an API to enable SOCKS proxying, which Starscream no longer supports 🤣🙄.


UPDATE JUNE 2019: Apparently socket.io-client-swift v15.1.0 now properly supports SOCKS proxy. I have not yet tried it, but it would mean that these manual edits to Starscream are no longer required.


The accepted answer does not seem to work with Socket.IO on iOS devices.

The latest version of Socket.IO-Client-Swift (15.0.0 at the time of writing) uses Starscream for WebSockets on iOS/OS X.

The good news is that Starscream supports SOCKS proxying however:

  1. Socket.IO does not expose the Starscream websocket or provide any API for enabling the SOCKS proxying behaviour.

  2. The SOCKS proxying built into Starscream uses the OS SOCKS proxy settings which are cumbersome to setup (at least for iOS).

If I get some time I might propose a PR to address this more thoroughly, but given that it requires work to both Starscream and Socket.IO-Client-Swift, this is not entirely straightforward.

The easiest way to hack around this for temporary debugging purposes (which is the use case for Charles!), is to edit the WebSocket.swift file as part of Starscream, and replace this code:

if enableSOCKSProxy {
    let proxyDict = CFNetworkCopySystemProxySettings()
    let socksConfig = CFDictionaryCreateMutableCopy(nil, 0, proxyDict!.takeRetainedValue())
    let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
    CFWriteStreamSetProperty(outputStream, propertyKey, socksConfig)
    CFReadStreamSetProperty(inputStream, propertyKey, socksConfig)
}

with this code:

let socksConfig = CFDictionaryCreateMutableCopy(nil, 0, CFNetworkCopySystemProxySettings()!.takeRetainedValue()) as! [String: Any]
let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
let ip = socksConfig["HTTPSProxy"]
let proxySocksConfig = ["SOCKSProxy": ip, "SOCKSPort": 8889, "SOCKSEnable": true] as CFDictionary // Where 8889 is the SOCKS proxy port in Charles
CFWriteStreamSetProperty(outputStream, propertyKey, proxySocksConfig)
CFReadStreamSetProperty(inputStream, propertyKey, proxySocksConfig)

This will ensure the SOCKS proxy is enabled by default, and will route all the websockets traffic via Charles.

You then need to ensure that the HTTP proxy settings are configured in iOS (since the same IP will be used for both HTTP and SOCKS), SOCKS proxy is enabled in Charles, and that the port matches the port in the code above (by default 8889).

Upvotes: 13

wang chenyu
wang chenyu

Reputation: 79

Found a workaround solution: After trying, I conclude that although iOS simulators follow system proxy settings for HTTP, WebSocket is not followed. Socks5 proxy settings is not followed, either. However, we can use Reverse Proxies in Charles to force the simulator to use it. In Charles, click Proxy -> Reverse Proxies, set up a local address to be your WebSocket server's transparent proxy, then use that address in your new WebSocket(<address>) (Well in the case of React Native), then you can see your WebSocket connections appear in Charles

Upvotes: 1

Andrew Paul Simmons
Andrew Paul Simmons

Reputation: 4523

Thanks for your very very helpful answer Jonathan Ellis! I'm using Pusher and this worked great!

However, I found socksConfig to not always contain valid data and didn't work or would crash the app when I pulled the IP from there. Since the only thing we are getting from there is the localhost IP I just replaced the following in WebSocket.swift

if enableSOCKSProxy {
    let proxyDict = CFNetworkCopySystemProxySettings()
    let socksConfig = CFDictionaryCreateMutableCopy(nil, 0, proxyDict!.takeRetainedValue())
    let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
    CFWriteStreamSetProperty(outputStream, propertyKey, socksConfig)
    CFReadStreamSetProperty(inputStream, propertyKey, socksConfig)
}

with this:

let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
let proxySocksConfig = ["SOCKSProxy": "127.0.0.1", "SOCKSPort": 8889, "SOCKSEnable": true] as CFDictionary // Where 8889 is the SOCKS proxy port in Charles
CFWriteStreamSetProperty(outputStream, propertyKey, proxySocksConfig)
CFReadStreamSetProperty(inputStream, propertyKey, proxySocksConfig)

And then enabled the socks proxy in Charles as you described.

Thanks Again!

Upvotes: 3

klimat
klimat

Reputation: 25011

I finally found the answer.

Charles 3.11.2 works perfectly with WebSocket.

I use socketIO, so I've already seen http requests sent during the negotiation phase, but I missed websockets traffic.

In the beginning, socketIO try to use polling then switches to use websockets.

The websocket traffic is visible when you go to the request with status: "Sending request body" which is actually wss:// request.

You even have a dedicated tab for this kind of traffic. The rest of messages will appear right there.

enter image description here

PS1. Ensure you're connected to socket properly then it appears in Charles.
PS2. I suggest using socketIO it's a great enhancement for full-duplex traffic like websockets.

Upvotes: 26

Related Questions