Skip to content

Commit

Permalink
typo
Browse files Browse the repository at this point in the history
  • Loading branch information
c0m4r committed Jan 3, 2024
1 parent 379902c commit ec0014f
Show file tree
Hide file tree
Showing 2 changed files with 1 addition and 389 deletions.
388 changes: 0 additions & 388 deletions loki-daemonized.patch
Original file line number Diff line number Diff line change
@@ -1,388 +0,0 @@
--- loki.py.original 2023-12-28 14:31:22.375121479 +0100
+++ loki.py 2024-01-03 03:56:32.048867818 +0100
@@ -48,6 +48,10 @@
from lib.doublepulsar import DoublePulsar
from lib.vuln_checker import VulnChecker

+# Daemon mode
+import socket
+from threading import Thread
+
# Platform
os_platform = ""

@@ -113,6 +117,8 @@
fullExcludes = []
# Platform specific excludes (match the beginning of the full path) (not user-defined)
startExcludes = []
+ # Excludes hash (md5, sha1 and sha256)
+ excludes_hash = []

# File type magics
filetype_magics = {}
@@ -196,6 +202,14 @@

def scan_path(self, path):

+ if os.path.isfile(path) and os_platform == "linux":
+ logger.log("INFO", "Init", "Single file mode for " + os_platform)
+ root = ''
+ directories = ''
+ files = [ path ]
+ loki.scan_path_files(root, directories, files)
+ return
+
# Check if path exists
if not os.path.exists(path):
logger.log("ERROR", "FileScan", "None Existing Scanning Path %s ... " % path)
@@ -210,9 +224,6 @@
"Skipping %s directory [fixed excludes] (try using --force, --allhds or --alldrives)" % skip)
return

- # Counter
- c = 0
-
for root, directories, files in os.walk(path, onerror=walk_error, followlinks=False):

# Skip paths that start with ..
@@ -233,6 +244,13 @@
newDirectories.append(dir)
directories[:] = newDirectories

+ loki.scan_path_files(root, directories, files)
+
+ def scan_path_files(self, root, directories, files):
+
+ # Counter
+ c = 0
+
# Loop through files
for filename in files:
try:
@@ -278,8 +296,12 @@
skipIt = True

# File mode
- mode = os.stat(filePath).st_mode
- if stat.S_ISCHR(mode) or stat.S_ISBLK(mode) or stat.S_ISFIFO(mode) or stat.S_ISLNK(mode) or stat.S_ISSOCK(mode):
+ try:
+ mode = os.stat(filePath).st_mode
+ if stat.S_ISCHR(mode) or stat.S_ISBLK(mode) or stat.S_ISFIFO(mode) or stat.S_ISLNK(mode) or stat.S_ISSOCK(mode):
+ continue
+ except:
+ logger.log("DEBUG", "FileScan", "Skipping element %s does not exist or is a broken symlink" % (filePath))
continue

# Skip
@@ -397,6 +419,11 @@
if md5_num in self.false_hashes.keys() or sha1_num in self.false_hashes.keys() or sha256_num in self.false_hashes.keys():
continue

+ # Skip exclude hash
+ if md5 in self.excludes_hash or sha1 in self.excludes_hash or sha256 in self.excludes_hash:
+ logger.log("DEBUG", "FileScan", "Skipping element %s excluded by hash" % (filePath))
+ continue
+
# Malware Hash
matchScore = 100
matchLevel = "Malware"
@@ -475,10 +502,13 @@
# Now print the total result
if total_score >= args.a:
message_type = "ALERT"
+ threading.current_thread().message = "ALERT"
elif total_score >= args.w:
message_type = "WARNING"
+ threading.current_thread().message = "WARNING"
elif total_score >= args.n:
message_type = "NOTICE"
+ threading.current_thread().message = "NOTICE"

if total_score < args.n:
continue
@@ -590,6 +620,78 @@
owner.upper().startswith("LO") or
owner.upper().startswith("SYSTEM"))

+ def scan_processes_linux(self):
+
+ processes = psutil.pids()
+
+ loki_pid = os.getpid()
+ loki_ppid = psutil.Process(os.getpid()).ppid()
+
+ for process in processes:
+
+ # Gather Process Information -------------------------------------
+ pid = process
+ try:
+ name = psutil.Process(process).name()
+ except (psutil.NoSuchProcess):
+ logger.log("DEBUG", "ProcessScan", "Skipping Process PID: %s as it just exited and no longer exists" % (str(pid)))
+ continue
+ owner = psutil.Process(process).username()
+ status = psutil.Process(process).status()
+ try:
+ cmd = ' '.join(psutil.Process(process).cmdline())
+ except (psutil.NoSuchProcess, psutil.ZombieProcess):
+ logger.log("WARNING", "ProcessScan", "Process PID: %s NAME: %s STATUS: %s" % (str(pid), name, status))
+ continue
+ path = psutil.Process(process).cwd()
+ bin = psutil.Process(process).exe()
+ tty = psutil.Process(process).terminal()
+ memory_maps = psutil.Process(process).memory_maps()
+ ws_size = psutil.Process(process).memory_info().vms
+ num_fds = psutil.Process(process).num_fds()
+ open_files = psutil.Process(process).open_files()
+
+ process_info = "PID: %s NAME: %s OWNER: %s STATUS: %s BIN: %s CMD: %s PATH: %s TTY: %s" % (str(pid), name, owner, status, bin, cmd, path, tty)
+
+ # Print info -------------------------------------------------------
+ logger.log("INFO", "ProcessScan", "Process %s" % process_info)
+
+ # Process Masquerading Detection -----------------------------------
+
+ if re.search('\[', cmd):
+ maps = Popen('cat /proc/' + str(pid) + '/maps', shell=True, stdout=subprocess.PIPE)
+ if(maps.stdout.read()):
+ logger.log("WARNING", "ProcessScan", "Potentional Process Masquerading PID: %s CMD: %s Check /proc/%s/maps" % (str(pid), cmd, str(pid)))
+
+ # File Name Checks -------------------------------------------------
+ for fioc in self.filename_iocs:
+ match = fioc['regex'].search(cmd)
+ if match:
+ if int(fioc['score']) > 70:
+ logger.log("ALERT", "ProcessScan", "File Name IOC matched PATTERN: %s DESC: %s MATCH: %s" % (fioc['regex'].pattern, fioc['description'], cmd))
+ elif int(fioc['score']) > 40:
+ logger.log("WARNING", "ProcessScan", "File Name Suspicious IOC matched PATTERN: %s DESC: %s MATCH: %s" % (fioc['regex'].pattern, fioc['description'], cmd))
+
+ # Process connections ----------------------------------------------
+ if not args.nolisten:
+ connections = psutil.Process(pid).connections()
+ conn_count = 0
+ conn_limit = 20
+ for pconn in connections:
+ conn_count += 1
+ if conn_count > conn_limit:
+ logger.log("NOTICE", "ProcessScan", "Process PID: %s NAME: %s More connections detected. Showing only %s" % (str(pid), name, conn_limit))
+ break
+ ip = pconn.laddr.ip
+ status = pconn.status
+ ext = pconn.raddr
+ if(ext):
+ ext_ip = pconn.raddr.ip
+ ext_port = pconn.raddr.port
+ logger.log("NOTICE", "ProcessScan", "Process PID: %s NAME: %s CONNECTION: %s <=> %s %s (%s)" % (str(pid), name, ip, ext_ip, ext_port, status))
+ else:
+ logger.log("NOTICE", "ProcessScan", "Process PID: %s NAME: %s CONNECTION: %s (%s)" % (str(pid), name, ip, status))
+
def scan_processes(self, nopesieve, nolisten, excludeprocess, pesieveshellc):
# WMI Handler
c = wmi.WMI()
@@ -1117,6 +1219,10 @@
# Full Path
yaraRuleFile = os.path.join(root, file)

+ if(file in args.disable_yara_files.split(",")):
+ logger.log("NOTICE", "Init", "Disabled yara file: " + file)
+ continue
+
# Skip hidden, backup or system related files
if file.startswith(".") or file.startswith("~") or file.startswith("_"):
continue
@@ -1290,6 +1396,7 @@
def initialize_excludes(self, excludes_file):
try:
excludes = []
+ excludes_hash = []
with open(excludes_file, 'r') as config:
lines = config.read().splitlines()

