Skip to content

MASTG-DEMO-0111: Network.framework TLS Minimum Version Lowered via sec_protocol_options

Download MASTG-DEMO-0111 IPA Open MASTG-DEMO-0111 Folder Build MASTG-DEMO-0111 IPA

Sample

The code sample uses NWProtocolTLS.Options with sec_protocol_options_set_min_tls_protocol_version to set the minimum TLS version to TLS 1.0 for a Network.framework connection. It also sets the maximum TLS version to TLS 1.0, constraining the connection to TLS 1.0 only. Because ATS doesn't apply to Network.framework, this configuration is not mitigated by any ATS policy and the connection will succeed against a TLS 1.0 server.

MastgTest.swift
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import Foundation
import Network

struct MastgTest {
    // SUMMARY: This sample demonstrates NWProtocolTLS.Options configured with an insecure minimum TLS version, bypassing ATS entirely.

    static func mastgTest(completion: @escaping (String) -> Void) {
        var result = "Testing Network.framework TLS configuration:\n\n"

        let host = NWEndpoint.Host("tls-v1-0.badssl.com")
        let port = NWEndpoint.Port(rawValue: 1010)!

        // FAIL: Minimum TLS version set to TLS 1.0 for a Network.framework connection.
        let tlsOptions = NWProtocolTLS.Options()
        sec_protocol_options_set_min_tls_protocol_version(
            tlsOptions.securityProtocolOptions,
            .TLSv10
        )

        // Optional, but useful for the demo because this endpoint only supports TLS 1.0.
        sec_protocol_options_set_max_tls_protocol_version(
            tlsOptions.securityProtocolOptions,
            .TLSv10
        )

        // Make the TLS server name explicit.
        sec_protocol_options_set_tls_server_name(
            tlsOptions.securityProtocolOptions,
            "tls-v1-0.badssl.com"
        )

        let parameters = NWParameters(tls: tlsOptions)
        let connection = NWConnection(host: host, port: port, using: parameters)

        var didComplete = false

        func finish(_ text: String) {
            guard !didComplete else { return }
            didComplete = true
            connection.cancel()

            DispatchQueue.main.async {
                completion(text)
            }
        }

        connection.stateUpdateHandler = { state in
            switch state {
            case .ready:
                result += "TLS connection established.\n"
                result += "Host: tls-v1-0.badssl.com\n"
                result += "Port: 1010\n"
                result += "Minimum TLS version: TLS 1.0\n\n"

                let request =
                    """
                    GET / HTTP/1.1\r
                    Host: tls-v1-0.badssl.com\r
                    Connection: close\r
                    \r

                    """

                connection.send(
                    content: request.data(using: .utf8),
                    completion: .contentProcessed { sendError in
                        if let sendError = sendError {
                            finish(result + "HTTP request send failed: \(sendError)\n")
                            return
                        }

                        receiveResponse(
                            connection: connection,
                            result: result,
                            finish: finish
                        )
                    }
                )

            case .failed(let error):
                finish(result + "Connection failed: \(error)\n")

            case .cancelled:
                break

            default:
                break
            }
        }

        connection.start(queue: .main)
    }

    private static func receiveResponse(
        connection: NWConnection,
        result: String,
        finish: @escaping (String) -> Void
    ) {
        var mutableResult = result

        connection.receive(minimumIncompleteLength: 1, maximumLength: 4096) { data, _, isComplete, error in
            if let data = data, !data.isEmpty {
                let text = String(data: data, encoding: .utf8) ?? "<non UTF8 response data>"
                mutableResult += "Received response:\n"
                mutableResult += text
                mutableResult += "\n"
            }

            if let error = error {
                finish(mutableResult + "Receive failed: \(error)\n")
                return
            }

            if isComplete {
                finish(mutableResult + "\nConnection closed by server.\n")
                return
            }

            receiveResponse(
                connection: connection,
                result: mutableResult,
                finish: finish
            )
        }
    }
}

Steps

  1. Unzip the app package and locate the main binary file ( Exploring the App Package), which in this case is ./Payload/MASTestApp.app/MASTestApp.
  2. Run radare2 (iOS) with the script to search for calls to sec_protocol_options_set_min_tls_protocol_version in the binary.
nw_tls.r2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
e asm.bytes=false
e scr.color=false
e asm.var=false

?e References to sec_protocol_options_set_min_tls_protocol_version:
f~sec_protocol_options_set_min_tls_protocol_version

?e

?e xrefs to sec_protocol_options_set_min_tls_protocol_version:
axt @ 0x10000c240

?e

?e Import stub for sec_protocol_options_set_min_tls_protocol_version:
pd-- 10 @ 0x1000099e8

