Skip to content

Update: Mastg test 0090 testing file integrity checks#3871

Open
Diolor wants to merge 12 commits into
OWASP:masterfrom
Diolor:mastg-test-0090-testing-file-integrity-checks
Open

Update: Mastg test 0090 testing file integrity checks#3871
Diolor wants to merge 12 commits into
OWASP:masterfrom
Diolor:mastg-test-0090-testing-file-integrity-checks

Conversation

@Diolor

@Diolor Diolor commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Description

Update copilot's content on TEST-0090 porting.
#3824


AI Tool Disclosure

Check exactly one option.

  • [ x] This contribution does not include AI-generated content.

If AI tools were used to generate or substantially modify code or text, complete the following.

  • AI tools used: Fable 5, Opus 4.8

Copilot AI and others added 11 commits May 24, 2026 10:05
Co-authored-by: cpholguera <29175115+cpholguera@users.noreply.github.com>
Co-authored-by: cpholguera <29175115+cpholguera@users.noreply.github.com>
- Add best-practices: [MASTG-BEST-0x01] to front matter
- Replace CC_MD5 with CC_SHA256 in source code integrity example
- Fix CCHmac bug: key and data parameters were swapped in both
  generate and verify call sites
- Remove detailed Bypass sections (violate KNOW file guidelines)
- Remove prescriptive language (belongs in BEST files)
- Remove incorrect @MASTG-KNOW-0090 reference from bypass section
- Improve writing voice (remove 'we discuss/we learn')
- Add Encrypt-then-MAC ordering note and SecKeyCreateSignature mention

Agent-Logs-Url: https://github.com/OWASP/mastg/sessions/27a55bf6-a673-45a3-a487-3ca26584380f

Co-authored-by: cpholguera <29175115+cpholguera@users.noreply.github.com>
@Diolor Diolor changed the base branch from copilot/mastg-test-0090-testing-file-integrity-checks to master June 14, 2026 13:01
@Diolor Diolor requested a review from sgIOlas June 14, 2026 13:02

@sgIOlas sgIOlas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments 👍

You can confirm the missing integrity check by patching the security-sensitive routine and observing that the app still runs:

1. Use @MASTG-TECH-0065 to locate the `isLicenseValid` comparison in the disassembly.
2. Use @MASTG-TECH-0147 to patch the binary so the check always grants access (for example, force the comparison to return `true`).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the goal of this test, I’d prefer a dynamic and less invasive validation method, such as Frida hooking or LLDB. Static patching plus re-signing can introduce unrelated side effects, like changing team ID, entitlements, or bundle identifier, which can break Keychain access for example.

It's okay though, just personal preference.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense @sgIOlas . I can move the evaluation to frooky.
For now I will skip the left suggestions on Demos until the iteration happens.

Are you ok with the Tests' content for now?

You can confirm the missing integrity check by patching the security-sensitive routine and observing that the app still runs:

1. Use @MASTG-TECH-0065 to locate the `isLicenseValid` comparison in the disassembly.
2. Use @MASTG-TECH-0147 to patch the binary so the check always grants access (for example, force the comparison to return `true`).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
2. Use @MASTG-TECH-0147 to patch the binary so the check always grants access (for example, force the comparison to return `true`).
2. Use @MASTG-TECH-0095 to hook or patch the app-owned code identified in step 1, so the check always grants access (for example, force the comparison to return `true`).


1. Use @MASTG-TECH-0065 to locate the `isLicenseValid` comparison in the disassembly.
2. Use @MASTG-TECH-0147 to patch the binary so the check always grants access (for example, force the comparison to return `true`).
3. Use @MASTG-TECH-0092 to re-sign and repackage the patched app, then reinstall it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
3. Use @MASTG-TECH-0092 to re-sign and repackage the patched app, then reinstall it.