@@ -1297,14 +1404,23 @@
if re.search(r'^[\s]*#', line):
continue
try:
+ # If the line contains md5sum
+ if re.search(r'^md5sum:', line):
+ excludes_hash.append(re.sub('(md5sum:|(\s\#|\#).*)','', line))
+ # If the line contains sha1sum
+ elif re.search(r'^sha1sum:', line):
+ excludes_hash.append(re.sub('(sha1sum:|(\s\#|\#).*)','', line))
+ elif re.search(r'^sha256sum:', line):
+ excludes_hash.append(re.sub('(sha256sum:|(\s\#|\#).*)','', line))
# If the line contains something
- if re.search(r'\w', line):
+ elif re.search(r'\w', line):
regex = re.compile(line, re.IGNORECASE)
excludes.append(regex)
except Exception:
logger.log("ERROR", "Init", "Cannot compile regex: %s" % line)

self.fullExcludes = excludes
+ self.excludes_hash = excludes_hash

except Exception:
if logger.debug:
@@ -1444,12 +1560,25 @@


# CTRL+C Handler --------------------------------------------------------------
+def remove_pidfile():
+ if(args.d):
+ try:
+ os.remove(args.pidfile)
+ except Exception:
+ pass
+
def signal_handler(signal_name, frame):
try:
print("------------------------------------------------------------------------------\n")
logger.log('INFO', 'Init', 'LOKI\'s work has been interrupted by a human. Returning to Asgard.')
except Exception:
print('LOKI\'s work has been interrupted by a human. Returning to Asgard.')
+ remove_pidfile()
+ sys.exit(0)
+
+def signal_handler_term(signal_name, frame):
+ remove_pidfile()
+ print('LOKI\'s work has been interrupted by a SIGTERM. Returning to Asgard.')
sys.exit(0)

def main():
@@ -1468,6 +1597,12 @@
parser.add_argument('-a', help='Alert score', metavar='alert-level', default=100)
parser.add_argument('-w', help='Warning score', metavar='warning-level', default=60)
parser.add_argument('-n', help='Notice score', metavar='notice-level', default=40)
+ parser.add_argument('-d', help='Run as a daemon', action='store_true', default=False)
+ parser.add_argument('--pidfile', help='Pid file path (default: loki.pid)', default='loki.pid')
+ parser.add_argument('--listen-host', help='Listen host for daemon mode (default: localhost)', default='localhost')
+ parser.add_argument('--listen-port', help='Listen port for daemon mode (default: 1337)', type=int, default=1337)
+ parser.add_argument('--auth', help='Auth key, only in daemon mode', default='')
+ parser.add_argument('--disable-yara-files', help='Comma separated list of yara files to disable', default='')
parser.add_argument('--allhds', action='store_true', help='Scan all local hard drives (Windows only)', default=False)
parser.add_argument('--alldrives', action='store_true', help='Scan all drives (including network drives and removable media)', default=False)
parser.add_argument('--printall', action='store_true', help='Print all files that are scanned', default=False)
@@ -1532,9 +1667,25 @@
# Signal handler for CTRL+C
signal_module.signal(signal_module.SIGINT, signal_handler)

+ # Signal handler for SIGTERM
+ signal_module.signal(signal_module.SIGTERM, signal_handler_term)
+
# Argument parsing
args = main()

+ # Save pidfile
+ if args.d is True:
+ if(os.path.exists(args.pidfile)):
+ fpid = open(args.pidfile, 'r')
+ loki_pid = int(fpid.read())
+ fpid.close()
+ if psutil.pid_exists(loki_pid):
+ print("LOKI daemon already running. Returning to Asgard.")
+ sys.exit(0)
+ with open(args.pidfile, 'w', encoding='utf-8') as fpid:
+ fpid.write(str(os.getpid()))
+ fpid.close()
+
# Remove old log file
if os.path.exists(args.l):
os.remove(args.l)
@@ -1553,8 +1704,20 @@
updateLoki(sigsOnly=False)
sys.exit(0)

+ if os_platform == "linux":
+ try:
+ for key, val in platform.freedesktop_os_release().items():
+ if key == 'PRETTY_NAME':
+ platform_pretty_name = val
+ except Exception:
+ platform_pretty_name = platform.system()
+ platform_machine = platform.machine()
+ platform_full = platform_pretty_name + " (" + platform_machine + ")"
+ else:
+ platform_full = getPlatformFull()
+
logger.log("NOTICE", "Init", "Starting Loki Scan VERSION: {3} SYSTEM: {0} TIME: {1} PLATFORM: {2}".format(
- getHostname(os_platform), getSyslogTimestamp(), getPlatformFull(), logger.version))
+ getHostname(os_platform), getSyslogTimestamp(), platform_full, logger.version))