?e

?e Search for ARM64 instructions that load TLS constants into w1:
?e 0x0301, TLS 1.0
?e 0x0302, TLS 1.1
?e 0x0303, TLS 1.2
?e 0x0304, TLS 1.3

/x 21608052
/x 41608052
/x 61608052
/x 81608052

?e

?e Print the surrounding instructions for the mov w1, 0x301 instruction that sets TLS 1.0:
pd-- 10 @ 0x1000041d4

The r2 script works in three stages:

  • First, it looks up the symbol and cross-references for sec_protocol_options_set_min_tls_protocol_version to confirm the function is imported and to find where it is called.
  • Second, it disassembles the import stub to show the indirect call structure.
  • Third, it searches the binary for ARM64 mov w1 instructions that load each of the known TLS protocol constants immediately before the function call. On ARM64, w1 holds the second argument of a C function call (the first being the sec_protocol_options_t handle in x0). The TLS constants are 0x0301 (TLS 1.0), 0x0302 (TLS 1.1), 0x0303 (TLS 1.2), and 0x0304 (TLS 1.3). Each produces a fixed 4-byte encoding, so the script searches for those byte sequences directly, then disassembles the surrounding instructions.
run.sh
1
2
#!/bin/bash
r2 -q -i nw_tls.r2 -A MASTestApp > output.asm

Observation

The output shows the imported symbol, its cross-references, the import stub, and the byte-pattern search results:

output.asm
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
References to sec_protocol_options_set_min_tls_protocol_version:
0x1000099e4 12 sym.imp.sec_protocol_options_set_min_tls_protocol_version
0x10000c240 8 reloc.sec_protocol_options_set_min_tls_protocol_version

xrefs to sec_protocol_options_set_min_tls_protocol_version:
sym.imp.sec_protocol_options_set_min_tls_protocol_version 0x1000099e8 [DATA:r--] ldr x16, reloc.sec_protocol_options_set_min_tls_protocol_version

Import stub for sec_protocol_options_set_min_tls_protocol_version:
            ; CALL XREF from sym.func.100005b50 @ 0x100005d78(x)
 12: void sym.imp.objc_retainAutoreleasedReturnValue (void *instance);
           0x1000099c0      adrp x16, segment.__DATA_CONST            ; 0x10000c000
           0x1000099c4      ldr x16, [x16, 0xb0]                      ; [0x10000c0b0:4]=20
                                                                      ; reloc.objc_retainAutoreleasedReturnValue
           0x1000099c8      br x16
            ; CALL XREF from sym.func.100009068 @ 0x1000091f0(x)
 12: void sym.imp.rewind (FILE *stream);
           0x1000099cc      adrp x16, segment.__DATA_CONST            ; 0x10000c000
           0x1000099d0      ldr x16, [x16, 0x138]                     ; [0x10000c138:4]=37 ; "%"
           0x1000099d4      br x16
            ; CALL XREF from sym.func.100004000 @ 0x1000041f4(x)
 12: sym.imp.sec_protocol_options_set_max_tls_protocol_version ();
           0x1000099d8      adrp x16, segment.__DATA_CONST            ; 0x10000c000
           0x1000099dc      ldr x16, [x16, 0x238]                     ; [0x10000c238:4]=69 ; "E"
           0x1000099e0      br x16
            ; CALL XREF from sym.func.100004000 @ 0x1000041d8(x)
 12: sym.imp.sec_protocol_options_set_min_tls_protocol_version ();
           0x1000099e4      adrp x16, segment.__DATA_CONST            ; 0x10000c000
           0x1000099e8      ldr x16, [x16, 0x240]                     ; [0x10000c240:4]=70 ; "F"
           0x1000099ec      br x16
            ; CALL XREF from sym.func.100004000 @ 0x100004214(x)
 12: sym.imp.sec_protocol_options_set_tls_server_name ();
           0x1000099f0      adrp x16, segment.__DATA_CONST            ; 0x10000c000
           0x1000099f4      ldr x16, [x16, 0x248]                     ; [0x10000c248:4]=71 ; "G"
           0x1000099f8      br x16
            ; CALL XREF from sym.func.100009068 @ 0x100009314(x)
 12: int sym.imp.sscanf (const char *s, const char *format,   ...);
           0x1000099fc      adrp x16, segment.__DATA_CONST            ; 0x10000c000
           0x100009a00      ldr x16, [x16, 0x140]                     ; [0x10000c140:4]=38 ; "&"
           0x100009a04      br x16
            ; XREFS: CALL 0x100004120  CALL 0x1000041b8  CALL 0x10000423c  CALL 0x1000042c8  CALL 0x1000042e4  
            ; XREFS: CALL 0x100004564  CALL 0x100004a30  CALL 0x100004d10  CALL 0x100004d30  CALL 0x100004d4c  
            ; XREFS: CALL 0x100005444  CALL 0x100005460  CALL 0x1000061c8  CALL 0x100006240  CALL 0x100006548  
            ; XREFS: CALL 0x10000673c  
 12: sym.imp.swift_allocObject ();
           0x100009a08      adrp x16, segment.__DATA_CONST            ; 0x10000c000
           0x100009a0c      ldr x16, [x16, 0x608]                     ; [0x10000c608:4]=191
                                                                      ; reloc.swift_allocObject

