Skip to content

MASTG-DEMO-0073: Uses of Insecure Random Number Generation with r2

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

Sample

The following sample demonstrates various methods of generating random tokens, and contrasts insecure and secure approaches. It includes:

  • Insecure methods using libc rand, srand, and drand48.
  • Other secure methods such as direct reads from /dev/random, arc4random, arc4random_uniform, SystemRandomNumberGenerator, and CCRandomGenerateBytes.
  • A preferred secure method using SecRandomCopyBytes.

Note

rand and srand are not part of the Swift standard library. In this demo, we call the libc rand and srand symbols via our own bindings.

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import Foundation
import Security
import Darwin
import CommonCrypto

// Unsafe bindings to libc srand and rand
@_silgen_name("srand")
func c_srand(_ seed: UInt32)

@_silgen_name("rand")
func c_rand() -> Int32

struct MastgTest {

    // Insecure: libc rand seeded with time, predictable and not suitable for cryptography
    static func generateRandomTokenRand() -> String {
        var token = ""

        for _ in 0..<16 {
            let value = c_rand() % 256
            token += String(format: "%02x", value)
        }
        return token
    }

    // Cryptographically secure on Apple platforms
    // Swift random APIs use SystemRandomNumberGenerator backed by the system CSPRNG via arc4random_buf
    // Shown here as a secure source that is not a dedicated crypto token API
    static func generateRandomTokenSwiftRandom() -> String {
        var token = ""
        for _ in 0..<16 {
            let b = UInt8.random(in: 0...255)
            token += String(format: "%02x", b)
        }
        return token
    }

    // Cryptographically secure: direct read from /dev/random on Apple platforms
    // However, this is a low level interface and is discouraged in favor of SecRandomCopyBytes
    static func generateRandomTokenDevRandom() -> String {
        let count = 16

        let fd = open("/dev/random", O_RDONLY)
        if fd < 0 {
            return "Error opening /dev/random"
        }

        var buffer = [UInt8](repeating: 0, count: count)
        let readCount = read(fd, &buffer, count)
        close(fd)

        if readCount != count {
            return "Error reading /dev/random"
        }

        return buffer.map { String(format: "%02x", $0) }.joined()
    }

    // Cryptographically secure but discouraged as a direct token API in Swift code 
    // because uses legacy C style interfaces that are easier to misuse
    // On Apple platforms arc4random_uniform is strong, but SecRandomCopyBytes or CryptoKit are preferred
    static func generateRandomTokenArc4RandomUniform() -> String {
        var token = ""
        for _ in 0..<16 {
            let value = arc4random_uniform(256)
            token += String(format: "%02x", value)
        }
        return token
    }

    // Cryptographically secure but discouraged as a direct token API
    // On Apple platforms arc4random is strong, but it is not the recommended crypto API
    static func generateRandomTokenArc4Random() -> String {
        var token = ""
        for _ in 0..<16 {
            let value = arc4random() % 256
            token += String(format: "%02x", value)
        }
        return token
    }

    // Cryptographically secure: SystemRandomNumberGenerator uses the system CSPRNG
    // It is suitable for cryptographic use, and CryptoKit builds on it
    // Included here to contrast secure generators with insecure ones
    static func generateRandomTokenSystemRNG() -> String {
        var token = ""
        var rng = SystemRandomNumberGenerator()

        for _ in 0..<16 {
            let b = UInt8.random(in: 0...255, using: &rng)
            token += String(format: "%02x", b)
        }
        return token
    }

    // Insecure: drand48 uses a 48 bit linear congruential generator
    // Not thread safe and not suitable for cryptographic purposes
    static func generateRandomTokenDrand48() -> String {
        var token = ""
        for _ in 0..<16 {
            let value = Int(drand48() * 256.0) % 256
            token += String(format: "%02x", value)
        }
        return token
    }

    // Cryptographically secure: CCRandomGenerateBytes uses the system CSPRNG
    // Secure, but a lower level API that is generally discouraged in favor of SecRandomCopyBytes
    static func generateRandomTokenCC() -> String {
        var buffer = [UInt8](repeating: 0, count: 16)
        let status = CCRandomGenerateBytes(&buffer, buffer.count)

        if status != kCCSuccess {
            return "Error generating random bytes with CCRandomGenerateBytes"
        }

        return buffer.map { String(format: "%02x", $0) }.joined()
    }

