Update: Mastg test 0090 testing file integrity checks#3871
Conversation
Co-authored-by: cpholguera <29175115+cpholguera@users.noreply.github.com>
Co-authored-by: cpholguera <29175115+cpholguera@users.noreply.github.com>
…ASTG-BEST-0048)" Agent-Logs-Url: https://github.com/OWASP/mastg/sessions/9f8fb07e-f29c-41fa-a7cf-80258d9be187 Co-authored-by: cpholguera <29175115+cpholguera@users.noreply.github.com>
Agent-Logs-Url: https://github.com/OWASP/mastg/sessions/48e4819f-ab75-4ece-834e-18ffc035f160 Co-authored-by: cpholguera <29175115+cpholguera@users.noreply.github.com>
Agent-Logs-Url: https://github.com/OWASP/mastg/sessions/e8d67e00-d51c-491b-8e3f-a659edf1e893 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>
| 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`). |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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`). |
There was a problem hiding this comment.
| 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. |
There was a problem hiding this comment.
| 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. |
There was a problem hiding this comment.
| 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 |
There was a problem hiding this comment.
Should these verification scripts be more generic?
|
|
||
| ?e | ||
| ?e Evidence that the app stores data on disk (filename string literal): | ||
| izz~user_profile.json |
|
|
||
| The following C example illustrates this pattern using `CC_SHA256` from CommonCrypto: | ||
|
|
||
| ```c |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
}Co-authored-by: Sergio García <32015541+sgIOlas@users.noreply.github.com>
Description
Update copilot's content on TEST-0090 porting.
#3824
AI Tool Disclosure
Check exactly one option.
If AI tools were used to generate or substantially modify code or text, complete the following.