# Loki
loki = Loki(args.intense)
@@ -1589,6 +1752,11 @@

# Scan Processes --------------------------------------------------
resultProc = False
+ if not args.noprocscan and os_platform == "linux":
+ if isAdmin:
+ loki.scan_processes_linux()
+ else:
+ logger.log("NOTICE", "Init", "Skipping process memory check. User has no admin rights.")
if not args.noprocscan and os_platform == "windows":
if isAdmin:
loki.scan_processes(args.nopesieve, args.nolisten, args.excludeprocess, args.pesieveshellc)
@@ -1619,6 +1787,63 @@
loki.scan_path(defaultPath)

# Linux & macOS
+ elif args.d is True:
+ logger.log("NOTICE", "Init", "Loki-daemonized (c) 2023 c0m4r")
+ server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ server.bind((args.listen_host, args.listen_port))
+ server.listen(5)
+
+ def handle_client(client_socket, address):
+ size = 2048
+ while True:
+ try:
+ clientid = threading.current_thread().name
+ threading.current_thread().message = ''
+ data = client_socket.recv(size)
+ scan_path = data.decode().split(" ")[0]
+ if args.auth:
+ server_authkey = args.auth
+ try:
+ client_authkey = data.decode().split(" ")[1]
+ except:
+ logger.log("NOTICE", "Auth", "Client " + str(address[0]) + ":" + str(address[1]) + " no valid authorization")
+ client_socket.send('authorization required'.encode())
+ client_socket.close()
+ return False
+
+ if client_authkey == server_authkey:
+ logger.log("NOTICE", "Auth", "Client " + str(address[0]) + ":" + str(address[1]) + " accepted")
+ else:
+ logger.log("NOTICE", "Auth", "Client " + str(address[0]) + ":" + str(address[1]) + " unauthorized")
+ client_socket.send('unauthorized'.encode())
+ client_socket.close()
+ return False
+ logger.log("INFO", "Init", "Received: " + data.decode() + " from: " + str(address[0]) + ":" + str(address[1]))
+ loki.scan_path(scan_path)
+ # Result ----------------------------------------------------------
+ if threading.current_thread().message == 'ALERT':
+ logger.log("RESULT", "Results", "Indicators detected! (Client: " + clientid + ")")
+ client_socket.send('RESULT: Indicators detected!'.encode())
+ elif threading.current_thread().message == 'WARNING':
+ logger.log("RESULT", "Results", "Suspicious objects detected! (Client: " + clientid + ")")
+ client_socket.send('RESULT: Suspicious objects detected!'.encode())
+ else:
+ logger.log("RESULT", "Results", "SYSTEM SEEMS TO BE CLEAN. (Client: " + clientid + ")")
+ client_socket.send('RESULT: SYSTEM SEEMS TO BE CLEAN.'.encode())
+
+ logger.log("NOTICE", "Results", "Finished LOKI Scan CLIENT: %s SYSTEM: %s TIME: %s" % (clientid, getHostname(os_platform), getSyslogTimestamp()))
+ client_socket.close()
+ return False
+ except socket.error:
+ client_socket.close()
+ return False
+
+ logger.log("NOTICE", "Init", "Listening on " + args.listen_host + ":" + str(args.listen_port))
+ while True:
+ client, addr = server.accept()
+ Thread(target=handle_client, args=(client, addr), name=str(addr[0]) + ":" + str(addr[1])).start()
+
else:
loki.scan_path(defaultPath)

2 changes: 1 addition & 1 deletion loki.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ def scan_processes_linux(self):
if re.search('\[', cmd):
maps = Popen('cat /proc/' + str(pid) + '/maps', shell=True, stdout=subprocess.PIPE)
if(maps.stdout.read()):
logger.log("WARNING", "ProcessScan", "Potentional Process Masquerading PID: %s CMD: %s Check /proc/%s/maps" % (str(pid), cmd, str(pid)))
logger.log("WARNING", "ProcessScan", "Potential Process Masquerading PID: %s CMD: %s Check /proc/%s/maps" % (str(pid), cmd, str(pid)))

# File Name Checks -------------------------------------------------
for fioc in self.filename_iocs:
Expand Down

0 comments on commit ec0014f

Please sign in to comment.