├── LICENSE ├── README.md └── sandfly-file-decloak.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2018-2022 Sandfly Security 4 | Agentless Security for Linux (www.sandflysecurity.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | software and associated documentation files (the “Software”), to deal in the Software 8 | without restriction, including without limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 10 | to whom the Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or 13 | substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sandfly File Decloak - Decloak data hidden by a Linux stealth rootkit 2 | 3 | This utility helps investigate a host for signs of an active Linux stealth rootkit that may 4 | be hiding data in critical files. It does this by reading in a file using standard file I/O 5 | operations and then doing the same using memory mapped I/O to see if the number of bytes 6 | read are identical. 7 | 8 | Any differences detected will generate an alert. Plus, you will see the hidden data 9 | decloaked to instantly see if it is suspicious or not. This utility will work against many 10 | common Loadable Kernel Module (LKM) stealth rootkits that use data hiding techniques. 11 | 12 | ## Linux Loadable Kernel Module Stealth Rootkit File Hiding Tactics 13 | 14 | Loadable Kernel Module rootkits on Linux use a variety of tactics to hide. One method is to 15 | hide processes which can be decloaked using our utility sandlfy-processdecloak 16 | (). The other is to hide data 17 | inside critical start-up scripts so it can maintain persistence between reboots but not be 18 | seen by investigators when running. 19 | 20 | For instance the files below are commonly targeted to hide data as they are used to insert 21 | kernel modules upon system boot. Or, these files can be used to insert malicious libraries 22 | to intercept system calls in libc, etc. to alter data to callers: 23 | 24 | ```text 25 | /etc/modules 26 | /etc/ld.so.conf 27 | ``` 28 | 29 | Also directories for module loading on boot such as: 30 | 31 | ```text 32 | /etc/modules-load.d 33 | /etc/init.d 34 | /etc/rc*.d 35 | /etc/systemd 36 | ``` 37 | 38 | Many more files can also be used for this purpose and often are hidden under /etc as part 39 | of system init scripts. 40 | 41 | ## Stealth Rootkit Hiding Method 42 | 43 | Most LKM rootkits generally accomplish data hiding by hooking common system calls for file 44 | read operations. They will use a special set of tags to mark data to hide. Between the tags 45 | the malicious data will be inserted. When the rootkit sees the start tag it simply does not 46 | show any data present until the end tag is read. By doing this the rootkit can effectively 47 | hide from discovery using common command line tools and even editors on Linux. 48 | 49 | For instance, a modified file may have tags inserted like this: 50 | 51 | ```bash 52 | # malicious content below 53 | # 54 | malicious_module 55 | # 56 | ``` 57 | 58 | Anything between the ** and ** will be masked (along with the tags 59 | themselves) when you use tools like cat, echo, vi and so on. It simply won't be shown. 60 | 61 | ## Detecting LKM rootkits 62 | 63 | It is one thing to convince the kernel to hide data, but something else entirely to get 64 | the file system to agree with it. In fact we know the data is there on the file system 65 | and we just need to bypass the hooked calls to see if we can get it to reveal itself. We'll 66 | accomplish this using memory mapped (mmap) file I/O instead of standard file I/O. LKM 67 | rootkits generally do not intercept mmap file I/O which is a significantly harder and 68 | riskier thing to do. 69 | 70 | We will read the file using standard system calls, then we read the file using mmap system 71 | calls in a simple Python script. We then compare the two results. If the results show the 72 | same number of bytes the system is likely clean. However if the two results do not match 73 | then data is being hidden and we will use the mmap I/O to show the data difference and 74 | decloak the data it saw that was different. 75 | 76 | ## Usage 77 | 78 | Simply execute the python script on the system in question and the answer will come forth. 79 | 80 | ```bash 81 | python3 ./sandfly-file-decloak.py -f 82 | ``` 83 | 84 | Below we find a system with cloaked data under /etc/modules. 85 | 86 | ```text 87 | root@sandflysecurity:/root # python3 ./sandfly-file-decloak.py -f /etc/modules 88 | 89 | Sandfly File Decloaking Utility - Version 1.0 90 | Copyright (c) 2018-2022 Sandfly Security 91 | Agentless Security for Linux - https://www.sandflysecurity.com 92 | 93 | 94 | ************************************** 95 | File contents with standard I/O 96 | ************************************** 97 | 98 | 99 | # /etc/modules: kernel modules to load at boot time. 100 | # 101 | # This file contains the names of kernel modules that should be loaded 102 | # at boot time, one per line. Lines beginning with "#" are ignored. 103 | 104 | # malicious content below 105 | 106 | 107 | 108 | ************************************** 109 | File contents with memory mapped I/O 110 | ************************************** 111 | 112 | 113 | # /etc/modules: kernel modules to load at boot time. 114 | # 115 | # This file contains the names of kernel modules that should be loaded 116 | # at boot time, one per line. Lines beginning with "#" are ignored. 117 | 118 | # malicious content below 119 | # 120 | malicious_module 121 | # 122 | 123 | 124 | 125 | Standard IO file size bytes: 222 126 | MMAP IO file size bytes: 260 127 | 128 | ******************************************************************************************** 129 | ALERT: File sizes do not match. File has cloaked data. Check contents above for hidden data. 130 | ******************************************************************************************** 131 | ``` 132 | 133 | ## Automating with Agentless Linux Endpoint Detection and Response (EDR) 134 | 135 | This tool can be built into an automated script to check hosts for signs of compromise. 136 | However, a much easier way is to use Sandfly to do it agentlessly for all your Linux systems 137 | continuously. 138 | 139 | We have modules to detect and decloak stealth rootkit activity instantly across all your 140 | Linux systems. Even better, we can do it without the risk of loading any agents on your 141 | endpoints. 142 | 143 | You can find out more information and get a free license to use on your Linux systems below: 144 | 145 | 146 | 147 | ## More Linux Forensics 148 | 149 | If you liked this utility, please check out our blog where we go into many other Linux 150 | forensic techniques using command line tools and procedures at our website: 151 | 152 | 153 | -------------------------------------------------------------------------------- /sandfly-file-decloak.py: -------------------------------------------------------------------------------- 1 | # Copyright © 2018-2022 Sandfly Security - Agentless Security for Linux (www.sandflysecurity.com) 2 | # 3 | # A utility to test if a file is hiding cloaked data due to a Loadable Kernel Module (LKM) or 4 | # LD_PRELOAD stealth rootkit on Linux. Simply call the command as follows against a suspect file: 5 | # 6 | # python3 ./sandfly-file-decloak.py -f /etc/modules 7 | # 8 | # Any cloaked data will be reported and decloaked so you can see the contents and investigate if 9 | # it is malicious. 10 | # 11 | # Sandfly produces an agentless Linux endpoint detection and incident response platform (EDR) that can detect 12 | # thousands of compromise tactics against Linux, including stealth rootkits. Sandfly hunts for threats 13 | # against your Linux systems without loading any agents on your endpoints and works against most distributions 14 | # and architectures. 15 | # 16 | # Please see our website for more information or a free trial. 17 | # 18 | # MIT Licensed 19 | # https://www.sandflysecurity.com 20 | # @SandflySecurity 21 | # 22 | # Please see our blog for more detailed information on this tool and other Linux forensics articles and 23 | # techniques. 24 | 25 | import getopt 26 | import mmap 27 | import sys 28 | import binascii 29 | 30 | VERSION="1.0" 31 | 32 | def main(): 33 | filename = None 34 | 35 | try: 36 | opts, args = getopt.getopt(sys.argv[1:], "f:") 37 | except getopt.GetoptError: 38 | print("Illegal option. Valid option is -f") 39 | sys.exit(-1) 40 | 41 | if len(opts) > 0: 42 | for opt, arg in opts: 43 | if opt == '-f': 44 | filename = arg 45 | 46 | if not filename: 47 | print("Need to supply filename with -f") 48 | sys.exit(-1) 49 | 50 | 51 | print("\nSandfly File Decloaking Utility - Version {0}".format(VERSION)) 52 | print("Copyright (c) 2018-2022 Sandfly Security") 53 | print("Agentless Security for Linux - https://www.sandflysecurity.com") 54 | 55 | print("\n\n**************************************") 56 | print("File contents with standard I/O") 57 | print("**************************************\n\n") 58 | with open(filename, "r+b") as f: 59 | file_size_standard_io = 0 60 | for line in f: 61 | output = line 62 | try: 63 | print(output.decode('utf-8').rstrip()) 64 | except UnicodeDecodeError: 65 | print("hex: ", binascii.hexlify(output)) 66 | file_size_standard_io += len(output) 67 | 68 | print("\n\n**************************************") 69 | print("File contents with memory mapped I/O") 70 | print("**************************************\n\n") 71 | with open(filename, "r+b") as f: 72 | map = mmap.mmap(f.fileno(), 0, access=mmap.PROT_READ) 73 | file_size_mmap = map.size() 74 | file_seek = 0 75 | while file_seek < file_size_mmap: 76 | output = map.readline() 77 | try: 78 | print(output.decode('utf-8').rstrip()) 79 | except UnicodeDecodeError: 80 | print("hex: ", binascii.hexlify(output)) 81 | file_seek += len(output) 82 | 83 | print("\n\n") 84 | print("Standard IO file size bytes: ", file_size_standard_io) 85 | print("MMAP IO file size bytes: ", file_size_mmap) 86 | if file_size_standard_io != file_size_mmap: 87 | print("\n********************************************************************************************") 88 | print("ALERT: File sizes do not match. File has cloaked data. Check contents above for hidden data.") 89 | print("********************************************************************************************\n\n") 90 | else: 91 | print("\nOK: File sizes are same so they are not cloaked.\n\n") 92 | 93 | if __name__ == '__main__': 94 | main() 95 | 96 | 97 | --------------------------------------------------------------------------------