    // Recommended: SecRandomCopyBytes is the high level, Apple recommended API for secure random bytes
    static func generateRandomTokenSecRandom() -> String {
        var randomBytes = [UInt8](repeating: 0, count: 16)
        let status = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes)

        guard status == errSecSuccess else {
            return "Error generating secure random bytes"
        }

        return randomBytes.map { String(format: "%02x", $0) }.joined()
    }

    static func mastgTest(completion: @escaping (String) -> Void) {
        // Seed libc rand with current time
        let now = UInt32(time(nil))
        c_srand(now)

        // Example of seeding drand48 with time, which also makes it predictable if the seed is known
        // srand48(time(nil))

        let value = """
        Using libc rand seeded with time
        Token: \(generateRandomTokenRand())

        Using Swift random API backed by SystemRandomNumberGenerator
        Token: \(generateRandomTokenSwiftRandom())

        Using /dev/random low level interface
        Token: \(generateRandomTokenDevRandom())

        Using arc4random_uniform as a direct token source
        Token: \(generateRandomTokenArc4RandomUniform())

        Using arc4random as a direct token source
        Token: \(generateRandomTokenArc4Random())

        Using SystemRandomNumberGenerator directly
        Token: \(generateRandomTokenSystemRNG())

        Using drand48 linear congruential generator
        Token: \(generateRandomTokenDrand48())

        Using CCRandomGenerateBytes lower level API
        Token: \(generateRandomTokenCC())

        Using SecRandomCopyBytes
        Token: \(generateRandomTokenSecRandom())
        """

        completion(value)
    }
}

Steps

  1. Unzip the app package and locate the main binary file, as described in Exploring the App Package. For this demo the path is ./Payload/MASTestApp.app/MASTestApp.
  2. Run radare2 for iOS on the binary and use the -i option to execute the script below.
1
r2 -q -i insecure_random.r2 -A MASTestApp > output.json
1
axtj @@=`ii~+rand[1]`

This script:

  • Uses ii to list imported symbols.
  • Filters that list with ~+rand to keep only imports whose names contain rand, such as rand, srand, drand48, arc4random, and arc4random_uniform.
  • Uses [1] to select the address column from that output.
  • Uses axtj @@=... to run axt on each of those addresses and print cross references in JSON.

Observation

The output of the script shows cross references to calls to functions whose names contain rand in the sample binary.

output.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[{"from":11936,"type":"CALL","perm":"--x","opcode":"bl sym.imp.FixedWidthInteger.random.setter....ztSGRd__lFZ","fcn_addr":11668,"fcn_name":"sym.MASTestApp.MastgTest.generateRandomTokenSystemRNG_...yFZ_","realname":"func.00002d94","refname":"sym.imp.FixedWidthInteger.random.setter....ztSGRd__lFZ"}]
[{"from":6784,"type":"CALL","perm":"--x","opcode":"bl sym.imp.FixedWidthInteger.random.setter_...FZ_","fcn_addr":6540,"fcn_name":"sym.MASTestApp.MastgTest.generateInsecureRandomTokenSwift...G0SSyFZ","realname":"func.0000198c","refname":"sym.imp.FixedWidthInteger.random.setter_...FZ_"}]
[{"from":11748,"type":"CALL","perm":"--x","opcode":"bl sym.imp.SystemRandomNumberGenerator...VABycfC","fcn_addr":11668,"fcn_name":"sym.MASTestApp.MastgTest.generateRandomTokenSystemRNG_...yFZ_","realname":"func.00002d94","refname":"sym.imp.SystemRandomNumberGenerator...VABycfC"}]
[]
[]
[{"from":13580,"type":"CALL","perm":"--x","opcode":"bl sym.imp.CCRandomGenerateBytes","fcn_addr":13084,"fcn_name":"sym.MASTestApp.MastgTest.generateRandomTokenCC_...yFZ_","realname":"func.0000331c","refname":"sym.imp.CCRandomGenerateBytes"}]
[{"from":14936,"type":"CALL","perm":"--x","opcode":"bl sym.imp.SecRandomCopyBytes","fcn_addr":14420,"fcn_name":"sym.MASTestApp.MastgTest.generateRandomTokenSecRandom_...yFZ_","realname":"func.00003854","refname":"sym.imp.SecRandomCopyBytes"}]
[{"from":11392,"type":"CALL","perm":"--x","opcode":"bl sym.imp.arc4random","fcn_addr":11200,"fcn_name":"sym.MASTestApp.MastgTest.generateInsecureRandomTokenArc4...G0SSyFZ","realname":"func.00002bc0","refname":"sym.imp.arc4random"}]
[{"from":10944,"type":"CALL","perm":"--x","opcode":"bl sym.imp.arc4random_uniform","fcn_addr":10748,"fcn_name":"sym.MASTestApp.MastgTest.generateInsecureRandomTokenArc4.Uniform_...yFZ_","realname":"func.000029fc","refname":"sym.imp.arc4random_uniform"}]
[{"from":12444,"type":"CALL","perm":"--x","opcode":"bl sym.imp.drand48","fcn_addr":12192,"fcn_name":"sym.MASTestApp.MastgTest.generateRandomTokenDrand48_...yFZ_","realname":"func.00002fa0","refname":"sym.imp.drand48"}]
[]
[{"from":5228,"type":"CALL","perm":"--x","opcode":"bl sym.imp.rand","fcn_addr":5036,"fcn_name":"sym.MASTestApp.MastgTest.generateRandomTokenRand_...yFZ_","realname":"func.000013ac","refname":"sym.imp.rand"}]
[{"from":16004,"type":"CALL","perm":"--x","opcode":"bl sym.imp.srand","fcn_addr":15768,"fcn_name":"sym.MASTestApp.MastgTest.mastg.completion_...FZ_","realname":"func.00003d98","refname":"sym.imp.srand"}]