1. Use @MASTG-TECH-0065 to locate the `isLicenseValid` comparison in the disassembly.
2. Use @MASTG-TECH-0147 to patch the binary so the check always grants access (for example, force the comparison to return `true`).
3. Use @MASTG-TECH-0092 to re-sign and repackage the patched app, then reinstall it.
4. Launch the app with any key and observe that it grants premium access. The app never detected the patch because it has no source code integrity check.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
4. Launch the app with any key and observe that it grants premium access. The app never detected the patch because it has no source code integrity check.
3. Use the app while hooking the license check logic and observe that it grants premium access. The app never detected the patch because it has no source code integrity check.


?e
?e Evidence of a security-sensitive routine (license key string literal):
izz~MAS-PREMIUM

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these verification scripts be more generic?


?e
?e Evidence that the app stores data on disk (filename string literal):
izz~user_profile.json

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here


The following C example illustrates this pattern using `CC_SHA256` from CommonCrypto:

```c

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation is for 32bit iOS and i don't think it will work with modern devices. Also, if this is iOS only we could skip C entirely and use Swift or ObjC.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works for me on the simulator. It's a modified version of what copilot initially proposed with a crash fix:

@_cdecl("mastg_source_integrity_anchor")
func mastgSourceIntegrityAnchor() {}

private func swiftTextSectionHash() -> String {
    // Step 1: Resolve the binary base address using dladdr
    var info = Dl_info()
    let symbol = unsafeBitCast(
        mastgSourceIntegrityAnchor as @convention(c) () -> Void,
        to: UnsafeRawPointer.self
    )
    guard dladdr(symbol, &info) != 0, let basePtr = info.dli_fbase else {
        return "Failed to resolve binary base address"
    }

    // Step 2: Parse the Mach-O header to locate the __TEXT/__text section
    let base = UnsafeRawPointer(basePtr)
    var offset = MemoryLayout<mach_header_64>.size
    var codePtr: UnsafeRawPointer?
    var textSize: Int  = 0

    let header = base.load(as: mach_header_64.self)
    for _ in 0 ..< Int(header.ncmds) {
        let cmd = base.load(fromByteOffset: offset, as: load_command.self)
        if cmd.cmd == LC_SEGMENT_64 {
            let seg = base.load(fromByteOffset: offset, as: segment_command_64.self)
            let segName = withUnsafeBytes(of: seg.segname) { raw in
                String(bytes: raw.prefix(while: { $0 != 0 }), encoding: .utf8) ?? ""
            }
            if segName == "__TEXT" {
                var secOffset = offset + MemoryLayout<segment_command_64>.size
                for _ in 0 ..< Int(seg.nsects) {
                    let sec = base.load(fromByteOffset: secOffset, as: section_64.self)
                    let secName = withUnsafeBytes(of: sec.sectname) { raw in
                        String(bytes: raw.prefix(while: { $0 != 0 }), encoding: .utf8) ?? ""
                    }
                    if secName == "__text" {
                        let runtimeOffset = Int(sec.addr - seg.vmaddr)
                        codePtr = base.advanced(by: runtimeOffset)
                        textSize = Int(sec.size)
                    }
                    secOffset += MemoryLayout<section_64>.size
                }
            }
        }
        offset += Int(cmd.cmdsize)
    }

    guard textSize > 0, let codePtr = codePtr else {
        return "Could not locate __TEXT/__text section"
    }

    // Step 3: Compute SHA-256 hash of the __text section to verify code integrity
    var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
    CC_SHA256(codePtr, CC_LONG(textSize), &digest)
    let hashHex = digest.map { String(format: "%02x", $0) }.joined()
    let matchesExpectedHash = hashHex == mastgExpectedTextSectionSHA256

    let value = """
    Binary base address : \(base)
    __TEXT/__text size  : \(textSize) bytes
    SHA-256 of __text   : \(hashHex)
    Expected SHA-256    : \(mastgExpectedTextSectionSHA256)
    Integrity check     : \(matchesExpectedHash ? "pass" : "fail")
    """
    return value
}

Comment thread knowledge/ios/MASVS-RESILIENCE/MASTG-KNOW-0086.md Outdated
Co-authored-by: Sergio García <32015541+sgIOlas@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants