Skip to content

MASTG-DEMO-0110: URLSession Minimum TLS Version Lowered in Code

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

Sample

The code sample sets tlsMinimumSupportedProtocolVersion on a URLSessionConfiguration to .TLSv10, which requests that URLSession connections accept TLS 1.0. Even though ATS would block such a connection at runtime unless an explicit Info.plist exception is also present, using this API with a deprecated TLS version is itself a bad practice and should be flagged.

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
import Foundation
import Network

struct MastgTest {
    // SUMMARY: This sample demonstrates an attempt to use TLS 1.0 endpoints in iOS apps.
    // However, the connection will fail because App Transport Security (ATS) requires TLS 1.2 or later by default.
    // This test shows that URLSession's TLS settings do not override ATS requirements, and that explicit exceptions in Info.plist are needed to allow older TLS versions.

    static let tls10Endpoint = "https://tls-v1-0.badssl.com:1010/"

    static func mastgTest(completion: @escaping (String) -> Void) {
        var result = "Testing TLS 1.0 URL connections:\n\n"

        guard let url = URL(string: tls10Endpoint) else {
            completion(result + "Invalid URL: \(tls10Endpoint)\n")
            return
        }

        let configuration = URLSessionConfiguration.ephemeral
        configuration.tlsMinimumSupportedProtocolVersion = .TLSv10

        let session = URLSession(configuration: configuration)

        let task = session.dataTask(with: url) { _, response, error in
            if let error = error as NSError? {
                result += "HTTP request to \(tls10Endpoint) failed:\n"
                result += "Domain: \(error.domain)\n"
                result += "Code: \(error.code)\n"
                result += "Description: \(error.localizedDescription)\n\n"
                result += "This is expected if ATS is not relaxed in Info.plist.\n"
                result += "URLSession TLS settings do not replace ATS exceptions.\n"
            } else if let httpResponse = response as? HTTPURLResponse {
                result += "HTTP request to \(tls10Endpoint) returned status: \(httpResponse.statusCode)\n"
            } else {
                result += "HTTP request to \(tls10Endpoint) completed without HTTP response.\n"
            }

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

        task.resume()
    }
}

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 the tlsMinimumSupportedProtocolVersion setter in the binary.
urlsession_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
35
36
37
38
39
40
e asm.bytes=false
e scr.color=false
e asm.var=false

?e References to URLSessionConfiguration tlsMinimumSupportedProtocolVersion setter:
f~setTLSMinimumSupportedProtocol

?e

?e xrefs to tlsMinimumSupportedProtocolVersion setter:
axt @ 0x100010180

?e

?e Code setting tlsMinimumSupportedProtocolVersion:
pd-- 20 @ 0x1000091a4


?e 

?e Search for ARM64 instructions that load the TLS protocol constants used by tlsMinimumSupportedProtocolVersion.

?e The TLS constants are 0x0301 for TLS 1.0, 0x0302 for TLS 1.1, 0x0303 for TLS 1.2, and 0x0304 for TLS 1.3.

?e In this binary, the value is passed as the first argument to the Objective C setter in register w2. For example, mov w2, 0x301 loads TLS 1.0.

?e The instruction mov w2, 0x301 is encoded as the ARM64 word 0x52806022. Because the binary stores instructions in little endian order, the bytes appear as 22 60 80 52, which r2 searches as 22608052.

?e Search for the encoded mov w2 instructions for each TLS version.

/x 22608052
/x 42608052
/x 62608052
/x 82608052

?e

?e Print the surrounding instructions

pd-- 10 @ 0x100004194

The r2 script works in three stages:

