Skip to content

MASTG-DEMO-0150: References to Storage Integrity Check APIs with radare2

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

Sample

The sample contains two storage flows, so the difference is visible in the disassembly:

  • An insecure flow writes a sensitive value to user_profile.json in the Documents directory and later reads it back, trusting it without computing or verifying any HMAC or signature.
  • A secure flow (storeWithIntegrity) writes the same data to user_profile_protected.json with an appended HMAC-SHA256 and verifies the HMAC when reading it back.

Because the insecure flow stores data the app trusts without any integrity check, the app cannot detect whether that data was modified on disk.

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
// SUMMARY: This sample stores sensitive data in a file in the app's Documents directory and
// later reads it back without computing or verifying any integrity value (HMAC or signature).
// An attacker who modifies the file on a jailbroken device can tamper with the data undetected.
//
// For contrast, the file also contains a PASS routine that stores the same data with an appended
// HMAC-SHA256 and verifies it on read. Keeping both cases in one binary makes the difference
// visible in the disassembly: the PASS routine references CryptoKit's HMAC, the FAIL routine
// references no integrity API at all.

import Foundation
import CryptoKit

struct MastgTest {
    // PASS case: store data with an appended HMAC-SHA256 tag and verify it before trusting the
    // data on read. A mismatch (for example after the file is tampered with) is detected. In a
    // real app the key would be held in the Keychain rather than generated in-process.
    static func storeWithIntegrity(_ data: Data, at fileURL: URL, using key: SymmetricKey) -> String {
        let tagLength = 32 // HMAC-SHA256 tag size in bytes
        do {
            let mac = HMAC<SHA256>.authenticationCode(for: data, using: key)
            try (data + Data(mac)).write(to: fileURL)

            let stored = try Data(contentsOf: fileURL)
            let payload = stored.prefix(stored.count - tagLength)
            let tag = stored.suffix(tagLength)
            let valid = HMAC<SHA256>.isValidAuthenticationCode(Data(tag), authenticating: Data(payload), using: key)
            return valid ? "verified" : "tampering detected"
        } catch {
            return "failed: \(error.localizedDescription)"
        }
    }

    static func mastgTest(completion: @escaping (String) -> Void) {
        // FAIL: [MASTG-TEST-0387] The app writes sensitive data to disk and later reads it back
        // without computing or verifying an HMAC or signature, so it cannot detect tampering.

        let fileManager = FileManager.default
        guard let documents = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
            completion("Could not locate the Documents directory")
            return
        }
        let fileURL = documents.appendingPathComponent("user_profile.json")

        // Store sensitive data without any integrity protection
        let sensitiveData = #"{"username":"alice","role":"user","premium":false}"#.data(using: .utf8)!

        do {
            try sensitiveData.write(to: fileURL)
        } catch {
            completion("Failed to write file: \(error.localizedDescription)")
            return
        }

        // Later, the app reads the data back and trusts it without verifying its integrity
        guard let loaded = try? Data(contentsOf: fileURL),
              let contents = String(data: loaded, encoding: .utf8) else {
            completion("Failed to read the file back")
            return
        }

        // PASS: for contrast, store the same data with an HMAC and verify it on read. The FAIL path
        // above produces no integrity value; this routine returns the verification result.
        let protectedURL = documents.appendingPathComponent("user_profile_protected.json")
        let integrityResult = storeWithIntegrity(sensitiveData, at: protectedURL, using: SymmetricKey(size: .bits256))

        let value = """
        Stored file : \(fileURL.path)
        Contents    : \(contents)

        FAIL (no integrity check) : the data above was read back without verifying any HMAC or signature.
        PASS (HMAC verified)      : \(integrityResult)
        """
        completion(value)
    }
}

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. Open the app binary with radare2 (iOS) with the -i option to run this script.
storage_integrity.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
e scr.color=0
e scr.interactive=false

?e
?e === 1) Storage APIs: does the app persist and read back local data? ===
?e     Foundation Data.write(to:) / Data(contentsOf:), UserDefaults
ii~Data.write,Data.contentsOf,UserDefaults

?e
?e === 2) Integrity APIs: are HMAC / signature / hash APIs referenced? ===
?e     CryptoKit HMAC, CommonCrypto CCHmac/CC_SHA, Security SecKeyCreateSignature
ii~HMAC,CCHmac,CC_SHA,SecKeyCreateSignature

?e
?e The app references both storage and HMAC APIs. Their presence alone does not
?e prove the stored data is protected, so we check where Data.write(to:) is
?e called from and whether each storage path also computes and verifies an HMAC.

?e
?e === 3) Call sites of Data.write(to:): two distinct storage flows ===
axt @ 0x100009754

?e
?e === 4) Storage flow A (fcn 0x1000050f8): writes user_profile.json and reads ===
?e     it back with NO HMAC on this data path, so the stored data is not protected
pdf @ 0x1000050f8 ~Data.write,Data.contentsOf,CryptoKit.HMAC

?e
?e === 5) Storage flow B (fcn 0x1000045bc): computes an HMAC, writes, reads back, ===
?e     and verifies it, so the stored data is integrity protected (for contrast)
pdf @ 0x1000045bc ~Data.write,Data.contentsOf,CryptoKit.HMAC
run.sh
1
2
#!/bin/bash
r2 -q -e bin.relocs.apply=true -A -i storage_integrity.r2 MASTestApp > output.asm

Observation

The output shows that the app references both storage APIs (Foundation.Data.write(to:) and Foundation.Data(contentsOf:)) and storage-integrity APIs (CryptoKit.HMAC.authenticationCode and CryptoKit.HMAC.isValidAuthenticationCode).

Cross-referencing Data.write(to:) reveals two distinct storage flows, and the disassembly of each shows how the storage and integrity calls relate:

  • Flow A (fcn.0x1000050f8) calls Data.write(to:) and Data(contentsOf:) with no HMAC call on that data path.
  • Flow B (fcn.0x1000045bc) calls HMAC.authenticationCode before Data.write(to:) and HMAC.isValidAuthenticationCode after Data(contentsOf:).
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
=== 1) Storage APIs: does the app persist and read back local data? ===
Foundation Data.write(to:) / Data(contentsOf:), UserDefaults
23  0x100009724 NONE FUNC               sym.imp.Foundation.Data.contentsOf.options.URL.NSDataReadingOptions...VtKcfC
28  0x100009754 NONE FUNC               sym.imp.Foundation.Data.write.to.options.URL.NSDataWritingOptions...VtKF

=== 2) Integrity APIs: are HMAC / signature / hash APIs referenced? ===
CryptoKit HMAC, CommonCrypto CCHmac/CC_SHA, Security SecKeyCreateSignature
140 0x1000099f4 NONE FUNC               sym.imp.CryptoKit.HMAC.authenticationCode.for.using.HashedAuthentication...E0VyxGqd___AA12SymmetricKeyVt10Foundation12DataProtocolRd__lFZ
141 0x100009a00 NONE FUNC               sym.imp.CryptoKit.HMAC.isValidAuthenticationCode.authenticating.using..._0_AA12SymmetricKeyVt10Foundation15ContiguousBytesRd__AI12DataProtocolRd_0_r0_lFZ

The app references both storage and HMAC APIs. Their presence alone does not
prove the stored data is protected, so we check where Data.write(to:) is
called from and whether each storage path also computes and verifies an HMAC.

=== 3) Call sites of Data.write(to:): two distinct storage flows ===
sym.func.1000045bc 0x10000499c [CALL:--x] bl sym.imp.Foundation.Data.write.to.options.URL.NSDataWritingOptions...VtKF
sym.func.1000050f8 0x100005394 [CALL:--x] bl sym.imp.Foundation.Data.write.to.options.URL.NSDataWritingOptions...VtKF

=== 4) Storage flow A (fcn 0x1000050f8): writes user_profile.json and reads ===
it back with NO HMAC on this data path, so the stored data is not protected
      ││   0x100005394      f0100094       bl sym.imp.Foundation.Data.write.to.options.URL.NSDataWritingOptions...VtKF
     ││   0x1000054ac      9e100094       bl sym.imp.Foundation.Data.contentsOf.options.URL.NSDataReadingOptions...VtKcfC

=== 5) Storage flow B (fcn 0x1000045bc): computes an HMAC, writes, reads back, ===
and verifies it, so the stored data is integrity protected (for contrast)
           0x100004690      d9140094       bl sym.imp.CryptoKit.HMAC.authenticationCode.for.using.HashedAuthentication...E0VyxGqd___AA12SymmetricKeyVt10Foundation12DataProtocolRd__lFZ
   ││     0x10000499c      6e130094       bl sym.imp.Foundation.Data.write.to.options.URL.NSDataWritingOptions...VtKF
   ││││    0x1000049dc      52130094       bl sym.imp.Foundation.Data.contentsOf.options.URL.NSDataReadingOptions...VtKcfC
 ││││╎││   0x100005020      78120094       bl sym.imp.CryptoKit.HMAC.isValidAuthenticationCode.authenticating.using..._0_AA12SymmetricKeyVt10Foundation15ContiguousBytesRd__AI12DataProtocolRd_0_r0_lFZ

Evaluation

The test case fails because the app stores data it later trusts without verifying its integrity. Although the binary references HMAC APIs, those references alone do not prove that all stored data is protected: the disassembly shows that flow A writes user_profile.json and reads it back with no HMAC computation or verification on that path, so the app cannot detect if that file is tampered with.

For contrast, flow B (storeWithIntegrity) is a passing path: it computes an HMAC over the data before writing it and verifies the HMAC after reading it back, so tampering with user_profile_protected.json would be detected. The presence of this protected flow is exactly why the references found in step 2 require manual validation: only by inspecting how each storage path uses the integrity APIs can you tell which stored data is actually protected.