Skip to content

A fork of this tool in Nim langΒ #3

@dmknght

Description

@dmknght

Hello Sandfly Security! I ported this tool into Nim lang and I just want to share my version to Sandfly :D From my test, it runs sightly faster (about 14 secs) and the binary size is more lightweight than the Golang version (205Kb normal build, 979Kb static build without any size optimization).

The source code

import os
import strutils
import strformat

const
  MAX_PID = 4194304

type
  PidStat = object
    pid: uint
    tgid: uint
    ppid: uint
    name: string
    exec: string


proc progress_bar_print(pid: int) =
  let progress = float(pid) / float(MAX_PID) * 100
  stdout.write(fmt"{progress:<2.2f}%")
  stdout.flushFile()


proc progress_bar_fush() =
  stdout.write("\e[2K\r")


proc show_process_status(pid_stat: PidStat, reason: string) =
  progress_bar_fush()
  echo "Reason: ", reason
  echo " PID: ", pid_stat.pid, " name: ", pid_stat.name
  echo " Binary: ", pid_stat.exec


proc attach_process(procfs: string, pid_stat: var PidStat): bool =
  try:
    pid_stat.exec = expandSymlink(procfs & "/exe")
  except:
    pid_stat.exec = ""

  try:
    for line in lines(procfs & "/status"):
      if line.startsWith("Name:"):
        pid_stat.name = line.split()[^1]
      elif line.startsWith("Pid:"):
        pid_stat.pid = parseUInt(line.split()[^1])
      elif line.startsWith("Tgid"):
        pid_stat.tgid = parseUInt(line.split()[^1])
      elif line.startsWith("PPid:"):
        pid_stat.ppid = parseUInt(line.split()[^1])
        return true
  except IOError:
    pid_stat.pid = parseUInt(splitPath(procfs).tail)
    return false


proc check_hidden(procfs: string): bool =
  var
    pid_stat: PidStat

  if not attach_process(procfs, pid_stat):
    show_process_status(pid_stat, "Hidden process: Prevent attaching status")
    return true

  if pid_stat.exec.endsWith("(deleted)"):
    show_process_status(pid_stat, "Fileless process: Binary was removed")
  if pid_stat.pid == pid_stat.tgid and pid_stat.ppid > 0:
    for kind, path in walkDir("/proc/"):
      if kind == pcDir and path == procfs:
        # proc is listed
        return false
    show_process_status(pid_stat, "Hidden process: Hide from ProcFS")
    return true
  else:
    return false

var
  has_hidden_process = false

for i in countup(1, MAX_PID):
  let
    procfs = "/proc/" & $i

  progress_bar_print(i)
  if dirExists(procfs) and check_hidden(procfs):
    has_hidden_process = true
  progress_bar_fush()


progress_bar_fush()
echo "Checking hidden processes completed"
if has_hidden_process:
  echo "Found hidden processes. Possibly rootkit?"
else:
  echo "No hidden processes found"

I'm researching on the method using Kernel's module to get the list of processes, then compare to the list of processes in the procfs. So the loop will decreased from MAX_PID * number_in_procfs to number_of_procs * number_in_procfs which could be a lot faster. This method has a limitation that it requires user to install kernel module to check. However, I think it will give more accurate result. I hope I can contribute my method to this tool to provide better tool in the future :D

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions