├── .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 | 
2 | 
3 | [](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 | 
6 |
7 | #### Warning of SKID mismatch and certificate revocation (KeRanger)
8 |
9 | 
10 |
11 | #### Application signed with Developer ID
12 |
13 | 
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 | 
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 | 
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 | 
36 |
37 | #### Application with entitlements (Mac App Store)
38 |
39 | 
40 |
41 | #### Application with valid but expired codesigning certificate
42 |
43 | 
44 |
45 | #### Application with untrusted third-party code signature and missing SKID
46 |
47 | 
48 |
49 | #### Adhoc-signed application with missing SKID
50 |
51 | 
52 |
53 | #### Unsigned application
54 |
55 | 
56 |
57 | #### Kernel extension
58 |
59 | 
60 |
61 | #### Codesigned command line interface with initial SKID scan
62 |
63 | 
64 |
65 | #### Codesigned disk image (DMG)
66 |
67 | 
68 |
69 | #### Malware disk image (DMG) with hash mismatch and signed with a fake code signature
70 |
71 | 
72 |
73 | #### Signed installer package
74 |
75 | 
76 |
77 | #### xip archive with verified hash and signed with Developer ID
78 |
79 | 
80 |
81 | #### xip archive signed with locally trusted key
82 |
83 | 
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 | 
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 | 
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"" '{print $1}' | grep -v "^$" | sort -u)
2371 | if [[ $cusers == "" ]] ; then
2372 | echo -e "creator:\tn/a" >> "$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"" '{print $1}' | grep -v "^$")
2424 | if [[ $timestamp == "" ]] ; then
2425 | hrtimestamp="n/a"
2426 | else
2427 | hrtimestamp=$(date -f '%FT%T' -j "$timestamp" +'%d %b %Y %H:%M:%S' | sed -e 's/^[ \t]*//')
2428 | fi
2429 | echo -e "\ntimestamp:\t$hrtimestamp\n" >> "$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 |
--------------------------------------------------------------------------------