Search for ARM64 instructions that load TLS constants into w1:
0x0301, TLS 1.0
0x0302, TLS 1.1
0x0303, TLS 1.2
0x0304, TLS 1.3
0x1000041d4 hit4_0 21608052
0x1000041f0 hit4_1 21608052

Print the surrounding instructions for the mov w1, 0x301 instruction that sets TLS 1.0:
           0x1000041ac      bl sym.imp.Network.NWProtocolTLS.Options.allocator..metadata.accessor_...a_ ; Network.NWProtocolTLS.Options.allocator..metadata.accessor(...a)
           0x1000041b0      ldr w1, [x0, 0x30]
           0x1000041b4      ldrh w2, [x0, 0x34]
           0x1000041b8      bl sym.imp.swift_allocObject
           0x1000041bc      mov x20, x0
           0x1000041c0      bl sym.imp.Network.NWProtocolTLS.Options.allocator.bool:_allocator__Options.bool:_allocator__C._...cfc_ ; Network.NWProtocolTLS.Options.allocator.bool: allocator, Options.bool: allocator, C.(...cfc)
           0x1000041c4      mov x26, x0
           0x1000041c8      mov x20, x0
           0x1000041cc      bl sym.imp.Network.NWProtocolTLS.Options.allocator.securityProtocol_...vgTj_ ; Network.NWProtocolTLS.Options.allocator.securityProtocol(...vgTj)
           0x1000041d0      mov x20, x0
           ;-- _0:
           0x1000041d4      mov w1, 0x301
           0x1000041d8      bl sym.imp.sec_protocol_options_set_min_tls_protocol_version
           0x1000041dc      mov x0, x20
           0x1000041e0      bl sym.imp.swift_unknownObjectRelease
           0x1000041e4      mov x20, x26
           0x1000041e8      bl sym.imp.Network.NWProtocolTLS.Options.allocator.securityProtocol_...vgTj_ ; Network.NWProtocolTLS.Options.allocator.securityProtocol(...vgTj)
           0x1000041ec      mov x20, x0
           ;-- _1:
           0x1000041f0      mov w1, 0x301
           0x1000041f4      bl sym.imp.sec_protocol_options_set_max_tls_protocol_version
           0x1000041f8      mov x0, x20

Evaluation

The test case fails because the binary calls both sec_protocol_options_set_min_tls_protocol_version and sec_protocol_options_set_max_tls_protocol_version with 0x0301 loaded in w1, which corresponds to TLS 1.0.

The relevant sequence in the output is:

0x1000041cc      bl sym.imp.Network.NWProtocolTLS.Options.allocator.securityProtocol_...vgTj_
0x1000041d0      mov x20, x0                ; sec_protocol_options_t handle

0x1000041d4      mov w1, 0x301              ; second argument: TLS 1.0
0x1000041d8      bl sym.imp.sec_protocol_options_set_min_tls_protocol_version

0x1000041e8      bl sym.imp.Network.NWProtocolTLS.Options.allocator.securityProtocol_...vgTj_
0x1000041ec      mov x20, x0                ; sec_protocol_options_t handle

0x1000041f0      mov w1, 0x301              ; second argument: TLS 1.0
0x1000041f4      bl sym.imp.sec_protocol_options_set_max_tls_protocol_version

On ARM64, C function arguments are passed in x0, x1, x2, ... (or their 32-bit aliases w0, w1, w2, ...). Here x0 carries the sec_protocol_options_t handle retrieved from NWProtocolTLS.Options.securityProtocolOptions, and w1 carries the TLS version constant. The value 0x0301 corresponds to tls_protocol_version_TLSv10 (TLS 1.0).

The two calls are equivalent to:

sec_protocol_options_set_min_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv10)
sec_protocol_options_set_max_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv10)

This pins the connection to TLS 1.0 only. Because Network.framework operates entirely outside of ATS, this configuration is not subject to any ATS enforcement and the connection will succeed against a TLS 1.0 server.