├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── img ├── .gitignore ├── grab_wys-app-3rdparty.jpg ├── grab_wys-app-adhoc.jpg ├── grab_wys-app-cracked-aux.jpg ├── grab_wys-app-cracked.jpg ├── grab_wys-app-entitlements.jpg ├── grab_wys-app-expired.jpg ├── grab_wys-app-mas.jpg ├── grab_wys-app-unsigned.jpg ├── grab_wys-app.jpg ├── grab_wys-binary.jpg ├── grab_wys-dmg.jpg ├── grab_wys-dmgfake.jpg ├── grab_wys-kext.jpg ├── grab_wys-malware.jpg ├── grab_wys-malware2.jpg ├── grab_wys-pkg.jpg ├── grab_wys-skidrevoke.jpg ├── grab_wys-verify.jpg ├── grab_wys-xip-user.jpg ├── grab_wys-xip.jpg └── jb-img.png ├── screengrabs.md └── wys /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .CS_Store 3 | TODO -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Joss Brown (pseud.) -- German laws apply -- Place of jurisdiction: Berlin, Germany 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | --- 24 | 25 | WhatsYourSign name & icon: Copyright (c) by Patrick Wardle (Objective-See) 26 | https://objective-see.com 27 | https://github.com/objective-see/WhatsYourSign 28 | Attribution-NonCommercial 4.0 International (CC BY-NC 4.0) 29 | https://creativecommons.org/licenses/by-nc/4.0/ 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![wys-platform-macos](https://img.shields.io/badge/platform-macOS-lightgrey.svg) 2 | ![wys-code-shell](https://img.shields.io/badge/code-shell-yellow.svg) 3 | [![wys-license](http://img.shields.io/badge/license-MIT+-blue.svg)](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/LICENSE) 4 | 5 | # wys – WhatsYourSign (shell script version) 6 | 7 | **wys is a shell script variant of Patrick Wardle's awesome [WhatsYourSign](https://github.com/objective-see/WhatsYourSign).** 8 | 9 | Full functionality including signature verification is available for bundles (e.g. app, kext, framework), binaries/executables, disk images (DMG, sparsebundle, sparseimage), package archives (pkg, mpkg, xip, xar). Basic functionality, e.g. checksum verification, is available for any and all regular files. 10 | 11 | The original **WhatsYourSign** is described as follows: 12 | 13 | > Verifying a file's cryptographic signature can deduce its origin or trustability. Unfortunately on OS X there is no simple way to view a file's signature from the UI. **WhatsYourSign** adds a menu item to Finder.app. Simply right-, or control-click on any file to display its cryptographic signing information! 14 | 15 | **wys**, on the other hand, is actually **WhatsYourSign** ***Extended***. In addition to the default functionality, **wys** also 16 | 17 | * generally works on mounted volumes, i.e. in `/Volumes` or other user-defined mount points (e.g. `smb` mounts etc.), i.e. you can safely scan a file or application on its mounted DMG volume before copying it, 18 | * prints the file size (B, MB, MiB) for regular files (data size) and directories (size on disk), 19 | * prints the download source domain names, so the user can detect potential temporary redirects, 20 | * checks if a file is quarantined, 21 | * verifies DMG checksums and prints disk image information on DMGs, sparsebundles and sparseimages, 22 | * verifies a signed bundle for modified, added or missing files with `codesign`, 23 | * compares a file hash (checksum) stored in the clipboard with the hash calculated for the local file (regular files only), 24 | * compares a file hash (checksum) stored in a checksum file, e.g. `*.sha256`, with the hash calculated for the local file (regular files only), 25 | * validates a regular file against its **GnuPG** signature contained in `.asc` or `.sig` files (optional), 26 | * accounts for macOS filename corruptions after download, e.g. `*.sha256.txt` or `*.asc.txt`, 27 | * checks the calculated hash (file or executable) against the VirusTotal database for malware detection (optional), 28 | * scans for malware using `clamscan` installed as part of **ClamXAV** or **ClamAV** (optional), 29 | * verifies code signing certificates (CSCs) against the current revocation list using `security` and accounts for potentially spoofed code signatures, 30 | * verifies installer package signing certificates (IPSCs) against the current revocation list using `security` and accounts for potentially spoofed signatures, 31 | * compares the CFBundleIdentifier with the identifier in the code signature, 32 | * creates a local sqlite database of any scanned CFBundleIdentifier and the associated SKID in the CSC, and compares successive scan data with the saved data, 33 | * prints Gatekeeper `spctl` assessment (packages: `install`; other: `execute`) and the associated source information, 34 | * prints a CSC's timestamp or signing time (depending on the signature), 35 | * prints an IPSC's signing timestamp and creator from a package's TOC, 36 | * explicitly checks entitlements for app sandboxing, and looks for the MAS receipt, 37 | * deep-scans a bundle to find 38 | * executable files that are unsigned, or 39 | * that have a different code signature than the main executable, and 40 | * permanently writes the scan results to log files (optional, recommended). 41 | 42 | ## Installation 43 | If you are using the macOS Finder, it's best to ignore **wys** and use Patrick's software, unless you need the extended functionality. The **wys** version is only meant as a quick hack for users who have disabled the Finder. Since the original **WhatsYourSign** is an `appex` (Finder extension), it will not work in other file managers. 44 | 45 | ### Example: Nimble Commander 46 | * navigate: NC > Preferences > Tools 47 | * set **Tool title**, e.g.: What's Your Sign? 48 | * set **Application**: `/path/to/wys` 49 | * set **Parameters**: `%P` 50 | * set **Startup Mode**: Detached 51 | * navigate: NC > Preferences > Hotkeys > All > Tools: What's Your Sign? 52 | * define keyboard shortcut, e.g.: CMD-SHIFT-S 53 | * navigate: NC > Preferences > Hotkeys > Conflicts 54 | * if necessary, change keyboard shortcut to resolve any potential conflicts 55 | 56 | ### Usage in default macOS Finder 57 | You can add the **wys** shell script to an **Automator** service/workflow, which will then be available in the **Services** contextual submenu; you can also assign a keyboard shortcut for it in **System Preferences**. 58 | 59 | ### GnuPG 60 | * Install `gpg` as part of the **[GPG Suite](https://gpgtools.org)** or the original **[GnuPG for macOS](https://sourceforge.net/p/gpgosx/docu/Download/)**. 61 | * Note: **GnuPG** can also be installed using **[Homebrew](https://brew.sh)**: `brew install gnupg` 62 | * Note: **wys** will account for the install locations used by 63 | * **GPG Suite** (`/usr/local/MacGPG2/bin`), and by 64 | * **GnuPG for macOS** and **Homebrew** (`/usr/local/bin`), **[MacPorts](https://www.macports.org)** (`/opt/local/bin`) and **[Fink](http://www.finkproject.org)** (`/sw/bin`). 65 | 66 | ### ClamAV 67 | * Install **[ClamXAV](https://www.clamxav.com)** or the original freeware version **[ClamAV](https://www.clamav.net)**. 68 | * Note: **ClamAV** can also be installed using **[Homebrew](https://brew.sh)**: `brew install clamav` 69 | * Note: **wys** will account for the install locations used by 70 | * **ClamXAV** (`/usr/local/clamXav/bin`), and by 71 | * **Homebrew** (`/usr/local/bin`), **[MacPorts](https://www.macports.org)** (`/opt/local/bin`) and **[Fink](http://www.finkproject.org)** (`/sw/bin`). 72 | 73 | ### VirusTotal API key 74 | * Create a free online account at **[VirusTotal](https://www.virustotal.com)**; 75 | * in your browser navigate: VirusTotal > Account > Profile > API Key; 76 | * copy the key and configure **wys** accordingly (*see below*). 77 | 78 | ### Notes 79 | * It probably helps to set OCSP and CRL to "Best attempt" in **macOS Keychain Access** > Preferences > Certificates. 80 | * The SKID comparison will occasionally produce false warnings, because a SKID (a certificate's **Subject Key Identifier**) can change for perfectly valid reasons, for example because the developer of a software has 81 | * renewed an expired certificate, 82 | * sold his product to another developer, or 83 | * received a new certificate (e.g. after company rebranding etc.). 84 | * VirusTotal results can produce false warnings, depending on the antivirus software involved; examples are: 85 | * **BBEdit:** VEX189B.Webshell (Bkav); 86 | * false positives like applications with `libswiftDispatch.dylib` marked as MacOS.BitCoinMiner-AS (Avast, AVG). 87 | * ***Please keep in mind*** that ClamAV and VirusTotal scans *do not help with unknown threats*, and even if a malware is known, these scans might not produce any results, for example: 88 | * if a malware is redistributed with a different code signature, 89 | * if the malware code itself has been changed, or 90 | * if only the zip or DMG used for distribution has been registered as malware, not the app itself. 91 | * The script uses `qlmanage`, which is part of **QuickLook**, to show the scan logs, and at least one QuickLook plugin is known to interfere with the accurate display of log files on macOS, namely **[QLColorCode](https://github.com/anthonygelibert/QLColorCode)**. If, after disabling QLColorCode, **wys** still doesn't produce a correct QuickLook preview, run `wys` in your terminal and look for any errors in the `qlmanage` output to narrow it down. 92 | 93 | ## Scan options 94 | 95 | ### Command line operation and configuration 96 | * Move, copy or (best practice) symlink **wys** from the cloned repository into your `$PATH`, e.g. to `/usr/local/bin/wys`, then configure using the CLI options. 97 | * The following command line options and arguments are available: 98 | 99 | ``` 100 | wys [ ... ] scan filepath(s) or file(s) from the command line 101 | 102 | Options: 103 | 104 | --discrete force-disable silent mode and all logging 105 | --init initialize wys 106 | --silent force silent mode for current scans 107 | --status print wys configuration status 108 | 109 | --config [report | silent | vt ] modify wys configuration file 110 | report toggle logging 111 | silent toggle silent mode 112 | vt enter VirusTotal API key 113 | 114 | --help this help page 115 | ``` 116 | 117 | ### Alternative configuration (GUI usage) 118 | * Run **wys** at least once to create the default wys configuration file, then 119 | * run the command `open -a TextEdit ~/.wys/config` to open the config file, or open it manually. 120 | 121 | #### Enable logging 122 | * In the **wys config file** replace `report=no` with `report=yes` and **save**. 123 | * Logs will be stored in `~/Library/Logs/wys` and will be accessible via Apple's **Console** application. 124 | 125 | #### Silent mode 126 | * In the **wys config file** replace `silent=no` with `silent=yes` and **save**. 127 | * **wys** will scan silently in the background and only log the SKIDs and (if logging is enabled) the scan results. 128 | 129 | #### VirusTotal API key 130 | In the **wys config file** look for the line that begins with `vtkey=`, paste the API key behind the `=` (equals sign) without whitespace, and **save**. 131 | 132 | ## Uninstall 133 | To uninstall, you need to remove the following files: 134 | 135 | * **wys** itself, 136 | * the wys GitHub directory (if you have cloned it), 137 | * the invisible directory `~/.wys`, which contains the config file, the SKID database, the wys icon, and the `./bin` directory with the `abspath` CLI), and 138 | * `~/Library/Logs/wys`, which contains the log files. 139 | 140 | Temporary files in `/tmp` will be automatically removed by **wys** after every scan, and potential detritus will be removed at macOS boot. 141 | 142 | ## [Screengrabs](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/screengrabs.md) 143 | 144 | ## Beta status 145 | * still needs general testing, lots of testing 146 | * timestamp information in Info.plist? (research) … approximate signing/creation time? 147 | * deep scan: parse CodeResources to thoroughly check for modified and unverified/added files (v1.1 rc) 148 | * validate MAS receipts (maybe) 149 | * XProtect yara scans (depends on release of UXProtect CLI) 150 | 151 | ## Thank you 152 | * Patrick Wardle (for the original **WhatsYourSign** and all his other great security tools) 153 | * lososik (feature ideas & testing) 154 | * Daniel Beck (`abspath`) 155 | -------------------------------------------------------------------------------- /img/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .CS_Store 3 | -------------------------------------------------------------------------------- /img/grab_wys-app-3rdparty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-app-3rdparty.jpg -------------------------------------------------------------------------------- /img/grab_wys-app-adhoc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-app-adhoc.jpg -------------------------------------------------------------------------------- /img/grab_wys-app-cracked-aux.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-app-cracked-aux.jpg -------------------------------------------------------------------------------- /img/grab_wys-app-cracked.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-app-cracked.jpg -------------------------------------------------------------------------------- /img/grab_wys-app-entitlements.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-app-entitlements.jpg -------------------------------------------------------------------------------- /img/grab_wys-app-expired.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-app-expired.jpg -------------------------------------------------------------------------------- /img/grab_wys-app-mas.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-app-mas.jpg -------------------------------------------------------------------------------- /img/grab_wys-app-unsigned.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-app-unsigned.jpg -------------------------------------------------------------------------------- /img/grab_wys-app.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-app.jpg -------------------------------------------------------------------------------- /img/grab_wys-binary.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-binary.jpg -------------------------------------------------------------------------------- /img/grab_wys-dmg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-dmg.jpg -------------------------------------------------------------------------------- /img/grab_wys-dmgfake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-dmgfake.jpg -------------------------------------------------------------------------------- /img/grab_wys-kext.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-kext.jpg -------------------------------------------------------------------------------- /img/grab_wys-malware.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-malware.jpg -------------------------------------------------------------------------------- /img/grab_wys-malware2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-malware2.jpg -------------------------------------------------------------------------------- /img/grab_wys-pkg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-pkg.jpg -------------------------------------------------------------------------------- /img/grab_wys-skidrevoke.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-skidrevoke.jpg -------------------------------------------------------------------------------- /img/grab_wys-verify.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-verify.jpg -------------------------------------------------------------------------------- /img/grab_wys-xip-user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-xip-user.jpg -------------------------------------------------------------------------------- /img/grab_wys-xip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/grab_wys-xip.jpg -------------------------------------------------------------------------------- /img/jb-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JayBrown/wys-WhatsYourSign-shell-script-version/63913e70522a36752f1aa741438b5a59f3e81b23/img/jb-img.png -------------------------------------------------------------------------------- /screengrabs.md: -------------------------------------------------------------------------------- 1 | ## Screengrabs 2 | 3 | #### Checksum for installer package verified from clipboard 4 | 5 | ![grab18](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-verify.jpg) 6 | 7 | #### Warning of SKID mismatch and certificate revocation (KeRanger) 8 | 9 | ![grab19](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-skidrevoke.jpg) 10 | 11 | #### Application signed with Developer ID 12 | 13 | ![grab1](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-app.jpg) 14 | 15 | #### Malware application (KeRanger) with revoked certificate and SKID mismatch 16 | *Notice the SKID mismatch, i.e. the SKID from the code signature used by the attacker differs from the original certificate by the Transmission developer.* 17 | 18 | *Notice the bogey RTF file, which is actually an executable part of the ransomware scheme.* 19 | 20 | ![grab2](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-malware.jpg) 21 | 22 | #### Malware application (OSX.CreativeUpdater) with fake codesigning certificate and SKID mismatch 23 | *Compare the main executable code signature (added by attacker) with the other signature (original by Mozilla), i.e. this is a legit app bundle within a malware app bundle to fool the user.* 24 | 25 | *Notice the bogey `script` file, which starts the download of the cryptominer malware.* 26 | 27 | *Please note that the security assessment using `spctl` can still accept a codesigning certificate, while the more up-to-date verification with `security` already shows it as revoked.* 28 | 29 | ![grab13](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-malware2.jpg) 30 | 31 | #### Application with entitlements (Apple System) 32 | 33 | *Notice that there is no CRL (certificate revocation list) for Apple System signatures (leaf certificate: "Software Signing"), so let's hope none of Apple's private codesigning keys ever get leaked.* 34 | 35 | ![grab3](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-app-entitlements.jpg) 36 | 37 | #### Application with entitlements (Mac App Store) 38 | 39 | ![grab10](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-app-mas.jpg) 40 | 41 | #### Application with valid but expired codesigning certificate 42 | 43 | ![grab20](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-app-expired.jpg) 44 | 45 | #### Application with untrusted third-party code signature and missing SKID 46 | 47 | ![grab17](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-app-3rdparty.jpg) 48 | 49 | #### Adhoc-signed application with missing SKID 50 | 51 | ![grab9](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-app-adhoc.jpg) 52 | 53 | #### Unsigned application 54 | 55 | ![grab8](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-app-unsigned.jpg) 56 | 57 | #### Kernel extension 58 | 59 | ![grab16](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-kext.jpg) 60 | 61 | #### Codesigned command line interface with initial SKID scan 62 | 63 | ![grab6](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-binary.jpg) 64 | 65 | #### Codesigned disk image (DMG) 66 | 67 | ![grab4](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-dmg.jpg) 68 | 69 | #### Malware disk image (DMG) with hash mismatch and signed with a fake code signature 70 | 71 | ![grab12](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-dmgfake.jpg) 72 | 73 | #### Signed installer package 74 | 75 | ![grab5](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-pkg.jpg) 76 | 77 | #### xip archive with verified hash and signed with Developer ID 78 | 79 | ![grab11](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-xip.jpg) 80 | 81 | #### xip archive signed with locally trusted key 82 | 83 | ![grab14](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-xip-user.jpg) 84 | 85 | #### Application with cracked executables using proprietary code signatures 86 | *Notice that the main code signature is unchanged: the SKID comparison shows a match.* 87 | 88 | ![grab7](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-app-cracked.jpg) 89 | 90 | #### Auxiliary list with unsigned executable files 91 | *Note that some developers erroneously set the executable bits on files that do not need it; this really messes things up, and *wys* scans will take longer in these cases.* 92 | 93 | ![grab15](https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version/blob/master/img/grab_wys-app-cracked-aux.jpg) 94 | -------------------------------------------------------------------------------- /wys: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # wys 4 | # WhatsYourSign shell script version 5 | # 6 | # extended shell script variant of Patrick Wardle's "WhatsYourSign" Finder extension (objective-see.com) 7 | # gain WhatsYourSign functionality (and more) in other file managers than macOS Finder 8 | # 9 | # v1.2 beta 1 10 | # Copyright (c) 2018 Joss Brown (pseud.) 11 | # license (wys): MIT+ 12 | # info: https://github.com/JayBrown/wys-WhatsYourSign-shell-script-version 13 | # 14 | # WhatsYourSign name & icon: (c) by Patrick Wardle ; Attribution-NonCommercial 4.0 International (CC BY-NC 4.0) 15 | 16 | # check for single user 17 | scrname=$(basename $0) 18 | singleuser=$(sysctl -n kern.singleuser) 19 | if [[ $singleuser == "1" ]] ; then 20 | echo "Error! $scrname can not be run in single-user mode." >&2 21 | exit 22 | fi 23 | 24 | export LANG=en_US.UTF-8 25 | 26 | # check compatibility 27 | osversion=$(sw_vers -productVersion | awk -F. '{print $2}') 28 | if [[ "$osversion" -le 7 ]] ; then 29 | echo "Error! Incompatible OS version: $scrname needs at least OS X 10.8." >&2 30 | info=$(osascript << EOT 31 | beep 32 | tell application "System Events" 33 | activate 34 | set userChoice to button returned of (display alert "wys: Error!" & return & return & "Minimum OS Requirement:" & return & "OS X 10.8 (Mountain Lion)" ¬ 35 | as critical ¬ 36 | buttons {"Quit"} ¬ 37 | default button 1 ¬ 38 | giving up after 60) 39 | end tell 40 | EOT 41 | ) 42 | exit 43 | fi 44 | 45 | export PATH=$PATH:/usr/local/bin:/opt/local/bin:/sw/bin 46 | 47 | shopt -s extglob 48 | 49 | cacheloc="$HOME/.wys" 50 | configloc="$cacheloc/config" 51 | version="1.2" 52 | bversion=" beta 1" 53 | 54 | # command line options 55 | silentoverride=false 56 | discrete=false 57 | if [[ $1 =~ ^(--init|--initialize|--reset)$ ]] ; then 58 | if [[ -f "$configloc" ]] ; then 59 | echo -e "All previous settings will be lost.\nDo you really want to initialize wys? (n/Y)\n" 60 | read -n 1 -s userinput 61 | if [[ $userinput == "Y" ]] ; then 62 | echo -e "report=no\nvtkey=\nsilent=no\nclamscan=no" > "$configloc" 63 | echo "wys: initialized." 64 | else 65 | echo "wys: no change." 66 | fi 67 | else 68 | ! [[ -d "$cacheloc" ]] && mkdir "$cacheloc" 69 | echo -e "report=no\nvtkey=\nsilent=no" > "$configloc" 70 | echo "wys: initialized." 71 | fi 72 | exit 73 | elif [[ $1 == "--config" ]] ; then 74 | configfull=$(cat "$configloc") 75 | if [[ $configfull == "" ]] ; then 76 | echo "wys: config is empty. Please run 'wys --init' first." >&2 77 | exit 78 | fi 79 | shift 80 | if [[ $1 =~ ^(vt|virustotal|virus-total)$ ]] ; then 81 | shift 82 | if [[ $1 == "" ]] ; then 83 | echo "wys: no VirusTotal API key specified." >&2 84 | exit 85 | else 86 | configfull=$(echo "$configfull" | grep -v "^vtkey=") 87 | echo -e "$configfull\nvtkey=$1" > "$configloc" 88 | echo "wys: VirusTotal API key added to config: $1" 89 | fi 90 | elif [[ $1 =~ ^(report|log)$ ]] ; then 91 | logstatus=$(echo "$configfull" | awk -F= '/^report/{print $2}') 92 | configfull=$(echo "$configfull" | grep -v "^report=") 93 | if [[ $logstatus == "no" ]] ; then 94 | echo -e "$configfull\nreport=yes" > "$configloc" 95 | echo "wys: logging enabled." 96 | else 97 | echo -e "$configfull\nreport=no" > "$configloc" 98 | echo "wys: logging disabled." 99 | fi 100 | elif [[ $1 =~ ^(silent|quiet)$ ]] ; then 101 | silentstatus=$(echo "$configfull" | awk -F= '/^silent/{print $2}') 102 | configfull=$(echo "$configfull" | grep -v "^silent=") 103 | if [[ $silentstatus == "no" ]] ; then 104 | echo -e "$configfull\nsilent=yes" > "$configloc" 105 | echo "wys: silent mode enabled." 106 | else 107 | echo -e "$configfull\nsilent=no" > "$configloc" 108 | echo "wys: silent mode disabled." 109 | fi 110 | elif [[ $1 == "" ]] ; then 111 | echo "wys: no argument specified." >&2 112 | else 113 | echo "wys: unknown argument: $1" >&2 114 | fi 115 | exit 116 | elif [[ $1 == "--status" ]] ; then 117 | configfull=$(cat "$configloc") 118 | if [[ $configfull == "" ]] ; then 119 | echo "wys: config is empty. Please run 'wys --init' first." >&2 120 | else 121 | logstatus=$(echo "$configfull" | awk -F= '/^report/{print $2}') 122 | [[ $logstatus == "" ]] && logstatus="n/a" 123 | silentstatus=$(echo "$configfull" | awk -F= '/^silent/{print $2}') 124 | [[ $silentstatus == "" ]] && silentstatus="n/a" 125 | vtkey=$(echo "$configfull" | awk -F= '/^vtkey/{print $2}') 126 | [[ $vtkey == "" ]] && vtkey="n/a" 127 | echo -e "logging:\t$logstatus" 128 | echo -e "silent mode:\t$silentstatus" 129 | echo -e "VirusTotal key:\t$vtkey" 130 | fi 131 | exit 132 | elif [[ $1 == "--help" ]] ; then 133 | echo -e "wys $version$bversion\n\nwys [ ... ]\t\tscan filepath(s) or file(s) from the command line\n\nOptions:\n\n--discrete\tforce-disable silent mode and all logging\n--init\t\tinitialize wys\n--silent\tforce silent mode for current scans\n--status\tprint wys configuration status\n\n--config [report | silent | vt ]\t\tmodify wys configuration file\n\treport\t\ttoggle logging\n\tsilent\t\ttoggle silent mode\n\tvt \tenter VirusTotal API key\n\n--help\t\tthis help page" 134 | exit 135 | elif [[ $1 == "--silent" ]] || [[ $1 == "--quiet" ]] ; then 136 | echo "wys: silent mode forced for current scans." 137 | silentoverride=true 138 | shift 139 | elif [[ $1 == "--discrete" ]] ; then 140 | discrete=true 141 | echo "wys: discrete mode enabled." 142 | echo "wys: silent mode disabled for current scans." 143 | echo "wys: all logging disabled for current scans." 144 | shift 145 | fi 146 | 147 | # check if path is valid (for command line usage) 148 | if [[ $@ == "" ]] ; then 149 | echo "No filepaths specified." >&2 150 | exit 151 | fi 152 | 153 | crlrefresh rp 2>/dev/null 154 | 155 | process="wys" 156 | account=$(id -un) 157 | 158 | icon64="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 692 | sMf/Ax59odGg0nmwAAAAAElFTkSuQmCC" 693 | 694 | # create wys icon 695 | ! [[ -d $cacheloc ]] && mkdir "$cacheloc" 696 | rm -f "$cacheloc/wys-icon.base64" 2>/dev/null 697 | if ! [[ -f $cacheloc/wys-icon.png ]] ; then 698 | echo "$icon64" > "$cacheloc/wys-icon.base64" 699 | base64 -D -i "$cacheloc/wys-icon.base64" -o "$cacheloc/wys-icon.png" && rm -f "$cacheloc/wys-icon.base64" 2>/dev/null 700 | fi 701 | if ! [[ -f $cacheloc/wys-icon.png ]] ; then 702 | icon_loc="/System/Library/PreferencePanes/Security.prefPane/Contents/Resources/FileVault.icns" 703 | else 704 | icon_loc="$cacheloc/wys-icon.png" 705 | fi 706 | 707 | # create database 708 | dbloc="$cacheloc/wys.db" 709 | ! [[ -f "$dbloc" ]] && sqlite3 "$dbloc" "create table WhatsYourSign(cfbid TEXT, csid TEXT, skid TEXT, misc1 TEXT, misc2 TEXT, misc3 TEXT);" 710 | 711 | # create bin directory and export abspath function 712 | wysbin="$cacheloc/bin" 713 | ! [[ -d "$wysbin" ]] && mkdir -p "$wysbin" 714 | if ! [[ -f "$wysbin/abspath" ]] ; then 715 | fabsp=$(/bin/cat << 'EOT' 716 | #!/usr/bin/env python 717 | import os.path 718 | import sys 719 | 720 | for arg in sys.argv[1:]: 721 | print os.path.realpath(os.path.abspath(arg)) 722 | EOT 723 | ) 724 | echo "$fabsp" > "$wysbin/abspath" && chmod u+x "$wysbin/abspath" 725 | fi 726 | export PATH=$PATH:"$wysbin" 727 | 728 | # create/read config 729 | if ! [[ -f "$configloc" ]] ; then 730 | echo -e "report=no\nvtkey=\nsilent=no\nclamscan=no" > "$configloc" 731 | fi 732 | configfull=$(cat "$configloc") 733 | 734 | # check for clamscan 735 | clamav=false 736 | clampath="/usr/local/ClamXAV3/bin/clamscan" 737 | if ! [[ -f $clampath ]] ; then 738 | clampath="/usr/local/clamXav/bin/clamscan" 739 | if ! [[ -f $clampath ]] ; then 740 | clamcheck=$(clamscan --version 2>/dev/null) 741 | if [[ $clamcheck != "" ]] ; then 742 | clampath="clamscan" 743 | clamav=true 744 | else 745 | clampath="" 746 | fi 747 | else 748 | clamav=true 749 | fi 750 | else 751 | clamav=true 752 | fi 753 | $clamav && echo "wys: will perform ClamAV scans." 754 | 755 | # check for gpg 756 | gnupg=false 757 | gpgpath="/usr/local/MacGPG2/bin/gpg" 758 | if ! [[ -f $gpgpath ]] ; then 759 | gpgpath="/usr/local/MacGPG2/bin/gpg2" 760 | if ! [[ -f $gpgpath ]] ; then 761 | gpgcheck=$(gpg --version 2>/dev/null) 762 | if [[ $gpgcheck != "" ]] ; then 763 | gpgpath="gpg" 764 | gnupg=true 765 | else 766 | gpgcheck=$(gpg2 --version 2>/dev/null) 767 | if [[ $gpgcheck != "" ]] ; then 768 | gpgpath="gpg2" 769 | gnupg=true 770 | else 771 | gpgpath="" 772 | fi 773 | fi 774 | else 775 | gnupg=true 776 | fi 777 | else 778 | gnupg=true 779 | fi 780 | $gnupg && echo "wys: will validate GnuPG signatures." 781 | 782 | # read report setting 783 | wyslog=false 784 | if ! $discrete ; then 785 | wysreport=$(echo "$configfull" | awk -F= '/report/{print $2}') 786 | if [[ $wysreport == "yes" ]] ; then 787 | wyslog=true 788 | logdir="$HOME/Library/Logs/wys" 789 | ! [[ -d "$logdir" ]] && mkdir -p "$logdir" 790 | echo "wys: configured to log scan reports." 791 | fi 792 | fi 793 | 794 | # read silent mode 795 | silent=false 796 | if ! $discrete ; then 797 | if ! $silentoverride ; then 798 | silentmode=$(echo "$configfull" | awk -F= '/silent/{print $2}') 799 | if [[ $silentmode == "yes" ]] ; then 800 | silent=true 801 | echo "wys: configured to run in silent mode." 802 | else 803 | echo "wys: silent mode disabled." 804 | fi 805 | else 806 | silent=true 807 | fi 808 | fi 809 | 810 | # read VirusTotal API key 811 | vtaccess=false 812 | vtkey=$(echo "$configfull" | awk -F= '/vtkey/{print $2}') 813 | if [[ $vtkey != "" ]] ; then 814 | vtaccess=true 815 | echo "wys: VirusTotal API Key detected." 816 | else 817 | echo "wys: VirusTotal API Key not found." 818 | fi 819 | 820 | # temporary subdirectory for certificate dumps 821 | certdir="/tmp/wys-certs" 822 | rm -rf "$certdir" 2>/dev/null 823 | 824 | # notify function 825 | _notify () { 826 | if ! $silent ; then 827 | if [[ "$tn_status" == "osa" ]] ; then 828 | osascript &>/dev/null << EOT 829 | tell application "System Events" 830 | display notification "$2" with title "$process [" & "$account" & "]" subtitle "$1" 831 | end tell 832 | EOT 833 | elif [[ "$tn_status" == "tn-app-new" ]] || [[ "$tn_status" == "tn-app-old" ]] ; then 834 | "$tn_loc/Contents/MacOS/terminal-notifier" \ 835 | -title "$process [$account]" \ 836 | -subtitle "$1" \ 837 | -message "$2" \ 838 | -appIcon "$icon_loc" \ 839 | >/dev/null 840 | elif [[ "$tn_status" == "tn-cli" ]] ; then 841 | "$tn" \ 842 | -title "$process [$account]" \ 843 | -subtitle "$1" \ 844 | -message "$2" \ 845 | -appIcon "$icon_loc" \ 846 | >/dev/null 847 | fi 848 | fi 849 | } 850 | 851 | # look for terminal-notifier 852 | tn=$(which terminal-notifier 2>/dev/null) 853 | if [[ "$tn" == "" ]] || [[ "$tn" == *"not found" ]] ; then 854 | tn_loc=$(mdfind -onlyin / "kMDItemCFBundleIdentifier == 'fr.julienxx.oss.terminal-notifier'" 2>/dev/null | awk 'NR==1') 855 | if [[ "$tn_loc" == "" ]] ; then 856 | tn_loc=$(mdfind -onlyin / "kMDItemCFBundleIdentifier == 'nl.superalloy.oss.terminal-notifier'" 2>/dev/null | awk 'NR==1') 857 | if [[ "$tn_loc" == "" ]] ; then 858 | tn_status="osa" 859 | else 860 | tn_status="tn-app-old" 861 | fi 862 | else 863 | tn_status="tn-app-new" 864 | fi 865 | else 866 | tn_vers=$("$tn" -help | head -1 | awk -F'[()]' '{print $2}' | awk -F. '{print $1"."$2}') 867 | if (( $(echo "$tn_vers >= 1.8" | bc -l) )) && (( $(echo "$tn_vers < 2.0" | bc -l) )) ; then 868 | tn_status="tn-cli" 869 | else 870 | tn_loc=$(mdfind -onlyin / "kMDItemCFBundleIdentifier == 'fr.julienxx.oss.terminal-notifier'" 2>/dev/null | awk 'NR==1') 871 | if [[ "$tn_loc" == "" ]] ; then 872 | tn_loc=$(mdfind -onlyin / "kMDItemCFBundleIdentifier == 'nl.superalloy.oss.terminal-notifier'" 2>/dev/null | awk 'NR==1') 873 | if [[ "$tn_loc" == "" ]] ; then 874 | tn_status="osa" 875 | else 876 | tn_status="tn-app-old" 877 | fi 878 | else 879 | tn_status="tn-app-new" 880 | fi 881 | fi 882 | fi 883 | 884 | # beep function 885 | _beep () { 886 | osascript -e "beep" 887 | } 888 | 889 | # hexer function 890 | _hxr () { 891 | echo -n "${1}" | xxd -p | sed -e 's/\(..\)/\\x\1/g' 892 | } 893 | 894 | # round function 895 | _round () { 896 | echo $(printf %.$2f $(echo "scale=$2;(((10^$2)*$1)+0.5)/(10^$2)" | bc)) 897 | } 898 | 899 | # other size calculations 900 | _sizes () { 901 | mbsizeraw=$(bc -l <<< "scale=6; $bsize/1000000") 902 | mbsize=$(_round "$mbsizeraw" 2) 903 | if [[ "$mbsize" == "0.00" ]] ; then 904 | mbsize="< 0.01" 905 | fi 906 | mibsizeraw=$(bc -l <<< "scale=6; $bsize/1048576") 907 | mibsize=$(_round "$mibsizeraw" 2) 908 | if [[ "$mibsize" == "0.00" ]] ; then 909 | mibsize="< 0.01" 910 | fi 911 | hrbsize=$(echo "$bsize" | perl -wpe '1 while s/(\d+)(\d\d\d)/$1,$2/;') 912 | } 913 | 914 | # gpg validation 915 | _gpgval () { 916 | if [[ -f "$filepath.asc" ]] ; then 917 | gpgvloc="$filepath.asc" 918 | gpgval=true 919 | elif [[ -f "$filepath.asc.txt" ]] ; then 920 | gpgvloc="$filepath.asc.txt" 921 | gpgval=true 922 | elif [[ -f "$filepath.sig" ]] ; then 923 | gpgvloc="$filepath.sig" 924 | gpgval=true 925 | elif [[ -f "$filepath.sig.txt" ]] ; then 926 | gpgvloc="$filepath.sig.txt" 927 | gpgval=true 928 | else 929 | gpgval=false 930 | fi 931 | if $gpgval ; then 932 | gpgver=$("$gpgpath" --verify "$gpgvloc" "$filepath" 2>&1) 933 | gpgresult=$(echo "$gpgver" | grep "gpg: Good signature") 934 | if [[ $gpgresult != "" ]] ; then 935 | gpgstat=$(echo "$gpgresult" | awk -F"gpg: " '{print $2}') 936 | gpgsig=$(echo "$gpgstat" | awk -F\" '{print $2}') 937 | gpgtrust=$(echo "$gpgstat" | awk -F '[][]' '{print $2}') 938 | gpgresult="good signature" 939 | _notify "✅ GnuPG signature validated" "$filename" 940 | else 941 | gpgerror=$(echo "$gpgver" | grep "gpg: BAD signature") 942 | if [[ $gpgerror != "" ]] ; then 943 | _beep 944 | gpgstat=$(echo "$gpgerror" | awk -F"gpg: " '{print $2}') 945 | gpgresult="BAD SIGNATURE" 946 | gpgsig=$(echo "$gpgstat" | awk -F\" '{print $2}') 947 | gpgtrust=$(echo "$gpgstat" | awk -F '[][]' '{print $2}') 948 | 949 | _notify "❌ GnuPG: bad signature!" "$filename" 950 | else 951 | _beep 952 | gpgresult="not validated" 953 | _notify "⚠️ GnuPG: not validated!" "$filename" 954 | fi 955 | fi 956 | if [[ $gpgresult == "not validated" ]] ; then 957 | echo -e "GnuPG:\t\t$gpgresult\n" >> "$tmploc" 958 | else 959 | echo -e "GnuPG:\t\t$gpgresult" >> "$tmploc" 960 | echo -e "\t\t$gpgsig" >> "$tmploc" 961 | echo -e "\t\ttrust: $gpgtrust\n" >> "$tmploc" 962 | fi 963 | fi 964 | } 965 | 966 | # parse pbpasted clipboard content for possible checksums (only md5, sha1, sha2) 967 | _cscn () { 968 | case $1 in 969 | ( *[!0-9A-Fa-f]* | "" ) echo "none" ;; 970 | ( * ) 971 | case ${#1} in 972 | ( 32 ) echo "md" ;; 973 | ( 40 ) echo "1" ;; 974 | ( 56 ) echo "224" ;; 975 | ( 64 ) echo "256" ;; 976 | ( 96 ) echo "384" ;; 977 | ( 128 ) echo "512" ;; 978 | ( * ) echo "none" ;; 979 | esac 980 | esac 981 | } 982 | 983 | # hashing function 984 | _hashes () { 985 | [[ $md5hash == "" ]] && md5hash=$(md5 -q "$1") 986 | echo -e "hashes:\t\tmd5:\t$md5hash" >> "$tmploc" 987 | [[ $sha1hash == "" ]] && sha1hash=$(shasum -a 1 "$1" | awk '{print $1}') 988 | echo -e "\t\tsha1:\t$sha1hash" >> "$tmploc" 989 | [[ $sha256hash == "" ]] && sha256hash=$(shasum -a 256 "$1" | awk '{print $1}') 990 | echo -e "\t\tsha256:\t$sha256hash\n" >> "$tmploc" 991 | [[ $vthash == "" ]] && vthash="$sha256hash" 992 | } 993 | 994 | # find checksum files 995 | _csfind () { 996 | read -d '' hdiglist <<"EOF" 997 | sha256 998 | sha256.txt 999 | sha1 1000 | sha1.txt 1001 | md5 1002 | md5.txt 1003 | sha512 1004 | sha512.txt 1005 | sha384 1006 | sha384.txt 1007 | sha224 1008 | sha224.txt 1009 | EOF 1010 | # first look for checksum files with the same filename 1011 | hfsfound=false 1012 | cshsingle="" 1013 | while read -r digestn 1014 | do 1015 | [[ -f "$filepath.$digestn" ]] && cshsingle="$cshsingle$filename.$digestn\n" 1016 | done < <(echo "$hdiglist") 1017 | cshsingle=$(echo -e "$cshsingle" | grep -v "^$") 1018 | [[ $cshsingle != "" ]] && hfsfound=true 1019 | # populate list of all other checksum files in target directory 1020 | hfmfound=false 1021 | cshlist="" 1022 | cd "$parentdir" 1023 | while read -r digestn 1024 | do 1025 | cshfname=$(find . -mindepth 1 -maxdepth 1 -type f -name "*.$digestn" | sed 's/^\.\///g') 1026 | [[ $cshfname != "" ]] && cshlist="$cshlist$cshfname\n" 1027 | done < <(echo "$hdiglist") 1028 | cd "$workingdir" 1029 | cshlist=$(echo -e "$cshlist" | grep -v "^$" | grep -vF "$filename") 1030 | [[ $cshlist != "" ]] && hfmfound=true 1031 | } 1032 | 1033 | # compare checksums (checksum file) 1034 | _cshcomp () { 1035 | compresult=false 1036 | hfhfound=false 1037 | cstype="" 1038 | hasherrors="" 1039 | while read -r hashfile 1040 | do 1041 | hfhashes=$(cat "$parentdir/$hashfile" | grep -F "$filename" | awk '{print $1}') 1042 | if [[ $hfhashes != "" ]] ; then 1043 | hfhfound=true 1044 | dbreaker=false 1045 | while read -r hfhash 1046 | do 1047 | case ${#hfhash} in 1048 | ( 32 ) cstype="md5" ;; 1049 | ( 40 ) cstype="1" ;; 1050 | ( 56 ) cstype="224" ;; 1051 | ( 64 ) cstype="256" ;; 1052 | ( 96 ) cstype="384" ;; 1053 | ( 128 ) cstype="512" ;; 1054 | esac 1055 | if [[ $cstype == "md5" ]] ; then 1056 | [[ $md5hash == "" ]] && md5hash=$(md5 -q "$filepath") 1057 | localhash="$md5hash" 1058 | hnote="MD5" 1059 | thnote="md5" 1060 | if $vtaccess && [[ $vthash == "" ]] ; then 1061 | if [[ $sha256hash == "" ]] ; then 1062 | sha256hash=$(shasum -a 256 "$filepath" | awk '{print $1}') 1063 | vthash="$sha256hash" 1064 | else 1065 | vthash="$sha256hash" 1066 | fi 1067 | fi 1068 | else 1069 | hnote="SHA-$cstype" 1070 | thnote="sha$cstype" 1071 | if [[ $cstype == "256" ]] ; then 1072 | [[ $sha256hash == "" ]] && sha256hash=$(shasum -a "$cstype" "$filepath" | awk '{print $1}') 1073 | localhash="$sha256hash" 1074 | vthash="$sha256hash" 1075 | else 1076 | if $vtaccess && [[ $vthash == "" ]]; then 1077 | if [[ $sha256hash == "" ]] ; then 1078 | sha256hash=$(shasum -a 256 "$filepath" | awk '{print $1}') 1079 | vthash="$sha256hash" 1080 | else 1081 | vthash="$sha256hash" 1082 | fi 1083 | fi 1084 | if [[ $cstype == "1" ]] ; then 1085 | [[ $sha1hash == "" ]] && sha1hash=$(shasum -a "$cstype" "$filepath" | awk '{print $1}') 1086 | localhash="$sha1hash" 1087 | elif [[ $cstype == "512" ]] ; then 1088 | [[ $sha512hash == "" ]] && sha512hash=$(shasum -a "$cstype" "$filepath" | awk '{print $1}') 1089 | localhash="$sha512hash" 1090 | elif [[ $cstype == "384" ]] ; then 1091 | [[ $sha384hash == "" ]] && sha384hash=$(shasum -a "$cstype" "$filepath" | awk '{print $1}') 1092 | localhash="$sha384hash" 1093 | elif [[ $cstype == "224" ]] ; then 1094 | [[ $sha224hash == "" ]] && sha224hash=$(shasum -a "$cstype" "$filepath" | awk '{print $1}') 1095 | localhash="$sha224hash" 1096 | fi 1097 | fi 1098 | fi 1099 | if [[ $hfhash == $localhash ]] ; then 1100 | hfherror=false 1101 | dbreaker=true 1102 | break 1103 | else 1104 | hasherrors="$hasherrors$thnote $hnote\n" 1105 | hfherror=true 1106 | fi 1107 | done < <(echo "$hfhashes") 1108 | $dbreaker && break 1109 | fi 1110 | done < <(echo -e "$cshsingle\n$cshlist" | grep -v "^$") 1111 | if $hfhfound ; then 1112 | if ! $hfherror ; then 1113 | echo -e "checksum file:\t$thnote:\t$localhash" >> "$tmploc" 1114 | echo -e "\t\tstatus:\tverified\n" >> "$tmploc" 1115 | _notify "✅ $hnote verified" "$filename" 1116 | compresult=true 1117 | else 1118 | merror=false 1119 | hasherrors=$(echo -e "$hasherrors" | grep -v "^$") 1120 | if [ $(echo "$hasherrors" | wc -l | xargs) -gt 1 ] ; then 1121 | merror=true 1122 | hasherrors=$(echo "$hasherrors" | awk '!seen[$0]++') 1123 | if [[ $(echo "$hasherrors" | wc -l | xargs) == "1" ]] ; then 1124 | herrl=$(echo "$hasherrors" | awk '{print $1}') 1125 | merrorl="multiple $herrl hashes" 1126 | herrn=$(echo "$hasherrors" | awk '{print $2}') 1127 | merrorn="Multiple $herrn" 1128 | else 1129 | merrorl="multiple hashes" 1130 | merrorn="Multiple hash" 1131 | fi 1132 | fi 1133 | _beep 1134 | if $cliperror ; then 1135 | _notify "❌ $clipnote mismatch!" "Rehashing: $filename" 1136 | fi 1137 | if ! $merror ; then 1138 | _notify "❌ $hnote mismatch!" "Rehashing: $filename" 1139 | echo -e "checksum file:\t$thnote:\t$hfhash" >> "$tmploc" 1140 | echo -e "\t\tstatus:\tMISMATCH" >> "$tmploc" 1141 | else 1142 | _notify "❌ $merrorn mismatches!" "Rehashing: $filename" 1143 | echo -e "checksum file:\t$merrorl" >> "$tmploc" 1144 | echo -e "\t\tstatus:\tMISMATCHES" >> "$tmploc" 1145 | fi 1146 | fi 1147 | else 1148 | if $cliperror ; then 1149 | _beep 1150 | _notify "❌ $clipnote mismatch!" "Rehashing: $filename" 1151 | fi 1152 | fi 1153 | if ! $compresult ; then # comparison with no results 1154 | _hashes "$filepath" 1155 | fi 1156 | } 1157 | 1158 | # compare checksums (clipboard) 1159 | _cscomp () { 1160 | compresult=false 1161 | if [[ $cstype == "md" ]] ; then 1162 | hnote="MD5" 1163 | thnote="md5" 1164 | [[ $md5hash == "" ]] && md5hash=$(md5 -q "$filepath") 1165 | localhash="$md5hash" 1166 | if $vtaccess && [[ $vthash == "" ]] ; then 1167 | if [[ $sha256hash == "" ]] ; then 1168 | sha256hash=$(shasum -a 256 "$filepath" | awk '{print $1}') 1169 | vthash="$sha256hash" 1170 | else 1171 | vthash="$sha256hash" 1172 | fi 1173 | fi 1174 | else 1175 | hnote="SHA-$cstype" 1176 | thnote="sha$cstype" 1177 | if [[ $cstype == "256" ]] ; then 1178 | [[ $sha256hash == "" ]] && sha256hash=$(shasum -a "$cstype" "$filepath" | awk '{print $1}') 1179 | localhash="$sha256hash" 1180 | vthash="$sha256hash" 1181 | else 1182 | if $vtaccess && [[ $vthash == "" ]] ; then 1183 | if [[ $sha256hash == "" ]] ; then 1184 | sha256hash=$(shasum -a 256 "$filepath" | awk '{print $1}') 1185 | vthash="$sha256hash" 1186 | else 1187 | vthash="$sha256hash" 1188 | fi 1189 | fi 1190 | if [[ $cstype == "1" ]] ; then 1191 | [[ $sha1hash == "" ]] && sha1hash=$(shasum -a "$cstype" "$filepath" | awk '{print $1}') 1192 | localhash="$sha1hash" 1193 | elif [[ $cstype == "512" ]] ; then 1194 | [[ $sha512hash == "" ]] && sha512hash=$(shasum -a "$cstype" "$filepath" | awk '{print $1}') 1195 | localhash="$sha512hash" 1196 | elif [[ $cstype == "384" ]] ; then 1197 | [[ $sha384hash == "" ]] && sha384hash=$(shasum -a "$cstype" "$filepath" | awk '{print $1}') 1198 | localhash="$sha384hash" 1199 | elif [[ $cstype == "224" ]] ; then 1200 | [[ $sha224hash == "" ]] && sha224hash=$(shasum -a "$cstype" "$filepath" | awk '{print $1}') 1201 | localhash="$sha224hash" 1202 | fi 1203 | fi 1204 | fi 1205 | if [[ $clipsum == $localhash ]] ; then 1206 | echo -e "clipped hash:\t$thnote:\t$localhash" >> "$tmploc" 1207 | echo -e "\t\tstatus:\tverified\n" >> "$tmploc" 1208 | _notify "✅ $hnote verified" "$filename" 1209 | compresult=true 1210 | else 1211 | if ! $hfsfound && ! $hfmfound ; then 1212 | _beep 1213 | _notify "❌ $hnote mismatch!" "Rehashing: $filename" 1214 | fi 1215 | echo -e "clipped hash:\t$thnote:\t$clipsum" >> "$tmploc" 1216 | echo -e "\t\tstatus:\tMISMATCH" >> "$tmploc" 1217 | fi 1218 | echo "" | pbcopy 1219 | if ! $compresult ; then # comparison with no results 1220 | if $hfsfound || $hfmfound ; then 1221 | cliperror=true 1222 | clipnote="$hnote" 1223 | _cshcomp 1224 | else 1225 | _hashes "$filepath" 1226 | fi 1227 | fi 1228 | } 1229 | 1230 | # ClamAV scan function 1231 | _clamav () { 1232 | clamlist=$("$clampath" -iz --block-encrypted=yes --block-macros=yes --detect-broken=yes --detect-pua=yes --no-summary "$1") 1233 | if [[ $clamlist != "" ]] ; then 1234 | clamlist=$(echo "$clamlist" | awk -F": " '{print substr($0, index($0,$2))}' | sed 's/ FOUND$//g' | grep -v "^$") 1235 | clamhits=$(echo "$clamlist" | wc -l | xargs) 1236 | clamsingle=false 1237 | if [ $clamhits -eq 1 ] ; then # only one result 1238 | clamfind="$clamlist" 1239 | clamsingle=true 1240 | else # multiple results 1241 | clamfind=$(echo "$clamlist" | uniq -c | sort | awk '{print substr($0, index($0,$2))}') # boil down 1242 | clamhits=$(echo "$clamfind" | wc -l | xargs) 1243 | clamsinglemulti=false 1244 | clammulti=false 1245 | if [ $clamhits -eq 1 ] ; then # multiple instances of one result 1246 | clamsinglemulti=true 1247 | else # multiple results 1248 | clammulti=true 1249 | clamchlist="$clamfind" 1250 | fi 1251 | fi 1252 | clamother=false 1253 | if [[ $(echo "$clamfind" | grep "^Heuristics.OLE2.ContainsMacros") != "" ]] ; then 1254 | _beep 1255 | _notify "⚠️ ClamAV: potential macro issue!" "$filename" 1256 | $clammulti && clamchlist=$(echo "$clamchlist" | grep -v "^Heuristics.OLE2.ContainsMacros") 1257 | clamother=true 1258 | clamstring="POTENTIAL MACRO ISSUE" 1259 | fi 1260 | if [[ $(echo "clamfind" | grep "^Broken.Executable") != "" ]] ; then 1261 | _beep 1262 | _notify "⚠️ ClamAV: broken executable!" "$filename" 1263 | $clammulti && clamchlist=$(echo "$clamchlist" | grep -v "^Broken.Executable") 1264 | clamother=true 1265 | clamstring="ISSUE" 1266 | fi 1267 | if [[ $(echo "$clamfind" | grep "^Encrypted\.") != "" ]] ; then 1268 | _beep 1269 | _notify "⚠️ ClamAV: encrypted file!" "$filename" 1270 | $clammulti && clamchlist=$(echo "$clamchlist" | grep -v "^Encrypted\.") 1271 | clamother=true 1272 | clamstring="ISSUE" 1273 | fi 1274 | if [[ $(echo "$clamfind" | grep "^PUA\.") != "" ]] ; then 1275 | _beep 1276 | _notify "⚠️ ClamAV: PUA detected!" "$filename" 1277 | $clammulti && clamchlist=$(echo "$clamchlist" | grep -v "^PUA\.") 1278 | clamother=true 1279 | clamstring="PUA DETECTED" 1280 | fi 1281 | if $clamsingle || $clamsinglemulti ; then # only one ClamAV result 1282 | if ! $clamother ; then # malware detected 1283 | _beep 1284 | _notify "☠️ ClamAV: malware detected!" "$filename" 1285 | echo -e "ClamAV:\t\tMALWARE DETECTED: $clamfind" >> "$tmploc" 1286 | else # no malware detected, but potential PUA, macro etc. 1287 | echo -e "ClamAV:\t\t$clamstring: $clamfind" >> "$tmploc" 1288 | fi 1289 | else # more than one ClamAV result 1290 | if ! $clamother ; then # only malware results 1291 | _beep 1292 | _notify "☠️ ClamAV: malware detected!" "$filename" 1293 | echo -e "ClamAV:\t\tMULTIPLE MALWARE RESULTS:" >> "$tmploc" 1294 | count=1 1295 | while read -r clamresult 1296 | do 1297 | echo -e "\t\t#$count: $clamresult" >> "$tmploc" 1298 | (( count++ )) 1299 | done < <(echo "$clamfind") 1300 | else # other results like PUA etc. and possibly malware 1301 | # check first if malware remains 1302 | clamremhits=$(echo "$clamchlist" | grep -v "^$" | wc -l | xargs) 1303 | if [[ $clamremhits == "" ]] || [[ $clamremhits == "0" ]] ; then # only other results 1304 | echo -e "ClamAV:\t\tMULTIPLE ISSUES:" >> "$tmploc" 1305 | else # malware also detected 1306 | _beep 1307 | echo -e "ClamAV:\t\tMULTIPLE MALWARE RESULTS & ISSUES:" >> "$tmploc" 1308 | _notify "☠️ ClamAV: malware detected!" "$filename" 1309 | fi 1310 | count=1 1311 | while read -r clamresult 1312 | do 1313 | echo -e "\t\t#$count: $clamresult" >> "$tmploc" 1314 | (( count++ )) 1315 | done < <(echo "$clamfind") 1316 | fi 1317 | fi 1318 | echo "" >> "$tmploc" 1319 | else 1320 | echo -e "ClamAV:\t\tno results\n" >> "$tmploc" 1321 | fi 1322 | } 1323 | 1324 | # ping VirusTotal for online/offline check 1325 | _vtping () { 1326 | (( pings = 3 )) 1327 | while [[ $pings -ne 0 ]] 1328 | do 1329 | ping -q -c 1 "virustotal.com" &>/dev/null 1330 | rc=$? 1331 | [[ $rc -eq 0 ]] && (( pings = 1 )) 1332 | (( pings = pings - 1 )) 1333 | done 1334 | if [[ $rc -eq 0 ]] ; then 1335 | vtaccess=true 1336 | else 1337 | vtaccess=false 1338 | fi 1339 | } 1340 | 1341 | # access Virus Total function 1342 | _virustotal () { 1343 | vtopen=false 1344 | vtresults=$(curl --connect-timeout 10 -s -X POST 'https://www.virustotal.com/vtapi/v2/file/report' --form apikey="$vtkey" --form resource="$1" 2>/dev/null \ 1345 | | awk -F"}" -v OFS="}\n" ' $1=$1 {print}' | sed -e 's/^{\"scans\": {//g' -e 's/^, //g' | grep -v "^}$") 1346 | vthits=$(echo "$vtresults" | awk -F'positives\":' '{print $2}' | awk -F, '{print $1}' | sed "s/^[ \t]*//" | xargs) 1347 | if [[ $vthits == "" ]] || [[ $vthits == "0" ]] ; then 1348 | echo -e "VirusTotal:\tno hits" >> "$tmploc" 1349 | else 1350 | vtdetects=$(echo "$vtresults" | grep ": {\"detected\": true," | awk -F\" '{print $12}' | sort) 1351 | vtmajority=$(echo "$vtdetects" | uniq -c | sort | sed -e '$!d' | awk '{print substr($0, index($0,$2))}') 1352 | _beep 1353 | if [ $vthits -gt 5 ] ; then 1354 | _notify "☠️ VirusTotal: malware detected!" "$filename" 1355 | echo -e "VirusTotal:\tMALWARE DETECTED: $vtmajority ($vthits hits)" >> "$tmploc" 1356 | else 1357 | if [[ $vthits == "1" ]] ; then 1358 | _notify "⚠️ VirusTotal: malware issue!" "$filename" 1359 | echo -e "VirusTotal:\tMALWARE ISSUE: $vtmajority ($vthits hit)" >> "$tmploc" 1360 | elif [[ $vthits == "2" ]] ; then 1361 | _notify "⚠️ VirusTotal: malware issues!" "$filename" 1362 | echo -e "VirusTotal:\tMALWARE ISSUES: $vtmajority ($vthits hits)" >> "$tmploc" 1363 | elif [[ $vthits == "3" ]] ; then 1364 | _notify "⚠️ VirusTotal: tentative malware!" "$filename" 1365 | echo -e "VirusTotal:\tTENTATIVE MALWARE: $vtmajority ($vthits hits)" >> "$tmploc" 1366 | elif [[ $vthits == "4" ]] ; then 1367 | _notify "⚠️ VirusTotal: possible malware!" "$filename" 1368 | echo -e "VirusTotal:\tPOSSIBLE MALWARE: $vtmajority ($vthits hits)" >> "$tmploc" 1369 | elif [[ $vthits == "5" ]] ; then 1370 | _notify "⚠️ VirusTotal: probable malware!" "$filename" 1371 | echo -e "VirusTotal:\tPROBABLE MALWARE: $vtmajority ($vthits hits)" >> "$tmploc" 1372 | fi 1373 | fi 1374 | vtopen=true 1375 | fi 1376 | ! $clamav && echo "" >> "$tmploc" 1377 | } 1378 | 1379 | # set standard .app Info.plist subpaths 1380 | iplistpath="/Contents/Info.plist" 1381 | iplistlocs="Contents/Info.plist\nResources/Info.plist\nInfo.plist" 1382 | 1383 | workingdir="$PWD" 1384 | 1385 | for filepath in "$@" 1386 | do 1387 | 1388 | sha256hash="" 1389 | vthash="" 1390 | localhash="" 1391 | sha1hash="" 1392 | md5hash="" 1393 | sha512hash="" 1394 | sha384hash="" 1395 | sha224hash="" 1396 | 1397 | filepath=$(abspath "$filepath") 1398 | 1399 | scandate=$(date) 1400 | 1401 | shortpath="${filepath/#$HOME/~}" 1402 | 1403 | parentdir=$(dirname "$filepath") 1404 | 1405 | filename=$(basename "$filepath") 1406 | shortname="${filename%.*}" 1407 | extension="${filename##*.}" 1408 | 1409 | # look for file-specific icons 1410 | if [[ -d "$filepath" ]] ; then 1411 | # first check if directory is empty 1412 | if ! [[ $(ls -A "$filepath") ]] ; then 1413 | echo "wys: directory $filename is empty." >&2 1414 | _notify "Error: empty directory" "$filename" 1415 | continue 1416 | fi 1417 | if [[ $extension =~ ^(app|APP)$ ]] ; then 1418 | iconfile=$(defaults read "$filepath$iplistpath" CFBundleIconFile 2>/dev/null) 1419 | [[ $iconfile != *"."* ]] && iconfile="$iconfile.icns" 1420 | icon_loc="$filepath/Contents/Resources/$iconfile" 1421 | ! [[ -f "$icon_loc" ]] && icon_loc="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns" 1422 | elif [[ $extension == "prefPane" ]] ; then 1423 | iconfile=$(defaults read "$filepath$iplistpath" CFBundleIconFile 2>/dev/null) 1424 | [[ $iconfile != *"."* ]] && iconfile="$iconfile.icns" 1425 | icon_loc="$filepath/Contents/Resources/$iconfile" 1426 | if ! [[ -f "$icon_loc" ]] ; then 1427 | iconfile=$(defaults read "$filepath$iplistpath" NSPrefPaneIconFile 2>/dev/null) 1428 | [[ $iconfile != *"."* ]] && iconfile="$iconfile.icns" 1429 | icon_loc="$filepath/Contents/Resources/$iconfile" 1430 | ! [[ -f "$icon_loc" ]] && icon_loc="/Applications/System Preferences.app/Contents/Resources/PrefFile.icns" 1431 | fi 1432 | elif [[ $extension =~ ^(sparsebundle|SPARSEBUNDLE)$ ]] ; then 1433 | icon_loc="/System/Library/CoreServices/DiskImageMounter.app/Contents/Resources/diskcopy-doc.icns" 1434 | elif [[ $extension =~ ^(framework|kext|bundle|FRAMEWORK|KEXT|BUNDLE)$ ]] ; then # apparently there's no default icon for .bundle 1435 | icon_loc="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/KEXT.icns" 1436 | fi 1437 | elif [[ -f "$filepath" ]] ; then 1438 | if [[ $extension =~ ^(dmg|sparseimage|DMG|SPARSEIMAGE)$ ]] ; then 1439 | icon_loc="/System/Library/CoreServices/DiskImageMounter.app/Contents/Resources/diskcopy-doc.icns" 1440 | elif [[ $extension =~ ^(pkg|PKG)$ ]] ; then 1441 | icon_loc="/System/Library/CoreServices/Installer.app/Contents/Resources/package.icns" 1442 | elif [[ $extension =~ ^(mpkg|MPKG)$ ]] ; then 1443 | icon_loc="/System/Library/CoreServices/Installer.app/Contents/Resources/metapackage.icns" 1444 | elif [[ $extension =~ ^(xip|xar|XIP|XAR)$ ]] ; then # apparently there's no default icon for .xar 1445 | icon_loc="/System/Library/CoreServices/Applications/Archive Utility.app/Contents/Resources/bah-xip.icns" 1446 | else 1447 | if [[ -f "/System/Library/CoreServices/Applications/Archive Utility.app/Contents/Resources/bah-$extension.icns" ]] ; then 1448 | extvar=$(echo "$extension" | tr '[:upper:]' '[:lower:]') 1449 | icon_loc="/System/Library/CoreServices/Applications/Archive Utility.app/Contents/Resources/bah-$extvar.icns" 1450 | fi 1451 | fi 1452 | fi 1453 | 1454 | # filepath checks 1455 | if ! [[ -e "$filepath" ]] ; then 1456 | _beep 1457 | echo "File does not exist: $filepath" >&2 1458 | _notify "❓ File not found!" "$filename" 1459 | continue 1460 | fi 1461 | if ! [[ -r "$filepath" ]] ; then 1462 | _beep 1463 | echo "File is not readable: $filepath" >&2 1464 | _notify "❌ File not readable!" "$filename" 1465 | continue 1466 | fi 1467 | 1468 | # precheck for "Folder" 1469 | mdlsall=$(mdls "$filepath" 2>/dev/null) 1470 | itemtype=$(echo "$mdlsall" | awk -F"= " '/^kMDItemKind/{print $2}' | sed 's/^"\(.*\)"$/\1/') 1471 | if [[ $itemtype =~ ^(Folder| Folder)$ ]] ; then 1472 | _beep 1473 | _notify "❌ Plain folder: aborting scan…" "$filename" 1474 | continue 1475 | fi 1476 | 1477 | _notify "Scanning..." "$filename" 1478 | 1479 | touch "$filepath" 2>/dev/null 1480 | 1481 | # initial stuff 1482 | pkgu=false 1483 | clipped=false 1484 | cliperror=false 1485 | smany=false ### DELETE FOR DEEP SCAN 1486 | umany=false 1487 | idmatch=false 1488 | 1489 | cfbid="" 1490 | csid="" 1491 | skid="" 1492 | oldskid="" 1493 | 1494 | posixtime=$(date +%s) 1495 | 1496 | tmploc="/tmp/$filename-$posixtime.log" 1497 | ptmploc="/tmp/$filename-$posixtime.plist" 1498 | stmploc="/tmp/$filename-$posixtime-os.log" ### DELETE FOR DEEP SCAN 1499 | utmploc="/tmp/$filename-$posixtime-us.log" 1500 | 1501 | [[ $extension =~ ^(pkg|mpkg|xip|xar|PKG|MPKG|XIP|XAR)$ ]] && pkgu=true 1502 | 1503 | echo "$filename" > "$tmploc" 1504 | 1505 | echo -e "$shortpath\n" >> "$tmploc" 1506 | 1507 | # determine file type (file) 1508 | filetype=$(file "$filepath" | awk -F": " '{print substr($0, index($0,$2))}') 1509 | if [[ $filetype != "directory" ]] ; then 1510 | 1511 | # paste clipboard and check for hash 1512 | clipsum=$(pbpaste | xargs 2>/dev/null) 1513 | cstype=$(_cscn "$clipsum") 1514 | if [[ $cstype != "none" ]] ; then 1515 | clipped=true 1516 | else 1517 | cstype="" 1518 | clipsum="" 1519 | fi 1520 | 1521 | # file size (single/regular files only) 1522 | bsize=$(stat -f%z "$filepath") 1523 | [ $bsize -gt 100000000 ] && _notify "Large file: please wait..." "$filename" 1524 | _sizes 1525 | echo -e "data size:\t$hrbsize B [$mbsize MB | $mibsize MiB]\n" >> "$tmploc" 1526 | else # disk usage (directory files only) 1527 | dirsize=$(du -schk "$filepath" | awk '/total$/{print $1}') 1528 | [ $dirsize -gt 100000 ] && _notify "Large file: please wait..." "$filename" 1529 | bsize=$(echo "$dirsize * 1024" | bc) 1530 | _sizes 1531 | echo -e "size on disk:\t$hrbsize B [$mbsize MB | $mibsize MiB]\n" >> "$tmploc" 1532 | fi 1533 | 1534 | echo -e "file type:\t$filetype" >> "$tmploc" 1535 | 1536 | # determine item type (mdls) 1537 | if [[ $itemtype == "" ]] ; then 1538 | if [[ $filetype != "directory" ]] ; then 1539 | echo -e "item type:\tn/a\n" >> "$tmploc" 1540 | else # mdls (mostly?) fails on mounted read-only volumes & new files, i.e read from bundle plist 1541 | while read -r iplistloc 1542 | do 1543 | pclass=$(defaults read "$filepath/$iplistloc" NSPrincipalClass 2>/dev/null | awk '!seen[$0]++') 1544 | [[ $pclass != "" ]] && break 1545 | done < <(echo -e "$iplistlocs") 1546 | if [[ $pclass == "" ]] ; then 1547 | while read -r iplistloc 1548 | do 1549 | ptype=$(defaults read "$filepath/$iplistloc" CFBundlePackageType 2>/dev/null | awk '!seen[$0]++') 1550 | [[ $ptype != "" ]] && break 1551 | done < <(echo -e "$iplistlocs") 1552 | if [[ $ptype == "" ]] ; then 1553 | echo -e "item type:\tn/a\n" >> "$tmploc" 1554 | else 1555 | echo -e "package type:\t$ptype\n" >> "$tmploc" 1556 | fi 1557 | else 1558 | echo -e "class:\t\t$pclass\n" >> "$tmploc" 1559 | fi 1560 | fi 1561 | else 1562 | echo -e "item type:\t$itemtype\n" >> "$tmploc" 1563 | fi 1564 | 1565 | # read version & build numbers 1566 | if [[ $filetype == "directory" ]] ; then 1567 | version="" 1568 | while read -r iplistloc 1569 | do 1570 | version=$(defaults read "$filepath/$iplistloc" CFBundleShortVersionString 2>/dev/null | awk '!seen[$0]++' | xargs) 1571 | [[ $version != "" ]] && break 1572 | done < <(echo -e "$iplistlocs") 1573 | ! [[ $version ]] && version="n/a" 1574 | build="" 1575 | while read -r iplistloc 1576 | do 1577 | build=$(defaults read "$filepath/$iplistloc" CFBundleVersion 2>/dev/null | awk '!seen[$0]++' | xargs) 1578 | [[ $build != "" ]] && break 1579 | done < <(echo -e "$iplistlocs") 1580 | ! [[ $build ]] && build="n/a" 1581 | echo -e "version:\t$version ($build)\n" >> "$tmploc" 1582 | fi 1583 | 1584 | if ! $pkgu ; then # apps & other bundles plus DMGs 1585 | 1586 | unsigned=false 1587 | 1588 | # read code signature and dump certificates to /tmp 1589 | rm -f /tmp/codesign+([0-9]) 2>/dev/null 1590 | cd /tmp 1591 | csign=$(codesign -dvvvv --requirements - --entitlements - --extract-certificates "$filepath" 2>&1) 1592 | cd / 1593 | 1594 | # check if codesigned 1595 | if [[ $(echo "$csign" | grep "is not signed at all") != "" ]] || [[ $(echo "$csign" | grep "unrecognized, invalid, or unsuitable") != "" ]] ; then # for unsigned items 1596 | unsigned=true 1597 | else 1598 | unsigned=false 1599 | fi 1600 | 1601 | # deal with extracted DER certificates 1602 | certfsize=$(stat -f%z /tmp/codesign0 2>/dev/null) 1603 | if [[ -f /tmp/codesign0 ]] && [ $certfsize -gt 0 ] ; then 1604 | 1605 | # read subject key identifier 1606 | skid=$(openssl x509 -in /tmp/codesign0 -inform DER -noout -text -fingerprint | grep -A1 "Subject Key Identifier" | tail -1 | xargs | sed s/://g) 1607 | [[ $skid == "" ]] && skid="SKID_NOT_AVAILABLE" 1608 | 1609 | # direct certificate verification using keychain utility (security) 1610 | certsecall=$(security verify-cert -c /tmp/codesign0 2>&1) 1611 | if [[ $(echo "$certsecall" | grep "Cert Verify Result:") != "" ]] ; then 1612 | certsec=$(echo "$certsecall" | awk -F": " '{print $2}') 1613 | elif [[ $certsecall == "...certificate verification successful." ]] ; then 1614 | certsec="successful" 1615 | else 1616 | certsec="$certsecall" 1617 | fi 1618 | if [[ $(echo "$certsec" | grep "REVOKED") != "" ]] ; then 1619 | _beep 1620 | _notify "❌ Certificate revoked!" "$filename" 1621 | elif [[ $(echo "$certsec" | grep "NOT_TRUSTED") != "" ]] ; then 1622 | _beep 1623 | _notify "❌ Certificate not trusted!" "$filename" 1624 | elif [[ $(echo "$certsec" | grep "kSecTrustResultDeny") != "" ]] ; then 1625 | _beep 1626 | _notify "❌ Certificate denied trust!" "$filename" 1627 | fi 1628 | else 1629 | if $unsigned ; then 1630 | skid="UNSIGNED" 1631 | certsec="$skid" 1632 | else 1633 | if [[ $(echo "$csign" | grep "^Signature=adhoc") != "" ]] ; then 1634 | skid="SKID_NOT_AVAILABLE" 1635 | certsec="n/a" 1636 | else 1637 | _beep 1638 | skid="CS_CERTS_MISSING" 1639 | certsec="POSSIBLE MALWARE: $skid" 1640 | _notify "⚠️ Possible malware!" "$filename" 1641 | fi 1642 | fi 1643 | fi 1644 | rm -f /tmp/codesign+([0-9]) 2>/dev/null 1645 | 1646 | # identifier in the code signature 1647 | csid=$(echo "$csign" | grep "^Identifier=" | awk -F= '{print $2}') 1648 | 1649 | # determine executable 1650 | if $unsigned ; then # for unsigned items 1651 | if [[ $filetype != "directory" ]] ; then # no bundle 1652 | executable="" 1653 | executablename="" 1654 | else # maybe a bundle 1655 | executablename=$(defaults read "$filepath/Contents/Info.plist" CFBundleExecutable 2>/dev/null) 1656 | if [[ $executablename != "" ]] ; then 1657 | executable="$filepath/Contents/MacOS/$executablename" 1658 | else 1659 | ! [[ -h "$filepath/Resources" ]] && executablename=$(defaults read "$filepath/Resources/Info.plist" CFBundleExecutable 2>/dev/null) 1660 | if [[ $executablename != "" ]] ; then 1661 | executable="$filepath/$executablename" 1662 | if ! [[ -f "$executable" ]] ; then 1663 | executable="$filepath/Resources/$executablename" 1664 | if ! [[ -f "$executable" ]] ; then 1665 | executable="$filepath/MacOS/$executable" 1666 | ! [[ -f "$executable" ]] && executable="none" 1667 | fi 1668 | fi 1669 | else 1670 | executablename=$(defaults read "$filepath/Versions/Current/Resources/Info.plist" CFBundleExecutable 2>/dev/null) 1671 | if [[ $executablename != "" ]] ; then 1672 | iplistrealpath=$(abspath "$filepath/Versions/Current/Resources/Info.plist") 1673 | iparentdir=$(dirname "$iplistrealpath") 1674 | execdir=$(dirname "$iparentdir") 1675 | executable="$execdir/$executablename" 1676 | else 1677 | executablename=$(defaults read "$filepath/Info.plist" CFBundleExecutable 2>/dev/null) 1678 | if [[ $executablename != "" ]] ; then 1679 | executable="$filepath/$executablename" 1680 | ! [[ -f "$executable" ]] && executable="none" 1681 | else 1682 | foundplist=(find "$filepath" -mindepth 1 -type f -wholename 'Info.plist' -print -quit) 1683 | executablename=$(defaults read "$foundplist" CFBundleExecutable 2>/dev/null) 1684 | if [[ $executablename != "" ]] ; then 1685 | iparentdir=$(dirname "$foundplist") 1686 | execdir=$(dirname "$iparentdir") 1687 | executable="$execdir/$executablename" 1688 | ! [[ -f "$executable" ]] && executable="none" 1689 | else 1690 | executable="none" 1691 | fi 1692 | fi 1693 | fi 1694 | fi 1695 | fi 1696 | fi 1697 | else # for signed items 1698 | if [[ $filetype == "directory" ]] ; then # maybe a bundle 1699 | executable=$(echo "$csign" | awk -F= '/Executable/{print substr($0, index($0,$2))}') 1700 | executable=$(abspath "$executable" 2>/dev/null) 1701 | if [[ $executable == "" ]] ; then 1702 | executablename=$(defaults read "$filepath/Contents/Info.plist" CFBundleExecutable 2>/dev/null) 1703 | if [[ $executablename != "" ]] ; then 1704 | executable="$filepath/Contents/MacOS/$executablename" 1705 | else 1706 | ! [[ -h "$filepath/Resources" ]] && executablename=$(defaults read "$filepath/Resources/Info.plist" CFBundleExecutable 2>/dev/null) 1707 | if [[ $executablename != "" ]] ; then 1708 | altiplistpath="$filepath/Resources/Info.plist" 1709 | executable="$filepath/$executablename" 1710 | if ! [[ -f "$executable" ]] ; then 1711 | executable="$filepath/Resources/$executablename" 1712 | if ! [[ -f "$executable" ]] ; then 1713 | executable="$filepath/MacOS/$executable" 1714 | ! [[ -f "$executable" ]] && executable="none" 1715 | fi 1716 | fi 1717 | else 1718 | executablename=$(defaults read "$filepath/Versions/Current/Resources/Info.plist" CFBundleExecutable 2>/dev/null) 1719 | if [[ $executablename != "" ]] ; then 1720 | altiplistpath=$(abspath "$filepath/Versions/Current/Resources/Info.plist") 1721 | iparentdir=$(dirname "$altiplistpath") 1722 | execdir=$(dirname "$iparentdir") 1723 | executable="$execdir/$executablename" 1724 | else 1725 | executablename=$(defaults read "$filepath/Info.plist" CFBundleExecutable 2>/dev/null) 1726 | if [[ $executablename != "" ]] ; then 1727 | altiplistpath="$filepath/Info.plist" 1728 | executable="$filepath/$executablename" 1729 | ! [[ -f "$executable" ]] && executable="none" 1730 | else 1731 | foundplist=(find "$filepath" -mindepth 1 -type f -wholename 'Info.plist' -print -quit) 1732 | executablename=$(defaults read "$foundplist" CFBundleExecutable 2>/dev/null) 1733 | if [[ $executablename != "" ]] ; then 1734 | altiplistpath="$foundplist" 1735 | iparentdir=$(dirname "$foundplist") 1736 | execdir=$(dirname "$iparentdir") 1737 | executable="$execdir/$executablename" 1738 | ! [[ -f "$executable" ]] && executable="none" 1739 | else 1740 | executable="none" 1741 | fi 1742 | fi 1743 | fi 1744 | fi 1745 | fi 1746 | else 1747 | executablename=$(basename "$executable") 1748 | if ! [[ -f "$filepath$iplistpath" ]] ; then 1749 | altiplistpath="$filepath/Resources/Info.plist" 1750 | if ! [[ -f "$altiplistpath" ]] ; then 1751 | altiplistpath="$filepath/Info.plist" 1752 | if ! [[ -f "$altiplistpath" ]] ; then 1753 | altiplistpath=$(find "$filepath" -mindepth 1 -type f -wholename 'Info.plist' -print -quit) 1754 | fi 1755 | fi 1756 | fi 1757 | fi 1758 | else # simple file, e.g. a CLI 1759 | executable="" 1760 | executablename="" 1761 | fi 1762 | fi 1763 | 1764 | # scan for files with executable permissions 1765 | if [[ $filetype == "directory" ]] && [[ $executable != "" ]] ; then 1766 | [[ $executable != "none" ]] && shortexec=$(echo "$executable" | sed "s:^$filepath::") 1767 | cd "$filepath" 1768 | # regular files only, executable permissions, don't follow symlinks 1769 | execfiles=$(find -P . -mindepth 1 -type f -perm +111 -print | sed 's/^\.//g' | sort) 1770 | cd / 1771 | [[ $executable != "none" ]] && execfiles=$(echo "$execfiles" | grep -v "^$shortexec$") 1772 | xfnumber=$(echo "$execfiles" | wc -l | xargs) 1773 | [ $xfnumber -gt 100 ] && _notify "$xfnumber executable files: please wait..." "$filename" 1774 | fi 1775 | 1776 | # check image encryption & verify 1777 | if [[ $extension =~ ^(dmg|sparsebundle|sparseimage)$ ]] ; then 1778 | encrypted=$(hdiutil isencrypted "$filepath" 2>&1 | awk -F": " '/^encrypted/{print $2}') 1779 | if [[ $encrypted == "NO" ]] ; then 1780 | hdinfo=$(hdiutil imageinfo "$filepath" 2>&1) 1781 | if [[ $(echo "$hdinfo" | grep "image not recognized$") != "" ]] ; then 1782 | echo -e "hdiutil:\tFAILED: image not recognized\n" >> "$tmploc" 1783 | _beep 1784 | _notify "❌ Image not recognized!" "$filename" 1785 | else 1786 | hdiverify=$(hdiutil verify "$filepath" 2>&1) 1787 | if [[ $(echo "$hdiverify" | grep "has no checksum\.$") != "" ]] ; then 1788 | echo -e "hdiutil:\tNO CHECKSUM" >> "$tmploc" 1789 | else 1790 | hdiresult=$(echo "$hdiverify" | grep "^hdiutil: verify: checksum" | rev | awk '{print $1}' | rev) 1791 | if [[ $hdiresult == "INVALID" ]] ; then 1792 | hdihash=$(echo "$hdiverify" | grep "^calculated" | xargs) 1793 | _beep 1794 | _notify "❌ Image verification invalid!" "$filename" 1795 | else 1796 | hdihash=$(echo "$hdiverify" | grep "^verified" | awk '{print substr($0, index($0,$2))}') 1797 | fi 1798 | echo -e "hdiutil:\t$hdiresult: $hdihash" >> "$tmploc" 1799 | fi 1800 | hdiformat=$(echo "$hdinfo" | awk -F": " '/^Format:/{print substr($0, index($0,$2))}' | xargs) 1801 | hdidescr=$(echo "$hdinfo" | awk -F": " '/^Format Description:/{print substr($0, index($0,$2))}' | xargs) 1802 | hdifs=$(echo "$hdinfo" | grep -A1 "partition-filesystems:" | tail -1 | xargs | awk -F: '{print $1}') 1803 | if [[ $hdifs == "" ]] ; then 1804 | hdifs=$(echo "$hdinfo" | grep "Name: disk image" | awk -F"(" '{print $2}' | awk -F: '{print $1}' | xargs) 1805 | if [[ $hdifs == "" ]] ; then 1806 | hdifs=$(echo "$hdinfo" | grep "Name: Mac_OS_X" | awk -F"(" '{print $2}' | awk -F: '{print $1}' | xargs) 1807 | if [[ $hdifs == "" ]] ; then 1808 | hdifs=$(echo "$hdinfo" | grep "partition-hint: " | grep "Apple_" | grep -v "Apple_Free" | grep -v "Apple_Driver" | grep -v "Apple_partition" | awk -F": " '{print $2}' | xargs) 1809 | fi 1810 | fi 1811 | fi 1812 | echo -e "\t\t$hdiformat: $hdidescr $hdifs\n" >> "$tmploc" 1813 | fi 1814 | else 1815 | echo -e "hdiutil:\tENCRYPTED: cannot scan\n" >> "$tmploc" 1816 | fi 1817 | fi 1818 | 1819 | # print download sources & quarantine check 1820 | dlflist=$(xattr -p com.apple.metadata:kMDItemWhereFroms "$filepath" 2>/dev/null | xxd -r -p | plutil -p - | grep -v "^\[" | grep -v "^\]" | awk -F\" '{print $2}' | awk -F/ '{print $1"//"$3}' | awk '!seen[$0]++' | grep -v "^//") 1821 | qxattr=$(xattr -p com.apple.quarantine "$filepath" 2>/dev/null | sed -e 's/;/ /g' -e 's/|/ /g') 1822 | if [[ $dlflist != "" ]] && [[ $dlflist != "//" ]] ; then 1823 | count=1 1824 | while read -r dldomain 1825 | do 1826 | if [[ $count == 1 ]] ; then 1827 | echo -e "downloaded:\t$dldomain" >> "$tmploc" 1828 | else 1829 | echo -e "\t\t$dldomain" >> "$tmploc" 1830 | fi 1831 | (( count++ )) 1832 | done < <(echo "$dlflist" | tail -r) 1833 | if [[ $qxattr != "" ]] ; then 1834 | echo -e "quarantine:\t$qxattr\n" >> "$tmploc" 1835 | else 1836 | echo "" >> "$tmploc" 1837 | fi 1838 | else 1839 | [[ $qxattr != "" ]] && echo -e "quarantine:\t$qxattr\n" >> "$tmploc" 1840 | fi 1841 | 1842 | # hashes/checksums 1843 | if [[ $executable == "none" ]] ; then 1844 | echo -e "hashes:\t\tn/a\n" >> "$tmploc" 1845 | else 1846 | if [[ $filetype == "directory" ]] ; then # not a single file: need to hash the executable 1847 | if [[ -h "$executable" ]] ; then 1848 | execsym="$executable" 1849 | executable=$(abspath "$executable") 1850 | else 1851 | execsym="" 1852 | fi 1853 | execsub=$(echo "$executable" | sed "s:^$filepath:\.:") 1854 | echo -e "executable:\t$execsub" >> "$tmploc" 1855 | _hashes "$executable" 1856 | else # single file: hash comparsions/calculations 1857 | # look for checksum files 1858 | _csfind 1859 | if ! $clipped ; then # no clipboard checksum 1860 | if $hfmfound || $hfsfound ; then # found & will parse checksum files 1861 | _cshcomp 1862 | else # found no checksum files 1863 | _hashes "$filepath" 1864 | fi 1865 | else # clipboard hash check 1866 | _cscomp 1867 | fi 1868 | fi 1869 | fi 1870 | 1871 | # gpg scan 1872 | if $gnupg && [[ $filetype != "directory" ]] ; then 1873 | _gpgval 1874 | fi 1875 | 1876 | # check hash at VirusTotal 1877 | if $vtaccess ; then 1878 | _vtping 1879 | if ! $vtaccess ; then 1880 | echo "wys: could not reach VirusTotal." >&2 1881 | _notify "Error: VirusTotal offline" "Not connected to the internet?" 1882 | echo -e "VirusTotal:\tn/a (offline)" >> "$tmploc" 1883 | ! $clamav && echo "" >> "$tmploc" 1884 | else 1885 | if [[ $vthash != "" ]] ; then 1886 | _virustotal "$vthash" 1887 | $vtopen && open "https://www.virustotal.com/#/file/$vthash" 1888 | fi 1889 | fi 1890 | fi 1891 | 1892 | # clamscan 1893 | if $clamav ; then 1894 | if [[ $filetype == "directory" ]] ; then 1895 | if [[ $executable != "" ]] && [[ $executable != "none" ]] ; then 1896 | _clamav "$executable" 1897 | fi 1898 | else 1899 | _clamav "$filepath" 1900 | fi 1901 | fi 1902 | 1903 | # bundle & signature identifiers 1904 | if ! $unsigned ; then 1905 | if [[ $filetype == "directory" ]] ; then # determine bundle identifier or fallback bundle name 1906 | cfbid=$(defaults read "$filepath$iplistpath" CFBundleIdentifier 2>/dev/null) 1907 | if [[ $cfbid == "" ]] ; then 1908 | cfbid=$(defaults read "$altiplistpath" CFBundleIdentifier 2>/dev/null) 1909 | if [[ $cfbid == "" ]] ; then 1910 | cfbid=$(defaults read "$filepath$iplistpath" CFBundleName 2>/dev/null) 1911 | if [[ $cfbid == "" ]] ; then 1912 | cfbid=$(defaults read "$altiplistpath" CFBundleName 2>/dev/null) 1913 | if [[ $cfbid == "" ]] ; then # final fallback: shortname (filename without extension) 1914 | cfbid="$shortname" 1915 | fi 1916 | fi 1917 | fi 1918 | fi 1919 | if [[ $cfbid == $csid ]] ; then 1920 | echo -e "identifiers:\tmatch ($cfbid)\n" >> "$tmploc" 1921 | idmatch=true 1922 | else 1923 | echo -e "identifiers:\tMISMATCH" >> "$tmploc" 1924 | echo -e "bundle:\t\t$cfbid" >> "$tmploc" 1925 | echo -e "signature:\t$csid\n" >> "$tmploc" 1926 | _beep 1927 | _notify "⚠️ ID mismatch!" "$filename" 1928 | fi 1929 | else # single file can't have a bundle identifier, i.e. copy the ID in the code signature 1930 | cfbid="$csid" 1931 | fi 1932 | fi 1933 | 1934 | # codesign verification 1935 | msigmissing=false 1936 | msigerror=false 1937 | csreport=false 1938 | if ! $unsigned ; then 1939 | csverify_raw=$(codesign --verify --verbose=4 --deep "$filepath" 2>&1 | grep -v "^In architecture:" | grep -v "^--prepared:" | grep -v "^In subcomponent: ") 1940 | csverify_raw=$(echo "$csverify_raw" | grep -v "^--validated:" | grep -v "^$") 1941 | csverify_all=$(echo "$csverify_raw" | grep "^/" | grep -F "$filepath: " | awk -F": " '{print $2}') 1942 | # verification digest 1943 | count=1 1944 | while read -r csverify 1945 | do 1946 | if [[ $count == "1" ]] ; then 1947 | if [[ $csverify == "invalid signature (code or signature have been modified)" ]] ; then 1948 | _beep 1949 | msigerror=true 1950 | echo -e "verification:\tMAIN EXECUTABLE: $csverify" >> "$tmploc" 1951 | _notify "⚠️ Issues: main executable!" "$filename" 1952 | else 1953 | echo -e "verification:\t$csverify" >> "$tmploc" 1954 | fi 1955 | else 1956 | echo -e "\t\t$csverify" >> "$tmploc" 1957 | fi 1958 | (( count++ )) 1959 | done < <(echo "$csverify_all") 1960 | echo "" >> "$tmploc" 1961 | 1962 | fi 1963 | if ! $unsigned && ! $msigerror ; then 1964 | # populate list of missing files 1965 | fdmissing=$(echo "$csverify_raw" | grep "^file missing: " | awk -F": " '{print $2}' | sed "s-^$filepath-\.-g") 1966 | if [[ $fdmissing != "" ]] ; then 1967 | _beep 1968 | csreport=true 1969 | _notify "⚠️ Files were removed!" "$filename" 1970 | fi 1971 | # populate lists of added files 1972 | fdaddedlist=$(echo "$csverify_raw" | grep "^file added: " | awk -F": " '{print $2}') 1973 | if [[ $fdaddedlist != "" ]] ; then 1974 | _beep 1975 | csreport=true 1976 | _notify "⚠️ Files were added!" "$filename" 1977 | dadded="" 1978 | fadded="" 1979 | oadded="" 1980 | while read -r fdadded 1981 | do 1982 | fdadded=$(abspath "$fdadded") 1983 | fdacert=$(codesign -dvv "$fdadded" 2>&1 | grep "^Authority=" | awk -F= '{print $2}' | head -1) 1984 | [[ $fdacert == "" ]] && fdacert="UNSIGNED" 1985 | fdadded=$(echo "$fdadded" | sed "s-^$filepath--g") 1986 | if [[ -d "$filepath$fdadded" ]] ; then 1987 | dadded="$dadded.$fdadded: $fdacert\n" 1988 | elif [[ -f "$filepath$fdadded" ]] ; then 1989 | fadded="$fadded.$fdadded: $fdacert\n" 1990 | execfiles=$(echo "$execfiles" | grep -v "^$fdadded$") 1991 | else 1992 | oadded="$oadded.$fdadded: $fdacert\n" 1993 | fi 1994 | done < <(echo "$fdaddedlist") 1995 | fi 1996 | # populate lists of modified files 1997 | fdmoddedlist=$(echo "$csverify_raw" | grep "^file modified: " | awk -F": " '{print $2}') 1998 | if [[ $fdmoddedlist != "" ]] ; then 1999 | _beep 2000 | csreport=true 2001 | _notify "⚠️ Files were modified!" "$filename" 2002 | dmodded="" 2003 | fmodded="" 2004 | omodded="" 2005 | while read -r fdmodded 2006 | do 2007 | fdmodded=$(abspath "$fdmodded") 2008 | fdmcert=$(codesign -dvv "$fdmodded" 2>&1 | grep "^Authority=" | awk -F= '{print $2}' | head -1) 2009 | [[ $fdmcert == "" ]] && fdmcert="UNSIGNED" 2010 | fdmodded=$(echo "$fdmodded" | sed "s-^$filepath--g") 2011 | if [[ -d "$filepath$fdmodded" ]] ; then 2012 | dmodded="$dmodded.$fdmodded: $fdmcert\n" 2013 | elif [[ -f "$filepath$fdmodded" ]] ; then 2014 | fmodded="$fmodded.$fdmodded: $fdmcert\n" 2015 | execfiles=$(echo "$execfiles" | grep -v "^$fdmodded$") 2016 | else 2017 | omodded="$omodded.$fdmodded: $fdmcert\n" 2018 | fi 2019 | done < <(echo "$fdmoddedlist") 2020 | fi 2021 | else 2022 | crsubpath="_CodeSignature/CodeResources" 2023 | if [[ -f "$filepath/Contents/$crsubpath" ]] ; then 2024 | msigmissing=true 2025 | crloc="$filepath/Contents/$crsubpath" 2026 | elif [[ -f "$filepath/Versions/Current/$crsubpath" ]] ; then 2027 | msigmissing=true 2028 | crloc=$(abspath "$filepath/Versions/Current/$crsubpath") 2029 | elif [[ -f "$filepath/$crsubpath" ]] ; then 2030 | msigmissing=true 2031 | crloc="$filepath/$crsubpath" 2032 | fi 2033 | if $msigmissing ; then 2034 | if ! $msigerror ; then 2035 | _notify "⚠️ Error: main signature missing!" "$filename" 2036 | echo -e "verification:\tMAIN EXECUTABLE: code signature missing\n" >> "$tmploc" 2037 | fi 2038 | ### manually parse & verify CodeResources (DEEP-SCAN) 2039 | ### write into f/d/o added/modded 2040 | ### if fadded || fmodded > remove from perm111 list 2041 | ### avoid double codesign check (perm111 list & CodeResources) 2042 | else 2043 | echo -e "verification:\tUNSIGNED\n" >> "$tmploc" 2044 | fi 2045 | fi 2046 | 2047 | # echo security verification result 2048 | echo -e "security:\t$certsec" >> "$tmploc" 2049 | 2050 | # subject key identifiers 2051 | if ! $unsigned ; then # read previously scanned SKID from sqlite database 2052 | oldskid=$(sqlite3 "$dbloc" "select skid from WhatsYourSign where cfbid=\"$cfbid\";") 2053 | if [[ $oldskid == "" ]] ; then # none found, i.e. initial scan and write to database 2054 | ! $discrete && sqlite3 "$dbloc" "insert into WhatsYourSign (cfbid,csid,skid) values (\"$cfbid\",\"$csid\",\"$skid\");" 2055 | echo -e "SKIDs:\t\tinitial scan: $skid\n" >> "$tmploc" 2056 | else # compare SKIDs 2057 | if [[ $skid == $oldskid ]] ; then 2058 | if [[ $skid == "SKID_NOT_AVAILABLE" ]] ; then 2059 | echo -e "SKIDs:\t\t$skid\n" >> "$tmploc" 2060 | else 2061 | echo -e "SKIDs:\t\tmatch: $skid\n" >> "$tmploc" 2062 | fi 2063 | else 2064 | echo -e "SKIDs:\t\tMISMATCH" >> "$tmploc" 2065 | echo -e "\t\tcurrent:\t$skid" >> "$tmploc" 2066 | echo -e "\t\tprevious:\t$oldskid\n" >> "$tmploc" 2067 | _beep 2068 | _notify "⚠️ SKID mismatch!" "$filename" 2069 | if $idmatch ; then 2070 | ! $discrete && sqlite3 "$dbloc" "update WhatsYourSign set skid=\"$skid\" where cfbid=\"$cfbid\";" 2071 | else 2072 | ! $discrete && sqlite3 "$dbloc" "update WhatsYourSign set (csid,skid) values (\"$csid\",\"$skid\") where cfbid=\"$cfbid\";" 2073 | fi 2074 | fi 2075 | fi 2076 | else 2077 | echo "" >> "$tmploc" 2078 | fi 2079 | 2080 | # default gatekeeper info (valid results only for bundle root) 2081 | spctlall=$(spctl -v --assess "$filepath" 2>&1) 2082 | 2083 | # gk assessment 2084 | assess=$(echo "$spctlall" | grep ": " | awk -F": " '{print $2}') 2085 | echo -e "GK assessment:\t$assess" >> "$tmploc" 2086 | 2087 | # gatekeeper source 2088 | asource=$(echo "$spctlall" | grep "source=" | awk -F= '{print $2}') 2089 | if [[ $asource == "" ]] ; then # gatekeeper info (execute) 2090 | asource=$(spctl -a -t execute --context context:primary-signature -v "$filepath" 2>&1 | awk -F"=" '/source=/{print $2}') 2091 | [[ $asource == "" ]] && asource="n/a" 2092 | fi 2093 | echo -e "source:\t\t$asource" >> "$tmploc" 2094 | if [[ $asource == "Mac App Store" ]] ; then 2095 | if ! [[ -f "$filepath/Contents/_MASReceipt/receipt" ]] ; then 2096 | echo -e "MAS receipt:\tMISSING\n" >> "$tmploc" 2097 | else 2098 | echo -e "MAS receipt:\tlocated\n" >> "$tmploc" 2099 | fi 2100 | else 2101 | echo "" >> "$tmploc" 2102 | fi 2103 | 2104 | if ! $unsigned ; then 2105 | 2106 | # parse for main code signature (certificate chain) 2107 | if [[ $(echo "$csign" | grep "Signature=") != "" ]] ; then # account for adhoc signatures 2108 | signature=$(echo "$csign" | grep "^Signature=" | awk -F= '{print $2}') 2109 | [[ $signature == "" ]] && signature="n/a" 2110 | [[ $csid == "" ]] && csid="n/a" 2111 | leafcert="$signature" 2112 | teamid=$(echo "$csign" | grep "TeamIdentifier=" | awk -F= '{print $2}') 2113 | echo -e "sign auth:\t$signature ($csid)" >> "$tmploc" 2114 | else 2115 | cscs=$(echo "$csign" | grep "Authority=" | awk -F= '{print $2}') 2116 | count=1 2117 | while read -r cert 2118 | do 2119 | if [[ $count == 1 ]] ; then 2120 | echo -e "sign auth:\t$cert" >> "$tmploc" 2121 | leafcert="$cert" 2122 | else 2123 | echo -e "\t\t$cert" >> "$tmploc" 2124 | fi 2125 | (( count++ )) 2126 | done < <(echo "$cscs") 2127 | teamid=$(echo "$csign" | grep "TeamIdentifier=" | awk -F= '{print $2}') 2128 | fi 2129 | 2130 | # timestamps 2131 | timestamp=$(echo "$csign" | grep "Timestamp=" | awk -F= '{print $2}') 2132 | if [[ $timestamp == "" ]] ; then # no verified timestamp found, looking for unverified signing time 2133 | timestamp=$(echo "$csign" | grep "Signed Time=" | awk -F= '{print $2}') 2134 | if [[ $timestamp == "" ]] ; then 2135 | echo -e "\ntimestamp:\tn/a\n" >> "$tmploc" 2136 | else 2137 | echo -e "\nsigned time:\t$timestamp\n" >> "$tmploc" 2138 | fi 2139 | else 2140 | echo -e "\ntimestamp:\t$timestamp\n" >> "$tmploc" 2141 | fi 2142 | 2143 | # parse entitlements & check for sandboxing 2144 | csignc=$(echo "$csign" | LANG=C LC_CTYPE=C sed 's/^.*\<\?xml/\<\?xml/g' | grep "<.*>") 2145 | echo "$csignc" > "$ptmploc" 2146 | enthr=$(plutil -p "$ptmploc" | grep -v "^{" | grep -v "^}" | sed -e 's/^[ \t]*//' | grep -v "^$") 2147 | sbxstat=$(echo "$enthr" | grep "com.apple.security.app-sandbox" | awk -F" => " '{print $2}') 2148 | if [[ $sbxstat == "1" ]] ; then 2149 | echo -e "sandboxing:\ttrue\n" >> "$tmploc" 2150 | else 2151 | echo -e "sandboxing:\tfalse\n" >> "$tmploc" 2152 | fi 2153 | if [[ $enthr != "" ]] ; then 2154 | count=1 2155 | while read -r ent 2156 | do 2157 | if [[ $count == 1 ]] ; then 2158 | echo -e "entitlements:\t$ent" >> "$tmploc" 2159 | else 2160 | echo -e "\t\t$ent" >> "$tmploc" 2161 | fi 2162 | (( count++ )) 2163 | done < <(echo "$enthr") 2164 | else 2165 | echo -e "entitlements:\tnone" >> "$tmploc" 2166 | fi 2167 | 2168 | else 2169 | echo -e "sign auth:\tUNSIGNED" >> "$tmploc" 2170 | fi 2171 | 2172 | # print added, modified or missing files 2173 | $csreport && echo "" >> "$tmploc" 2174 | [[ $fadded != "" ]] && echo -e "added regular files:\n$fadded" >> "$tmploc" 2175 | [[ $dadded != "" ]] && echo -e "added directories:\n$dadded" >> "$tmploc" 2176 | [[ $oadded != "" ]] && echo -e "added files (other):\n$oadded" >> "$tmploc" 2177 | [[ $fmodded != "" ]] && echo -e "modified regular files:\n$fmodded" >> "$tmploc" 2178 | [[ $dmodded != "" ]] && echo -e "modified directories:\n$dmodded" >> "$tmploc" 2179 | [[ $omodded != "" ]] && echo -e "modified files (other):\n$omodded" >> "$tmploc" 2180 | [[ $fdmissing != "" ]] && echo -e "missing files:\n$fdmissing\n" >> "$tmploc" 2181 | 2182 | # deal with executable files ### IMPROVE AS DEEP-SCAN (v1.1) 2183 | if [[ $filetype == "directory" ]] ; then 2184 | if [[ $executable != "" ]] ; then 2185 | 2186 | xfunsigned="unsigned files with executable permissions:" 2187 | xfothersign="executable files with other code signatures:" 2188 | 2189 | # check code signature of results and write into separate lists 2190 | while read -r execfile 2191 | do 2192 | [[ $execfile == "" ]] && continue 2193 | execfilepath="$filepath$execfile" 2194 | xfcsign=$(codesign -dvv "$execfilepath" 2>&1) 2195 | if [[ $(echo "$xfcsign" | grep "No such file or directory") != "" ]] ; then 2196 | xfcsign=$(codesign -dvv "$execfilepath"* 2>&1) 2197 | fi 2198 | [[ $(echo "$xfcsign" | grep "No such file or directory") != "" ]] && continue 2199 | if [[ $(echo "$xfcsign" | grep "is not signed at all") != "" ]] ; then 2200 | xfunsigned="$xfunsigned\n.$execfile" 2201 | else 2202 | xfleafcert=$(echo "$xfcsign" | grep "^Authority=" | awk -F= '{print $2}' | head -1) 2203 | if [[ $xfleafcert == "" ]] ; then # account for adhoc signatures 2204 | xfleafcert=$(echo "$xfcsign" | grep "^Signature=" | awk -F= '{print $2}' | head -1) 2205 | [[ $xfleafcert == "" ]] && xfleafcert="n/a" 2206 | if [[ $xfleafcert != $leafcert ]] ; then 2207 | xfothersign="$xfothersign\n.$execfile: $xfleafcert" 2208 | fi 2209 | else 2210 | xfteamid=$(echo "$xfcsign" | grep "^TeamIdentifier=" | awk -F= '{print $2}') 2211 | if [[ $xfleafcert != $leafcert ]] && [[ $xfteamid != $teamid ]] ; then 2212 | xfothersign="$xfothersign\n.$execfile: $xfleafcert" 2213 | fi 2214 | fi 2215 | fi 2216 | done < <(echo "$execfiles") 2217 | 2218 | # count number of lines (files with other code signatures) ### DELETE WITH DEEP-SCAN 2219 | snumber=$(echo -e "$xfothersign" | wc -l | xargs) 2220 | ! $csreport && echo "" >> "$tmploc" 2221 | if [[ $snumber != "1" ]] ; then 2222 | if [ $snumber -gt 20 ] ; then 2223 | smany=true 2224 | echo -e "executable files with other code signatures:\n[see auxiliary list]" >> "$tmploc" 2225 | echo -e "$filename\n$filepath\n\n$xfothersign" > "$stmploc" 2226 | else 2227 | echo -e "$xfothersign" >> "$tmploc" 2228 | fi 2229 | else 2230 | echo -e "$xfothersign\nnone" >> "$tmploc" 2231 | fi 2232 | 2233 | # count number of lines (files without code signatures) 2234 | unumber=$(echo -e "$xfunsigned" | wc -l | xargs) 2235 | echo "" >> "$tmploc" 2236 | if [[ $unumber != "1" ]] ; then 2237 | if [ $unumber -gt 10 ] ; then 2238 | umany=true 2239 | echo -e "unsigned files with executable permissions:\n[see auxiliary list]" >> "$tmploc" 2240 | echo -e "$filename\n$filepath\n\n$xfunsigned" > "$utmploc" 2241 | else 2242 | echo -e "$xfunsigned" >> "$tmploc" 2243 | fi 2244 | else 2245 | echo -e "$xfunsigned\nnone" >> "$tmploc" 2246 | fi 2247 | fi 2248 | fi 2249 | 2250 | else # pkg, mpkg, xip, xar (maybe) & others like zip, 7zip, rar etc. 2251 | 2252 | # print download sources & quarantine check ### check for mail attachment downloads: // 2253 | dlflist=$(xattr -p com.apple.metadata:kMDItemWhereFroms "$filepath" 2>/dev/null | xxd -r -p | plutil -p - | grep -v "^\[" | grep -v "^\]" | awk -F\" '{print $2}' | awk -F/ '{print $1"//"$3}' | awk '!seen[$0]++') 2254 | qxattr=$(xattr -p com.apple.quarantine "$filepath" 2>/dev/null | sed -e 's/;/ /g' -e 's/|/ /g') 2255 | if [[ $dlflist != "" ]] && [[ $dlflist != "//" ]] ; then 2256 | count=1 2257 | while read -r dldomain 2258 | do 2259 | if [[ $count == 1 ]] ; then 2260 | echo -e "downloaded:\t$dldomain" >> "$tmploc" 2261 | else 2262 | echo -e "\t\t$dldomain" >> "$tmploc" 2263 | fi 2264 | (( count++ )) 2265 | done < <(echo "$dlflist" | tail -r) 2266 | if [[ $qxattr != "" ]] ; then 2267 | echo -e "quarantine:\t$qxattr\n" >> "$tmploc" 2268 | else 2269 | echo "" >> "$tmploc" 2270 | fi 2271 | else 2272 | [[ $qxattr != "" ]] && echo -e "quarantine:\t$qxattr\n" >> "$tmploc" 2273 | fi 2274 | 2275 | # calculate hashes/checksums 2276 | _csfind # look for checksum files 2277 | if ! $clipped ; then # no clipboard checksum 2278 | if $hfmfound || $hfsfound ; then # found & will parse checksum files 2279 | _cshcomp 2280 | else # found no checksum files 2281 | _hashes "$filepath" 2282 | fi 2283 | else # clipboard hash detected 2284 | _cscomp 2285 | fi 2286 | 2287 | # gpg scan 2288 | $gnupg && _gpgval 2289 | 2290 | # check signature with pkgutil standard tool 2291 | pkginfo=$(pkgutil --check-signature "$filepath") 2292 | 2293 | # security status 2294 | pkgstat=$(echo "$pkginfo" 2>&1 | awk -F": " '/Status:/{print $2}') 2295 | [[ $pkgstat == "" ]] && pkgstat="n/a" 2296 | if [[ $pkgstat == "no signature" ]] ; then 2297 | unsigned=true 2298 | else 2299 | unsigned=false 2300 | fi 2301 | 2302 | # check hash at VirusTotal 2303 | if $vtaccess ; then 2304 | _vtping 2305 | if ! $vtaccess ; then 2306 | echo "wys: could not reach VirusTotal." >&2 2307 | _notify "Error: VirusTotal offline" "Not connected to the internet?" 2308 | echo -e "VirusTotal:\tn/a (offline)" >> "$tmploc" 2309 | ! $clamav && echo "" >> "$tmploc" 2310 | else 2311 | if [[ $vthash != "" ]] ; then 2312 | _virustotal "$vthash" 2313 | $vtopen && open "https://www.virustotal.com/#/file/$vthash" 2314 | fi 2315 | fi 2316 | fi 2317 | 2318 | # clamscan 2319 | $clamav && _clamav "$filepath" 2320 | 2321 | # dump the package table of contents (header) 2322 | pkgheader=$(xar --dump-toc=- -f "$filepath") 2323 | mkdir "$certdir" 2324 | 2325 | # extract installer package signing certificates 2326 | echo "$pkgheader" | xmllint --xpath '//signature[@style="RSA"]' - \ 2327 | | sed -n '//,/<\/X509Certificate>/p' | xargs \ 2328 | | awk '{gsub("","-----BEGINCERTIFICATE-----"); gsub("","-----ENDCERTIFICATE-----"); print}' \ 2329 | | awk '{gsub(" ","\n"); print}' \ 2330 | | awk '{gsub("BEGINCERTIFICATE-----","BEGIN CERTIFICATE-----\n"); gsub("-----ENDCERTIFICATE","\n-----END CERTIFICATE"); print}' \ 2331 | | csplit -k -s -n 1 -f "$certdir/$filename"-cert - '/END CERTIFICATE/+1' '{3}' 2>/dev/null 2332 | for cert in "$certdir/$filename-cert"* ; do 2333 | mv "$cert" "$cert.pem" 2>/dev/null 2334 | done 2335 | certfsize=$(stat -f%z "$certdir/$filename-cert0.pem" 2>/dev/null) 2336 | if [[ -f "$certdir/$filename-cert0.pem" ]] && [ $certfsize -gt 0 ] ; then # verify IPSC with keychain tool (security) 2337 | certsecall=$(security verify-cert -c "$certdir/$filename-cert0.pem" 2>&1) 2338 | if [[ $(echo "$certsecall" | grep "Cert Verify Result:") != "" ]] ; then 2339 | certsec=$(echo "$certsecall" | awk -F": " '{print $2}') 2340 | elif [[ $certsecall == "...certificate verification successful." ]] ; then 2341 | certsec="successful" 2342 | else 2343 | certsec="$certsecall" 2344 | fi 2345 | if [[ $(echo "$certsec" | grep "REVOKED") != "" ]] ; then 2346 | _beep 2347 | _notify "❌ Certificate revoked!" "$filename" 2348 | elif [[ $(echo "$certsec" | grep "NOT_TRUSTED") != "" ]] ; then 2349 | _beep 2350 | _notify "❌ Certificate not trusted!" "$filename" 2351 | elif [[ $(echo "$certsec" | grep "kSecTrustResultDeny") != "" ]] ; then 2352 | _beep 2353 | _notify "❌ Certificate denied trust!" "$filename" 2354 | fi 2355 | else 2356 | if $unsigned ; then 2357 | certsec="UNSIGNED" 2358 | else 2359 | _beep 2360 | certsec="POSSIBLE MALWARE: IPS_CERTS_MISSING" 2361 | _notify "⚠️ Possible malware!" "$filename" 2362 | fi 2363 | fi 2364 | 2365 | echo -e "verification:\t$certsec\n" >> "$tmploc" 2366 | 2367 | rm -rf "$certdir" 2>/dev/null 2368 | 2369 | # originating macOS user (creator) 2370 | cusers=$(echo "$pkgheader" | grep "" | awk -F">" '{print $2}' | awk -F"> "$tmploc" 2373 | else 2374 | cusercount=$(echo "$cusers" | wc -l | xargs) 2375 | if [ $cusercount -gt 1 ] ; then 2376 | cusers=$(echo "$cusers" | grep -v "^root$") 2377 | fi 2378 | count=1 2379 | while read -r cuser 2380 | do 2381 | if [[ $count == 1 ]] ; then 2382 | echo -e "creator:\t$cuser" >> "$tmploc" 2383 | else 2384 | echo -e "\t\t$cuser" >> "$tmploc" 2385 | fi 2386 | (( count++ )) 2387 | done < <(echo "$cusers") 2388 | fi 2389 | 2390 | # echo security status 2391 | echo -e "status:\t\t$pkgstat" >> "$tmploc" 2392 | 2393 | # gatekeeper info (install) 2394 | spctlall=$(spctl -a -t install --context context:primary-signature -v "$filepath" 2>&1) 2395 | 2396 | # gk assessment 2397 | assess=$(echo "$spctlall" | grep ": " | awk -F": " '{print $2}') 2398 | echo -e "\nGK assessment:\t$assess" >> "$tmploc" 2399 | 2400 | # gk source 2401 | asource=$(echo "$spctlall" | grep "source=" | awk -F= '{print $2}') 2402 | [[ $asource == "" ]] && asource="n/a" 2403 | echo -e "source:\t\t$asource\n" >> "$tmploc" 2404 | 2405 | # parse certificate chain 2406 | pkgsig=$(echo "$pkginfo" | sed -n -e '/Certificate Chain:/,$p' | grep "^.*[0-9]\." | awk -F. '{print substr($0, index($0,$2))}' | sed -e 's/^[ \t]*//' | grep -v "^$") 2407 | if [[ $pkgsig == "" ]] ; then 2408 | echo -e "sign auth:\tUNSIGNED" >> "$tmploc" 2409 | else 2410 | count=1 2411 | while read -r cert 2412 | do 2413 | if [[ $count == 1 ]] ; then 2414 | echo -e "sign auth:\t$cert" >> "$tmploc" 2415 | else 2416 | echo -e "\t\t$cert" >> "$tmploc" 2417 | fi 2418 | (( count++ )) 2419 | done < <(echo "$pkgsig") 2420 | fi 2421 | 2422 | # read embedded timestamp 2423 | timestamp=$(echo "$pkgheader" | grep "" | awk -F">" '{print $2}' | awk -F"> "$tmploc" 2430 | 2431 | fi 2432 | 2433 | # use qlmanage (QuickLook CLI) to display results 2434 | if $wyslog ; then 2435 | tmploc_body=$(cat "$tmploc") 2436 | stmploc_body=$(cat "$stmploc" 2>/dev/null) 2437 | utmploc_body=$(cat "$utmploc" 2>/dev/null) 2438 | logbody="scan start: $scandate 2439 | $tmploc_body 2440 | $stmploc_body 2441 | $utmploc_body 2442 | *** end of scan ***" 2443 | logbody=$(echo "$logbody" | grep -v "^$" | ruby -pe '$_.gsub!(/\t/," ")' | sed 's/: /: /g' | sed 's/ / /g') 2444 | if [[ $cfbid == $shortname ]] || [[ $cfbid == "" ]] ; then 2445 | logfilename="$filename.log" 2446 | else 2447 | logfilename="$cfbid.log" 2448 | fi 2449 | logger -i -s -t local.lcars.wys "$logbody" 2>> "$logdir/$logfilename" 2450 | fi 2451 | 2452 | if ! $silent ; then 2453 | if $umany ; then 2454 | qlmanage -p "$utmploc" >/dev/null & 2455 | fi 2456 | if $smany ; then ### REMOVE FOR DEEP-SCAN 2457 | qlmanage -p "$stmploc" >/dev/null & 2458 | fi 2459 | qlmanage -p "$tmploc" >/dev/null & 2460 | wait 2461 | fi 2462 | 2463 | rm -f "$tmploc" "$ptmploc" "$stmploc" "$utmploc" 2>/dev/null 2464 | 2465 | cd "$workingdir" 2466 | 2467 | done 2468 | 2469 | $silent && echo "Done." 2470 | 2471 | exit 2472 | --------------------------------------------------------------------------------