Note: the output also shows calls to secure sources such as SecRandomCopyBytes, CCRandomGenerateBytes, SystemRandomNumberGenerator, and the Swift FixedWidthInteger.random implementation. These are present in the sample for contrast, but they are not the reason the test fails. The evaluation only treats uses of insecure libc PRNGs as findings.

Evaluation

The test fails because insecure PRNGs are used in a security relevant context. Specifically:

  • sym.imp.rand and sym.imp.srand, which expose the insecure libc PRNG.
  • sym.imp.drand48, which also uses an insecure linear congruential generator.
 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
[
  {
    "from": 12444,
    "type": "CALL",
    "perm": "--x",
    "opcode": "bl sym.imp.drand48",
    "fcn_addr": 12192,
    "fcn_name": "sym.MASTestApp.MastgTest.generateRandomTokenDrand48_...yFZ_",
    "realname": "func.00002fa0",
    "refname": "sym.imp.drand48"
  },
  {
    "from": 5228,
    "type": "CALL",
    "perm": "--x",
    "opcode": "bl sym.imp.rand",
    "fcn_addr": 5036,
    "fcn_name": "sym.MASTestApp.MastgTest.generateRandomTokenRand_...yFZ_",
    "realname": "func.000013ac",
    "refname": "sym.imp.rand"
  },
  {
    "from": 16004,
    "type": "CALL",
    "perm": "--x",
    "opcode": "bl sym.imp.srand",
    "fcn_addr": 15768,
    "fcn_name": "sym.MASTestApp.MastgTest.mastg.completion_...FZ_",
    "realname": "func.00003d98",
    "refname": "sym.imp.srand"
  }
]
1
jq -s 'add | map(select(.refname | test("^sym[.]imp[.](rand|srand|drand48)")))' output.json > evaluation.json

Now we disassemble the functions that call the insecure PRNGs to confirm their use in security-relevant contexts.

As an example, take "fcn_name": "sym.MASTestApp.MastgTest.generateRandomTokenDrand48_...yFZ_" from the evaluation output and disassemble or decompile it.

[0x00002fa0]> pdf @ sym.MASTestApp.MastgTest.generateRandomTokenDrand48_...yFZ_
<disassembly output>

Reading the disassembly confirms that it uses the output of drand48 to generate a random token (we intentionally don't perform this step for you here, please try it yourself).

Next we look for cross references to see where it is being called from.

[0x00002fa0]> axt @ sym.MASTestApp.MastgTest.generateRandomTokenDrand48_...yFZ_
sym.MASTestApp.MastgTest.mastg.completion_...FZ_ 0x41d4 [CALL:--x] bl sym.MASTestApp.MastgTest.generateRandomTokenDrand48_...yFZ_

Here it is called from sym.MASTestApp.MastgTest.mastg.completion_...FZ_. We can disassemble that function to understand its purpose and keep iterating.

If we find that it is called from a security-relevant context, such as during authentication or cryptographic operations, we can conclude that the use of the insecure PRNG is a security issue.