  • First, it looks up the symbol name and cross-references for setTLSMinimumSupportedProtocolVersion: to confirm the setter is present and to find the stub function used to dispatch it.
  • Second, it disassembles the stub (fcn.1000091a0) to show the Objective-C message dispatch pattern.
  • Third, it searches the binary for ARM64 mov w2 instructions that load each of the known TLS protocol constants immediately before the setter call. On ARM64, w2 carries the first argument in an Objective-C message send. The TLS constants are 0x0301 (TLS 1.0), 0x0302 (TLS 1.1), 0x0303 (TLS 1.2), and 0x0304 (TLS 1.3). Because each constant produces a fixed 4-byte instruction encoding (for example, mov w2, 0x301 encodes as 22 60 80 52 in little-endian), the script searches for those byte sequences directly and then disassembles the surrounding instructions.
run.sh
1
2
#!/bin/bash
r2 -q -i urlsession_tls.r2 -A MASTestApp > output.asm

Observation

The output shows the setter symbol, its cross-reference, the 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
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
References to URLSessionConfiguration tlsMinimumSupportedProtocolVersion setter:
0x10000a772 39 str.setTLSMinimumSupportedProtocolVersion:
0x100010180 8 reloc.fixup.setTLSMinimumSupportedProtocolV

xrefs to tlsMinimumSupportedProtocolVersion setter:
fcn.1000091a0 0x1000091a4 [DATA:r--] ldr x1, reloc.fixup.setTLSMinimumSupportedProtocolV

Code setting tlsMinimumSupportedProtocolVersion:
           0x100009154      brk 1
            0x100009158      brk 1
            0x10000915c      brk 1
            ; CALL XREF from sym.func.100004000 @ 0x10000426c(x)
 24: fcn.100009160 ();
           0x100009160      adrp x1, segment.__DATA                   ; 0x100010000
           0x100009164      ldr x1, [x1, 0x170]                       ; [0x100010170:4]=0xa751 ; "Q\xa7"
           0x100009168      adrp x16, segment.__DATA_CONST            ; 0x10000c000
           0x10000916c      ldr x16, [x16, 0xc0]                      ; [0x10000c0c0:4]=22
                                                                      ; reloc.objc_msgSend
           0x100009170      br x16
           0x100009174      brk 1
            0x100009178      brk 1
            0x10000917c      brk 1
            ; CALL XREF from sym.func.100004000 @ 0x1000041ac(x)
 24: fcn.100009180 ();
           0x100009180      adrp x1, segment.__DATA                   ; 0x100010000
           0x100009184      ldr x1, [x1, 0x178]                       ; [0x100010178:4]=0xa758 ; "X\xa7"
           0x100009188      adrp x16, segment.__DATA_CONST            ; 0x10000c000
           0x10000918c      ldr x16, [x16, 0xc0]                      ; [0x10000c0c0:4]=22
                                                                      ; reloc.objc_msgSend
           0x100009190      br x16
           0x100009194      brk 1
            0x100009198      brk 1
            0x10000919c      brk 1
            ; CALL XREF from sym.func.100004000 @ 0x100004198(x)
 24: fcn.1000091a0 ();
           0x1000091a0      adrp x1, segment.__DATA                   ; 0x100010000
           0x1000091a4      ldr x1, [x1, 0x180]                       ; [0x100010180:4]=0xa772 ; "r\xa7"
           0x1000091a8      adrp x16, segment.__DATA_CONST            ; 0x10000c000
           0x1000091ac      ldr x16, [x16, 0xc0]                      ; [0x10000c0c0:4]=22
                                                                      ; reloc.objc_msgSend
           0x1000091b0      br x16
           0x1000091b4      brk 1
            0x1000091b8      brk 1
            0x1000091bc      brk 1
            ; CALL XREF from sym.func.100004370 @ 0x100004700(x)
 24: fcn.1000091c0 ();
           0x1000091c0      adrp x1, segment.__DATA                   ; 0x100010000
           0x1000091c4      ldr x1, [x1, 0x188]                       ; [0x100010188:4]=0xa799 ; reloc.fixup.statusCode
           0x1000091c8      adrp x16, segment.__DATA_CONST            ; 0x10000c000
           0x1000091cc      ldr x16, [x16, 0xc0]                      ; [0x10000c0c0:4]=22
                                                                      ; reloc.objc_msgSend
           0x1000091d0      br x16
           0x1000091d4      brk 1
            0x1000091d8      brk 1
            0x1000091dc      brk 1
            ;-- section.3.__TEXT.__const:
            ; NULL XREF from segment.__TEXT @ +0x1c0(r)
            ; DATA XREF from sym.func.100004000 @ 0x1000041fc(r)
            ; DATA XREF from sym.func.100004370 @ 0x10000486c(r)
            0x1000091e0      .qword 0x0000000042000000                 ; [03] -r-x section size 1236 named 3.__TEXT.__const
            ; DATA XREF from sym.func.100004000 @ 0x10000403c(r)
            ; DATA XREF from sym.func.100004328 @ 0x100004344(r)
            0x1000091e8      .qword 0x00000007000004cc
            ; DATA XREF from sym.func.100004370 @ 0x1000048d8(r)
            ; DATA XREF from sym.func.100004c20 @ 0x100004c44(r)
            0x1000091f0      .qword 0x00000009000004e0
            0x1000091f8      .qword 0x0000000000000003
            ; DATA XREF from sym.func.100005500 @ 0x10000581c(r)
            0x100009200      .qword 0x0000000000000002

Search for ARM64 instructions that load the TLS protocol constants used by tlsMinimumSupportedProtocolVersion.
The TLS constants are 0x0301 for TLS 1.0, 0x0302 for TLS 1.1, 0x0303 for TLS 1.2, and 0x0304 for TLS 1.3.
In this binary, the value is passed as the first argument to the Objective C setter in register w2. For example, mov w2, 0x301 loads TLS 1.0.
The instruction mov w2, 0x301 is encoded as the ARM64 word 0x52806022. Because the binary stores instructions in little endian order, the bytes appear as 22 60 80 52, which r2 searches as 22608052.
Search for the encoded mov w2 instructions for each TLS version.
0x100004194 hit4_0 22608052
Print the surrounding instructions
           0x10000416c      mov x1, x24
           0x100004170      mov x2, x22
           0x100004174      blr x8
           0x100004178      adrp x8, segment.__DATA_CONST             ; 0x10000c000
           0x10000417c      ldr x0, [x8, 0xb28]                       ; [0x10000cb28:4]=246
                                                                      ; reloc.NSURLSessionConfiguration
                                                                      [23] -rw- section size 40 named 23.__DATA_CONST.__objc_classrefs ; void *arg0
           0x100004180      bl sym.imp.objc_opt_self                  ; void *objc_opt_self(void *arg0)
           0x100004184      bl fcn.100009120
           0x100004188      mov x29, x29
           0x10000418c      bl sym.imp.objc_retainAutoreleasedReturnValue ; void objc_retainAutoreleasedReturnValue(void *instance)
           0x100004190      mov x24, x0
           ;-- _0:
           0x100004194      mov w2, 0x301
           0x100004198      bl fcn.1000091a0
           0x10000419c      adrp x8, segment.__DATA_CONST             ; 0x10000c000
           0x1000041a0      ldr x0, [x8, 0xb30]                       ; [0x10000cb30:4]=247
                                                                      ; reloc.NSURLSession ; void *arg0
           0x1000041a4      bl sym.imp.objc_opt_self                  ; void *objc_opt_self(void *arg0)
           0x1000041a8      mov x2, x24
           0x1000041ac      bl fcn.100009180
           0x1000041b0      mov x29, x29
           0x1000041b4      bl sym.imp.objc_retainAutoreleasedReturnValue ; void objc_retainAutoreleasedReturnValue(void *instance)
           0x1000041b8      mov x25, x0

Evaluation

The test case fails because the binary calls setTLSMinimumSupportedProtocolVersion: with the value 0x0301, which corresponds to TLS 1.0.

The relevant sequence in the output is:

0x10000417c      ldr x0, [x8, 0xb28]        ; NSURLSessionConfiguration class
0x100004180      bl sym.imp.objc_opt_self
0x100004184      bl fcn.100009120            ; ephemeral configuration factory
0x10000418c      bl sym.imp.objc_retainAutoreleasedReturnValue
0x100004190      mov x24, x0                 ; retain configuration object

0x100004194      mov w2, 0x301              ; first argument: TLS 1.0
0x100004198      bl fcn.1000091a0           ; setTLSMinimumSupportedProtocolVersion:

0x1000041a8      mov x2, x24               ; pass configuration object
0x1000041ac      bl fcn.100009180           ; URLSession factory

On ARM64, Objective-C message sends follow the convention x0 = receiver, x1 = selector, x2 = first argument. fcn.1000091a0 loads the setTLSMinimumSupportedProtocolVersion: selector into x1 and jumps to objc_msgSend. The instruction at 0x100004194 loads w2 with 0x301 immediately before that call, making this equivalent to:

configuration.tlsMinimumSupportedProtocolVersion = .TLSv10

The subsequent call at 0x1000041ac passes the configured NSURLSessionConfiguration object to a NSURLSession factory method, confirming the session is created with this TLS setting.

Although ATS would block the connection at runtime unless a matching Info.plist exception is also present, explicitly setting tlsMinimumSupportedProtocolVersion to a deprecated TLS version is a bad practice and must be flagged regardless of whether the connection would succeed.