From b94d8ad7d06affdad937c7d0a0992eca1c3a1ee3 Mon Sep 17 00:00:00 2001 From: 82Flex <82flex@gmail.com> Date: Sun, 14 Jan 2024 09:49:06 +0800 Subject: [PATCH] security: add resources check Signed-off-by: 82Flex <82flex@gmail.com> --- Certificates/Certificates.p12 | Bin 0 -> 2539 bytes Certificates/Lessica.cer | Bin 0 -> 873 bytes .../DataModels/Presets/SecurityPresets.swift | 26 +++++++++++++++-- .../Settings.bundle/en.lproj/Root.strings | Bin 1106 -> 385 bytes .../zh_Hans.lproj/Root.strings | Bin 938 -> 379 bytes Reveil/SecuritySuite/FileIntegrityCheck.swift | 9 ++++-- Reveil/SecuritySuite/IntegrityChecker.swift | 10 +++++-- Reveil/ViewModels/Security.swift | 27 ++++++++++++++++++ Reveil/ViewModels/SecurityCheck.swift | 23 +++++++++++++++ Reveil/en.lproj/Localizable.strings | 6 ++++ Reveil/zh-Hans.lproj/Localizable.strings | 6 ++++ 11 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 Certificates/Certificates.p12 create mode 100644 Certificates/Lessica.cer diff --git a/Certificates/Certificates.p12 b/Certificates/Certificates.p12 new file mode 100644 index 0000000000000000000000000000000000000000..a19c57ba8acfdbbfd7c84148d04539f963b26794 GIT binary patch literal 2539 zcmY+^c{CLK8V7J=#xizeH$?U&W`ryuWEs-PRy34IOqT34yq3v2sIfDandY@dGRUq* z$-WLs*$ER7suu~@d(OT0z4!dibAIQ1zvn!EJb!%PSnwJn00_r|YnZ`G35bL}P5=vl zgas2pSa887IitNI?_}c;O03KIilK@f^M_Uzw9X9U1{n1Jn%^G#Dcad0 zAS>Bh9wk_tge|`H^o z-@9cqu1iib(mPg#nFFnmU{6nqqgNZZzh69sSE5RiM z6PmhE!ltsbjJM}3=x3?8Z(NsCz{5n8-H(DApz?N?e+J(OkN_13Um%=4 zIvVU$E=_S3D+K$B6%#BkC6&N)gsYGTkt_FeUHoid#&41m(#Pq$DsNkVY-`oc@(i$5=pM(9_K#Ntv6pQ@y8?q`Mp}>WT^hLNaTZbjI7C(4-*cw+#ik+> zH3s z)xtcbymBD6S!c!tS2b53CfnCEY(Cd%k7nA~ zVkD}0&%5sm2ih_ovaIY&nX?IR!EDQ#wvE0Ctl4+X~Xzo~W#{>)*5mDw}4>+dT1X_ls%9dAo}8Dt?IfVx0{vwTRgT=d@i|^DTJAV$2%D z3J%NXsm)7}bT#I<+?Ty+6bQ#Mx&99fBrKB+2+MT!G`2V$Q)c%6Fu?|3JoRMj6q5D- z(*yXo9>R*7l)C%^+~0bDu%O;d{sDYe<{@%|Kfn4Dx%+N5!~FC-rP)e>=l5)s=HC z;>{722q#&Z%=;zXbCshT5^ST5kU!%hd;59KIt{k<%Uig*@;M2Y`OEKzFR}q{=~|GU zH1nw5Z{pMJJ4w-IZz~PYxvejH;g#wGX{W(^7tm=zKShM;!C{iW8O&kI8sEN~!YZOO z?X`VMQ{_j6GKoVz=VKd$tUH=5Fl|KP>qm~+lcYW`+Cqd6+PgHEbLd7YuA#y*@egm* zuh8|%n1RPQ%+ApAoS`wH{U^Ov3Z4s`FpYQ)#|1}`%LR(#nLD*H18nZTV?;`^veYY=FfjjmAkC8J+W=i?U$RA(IkD-ly z=;22{S9?tHPNp$;uFf{4ICl&=r+x_tbI>iQi-1b$JZ`nrdgR_y63K%W6bUSzLXM`F zYY1nYjWl`Mk*q4amB#Ef>E-=c)-niI2f0>=|JM0N<(F<*r*qPZm(D5*PO2x8FUlAv zN~Jw(kh-T5H)XiIRBCqxp^=G>CfU@;4>G#FJG0-skSIVIFN$849xZDz@FjJWZEIM> zcc^~e#TLVQ;ZMywFovj@r4p%$bcSNxEvun-@`Zj%6BGJ=8Qit60=NM!_i~phBzk67)!3F0baFdK(#A zu+$#R{Bmr21ihm6a)xzDN>g7tPW*YzjS--Yb?&0adL^kTZ}a>|%DGA7i8b28LxgGg zU`VaVL%R-;n0?#>ouf2O4&4lAVeYN522?i5B(Nf>(f+0H5_AgE+N#))IJZ>e^3ndo z0)gpd&VzkG)-3nEx>CBTFyWKs!m;mU(!#XFIv+Ba8gMcv&Jn%X(R!~zC4KCC?7s4R z@vDnIM&VW88z9!rtd|CP>(3+^0f2u2IC7xR literal 0 HcmV?d00001 diff --git a/Certificates/Lessica.cer b/Certificates/Lessica.cer new file mode 100644 index 0000000000000000000000000000000000000000..534658ba6d30ee95ec327ab4dabaf069b33a4a5a GIT binary patch literal 873 zcmXqLVoo(^V)9+U%*4pV#K>sC%f_kI=F#?@mywa1mBGNlP{4qXjX9KsnTOpcwYWGl zInj{YfD&8!g-&~=bbRL zvN-X-_PTI{_2dsKex(bf^K+g&Pc%NV&E*;o>+GOp#luIByOxGFK7&`9GgvqnTg|tK98Y>Ie%tzv#oU~onitp4 zm0P>!x{c;*8OaCzk4u+-{@bm6I90nZ=9P%U{Oqpe=Lf$ky1vhC4v1L5rpm<3$iTQb z%OJym9~f`4!iIzARL7LoIgU3VJN>|QBkEpXfr>U7FMymJY1YycAxFg6$&yid!gzn6M` z{?gfV^W>V70SxlH1A)HV=Cie*CUhPT?-U^bW7Tl=eyXCSba0a^sdc{ z@w@IkteD0bwlZ63Mpy#qlSizNF zON1*n6)ilo;_I3!*LM}=%m1=EFPQ0hDQf=Xd;O6&PH(nq_}KZn(2BX2b>$)cTZ#vc z?mEyY@Q~%|OWzkS*GpH6JrOoEThr+FZmvU%oE*ajW_jI0)0v$YUTm0LWNy*?w#EPH HfmtB{=XXa@ literal 0 HcmV?d00001 diff --git a/Reveil/DataModels/Presets/SecurityPresets.swift b/Reveil/DataModels/Presets/SecurityPresets.swift index b5a21f5..53f5f08 100644 --- a/Reveil/DataModels/Presets/SecurityPresets.swift +++ b/Reveil/DataModels/Presets/SecurityPresets.swift @@ -51,11 +51,21 @@ struct SecurityPresets: Codable { } if let bundleProvisioningProfilePath = bundle.path(forResource: "embedded", ofType: "mobileprovision"), - let profileHashValue = IntegrityChecker.getMobileProvisionProfileHashValue(path: bundleProvisioningProfilePath) + let profileHashValue = IntegrityChecker.calculateHashValue(path: bundleProvisioningProfilePath) { secureMobileProvisioningProfileHashes.removeAll(keepingCapacity: true) secureMobileProvisioningProfileHashes.insert(profileHashValue) } + + var updatedHashes: Dictionary = secureResourceHashes + for secureResourceName in secureResourceHashes.keys { + let resourcePath = bundle.path(forResource: secureResourceName, ofType: nil) + if let resourcePath, let resourceHashValue = IntegrityChecker.calculateHashValue(path: resourcePath) + { + updatedHashes.updateValue(resourceHashValue, forKey: secureResourceName) + } + } + secureResourceHashes = updatedHashes } var secureStandaloneLibraries: Set = [ @@ -84,11 +94,21 @@ struct SecurityPresets: Codable { ] var secureMobileProvisioningProfileHashes: Set = [ - "0675eb798917a1d44f11424be328c72a58812ba03900f1c58569af867353f438", + "", ] var secureMainExecutableMachOHashes: Set = [ - "502a2f7f57fcd163c4a8ccaa09b1d8831e53d214c1a8e4ef49904c7615324fdc", + "", + ] + + var secureResourceHashes: Dictionary = [ + "library_stub.zip": "", + "rsc-001-country-mapping.json": "", + "rsc-002-ios-versions.json": "", + "rsc-003-iphone-models.json": "", + "rsc-004-carriers.json": "", + "rsc-005-ipad-models.json": "", + "rsc-006-ipod-models.json": "", ] var insecureEnvironmentVariables: Set = [ diff --git a/Reveil/Resources/Settings.bundle/en.lproj/Root.strings b/Reveil/Resources/Settings.bundle/en.lproj/Root.strings index b24a0f14badcf06a440499eb738d1f8567423b3b..c4b4b57db1cf6d2ed9359e097b487b0b481c5cf4 100644 GIT binary patch literal 385 zcmb7;K?=e!5JmSr#n1zY;Ig<-P!U9==mExdOhcP#Nn_Dn52BarNo*3N6}oXZ|NZ}m zAFfy`G?c;`lU(TX&S?ls2&Z@y1Oq?Q!~7;4v6&6ywIKiJdt1Ki-&d~VFUt-jFnPjd z@A#mcCzUj?)u;&Kek7O@7IPn*wruOz$6dx9mT<`gLN>&XkEC@^q;I8G$ce9wwe@4F j4Qc~j`--U{S#<5&E>W2Gi!#Zgh77BUc;?>!laGQA?MQl^ literal 1106 zcmb`G-Acni5QXR3XHZyrS)>)b6fY{M2qIGI1H>e4gKbjUv|1lt{myP``l|>MHk&w8&=U39)??&~cRoH?W37K*v1&5~s-H17} zcM$oA!EzX?S$b~4JAonBBiPGtbfq&VCZF1a!p|u@`E;!NynpLUoiwd&M?4L?^?v8n zYk*%eHn9uOdZ5Obx;3Xp0wedmsM7)d7rv*Oy+0ZyI_pzd)S59|)? z%sbZ|(T(XbztK^h`4i`m$`u}Bc4JVTeZJvkS8+c-sNZ{s`s+~%gmf&r8?6!*YXjD5XP9{Zk`R={vz01>93AP|bC9npKis{;iq@;lrpz*7|U?*aE)?97z^C+9LJ4I|^ z3N~D5A)w1Psv3MMSjQ61sxggs_GELf3!%9xlnP==0! o$UnB4VZvZ$jOXxC9MijC)AyC&*s z|2&-w&ux8S08K28lc2QGVDhBx+QLJ^@pr#`9JRd$Lx zhd{$FhxdguH~`+j!@kZ8cz-y*wYT?WZN_Sstt;}j zD_KKEI431J{LzK!(X6BfTx2ija^gN;#fxXng^ZFg8qPY6ELVHrtt-gJK;C1ovt=-{ FzX39$scirN diff --git a/Reveil/SecuritySuite/FileIntegrityCheck.swift b/Reveil/SecuritySuite/FileIntegrityCheck.swift index d18d13f..459ffe9 100644 --- a/Reveil/SecuritySuite/FileIntegrityCheck.swift +++ b/Reveil/SecuritySuite/FileIntegrityCheck.swift @@ -14,6 +14,7 @@ enum FileIntegrityCheck: Codable { // Compare current hash value(SHA256 hex string) of `embedded.mobileprovision` with a specified hash value. // Use command `"shasum -a 256 /path/to/embedded.mobileprovision"` to get SHA256 value on your macOS. case mobileProvision(String) + case commonResource(String, String) // Compare current hash value(SHA256 hex string) of executable file with a specified (Image Name, Hash Value). // Only work on dynamic library and arm64. @@ -24,11 +25,13 @@ extension FileIntegrityCheck: Explainable { var description: String { switch self { case let .bundleID(exceptedBundleID): - return "The expected bundle identify was \(exceptedBundleID)" + return "The expected bundle identify was \(exceptedBundleID)." case let .mobileProvision(expectedSha256Value): - return "The expected hash value of Mobile Provision file was \(expectedSha256Value)" + return "The expected hash value of Mobile Provision file was \(expectedSha256Value)." + case let .commonResource(resourceName, expectedSha256Value): + return "The expected hash value of the resource named \(resourceName) was \(expectedSha256Value)." case let .machO(imageName, expectedSha256Value): - return "The expected hash value of \"__TEXT.__text\" data of \(imageName) Mach-O file was \(expectedSha256Value)" + return "The expected hash value of \"__TEXT.__text\" data of \(imageName) Mach-O file was \(expectedSha256Value)." } } } diff --git a/Reveil/SecuritySuite/IntegrityChecker.swift b/Reveil/SecuritySuite/IntegrityChecker.swift index db91802..7b24926 100644 --- a/Reveil/SecuritySuite/IntegrityChecker.swift +++ b/Reveil/SecuritySuite/IntegrityChecker.swift @@ -29,6 +29,12 @@ final class IntegrityChecker { result = true hitChecks.append(check) } + case let .commonResource(resourceName, expectedSha256Value): + let resourcePath = Bundle(for: Self.self).path(forResource: resourceName, ofType: nil) + if let resourcePath, calculateHashValue(path: resourcePath) != expectedSha256Value { + result = true + hitChecks.append(check) + } case let .machO(imageName, expectedSha256Value): if !checkMachO(imageName, with: expectedSha256Value.lowercased()) { result = true @@ -58,13 +64,13 @@ final class IntegrityChecker { static func checkMobileProvision(_ expectedSha256Values: Set) -> Bool { guard let path = Bundle(for: Self.self).path(forResource: "embedded", ofType: "mobileprovision"), - let hashValue = getMobileProvisionProfileHashValue(path: path) + let hashValue = calculateHashValue(path: path) else { return false } return expectedSha256Values.contains(hashValue) } - static func getMobileProvisionProfileHashValue(path: String) -> String? { + static func calculateHashValue(path: String) -> String? { guard FileManager.default.fileExists(atPath: path) else { return nil } diff --git a/Reveil/ViewModels/Security.swift b/Reveil/ViewModels/Security.swift index 39a4167..17c4566 100644 --- a/Reveil/ViewModels/Security.swift +++ b/Reveil/ViewModels/Security.swift @@ -362,6 +362,33 @@ final class Security: ObservableObject, StaticEntryProvider, Explainable { IntegrityChecker.checkMobileProvision(SecurityPresets.default.secureMobileProvisioningProfileHashes) } + func checkResourceHashes() -> Bool { + let resHashes = SecurityPresets.default.secureResourceHashes + for resName in resHashes.keys { + if let resHash = resHashes[resName] { + let resTampered = IntegrityChecker.amITampered([.commonResource(resName, resHash)]).result + if resTampered { + return false + } + } + } + return true + } + + func getModifiedResourceNames() -> [String] { + var modifiedNames = [String]() + let resHashes = SecurityPresets.default.secureResourceHashes + for resName in resHashes.keys { + if let resHash = resHashes[resName] { + let resTampered = IntegrityChecker.amITampered([.commonResource(resName, resHash)]).result + if resTampered { + modifiedNames.append(resName) + } + } + } + return modifiedNames + } + func checkMachOHash() -> Bool { #if DEBUG return IntegrityChecker.checkMachO(currentExecutablePath, with: SecurityPresets.default.secureMainExecutableMachOHashes) diff --git a/Reveil/ViewModels/SecurityCheck.swift b/Reveil/ViewModels/SecurityCheck.swift index 1ed3f4d..8a3e260 100644 --- a/Reveil/ViewModels/SecurityCheck.swift +++ b/Reveil/ViewModels/SecurityCheck.swift @@ -45,6 +45,7 @@ enum SecurityCheck: CaseIterable, Codable, Equatable, Hashable, RawRepresentable case identifiedBundleIdentifier(Status) case identifiedMobileProvisioningProfile(Status) + case identifiedResources(Status) case identifiedMachO(Status) case identifiedEntitlements(Status) @@ -100,6 +101,8 @@ enum SecurityCheck: CaseIterable, Codable, Equatable, Hashable, RawRepresentable self = .identifiedBundleIdentifier(status) case "identifiedMobileProvisioningProfile": self = .identifiedMobileProvisioningProfile(status) + case "identifiedResources": + self = .identifiedResources(status) case "identifiedMachO": self = .identifiedMachO(status) case "identifiedEntitlements": @@ -173,6 +176,8 @@ enum SecurityCheck: CaseIterable, Codable, Equatable, Hashable, RawRepresentable return status.prefix + "identifiedBundleIdentifier" case let .identifiedMobileProvisioningProfile(status): return status.prefix + "identifiedMobileProvisioningProfile" + case let .identifiedResources(status): + return status.prefix + "identifiedResources" case let .identifiedMachO(status): return status.prefix + "identifiedMachO" case let .identifiedEntitlements(status): @@ -232,6 +237,7 @@ enum SecurityCheck: CaseIterable, Codable, Equatable, Hashable, RawRepresentable .noSuspiciousEnvironmentVariables(.unchanged), .identifiedBundleIdentifier(.unchanged), .identifiedMobileProvisioningProfile(.unchanged), + .identifiedResources(.unchanged), .identifiedMachO(.unchanged), .identifiedEntitlements(.unchanged), .expectedCodeSigningStatus(.unchanged), @@ -269,6 +275,7 @@ enum SecurityCheck: CaseIterable, Codable, Equatable, Hashable, RawRepresentable case let .noSuspiciousEnvironmentVariables(status): fallthrough case let .identifiedBundleIdentifier(status): fallthrough case let .identifiedMobileProvisioningProfile(status): fallthrough + case let .identifiedResources(status): fallthrough case let .identifiedMachO(status): fallthrough case let .identifiedEntitlements(status): fallthrough case let .expectedCodeSigningStatus(status): fallthrough @@ -308,6 +315,7 @@ enum SecurityCheck: CaseIterable, Codable, Equatable, Hashable, RawRepresentable case let .noSuspiciousEnvironmentVariables(status): fallthrough case let .identifiedBundleIdentifier(status): fallthrough case let .identifiedMobileProvisioningProfile(status): fallthrough + case let .identifiedResources(status): fallthrough case let .identifiedMachO(status): fallthrough case let .identifiedEntitlements(status): fallthrough case let .expectedCodeSigningStatus(status): fallthrough @@ -347,6 +355,7 @@ enum SecurityCheck: CaseIterable, Codable, Equatable, Hashable, RawRepresentable case let .noSuspiciousEnvironmentVariables(status): fallthrough case let .identifiedBundleIdentifier(status): fallthrough case let .identifiedMobileProvisioningProfile(status): fallthrough + case let .identifiedResources(status): fallthrough case let .identifiedMachO(status): fallthrough case let .identifiedEntitlements(status): fallthrough case let .expectedCodeSigningStatus(status): fallthrough @@ -419,6 +428,10 @@ enum SecurityCheck: CaseIterable, Codable, Equatable, Hashable, RawRepresentable return status == .failed ? NSLocalizedString("TAMPERED_MOBILE_PROVISIONING_PROFILE", comment: "Mobile provisioning profile was tampered") : NSLocalizedString("ORIGINAL_MOBILE_PROVISIONING_PROFILE", comment: "Mobile provisioning profile is trusted") + case let .identifiedResources(status): + return status == .failed ? + NSLocalizedString("TAMPERED_RESOURCES", comment: "Some of the embedded resources were tampered") : + NSLocalizedString("ORIGINAL_RESOURCES", comment: "Embedded resources are original") case let .identifiedMachO(status): return status == .failed ? NSLocalizedString("TAMPERED_MACH_O", comment: "Main executable was tampered") : @@ -548,6 +561,8 @@ enum SecurityCheck: CaseIterable, Codable, Equatable, Hashable, RawRepresentable return .staticIntegrity case .identifiedMobileProvisioningProfile: return .staticIntegrity + case .identifiedResources: + return .staticIntegrity case .identifiedMachO: return .staticIntegrity case .identifiedEntitlements: @@ -616,6 +631,8 @@ enum SecurityCheck: CaseIterable, Codable, Equatable, Hashable, RawRepresentable return .identifiedBundleIdentifier(Security.shared.checkMainBundleIdentifier() ? .passed : .failed) case .identifiedMobileProvisioningProfile: return .identifiedMobileProvisioningProfile(Security.shared.checkMobileProvisioningProfileHash() ? .passed : .failed) + case .identifiedResources: + return .identifiedResources(Security.shared.checkResourceHashes() ? .passed : .failed) case .identifiedMachO: return .identifiedMachO(Security.shared.checkMachOHash() ? .passed : .failed) case .identifiedEntitlements: @@ -726,6 +743,12 @@ enum SecurityCheck: CaseIterable, Codable, Equatable, Hashable, RawRepresentable BasicEntry(customLabel: $0, allowedToCopy: true) } } + case let .identifiedResources(status): + if status == .failed { + entryChildren = Security.shared.getModifiedResourceNames().map { + BasicEntry(customLabel: $0, allowedToCopy: true) + } + } case let .identifiedEntitlements(status): if status == .failed { entryChildren = Security.shared.getUnknownEntitlementKeys().map { diff --git a/Reveil/en.lproj/Localizable.strings b/Reveil/en.lproj/Localizable.strings index 587573b..602d24a 100644 --- a/Reveil/en.lproj/Localizable.strings +++ b/Reveil/en.lproj/Localizable.strings @@ -751,6 +751,9 @@ /* Mobile provisioning profile is trusted */ "ORIGINAL_MOBILE_PROVISIONING_PROFILE" = "Mobile provisioning profile is trusted"; +/* Embedded resources are original */ +"ORIGINAL_RESOURCES" = "Embedded resources are original"; + /* Others */ "OTHERS" = "Others"; @@ -916,6 +919,9 @@ /* Mobile provisioning profile was tampered */ "TAMPERED_MOBILE_PROVISIONING_PROFILE" = "Mobile provisioning profile was tampered"; +/* Some of the embedded resources were tampered */ +"TAMPERED_RESOURCES" = "Some of the embedded resources were tampered"; + /* TB Frequency */ "TB_FREQ" = "Time-Based Frequency"; diff --git a/Reveil/zh-Hans.lproj/Localizable.strings b/Reveil/zh-Hans.lproj/Localizable.strings index 7918587..665739a 100644 --- a/Reveil/zh-Hans.lproj/Localizable.strings +++ b/Reveil/zh-Hans.lproj/Localizable.strings @@ -751,6 +751,9 @@ /* Mobile provisioning profile is trusted */ "ORIGINAL_MOBILE_PROVISIONING_PROFILE" = "已验证应用程序分发配置文件"; +/* Embedded resources are original */ +"ORIGINAL_RESOURCES" = "已验证内建资源均为初始状态"; + /* Others */ "OTHERS" = "其他"; @@ -916,6 +919,9 @@ /* Mobile provisioning profile was tampered */ "TAMPERED_MOBILE_PROVISIONING_PROFILE" = "应用程序分发配置文件被篡改"; +/* Some of the embedded resources were tampered */ +"TAMPERED_RESOURCES" = "部分内建资源被篡改"; + /* TB Frequency */ "TB_FREQ" = "时基频率";